uc_recurring_hosted.paypal_ipn.inc in UC Recurring Payments and Subscriptions 6.2
Same filename and directory in other branches
Handle paypal IPN callbacks for recurring payments
File
modules/uc_recurring_hosted/uc_recurring_hosted.paypal_ipn.incView source
<?php
/**
* @file
* Handle paypal IPN callbacks for recurring payments
*/
/**
* Handle IPN callbacks for PayPal recurring payments
*/
function uc_recurring_hosted_paypal_ipn($order_id = NULL) {
// Assign posted variables to local variables
$ipn = (object) $_POST;
$ipn->received = time();
if (!$order_id) {
$order_id = $ipn->rp_invoice_id;
}
watchdog('uc_recurring_hosted', 'Receiving recurring IPN at URL for order @order_id. !debug', array(
'@order_id' => $order_id,
'!debug' => variable_get('uc_paypal_wps_debug_ipn', FALSE) ? '<pre>' . check_plain(print_r($_POST, TRUE)) . '</pre>' : '',
));
$order = _uc_recurring_hosted_paypal_ipn_order($ipn);
if ($order == FALSE) {
watchdog('uc_recurring_hosted', 'IPN attempted for non-existent order.', array(), WATCHDOG_ERROR);
return;
}
$ipn->order_id = $order->order_id;
// Log the IPN against the actual order if debug enabled.
if (variable_get('uc_paypal_wps_debug_ipn', FALSE)) {
uc_order_log_changes($order->order_id, array(
'<pre>' . print_r($ipn, TRUE) . '</pre>',
));
}
if (_uc_recurring_hosted_paypal_ipn_validate($ipn)) {
switch ($ipn->txn_type) {
case 'subscr_signup':
case 'recurring_payment_profile_created':
// First we need to create the recurring fee as paypal_wps overrides the
// submit order function where this normally happens.
uc_recurring_product_process_order($order);
// Subscriptions do not have a txn_id, so we will use the subscr_id.
$ipn->txn_id = $ipn->subscr_id;
break;
case 'subscr_payment':
case 'recurring_payment':
// If the order already has been paid for then this must be a renewal.
if (uc_payment_balance($order) <= 0) {
// Fetch fee from database.
$fees = uc_recurring_get_fees($order);
foreach ($fees as $fee) {
$fee->ipn = $ipn;
uc_recurring_renew($fee);
}
}
else {
// Process the first subscription payment.
_uc_recurring_hosted_paypal_ipn_payment($ipn);
}
break;
case 'subscr_failed':
// Calculate when the next retry will be and then extend.
$retry_at = strtotime(check_plain($_POST['retry_at']));
$fees = uc_recurring_get_fees($order);
foreach ($fees as $fee) {
$extend_seconds = $retry_at - time();
uc_recurring_extend_fee($fee, $extend_seconds);
}
return;
case 'recurring_payment_failed':
// Calculate when the next retry will be and then extend.
$next_payment_date = strtotime(check_plain($_POST['NEXTBILLINGDATE']));
$fees = uc_recurring_get_fees($order);
foreach ($fees as $fee) {
$extend_seconds = $next_payment_date - time();
uc_recurring_extend_fee($fee, $extend_seconds);
}
return;
case 'subscr_eot':
case 'subscr_cancel':
$fees = uc_recurring_get_fees($order);
if ($fee = $fees[0]) {
uc_recurring_fee_cancel($fee->rfid, $fee);
}
$ipn->txn_id = check_plain($_POST['subscr_id']);
// subscriptions do not have a txn_id, so we will record the subscr_id instead
break;
case 'recurring_payment_skipped':
$ipn->txn_id = $ipn->recurring_payment_id;
$fees = uc_recurring_get_fees($order);
foreach ($fees as $fee) {
$fee->ipn = $ipn;
uc_recurring_renew($fee);
}
break;
case 'recurring_payment_suspended_due_to_max_failed_payment':
$ipn->txn_id = $ipn->recurring_payment_id;
$fees = uc_recurring_get_fees($order);
foreach ($fees as $fee) {
uc_recurring_hosted_paypal_wpp_suspension_log($fee, $ipn);
}
break;
case 'recurring_payment_profile_cancel':
$fees = uc_recurring_get_fees($order);
if ($fee = $fees[0]) {
$ipn->txn_id = $ipn->recurring_payment_id;
if ($fee->remaining_intervals == -1) {
uc_recurring_fee_cancel($fee->rfid, $fee);
}
uc_order_comment_save($fee->order_id, NULL, t('PayPal confirmed cancellation of billing agreement.'));
}
break;
}
// For some reason paypal sets this as the new order id, which is a fail
$ipn->order_id = $order->order_id;
//save our ipn transaction
_uc_recurring_hosted_paypal_ipn_save($ipn);
}
return;
}
/**
* Handles payment information from IPN message.
*/
function _uc_recurring_hosted_paypal_ipn_payment($ipn) {
global $user;
$context = array(
'revision' => 'formatted-original',
'type' => 'amount',
);
$options = array(
'sign' => FALSE,
);
switch ($ipn->payment_status) {
case 'Canceled_Reversal':
uc_order_comment_save($ipn->order_id, 0, t('PayPal has cancelled the reversal and returned !amount !currency to your account.', array(
'!amount' => uc_price($ipn->mc_gross, $context, $options),
'!currency' => $ipn->mc_currency,
)), 'admin');
break;
case 'Completed':
$comment = t('PayPal transaction ID: @txn_id', array(
'@txn_id' => $ipn->txn_id,
));
uc_payment_enter($ipn->order_id, 'paypal_wps', $ipn->mc_gross, $user->uid, NULL, $comment);
uc_order_comment_save($ipn->order_id, 0, t('Payment of @amount @currency submitted through PayPal.', array(
'@amount' => uc_price($ipn->mc_gross, $context, $options),
'@currency' => $ipn->mc_currency,
)), 'order', 'payment_received');
uc_order_comment_save($ipn->order_id, 0, t('PayPal IPN reported a payment of @amount @currency.', array(
'@amount' => uc_price($ipn->mc_gross, $context, $options),
'@currency' => $ipn->mc_currency,
)));
break;
case 'Denied':
uc_order_comment_save($ipn->order_id, 0, t("You have denied the customer's payment."), 'admin');
break;
case 'Expired':
uc_order_comment_save($ipn->order_id, 0, t('The authorization has failed and cannot be captured.'), 'admin');
break;
case 'Failed':
uc_order_comment_save($ipn->order_id, 0, t("The customer's attempted payment from a bank account failed."), 'admin');
break;
case 'Pending':
uc_order_update_status($ipn->order_id, 'paypal_pending');
uc_order_comment_save($ipn->order_id, 0, t('Payment is pending at PayPal: @reason', array(
'@reason' => _uc_paypal_pending_message(check_plain($ipn->pending_reason)),
)), 'admin');
break;
// You, the merchant, refunded the payment.
case 'Refunded':
$comment = t('PayPal transaction ID: @txn_id', array(
'@txn_id' => $ipn->txn_id,
));
uc_payment_enter($ipn->order_id, 'paypal_wps', $ipn->mc_gross, $ipn->uid, NULL, $comment);
break;
case 'Reversed':
watchdog('uc_recurring_hosted', 'PayPal has reversed a payment!', array(), WATCHDOG_ERROR);
uc_order_comment_save($ipn->order_id, 0, t('Payment has been reversed by PayPal: @reason', array(
'@reason' => _uc_paypal_reversal_message($ipn->reason_code),
)), 'admin');
break;
case 'Processed':
uc_order_comment_save($ipn->order_id, 0, t('A payment has been accepted.'), 'admin');
break;
case 'Voided':
uc_order_comment_save($ipn->order_id, 0, t('The authorization has been voided.'), 'admin');
break;
}
}
/**
* Mock page for testing the Paypal verify call.
*/
function _uc_recurring_hosted_paypal_mock_web_page() {
echo 'VERIFIED';
exit;
}
// Helper functions for paypal IPN.
/**
* Loads the order object associated with either a WPS or WPP IPN.
*
* @return
* Ubercart Order object.
*/
function _uc_recurring_hosted_paypal_ipn_order($ipn) {
if (!isset($ipn->invoice) && !isset($ipn->rp_invoice_id)) {
watchdog('uc_recurring_hosted', 'IPN attempted with invalid order ID.', array(), WATCHDOG_ERROR);
return FALSE;
}
if (isset($ipn->rp_invoice_id)) {
// We are using wpp, use rp_invoice_id
$order_id = intval($_POST['rp_invoice_id']);
}
elseif (isset($ipn->invoice)) {
if (($len = strpos($_POST['invoice'], '-')) > 0) {
$order_id = intval(substr($_POST['invoice'], 0, $len));
}
else {
$order_id = intval($_POST['invoice']);
}
}
return uc_order_load($order_id);
}
/**
* Validate Paypal IPN.
*/
function _uc_recurring_hosted_paypal_ipn_validate($ipn) {
if (_uc_recurring_hosted_paypal_ipn_is_duplicate($ipn)) {
return FALSE;
}
// @todo: any reason we can't use http_build_query() here?
$req = '';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= $key . '=' . $value . '&';
}
$req .= 'cmd=_notify-validate';
if (variable_get('uc_paypal_wpp_server', '') == 'https://api-3t.paypal.com/nvp') {
$host = 'https://www.paypal.com/cgi-bin/webscr';
}
else {
$host = variable_get('uc_paypal_wps_server', 'https://www.sandbox.paypal.com/cgi-bin/webscr');
}
$response = drupal_http_request($host, array(), 'POST', $req);
// @todo: Change this to property_exists when we have a PHP requirement >= 5.1.
if (array_key_exists('error', $response)) {
watchdog('uc_recurring_hosted', 'IPN failed with HTTP error @error, code @code.', array(
'@error' => $response->error,
'@code' => $response->code,
), WATCHDOG_ERROR);
return FALSE;
}
if (strcmp($response->data, 'VERIFIED') == 0) {
watchdog('uc_recurring_hosted', 'PayPal Recurring IPN Transaction Verified.');
return TRUE;
}
elseif (strcmp($response->data, 'INVALID') == 0) {
watchdog('uc_recurring_hosted', 'IPN transaction failed verification.', array(), WATCHDOG_ERROR);
uc_order_comment_save($ipn->order_id, 0, t('An IPN transaction failed verification for this order.'), 'admin');
}
return FALSE;
}
/**
* Check if we have already recieved a IPN with the same details.
*/
function _uc_recurring_hosted_paypal_ipn_is_duplicate($ipn) {
if ($ipn->txn_id) {
$duplicate = db_result(db_query("SELECT COUNT(*) FROM {uc_payment_paypal_ipn} WHERE txn_id = '%s' AND txn_type = '%s' AND status <> 'Pending'", $ipn->txn_id, $ipn->txn_type));
if ($duplicate > 0) {
watchdog('uc_recurring_hosted', 'IPN (Order:@order_id Txn: @txn_id) has been processed before.', array(
'@order_id' => $ipn->order_id,
'@txn_id' => $ipn->txn_id,
), WATCHDOG_NOTICE);
return TRUE;
}
}
return FALSE;
}
/**
* Paypal IPN save function.
*/
function _uc_recurring_hosted_paypal_ipn_save($ipn) {
drupal_write_record('uc_payment_paypal_ipn', $ipn);
}
Functions
Name | Description |
---|---|
uc_recurring_hosted_paypal_ipn | Handle IPN callbacks for PayPal recurring payments |
_uc_recurring_hosted_paypal_ipn_is_duplicate | Check if we have already recieved a IPN with the same details. |
_uc_recurring_hosted_paypal_ipn_order | Loads the order object associated with either a WPS or WPP IPN. |
_uc_recurring_hosted_paypal_ipn_payment | Handles payment information from IPN message. |
_uc_recurring_hosted_paypal_ipn_save | Paypal IPN save function. |
_uc_recurring_hosted_paypal_ipn_validate | Validate Paypal IPN. |
_uc_recurring_hosted_paypal_mock_web_page | Mock page for testing the Paypal verify call. |