@salla.sa/twilight-components
Version:
Salla Web Component
507 lines (506 loc) • 21 kB
JavaScript
/*!
* Crafted with ❤ by Salla
*/
import { Host, h } from "@stencil/core";
import "@salla.sa/applepay/src/index";
export class SallaQuickBuy {
constructor() {
/**
* Button type.
*
* @type {string}
* @default buy
**/
this.type = 'buy';
/**
* Product type.
*
* @type {string}
**/
this.productType = 'product';
/**
* Product options, if is empty will get the data from the document.querySelector('salla-product-options[product-id="X"]')
*
* @type {object}
* @default {}
*/
this.options = {};
this.quickBuy = salla.lang.get('pages.products.buy_now');
salla.lang.onLoaded(() => {
this.quickBuy = salla.lang.get('pages.products.buy_now');
});
}
async quickBuyHandler() {
// user guest and guest-checkout not enabled
if (salla.config.isGuest() && !this.isGuestCheckout()) {
this.requireLogin.emit({ productId: this.productId });
// todo (low) :: find a way to re-fire the method after success
let afterLoginEvent = "salla-quick-buy::user.logged-in";
salla.event.on(afterLoginEvent, () => this.settlePayment());
salla.api.auth.setAfterLoginEvent(afterLoginEvent);
return salla.auth.event.dispatch('login::open', { withoutReload: true });
}
await this.settlePayment();
}
async settlePayment() {
let optionsElement = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
//make sure all the required options are selected
if (optionsElement && !await optionsElement.reportValidity()) {
this.validationFailed.emit({ productId: this.productId });
return salla.error(salla.lang.get('common.messages.required_fields'));
}
//use this way to get quantity too
let data = this.host.getElementSallaData();
// if the store doesn't have Apple Pay , just create a cart and then redirect to check out page
if (!this.isApplePayActive) {
// return salla.product.buyNow(this.productId, data);
return salla.api.request('checkout/quick-purchase/' + this.productId, data, 'post')
.then(resp => {
if (resp.data.redirect) {
window.location.href = resp.data.redirect;
}
return resp;
});
}
data.is_applepay = true;
if ('append' in data) {
data.append('is_applepay', true);
}
// noinspection TypeScriptValidateJSTypes
salla.event.dispatch('payments::apple-pay.start-transaction', {
amount: this.amount, // 1000
currency: this.currency || 'SAR', // SAR
requiredShippingContactFields: this.getRequiredShippingContactFields(),
shippingMethods: this.isRequireShipping ? [] : undefined,
supportedNetworks: salla.config.get('store.settings.buy_now.networks'),
supportedCountries: salla.config.get('store.settings.buy_now.countries'),
countryCode: salla.config.get('store.store_country') || 'SA',
validateMerchant: {
url: this.validateHost ? `${this.validateHost}/checkout/applepay/validate` : salla.url.get('checkout/applepay/validate'),
onSuccess: (response) => {
if (this.applePayOnly && !this.productId) { // the cart is not passes
if (!this.cartId) {
salla.logger.warn('🍏 Pay: trying to create applePay transaction without cartId/ProductId !');
return Promise.resolve(response);
}
window.SallaApplePay.id = this.cartId;
salla.log('🍏 Pay: create checkout success: with id #' + this.cartId);
return Promise.resolve(response);
}
return salla.api.request('checkout/quick-purchase/' + this.productId, typeof data == 'object' ? data : undefined, 'post', {}).then(response => {
// if is redirect url returned for any reason, lets redirect the user to check out
if (response?.data?.redirect) {
salla.log('🍏 Pay: create checkout success: redirect exits, go to checkout page');
window.location.href = response.data.redirect.url;
return response;
}
// the cart is not ready to complete apply pay session
if (!response?.data?.id) {
salla.logger.warn('🍏 Pay: create checkout success: No id, or redirect');
return response;
}
window.SallaApplePay.id = response.data.id;
salla.log('🍏 Pay: create checkout success: with id #' + window.SallaApplePay.id);
});
}
},
authorized: {
// submit checkout route
url: salla.url.get('checkout/{id}/payments/submit'),
onFailed: (response) => {
window.SallaApplePay.onCancel({}, response?.data?.error?.message || response?.data?.error?.code || salla.lang.get('pages.checkout.payment_failed'));
},
onSuccess: (response) => {
window.location.href = response.redirect.url;
salla.log('🍏 Pay: authorized Success:: redirect to thank you page, order placed');
}
},
shippingMethodSelected: this.isRequireShipping ? {
url: salla.url.get('checkout/{id}/shipping/details'),
} : undefined,
shippingContactSelected: this.isRequireShipping ? {
url: salla.url.get('checkout/{id}/address/add'),
} : undefined,
guestContactSelected: this.isGuestCheckout() ? {
url: salla.url.get('checkout/{id}/customer'),
} : undefined,
onCouponCodeChanged: {
url: salla.url.get('checkout/{id}/coupons')
},
recalculateTotal: {
url: salla.url.get('checkout/{id}/payments/recalculate?payment_method=apple_pay')
},
onError: function (message) {
salla.log(message);
salla.notify.error(message);
}
});
}
isGuestCheckout() {
return salla.config.isGuest() && salla.config.get('store.features').includes('guest-checkout') && this.isPhysicalProduct();
}
getRequiredShippingContactFields() {
let fields = [];
if (this.isRequireShipping) {
fields.push('postalAddress');
}
if (this.isGuestCheckout()) {
fields.push('email', 'phone', 'name');
}
return fields;
}
componentWillLoad() {
console.log('🍏 Pay: Quick Buy Component Loaded');
const canMakePayments = typeof window !== 'undefined' && !!window.ApplePaySession?.canMakePayments?.();
const storeHasApplePay = salla?.config?.get?.('store.settings.payments')?.includes?.('apple_pay') ?? true;
const isSallaGateway = salla?.config?.get?.('store.settings.is_salla_gateway', false) ?? true;
this.isApplePayActive = canMakePayments && storeHasApplePay && isSallaGateway;
const runInit = async () => {
if (!this.currency && salla?.config?.get) {
this.currency = salla.config.get('store.settings.buy_now.multi_currency') ? salla.config.get('user.currency_code') : 'SAR';
}
if (!this.productId && salla?.config?.get && salla.url?.is_page) {
this.productId = salla.config.get('page.id');
}
if (!this.applePayOnly && !this.productId) {
salla?.logger?.warn?.('🍏 Pay: Failed load the quick buy, the product id is missing');
return;
}
if ((!this.amount || !this.isRequireShipping) && this.productId && salla?.product?.getDetails) {
await salla.product.getDetails(this.productId, []).then((response) => {
this.amount = response.data.price;
this.isRequireShipping = response?.data?.is_require_shipping || false;
}).catch((error) => {
salla?.logger?.warn?.('🍏 Pay: Failed load the quick buy, get the product details failed: ', error);
});
}
if (this.type === 'donate' && salla?.event?.on) {
salla.event.on('product-options::donation-changed', (data) => {
if (String(data.id) !== String(this.productId))
return;
this.amount = data.price;
});
}
else if (salla?.url?.is_page?.('product.single') && salla?.product?.event?.onPriceUpdated) {
salla.product.event.onPriceUpdated(response => { this.amount = response.data.price; });
}
this.isApplePayActive = (salla?.helpers?.hasApplePay?.() ?? canMakePayments)
&& (salla?.config?.get?.('store.settings.payments')?.includes?.('apple_pay') ?? storeHasApplePay)
&& (salla?.config?.get?.('store.settings.is_salla_gateway', false) ?? isSallaGateway);
if (!document.getElementById('apple-pay-sdk') && this.isApplePayActive) {
const script = document.createElement('script');
script.src = 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js';
script.setAttribute('id', 'apple-pay-sdk');
script.async = true;
document.body.appendChild(script);
}
};
return new Promise((resolve) => {
if (typeof salla?.onReady === 'function') {
salla.onReady(() => runInit().then(() => resolve(true)));
}
else {
runInit().then(() => resolve(true));
}
});
}
render() {
return h(Host, { key: '76958d08c18982d3f643c65ac1f6119830414d5d' }, this.quickBuyButton());
}
quickBuyButton() {
return h("apple-pay-button", { locale: salla?.config?.get?.('user.language_code') || 'ar', onClick: () => this.quickBuyHandler(), "data-quick-purchase": "applepay", class: "s-quick-buy-apple-pay", "data-is-applepay": "1", buttonstyle: "black", type: this.type });
}
// function to check if product is physical
isPhysicalProduct() {
return this.productType === 'product' || this.productType === 'group_products' || this.productType === 'food';
}
static get is() { return "salla-quick-buy"; }
static get originalStyleUrls() {
return {
"$": ["salla-quick-buy.scss"]
};
}
static get styleUrls() {
return {
"$": ["salla-quick-buy.css"]
};
}
static get properties() {
return {
"type": {
"type": "string",
"attribute": "type",
"mutable": true,
"complexType": {
"original": "'plain' | 'buy' | 'donate' | 'book' | 'pay' | 'order'",
"resolved": "\"book\" | \"buy\" | \"donate\" | \"order\" | \"pay\" | \"plain\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{string}"
}, {
"name": "default",
"text": "buy"
}],
"text": "Button type."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "'buy'"
},
"productType": {
"type": "string",
"attribute": "product-type",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{string}"
}],
"text": "Product type."
},
"getter": false,
"setter": false,
"reflect": false,
"defaultValue": "'product'"
},
"productId": {
"type": "string",
"attribute": "product-id",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{string}"
}],
"text": "Product ID."
},
"getter": false,
"setter": false,
"reflect": false
},
"cartId": {
"type": "string",
"attribute": "cart-id",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{string}"
}],
"text": "Cart ID, when you need to applePay for existed cart"
},
"getter": false,
"setter": false,
"reflect": false
},
"amount": {
"type": "number",
"attribute": "amount",
"mutable": true,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{number}"
}, {
"name": "default",
"text": "0"
}],
"text": "Product amount in base currency (SAR)."
},
"getter": false,
"setter": false,
"reflect": true
},
"currency": {
"type": "string",
"attribute": "currency",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{string}"
}, {
"name": "default",
"text": "SAR"
}],
"text": "base currency"
},
"getter": false,
"setter": false,
"reflect": false
},
"options": {
"type": "unknown",
"attribute": "options",
"mutable": false,
"complexType": {
"original": "{}",
"resolved": "{}",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{object}"
}, {
"name": "default",
"text": "{}"
}],
"text": "Product options, if is empty will get the data from the document.querySelector('salla-product-options[product-id=\"X\"]')"
},
"getter": false,
"setter": false,
"defaultValue": "{}"
},
"isRequireShipping": {
"type": "boolean",
"attribute": "is-require-shipping",
"mutable": true,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{boolean}"
}],
"text": "To be passed to purchaseNow request"
},
"getter": false,
"setter": false,
"reflect": false
},
"applePayOnly": {
"type": "boolean",
"attribute": "apple-pay-only",
"mutable": true,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{boolean}"
}],
"text": "Show Apple Pay only"
},
"getter": false,
"setter": false,
"reflect": false
},
"validateHost": {
"type": "string",
"attribute": "validate-host",
"mutable": true,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{string}"
}],
"text": "Custom host for Apple Pay validate merchant URL.\nWhen set, the validate URL will use this host instead of the store URL."
},
"getter": false,
"setter": false,
"reflect": false
}
};
}
static get states() {
return {
"isApplePayActive": {},
"quickBuy": {}
};
}
static get events() {
return [{
"method": "validationFailed",
"name": "validationFailed",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when Apple Pay button is clicked but form validation fails."
},
"complexType": {
"original": "{ productId: string }",
"resolved": "{ productId: string; }",
"references": {}
}
}, {
"method": "requireLogin",
"name": "requireLogin",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when Apple Pay button is clicked but user is not logged in."
},
"complexType": {
"original": "{ productId: string }",
"resolved": "{ productId: string; }",
"references": {}
}
}];
}
static get elementRef() { return "host"; }
}