laravel / cashier-stripe

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.
https://laravel.com/docs/billing
MIT License
2.37k stars 671 forks source link

Cashier charge method for 3d payments #848

Closed ammarax closed 4 years ago

ammarax commented 4 years ago

Description:

When you use a paymentMethod for a single charge with two step transaction, like on 3d payment method, is not possible to verify the success of the transaction. i check on db table and in the request but i found nothing. i tried to find something in the documentation but cachier seems to be usefull only for subscription method.

how can i verify the success of 3D method? i need to imlement by my self throught stripe PHP API?

Steps To Reproduce:


public function open(Request $request) {
    $user = Auth::user();
    $paymentMethodId = $request->input('paymentMethod')
    try {
        $payment = $user->charge($price * 100, $paymentMethodId);
    } catch  (IncompletePayment $exception) {
        return redirect()->route('cashier.payment',
                [$exception->payment->id, 'redirect' => route('payment.close')]);
    }
}

//this method is called on route('payment.close')
public function close(Request $request) {
    //it return nothing from stripe website
    return dd($request->session()->all());
}
driesvints commented 4 years ago

Hmm, this is an excellent question. The thing is that for subscriptions we use the subscription.updated event to update the subscription in the database. This is done so fast that by the time the user navigates back from the button the subscription will already be active. However, we don't keep charges in the database. So there's indeed no way of knowing if a charge was successful or not.

I'm thinking right now of appending a query parameter to the back button. Since all the payment processing is done client side, we can't set anything in storage. So appending a query parameter like payment=successful or payment=canceled. You can then use this on the return to display a message or proceed with any follow-up logic.

The only downside to this that I see is that we'll need to find a way to append the query parameter when you've set a custom url. We'll need to parse the url and set the query parameter dynamically. A bit cumbersome but doable.

ammarax commented 4 years ago

i suggest to make this modifications to laravel/vendor/cashier/resources/views/payement.blade.php

add in the header

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

sobstitute the link "Go back" with

<button class="inline-block w-full px-4 py-3 bg-gray-200 hover:bg-gray-300 text-center text-gray-700 rounded-lg"
        @click="goBack">
        {{ __('Go back')}}
</button>

add to methods script section

methods: {
    confirmPayment: ...
    goBack: function () {
        var self = this;
        axios.post(self.redirect, {
            'paymentProcessed' : self.paymentProcessed,
            'successMessage' : self.successMessage,
            'errorMessage' : self.errorMessage,
            'paymentProcessing' : self.paymentProcessing,
        }).then(function (response) {
            location.href = response.data.url;
        })
        .catch(function (error, status) {
            if (error.response.status === 405) {
                location.href = self.redirect;
            }
        });
    }

Doing this you can support the old code withouth regression. if you allow a POST request to your redirect route, you can check the error, the "goback action" and the success of the request.

here a example code for "one method" controller

public function close(Request $request)
    {
        //For "POST" request
        if ($request->isMethod('post')) {
            if ($request->input('paymentProcessed') == true && $request->input('successMessage') != null) {
                //TODO in case of success
                $saleTransaction = $this->transaction_end($request);
                //return redirect link for success
                //in my case i added the payement_id that i save on my personal table on db
                return response()->json([
                    'url' => route('payment.resume', ['payment_id' => $saleTransaction->id]),
                ]);
            }
            else  {
                //TODO in case of error
                $saleTransaction = $this->transaction_revert($request);
                //return redirect link
                //in my case i added the payement_id that i save on my personal table on db for errors
                return response()->json([
                    'url' => route('payment.pre_request', ['payment_id' => $saleTransaction->id]),
                ]);
            }
        } else {
            //for "GET" method redirect to a view or charge it 
            return view('payment.resume');
        }
    }
driesvints commented 4 years ago

Sent in a PR for this here: https://github.com/laravel/cashier/pull/896