mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
Stripe: Move Multibanco to payment intents (#4243)
This commit is contained in:
@@ -944,6 +944,17 @@ class StripeMethod(BasePaymentProvider):
|
||||
reference=intent.id,
|
||||
defaults={'order': payment.order, 'payment': payment}
|
||||
)
|
||||
if intent.status == 'requires_action':
|
||||
payment.info = str(intent)
|
||||
if intent.next_action.type == 'multibanco_display_details':
|
||||
payment.state = OrderPayment.PAYMENT_STATE_PENDING
|
||||
payment.save()
|
||||
return
|
||||
|
||||
payment.state = OrderPayment.PAYMENT_STATE_CREATED
|
||||
payment.save()
|
||||
return self._redirect_to_sca(request, payment)
|
||||
|
||||
if intent.status == 'requires_action':
|
||||
payment.info = str(intent)
|
||||
payment.state = OrderPayment.PAYMENT_STATE_CREATED
|
||||
@@ -1046,135 +1057,6 @@ class StripeMethod(BasePaymentProvider):
|
||||
'with us if this problem persists.'))
|
||||
|
||||
|
||||
class StripeSourceMethod(StripeMethod):
|
||||
def payment_is_valid_session(self, request):
|
||||
return True
|
||||
|
||||
def _charge_source(self, request, source, payment):
|
||||
try:
|
||||
params = {}
|
||||
if not source.startswith('src_'):
|
||||
params['statement_descriptor'] = self.statement_descriptor(payment)
|
||||
params.update(self.api_kwargs)
|
||||
params.update(self._connect_kwargs(payment))
|
||||
charge = stripe.Charge.create(
|
||||
amount=self._get_amount(payment),
|
||||
currency=self.event.currency.lower(),
|
||||
source=source,
|
||||
description='{event}-{code}'.format(
|
||||
event=self.event.slug.upper(),
|
||||
code=payment.order.code
|
||||
),
|
||||
metadata={
|
||||
'order': str(payment.order.id),
|
||||
'event': self.event.id,
|
||||
'code': payment.order.code
|
||||
},
|
||||
# TODO: Is this sufficient?
|
||||
idempotency_key=str(self.event.id) + payment.order.code + source,
|
||||
**params
|
||||
)
|
||||
except stripe.error.CardError as e:
|
||||
if e.json_body:
|
||||
err = e.json_body['error']
|
||||
logger.exception('Stripe error: %s' % str(err))
|
||||
else:
|
||||
err = {'message': str(e)}
|
||||
logger.exception('Stripe error: %s' % str(e))
|
||||
logger.info('Stripe card error: %s' % str(err))
|
||||
payment.fail(info={
|
||||
'error': True,
|
||||
'message': err['message'],
|
||||
})
|
||||
raise PaymentException(_('Stripe reported an error with your card: %s') % err['message'])
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
if e.json_body and 'error' in e.json_body:
|
||||
err = e.json_body['error']
|
||||
logger.exception('Stripe error: %s' % str(err))
|
||||
|
||||
if err.get('code') == 'idempotency_key_in_use':
|
||||
# This is not an error we normally expect, however some payment methods like iDEAL will redirect
|
||||
# the user back to our confirmation page at the same time from two devices: the web browser the
|
||||
# purchase is executed from and the online banking app the payment is authorized from.
|
||||
# In this case we will just log the idempotency error but not expose it to the user and just
|
||||
# forward them back to their order page. There is a good chance that by the time the user hits
|
||||
# the order page, the other request has gone through and the payment is confirmed.
|
||||
# Usually however this should be prevented by SELECT FOR UPDATE calls!
|
||||
return
|
||||
|
||||
else:
|
||||
err = {'message': str(e)}
|
||||
logger.exception('Stripe error: %s' % str(e))
|
||||
|
||||
payment.fail(info={
|
||||
'error': True,
|
||||
'message': err['message'],
|
||||
})
|
||||
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
|
||||
'with us if this problem persists.'))
|
||||
else:
|
||||
ReferencedStripeObject.objects.get_or_create(
|
||||
reference=charge.id,
|
||||
defaults={'order': payment.order, 'payment': payment}
|
||||
)
|
||||
if charge.status == 'succeeded' and charge.paid:
|
||||
try:
|
||||
payment.info = str(charge)
|
||||
payment.confirm()
|
||||
except Quota.QuotaExceededException as e:
|
||||
raise PaymentException(str(e))
|
||||
|
||||
except SendMailException:
|
||||
raise PaymentException(_('There was an error sending the confirmation mail.'))
|
||||
elif charge.status == 'pending':
|
||||
if request:
|
||||
messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '
|
||||
'payment completed.'))
|
||||
payment.info = str(charge)
|
||||
payment.state = OrderPayment.PAYMENT_STATE_PENDING
|
||||
payment.save()
|
||||
return
|
||||
else:
|
||||
logger.info('Charge failed: %s' % str(charge))
|
||||
payment.fail(info=str(charge))
|
||||
raise PaymentException(_('Stripe reported an error: %s') % charge.failure_message)
|
||||
|
||||
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
|
||||
self._init_api()
|
||||
try:
|
||||
source = self._create_source(request, payment)
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
if e.json_body and 'err' in e.json_body:
|
||||
err = e.json_body['error']
|
||||
logger.exception('Stripe error: %s' % str(err))
|
||||
|
||||
if err.get('code') == 'idempotency_key_in_use':
|
||||
# Same thing happening twice – we don't want to record a failure, as that might prevent the
|
||||
# other thread from succeeding.
|
||||
return
|
||||
else:
|
||||
err = {'message': str(e)}
|
||||
logger.exception('Stripe error: %s' % str(e))
|
||||
payment.fail(info={
|
||||
'error': True,
|
||||
'message': err['message'],
|
||||
})
|
||||
raise PaymentException(_('We had trouble communicating with Stripe. Please try again and get in touch '
|
||||
'with us if this problem persists.'))
|
||||
|
||||
ReferencedStripeObject.objects.get_or_create(
|
||||
reference=source.id,
|
||||
defaults={'order': payment.order, 'payment': payment}
|
||||
)
|
||||
payment.info = str(source)
|
||||
payment.state = OrderPayment.PAYMENT_STATE_PENDING
|
||||
payment.save()
|
||||
request.session['payment_stripe_order_secret'] = payment.order.secret
|
||||
return self.redirect(request, source.redirect.url)
|
||||
|
||||
|
||||
class StripeRedirectMethod(StripeMethod):
|
||||
redirect_action_handling = "redirect"
|
||||
|
||||
@@ -1793,53 +1675,26 @@ class StripeEPS(StripeRedirectWithAccountNamePaymentIntentMethod):
|
||||
return super().payment_presale_render(payment)
|
||||
|
||||
|
||||
class StripeMultibanco(StripeSourceMethod):
|
||||
class StripeMultibanco(StripeRedirectMethod):
|
||||
identifier = 'stripe_multibanco'
|
||||
verbose_name = _('Multibanco via Stripe')
|
||||
public_name = _('Multibanco')
|
||||
method = 'multibanco'
|
||||
explanation = _(
|
||||
'Multibanco is a payment method available to Portuguese bank account holders.'
|
||||
)
|
||||
redirect_in_widget_allowed = False
|
||||
abort_pending_allowed = True
|
||||
|
||||
def payment_form_render(self, request) -> str:
|
||||
template = get_template('pretixplugins/stripe/checkout_payment_form_simple_noform.html')
|
||||
ctx = {
|
||||
'request': request,
|
||||
'event': self.event,
|
||||
'settings': self.settings,
|
||||
'explanation': self.explanation,
|
||||
'form': self.payment_form(request)
|
||||
def _payment_intent_kwargs(self, request, payment):
|
||||
return {
|
||||
"payment_method_data": {
|
||||
"type": "multibanco",
|
||||
"billing_details": {
|
||||
"email": payment.order.email,
|
||||
}
|
||||
}
|
||||
}
|
||||
return template.render(ctx)
|
||||
|
||||
def _create_source(self, request, payment):
|
||||
source = stripe.Source.create(
|
||||
type='multibanco',
|
||||
amount=self._get_amount(payment),
|
||||
currency=self.event.currency.lower(),
|
||||
metadata={
|
||||
'order': str(payment.order.id),
|
||||
'event': self.event.id,
|
||||
'code': payment.order.code
|
||||
},
|
||||
owner={
|
||||
'email': payment.order.email
|
||||
},
|
||||
redirect={
|
||||
'return_url': build_absolute_uri(self.event, 'plugins:stripe:return', kwargs={
|
||||
'order': payment.order.code,
|
||||
'payment': payment.pk,
|
||||
'hash': payment.order.tagged_secret('plugins:stripe'),
|
||||
})
|
||||
},
|
||||
**self.api_kwargs
|
||||
)
|
||||
return source
|
||||
|
||||
def payment_is_valid_session(self, request):
|
||||
return True
|
||||
|
||||
def checkout_prepare(self, request, cart):
|
||||
return True
|
||||
|
||||
|
||||
class StripePrzelewy24(StripeRedirectMethod):
|
||||
|
||||
@@ -1,10 +1,43 @@
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% load money %}
|
||||
{% if payment.state == "pending" %}
|
||||
<p>{% blocktrans trimmed %}
|
||||
We're waiting for an answer from the payment provider regarding your payment. Please contact us if this
|
||||
takes more than a few days.
|
||||
{% endblocktrans %}</p>
|
||||
{% if payment_info.next_action.type == "multibanco_display_details" %}
|
||||
<p><strong>{% trans "Payment instructions" %}:</strong></p>
|
||||
<ol>
|
||||
<li>
|
||||
{% blocktrans trimmed %}
|
||||
In your online bank account or from an ATM, choose "Payment and other services".
|
||||
{% endblocktrans %}
|
||||
</li>
|
||||
<li>
|
||||
{% blocktrans trimmed %}
|
||||
Click "Payments of services/shopping".
|
||||
{% endblocktrans %}
|
||||
</li>
|
||||
<li>
|
||||
{% blocktrans trimmed %}
|
||||
Enter the entity number, reference number, and amount.
|
||||
{% endblocktrans %}
|
||||
</li>
|
||||
</ol>
|
||||
<dl class="dl-inline">
|
||||
<dt>{% trans "Entity number:" %}</dt> <dd>{{ payment_info.next_action.multibanco_display_details.entity }}</dd><br>
|
||||
<dt>{% trans "Reference number:" %}</dt> <dd>{{ payment_info.next_action.multibanco_display_details.reference }}</dd><br>
|
||||
<dt>{% trans "Amount:" %}</dt> <dd>{{ payment.amount|money:event.currency }}</dd>
|
||||
</dl>
|
||||
<p class="text-muted">
|
||||
{% trans "There is no further action required on this website." %}
|
||||
{% trans "We will send you an email as soon as we received your payment." %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
We're waiting for an answer from the payment provider regarding your payment. Please contact us if this
|
||||
takes more than a few days.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% elif payment.state == "created" and payment_info.status == "requires_action" %}
|
||||
<p>{% blocktrans trimmed %}
|
||||
You need to confirm your payment. Please click the link below to do so or start a new payment.
|
||||
|
||||
@@ -595,7 +595,7 @@ class ScaView(StripeOrderView, View):
|
||||
|
||||
if intent.status == 'requires_action' and intent.next_action.type in [
|
||||
'use_stripe_sdk', 'redirect_to_url', 'alipay_handle_redirect', 'wechat_pay_display_qr_code',
|
||||
'swish_handle_redirect_or_display_qr_code',
|
||||
'swish_handle_redirect_or_display_qr_code', 'multibanco_display_details',
|
||||
]:
|
||||
ctx = {
|
||||
'order': self.order,
|
||||
@@ -610,6 +610,9 @@ class ScaView(StripeOrderView, View):
|
||||
elif intent.next_action.type == 'swish_handle_redirect_or_display_qr_code':
|
||||
ctx['payment_intent_next_action_redirect_url'] = intent.next_action.swish_handle_redirect_or_display_qr_code['hosted_instructions_url']
|
||||
ctx['payment_intent_redirect_action_handling'] = 'iframe'
|
||||
elif intent.next_action.type == 'multibanco_display_details':
|
||||
ctx['payment_intent_next_action_redirect_url'] = intent.next_action.multibanco_display_details['hosted_voucher_url']
|
||||
ctx['payment_intent_redirect_action_handling'] = 'iframe'
|
||||
|
||||
r = render(request, 'pretixplugins/stripe/sca.html', ctx)
|
||||
r._csp_ignore = True
|
||||
|
||||
Reference in New Issue
Block a user