lukec/stripe-perl

European payments are changing

Closed this issue · 7 comments

Strong Customer Authentication (SCA), a new rule coming into effect on September 14, 2019 as part of PSD2 regulation in Europe, will require changes to how your European customers authenticate online payments.

If you identify any necessary changes to Net::Stripe please open an issue - or better still, a pull request! Read more here:

https://stripe.com/gb/payments/strong-customer-authentication
https://stripe.com/docs/strong-customer-authentication

Please note - the legislative changes aren't yet completely nailed down, for example:
https://stripe.com/docs/billing/migration/strong-customer-authentication#scenario-2

SCA rules around subscriptions that don’t immediately process a payment are actively being developed by card networks. As a result, Stripe does not yet have an API interface to authenticate in this way.

so feel free to add comments to this thread as placeholders for un-spec'ed changes.

Hi, I'm trying to get my head around this at the moment.

Currently we get the customers card id, from Stripe, then create a charge, which is all working fine.

However, now there seems to be the PaymentIntents update with Stripe, i.e
https://stripe.com/docs/api/setup_intents &&
https://stripe.com/docs/api/payment_intents

Does this mean that one would no longer be able to create a charge, and would need to create an Intent ? I'm a bit confused as to the difference here...

I'm sorry I don't have an answer to your question. I suggest you ask Stripe support https://support.stripe.com/contact I'd also be very grateful if you could share what you learn here. Thanks!

Hi, will feedback soon, one thing I wasn't sure of with the API, is there a way to access an API call that isn't included yet as a method ? E.g if get_intents doesn't exist, something like $stripe->api('/payment_intents', x => y)

I think something like $stripe->_post('/payment_intents', { x=> $y}) could work but please feel free to setup new methods and issue a pull request and I'll release changes as soon as possible.

Just for info for anyone else trying to get my hear around this one and off-session payments only, here is my current flow...

Create a Setup Intent for a customer
Grab the client_secret from the intent.
Pass the client_secret to the front end, with a stripe.js based form. Allowing card details entered and passed to Stripe.
Later on, as desired, create a Payment Intent for an amount, and process. Hopefully this all goes through, no further action needed.

However, if the Payment Intent fails with a card authentication error, email them with a link, which takes them to a page which allows an on-session payment (using stripe.js and passing payment intent secret as a "data-secret" field in the form.

Bit long winded, but it seems to work. Interested if anyone else has any success. Unfortunately I've not been able to get Stripe-Perl working (see other issue r.e kavorka), but will try and update if I have any success integrating it into this.

If it's helpful to anyone I've had success migrating from the Charges API to the Payment Intents API in the following way:

I use Stripe's Elements js to generate the payment_method_id which allows a nice clean on page 2FA:

stripe.createPaymentMethod('card', cardElement, {
}).then(function(result) {
        if (result.error) {
                console.log(result.error);
                cardError.innerHTML = result.error.message;
                $("#card-errors").show();
                // Show error in payment form
        } else {
                // Otherwise send paymentMethod.id to your server (see Step 2)
                fetch('/stripe-ajax-page-here/confirm_payment/', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({ payment_method_id: result.paymentMethod.id })
                }).then(function(result) {
                        // Handle server response (see Step 3)
                        result.json().then(function(json) {
                                handleServerResponse(json);
                        })
                });
        }
});

Once the 2FA has been negotiated via Elements js / handleServerResponse it posts payment_method_id to the following (Dancer2) route which I use to create the payment_intent_id:

my ($error,$intent);
try {
        # Don't retry intent if it's already been initialised
        if ($json->{payment_intent_id}) {
                # Confirm a PaymentIntent
                $intent = $stripe->_post('payment_intents/' . $json->{payment_intent_id} . '/confirm', {
                                payment_method => $json->{payment_method_id},
                        });
                debug "confirm_PaymentIntent= ",$intent;

                # Save payment intent id for later capture.
                session stripe_payment_intent_id => $json->{payment_intent_id};

        } elsif ($json->{payment_method_id}) {
                # Create a PaymentIntent
                $intent = $stripe->_post('payment_intents', {
                                payment_method => $json->{payment_method_id},
                                amount => $charge_amount,
                                currency => 'gbp',
                                confirmation_method => 'manual',
                                capture_method => 'manual', # Allows for capture later
                                confirm => 'true',
                        });
                debug "create_PaymentIntent= ",$intent;

        } else {
                debug 'unknown_intent=',$json;
        }

} catch { ($error) = @_; };

I set capture_method to "manual" to pre-auth the card as I have a checkout review page after this card entry / billing details page where I give the customer an opportunity to look over the order before proceeding. Set capture_method to "automatic" if you want the capture to occur immediately.

Once the customer has reviewed the order I capture the funds using:

my ($error,$intent);
try {
        $intent = $stripe->_post('payment_intents/' . session('stripe_payment_intent_id') . '/capture', { });
        debug "capture_PaymentIntent=",$intent;
} catch { ($error) = @_; };

This was mostly cobbled together from whatever documentation I could find and fits my use case well. I'm interested to know other people's integration/migration methods and if I could do things in a better way.

@morrisonj, not sure if you are still monitoring this, but would you be interested in field-testing my proposed updates (https://github.com/sherrardb/stripe-perl/tree/payment-intents-v1) for implementation of PaymentMethods and PaymentIntents?

i am mostly interested in:

  • whether the methods provided are sufficient to complete an actual transaction

  • whether the exposed API variables, both setters and getters, are sufficient

  • whether the exposed API variables are configured appropriately, ie required vs optional

but i also welcome any feedback on code structure, implementation, etc.

TIA