[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:
Antoine Vandevenne (anv)
2021-06-02 13:26:23 +00:00
parent 4c4a740e0a
commit a2fa71a565
3 changed files with 41 additions and 19 deletions
+4 -2
View File
@@ -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.")