woocommerce-paypal-payments/modules/ppcp-applepay/resources/js/ApplepayButton.js

589 lines
25 KiB
JavaScript
Raw Normal View History

2023-08-31 12:48:01 +02:00
import ContextHandlerFactory from "./Context/ContextHandlerFactory";
import {createAppleErrors} from "./Helper/applePayError";
2023-09-08 12:29:23 +02:00
import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
import FormValidator from "../../../ppcp-button/resources/js/modules/Helper/FormValidator";
import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
2023-08-31 12:48:01 +02:00
class ApplepayButton {
constructor(context, externalHandler, buttonConfig, ppcpConfig) {
this.isInitialized = false;
this.context = context;
this.externalHandler = externalHandler;
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.paymentsClient = null;
this.form_saved = false;
2023-08-31 12:48:01 +02:00
this.contextHandler = ContextHandlerFactory.create(
this.context,
this.buttonConfig,
this.ppcpConfig
);
this.updated_contact_info = []
this.selectedShippingMethod = []
2023-10-26 16:33:00 +01:00
this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value
2023-10-13 14:36:11 +01:00
this.log = function() {
if ( this.buttonConfig.is_debug ) {
2023-10-13 14:36:11 +01:00
console.log('[ApplePayButton]', ...arguments);
}
}
this.refreshContextData();
2023-08-31 12:48:01 +02:00
}
init(config) {
if (this.isInitialized) {
return;
}
2023-10-20 18:13:09 +01:00
this.log('Init', this.context);
2023-09-08 12:29:23 +02:00
this.initEventHandlers();
2023-08-31 12:48:01 +02:00
this.isInitialized = true;
this.applePayConfig = config;
const isEligible = this.applePayConfig.isEligible;
if (isEligible) {
2023-09-07 09:56:46 +02:00
this.fetchTransactionInfo().then(() => {
2023-09-08 12:52:19 +02:00
const isSubscriptionProduct = this.ppcpConfig.data_client_id.has_subscriptions === true;
if (isSubscriptionProduct) {
2023-09-08 12:52:19 +02:00
return;
}
2023-09-07 09:56:46 +02:00
this.addButton();
2023-09-10 13:21:19 +02:00
const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper;
const id = "#apple-" + this.buttonConfig.button.wrapper;
if (this.context === 'mini-cart') {
document.querySelector(id_minicart)?.addEventListener('click', (evt) => {
2023-09-10 13:21:19 +02:00
evt.preventDefault();
this.onButtonClick();
});
} else {
document.querySelector(id)?.addEventListener('click', (evt) => {
2023-09-10 13:21:19 +02:00
evt.preventDefault();
this.onButtonClick();
});
}
// Listen for changes on any input within the WooCommerce checkout form
jQuery('form.checkout').on('change', 'input, select, textarea', () => {
this.fetchTransactionInfo();
});
2023-09-07 09:56:46 +02:00
});
}
2023-08-31 12:48:01 +02:00
}
2023-10-20 18:13:09 +01:00
reinit() {
if (!this.applePayConfig) {
return;
}
this.isInitialized = false;
this.init(this.applePayConfig);
}
2023-09-07 09:56:46 +02:00
async fetchTransactionInfo() {
this.transactionInfo = await this.contextHandler.transactionInfo();
}
2023-09-08 12:29:23 +02:00
/**
* Returns configurations relative to this button context.
*/
contextConfig() {
let config = {
wrapper: this.buttonConfig.button.wrapper,
ppcpStyle: this.ppcpConfig.button.style,
//buttonStyle: this.buttonConfig.button.style,
ppcpButtonWrapper: this.ppcpConfig.button.wrapper
}
if (this.context === 'mini-cart') {
config.wrapper = this.buttonConfig.button.mini_cart_wrapper;
config.ppcpStyle = this.ppcpConfig.button.mini_cart_style;
config.buttonStyle = this.buttonConfig.button.mini_cart_style;
config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper;
}
2023-08-31 12:48:01 +02:00
2023-09-08 12:29:23 +02:00
if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) {
config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway';
}
return config;
}
initEventHandlers() {
const { wrapper, ppcpButtonWrapper } = this.contextConfig();
2023-09-18 12:34:34 +02:00
const wrapper_id = '#' + wrapper;
2023-09-08 12:29:23 +02:00
const syncButtonVisibility = () => {
const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper);
2023-09-18 12:34:34 +02:00
setVisible(wrapper_id, $ppcpButtonWrapper.is(':visible'));
setEnabled(wrapper_id, !$ppcpButtonWrapper.hasClass('ppcp-disabled'));
2023-09-08 12:29:23 +02:00
}
jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => {
if (jQuery(data.selector).is(ppcpButtonWrapper)) {
syncButtonVisibility();
}
2023-08-31 12:48:01 +02:00
});
2023-09-08 12:29:23 +02:00
syncButtonVisibility();
2023-08-31 12:48:01 +02:00
}
2023-09-08 12:29:23 +02:00
applePaySession(paymentRequest) {
2023-10-20 18:13:09 +01:00
this.log('applePaySession', paymentRequest);
const session = new ApplePaySession(4, paymentRequest)
session.begin()
if (this.buttonConfig.product.needShipping) {
session.onshippingmethodselected = this.onshippingmethodselected(session)
session.onshippingcontactselected = this.onshippingcontactselected(session)
}
session.onvalidatemerchant = this.onvalidatemerchant(session);
session.onpaymentauthorized = this.onpaymentauthorized(session);
return session;
2023-08-31 12:48:01 +02:00
}
2023-08-31 12:48:01 +02:00
/**
* Add a Apple Pay purchase button
*/
addButton() {
2023-08-31 12:48:01 +02:00
const wrapper =
(this.context === 'mini-cart')
? this.buttonConfig.button.mini_cart_wrapper
: this.buttonConfig.button.wrapper;
2023-09-12 12:08:08 +02:00
const shape =
2023-08-31 12:48:01 +02:00
(this.context === 'mini-cart')
? this.ppcpConfig.button.mini_cart_style.shape
2023-09-12 12:08:08 +02:00
: this.ppcpConfig.button.style.shape;
2023-09-10 13:21:19 +02:00
const appleContainer = this.context === 'mini-cart' ? document.getElementById("applepay-container-minicart") : document.getElementById("applepay-container");
const type = this.buttonConfig.button.type;
const language = this.buttonConfig.button.lang;
const color = this.buttonConfig.button.color;
const id = "apple-" + wrapper;
if (appleContainer) {
appleContainer.innerHTML = `<apple-pay-button id="${id}" buttonstyle="${color}" type="${type}" locale="${language}">`;
}
2023-09-10 13:21:19 +02:00
2023-09-12 14:05:06 +02:00
jQuery('#' + wrapper).addClass('ppcp-button-' + shape);
jQuery(wrapper).append(appleContainer);
2023-08-31 12:48:01 +02:00
}
//------------------------
// Button click
//------------------------
/**
* Show Apple Pay payment sheet when Apple Pay payment button is clicked
*/
async onButtonClick() {
2023-10-20 18:13:09 +01:00
this.log('onButtonClick', this.context);
const paymentDataRequest = this.paymentDataRequest();
// trigger woocommerce validation if we are in the checkout page
if (this.context === 'checkout') {
const checkoutFormSelector = 'form.woocommerce-checkout';
const errorHandler = new ErrorHandler(
PayPalCommerceGateway.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
try {
const formData = new FormData(document.querySelector(checkoutFormSelector));
this.form_saved = Object.fromEntries(formData.entries());
// This line should be reviewed, the paypal.Applepay().confirmOrder fails if we add it.
//this.update_request_data_with_form(paymentDataRequest);
} catch (error) {
console.error(error);
}
const session = this.applePaySession(paymentDataRequest)
const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ?
new FormValidator(
PayPalCommerceGateway.ajax.validate_checkout.endpoint,
PayPalCommerceGateway.ajax.validate_checkout.nonce,
) : null;
if (formValidator) {
try {
const errors = await formValidator.validate(document.querySelector(checkoutFormSelector));
if (errors.length > 0) {
errorHandler.messages(errors);
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
session.abort();
return;
}
} catch (error) {
console.error(error);
}
}
return;
}
this.applePaySession(paymentDataRequest)
2023-08-31 12:48:01 +02:00
}
update_request_data_with_form(paymentDataRequest) {
paymentDataRequest.billingContact = this.fill_billing_contact(this.form_saved);
paymentDataRequest.applicationData = this.fill_application_data(this.form_saved);
if (!this.buttonConfig.product.needShipping) {
return;
}
paymentDataRequest.shippingContact = this.fill_shipping_contact(this.form_saved);
}
paymentDataRequest() {
const applepayConfig = this.applePayConfig
const buttonConfig = this.buttonConfig
2023-09-07 09:56:46 +02:00
let baseRequest = {
countryCode: applepayConfig.countryCode,
merchantCapabilities: applepayConfig.merchantCapabilities,
supportedNetworks: applepayConfig.supportedNetworks,
2023-10-16 15:49:28 +02:00
requiredShippingContactFields: ["postalAddress", "email", "phone"],
requiredBillingContactFields: ["postalAddress", "email", "phone"],
2023-09-05 09:14:20 +02:00
}
2023-09-07 09:56:46 +02:00
const paymentDataRequest = Object.assign({}, baseRequest);
paymentDataRequest.currencyCode = buttonConfig.shop.currencyCode;
paymentDataRequest.total = {
label: buttonConfig.shop.totalLabel,
type: "final",
amount: this.transactionInfo.totalPrice,
2023-08-31 12:48:01 +02:00
}
2023-09-05 09:14:20 +02:00
2023-09-07 09:56:46 +02:00
return paymentDataRequest
2023-08-31 12:48:01 +02:00
}
refreshContextData() {
switch (this.context) {
case 'product':
// Refresh product data that makes the price change.
this.productQuantity = document.querySelector('input.qty').value;
this.products = this.contextHandler.products();
this.log('Products updated', this.products);
break;
}
}
2023-08-31 12:48:01 +02:00
//------------------------
// Payment process
//------------------------
onvalidatemerchant(session) {
2023-10-20 18:13:09 +01:00
this.log('onvalidatemerchant', this.buttonConfig.ajax_url);
return (applePayValidateMerchantEvent) => {
2023-10-20 18:13:09 +01:00
this.log('onvalidatemerchant call');
2023-09-05 09:14:20 +02:00
paypal.Applepay().validateMerchant({
validationUrl: applePayValidateMerchantEvent.validationURL
})
.then(validateResult => {
2023-10-20 18:13:09 +01:00
this.log('onvalidatemerchant ok');
session.completeMerchantValidation(validateResult.merchantSession);
//call backend to update validation to true
2023-09-06 10:47:53 +02:00
jQuery.ajax({
url: this.buttonConfig.ajax_url,
type: 'POST',
data: {
action: 'ppcp_validate',
validation: true,
'woocommerce-process-checkout-nonce': this.nonce,
}
})
})
.catch(validateError => {
2023-10-20 18:13:09 +01:00
this.log('onvalidatemerchant error', validateError);
console.error(validateError);
//call backend to update validation to false
2023-09-05 09:14:20 +02:00
jQuery.ajax({
url: this.buttonConfig.ajax_url,
type: 'POST',
data: {
action: 'ppcp_validate',
validation: false,
2023-09-06 10:47:53 +02:00
'woocommerce-process-checkout-nonce': this.nonce,
2023-09-05 09:14:20 +02:00
}
})
2023-10-20 18:13:09 +01:00
this.log('onvalidatemerchant session abort');
session.abort();
2023-08-31 12:48:01 +02:00
});
};
}
onshippingmethodselected(session) {
2023-10-20 18:13:09 +01:00
this.log('onshippingmethodselected', this.buttonConfig.ajax_url);
const ajax_url = this.buttonConfig.ajax_url
return (event) => {
2023-10-20 18:13:09 +01:00
this.log('onshippingmethodselected call');
const data = this.getShippingMethodData(event);
2023-09-05 09:14:20 +02:00
jQuery.ajax({
url: ajax_url,
2023-09-05 09:14:20 +02:00
method: 'POST',
data: data,
success: (applePayShippingMethodUpdate, textStatus, jqXHR) => {
2023-10-20 18:13:09 +01:00
this.log('onshippingmethodselected ok');
let response = applePayShippingMethodUpdate.data
if (applePayShippingMethodUpdate.success === false) {
response.errors = createAppleErrors(response.errors)
}
this.selectedShippingMethod = event.shippingMethod
//order the response shipping methods, so that the selected shipping method is the first one
let orderedShippingMethods = response.newShippingMethods.sort((a, b) => {
if (a.label === this.selectedShippingMethod.label) {
return -1
}
return 1
})
//update the response.newShippingMethods with the ordered shipping methods
response.newShippingMethods = orderedShippingMethods
if (applePayShippingMethodUpdate.success === false) {
response.errors = createAppleErrors(response.errors)
2023-08-31 12:48:01 +02:00
}
session.completeShippingMethodSelection(response)
},
error: (jqXHR, textStatus, errorThrown) => {
2023-10-20 18:13:09 +01:00
this.log('onshippingmethodselected error', textStatus);
console.warn(textStatus, errorThrown)
session.abort()
},
})
};
2023-08-31 12:48:01 +02:00
}
onshippingcontactselected(session) {
2023-10-20 18:13:09 +01:00
this.log('onshippingcontactselected', this.buttonConfig.ajax_url);
const ajax_url = this.buttonConfig.ajax_url
return (event) => {
2023-10-20 18:13:09 +01:00
this.log('onshippingcontactselected call');
const data = this.getShippingContactData(event);
2023-09-05 09:14:20 +02:00
jQuery.ajax({
url: ajax_url,
2023-09-05 09:14:20 +02:00
method: 'POST',
data: data,
success: (applePayShippingContactUpdate, textStatus, jqXHR) => {
2023-10-20 18:13:09 +01:00
this.log('onshippingcontactselected ok');
let response = applePayShippingContactUpdate.data
this.updated_contact_info = event.shippingContact
if (applePayShippingContactUpdate.success === false) {
response.errors = createAppleErrors(response.errors)
}
if (response.newShippingMethods) {
this.selectedShippingMethod = response.newShippingMethods[0]
}
2023-10-20 19:07:07 +01:00
session.completeShippingContactSelection(response)
},
error: (jqXHR, textStatus, errorThrown) => {
2023-10-20 18:13:09 +01:00
this.log('onshippingcontactselected error', textStatus);
console.warn(textStatus, errorThrown)
session.abort()
},
})
};
}
getShippingContactData(event) {
const product_id = this.buttonConfig.product.id;
this.refreshContextData();
switch (this.context) {
case 'product':
return {
action: 'ppcp_update_shipping_contact',
product_id: product_id,
products: JSON.stringify(this.products),
caller_page: 'productDetail',
product_quantity: this.productQuantity,
simplified_contact: event.shippingContact,
need_shipping: this.buttonConfig.product.needShipping,
'woocommerce-process-checkout-nonce': this.nonce,
};
case 'cart':
case 'checkout':
case 'cart-block':
case 'checkout-block':
2023-09-10 13:21:19 +02:00
case 'mini-cart':
return {
action: 'ppcp_update_shipping_contact',
simplified_contact: event.shippingContact,
caller_page: 'cart',
need_shipping: this.buttonConfig.product.needShipping,
'woocommerce-process-checkout-nonce': this.nonce,
};
}
}
getShippingMethodData(event) {
const product_id = this.buttonConfig.product.id;
this.refreshContextData();
switch (this.context) {
case 'product': return {
action: 'ppcp_update_shipping_method',
shipping_method: event.shippingMethod,
product_id: product_id,
products: JSON.stringify(this.products),
caller_page: 'productDetail',
product_quantity: this.productQuantity,
simplified_contact: this.updated_contact_info,
'woocommerce-process-checkout-nonce': this.nonce,
}
case 'cart':
case 'checkout':
case 'cart-block':
case 'checkout-block':
2023-09-10 13:21:19 +02:00
case 'mini-cart':
return {
action: 'ppcp_update_shipping_method',
shipping_method: event.shippingMethod,
caller_page: 'cart',
simplified_contact: this.updated_contact_info,
'woocommerce-process-checkout-nonce': this.nonce,
}
}
}
2023-08-31 12:48:01 +02:00
onpaymentauthorized(session) {
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized');
2023-10-16 15:49:28 +02:00
return async (event) => {
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized call');
function form() {
return document.querySelector('form.cart');
2023-08-31 12:48:01 +02:00
}
const processInWooAndCapture = async (data) => {
2023-10-16 15:49:28 +02:00
return new Promise((resolve, reject) => {
try {
const billingContact = data.billing_contact
const shippingContact = data.shipping_contact
let request_data = {
action: 'ppcp_create_order',
2023-10-16 15:49:28 +02:00
'caller_page': this.context,
'product_id': this.buttonConfig.product.id ?? null,
'products': JSON.stringify(this.products),
2023-10-16 15:49:28 +02:00
'product_quantity': this.productQuantity ?? null,
'shipping_contact': shippingContact,
'billing_contact': billingContact,
'token': event.payment.token,
2023-10-16 15:49:28 +02:00
'shipping_method': this.selectedShippingMethod,
'woocommerce-process-checkout-nonce': this.nonce,
'funding_source': 'applepay',
'_wp_http_referer': '/?wc-ajax=update_order_review',
'paypal_order_id': data.paypal_order_id,
2023-10-16 15:49:28 +02:00
};
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized request', this.buttonConfig.ajax_url, data);
2023-10-16 15:49:28 +02:00
jQuery.ajax({
url: this.buttonConfig.ajax_url,
method: 'POST',
data: request_data,
complete: (jqXHR, textStatus) => {
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized complete');
2023-10-16 15:49:28 +02:00
},
success: (authorizationResult, textStatus, jqXHR) => {
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized ok');
2023-10-16 15:49:28 +02:00
resolve(authorizationResult)
},
error: (jqXHR, textStatus, errorThrown) => {
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized error', textStatus);
2023-10-16 15:49:28 +02:00
reject(new Error(errorThrown));
},
})
} catch (error) {
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized catch', error);
2023-10-16 15:49:28 +02:00
console.log(error) // handle error
}
});
}
let id = await this.contextHandler.createOrder();
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized paypal order ID', id, event.payment.token, event.payment.billingContact);
2023-10-16 15:49:28 +02:00
try {
const confirmOrderResponse = await paypal.Applepay().confirmOrder({
orderId: id,
token: event.payment.token,
billingContact: event.payment.billingContact,
});
2023-10-20 18:13:09 +01:00
this.log('onpaymentauthorized confirmOrderResponse', confirmOrderResponse);
2023-10-16 15:49:28 +02:00
if (confirmOrderResponse && confirmOrderResponse.approveApplePayPayment) {
if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") {
try {
let data = {
billing_contact: event.payment.billingContact,
shipping_contact: event.payment.shippingContact,
paypal_order_id: id,
};
let authorizationResult = await processInWooAndCapture(data);
if (authorizationResult.result === "success") {
2023-10-16 15:49:28 +02:00
session.completePayment(ApplePaySession.STATUS_SUCCESS)
window.location.href = authorizationResult.redirect
} else {
2023-10-16 15:49:28 +02:00
session.completePayment(ApplePaySession.STATUS_FAILURE)
}
2023-10-16 15:49:28 +02:00
} catch (error) {
session.completePayment(ApplePaySession.STATUS_FAILURE);
session.abort()
2023-10-16 15:49:28 +02:00
console.error(error);
}
2023-10-16 15:49:28 +02:00
} else {
console.error('Error status is not APPROVED');
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
2023-10-16 15:49:28 +02:00
} else {
console.error('Invalid confirmOrderResponse');
session.completePayment(ApplePaySession.STATUS_FAILURE);
}
} catch (error) {
console.error('Error confirming order with applepay token', error);
session.completePayment(ApplePaySession.STATUS_FAILURE);
session.abort()
2023-10-16 15:49:28 +02:00
}
};
2023-08-31 12:48:01 +02:00
}
fill_billing_contact(form_saved) {
return {
givenName: form_saved.billing_first_name ?? '',
familyName: form_saved.billing_last_name ?? '',
emailAddress: form_saved.billing_email ?? '',
phoneNumber: form_saved.billing_phone ?? '',
addressLines: [form_saved.billing_address_1, form_saved.billing_address_2],
locality: form_saved.billing_city ?? '',
postalCode: form_saved.billing_postcode ?? '',
countryCode: form_saved.billing_country ?? '',
administrativeArea: form_saved.billing_state ?? '',
}
}
fill_shipping_contact(form_saved) {
if (form_saved.shipping_first_name === "") {
return this.fill_billing_contact(form_saved)
}
return {
givenName: (form_saved?.shipping_first_name && form_saved.shipping_first_name !== "") ? form_saved.shipping_first_name : form_saved?.billing_first_name,
familyName: (form_saved?.shipping_last_name && form_saved.shipping_last_name !== "") ? form_saved.shipping_last_name : form_saved?.billing_last_name,
emailAddress: (form_saved?.shipping_email && form_saved.shipping_email !== "") ? form_saved.shipping_email : form_saved?.billing_email,
phoneNumber: (form_saved?.shipping_phone && form_saved.shipping_phone !== "") ? form_saved.shipping_phone : form_saved?.billing_phone,
addressLines: [form_saved.shipping_address_1 ?? '', form_saved.shipping_address_2 ?? ''],
locality: (form_saved?.shipping_city && form_saved.shipping_city !== "") ? form_saved.shipping_city : form_saved?.billing_city,
postalCode: (form_saved?.shipping_postcode && form_saved.shipping_postcode !== "") ? form_saved.shipping_postcode : form_saved?.billing_postcode,
countryCode: (form_saved?.shipping_country && form_saved.shipping_country !== "") ? form_saved.shipping_country : form_saved?.billing_country,
administrativeArea: (form_saved?.shipping_state && form_saved.shipping_state !== "") ? form_saved.shipping_state : form_saved?.billing_state,
}
}
fill_application_data(form_saved) {
const jsonString = JSON.stringify(form_saved);
let utf8Str = encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (match, p1) => {
return String.fromCharCode('0x' + p1);
});
return btoa(utf8Str);
}
2023-08-31 12:48:01 +02:00
}
export default ApplepayButton;