Skip to content

Commit 5639f37

Browse files
Improve support for recurring billing & tokens
1 parent 38d5650 commit 5639f37

File tree

3 files changed

+83
-16
lines changed

3 files changed

+83
-16
lines changed

CRM/Core/Payment/OmnipayMultiProcessor.php

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -140,21 +140,44 @@ public function doPayment(&$params, $component = 'contribute') {
140140
$this->saveBillingAddressIfRequired($params);
141141

142142
try {
143-
if (!empty($params['is_recur'])) {
144-
$response = $this->gateway->createCard($this->getCreditCardOptions(array_merge($params, array('action' => 'Purchase')), $component))->send();
145-
}
146-
elseif (!empty($params['token'])) {
143+
if (!empty($params['token'])) {
144+
// If it is not recurring we will have succeeded in an Authorize so we should capture.
145+
// The only recurring currently working with is_recur + pre-authorize is eWay rapid
146+
// and, at least in that case, the createCreditCard call ignores any attempt to authorise.
147+
// that is likely to be a pattern.
148+
$action = CRM_Utils_Array::value('payment_action', $params, empty($params['is_recur']) ? 'capture' : 'purchase');
147149
$params['transactionReference'] = ($params['token']);
148-
$response = $this->gateway->capture($this->getCreditCardOptions($params, $component))
150+
$response = $this->gateway->$action($this->getCreditCardOptions($params))
149151
->send();
150152
}
153+
elseif (!empty($params['is_recur'])) {
154+
$response = $this->gateway->createCard($this->getCreditCardOptions(array_merge($params, array('action' => 'Purchase')), $component))->send();
155+
}
151156
else {
152-
$response = $this->gateway->purchase($this->getCreditCardOptions($params, $component))
157+
$response = $this->gateway->purchase($this->getCreditCardOptions($params))
153158
->send();
154159
}
155160
if ($response->isSuccessful()) {
156161
// mark order as complete
162+
if (!empty($params['is_recur'])) {
163+
$paymentToken = civicrm_api3('PaymentToken', 'create', array(
164+
'contact_id' => $params['contactID'],
165+
'token' => $params['token'],
166+
'payment_processor_id' => $this->_paymentProcessor['id'],
167+
'created_id' => CRM_Core_Session::getLoggedInContactID(),
168+
'email' => $params['email'],
169+
'billing_first_name' => $params['billing_first_name'],
170+
'billing_middle_name' => $params['billing_middle_name'],
171+
'billing_last_name' => $params['billing_last_name'],
172+
'expiry_date' => date("Y-m-t", strtotime($params['credit_card_exp_date']['Y'] . '-' . $params['credit_card_exp_date']['M'])),
173+
'masked_account_number' => $this->getMaskedCreditCardNumber($params),
174+
'ip_address' => CRM_Utils_System::ipAddress(),
175+
));
176+
civicrm_api3('ContributionRecur', 'create', array('id' => $params['contributionRecurID'], 'payment_token_id' => $paymentToken['id']));
177+
}
157178
$params['trxn_id'] = $response->getTransactionReference();
179+
$params['payment_status_id'] = 1;
180+
// @todo fetch masked card, card type, card expiry from params. Eway def provides these.
158181
//gross_amount ? fee_amount?
159182
return $params;
160183
}
@@ -343,6 +366,10 @@ private function getCreditCardObjectParams($params) {
343366
$cardFields[$cardField] = isset($params[$civicrmField]) ? $params[$civicrmField] : '';
344367
}
345368

369+
// Compensate for some unreliability in calling function, especially from pre-Approval.
370+
if (empty($cardFields['billingCountry']) && isset($params['billing_country_id-' . $billingID])) {
371+
$cardFields['billingCountry'] = $params['billing_country_id-' . $billingID];
372+
}
346373
if (empty($cardFields['email'])) {
347374
if (!empty($params['email-' . $billingID])) {
348375
$cardFields['email'] = $params['email-' . $billingID];
@@ -394,11 +421,10 @@ private function getSensitiveCreditCardObjectOptions($params) {
394421
* Get options for credit card.
395422
*
396423
* @param array $params
397-
* @param string $component
398424
*
399425
* @return array
400426
*/
401-
private function getCreditCardOptions($params, $component) {
427+
private function getCreditCardOptions($params) {
402428
// Contribution page in 4.4 passes amount - not sure which passes total_amount if any.
403429
if (isset($params['total_amount'])) {
404430
$amount = (float) CRM_Utils_Rule::cleanMoney($params['total_amount']);
@@ -628,6 +654,10 @@ private function getCorePaymentFields() {
628654
* @return array
629655
*/
630656
public function getBillingAddressFields($billingLocationID = NULL) {
657+
$fields = $this->getProcessorTypeMetadata('fields');
658+
if (isset ($fields['billing_fields'])) {
659+
return $fields['billing_fields'];
660+
}
631661
if (!$this->isTransparentRedirect()) {
632662
return parent::getBillingAddressFields($billingLocationID);
633663
}
@@ -896,8 +926,7 @@ protected function supportsFutureRecurStartDate() {
896926
* @return bool
897927
*/
898928
protected function supportsPreApproval() {
899-
$this->getProcessorTypeMetadata('supports_preapproval');
900-
return FALSE;
929+
return $this->getProcessorTypeMetadata('supports_preapproval');
901930
}
902931

903932
/**
@@ -913,7 +942,7 @@ protected function supportsPreApproval() {
913942
* @return array
914943
*/
915944
public function doPreApproval(&$params) {
916-
$this->_component = 'contribute';
945+
$this->_component = $params['component'];
917946
$this->ensurePaymentProcessorTypeIsSet();
918947
$this->gateway = Omnipay::create(str_replace('omnipay_', '', $this->_paymentProcessor['payment_processor_type']));
919948
$this->setProcessorFields();
@@ -922,11 +951,20 @@ public function doPreApproval(&$params) {
922951
$this->saveBillingAddressIfRequired($params);
923952

924953
try {
925-
$response = $this->gateway->authorize($this->getCreditCardOptions($params, 'contribute'))
926-
->send();
954+
if (!empty($params['is_recur'])) {
955+
$response = $this->gateway->createCard($this->getCreditCardOptions(array_merge($params, array('action' => 'Authorize'))))->send();
956+
}
957+
else {
958+
$response = $this->gateway->authorize($this->getCreditCardOptions($params))
959+
->send();
960+
}
927961
if ($response->isSuccessful()) {
928962
$params['trxn_id'] = $params['token'] = $response->getTransactionReference();
929-
$creditCardPan = '************' . substr($params['credit_card_number'], -4);
963+
if (!empty($params['is_recur'])) {
964+
$params['token'] = $response->getCardReference();
965+
}
966+
967+
$creditCardPan = $this->getMaskedCreditCardNumber($params);
930968
foreach ($_SESSION as $key => $value) {
931969
if (isset($value['values'])) {
932970
foreach ($value['values'] as $pageName => $pageValues) {
@@ -941,7 +979,7 @@ public function doPreApproval(&$params) {
941979
unset($params['credit_card_number']);
942980
unset($params['cvv2']);
943981
return array(
944-
'pre_approval_parameters' => array('token' => $response->getTransactionReference())
982+
'pre_approval_parameters' => array('token' => $params['token'])
945983
);
946984
}
947985
else {
@@ -1076,5 +1114,30 @@ protected function getProcessorTypeMetadata($parameter) {
10761114
return FALSE;
10771115
}
10781116

1117+
/**
1118+
* @param $params
1119+
* @return string
1120+
*/
1121+
protected function getMaskedCreditCardNumber(&$params) {
1122+
$creditCardPan = '************' . substr($params['credit_card_number'], -4);
1123+
return $creditCardPan;
1124+
}
1125+
1126+
/**
1127+
* Default payment instrument validation.
1128+
*
1129+
* Implement the usual Luhn algorithm via a static function in the CRM_Core_Payment_Form if it's a credit card
1130+
* Not a static function, because I need to check for payment_type.
1131+
*
1132+
* @param array $values
1133+
* @param array $errors
1134+
*/
1135+
public function validatePaymentInstrument($values, &$errors) {
1136+
CRM_Core_Form::validateMandatoryFields($this->getMandatoryFields(), $values, $errors);
1137+
if ($this->_paymentProcessor['payment_type'] == 1) {
1138+
CRM_Core_Payment_Form::validateCreditCard($values, $errors, $this->_paymentProcessor['id']);
1139+
}
1140+
}
1141+
10791142
}
10801143

api/v3/Job/ProcessRecurring.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ function civicrm_api3_job_process_recurring($params) {
4646
'return' => 'token',
4747
)),
4848
));
49+
$payment = reset($payment['values']);
4950

5051
civicrm_api3('Contribution', 'completetransaction', array(
5152
'id' => $pending['id'],

api/v3/PaymentProcessor/Pay.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function civicrm_api3_payment_processor_pay($params) {
1515
if (is_a($result, 'CRM_Core_Error')) {
1616
throw API_Exception('Payment failed');
1717
}
18-
return civicrm_api3_create_success($result, $params);
18+
return civicrm_api3_create_success(array($result), $params);
1919
}
2020

2121
/**
@@ -28,4 +28,7 @@ function civicrm_api3_payment_processor_pay($params) {
2828
function _civicrm_api3_payment_processor_pay_spec(&$params) {
2929
$params['payment_processor_id']['api.required'] = 1;
3030
$params['amount']['api.required'] = 1;
31+
$params['payment_action'] = array(
32+
'api.default' => 'purchase',
33+
);
3134
}

0 commit comments

Comments
 (0)