Skip to content

Commit a2fa71a

Browse files
committed
[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#71602 X-original-commit: e93b3b3 Related: odoo/enterprise#18680 Signed-off-by: Antoine Vandevenne (anv) <AntoineVDV@users.noreply.github.com>
1 parent 4c4a740 commit a2fa71a

File tree

3 files changed

+41
-19
lines changed

3 files changed

+41
-19
lines changed

addons/payment_stripe/controllers/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ def stripe_webhook(self):
118118
_logger.exception("unable to handle the event data; skipping to acknowledge")
119119
return ''
120120

121-
def _include_payment_intent_in_feedback_data(self, payment_intent, data):
121+
@staticmethod
122+
def _include_payment_intent_in_feedback_data(payment_intent, data):
122123
data.update({'payment_intent': payment_intent})
123124
if payment_intent.get('charges', {}).get('total_count', 0) > 0:
124125
charge = payment_intent['charges']['data'][0] # Use the latest charge object
@@ -127,7 +128,8 @@ def _include_payment_intent_in_feedback_data(self, payment_intent, data):
127128
'payment_method': charge.get('payment_method_details'),
128129
})
129130

130-
def _include_setup_intent_in_feedback_data(self, setup_intent, data):
131+
@staticmethod
132+
def _include_setup_intent_in_feedback_data(setup_intent, data):
131133
data.update({
132134
'setup_intent': setup_intent,
133135
'payment_method': setup_intent.get('payment_method')

addons/payment_stripe/models/payment_acquirer.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ def _get_validation_amount(self):
3939

4040
return 1.0
4141

42-
def _stripe_make_request(self, endpoint, payload=None, method='POST'):
42+
def _stripe_make_request(self, endpoint, payload=None, method='POST', offline=False):
4343
""" Make a request to Stripe API at the specified endpoint.
4444
4545
Note: self.ensure_one()
4646
4747
:param str endpoint: The endpoint to be reached by the request
4848
:param dict payload: The payload of the request
4949
:param str method: The HTTP method of the request
50+
:param bool offline: Whether the operation of the transaction being processed is 'offline'
5051
:return The JSON-formatted content of the response
5152
:rtype: dict
5253
:raise: ValidationError if an HTTP error occurs
@@ -63,7 +64,9 @@ def _stripe_make_request(self, endpoint, payload=None, method='POST'):
6364
# Stripe can send 4XX errors for payment failures (not only for badly-formed requests).
6465
# Check if an error code is present in the response content and raise only if not.
6566
# See https://stripe.com/docs/error-codes.
67+
# If the request originates from an offline operation, don't raise and return the resp.
6668
if not response.ok \
69+
and not offline \
6770
and 400 <= response.status_code < 500 \
6871
and response.json().get('error'): # The 'code' entry is sometimes missing
6972
try:

addons/payment_stripe/models/payment_transaction.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -165,31 +165,45 @@ def _send_payment_request(self):
165165
raise UserError("Stripe: " + _("The transaction is not linked to a token."))
166166

167167
payment_intent = self._stripe_create_payment_intent()
168-
feedback_data = {
169-
'reference': self.reference,
170-
'payment_intent': payment_intent,
171-
}
168+
feedback_data = {'reference': self.reference}
169+
StripeController._include_payment_intent_in_feedback_data(payment_intent, feedback_data)
172170
_logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data))
173171
self._handle_feedback_data('stripe', feedback_data)
174172

175173
def _stripe_create_payment_intent(self):
176174
""" Create and return a PaymentIntent.
177175
176+
Note: self.ensure_one()
177+
178178
:return: The Payment Intent
179179
:rtype: dict
180180
"""
181181
if not self.token_id.stripe_payment_method: # Pre-SCA token -> migrate it
182182
self.token_id._stripe_sca_migrate_customer()
183183

184-
payment_intent = self.acquirer_id._stripe_make_request('payment_intents', payload={
185-
'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id),
186-
'currency': self.currency_id.name.lower(),
187-
'confirm': True,
188-
'customer': self.token_id.acquirer_ref,
189-
'off_session': True,
190-
'payment_method': self.token_id.stripe_payment_method,
191-
'description': self.reference,
192-
})
184+
response = self.acquirer_id._stripe_make_request(
185+
'payment_intents',
186+
payload={
187+
'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id),
188+
'currency': self.currency_id.name.lower(),
189+
'confirm': True,
190+
'customer': self.token_id.acquirer_ref,
191+
'off_session': True,
192+
'payment_method': self.token_id.stripe_payment_method,
193+
'description': self.reference,
194+
},
195+
offline=self.operation == 'offline',
196+
)
197+
if 'error' not in response:
198+
payment_intent = response
199+
else: # A processing error was returned in place of the payment intent
200+
error_msg = response['error'].get('message')
201+
self._set_error("Stripe: " + _(
202+
"The communication with the API failed.\n"
203+
"Stripe gave us the following info about the problem:\n'%s'", error_msg
204+
)) # Flag transaction as in error now as the intent status might have a valid value
205+
payment_intent = response['error'].get('payment_intent') # Get the PI from the error
206+
193207
return payment_intent
194208

195209
@api.model
@@ -234,11 +248,14 @@ def _process_feedback_data(self, data):
234248
if self.provider != 'stripe':
235249
return
236250

251+
if 'charge' in data:
252+
self.acquirer_reference = data['charge']['id']
253+
237254
# Handle the intent status
238-
if self.operation == 'online_redirect' or self.operation == 'online_token':
239-
intent_status = data.get('payment_intent', {}).get('status')
240-
else: # 'validation'
255+
if self.operation == 'validation':
241256
intent_status = data.get('setup_intent', {}).get('status')
257+
else: # 'online_redirect', 'online_token', 'offline'
258+
intent_status = data.get('payment_intent', {}).get('status')
242259
if not intent_status:
243260
raise ValidationError(
244261
"Stripe: " + _("Received data with missing intent status.")

0 commit comments

Comments
 (0)