Background

Beginning with the 14th of September 2019 new regulation will require Strong Customer Authentication (SCA) for European card payments. This will require a different payment workflow as users will need to authenticate the payment after they entered card details. You can find more information about Stripe support for SCA here. If you use laravel-omnipay package for stripe payments – as the time of writing – you’d need to upgrade the package to the development version. The latest release doesn’t include the PaymentIntents workflow.

In this article I am going to describe the steps and the challenges, how I managed to upgrade the stripe payments to support SCA.

Upgrade the Stripe.js to v3

My first step was to upgrade the stripe.js to Stripe Elements. I am not sure if it is absolutely necessary for integrating Payment Intents for stripe, but if we need to do changes anyway, it is better to use the latest version. The basic difference between the v2 and v3 is while with the v2 the fields were created manually, the v3 only needs a container, it automatically creates the inputs for the card details. I won’t go into the details here, as Stripe has an excellent migration guide, I also followed that. Please find it here.

Upgrade the thephpleague/omnipay-stripe package to the dev version

Until the next version of the package is released, we’d need to use the development version of the package. Add the following to the composer.json in your application:

“omnipay/stripe”: “3.1.x-dev” it is a branch alias for the “dev-master” in the package’s composer.json.

To prevent potential problems with eventual breaking changes in the future, I’ve used the latest commit hash in the composer.json like this:

"omnipay/stripe": "3.1.x-dev#37df2a791e8feab45543125f4c5f22d5d305096d" 1 "omnipay/stripe" : "3.1.x-dev#37df2a791e8feab45543125f4c5f22d5d305096d"

When this is done use composer update to update the package:

composer update omnipay/stripe 1 composer update omnipay / stripe

Use the Payment Intents gateway

Now that you’ve installed the latest version, you can use the PaymentIntents gateway. Add the gateway to the gateways section in the config/omnipay.php:

'Stripe_PaymentIntents' => array( 'ApiKey' => env('StripeApiKey', ''), ), 1 2 3 'Stripe_PaymentIntents' = > array ( 'ApiKey' = > env ( 'StripeApiKey' , '' ) , ) ,

If you use the Facade for accessing the gateway, you should use the new gateway’s name as an argument like this:

Omnipay::gateway('Stripe_PaymentIntents'); 1 Omnipay:: gateway ( 'Stripe_PaymentIntents' ) ;

I’ve followed the steps described in the package’s readme here. I’ll summarize only the major steps here.

Add ‘confirm => true’ to the parameter array for the purchase method, to prevent the need for manual confirmation of the payment intent:

$response = $gateway->purchase([ 'amount' => '10.00', 'currency' => 'USD', 'description' => 'This is a test purchase transaction.', 'token' => $token, 'returnUrl' => $completePaymentUrl, 'confirm' => true, ])->send(); 1 2 3 4 5 6 7 8 $response = $gateway -> purchase ( [ 'amount' = > '10.00' , 'currency' = > 'USD' , 'description' = > 'This is a test purchase transaction.' , 'token' = > $token , 'returnUrl' = > $completePaymentUrl , 'confirm' = > true , ] ) -> send ( ) ;

As the documentation suggests, the token can be used instead of paymentMethod, so I also used that.

When the purchase request is sent, we can get 3 types of response:

Successful : the 3DS authentication is not necessary, payment was successful

: the 3DS authentication is not necessary, payment was successful Redirect : 3DS authentication is necessary, we need to redirect the user to authenticate the transaction

: 3DS authentication is necessary, we need to redirect the user to authenticate the transaction Fail: something went wrong, we need to handle the error

if ($response->isSuccessful()) { // Pop open that champagne bottle, because the payment is complete. } else if($response->isRedirect()) { $response->redirect(); } else { // The payment has failed. Use $response->getMessage() to figure out why and return to step (1). } 1 2 3 4 5 6 7 if ( $response -> isSuccessful ( ) ) { // Pop open that champagne bottle, because the payment is complete. } else if ( $response -> isRedirect ( ) ) { $response -> redirect ( ) ; } else { // The payment has failed. Use $response->getMessage() to figure out why and return to step (1). }

If the redirect is necessary, we’ll redirect the user to the 3DS authentication page. Once the authentication has been completed (or not), the user gets redirected to the completePaymentUrl. When it happens we confirm the payment intent and check the response if the payment has been completed successfully.

$response = $gateway->confirm([ 'paymentIntentReference' => $paymentIntentReference, 'returnUrl' => $completePaymentUrl, ])->send(); if ($response->isSuccessful()) { // All done!! Big bucks! } else { // The response will not be successful if the 3DS authentication process failed or the card has been declined. Either way, it's back to step (1)! } 1 2 3 4 5 6 7 8 9 10 11 $response = $gateway -> confirm ( [ 'paymentIntentReference' = > $paymentIntentReference , 'returnUrl' = > $completePaymentUrl , ] ) -> send ( ) ; if ( $response -> isSuccessful ( ) ) { // All done!! Big bucks! } else { // The response will not be successful if the 3DS authentication process failed or the card has been declined. Either way, it's back to step (1)! }

Conclusion

The deadline is close, this is now a hot topic amongst developers, I hope my blog post helps you to make the transition on time.