Skip to content
Snippets Groups Projects
Commit cf4e17a3 authored by mattwire's avatar mattwire
Browse files

Fix and record paymentIntents for recurring contributions - show...

Fix and record paymentIntents for recurring contributions - show authentication to user on thankyou page
parent 5659c61e
No related branches found
No related tags found
No related merge requests found
......@@ -521,65 +521,21 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
$intentParams['amount'] = $this->getAmount($params);
//$this->handleError('Amount differs', E::ts('Amount is different from the authorised amount (%1, %2)', [1 => $intent->amount, 2=> $this->getAmount($params)]), $params['stripe_error_url']);
}
$intent = \Stripe\PaymentIntent::update($params['paymentIntentID'], $intentParams);
if ($intent->status === 'requires_confirmation') {
$intent->confirm();
}
switch ($intent->status) {
case 'requires_capture':
$intent->capture();
// Return fees & net amount for Civi reporting.
$stripeCharge = $intent->charges->data[0];
try {
$stripeBalanceTransaction = \Stripe\BalanceTransaction::retrieve($stripeCharge->balance_transaction);
}
catch (Exception $e) {
$err = self::parseStripeException('retrieve_balance_transaction', $e, FALSE);
$errorMessage = $this->handleErrorNotification($err, $params['stripe_error_url']);
throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Balance Transaction: ' . $errorMessage);
}
$newParams['fee_amount'] = $stripeBalanceTransaction->fee / 100;
$newParams['net_amount'] = $stripeBalanceTransaction->net / 100;
// Success!
// Set the desired contribution status which will be set later (do not set on the contribution here!)
$params['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
case 'requires_action':
\Civi::$statics['paymentIntentID'] = $params['paymentIntentID'];
if ((boolean) \Civi::settings()->get('stripe_oneoffreceipt')) {
// Send a receipt from Stripe - we have to set the receipt_email after the charge has been captured,
// as the customer receives an email as soon as receipt_email is updated and would receive two if we updated before capture.
\Stripe\PaymentIntent::update($params['paymentIntentID'], ['receipt_email' => $email]);
}
break;
}
$intent = \Stripe\PaymentIntent::update($intent->id, $intentParams);
}
catch (Exception $e) {
$this->handleError($e->getCode(), $e->getMessage(), $params['stripe_error_url']);
}
// Update the paymentIntent in the CiviCRM database for later tracking
$intentParams = [
'paymentintent_id' => $intent->id,
'payment_processor_id' => $this->_paymentProcessor['id'],
'status' => $intent->status,
'contribution_id' => $this->getContributionId($params),
'description' => $intentParams['description'],
'identifier' => $params['qfKey'],
'contact_id' => $this->getContactId($params),
];
if (empty($intentParams['contribution_id'])) {
$intentParams['flags'][] = 'NC';
list($params, $newParams) = $this->processPaymentIntent($params, $intent);
// For a single charge there is no stripe invoice, we set OrderID to the ChargeID.
if (empty($this->getPaymentProcessorOrderID())) {
$this->setPaymentProcessorOrderID($this->getPaymentProcessorTrxnID());
}
CRM_Stripe_BAO_StripePaymentintent::create($intentParams);
// For contribution workflow we have a contributionId so we can set parameters directly.
// For events/membership workflow we have to return the parameters and they might get set...
// For a single charge there is no stripe invoice.
$this->setPaymentProcessorOrderID($stripeCharge->id);
$this->setPaymentProcessorTrxnID($stripeCharge->id);
return $this->endDoPayment($params, $newParams);
}
......@@ -655,10 +611,86 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
// Update the recurring payment
civicrm_api3('ContributionRecur', 'create', $recurParams);
// Get the paymentIntent for the latest invoice
$intent = $stripeSubscription->latest_invoice['payment_intent'];
list($params, $newParams) = $this->processPaymentIntent($params, $intent);
// Set the orderID (trxn_id) to the invoice ID
// The IPN will change it to the charge_id
$this->setPaymentProcessorOrderID($stripeSubscription->latest_invoice['id']);
return $this->endDoPayment($params);
return $this->endDoPayment($params, $newParams);
}
/**
* This performs the processing and recording of the paymentIntent for both recurring and non-recurring payments
* @param array $params
* @param \Stripe\PaymentIntent $intent
*
* @return array [$params, $newParams]
*/
private function processPaymentIntent($params, $intent) {
$contactId = $this->getContactId($params);
$email = $this->getBillingEmail($params, $contactId);
$newParams = [];
try {
if ($intent->status === 'requires_confirmation') {
$intent->confirm();
}
switch ($intent->status) {
case 'requires_capture':
$intent->capture();
// Return fees & net amount for Civi reporting.
$stripeCharge = $intent->charges->data[0];
try {
$stripeBalanceTransaction = \Stripe\BalanceTransaction::retrieve($stripeCharge->balance_transaction);
}
catch (Exception $e) {
$err = self::parseStripeException('retrieve_balance_transaction', $e, FALSE);
$errorMessage = $this->handleErrorNotification($err, $params['stripe_error_url']);
throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to retrieve Stripe Balance Transaction: ' . $errorMessage);
}
$newParams['fee_amount'] = $stripeBalanceTransaction->fee / 100;
$newParams['net_amount'] = $stripeBalanceTransaction->net / 100;
// Success!
// Set the desired contribution status which will be set later (do not set on the contribution here!)
$params['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
// Transaction ID is always stripe Charge ID.
$this->setPaymentProcessorTrxnID($stripeCharge->id);
case 'requires_action':
// We fall through to this in requires_capture / requires_action so we always set a receipt_email
if ((boolean) \Civi::settings()->get('stripe_oneoffreceipt')) {
// Send a receipt from Stripe - we have to set the receipt_email after the charge has been captured,
// as the customer receives an email as soon as receipt_email is updated and would receive two if we updated before capture.
\Stripe\PaymentIntent::update($intent->id, ['receipt_email' => $email]);
}
break;
}
}
catch (Exception $e) {
$this->handleError($e->getCode(), $e->getMessage(), $params['stripe_error_url']);
}
// Update the paymentIntent in the CiviCRM database for later tracking
$intentParams = [
'paymentintent_id' => $intent->id,
'payment_processor_id' => $this->_paymentProcessor['id'],
'status' => $intent->status,
'contribution_id' => $this->getContributionId($params),
'description' => $this->getDescription($params, 'description'),
'identifier' => $params['qfKey'],
'contact_id' => $this->getContactId($params),
];
if (empty($intentParams['contribution_id'])) {
$intentParams['flags'][] = 'NC';
}
CRM_Stripe_BAO_StripePaymentintent::create($intentParams);
return [$params, $newParams];
}
/**
......
......@@ -8,9 +8,11 @@ CRM.$(function($) {
debugging('CRM.vars.stripe not defined! Not a Stripe processor?');
return;
}
if (CRM.vars.stripe.paymentIntentStatus === 'succeeded') {
debugging('already succeeded');
return;
switch (CRM.vars.stripe.paymentIntentStatus) {
case 'succeeded':
case 'cancelled':
debugging('paymentIntent: ' . CRM.vars.stripe.paymentIntentStatus);
return;
}
checkAndLoad();
......@@ -43,17 +45,37 @@ CRM.$(function($) {
}
function handleAction(response) {
stripe.handleCardAction(response.payment_intent_client_secret)
.then(function(result) {
if (result.error) {
// Show error in payment form
handleCardConfirm();
} else {
// The card action has been handled
debugging('card action success');
handleCardConfirm();
}
});
switch (CRM.vars.stripe.paymentIntentMethod) {
case 'automatic':
stripe.handleCardPayment(response.payment_intent_client_secret)
.then(function (result) {
if (result.error) {
// Show error in payment form
handleCardConfirm();
}
else {
// The card action has been handled
debugging('card payment success');
handleCardConfirm();
}
});
break;
case 'manual':
stripe.handleCardAction(response.payment_intent_client_secret)
.then(function (result) {
if (result.error) {
// Show error in payment form
handleCardConfirm();
}
else {
// The card action has been handled
debugging('card action success');
handleCardConfirm();
}
});
break;
}
}
function handleCardConfirm() {
......
CRM.$(function(g){f("civicrmStripeConfirm loaded");if(typeof CRM.vars.stripe==="undefined"){f("CRM.vars.stripe not defined! Not a Stripe processor?");return}if(CRM.vars.stripe.paymentIntentStatus==="succeeded"){f("already succeeded");return}a();if(typeof h==="undefined"){h=Stripe(CRM.vars.stripe.publishableKey)}c();var h;var e=false;window.onbeforeunload=null;function d(i){f("handleServerResponse");if(i.error){}else{if(i.requires_action){b(i)}else{f("success - payment captured")}}}function b(i){h.handleCardAction(i.payment_intent_client_secret).then(function(j){if(j.error){c()}else{f("card action success");c()}})}function c(){f("handle card confirm");var i=CRM.url("civicrm/stripe/confirm-payment");g.post(i,{payment_intent_id:CRM.vars.stripe.paymentIntentID,capture:true,id:CRM.vars.stripe.id}).then(function(j){d(j)})}function a(){if(typeof Stripe==="undefined"){if(e){return}e=true;f("Stripe.js is not loaded!");g.getScript("https://js.stripe.com/v3",function(){f("Script loaded and executed.");e=false})}}function f(i){if((typeof(CRM.vars.stripe)==="undefined")||(Boolean(CRM.vars.stripe.jsDebug)===true)){console.log(new Date().toISOString()+" civicrm_stripe.js: "+i)}}});
\ No newline at end of file
CRM.$(function(g){f("civicrmStripeConfirm loaded");if(typeof CRM.vars.stripe==="undefined"){f("CRM.vars.stripe not defined! Not a Stripe processor?");return}switch(CRM.vars.stripe.paymentIntentStatus){case"succeeded":case"cancelled":f("paymentIntent: ".CRM.vars.stripe.paymentIntentStatus);return}a();if(typeof h==="undefined"){h=Stripe(CRM.vars.stripe.publishableKey)}c();var h;var e=false;window.onbeforeunload=null;function d(i){f("handleServerResponse");if(i.error){}else{if(i.requires_action){b(i)}else{f("success - payment captured")}}}function b(i){switch(CRM.vars.stripe.paymentIntentMethod){case"automatic":h.handleCardPayment(i.payment_intent_client_secret).then(function(j){if(j.error){c()}else{f("card payment success");c()}});break;case"manual":h.handleCardAction(i.payment_intent_client_secret).then(function(j){if(j.error){c()}else{f("card action success");c()}});break}}function c(){f("handle card confirm");var i=CRM.url("civicrm/stripe/confirm-payment");g.post(i,{payment_intent_id:CRM.vars.stripe.paymentIntentID,capture:true,id:CRM.vars.stripe.id}).then(function(j){d(j)})}function a(){if(typeof Stripe==="undefined"){if(e){return}e=true;f("Stripe.js is not loaded!");g.getScript("https://js.stripe.com/v3",function(){f("Script loaded and executed.");e=false})}}function f(i){if((typeof(CRM.vars.stripe)==="undefined")||(Boolean(CRM.vars.stripe.jsDebug)===true)){console.log(new Date().toISOString()+" civicrm_stripe.js: "+i)}}});
\ No newline at end of file
......@@ -171,14 +171,27 @@ function stripe_civicrm_buildForm($formName, &$form) {
}
}
$jsVars = [
'id' => $form->_paymentProcessor['id'],
'paymentIntentID' => $paymentIntent['paymentintent_id'],
'paymentIntentStatus' => $paymentIntent['status'],
'publishableKey' => CRM_Core_Payment_Stripe::getPublicKeyById($form->_paymentProcessor['id']),
'jsDebug' => (boolean) \Civi::settings()->get('stripe_jsdebug'),
];
\Civi::resources()->addVars(E::SHORT_NAME, $jsVars);
/** @var \CRM_Core_Payment_Stripe $paymentProcessor */
$paymentProcessor = \Civi\Payment\System::singleton()->getById($form->_paymentProcessor['id']);
$paymentProcessor->setAPIParams();
try {
$intent = \Stripe\PaymentIntent::retrieve($paymentIntent['paymentintent_id']);
if (!in_array($intent->status, ['succeeded', 'cancelled'])) {
// We need the confirmation_method to decide whether to use handleCardAction (manual) or handleCardPayment (automatic) on the js side
$jsVars = [
'id' => $form->_paymentProcessor['id'],
'paymentIntentID' => $paymentIntent['paymentintent_id'],
'paymentIntentStatus' => $intent->status,
'paymentIntentMethod' => $intent->confirmation_method,
'publishableKey' => CRM_Core_Payment_Stripe::getPublicKeyById($form->_paymentProcessor['id']),
'jsDebug' => (boolean) \Civi::settings()->get('stripe_jsdebug'),
];
\Civi::resources()->addVars(E::SHORT_NAME, $jsVars);
}
}
catch (Exception $e) {
// Do nothing, we won't attempt further stripe processing
}
break;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment