Stripe SCA (#1275)

* Stripe SCA
- Upgrade to latest Stripe API
- Deprecate Stripe Checkout for CC
- Migrate CC payments to Payment Intents

* Move SCA to its own view

* Handle CardErrors for PaymentIntents

* Abilty to handle charge webhooks with PaymentIntents

* Better handling of Stripe References

* Fix Stripe Tests

* Move SCA page into orderlayout; perform iFrame SCA

* Handle disputes and pi-webhooks better, fill more into ReferencedStripeObject

* Optionally pass prefetched PaymentIntent to handle-func

* Fix style

* Send message to window.parent not window.top (widget compatibility)

* More accurate loading message

* Show a cog on sca_return.html. On a good internet connection, you barely see it, but on a bad one…

* Robust error handling

* If it's a method and used like a method, let's actually call it like a method!

* Remove logging statement

* Fix JavaScript interference with other frame events

* Use 4:3 aspect ratio, but at least 600px

* Adjust to django_scopes
This commit is contained in:
Martin Gross
2019-07-02 12:37:07 +02:00
committed by Raphael Michel
parent b727207e79
commit 446cf68377
17 changed files with 543 additions and 371 deletions

View File

@@ -10,15 +10,26 @@ from pretix.testutils.sessions import add_cart_session, get_cart_session_key
class MockedCharge():
def __init__(self):
self.status = ''
self.paid = False
self.id = 'ch_123345345'
status = ''
paid = False
id = 'ch_123345345'
def refresh(self):
pass
class Object():
pass
class MockedPaymentintent():
status = ''
id = 'pi_1EUon12Tb35ankTnZyvC3SdE'
charges = Object()
charges.data = [MockedCharge()]
last_payment_error = None
@pytest.fixture
def env(client):
orga = Organizer.objects.create(name='CCC', slug='ccc')
@@ -41,16 +52,17 @@ def env(client):
@pytest.mark.django_db
def test_payment(env, monkeypatch):
def charge_create(**kwargs):
def paymentintent_create(**kwargs):
assert kwargs['amount'] == 1337
assert kwargs['currency'] == 'eur'
assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu'
c = MockedCharge()
assert kwargs['payment_method'] == 'pm_189fTT2eZvKYlo2CvJKzEzeu'
c = MockedPaymentintent()
c.status = 'succeeded'
c.paid = True
charge_create.called = True
c.charges.data[0].paid = True
setattr(paymentintent_create, 'called', True)
return c
monkeypatch.setattr("stripe.Charge.create", charge_create)
monkeypatch.setattr("stripe.PaymentIntent.create", paymentintent_create)
client, ticket = env
session_key = get_cart_session_key(client, ticket.event)
@@ -62,17 +74,16 @@ def test_payment(env, monkeypatch):
client.post('/%s/%s/checkout/questions/' % (ticket.event.organizer.slug, ticket.event.slug), {
'email': 'admin@localhost'
}, follow=True)
charge_create.called = False
paymentintent_create.called = False
response = client.post('/%s/%s/checkout/payment/' % (ticket.event.organizer.slug, ticket.event.slug), {
'payment': 'stripe',
'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu',
'payment_method': 'pm_189fTT2eZvKYlo2CvJKzEzeu',
'stripe_card_brand': 'visa',
'stripe_card_last4': '1234'
}, follow=True)
assert not charge_create.called
assert not paymentintent_create.called
assert response.status_code == 200
assert 'alert-danger' not in response.rendered_content
response = client.post('/%s/%s/checkout/confirm/' % (ticket.event.organizer.slug, ticket.event.slug), {
}, follow=True)
assert charge_create.called
assert response.status_code == 200

View File

@@ -46,39 +46,51 @@ class MockedRefunds():
class MockedCharge():
def __init__(self):
self.status = ''
self.paid = False
self.id = 'ch_123345345'
self.refunds = MockedRefunds()
status = ''
paid = False
id = 'ch_123345345'
refunds = MockedRefunds()
def refresh(self):
pass
class Object():
pass
class MockedPaymentintent():
status = ''
id = 'pi_1EUon12Tb35ankTnZyvC3SdE'
charges = Object()
charges.data = [MockedCharge()]
last_payment_error = None
@pytest.mark.django_db
def test_perform_success(env, factory, monkeypatch):
event, order = env
def charge_create(**kwargs):
def paymentintent_create(**kwargs):
assert kwargs['amount'] == 1337
assert kwargs['currency'] == 'eur'
assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu'
c = MockedCharge()
assert kwargs['payment_method'] == 'pm_189fTT2eZvKYlo2CvJKzEzeu'
c = MockedPaymentintent()
c.status = 'succeeded'
c.paid = True
c.charges.data[0].paid = True
return c
monkeypatch.setattr("stripe.Charge.create", charge_create)
monkeypatch.setattr("stripe.PaymentIntent.create", paymentintent_create)
prov = StripeCC(event)
req = factory.post('/', {
'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu',
'stripe_payment_method_id': 'pm_189fTT2eZvKYlo2CvJKzEzeu',
'stripe_last4': '4242',
'stripe_brand': 'Visa'
})
req.session = {}
prov.checkout_prepare(req, {})
assert 'payment_stripe_token' in req.session
assert 'payment_stripe_payment_method_id' in req.session
payment = order.payments.create(
provider='stripe_cc', amount=order.total
)
@@ -93,25 +105,25 @@ def test_perform_success_zero_decimal_currency(env, factory, monkeypatch):
event.currency = 'JPY'
event.save()
def charge_create(**kwargs):
def paymentintent_create(**kwargs):
assert kwargs['amount'] == 13
assert kwargs['currency'] == 'jpy'
assert kwargs['source'] == 'tok_189fTT2eZvKYlo2CvJKzEzeu'
c = MockedCharge()
assert kwargs['payment_method'] == 'pm_189fTT2eZvKYlo2CvJKzEzeu'
c = MockedPaymentintent()
c.status = 'succeeded'
c.paid = True
c.charges.data[0].paid = True
return c
monkeypatch.setattr("stripe.Charge.create", charge_create)
monkeypatch.setattr("stripe.PaymentIntent.create", paymentintent_create)
prov = StripeCC(event)
req = factory.post('/', {
'stripe_token': 'tok_189fTT2eZvKYlo2CvJKzEzeu',
'stripe_payment_method_id': 'pm_189fTT2eZvKYlo2CvJKzEzeu',
'stripe_last4': '4242',
'stripe_brand': 'Visa'
})
req.session = {}
prov.checkout_prepare(req, {})
assert 'payment_stripe_token' in req.session
assert 'payment_stripe_payment_method_id' in req.session
payment = order.payments.create(
provider='stripe_cc', amount=order.total
)
@@ -136,7 +148,7 @@ def test_perform_card_error(env, factory, monkeypatch):
})
req.session = {}
prov.checkout_prepare(req, {})
assert 'payment_stripe_token' in req.session
assert 'payment_stripe_payment_method_id' in req.session
with pytest.raises(PaymentException):
payment = order.payments.create(
provider='stripe_cc', amount=order.total
@@ -162,7 +174,7 @@ def test_perform_stripe_error(env, factory, monkeypatch):
})
req.session = {}
prov.checkout_prepare(req, {})
assert 'payment_stripe_token' in req.session
assert 'payment_stripe_payment_method_id' in req.session
with pytest.raises(PaymentException):
payment = order.payments.create(
provider='stripe_cc', amount=order.total
@@ -192,7 +204,7 @@ def test_perform_failed(env, factory, monkeypatch):
})
req.session = {}
prov.checkout_prepare(req, {})
assert 'payment_stripe_token' in req.session
assert 'payment_stripe_payment_method_id' in req.session
with pytest.raises(PaymentException):
payment = order.payments.create(
provider='stripe_cc', amount=order.total