Pipes, Stripe und die SCA-Richtlinie
Friday, 9. August 2019
Strong Customer Authentication (SCA) – schon vor einer Weile hatte Stripe vielen Kunden und auch mir geschrieben, dass dadurch Änderungen anstehen. Zukünftig werde das vorgeschrieben sein (schon im September) und daher musste die Stripe-Integration von Pipes angepasst werden. Heute kam noch einmal solch eine sehr warnende Email, ich habe mir das angeschaut und tatsächlich den Bezahlvorgang angepasst.
Aber trotz eigentlich geringen und positiven Entwicklungsaufwands war der Gesamtprozess nicht toll. Stripe verweist auf diese Dokumentation und erklärt dort ganz viel, aber wenig konkretes. Mich interessieren doch nur zwei Dinge:
- Was muss ich tun, damit Nutzer von Pipes weiterhin ein Abonnement abschließen können?
- Muss ich etwas tun, damit bestehende Abonnements weiterhin monatlich/jährlich abgebucht werden?
Das ist für Stripe sicher schwierig im Voraus zu beantworten, weil die Antwort nicht bei jedem Kunden gleich ist. Aber die Dokumentation scheint mir auch nicht auf die Beantwortung dieser Fragen ausgelegt. Damit bin ich etwas unzufrieden.
Wie auch immer, der erste Schritt war in meinem Fall relativ einfach. Pipes nutzt Stripe Checkout. Das ist eine recht komfortable Softwarelösung, ein bisschen Javascript und etwas Servercode und schon hat man dieses vielleicht bekannte Kreditkarteneingabefenster und kann damit bezahlen bzw Zahlungen entgegennehmen. Also wird das automatisch aktualisiert und Checkout-Integratoren müssen nichts machen? Nein! Stattdessen ist das kleine Fenster jetzt die Legacy-Variante, das neue Checkout funktioniert etwas anders, löst aber die Authentifizierung.
Und genau das hätte mir Stripe in der Email oder der SCA-Dokumentation auch direkt sagen können. Stattdessen ist es in der Checkout-Doku versteckt.
Dabei ist die neue Lösung nichtmal schlecht. Sie sieht hübsch aus und es ist weniger eigener Code notwendig.
Es gibt eine vernünftige Wechselanleitung. Aber im Grunde wechselt man erstmal das Javascript aus. Vorher basierte das auf einem Formular:
<form action="/subscribe" method="POST"> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="pk_test_..." data-name="pipes.digital" data-description="Regular Plan" data-amount="20" data-currency="eur" > </script> </form>
Jetzt wird eine Funktion aufgerufen:
<script src="https://js.stripe.com/v3"> <button id="checkout-button">Subscribe <script> var stripe = Stripe('pk_test_...'); var checkoutButton = document.querySelector('#checkout-button'); checkoutButton.addEventListener('click', function () { stripe.redirectToCheckout({ items: [{ plan: 'regular_v1', quantity: 1 }], customerEmail: 'customer@example.com', successUrl: 'https://pipes.digital/sub_success', cancelUrl: 'https://pipes.digital/pricing' }); }); </script>
Klar, bei mir liegt das alles in erb-Templates und sieht daher etwas anders aus.
Vorher musste auf dem Server dann noch das Abonnement angelegt werden:
post '/charge' do customer = Stripe::Customer.create( :email => authorized_email, :source => params[:stripeToken] ) subscription = Stripe::Subscription.create( :customer => customer.id, :items => [ { :plan => params[:plan] }, ], ) end
Nun:
Das entfällt völlig! Da das neue Checkout auf eine Seite bei Stripe weiterleitet wird alles dort erledigt.
Trotzdem muss noch auf dem eigenen System darauf reagiert werden, dass der Nutzer etwas bestellt hat. Bei Pipes wird ein Datenbankeintrag angelegt. Dafür gibt es einen Webhook (einen Ort, an dem ein anderer Server einen POST hinsenden kann), der in der Doku wieder gut erklärt wird. Prinzipiell aktiviert man ihn im Stripe-Dashboard und fängt ihn dann so:
# webhook endpoint for stripes post '/webhook' do payload = request.body.read event = nil # Verify webhook signature and extract the event # See https://stripe.com/docs/webhooks/signatures for more information. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] begin event = Stripe::Webhook.construct_event( payload, sig_header, endpoint_secret ) rescue JSON::ParserError => e # Invalid payload warn "invalid webhook payload" status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature warn "invalid webhook signature" status 400 return end # Handle the checkout.session.completed event if event['type'] == 'checkout.session.completed' session = event['data']['object'] # Fulfill the purchase: plan = session['display_items'][0]['plan']['id'] if plan == 'regular_v1' User.new(email: session['client_reference_id']).promoteToRegular(subscription_id: session['subscription']) end end status 200 end
Es gibt natürlich noch viel mehr Events, die man mit diesem Webhook fangen könnte, zum Beispiel wenn Zahlungen fehlschlagen.
Und die zweite Frage? Ob bestehende Abonnements weiterlaufen blieb mit bisher unklar. Es gibt da zwar einen Abschnitt für, aber ob dieses Grandfathering passieren kann hängt wohl davon ab, ob das Abo so erstellt wurde:
Ihr saht den Code oben, ist das bisher passiert? Der verlinkte Beispielcode sagt ja, aber nichts davon wird explizit im Code erledigt.
Meine jetzige Annahme ist, dass beim Erstellen von Abonnements gemäß des alten Checkout-Verfahrens die Zahlungen weiterlaufen sollten. Man müsste aber wohl eigentlich auf Nummer sichergehen und eine Seite bauen, die über die Stripe-API den Kunden die Möglichkeit gibt, die Authentifizerung nachzuholen. Eine so simple Lösung wie Checkout gibt es dafür aber nicht. Im Fall von Pipes würde sich das Abtauchen in die API kaum lohnen, im Fall der Fälle würde ich dann eher die Nutzer bitten, nochmal neu zu abonnieren.
Naja, spätestens im September wird sich das aufklären.