Blade + Alpine JS

Implementing the payment gateways using Alpine JS in the frontend.

Include this Alpine JS component into your application.

export default function useTakaden({ routes, redirects, orderAmount, getPayload, enabledProviders = [], selectedProvider, init = () => { }, ...otherProps }) {
    return {
        orderAmount,
        getPayload,
        getInitiateUrl() {
            return routes.initiate + '/' + this.selectedProvider;
        },
        getExecuteUrl() {
            return routes.execute + '/' + this.selectedProvider;
        },
        getSuccessUrl() {
            return redirects.success + '?' + new URLSearchParams(this.getPayload()).toString();
        },
        getFailureUrl() {
            return redirects.failure + '?' + new URLSearchParams(this.getPayload()).toString();
        },
        getCompleteUrl() {
            return redirects.complete + '?' + new URLSearchParams(this.getPayload()).toString();
        },
        errors: {},
        selectedProvider,
        loading: false,
        initResponse: {},
        paymentProviders: [
            {
                name: 'bkash',
                label: 'bKash',
                enabled: enabledProviders.includes('bkash'),
            },
            {
                name: 'rocket',
                label: 'Rocket',
                enabled: enabledProviders.includes('rocket'),
            },
            {
                name: 'nagad',
                label: 'Nagad',
                enabled: enabledProviders.includes('nagad'),
            },
            {
                name: 'upay',
                label: 'Upay',
                enabled: enabledProviders.includes('upay'),
            },
            {
                name: 'bank_transfer',
                label: 'Bank Transfer',
                enabled: enabledProviders.includes('bank_transfer'),
            },
            {
                name: 'cash',
                label: 'Cash on Checkin ',
                enabled: enabledProviders.includes('cash'),
            },
        ],
        getBkashConfig() {
            return {
                paymentMode: 'checkout',
                paymentRequest: {
                    amount: this.orderAmount,
                    intent: 'authorization',
                },
                createRequest: () => {
                    this.initPayment()
                },
                executeRequestOnAuthorization: () => {
                    this.executePayment({
                        payment_id: this.initResponse.paymentID,
                    }).catch(error => {
                        bKash.execute().onError();
                        console.log('Bkash execution error', error);
                    });
                },
                onClose: () => {
                    console.log('Close Bkash Modal');
                }
            };
        },
        init() {
            bKash.init(this.getBkashConfig());
            this.$watch('orderAmount', () => {
                document.getElementById('bKashFrameWrapper')?.remove();
                bKash.init(this.getBkashConfig())
            });
        },
        handleCheckoutFormSubmit() {
            switch (this.selectedProvider) {
                case 'bkash':
                    document.getElementById('bKash_button').click();
                    break;
                default:
                    this.initPayment();
                    break;
            }
        },
        async initPayment() {
            this.loading = true;
            this.errors = {};
            return axios.post(this.getInitiateUrl(), this.getPayload()).then((response) => {
                if (response.data) {
                    this.initResponse = response.data;
                    this.proceedWithProvider(true, response.data);
                    console.log(response.data);
                } else {
                    throw new Error('Whoops! Something went wrong.');
                }
            }).catch(error => {
                if (error.response?.status === 422) {
                    this.errors = error.response.data.errors;
                } else {
                    this.errors = {
                        0: error.response?.data?.message || error.message,
                    };
                }
                // this.proceedWithProvider(false, error.response);
                throw error;
            }).finally(() => this.loading = false);
        },
        async executePayment(payload) {
            return axios.post(this.getExecuteUrl(), payload)
                .then(response => { window.location.href = this.getSuccessUrl(); })
                .catch(error => {
                    if (error.response?.status === 422) {
                        this.errors = error.response.data.errors;
                    } else {
                        this.errors = {
                            1: error.response?.data?.message || error.message,
                        };
                    }
                    this.proceedWithProvider(false, error.response);
                    throw error;
                });
        },
        async proceedWithProvider(success, responseData, provider = this.selectedProvider) {
            switch (provider) {
                case 'cash':
                    return this.proceedWithCash(success, responseData);
                case 'balance':
                    return this.proceedWithBalance(success, responseData);
                case 'bank_transfer':
                    return this.proceedWithBankTransfer(success, responseData);
                case 'upay':
                    return this.proceedWithUpay(success, responseData);
                case 'bkash':
                    return this.proceedWithBkash(success, responseData);
                case 'nagad':
                    return this.proceedWithNagad(success, responseData);
                case 'rocket':
                    return this.proceedWithRocket(success, responseData);
            }
        },
        proceedWithCash(success, responseData) {
            if (success) {
                return window.location.href = this.getCompleteUrl();
            }
        },
        proceedWithBalance(success, responseData) {
            if (success) {
                return window.location.href = this.getCompleteUrl();
            }
        },
        proceedWithBankTransfer(success, responseData) {
            if (success) {
                return window.location.href = this.getCompleteUrl();
            }
        },
        proceedWithUpay(success, responseData) {
            if (success) {
                return window.location.href = responseData;
            }
        },
        proceedWithBkash(success, responseData) {
            if (success) {
                return bKash.create().onSuccess(responseData);
            }
            bKash.create().onError();
        },
        proceedWithNagad(success, responseData) {
            if (success) {
                return window.location.href = responseData;
            }
            window.location.href = this.getFailureUrl();
        },
        proceedWithRocket(success, responseData) {

        },
        ...otherProps,
    }
}

if (typeof window !== typeof undefined) {
    window.useTakaden = useTakaden;
}

Bkash & SSLCommerz requires JQuery for their payment widget 😒. So, if you want to use either of them you need to include jQuery in the head of your checkout page.

<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

If you want to use Bkash you'll also have to include their script after jQuery.

<script src="{{ config('takaden.providers.bkash.script_url') }}"></script>
// Or
<script src="https://scripts.sandbox.bka.sh/versions/1.2.0-beta/checkout/bKash-checkout-sandbox.js"></script>

Now the main portion of your checkout page:

<div
    class="max-w-6xl mx-auto"
    x-data="takadenCheckout({
        routes: {
            initiate: '{{ route('takaden.checkout.initiate') }}',
            execute: '{{ route('takaden.checkout.execute') }}',
        },
        redirects: {
            success: '{{ route('checkout.success') }}',
            failure: '{{ route('checkout.failure') }}',
            complete: '{{ route('checkout.complete') }}',
        },
        orderAmount: @js($payment->due_total),
        enabledProviders: ['bkash', 'rocket', 'nagad', 'upay', 'bank_transfer', 'cash'],
        selectedProvider: '',
        getPayload() {
            return {
                payment_method: this.selectedProvider,
                orderable_id: @js($payment->id),
                orderable_type: @js($payment::class),
            };
        },
        getPayableAmount() {
            return {{ $payment->due_total }};
        },
        handleCheckoutFormSubmit() {
            switch (this.selectedProvider) {
                case 'bkash':
                    document.getElementById('bKash_button').click();
                    break;
                default:
                    this.initPayment();
                    break;
            }
        },
    })"
>
    <div class="flex flex-col gap-4 lg:flex-row">
        {{-- Payment Info --}}
        <x-payment.info :payment="$payment" />

        {{-- Checkout --}}
        <div class="flex-1 p-4 card">
            <form @submit.prevent="handleCheckoutFormSubmit()">
                <div>
                    <p class="mb-2 text-lg font-semibold">Select Payment Method</p>
                    <div class="flex flex-col gap-2">
                        <template
                            x-for="(provider, i) in paymentProviders"
                            x-key="i"
                        >
                            <label class="inline-flex items-center gap-2">
                                <input
                                    x-model="selectedProvider"
                                    type="radio"
                                    name="payment_provider"
                                    :value="provider.name"
                                >
                                <span x-text="provider.label"></span>
                            </label>
                        </template>
                    </div>
                </div>

                <div class="flex mt-4">
                    <button
                        type="submit"
                        class="btn btn-primary"
                        :disabled="loading"
                    >
                        <i :class="{ 'fa fa-arrow-right': !loading, 'fa fa-circle-notch animate-spin': loading }"></i>
                        <span>Continue</span>
                    </button>
                    {{-- Invisible buttons for triggering popop --}}
                    <button
                        type="button"
                        id="bKash_button"
                        class="invisible w-0 h-0"
                    >Pay with bKash</button>
                </div>

                <template x-if="errors && Object.keys(errors).length > 0">
                    <ul class="mt-4 space-y-1 text-red-500">
                        <template x-for="error in errors">
                            <li x-text="error"></li>
                        </template>
                    </ul>
                </template>
            </form>
        </div>
    </div>
</div>

Last updated