@salla.sa/applepay
Version:
Salla Apple Pay light
442 lines (364 loc) • 17.2 kB
JavaScript
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');
}