Payum / Payum

PHP Payment processing library. It offers everything you need to work with payments: Credit card & offsite purchasing, subscriptions, payouts etc.
https://payum.gitbook.io/payum
MIT License
1.84k stars 339 forks source link

psd2 sca readiness of stripe gateway #804

Open bruno-ds opened 5 years ago

bruno-ds commented 5 years ago

Hello,

I think that at least Stripe gateway will require some work in order to be compatible with the upcoming PSD2 (source).

Do you plan to implement one of the new compatible gateway? Since the time remaining is so short, would it be possible to communicate over a date?

Bonus question: I suppose this is not the only gateway that would require some work. Would it be possible to have a state of the art of the PSD2 readiness of your gateways?

PyRowMan commented 5 years ago

There is indeed something that can be done at least with stripe checkout;

Here is the documentation of the new stripe checkout api : https://stripe.com/docs/payments/checkout/server

It seems that it is now mandatory to provide an article to stripe. but it can be a virtual item.

I've managed to modify my working copy in order make a little proof of concept, and it seems to work without much modifications of the stripe code.

The important thing is only the requirements of "stripe/stripe-php" it would depends at least on the v6.29.0 of the package.

bruno-ds commented 5 years ago

This would be astonishingly easy if it is confirmed to be supported by Stripe. According to Stripe's "client side" documentation, the sku is mandatory, and the "virtual item" is not mentioned.

I immediately send an email to their support! Thank you!

bruno-ds commented 5 years ago

According to Stripe's support team, the "Client integration" can't be used that way, only the "server integration":

Hi Bruno, To accomplish what you are doing, use the Server integration quickstart for checkout. This will allow you to maintain a self-managed product catalog.

I then asked them to confirm that the Client integration is not compatible with self-managed product catalog:

Getting right to it, you are correct. Being that self-managed product catalogs tend to be more complex in terms of Checkout, the more technical Server integration path is properly suited for the job. The Client integration path is designed to be barebones, for simpler processing requests.

@PyRowMan : Since you linked the "server integration" I'm really curious about how you did your integration, if you're allowed to, could you please share your proof of concept?

PyRowMan commented 5 years ago

Hi @bruno-ds,

Here is the files of my Proof of concept; however you have to keep in mind that it's a proof concept, therefore, the code is not really pretty !

This is not fully functional because the return urls are not set, but it should not be that difficult to set and then catch them. Don't forget to use the right version of strip/stripe-php it would depends at least on the v6.29.0 of the package.

// vendor/payum/payum/src/Payum/Stripe/Action/Api/ObtainTokenAction.php

use Stripe\Checkout\Session;
use Stripe\Stripe;

public function execute($request)
{
        /** @var $request ObtainToken */
        RequestNotSupportedException::assertSupports($this, $request);

        $model = ArrayObject::ensureArrayObject($request->getModel());

        if ($model['card']) {
            throw new LogicException('The token has already been set.');
        }

        $getHttpRequest = new GetHttpRequest();
        $this->gateway->execute($getHttpRequest);
        if ($getHttpRequest->method == 'POST' && isset($getHttpRequest->request['stripeToken'])) {
            $model['card'] = $getHttpRequest->request['stripeToken'];

            return;
        }
        Stripe::setApiKey($this->keys->getSecretKey());

        $session = Session::create([
            "success_url" => "https://example.com/success",
            "cancel_url" => "https://example.com/cancel",
            "payment_method_types" => ["card"],
            "line_items" => [[
                "name" => $model['description'],
//                "description" => $model['description'],
                "amount" => $model['amount'],
                "currency" => $model['currency'],
                "quantity" => 1
            ]]
        ]);
        $this->gateway->execute($renderTemplate = new RenderTemplate($this->templateName, array(
            'model' => $model,
            'publishable_key' => $this->keys->getPublishableKey(),
            'actionUrl' => $request->getToken() ? $request->getToken()->getTargetUrl() : null,
            "session_id" => $session->id
        )));

        throw new HttpResponse($renderTemplate->getResult());
}
{# vendor/payum/payum/src/Payum/Stripe/Resources/views/Action/obtain_checkout_token.html.twig #}
{% block payum_body %}
    {{ parent() }}
    <button id="stripe-button-el">Purchase</button>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script type="text/javascript" async=false defer=false src="https://js.stripe.com/v3/"></script>
    <script>
            $.getScript("https://js.stripe.com/v3/")
                .done(function (script, textStatus) {
                    var stripe = Stripe('{{ publishable_key }}');
                    stripe.redirectToCheckout({
                        // Make the id field from the Checkout Session creation API response
                        // available to this file, so you can provide it as parameter here
                        // instead of the CHECKOUT_SESSION_ID placeholder.
                        sessionId: '{{session_id}}'
                    }).then(function (result) {
                        // If `redirectToCheckout` fails due to a browser or network
                        // error, display the localized error message to your customer
                        // using `result.error.message`.
                    });
        });

    </script>

{% endblock %}
bruno-ds commented 5 years ago

Thank you, I've started a new gateway project : https://github.com/Combodo/CombodoPayumStripe I should have a few days dedicated to move forward :crossed_fingers:

bruno-ds commented 5 years ago

please find my proposal here : https://github.com/Combodo/CombodoPayumStripe attention, this is an early WIP, it requires two more services on the impementation side.

edit: code sample removed, because they are now maintained here : https://github.com/Combodo/CombodoPayumStripe/tree/master/doc

bruno-ds commented 5 years ago

and of course, you have to create a webhook on stripe's side : image

It should point to the route /payment/notify/unsafe/{gateway} (/payment/notify/unsafe/stripe_checkout_v3)

trag-stripe commented 5 years ago

Hi Payum contributors!

Chris here from Stripe checking in if you plan on supporting European customers once the SCA / PSD2 regulation comes into effect? (Strong Customer Authentication requirement)

We’ve written an overview and we can see in our system Payum plugin users are at risk of getting blocked by the EU's law starting enforcement September 14th https://stripe.com/payments/strong-customer-authentication

Please let me know your plans as customers are actively asking us for SCA-ready solutions.

Thanks,

Chris Traganos Developer Relations @ Stripe

bruno-ds commented 5 years ago

Hello @trag-stripe :hugs: As far as I know, the only gateway SCA ready is this unofficial one: https://github.com/Combodo/CombodoPayumStripe

@PyRowMan and I are actively working on it. If it is possible, I'd love to have your team review (or even contribute to) it!

trag-stripe commented 5 years ago

Will these updates eventually get merged back into the Payum stripe main or are you considering this a fork?

On Tue, Aug 6, 2019 at 12:29 AM OИUЯd da silva notifications@github.com wrote:

Hello @trag-stripe https://github.com/trag-stripe 🤗 As far as I know, the only gateway SAC ready is un unofficial one: https://github.com/Combodo/CombodoPayumStripe

@PyRowMan https://github.com/PyRowMan and I are actively working on it. If it is possible, I'd love to have your team review it!

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Payum/Payum/issues/804?email_source=notifications&email_token=ALNGOJMDFNL3JNKQPPLODELQDER7NA5CNFSM4HU4FMSKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD3UF3JY#issuecomment-518544807, or mute the thread https://github.com/notifications/unsubscribe-auth/ALNGOJOUKJPE6J6TPGAXBHLQDER7NANCNFSM4HU4FMSA .

bruno-ds commented 5 years ago

Well, The code has an MIT licence, so The Payum team has the liberty to merge it back, but I cannot speak for them. @makasim : could you please state if you'd agree to do so?

PS: this is definitively not a fork, is is just a gateway (see it as an extension) that enable stripe checkout server v3.

lolmx commented 5 years ago

Hello, i currently have a fork i'm working on here. Basically i just do same things but added an option to choose sca flow. It lacks some independent tests still, but i'm already polishing the integration on my project. If you have any suggestions, feel free to @ me or in the repo :)

Edit: i'll pr it once i'm satisfied with it :D

arnofo commented 5 years ago

Hello,

As the new European rules will be in effect starting from 14 September 2019.

Do you think there is any chance a new Payum release adressing this issue will be available in time ? Or should i start looking for another solution ?

Thank you very much.

dzschille commented 5 years ago

@arnofo you can test the fork of @lolmx over here. If it works he will open a PR here to merge it back.

Info for everyone who gets nervous if everything is ready in 10 days, here are some informations about delayed SCA enforcements made by the EU countries: https://support.stripe.com/questions/strong-customer-authentication-sca-enforcement-date

lolmx commented 5 years ago

I released it as of 1.6.

To easily test it, add in your composer.json

# composer.json
    // ...
    "repositories": [
        {
            "type": "git",
            "url": "https://github.com/lolmx/Stripe.git"
        }
    ],

Then you just have to composer require Payum/Stripe:"^1.6" and add sca_flow => true in your payum config, to begin using SCA payments :)

I'll pr back to this repository pretty soon, waiting for some feedback :)

Edit: don't forget to composer require stripe/stripe-php:"^6", they added new Intent apis in 6.9

dzschille commented 5 years ago

@lolmx that worked well in my app. I just added your steps and than i could successfully pay with Stripes regulatory test cards. Thanks!

hschiebold commented 5 years ago

@lolmx just tried to use your sca implementation but gettting error { ["message"]=> string(142) "A token may not be passed in as a PaymentMethod. Instead, use payment_method_data with type=card and card[token]=*****." ["param"]=> string(14) "payment_method" ["type"]=> string(21) "invalid_request_error" } } }

code snippet from my booking test action:

$storage = $this->getServiceLocator()->get('payum')->getStorage('Application\Model\PaymentDetails'); $details = $storage->create(); $details["amount"] = $total; $details["currency"] = 'EUR'; $details["description"] = $booking->get('bid'); $storage->update($details); $captureToken = $this->getServiceLocator()->get('payum.security.token_factory')->createCaptureToken( 'stripe', $details, 'square/booking/payment_done' );

done action:

$token = $this->getServiceLocator()->get('payum.security.http_request_verifier')->verify($this); $gateway = $this->getServiceLocator()->get('payum')->getGateway($token->getGatewayName()); $gateway->execute($status = new GetHumanStatus($token));

then status gives me the error

can somebody give me a hint what i'am doing wrong

regards Holger

lolmx commented 5 years ago

Hello @hschiebold !

Problem comes from your JS, you have to use new method stripe.createPaymentMethod

It tokenises the payment method where old createToken tokenised the card directly

hschiebold commented 5 years ago

@lolmx maybe my problem is that i try to use StripeCheckoutGatewayFactory only and then the referenced template obtain_checkout_token.html.twig with checkout.stripe.com/checkout.js in it is outdated? In StripeJsGatewayFactory the template obtain_js_token_for_strong_customer_authentication.html.twig is used with the newer js api js.stripe.com/v3/ The template for StripeCheckoutGatewayFactory should look like the one from @PyRowMan with the v3 api - am i right or do i missunderstood something? I'm not experienced with payum - only try to use it for a simple tennis court booking app for my tennis club. regards Holger

Arvi89 commented 5 years ago

Hi! It doesn't work for me either, I get the setPublishableKey is not a function, which makes sense as it has been removed for v3, so I don't understand how it can work for you guys ^^ https://stackoverflow.com/questions/49448364/stripe-setpublishablekey-is-not-a-function-error

Sybio commented 5 years ago

Same problem than @hschiebold :s

And the method used in vendor\payum\stripe\Payum\Stripe\Resources\views\Action\obtain_js_token_for_strong_customer_authentication.html.twig line 60 :

Stripe.createPaymentMethod('card', $form, stripeResponseHandler);

prepareAction :

public function stripePrepareAction(Request $request, $amount)
    {
        $amount *= 100;
        $gatewayName = 'stripe_checkout';
        $storage = $this->get('payum')->getStorage(Payment::class);

        $payment = $storage->create();
        $payment->setNumber(uniqid());
        $payment->setCurrencyCode('EUR');
        $payment->setTotalAmount($amount);
        $payment->setDescription('The product');

        $storage->update($payment);

        return $this->redirect($this->get('payum')->getTokenFactory()->createCaptureToken(
            $gatewayName,
            $payment,
            'fp.payment.stripe_execute'
        )->getTargetUrl());
    }

executeAction :

public function stripeExecuteAction(Request $request)
    {
        $token = $this->get('payum')->getHttpRequestVerifier()->verify($request);
        $gateway = $this->get('payum')->getGateway($token->getGatewayName());
        $gateway->execute($status = new GetHumanStatus($token));
        $this->get('payum')->getHttpRequestVerifier()->invalidate($token);
        $identity = $token->getDetails();
        $payumPayment = $this->get('payum')->getStorage($identity->getClass())->find($identity);

        if ($status->isCaptured() || $status->isAuthorized()) { // Success
            $this->addFlash(
                'message.success',
                'Success'
            );
        } elseif ($status->isPending()) { // Pending
            $this->addFlash(
                'message.fail',
                'Waiting approbation.'
            );
        } else { // Failed
            $this->addFlash(
                'message.fail',
                'Payment rejected.'
            );
        }

        return $this->redirectToRoute('fp.payment.ended');
    }

stripe_pay.html.twig :

<form action="{{ actionUrl|default('') }}" method="POST" class="payment-form">
                        <script
                            src="https://checkout.stripe.com/checkout.js" class="stripe-button"
                            data-key="{{ stripePublicKey }}"
                            data-image="{{ asset('img/paiement/logo-150x150.jpg') }}"
                            data-name="mywebsite.com"
                            data-description="{{ model.description|default("") }}"
                            data-amount="{{ model.amount }}"
                            data-currency="{{ model.currency|default("EUR") }}"
                            data-locale="fr"
                            data-label="Payer par carte bancaire - {{ model.amount / 100}}€"
                        >
                        </script>
                    </form>

My config :

payum:
    security:
        token_storage:
            AppBundle\Entity\PaymentToken: { doctrine: orm }
    storages:
        AppBundle\Entity\Payment: { doctrine: orm }
    gateways:
        stripe_checkout:
            factory: stripe_checkout
            publishable_key: %stripe_public_key%
            secret_key: %stripe_secret_key%
            sca_flow: true
            payum.template.obtain_token: "front/payment/stripe_pay.html.twig"

Versions used :

lolmx commented 5 years ago

@hschiebold @Arvi89 @Sybio Indeed, I forget to update the default template to use the new method stripe.createPaymentMethod, it's working for us because we override the template used with a custom one with said method.

I've just merged a fix to update the default template. If you can try again using master branch it should be good now. If all's good, i'll release a new patch version, let me know.

If you have other problems can you open issues on the fork please? It'll be easier for me to not forgot some comments :)

lolmx commented 5 years ago

Here i'm back! I would need some feedbacks about stripe version support. I opened a new discussion, any input is greatly appreciated 👍

francesco-laricchia commented 4 years ago

Any update on this topic? Lolmx fork is still the most recommended one?

bruno-ds commented 4 years ago

Any update on this topic? Lolmx fork is still the most recommended one?

Personally, I use mine in production : https://packagist.org/packages/combodo/stripe-v3 From what I know it is used by some other Sylius stores and by raw Symfony projects.

Prometee commented 4 years ago

For those who need a gateway using Stripe Session Checkout I made a bundle and a Sylius plugin to use it : https://github.com/Prometee/PayumStripeCheckoutSessionBundle https://github.com/Prometee/SyliusPayumStripeCheckoutSessionPlugin

All based on the lib : https://github.com/Prometee/PayumStripe