thephpleague / omnipay-stripe

Stripe driver for the Omnipay PHP payment processing library
MIT License
184 stars 167 forks source link

How to process 3D Secure Payments? #161

Open marbuser opened 4 years ago

marbuser commented 4 years ago

So I'm initialising my payment like so;

        $response = $gateway->purchase([
            'amount' => money_format('%i', ($amount / 100)),
            'currency' => 'USD',
            'token' => $payment_token,
            'description' => "Test Product",
            'confirm' => true,
            'returnUrl' => 'http://mysite.test/me/transactions,
        ])->send();

This works perfectly fine for cards that don't have 3D Secure, and triggers my Webhook perfectly. However, as soon as I use a Stripe test card with 3D secure, after clicking 'Complete' on Stripe's test page, it sends a URL back like so; http://mysite.test/me/transactions?payment_intent=pi_0000000000000000&payment_intent_client_secret=pi_000000000000000_secret_0000000000000000&source_type=card

I'm not entirely sure what to do with this. Since I'm developing my application as a REST API with an SPA frontend, I'm not really sure what to do with this URL and the payment remains as The customer has not completed the payment. in the Stripe dashboard, and my Webhook is never triggered.

Hopefully someone here can help as I can't really find any sort of information about how to deal with this.

jannejava commented 4 years ago

The basic idea is that you will get a URL that you have to follow to an external site. When it's completed you will be redirected back to your app, so you have to leave your app even if it's a SPA.

So when you get the response from $gateway->purchase() you have to check for $response->isRedirect() and $response->getRedirectUrl().

I wrote a quick blog-post about this.

domis86 commented 4 years ago

To do it without leaving the SPA you can do as is described in this issue: https://github.com/thephpleague/omnipay-stripe/issues/152#issuecomment-530711215

marbuser commented 4 years ago

@jannejava Thanks for the blog post, but I'm hoping to use a more Webhook based approach than manual confirmation.

@domis86 Yeah I was testing this approach yesterday. I got it working, but 1 thing I really dislike about it is the "Manual Confirmation" part of it. Is there anyway I can use it in conjunction with a webhook so that as soon as they complete 3DSecure, it confirms the payment intent and triggers the webhook?

Thanks!

jannejava commented 4 years ago

@marbuser Someone has to correct me if I am wrong but this tripped me up: the Omnipay/Stripe is built around the manual flow. If you want the async/webhooks, you have to use Stripe's own PHP SDK. In my project, the manual flow did work much better.

kpinkowski commented 4 years ago

@marbuser How did you ended up implementing this?

laoneo commented 4 years ago

I'v just spent the last couple of days to implement this without the Stripe SDK and just with Omnipay and Stripe.js. First you need to create the payment method with Stripe.js and then authorize. When the response has the status 'requires_capture', then do capture on the gateway. Otherwise you probably have to do the redirect to the banks web site with extra authentication. On the next callback you should have then a payment_intent parameter in the url. Then do the confirm on the gateway and when the status is again requires_capture on the response, do the capture.

That's how it worked for me to finally get the balance transaction. So you got two times a callback and depending on the url parameters you have to figure out what to do. Not sure if this is the best way, but at least it works now.

domis86 commented 4 years ago

@marbuser

Yeah I was testing this approach yesterday. I got it working, but 1 thing I really dislike about it is the "Manual Confirmation" part of it. Is there anyway I can use it in conjunction with a webhook so that as soon as they complete 3DSecure, it confirms the payment intent and triggers the webhook?

Ok so i think you my want to do like this:

  1. See this line https://github.com/thephpleague/omnipay-stripe/blob/master/src/Message/PaymentIntents/AuthorizeRequest.php#L346

    $data['confirmation_method'] = 'manual';

    ^ it always sets this paramater value to 'manual' - see full description in Stripe docs: https://stripe.com/docs/api/payment_intents/create#create_payment_intent-confirmation_method . For what you want to do you should use 'automatic' instead

  2. To change this parameter, without creating own custom class that extends \Omnipay\Stripe\Message\PaymentIntents\AuthorizeRequest , you can do like i described in https://github.com/thephpleague/omnipay-stripe/issues/152#issuecomment-530711215 but you need to add this line:

    $data['confirmation_method'] = 'automatic';

    before the line with thats sends custom $data - so before:

    /** @var \Omnipay\Stripe\Message\PaymentIntents\Response $paymentResponse */
    $paymentResponse = $paymentIntentAuthorizeRequest->sendData($data);

This will cause that PaymentIntent will be automatically confirmed after customer finishes "3D Secure 2" authentication on client side (or if such authentication is not needed) - similar as is described in "Step 4" here: https://stripe.com/docs/payments/accept-a-payment#web-submit-payment . In that guide they say to use this javascripts method : https://stripe.com/docs/stripe-js/reference#stripe-confirm-card-payment

Then of course you need to implement your "Webhook endpoint" on your server etc. For that you need to use Stripe PHP SDK (as @jannejava mentioned above https://github.com/thephpleague/omnipay-stripe/issues/161#issuecomment-552802002 ) or you can build some simpler custom solution basing on "webhook related code" from that Stripe PHP SDK repository. See more in docs:

https://stripe.com/docs/webhooks https://stripe.com/docs/webhooks/build https://stripe.com/docs/payments/accept-a-payment#web-fulfillment