Skip to content

Commit

Permalink
Merge pull request #12091 from eileenmcnaughton/paypal_exp
Browse files Browse the repository at this point in the history
dev/financial#14 Fix enotices & inability to cancel recurring on paypal express
  • Loading branch information
seamuslee001 authored Jun 8, 2018
2 parents c5b63c7 + 6439290 commit 38a6a10
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 28 deletions.
30 changes: 14 additions & 16 deletions CRM/Core/Payment/PayPalImpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,6 @@ public function doExpressCheckout(&$params) {
*/
public function createRecurringPayments(&$params) {
$args = array();
// @todo this function is riddled with enotices - perhaps use $this->mapPaypalParamsToCivicrmParams($fieldMap, $result)
$this->initialize($args, 'CreateRecurringPaymentsProfile');

$start_time = strtotime(date('m/d/Y'));
Expand All @@ -424,15 +423,12 @@ public function createRecurringPayments(&$params) {
$args['currencyCode'] = $params['currencyID'];
$args['payerID'] = $params['payer_id'];
$args['invnum'] = $params['invoiceID'];
$args['returnURL'] = $params['returnURL'];
$args['cancelURL'] = $params['cancelURL'];
$args['profilestartdate'] = $start_date;
$args['method'] = 'CreateRecurringPaymentsProfile';
$args['billingfrequency'] = $params['frequency_interval'];
$args['billingperiod'] = ucwords($params['frequency_unit']);
$args['desc'] = $params['amount'] . " Per " . $params['frequency_interval'] . " " . $params['frequency_unit'];
//$args['desc'] = 'Recurring Contribution';
$args['totalbillingcycles'] = $params['installments'];
$args['totalbillingcycles'] = CRM_Utils_Array::value('installments', $params);
$args['version'] = '56.0';
$args['profilereference'] = "i={$params['invoiceID']}" .
"&m=" .
Expand All @@ -450,16 +446,18 @@ public function createRecurringPayments(&$params) {
return $result;
}

/* Success */
$params['trxn_id'] = $result['transactionid'];
$params['gross_amount'] = $result['amt'];
$params['fee_amount'] = $result['feeamt'];
$params['net_amount'] = $result['settleamt'];
if ($params['net_amount'] == 0 && $params['fee_amount'] != 0) {
$params['net_amount'] = number_format(($params['gross_amount'] - $params['fee_amount']), 2);
}
$params['payment_status'] = $result['paymentstatus'];
$params['pending_reason'] = $result['pendingreason'];
/* Success - result looks like"
* array (
* 'profileid' => 'I-CP1U0PLG91R2',
* 'profilestatus' => 'ActiveProfile',
* 'timestamp' => '2018-05-07T03:55:52Z',
* 'correlationid' => 'e717999e9bf62',
* 'ack' => 'Success',
* 'version' => '56.0',
* 'build' => '39949200',)
*/
$params['trxn_id'] = $result['profileid'];
$params['payment_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending');

return $params;
}
Expand Down Expand Up @@ -721,7 +719,7 @@ public function isSuppressSubmitButtons() {
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public function cancelSubscription(&$message = '', $params = array()) {
if ($this->isPayPalType($this::PAYPAL_PRO)) {
if ($this->isPayPalType($this::PAYPAL_PRO) || $this->isPayPalType($this::PAYPAL_EXPRESS)) {
$args = array();
$this->initialize($args, 'ManageRecurringPaymentsProfileStatus');

Expand Down
23 changes: 13 additions & 10 deletions CRM/Core/Payment/PayPalProIPN.php
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ public function getInput(&$input, &$ids) {

/**
* Handle payment express IPNs.
*
* For one off IPNS no actual response is required
* Recurring is more difficult as we have limited confirmation material
* lets look up invoice id in recur_contribution & rely on the unique transaction id to ensure no
Expand All @@ -560,23 +561,25 @@ public function handlePaymentExpress() {
// as membership id etc can be derived by the load objects fn
$objects = $ids = $input = array();
$isFirst = FALSE;
$input['invoice'] = self::getValue('i', FALSE);
$input['txnType'] = $this->retrieve('txn_type', 'String');
if ($input['txnType'] != 'recurring_payment') {
$contributionRecur = civicrm_api3('contribution_recur', 'getsingle', array(
'return' => 'contact_id, id, payment_processor_id',
'invoice_id' => $input['invoice'],
));

if ($input['txnType'] !== 'recurring_payment' && $input['txnType'] !== 'recurring_payment_profile_created') {
throw new CRM_Core_Exception('Paypal IPNS not handled other than recurring_payments');
}
$input['invoice'] = self::getValue('i', FALSE);

$this->getInput($input, $ids);
if ($this->transactionExists($input['trxn_id'])) {
if ($input['txnType'] === 'recurring_payment' && $this->transactionExists($input['trxn_id'])) {
throw new CRM_Core_Exception('This transaction has already been processed');
}

$contributionRecur = civicrm_api3('contribution_recur', 'getsingle', array(
'return' => 'contact_id, id',
'invoice_id' => $input['invoice'],
));
$ids['contact'] = $contributionRecur['contact_id'];
$ids['contributionRecur'] = $contributionRecur['id'];
$result = civicrm_api3('contribution', 'getsingle', array('invoice_id' => $input['invoice']));
$result = civicrm_api3('contribution', 'getsingle', ['invoice_id' => $input['invoice'], 'contribution_test' => '']);

$ids['contribution'] = $result['id'];
//@todo hard - coding 'pending' for now
Expand All @@ -595,12 +598,12 @@ public function handlePaymentExpress() {
// membership would be an easy add - but not relevant to my customer...
$this->_component = $input['component'] = 'contribute';
$input['trxn_date'] = date('Y-m-d-H-i-s', strtotime(self::retrieve('time_created', 'String')));
$paymentProcessorID = self::getPayPalPaymentProcessorID();
$paymentProcessorID = $contributionRecur['payment_processor_id'];

if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
throw new CRM_Core_Exception('Data did not validate');
}
return $this->recur($input, $ids, $objects, $isFirst);
$this->recur($input, $ids, $objects, $isFirst);
}

/**
Expand Down
58 changes: 56 additions & 2 deletions tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ public function getSubsequentPaypalExpressTransaction() {
'amount_per_cycle' => '5.00',
'payer_status' => 'unverified',
'currency_code' => 'USD',
'business' => 'mpa@mainepeoplesalliance.org',
'business' => 'mpa@example.com',
'address_country' => 'UNITED STATES',
'address_city' => 'Limestone',
'verify_sign' => 'AXi4DULbes8quzIiq2YNsdTJH5ciPPPzG9PcQvkQg4BjfvWi8aY9GgDb',
Expand All @@ -297,7 +297,7 @@ public function getSubsequentPaypalExpressTransaction() {
'payment_type' => 'instant',
'last_name' => 'Morrissette',
'address_state' => 'ME',
'receiver_email' => 'info@civicrm.org',
'receiver_email' => 'info@example.com',
'payment_fee' => '0.41',
'receiver_id' => 'GTH8P7UQWWTY6',
'txn_type' => 'recurring_payment',
Expand Down Expand Up @@ -371,4 +371,58 @@ public function getPaypalProRecurSubsequentTransaction() {
return array_merge($this->getPaypalProRecurTransaction(), array('txn_id' => 'secondone'));
}

/**
* Test IPN response update for a paypal express profile creation confirmation.
*/
public function testIPNPaymentExpressRecurSuccess() {
$this->setupRecurringPaymentProcessorTransaction(['processor_id' => '']);
$paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalExpressRecurSubscriptionConfirmation());
$paypalIPN->main();
$contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $this->_contributionRecurID));
$this->assertEquals('I-JW77S1PY2032', $contributionRecur['processor_id']);
}

/**
* Get response consistent with creating a new profile.
*
* @return array
*/
public function getPaypalExpressRecurSubscriptionConfirmation() {
return [
'payment_cycle' => 'Monthly',
'txn_type' => 'recurring_payment_profile_created',
'last_name' => 'buyer',
'next_payment_date' => '03:00:00 May 09, 2018 PDT',
'residence_country' => 'GB',
'initial_payment_amount' => '0.00',
'rp_invoice_id' => 'i=' . $this->_invoiceID
. '&m=&c=' . $this->_contributionID
. '&r=' . $this->_contributionRecurID
. '&b=' . $this->_contactID
. '&p=' . $this->_contributionPageID,
'currency_code' => 'GBP',
'time_created' => '12:39:01 May 09, 2018 PDT',
'verify_sign' => 'AUg223oCjn4HgJXKkrICawXQ3fyUA2gAd1.f1IPJ4r.9sln-nWcB-EJG',
'period_type' => 'Regular',
'payer_status' => 'verified',
'test_ipn' => '1',
'tax' => '0.00',
'payer_email' => 'payer@example.com',
'first_name' => 'test',
'receiver_email' => 'shop@example.com',
'payer_id' => 'BWXXXM8111HDS',
'product_type' => 1,
'shipping' => '0.00',
'amount_per_cycle' => '6.00',
'profile_status' => 'Active',
'charset' => 'windows-1252',
'notify_version' => '3.9',
'amount' => '6.00',
'outstanding_balance' => '0.00',
'recurring_payment_id' => 'I-JW77S1PY2032',
'product_name' => '6 Per 1 month',
'ipn_track_id' => '6255554274055',
];
}

}

0 comments on commit 38a6a10

Please sign in to comment.