Skip to content

Commit

Permalink
Stripe: Convert all payment methods to intents except multibanco (#3780)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard Schreiber <schreiber@rami.io>
  • Loading branch information
raphaelm and wiffbi committed Jan 12, 2024
1 parent ea33c7b commit 94cbb19
Show file tree
Hide file tree
Showing 11 changed files with 640 additions and 685 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ dependencies = [
"sepaxml==2.6.*",
"slimit",
"static3==0.7.*",
"stripe==5.4.*",
"stripe==7.9.*",
"text-unidecode==1.*",
"tlds>=2020041600",
"tqdm==4.*",
Expand Down
989 changes: 395 additions & 594 deletions src/pretix/plugins/stripe/payment.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ var pretixstripe = {
}
);
},
'handleCardAction': function (payment_intent_client_secret) {
'withStripe': function (callback) {
$.ajax({
url: 'https://js.stripe.com/v3/',
dataType: 'script',
Expand All @@ -223,13 +223,68 @@ var pretixstripe = {
locale: $.trim($("body").attr("data-locale"))
});
}
pretixstripe.stripe.handleCardAction(
payment_intent_client_secret
).then(function (result) {
callback();
}
});
},
'handleAlipayAction': function (payment_intent_client_secret) {
pretixstripe.withStripe(function () {
pretixstripe.stripe.confirmAlipayPayment(
payment_intent_client_secret,
{
return_url: window.location.href
}
).then(function (result) {
if (result.error) {
waitingDialog.hide();
$(".stripe-errors").stop().hide().removeClass("sr-only");
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
$(".stripe-errors").slideDown();
} else {
waitingDialog.show(gettext("Confirming your payment …"));
}
});
});
},
'handleWechatAction': function (payment_intent_client_secret) {
pretixstripe.withStripe(function () {
pretixstripe.stripe.confirmWechatPayPayment(
payment_intent_client_secret,
{
payment_method_options: {
wechat_pay: {
client: 'web',
},
},
}
).then(function (result) {
if (result.error) {
waitingDialog.hide();
$(".stripe-errors").stop().hide().removeClass("sr-only");
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
$(".stripe-errors").slideDown();
} else {
waitingDialog.show(gettext("Confirming your payment …"));
location.reload();
});
}
}
});
});
},
'handleCardAction': function (payment_intent_client_secret) {
pretixstripe.withStripe(function () {
pretixstripe.stripe.handleCardAction(
payment_intent_client_secret
).then(function (result) {
if (result.error) {
waitingDialog.hide();
$(".stripe-errors").stop().hide().removeClass("sr-only");
$(".stripe-errors").html("<div class='alert alert-danger'>Technical error, please contact support: " + result.error.message + "</div>");
$(".stripe-errors").slideDown();
} else {
waitingDialog.show(gettext("Confirming your payment …"));
location.reload();
}
});
});
},
'handlePaymentRedirectAction': function (payment_intent_next_action_redirect_url) {
Expand Down Expand Up @@ -270,6 +325,12 @@ $(function () {
} else if ($("#stripe_payment_intent_next_action_redirect_url").length) {
let payment_intent_next_action_redirect_url = $.trim($("#stripe_payment_intent_next_action_redirect_url").html());
pretixstripe.handlePaymentRedirectAction(payment_intent_next_action_redirect_url);
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "wechat_pay_display_qr_code") {
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
pretixstripe.handleWechatAction(payment_intent_client_secret);
} else if ($.trim($("#stripe_payment_intent_action_type").html()) === "alipay_handle_redirect") {
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
pretixstripe.handleAlipayAction(payment_intent_client_secret);
} else if ($("#stripe_payment_intent_client_secret").length) {
let payment_intent_client_secret = $.trim($("#stripe_payment_intent_client_secret").html());
pretixstripe.handleCardAction(payment_intent_client_secret);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,65 @@
<dt>{% trans "Charge ID" %}</dt>
<dd>{{ payment_info.id }}</dd>
{% endif %}
{% if "source" in payment_info %}
{% if payment_info.source.card %}
{% if details %}
{% if details.card %}
<dt>{% trans "Card type" %}</dt>
<dd>{{ payment_info.source.card.brand }}</dd>
<dd>{{ details.card.brand }}</dd>
<dt>{% trans "Card number" %}</dt>
<dd>**** **** **** {{ payment_info.source.card.last4 }}</dd>
{% if payment_info.source.owner.name %}
<dd>
**** **** **** {{ details.card.last4 }}
{% if details.card.moto %}
<span class="label label-info">{% trans "MOTO" %}</span>
{% endif %}
</dd>
{% endif %}
{% if details.type == "sepa_debit" %}
<dt>{% trans "Bank" %}</dt>
<dd>{{ details.sepadirectdebit.bank_name }}</dd>
{% if details.sepadirectdebit.verified_name %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ payment_info.source.owner.name }}</dd>
<dd>{{ details.sepadirectdebit.verified_name }}</dd>
{% endif %}
{% endif %}
{% if payment_info.source.type == "sepa_debit" %}
{% if details.type == "giropay" %}
<dt>{% trans "Bank" %}</dt>
<dd>{{ payment_info.source.sepadirectdebit.bank_name }}</dd>
<dt>{% trans "Payer name" %}</dt>
<dd>{{ payment_info.source.owner.verified_name|default:payment_info.source.owner.name }}</dd>
<dd>{{ details.giropay.bank_name }} ({{ details.giropay.bic }})</dd>
{% if details.giropay.verified_name %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ details.giropay.verified_name }}</dd>
{% endif %}
{% endif %}
{% if payment_info.source.type == "giropay" %}
{% if details.type == "eps" %}
<dt>{% trans "Bank" %}</dt>
<dd>{{ payment_info.source.giropay.bank_name }} ({{ payment_info.source.giropay.bic }})</dd>
<dt>{% trans "Payer name" %}</dt>
<dd>{{ payment_info.source.owner.verified_name|default:payment_info.source.owner.name }}</dd>
<dd>{{ details.eps.bank }}</dd>
{% if details.eps.verified_name %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ details.eps.verified_name }}</dd>
{% endif %}
{% endif %}
{% if payment_info.source.type == "bancontact" %}
{% if details.type == "bancontact" %}
<dt>{% trans "Bank" %}</dt>
<dd>{{ payment_info.source.bancontact.bank_name }} ({{ payment_info.source.bancontact.bic }})</dd>
{% if owner in payment_info.source %}
<dd>{{ details.bancontact.bank_name }} ({{ details.bancontact.bic }})</dd>
{% if details.bancontact.verified_name %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ payment_info.source.owner.verified_name|default:payment_info.source.owner.name }}</dd>
<dd>{{ details.bancontact.verified_name }}</dd>
{% endif %}
{% endif %}
{% if payment_info.source.type == "ideal" %}
{% if details.type == "ideal" %}
<dt>{% trans "Bank" %}</dt>
<dd>{{ payment_info.source.ideal.bank }} ({{ payment_info.source.ideal.bic }})</dd>
<dt>{% trans "Payer name" %}</dt>
<dd>{{ payment_info.source.owner.verified_name|default:payment_info.source.owner.name }}</dd>
<dd>{{ details.ideal.bank }} ({{ details.ideal.bic }})</dd>
{% if details.ideal.verified_name %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ details.ideal.verified_name }}</dd>
{% endif %}
{% endif %}
{% endif %}
{% if payment_info.charges.data.0 %}
{% if payment_info.charges.data.0.payment_method_details.card %}
<dt>{% trans "Card type" %}</dt>
<dd>{{ payment_info.charges.data.0.payment_method_details.card.brand }}</dd>
<dt>{% trans "Card number" %}</dt>
<dd>
**** **** **** {{ payment_info.charges.data.0.payment_method_details.card.last4 }}
{% if payment_info.charges.data.0.payment_method_details.card.moto %}
<span class="label label-info">{% trans "MOTO" %}</span>
{% endif %}
</dd>
{% endif %}
{% if details.owner.verified_name %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ details.owner.verified_name }}</dd>
{% elif details.owner.name %}
<dt>{% trans "Payer name" %}</dt>
<dd>{{ details.owner.name }}</dd>
{% endif %}
{% if "amount" in payment_info %}
<dt>{% trans "Total value" %}</dt>
Expand Down
12 changes: 10 additions & 2 deletions src/pretix/plugins/stripe/templates/pretixplugins/stripe/sca.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
{% block custom_header %}
{{ block.super }}
{% include "pretixplugins/stripe/presale_head.html" with settings=stripe_settings %}
<script type="text/plain" id="stripe_payment_intent_action_type">{{ payment_intent_action_type }}</script>
<script type="text/plain" id="stripe_payment_intent_client_secret">{{ payment_intent_client_secret }}</script>
<script type="text/plain" id="stripe_payment_intent_next_action_redirect_url">{{ payment_intent_next_action_redirect_url }}</script>
<script type="text/plain" id="stripe_payment_intent_redirect_action_handling">{{ payment_intent_redirect_action_handling }}</script>
{% if payment_intent_next_action_redirect_url %}
<script type="text/plain" id="stripe_payment_intent_next_action_redirect_url">{{ payment_intent_next_action_redirect_url }}</script>
{% endif %}}
{% if payment_intent_redirect_action_handling %}
<script type="text/plain" id="stripe_payment_intent_redirect_action_handling">{{ payment_intent_redirect_action_handling }}</script>
{% endif %}
{% endblock %}
{% block content %}
<div class="panel panel-primary">
Expand All @@ -18,6 +23,9 @@ <h3 class="panel-title">
Confirm payment: {{ code }}
{% endblocktrans %}
</h3>
</div>
<div class="stripe-errors sr-only panel-body">

</div>
<div class="panel-body embed-responsive embed-responsive-sca" id="scacontainer">

Expand Down
24 changes: 17 additions & 7 deletions src/pretix/plugins/stripe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,11 @@ def charge_webhook(event, event_json, charge_id, rso):
prov._init_api()

try:
charge = stripe.Charge.retrieve(charge_id, expand=['dispute'], **prov.api_kwargs)
charge = stripe.Charge.retrieve(
charge_id,
expand=['dispute', 'refunds', 'payment_intent', 'payment_intent.latest_charge'],
**prov.api_kwargs
)
except stripe.error.StripeError:
logger.exception('Stripe error on webhook. Event data: %s' % str(event_json))
return HttpResponse('Charge not found', status=500)
Expand Down Expand Up @@ -321,7 +325,7 @@ def charge_webhook(event, event_json, charge_id, rso):

order.log_action('pretix.plugins.stripe.event', data=event_json)

is_refund = charge['refunds']['total_count'] or charge['dispute']
is_refund = charge['amount_refunded'] or charge['refunds']['total_count'] or charge['dispute']
if is_refund:
known_refunds = [r.info_data.get('id') for r in payment.refunds.all()]
migrated_refund_amounts = [r.amount for r in payment.refunds.all() if not r.info_data.get('id')]
Expand Down Expand Up @@ -354,6 +358,8 @@ def charge_webhook(event, event_json, charge_id, rso):
OrderPayment.PAYMENT_STATE_CANCELED,
OrderPayment.PAYMENT_STATE_FAILED):
try:
if getattr(charge, "payment_intent", None):
payment.info = str(charge.payment_intent)
payment.confirm()
except LockTimeoutException:
return HttpResponse("Lock timeout, please try again.", status=503)
Expand Down Expand Up @@ -439,14 +445,14 @@ def paymentintent_webhook(event, event_json, paymentintent_id, rso):
prov._init_api()

try:
paymentintent = stripe.PaymentIntent.retrieve(paymentintent_id, **prov.api_kwargs)
paymentintent = stripe.PaymentIntent.retrieve(paymentintent_id, expand=["latest_charge"], **prov.api_kwargs)
except stripe.error.StripeError:
logger.exception('Stripe error on webhook. Event data: %s' % str(event_json))
return HttpResponse('Charge not found', status=500)

for charge in paymentintent.charges.data:
if paymentintent.latest_charge:
ReferencedStripeObject.objects.get_or_create(
reference=charge.id,
reference=paymentintent.latest_charge.id,
defaults={'order': rso.payment.order, 'payment': rso.payment}
)

Expand Down Expand Up @@ -581,6 +587,7 @@ def get(self, request, *args, **kwargs):
try:
intent = stripe.PaymentIntent.retrieve(
payment_info['id'],
expand=["latest_charge"],
**prov.api_kwargs
)
except stripe.error.InvalidRequestError:
Expand All @@ -591,12 +598,15 @@ def get(self, request, *args, **kwargs):
messages.error(self.request, _('Sorry, there was an error in the payment process.'))
return self._redirect_to_order()

if intent.status == 'requires_action' and intent.next_action.type in ['use_stripe_sdk', 'redirect_to_url']:
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'
]:
ctx = {
'order': self.order,
'stripe_settings': StripeSettingsHolder(self.order.event).settings,
}
if intent.next_action.type == 'use_stripe_sdk':
ctx['payment_intent_action_type'] = intent.next_action.type
if intent.next_action.type in ('use_stripe_sdk', 'alipay_handle_redirect', 'wechat_pay_display_qr_code'):
ctx['payment_intent_client_secret'] = intent.client_secret
elif intent.next_action.type == 'redirect_to_url':
ctx['payment_intent_next_action_redirect_url'] = intent.next_action.redirect_to_url['url']
Expand Down
20 changes: 12 additions & 8 deletions src/tests/api/test_orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
from django.utils.timezone import now
from django_countries.fields import Country
from django_scopes import scopes_disabled
from stripe.error import APIConnectionError
from stripe import error
from tests.plugins.stripe.test_checkout import apple_domain_create
from tests.plugins.stripe.test_provider import MockedCharge

from pretix.base.models import InvoiceAddress, Order, OrderPosition
Expand Down Expand Up @@ -744,13 +745,14 @@ def test_payment_refund_fail(token_client, organizer, event, order, monkeypatch)

@pytest.mark.django_db
def test_payment_refund_success(token_client, organizer, event, order, monkeypatch):
def charge_retr(*args, **kwargs):
def refund_create(amount):
r = MockedCharge()
r.id = 'foo'
r.status = 'succeeded'
return r

def refund_create(*args, **kwargs):
r = MockedCharge()
r.id = 'foo'
r.status = 'succeeded'
return r

def charge_retr(*args, **kwargs):
c = MockedCharge()
c.refunds.create = refund_create
return c
Expand All @@ -765,7 +767,9 @@ def refund_create(amount):
'id': 'ch_123345345'
})
)
monkeypatch.setattr("stripe.ApplePayDomain.create", apple_domain_create)
monkeypatch.setattr("stripe.Charge.retrieve", charge_retr)
monkeypatch.setattr("stripe.Refund.create", refund_create)
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/{}/refund/'.format(
organizer.slug, event.slug, order.code, p1.local_id
), format='json', data={
Expand All @@ -784,7 +788,7 @@ def refund_create(amount):
def test_payment_refund_unavailable(token_client, organizer, event, order, monkeypatch):
def charge_retr(*args, **kwargs):
def refund_create(amount):
raise APIConnectionError(message='Foo')
raise error.APIConnectionError(message='Foo')

c = MockedCharge()
c.refunds.create = refund_create
Expand Down

0 comments on commit 94cbb19

Please sign in to comment.