FLUX-SE / PayumStripe

Payum Stripe gateways (with SCA support)
MIT License
28 stars 15 forks source link

SetupIntent without charging an amount #8

Open zspine opened 3 years ago

zspine commented 3 years ago

Hi!

I am using this library (stripe_js) in a production setup and am in the process of migrating all the payments to SCA ready.

I am trying to use the Create Setup Intent API to save a card before charge the customer. But I couldn't find any examples or documentation to understand the implementation steps.

Any suggestions greatly appreciated, Thank you

Prometee commented 3 years ago

Hi @zspine,

For stripe_checkout_session : Capturing cards for later payment is pretty easy, if you will handle only setup intent with this gateway the only thing to do is to make your own ConvertPaymentAction and replace the definition of the original one.

The array to build will required this kind of data :

// @see https://github.com/FLUX-SE/PayumStripe/blob/master/src/Action/ConvertPaymentAction.php#L24-L37
// ^-- Replace those lines with the code bellow
$details = [
    'payment_method_types' => ['card'],
    'mode' => 'setup',
    // even if this field is empty, it's required by this lib but not by Stripe
    // @see https://github.com/FLUX-SE/PayumStripe/blob/master/src/Action/CaptureAction.php#L107-L109
    'setup_intent_data' => [],
];
$request->setResult($details);

For stripe_js :

I don't test it but you could create a JsCaptureSetupIntentAction base on src/Action/JsCaptureAction.php and replace the original definition with it.

<?php
declare(strict_types=1);

namespace FluxSE\PayumStripe\Action;

use FluxSE\PayumStripe\Request\Api\Pay;
use LogicException;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Request\Capture;
use Payum\Core\Security\TokenInterface;
use Stripe\ApiResource;
use Stripe\SetupIntent;

class JsCaptureSetupIntentAction extends CaptureAction
{
    protected function createCaptureResource(ArrayObject $model, Capture $request): ApiResource
    {
        // This class CreateSetupIntent should be created by you, it will be the same class as :
        // @see \FluxSE\PayumStripe\Request\Api\Resource\CreatePaymentIntent
        // /!\ The related action have to be also created :
        // @see \FluxSE\PayumStripe\Action\Api\Resource\CreatePaymentIntentAction
        $createSetupIntent = new CreateSetupIntent($model->getArrayCopy());
        $this->gateway->execute($createSetupIntent);

        return $createSetupIntent->getApiResource();
    }

    protected function renderCapture(ApiResource $captureResource, Capture $request): void
    {
        if (false === $captureResource instanceof SetupIntent) {
            throw new LogicException(sprintf('The $captureResource should be a "%s" !', SetupIntent::class));
        }

        $token = $this->getRequestToken($request);
        $actionUrl = $token->getTargetUrl();
        $pay = new Pay($captureResource, $actionUrl);
        $this->gateway->execute($pay);
    }

    public function embedOnModeData(ArrayObject $model, TokenInterface $token, string $modeDataKey): void
    {
        // not need for this on Stripe JS
    }
}

You will also have to make your own JsConvertPaymentAction.php to provide the required $details array to the creation of the SetupIntent.

After this action the PayAction is executed displaying the actual stripe_js html/js template, looking at the difference between PaymentIntent And SetupIntent I think all will be working.

Finally The rest (catching the SetupIntent results from Stripe, etc) is already handle by this lib so it should work.

PS: Because the stripe_checkout_session is already handling payment, setup and subscription; maybe what you are requesting could be built as a possible configuration to the stripe_js gateway or a new payment gateway named stripe_js_setup

zspine commented 3 years ago

@Prometee Hi

Thank you very much taking the time to explain this in detail.

Now I understand how the whole thing works. Amazing!!! I have spent several hours digging the source code to have an understanding of the whole functionality and flow.

Thanks again for this awesome library and symfony bundle, it saves lots of time and hassle.

Prometee commented 3 years ago

I know Payum is not easy too at first sight...

@zspine Thank you very much for your support !

Don't forget to get back to me if you succeed, it would be very interesting to add setup intent capabilities to the stripe_js gateway.

Prometee commented 3 years ago

PS: I edited my first comment (small corrections and add)

zspine commented 3 years ago

@Prometee Definitely, I will give it a try and will share the final code if it's successful.

Prometee commented 3 years ago

Hi @zspine !

With @syjust we succeeded making a SetupIntent then triggered a PaymentIntent. We would like to add a new dedicated gateway to handle this specific case and allow it to be used in the bundle and the Sylius plugin. It will also need a specific documentation because this processus need to be handle in two time :

If you have an other behaviour, you can share it with us, it will help making a generic gateway 😉

zspine commented 3 years ago

Hi @Prometee

Perfect timing :) almost like miracle! Thanks a million for the update!

I was on leave for 2 months due to some personal issues. I just started to work and was referring through the code base to refresh the memories. Definitely will try it this week and will give you an update!

Thank you @syjust

Prometee commented 3 years ago

Hi @zspine,

I wanted to give you some update on the evolution of this library, I added PaymentIntent with 'capture_method' => 'manual'. Here is the associated Stripe documentation article: https://stripe.com/docs/payments/capture-later.

I know this isn't exactly what you needed, but now you can easily add a new AuthorizeAction which will create aSetupIntent, then you can create a CaptureAction which will create the associated PaymentIntent when you will need it.

I will let this issue opened because SetupIntent is something which is very important for many projects.