[FIX] payment_stripe: fix post refactoring issues
- The acquirer reference was never saved. - Transactions with operation 'offline' were not processed. - If Stripe rejected an offline payment, an exception would be raised. This lead to Subscriptions assuming that the processing itself failed and prevented the transaction to be processed. Instead, the exception is now caught and the transaction's state set to 'error'. task-2494916 closes odoo/odoo#71602 X-original-commit: e93b3b3c5b9bc86768a5d26ed456ef11cfb40734 Related: odoo/enterprise#18680 Signed-off-by: Antoine Vandevenne (anv) <AntoineVDV@users.noreply.github.com>
This commit is contained in:
@@ -118,7 +118,8 @@ class StripeController(http.Controller):
|
||||
_logger.exception("unable to handle the event data; skipping to acknowledge")
|
||||
return ''
|
||||
|
||||
def _include_payment_intent_in_feedback_data(self, payment_intent, data):
|
||||
@staticmethod
|
||||
def _include_payment_intent_in_feedback_data(payment_intent, data):
|
||||
data.update({'payment_intent': payment_intent})
|
||||
if payment_intent.get('charges', {}).get('total_count', 0) > 0:
|
||||
charge = payment_intent['charges']['data'][0] # Use the latest charge object
|
||||
@@ -127,7 +128,8 @@ class StripeController(http.Controller):
|
||||
'payment_method': charge.get('payment_method_details'),
|
||||
})
|
||||
|
||||
def _include_setup_intent_in_feedback_data(self, setup_intent, data):
|
||||
@staticmethod
|
||||
def _include_setup_intent_in_feedback_data(setup_intent, data):
|
||||
data.update({
|
||||
'setup_intent': setup_intent,
|
||||
'payment_method': setup_intent.get('payment_method')
|
||||
|
||||
@@ -39,7 +39,7 @@ class PaymentAcquirer(models.Model):
|
||||
|
||||
return 1.0
|
||||
|
||||
def _stripe_make_request(self, endpoint, payload=None, method='POST'):
|
||||
def _stripe_make_request(self, endpoint, payload=None, method='POST', offline=False):
|
||||
""" Make a request to Stripe API at the specified endpoint.
|
||||
|
||||
Note: self.ensure_one()
|
||||
@@ -47,6 +47,7 @@ class PaymentAcquirer(models.Model):
|
||||
:param str endpoint: The endpoint to be reached by the request
|
||||
:param dict payload: The payload of the request
|
||||
:param str method: The HTTP method of the request
|
||||
:param bool offline: Whether the operation of the transaction being processed is 'offline'
|
||||
:return The JSON-formatted content of the response
|
||||
:rtype: dict
|
||||
:raise: ValidationError if an HTTP error occurs
|
||||
@@ -63,7 +64,9 @@ class PaymentAcquirer(models.Model):
|
||||
# Stripe can send 4XX errors for payment failures (not only for badly-formed requests).
|
||||
# Check if an error code is present in the response content and raise only if not.
|
||||
# See https://stripe.com/docs/error-codes.
|
||||
# If the request originates from an offline operation, don't raise and return the resp.
|
||||
if not response.ok \
|
||||
and not offline \
|
||||
and 400 <= response.status_code < 500 \
|
||||
and response.json().get('error'): # The 'code' entry is sometimes missing
|
||||
try:
|
||||
|
||||
@@ -165,31 +165,45 @@ class PaymentTransaction(models.Model):
|
||||
raise UserError("Stripe: " + _("The transaction is not linked to a token."))
|
||||
|
||||
payment_intent = self._stripe_create_payment_intent()
|
||||
feedback_data = {
|
||||
'reference': self.reference,
|
||||
'payment_intent': payment_intent,
|
||||
}
|
||||
feedback_data = {'reference': self.reference}
|
||||
StripeController._include_payment_intent_in_feedback_data(payment_intent, feedback_data)
|
||||
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data))
|
||||
self._handle_feedback_data('stripe', feedback_data)
|
||||
|
||||
def _stripe_create_payment_intent(self):
|
||||
""" Create and return a PaymentIntent.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: The Payment Intent
|
||||
:rtype: dict
|
||||
"""
|
||||
if not self.token_id.stripe_payment_method: # Pre-SCA token -> migrate it
|
||||
self.token_id._stripe_sca_migrate_customer()
|
||||
|
||||
payment_intent = self.acquirer_id._stripe_make_request('payment_intents', payload={
|
||||
'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id),
|
||||
'currency': self.currency_id.name.lower(),
|
||||
'confirm': True,
|
||||
'customer': self.token_id.acquirer_ref,
|
||||
'off_session': True,
|
||||
'payment_method': self.token_id.stripe_payment_method,
|
||||
'description': self.reference,
|
||||
})
|
||||
response = self.acquirer_id._stripe_make_request(
|
||||
'payment_intents',
|
||||
payload={
|
||||
'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id),
|
||||
'currency': self.currency_id.name.lower(),
|
||||
'confirm': True,
|
||||
'customer': self.token_id.acquirer_ref,
|
||||
'off_session': True,
|
||||
'payment_method': self.token_id.stripe_payment_method,
|
||||
'description': self.reference,
|
||||
},
|
||||
offline=self.operation == 'offline',
|
||||
)
|
||||
if 'error' not in response:
|
||||
payment_intent = response
|
||||
else: # A processing error was returned in place of the payment intent
|
||||
error_msg = response['error'].get('message')
|
||||
self._set_error("Stripe: " + _(
|
||||
"The communication with the API failed.\n"
|
||||
"Stripe gave us the following info about the problem:\n'%s'", error_msg
|
||||
)) # Flag transaction as in error now as the intent status might have a valid value
|
||||
payment_intent = response['error'].get('payment_intent') # Get the PI from the error
|
||||
|
||||
return payment_intent
|
||||
|
||||
@api.model
|
||||
@@ -234,11 +248,14 @@ class PaymentTransaction(models.Model):
|
||||
if self.provider != 'stripe':
|
||||
return
|
||||
|
||||
if 'charge' in data:
|
||||
self.acquirer_reference = data['charge']['id']
|
||||
|
||||
# Handle the intent status
|
||||
if self.operation == 'online_redirect' or self.operation == 'online_token':
|
||||
intent_status = data.get('payment_intent', {}).get('status')
|
||||
else: # 'validation'
|
||||
if self.operation == 'validation':
|
||||
intent_status = data.get('setup_intent', {}).get('status')
|
||||
else: # 'online_redirect', 'online_token', 'offline'
|
||||
intent_status = data.get('payment_intent', {}).get('status')
|
||||
if not intent_status:
|
||||
raise ValidationError(
|
||||
"Stripe: " + _("Received data with missing intent status.")
|
||||
|
||||
Reference in New Issue
Block a user