UNPKG

@salla.sa/applepay

Version:
442 lines (364 loc) 17.2 kB
import Http from './http'; import DetectOS from './DetectOS'; import { mutateShipmentAddress } from './utils'; window.Salla = window.Salla || {}; window.Salla.Payments = window.Salla.Payments || {}; /** * Full Example * * Salla.event.createAndDispatch('payments::apple-pay.start-transaction', { * amount: 1000, * validateMerchant: { * url: '{{ route('cp.marketplace.cart.pay', ['cart' => $cart]) }}', * // onFailed: (response) => { * // laravel.ajax.errorHandler(response); * // this.onCancel({}, response.data.error.message); * // }, * // onSuccess: (response) => { * // laravel.ajax.successHandler(response); * // } * }, * authorized: { * url: '{{ route('cp.marketplace.cart.confirm', ['cart' => $cart]) }}', * // onFailed: (response) => { * // laravel.ajax.errorHandler(response); * // this.onCancel({}, response.data.error.message); * // }, * // onSuccess: (response) => { * // // nothing * // } * }, * // onError: function (message) { * // laravel.alert(message); * // } * }); */ window.SallaApplePay = { session: null, detail: null, address_id: null, shipping_methods: [], total: undefined, request: undefined, id: undefined, init: function () { document.removeEventListener('payments::apple-pay.start-transaction', SallaApplePay.startSession); Salla.event.addEventListener('payments::apple-pay.start-transaction', SallaApplePay.startSession); }, initDefault: function () { if (!SallaApplePay.detail.onError) { SallaApplePay.detail.onError = function (message) { salla.notify.error(message); }; } if (!SallaApplePay.detail.authorized.onFailed) { SallaApplePay.detail.authorized.onFailed = (response) => { salla.logger.log(JSON.stringify(response)) salla.api.handleErrorResponse(response); SallaApplePay.onCancel({}, response.data.error.message); }; } if (!SallaApplePay.detail.validateMerchant.onFailed) { SallaApplePay.detail.validateMerchant.onFailed = (response) => { salla.logger.log(JSON.stringify(response)) salla.api.handleErrorResponse(response); SallaApplePay.onCancel({}, response.data.error.message); }; } if (!SallaApplePay.detail.authorized.onSuccess) { SallaApplePay.detail.authorized.onSuccess = (response) => { salla.logger.log(JSON.stringify(response)) salla.api.handleAfterResponseActions(response); }; } }, prepareLineItems: function () { if(!SallaApplePay.detail?.items?.length){ SallaApplePay.detail.items = [ { label: salla.lang.get('pages.cart.items_total'), amount: SallaApplePay.detail.amount } ] } return SallaApplePay.detail.items; }, prepareTotal: function () { return { // apple ask to use business name label: window.location.hostname || 'Salla', //label: salla.lang.get('pages.cart.final_total'), amount: SallaApplePay.detail.amount } }, startSession: async function (event) { SallaApplePay.detail = event.detail || event; salla.log('🍏 Pay: payments::apple-pay.start-transaction', SallaApplePay.detail); SallaApplePay.initDefault(); let version = SallaApplePay.getApplePaySessionVersion(); let supportedNetworks = SallaApplePay.detail.supportedNetworks || ['masterCard', 'visa']; if (version === 5) { supportedNetworks.push('mada'); } SallaApplePay.request = { countryCode: 'SA', supportsCouponCode: true, couponCode: '', currencyCode: SallaApplePay.detail.currency || 'SAR', requiredShippingContactFields: SallaApplePay.detail.requiredShippingContactFields ? SallaApplePay.detail.requiredShippingContactFields : [], merchantCapabilities: ['supports3DS'], supportedNetworks: supportedNetworks, supportedCountries: SallaApplePay.detail.supportedCountries || ['SA'], total: SallaApplePay.prepareTotal(), shippingContact: SallaApplePay.detail.shippingContact ? SallaApplePay.detail.shippingContact : {}, shippingMethods: SallaApplePay.detail.shippingMethods && SallaApplePay.detail.shippingMethods.length ? SallaApplePay.mappingShippingMethods(event.detail.shippingMethods) : [], lineItems: SallaApplePay.prepareLineItems() }; salla.log('🍏 Pay: init ', SallaApplePay.request); // https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest SallaApplePay.session = new ApplePaySession(version, SallaApplePay.request); SallaApplePay.session.onshippingcontactselected = SallaApplePay.onShippingContactSelected; SallaApplePay.session.onshippingmethodselected = SallaApplePay.onShippingMethodSelected; SallaApplePay.session.onvalidatemerchant = SallaApplePay.onValidateMerchant; SallaApplePay.session.onpaymentauthorized = SallaApplePay.onPaymentAuthorized; SallaApplePay.session.oncancel = SallaApplePay.onCancel; SallaApplePay.session.oncouponcodechanged = SallaApplePay.oncouponcodechanged; SallaApplePay.session.onpaymentmethodselected = SallaApplePay.onpaymentmethodselected; SallaApplePay.session.begin(); }, async onpaymentmethodselected(event) { await SallaApplePay.recalculateTotal(); const updatedPaymentDetails = { newTotal: SallaApplePay.prepareTotal(), newLineItems: SallaApplePay.prepareLineItems(), }; SallaApplePay.session.completePaymentMethodSelection(updatedPaymentDetails); }, oncouponcodechanged(event) { Salla.event.dispatch('payments::apple-pay.coupon.change', event); return Http.post(SallaApplePay.detail.oncouponcodechanged.url.replace('{id}', SallaApplePay.id), { 'coupon': event.couponCode, 'payment_method': 'apple_pay', }, async ({data}) => { if (typeof SallaApplePay.detail.oncouponcodechanged.onSuccess === 'function') { SallaApplePay.detail.oncouponcodechanged.onSuccess(data); } salla.log('🍏 Pay: Coupon applied success'); await SallaApplePay.recalculateTotal(); SallaApplePay.session.completeCouponCodeChange({ newTotal: SallaApplePay.prepareTotal(), newLineItems: SallaApplePay.prepareLineItems() }); }, async (error) => { let response = error?.response; Salla.event.dispatch('payments::apple-pay.coupon.failed', response); // SallaApplePay.abortSession(); if (typeof SallaApplePay.detail.oncouponcodechanged.onFailed === 'function') { SallaApplePay.detail.oncouponcodechanged.onFailed(response); } await SallaApplePay.recalculateTotal(); SallaApplePay.session.completeCouponCodeChange({ newTotal: SallaApplePay.prepareTotal(), newLineItems: SallaApplePay.prepareLineItems(), errors: [new window.ApplePayError('couponCodeInvalid')] }); }); }, onCancel: (event = {}, message = null) => { SallaApplePay.detail.onError(message || salla.lang.get('pages.checkout.payment_failed')); Salla.event.createAndDispatch('payments::apple-pay.canceled', event); }, /** * Confirm payment after authorization. * * @param event */ onPaymentAuthorized: async (event) => { salla.logger.log('🍏 Pay: onPaymentAuthorized', event.payment); // Update the payment address await mutateShipmentAddress(SallaApplePay, event.payment.shippingContact, true); Salla.event.dispatch('payments::apple-pay.authorized.init', event); Http.post(SallaApplePay.detail.authorized.url.replace('{id}', SallaApplePay.id), { payment_method: 'apple_pay', applepay_token: JSON.stringify(event.payment) }, ({data}) => { Salla.event.dispatch('payments::apple-pay.authorized.success', data); SallaApplePay.session.completePayment(ApplePaySession.STATUS_SUCCESS); if (typeof SallaApplePay.detail.authorized.onSuccess === 'function') { SallaApplePay.detail.authorized.onSuccess(data); } }, (error) => { let response = error?.response; Salla.event.dispatch('payments::apple-pay.authorized.failed', response); SallaApplePay.session.completePayment({ status: ApplePaySession.STATUS_FAILURE, errors: [new ApplePayError("unknown", undefined, response?.data?.error?.message || response?.data?.error?.code || 'Failed to parse authorized response')] }) if (typeof SallaApplePay.detail.authorized.onFailed === 'function') { SallaApplePay.detail.authorized.onFailed(response); } }); }, /** * Validate Submit. * * @param event */ onValidateMerchant: async (event) => { try { // Dispatch event to initialize Apple Pay merchant validation Salla.event.dispatch('payments::apple-pay.validate-merchant.init', event); // Post request to validate merchant const { data } = await Http.post(SallaApplePay.detail.validateMerchant.url.replace('{id}', SallaApplePay.id), { validation_url: event.validationURL }); // Dispatch event on successful merchant validation Salla.event.dispatch('payments::apple-pay.validate-merchant.success', data); // Define a function to handle the completion of merchant validation const completeMerchantValidation = (responseData) => { SallaApplePay.session.completeMerchantValidation(responseData); }; // Check if onSuccess function is defined in SallaApplePay.detail.validateMerchant if (typeof SallaApplePay.detail.validateMerchant.onSuccess === 'function') { // Call onSuccess function and handle response const response = await SallaApplePay.detail.validateMerchant.onSuccess(data); if (response?.redirect) { // Handle redirect if present window.location = response.redirect; return SallaApplePay.abortValidateMerchant(response); } } completeMerchantValidation(data.data); } catch (error) { // Handle errors console.error(error); SallaApplePay.abortValidateMerchant(error?.response); } }, abortValidateMerchant: (response = null) => { SallaApplePay.abortSession(); Salla.event.dispatch('payments::apple-pay.validate-merchant.failed', response); if (typeof SallaApplePay.detail.validateMerchant.onFailed === 'function') { SallaApplePay.detail.validateMerchant.onFailed(response); } }, /** * Select Shipping Contact * * @param event */ onShippingContactSelected: async (event) => { salla.logger.log('🍏 Pay: onShippingContactSelected', event.shippingContact); // create address for shipping calculation mutateShipmentAddress(SallaApplePay, event.shippingContact); }, /** * Select Shipping Method * * @param event * */ onShippingMethodSelected: async (event) => { salla.logger.log(event); let shipping_ids = event.shippingMethod.identifier.split(','); try { await SallaApplePay.selectApplePayShippingMethod(shipping_ids[0], typeof shipping_ids[1] === 'undefined' ? null : shipping_ids[1]); await SallaApplePay.recalculateTotal(); SallaApplePay.session.completeShippingMethodSelection({ newTotal: SallaApplePay.prepareTotal(), newLineItems: SallaApplePay.prepareLineItems(), }); } catch (error) { salla.logger.warn('🍏 Pay: Failed set the shipping details to api', error); // todo :: find a better handling for error without abort session SallaApplePay.abortSession(); } }, abortSession: () => { if (SallaApplePay.session) { SallaApplePay.session.abort(); } }, getApplePaySessionVersion: () => { const userAgent = navigator.userAgent || navigator.vendor || window.opera; if (userAgent === 'sallapp') { return 5; } // can't handle custom user agent like sallapp let detection = DetectOS.init(); let v = parseFloat(detection.os.version); if (detection.os.name === 'Macintosh') { if (v < 10.142) { return 1; } } else { if (v < 12.11) { return 1; } } return 5; }, recalculateTotal: () => { salla.logger.log('Recalculate Total'); return Http.requestWithSupportAjax(SallaApplePay.detail.recalculateTotal.url.replace('{id}', SallaApplePay.id),{}, 'get').then((data) => { let cart = data.data.initial_data?.cart || data.data.cart; // todo :: enhance response from backend SallaApplePay.detail.amount = cart.total; SallaApplePay.detail.items = (cart.totals || cart.items).map((item) => { return { label: item.title, amount: item.amount === 'مجاني' ? 0 : item.amount.toString().replace('+', ''), }; }) // lets remove last element (final total) SallaApplePay.detail.items.pop(); return data; }).catch((error) => { salla.logger.warn('🍏 Pay: recalculate total failed', error); // general error return error?.response?.data?.message; }); }, selectApplePayShippingMethod: (company_id, private_company_id) => { salla.logger.log('🍏 Pay: select shipping method ', 'company_id : ' + company_id, 'private_company_id: ' + private_company_id); return Http.requestWithSupportAjax(SallaApplePay.detail.shippingMethodSelected.url.replace('{id}', SallaApplePay.id), { address_id: SallaApplePay.address_id, company_id: company_id, private_company_id: private_company_id, payment_method: 'apple_pay' }, 'post').then(() => { if (typeof SallaApplePay.detail. shippingMethodSelected.onSuccess === 'function') { SallaApplePay.detail.shippingMethodSelected.onSuccess(data); } // we don't have any data in this request, lets resolve the promise return true; }).catch((error) => { salla.logger.warn('🍏 Pay: Set shipping method failed', error); if (typeof SallaApplePay.detail.shippingMethodSelected.onFailed === 'function') { SallaApplePay.detail.shippingMethodSelected.onFailed(error); } // parse 422 errors let response = error?.response?.data?.error; // address id is not valid if (response?.data?.fields?.address_id) { return response?.data?.fields?.address_id[0]; } // general error return response?.data?.message; }); }, mappingShippingMethods: methods => methods.map(method => ({ 'label': method.shipping_title, 'amount': method.enable_free_shipping ? 0 : method.ship_cost, 'detail': '', 'identifier': method.ship_id.toString() + (method.private_ship_id ? ',' + method.private_ship_id.toString() : '') })) } //applePay doesn't allow iframes // if (window.ApplePaySession && window.self === window.top && ApplePaySession.canMakePayments()) { if (window.ApplePaySession?.canMakePayments()) { SallaApplePay.init(); } else { // You can hide the Apple Pay button easy with add data-show-if-apple-pay-supported to element like <div data-show-if-apple-pay-supported> document.querySelectorAll('data-show-if-apple-pay-supported').forEach(element => element.style.display = 'none'); }