mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
🔀 Merge branch 'trunk'
# Conflicts: # modules/ppcp-googlepay/resources/js/GooglepayButton.js
This commit is contained in:
commit
fe39793e9a
86 changed files with 6565 additions and 2433 deletions
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
|
"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"wc": true
|
"wc": true,
|
||||||
|
"jQuery": "readonly"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": ["error", { "allow": ["warn", "error"] }]
|
"no-console": ["error", { "allow": ["warn", "error"] }]
|
||||||
|
|
1
.megaignore
Normal file
1
.megaignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-s:*
|
20
.psalm/wc-bookings.php
Normal file
20
.psalm/wc-bookings.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and create a new booking manually.
|
||||||
|
*
|
||||||
|
* @version 1.10.7
|
||||||
|
* @see WC_Booking::new_booking() for available $new_booking_data args
|
||||||
|
* @param int $product_id you are booking
|
||||||
|
* @param array $new_booking_data
|
||||||
|
* @param string $status
|
||||||
|
* @param bool $exact If false, the function will look for the next available block after your start date if the date is unavailable.
|
||||||
|
* @return mixed WC_Booking object on success or false on fail
|
||||||
|
*/
|
||||||
|
function create_wc_booking( $product_id, $new_booking_data = array(), $status = 'confirmed', $exact = false ) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the product is a booking product, false if not
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function is_wc_booking_product( $product ) {}
|
|
@ -1444,7 +1444,6 @@ return array(
|
||||||
'CN' => array(
|
'CN' => array(
|
||||||
'mastercard' => array(),
|
'mastercard' => array(),
|
||||||
'visa' => array(),
|
'visa' => array(),
|
||||||
'amex' => array(),
|
|
||||||
),
|
),
|
||||||
'CY' => array(
|
'CY' => array(
|
||||||
'mastercard' => array(),
|
'mastercard' => array(),
|
||||||
|
|
|
@ -269,7 +269,7 @@ return array(
|
||||||
'classes' => array( 'ppcp-field-indent' ),
|
'classes' => array( 'ppcp-field-indent' ),
|
||||||
'class' => array(),
|
'class' => array(),
|
||||||
'input_class' => array( 'wc-enhanced-select' ),
|
'input_class' => array( 'wc-enhanced-select' ),
|
||||||
'default' => 'pay',
|
'default' => 'plain',
|
||||||
'options' => PropertiesDictionary::button_types(),
|
'options' => PropertiesDictionary::button_types(),
|
||||||
'screens' => array( State::STATE_ONBOARDED ),
|
'screens' => array( State::STATE_ONBOARDED ),
|
||||||
'gateway' => 'dcc',
|
'gateway' => 'dcc',
|
||||||
|
|
|
@ -52,3 +52,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ppc-button-ppcp-applepay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/* global ApplePaySession */
|
||||||
|
/* global PayPalCommerceGateway */
|
||||||
|
|
||||||
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
||||||
import { createAppleErrors } from './Helper/applePayError';
|
import { createAppleErrors } from './Helper/applePayError';
|
||||||
import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
|
import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
|
||||||
|
@ -7,18 +10,95 @@ import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler
|
||||||
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
|
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
|
||||||
import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons';
|
import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons';
|
||||||
|
|
||||||
class ApplepayButton {
|
/**
|
||||||
constructor( context, externalHandler, buttonConfig, ppcpConfig ) {
|
* Plugin-specific styling.
|
||||||
apmButtonsInit( ppcpConfig );
|
*
|
||||||
|
* Note that most properties of this object do not apply to the Apple Pay button.
|
||||||
|
*
|
||||||
|
* @typedef {Object} PPCPStyle
|
||||||
|
* @property {string} shape - Outline shape.
|
||||||
|
* @property {?number} height - Button height in pixel.
|
||||||
|
*/
|
||||||
|
|
||||||
this.isInitialized = false;
|
/**
|
||||||
|
* Style options that are defined by the Apple Pay SDK and are required to render the button.
|
||||||
|
*
|
||||||
|
* @typedef {Object} ApplePayStyle
|
||||||
|
* @property {string} type - Defines the button label.
|
||||||
|
* @property {string} color - Button color
|
||||||
|
* @property {string} lang - The locale; an empty string will apply the user-agent's language.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of valid context values that the button can have.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
const CONTEXT = {
|
||||||
|
Product: 'product',
|
||||||
|
Cart: 'cart',
|
||||||
|
Checkout: 'checkout',
|
||||||
|
PayNow: 'pay-now',
|
||||||
|
MiniCart: 'mini-cart',
|
||||||
|
BlockCart: 'cart-block',
|
||||||
|
BlockCheckout: 'checkout-block',
|
||||||
|
Preview: 'preview',
|
||||||
|
|
||||||
|
// Block editor contexts.
|
||||||
|
Blocks: [ 'cart-block', 'checkout-block' ],
|
||||||
|
|
||||||
|
// Custom gateway contexts.
|
||||||
|
Gateways: [ 'checkout', 'pay-now' ],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A payment button for Apple Pay.
|
||||||
|
*
|
||||||
|
* On a single page, multiple Apple Pay buttons can be displayed, which also means multiple
|
||||||
|
* ApplePayButton instances exist. A typical case is on the product page, where one Apple Pay button
|
||||||
|
* is located inside the minicart-popup, and another pay-now button is in the product context.
|
||||||
|
*/
|
||||||
|
class ApplePayButton {
|
||||||
|
/**
|
||||||
|
* Whether the payment button is initialized.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#isInitialized = false;
|
||||||
|
|
||||||
|
#wrapperId = '';
|
||||||
|
#ppcpButtonWrapperId = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context describes the button's location on the website and what details it submits.
|
||||||
|
*
|
||||||
|
* @type {''|'product'|'cart'|'checkout'|'pay-now'|'mini-cart'|'cart-block'|'checkout-block'|'preview'}
|
||||||
|
*/
|
||||||
|
context = '';
|
||||||
|
|
||||||
|
externalHandler = null;
|
||||||
|
buttonConfig = null;
|
||||||
|
ppcpConfig = null;
|
||||||
|
paymentsClient = null;
|
||||||
|
formData = null;
|
||||||
|
contextHandler = null;
|
||||||
|
updatedContactInfo = [];
|
||||||
|
selectedShippingMethod = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores initialization data sent to the button.
|
||||||
|
*/
|
||||||
|
initialPaymentRequest = null;
|
||||||
|
|
||||||
|
constructor( context, externalHandler, buttonConfig, ppcpConfig ) {
|
||||||
|
this._initDebug( !! buttonConfig?.is_debug );
|
||||||
|
|
||||||
|
apmButtonsInit( ppcpConfig );
|
||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.externalHandler = externalHandler;
|
this.externalHandler = externalHandler;
|
||||||
this.buttonConfig = buttonConfig;
|
this.buttonConfig = buttonConfig;
|
||||||
this.ppcpConfig = ppcpConfig;
|
this.ppcpConfig = ppcpConfig;
|
||||||
this.paymentsClient = null;
|
|
||||||
this.formData = null;
|
|
||||||
|
|
||||||
this.contextHandler = ContextHandlerFactory.create(
|
this.contextHandler = ContextHandlerFactory.create(
|
||||||
this.context,
|
this.context,
|
||||||
|
@ -26,36 +106,226 @@ class ApplepayButton {
|
||||||
this.ppcpConfig
|
this.ppcpConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
this.updatedContactInfo = [];
|
|
||||||
this.selectedShippingMethod = [];
|
|
||||||
this.nonce =
|
|
||||||
document.getElementById( 'woocommerce-process-checkout-nonce' )
|
|
||||||
?.value || buttonConfig.nonce;
|
|
||||||
|
|
||||||
// Stores initialization data sent to the button.
|
|
||||||
this.initialPaymentRequest = null;
|
|
||||||
|
|
||||||
// Default eligibility status.
|
|
||||||
this.isEligible = true;
|
|
||||||
|
|
||||||
this.log = function () {
|
|
||||||
if ( this.buttonConfig.is_debug ) {
|
|
||||||
//console.log('[ApplePayButton]', ...arguments);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.refreshContextData();
|
this.refreshContextData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOOP log function to avoid errors when debugging is disabled.
|
||||||
|
*/
|
||||||
|
log() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables debugging tools, when the button's is_debug flag is set.
|
||||||
|
*
|
||||||
|
* @param {boolean} enableDebugging If debugging features should be enabled for this instance.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_initDebug( enableDebugging ) {
|
||||||
|
if ( ! enableDebugging || this.#isInitialized ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Debug helpers
|
|
||||||
jQuery( document ).on( 'ppcp-applepay-debug', () => {
|
|
||||||
console.log( 'ApplePayButton', this.context, this );
|
|
||||||
} );
|
|
||||||
document.ppcpApplepayButtons = document.ppcpApplepayButtons || {};
|
document.ppcpApplepayButtons = document.ppcpApplepayButtons || {};
|
||||||
document.ppcpApplepayButtons[ this.context ] = this;
|
document.ppcpApplepayButtons[ this.context ] = this;
|
||||||
|
|
||||||
|
this.log = ( ...args ) => {
|
||||||
|
console.log( `[ApplePayButton | ${ this.context }]`, ...args );
|
||||||
|
};
|
||||||
|
|
||||||
|
jQuery( document ).on( 'ppcp-applepay-debug', () => {
|
||||||
|
this.log( this );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The nonce for ajax requests.
|
||||||
|
*
|
||||||
|
* @return {string} The nonce value
|
||||||
|
*/
|
||||||
|
get nonce() {
|
||||||
|
const input = document.getElementById(
|
||||||
|
'woocommerce-process-checkout-nonce'
|
||||||
|
);
|
||||||
|
|
||||||
|
return input?.value || this.buttonConfig.nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the current page qualifies to use the Apple Pay button.
|
||||||
|
*
|
||||||
|
* In admin, the button is always eligible, to display an accurate preview.
|
||||||
|
* On front-end, PayPal's response decides if customers can use Apple Pay.
|
||||||
|
*
|
||||||
|
* @return {boolean} True, if the button can be displayed.
|
||||||
|
*/
|
||||||
|
get isEligible() {
|
||||||
|
if ( ! this.#isInitialized ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( CONTEXT.Preview === this.context ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the ApplePaySession is available and accepts payments
|
||||||
|
* This check is required when using Apple Pay SDK v1; canMakePayments() returns false
|
||||||
|
* if the current device is not liked to iCloud or the Apple Wallet is not available
|
||||||
|
* for a different reason.
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
if ( ! window.ApplePaySession?.canMakePayments() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch ( error ) {
|
||||||
|
console.warn( error );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !! this.applePayConfig.isEligible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current payment button should be rendered as a stand-alone gateway.
|
||||||
|
* The return value `false` usually means, that the payment button is bundled with all available
|
||||||
|
* payment buttons.
|
||||||
|
*
|
||||||
|
* The decision depends on the button context (placement) and the plugin settings.
|
||||||
|
*
|
||||||
|
* @return {boolean} True, if the current button represents a stand-alone gateway.
|
||||||
|
*/
|
||||||
|
get isSeparateGateway() {
|
||||||
|
return (
|
||||||
|
this.buttonConfig.is_wc_gateway_enabled &&
|
||||||
|
CONTEXT.Gateways.includes( this.context )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the wrapper ID for the current button context.
|
||||||
|
* The ID varies for the MiniCart context.
|
||||||
|
*
|
||||||
|
* @return {string} The wrapper-element's ID (without the `#` prefix).
|
||||||
|
*/
|
||||||
|
get wrapperId() {
|
||||||
|
if ( ! this.#wrapperId ) {
|
||||||
|
let id;
|
||||||
|
|
||||||
|
if ( CONTEXT.MiniCart === this.context ) {
|
||||||
|
id = this.buttonConfig.button.mini_cart_wrapper;
|
||||||
|
} else if ( this.isSeparateGateway ) {
|
||||||
|
id = 'ppc-button-ppcp-applepay';
|
||||||
|
} else {
|
||||||
|
id = this.buttonConfig.button.wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#wrapperId = id.replace( /^#/, '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#wrapperId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the wrapper ID for the ppcpButton
|
||||||
|
*
|
||||||
|
* @return {string} The wrapper-element's ID (without the `#` prefix).
|
||||||
|
*/
|
||||||
|
get ppcpButtonWrapperId() {
|
||||||
|
if ( ! this.#ppcpButtonWrapperId ) {
|
||||||
|
let id;
|
||||||
|
|
||||||
|
if ( CONTEXT.MiniCart === this.context ) {
|
||||||
|
id = this.ppcpConfig.button.mini_cart_wrapper;
|
||||||
|
} else if ( CONTEXT.Blocks.includes( this.context ) ) {
|
||||||
|
id = '#express-payment-method-ppcp-gateway-paypal';
|
||||||
|
} else {
|
||||||
|
id = this.ppcpConfig.button.wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#ppcpButtonWrapperId = id.replace( /^#/, '' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#ppcpButtonWrapperId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the context-relevant PPCP style object.
|
||||||
|
* The style for the MiniCart context can be different.
|
||||||
|
*
|
||||||
|
* The PPCP style are custom style options, that are provided by this plugin.
|
||||||
|
*
|
||||||
|
* @return {PPCPStyle} The style object.
|
||||||
|
*/
|
||||||
|
get ppcpStyle() {
|
||||||
|
if ( CONTEXT.MiniCart === this.context ) {
|
||||||
|
return this.ppcpConfig.button.mini_cart_style;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ppcpConfig.button.style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns default style options that are propagated to and rendered by the Apple Pay button.
|
||||||
|
*
|
||||||
|
* These styles are the official style options provided by the Apple Pay SDK.
|
||||||
|
*
|
||||||
|
* @return {ApplePayStyle} The style object.
|
||||||
|
*/
|
||||||
|
get buttonStyle() {
|
||||||
|
return {
|
||||||
|
type: this.buttonConfig.button.type,
|
||||||
|
lang: this.buttonConfig.button.lang,
|
||||||
|
color: this.buttonConfig.button.color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTML element that wraps the current button
|
||||||
|
*
|
||||||
|
* @return {HTMLElement|null} The wrapper element, or null.
|
||||||
|
*/
|
||||||
|
get wrapperElement() {
|
||||||
|
return document.getElementById( this.wrapperId );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of HTMLElements that belong to the payment button.
|
||||||
|
*
|
||||||
|
* @return {HTMLElement[]} List of payment button wrapper elements.
|
||||||
|
*/
|
||||||
|
get allElements() {
|
||||||
|
const selectors = [];
|
||||||
|
|
||||||
|
// Payment button (Pay now, smart button block)
|
||||||
|
selectors.push( `#${ this.wrapperId }` );
|
||||||
|
|
||||||
|
// Block Checkout: Express checkout button.
|
||||||
|
if ( CONTEXT.Blocks.includes( this.context ) ) {
|
||||||
|
selectors.push( '#express-payment-method-ppcp-applepay' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classic Checkout: Apple Pay gateway.
|
||||||
|
if ( CONTEXT.Gateways.includes( this.context ) ) {
|
||||||
|
selectors.push( '.wc_payment_method.payment_method_ppcp-applepay' );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log( 'Wrapper Elements:', selectors );
|
||||||
|
return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) =>
|
||||||
|
Array.from( document.querySelectorAll( selector ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the main button-wrapper is present in the current DOM.
|
||||||
|
*
|
||||||
|
* @return {boolean} True, if the button context (wrapper element) is found.
|
||||||
|
*/
|
||||||
|
get isPresent() {
|
||||||
|
return this.wrapperElement instanceof HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
init( config ) {
|
init( config ) {
|
||||||
if ( this.isInitialized ) {
|
if ( this.#isInitialized ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,41 +333,35 @@ class ApplepayButton {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log( 'Init', this.context );
|
this.log( 'Init' );
|
||||||
this.initEventHandlers();
|
this.initEventHandlers();
|
||||||
this.isInitialized = true;
|
|
||||||
|
this.#isInitialized = true;
|
||||||
this.applePayConfig = config;
|
this.applePayConfig = config;
|
||||||
this.isEligible =
|
|
||||||
( this.applePayConfig.isEligible && window.ApplePaySession ) ||
|
|
||||||
this.buttonConfig.is_admin;
|
|
||||||
|
|
||||||
if ( this.isEligible ) {
|
if ( ! this.isEligible ) {
|
||||||
this.fetchTransactionInfo().then( () => {
|
this.hide();
|
||||||
this.addButton();
|
|
||||||
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 ) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
this.onButtonClick();
|
|
||||||
} );
|
|
||||||
} else {
|
|
||||||
document
|
|
||||||
.querySelector( id )
|
|
||||||
?.addEventListener( 'click', ( evt ) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
this.onButtonClick();
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
} );
|
|
||||||
} else {
|
} else {
|
||||||
jQuery( '#' + this.buttonConfig.button.wrapper ).hide();
|
// Bail if the button wrapper is not present; handles mini-cart logic on checkout page.
|
||||||
jQuery( '#' + this.buttonConfig.button.mini_cart_wrapper ).hide();
|
if ( ! this.isPresent ) {
|
||||||
jQuery( '#express-payment-method-ppcp-applepay' ).hide();
|
this.log( 'Abort init (no wrapper found)' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.show();
|
||||||
|
|
||||||
|
this.fetchTransactionInfo().then( () => {
|
||||||
|
const button = this.addButton();
|
||||||
|
|
||||||
|
if ( ! button ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener( 'click', ( evt ) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
this.onButtonClick();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,49 +370,51 @@ class ApplepayButton {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isInitialized = false;
|
this.#isInitialized = false;
|
||||||
this.init( this.applePayConfig );
|
this.init( this.applePayConfig );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides all wrappers that belong to this ApplePayButton instance.
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
this.log( 'Hide button' );
|
||||||
|
this.allElements.forEach( ( element ) => {
|
||||||
|
element.style.display = 'none';
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures all wrapper elements of this ApplePayButton instance are visible.
|
||||||
|
*/
|
||||||
|
show() {
|
||||||
|
this.log( 'Show button' );
|
||||||
|
if ( ! this.isPresent ) {
|
||||||
|
this.log( '!! Cannot show button, wrapper is not present' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classic Checkout/PayNow: Make the Apple Pay gateway visible after page load.
|
||||||
|
document
|
||||||
|
.querySelectorAll( 'style#ppcp-hide-apple-pay' )
|
||||||
|
.forEach( ( el ) => el.remove() );
|
||||||
|
|
||||||
|
this.allElements.forEach( ( element ) => {
|
||||||
|
element.style.display = '';
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
async fetchTransactionInfo() {
|
async fetchTransactionInfo() {
|
||||||
this.transactionInfo = await this.contextHandler.transactionInfo();
|
this.transactionInfo = await this.contextHandler.transactionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns configurations relative to this button context.
|
|
||||||
*/
|
|
||||||
contextConfig() {
|
|
||||||
const 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
[ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1
|
|
||||||
) {
|
|
||||||
config.ppcpButtonWrapper =
|
|
||||||
'#express-payment-method-ppcp-gateway-paypal';
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
initEventHandlers() {
|
initEventHandlers() {
|
||||||
const { wrapper, ppcpButtonWrapper } = this.contextConfig();
|
const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`;
|
||||||
const wrapper_id = '#' + wrapper;
|
const wrapperId = `#${ this.wrapperId }`;
|
||||||
|
|
||||||
if ( wrapper_id === ppcpButtonWrapper ) {
|
if ( wrapperId === ppcpButtonWrapper ) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper_id }"`
|
`[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapperId }"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,9 +424,9 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper );
|
const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper );
|
||||||
setVisible( wrapper_id, $ppcpButtonWrapper.is( ':visible' ) );
|
setVisible( wrapperId, $ppcpButtonWrapper.is( ':visible' ) );
|
||||||
setEnabled(
|
setEnabled(
|
||||||
wrapper_id,
|
wrapperId,
|
||||||
! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' )
|
! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' )
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -178,8 +444,9 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts an ApplePay session.
|
* Starts an Apple Pay session.
|
||||||
* @param paymentRequest
|
*
|
||||||
|
* @param {Object} paymentRequest The payment request object.
|
||||||
*/
|
*/
|
||||||
applePaySession( paymentRequest ) {
|
applePaySession( paymentRequest ) {
|
||||||
this.log( 'applePaySession', paymentRequest );
|
this.log( 'applePaySession', paymentRequest );
|
||||||
|
@ -192,6 +459,7 @@ class ApplepayButton {
|
||||||
session.onshippingcontactselected =
|
session.onshippingcontactselected =
|
||||||
this.onShippingContactSelected( session );
|
this.onShippingContactSelected( session );
|
||||||
}
|
}
|
||||||
|
|
||||||
session.onvalidatemerchant = this.onValidateMerchant( session );
|
session.onvalidatemerchant = this.onValidateMerchant( session );
|
||||||
session.onpaymentauthorized = this.onPaymentAuthorized( session );
|
session.onpaymentauthorized = this.onPaymentAuthorized( session );
|
||||||
return session;
|
return session;
|
||||||
|
@ -199,32 +467,38 @@ class ApplepayButton {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an Apple Pay purchase button.
|
* Adds an Apple Pay purchase button.
|
||||||
|
*
|
||||||
|
* @return {HTMLElement|null} The newly created `<apple-pay-button>` element. Null on failure.
|
||||||
*/
|
*/
|
||||||
addButton() {
|
addButton() {
|
||||||
this.log( 'addButton', this.context );
|
this.log( 'addButton' );
|
||||||
|
|
||||||
const { wrapper, ppcpStyle } = this.contextConfig();
|
const wrapper = this.wrapperElement;
|
||||||
|
const style = this.buttonStyle;
|
||||||
|
const id = 'apple-' + this.wrapperId;
|
||||||
|
|
||||||
const appleContainer = document.getElementById( wrapper );
|
if ( ! wrapper ) {
|
||||||
const type = this.buttonConfig.button.type;
|
return null;
|
||||||
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 }">`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const $wrapper = jQuery( '#' + wrapper );
|
const ppcpStyle = this.ppcpStyle;
|
||||||
$wrapper.addClass( 'ppcp-button-' + ppcpStyle.shape );
|
|
||||||
|
wrapper.innerHTML = `<apple-pay-button id='${ id }' buttonstyle='${ style.color }' type='${ style.type }' locale='${ style.lang }' />`;
|
||||||
|
wrapper.classList.add(
|
||||||
|
`ppcp-button-${ ppcpStyle.shape }`,
|
||||||
|
'ppcp-button-apm',
|
||||||
|
'ppcp-button-applepay'
|
||||||
|
);
|
||||||
|
|
||||||
if ( ppcpStyle.height ) {
|
if ( ppcpStyle.height ) {
|
||||||
$wrapper.css(
|
wrapper.style.setProperty(
|
||||||
'--apple-pay-button-height',
|
'--apple-pay-button-height',
|
||||||
`${ ppcpStyle.height }px`
|
`${ ppcpStyle.height }px`
|
||||||
);
|
);
|
||||||
$wrapper.css( 'height', `${ ppcpStyle.height }px` );
|
wrapper.style.height = `${ ppcpStyle.height }px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return wrapper.querySelector( 'apple-pay-button' );
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------
|
//------------------------
|
||||||
|
@ -235,19 +509,21 @@ class ApplepayButton {
|
||||||
* Show Apple Pay payment sheet when Apple Pay payment button is clicked
|
* Show Apple Pay payment sheet when Apple Pay payment button is clicked
|
||||||
*/
|
*/
|
||||||
async onButtonClick() {
|
async onButtonClick() {
|
||||||
this.log( 'onButtonClick', this.context );
|
this.log( 'onButtonClick' );
|
||||||
|
|
||||||
const paymentRequest = this.paymentRequest();
|
const paymentRequest = this.paymentRequest();
|
||||||
|
|
||||||
window.ppcpFundingSource = 'apple_pay'; // Do this on another place like on create order endpoint handler.
|
// Do this on another place like on create order endpoint handler.
|
||||||
|
window.ppcpFundingSource = 'apple_pay';
|
||||||
|
|
||||||
// Trigger woocommerce validation if we are in the checkout page.
|
// Trigger woocommerce validation if we are in the checkout page.
|
||||||
if ( this.context === 'checkout' ) {
|
if ( CONTEXT.Checkout === this.context ) {
|
||||||
const checkoutFormSelector = 'form.woocommerce-checkout';
|
const checkoutFormSelector = 'form.woocommerce-checkout';
|
||||||
const errorHandler = new ErrorHandler(
|
const errorHandler = new ErrorHandler(
|
||||||
PayPalCommerceGateway.labels.error.generic,
|
PayPalCommerceGateway.labels.error.generic,
|
||||||
document.querySelector( '.woocommerce-notices-wrapper' )
|
document.querySelector( '.woocommerce-notices-wrapper' )
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = new FormData(
|
const formData = new FormData(
|
||||||
document.querySelector( checkoutFormSelector )
|
document.querySelector( checkoutFormSelector )
|
||||||
|
@ -269,6 +545,7 @@ class ApplepayButton {
|
||||||
PayPalCommerceGateway.ajax.validate_checkout.nonce
|
PayPalCommerceGateway.ajax.validate_checkout.nonce
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if ( formValidator ) {
|
if ( formValidator ) {
|
||||||
try {
|
try {
|
||||||
const errors = await formValidator.validate(
|
const errors = await formValidator.validate(
|
||||||
|
@ -296,13 +573,13 @@ class ApplepayButton {
|
||||||
/**
|
/**
|
||||||
* If the button should show the shipping fields.
|
* If the button should show the shipping fields.
|
||||||
*
|
*
|
||||||
* @return {false|*}
|
* @return {boolean} True, if shipping fields should be captured by ApplePay.
|
||||||
*/
|
*/
|
||||||
shouldRequireShippingInButton() {
|
shouldRequireShippingInButton() {
|
||||||
return (
|
return (
|
||||||
this.contextHandler.shippingAllowed() &&
|
this.contextHandler.shippingAllowed() &&
|
||||||
this.buttonConfig.product.needShipping &&
|
this.buttonConfig.product.needShipping &&
|
||||||
( this.context !== 'checkout' ||
|
( CONTEXT.Checkout !== this.context ||
|
||||||
this.shouldUpdateButtonWithFormData() )
|
this.shouldUpdateButtonWithFormData() )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -310,10 +587,10 @@ class ApplepayButton {
|
||||||
/**
|
/**
|
||||||
* If the button should be updated with the form addresses.
|
* If the button should be updated with the form addresses.
|
||||||
*
|
*
|
||||||
* @return {boolean}
|
* @return {boolean} True, when Apple Pay data should be submitted to WooCommerce.
|
||||||
*/
|
*/
|
||||||
shouldUpdateButtonWithFormData() {
|
shouldUpdateButtonWithFormData() {
|
||||||
if ( this.context !== 'checkout' ) {
|
if ( CONTEXT.Checkout !== this.context ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -323,29 +600,28 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates how payment completion should be handled if with the context handler default actions.
|
* Indicates how payment completion should be handled if with the context handler default
|
||||||
* Or with ApplePay module specific completion.
|
* actions. Or with Apple Pay module specific completion.
|
||||||
*
|
*
|
||||||
* @return {boolean}
|
* @return {boolean} True, when the Apple Pay data should be submitted to WooCommerce.
|
||||||
*/
|
*/
|
||||||
shouldCompletePaymentWithContextHandler() {
|
shouldCompletePaymentWithContextHandler() {
|
||||||
// Data already handled, ex: PayNow
|
// Data already handled, ex: PayNow
|
||||||
if ( ! this.contextHandler.shippingAllowed() ) {
|
if ( ! this.contextHandler.shippingAllowed() ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use WC form data mode in Checkout.
|
// Use WC form data mode in Checkout.
|
||||||
if (
|
return (
|
||||||
this.context === 'checkout' &&
|
CONTEXT.Checkout === this.context &&
|
||||||
! this.shouldUpdateButtonWithFormData()
|
! this.shouldUpdateButtonWithFormData()
|
||||||
) {
|
);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates ApplePay paymentRequest with form data.
|
* Updates Apple Pay paymentRequest with form data.
|
||||||
* @param paymentRequest
|
*
|
||||||
|
* @param {Object} paymentRequest Object to extend with form data.
|
||||||
*/
|
*/
|
||||||
updateRequestDataWithForm( paymentRequest ) {
|
updateRequestDataWithForm( paymentRequest ) {
|
||||||
if ( ! this.shouldUpdateButtonWithFormData() ) {
|
if ( ! this.shouldUpdateButtonWithFormData() ) {
|
||||||
|
@ -358,8 +634,9 @@ class ApplepayButton {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add custom data.
|
// Add custom data.
|
||||||
// "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder().
|
// "applicationData" is originating a "PayPalApplePayError: An internal server error has
|
||||||
// paymentRequest.applicationData = this.fillApplicationData(this.formData);
|
// occurred" on paypal.Applepay().confirmOrder(). paymentRequest.applicationData =
|
||||||
|
// this.fillApplicationData(this.formData);
|
||||||
|
|
||||||
if ( ! this.shouldRequireShippingInButton() ) {
|
if ( ! this.shouldRequireShippingInButton() ) {
|
||||||
return;
|
return;
|
||||||
|
@ -425,7 +702,8 @@ class ApplepayButton {
|
||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
],
|
],
|
||||||
requiredBillingContactFields: [ 'postalAddress' ], // ApplePay does not implement billing email and phone fields.
|
requiredBillingContactFields: [ 'postalAddress' ], // ApplePay does not implement billing
|
||||||
|
// email and phone fields.
|
||||||
};
|
};
|
||||||
|
|
||||||
if ( ! this.shouldRequireShippingInButton() ) {
|
if ( ! this.shouldRequireShippingInButton() ) {
|
||||||
|
@ -453,14 +731,11 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshContextData() {
|
refreshContextData() {
|
||||||
switch ( this.context ) {
|
if ( CONTEXT.Product === this.context ) {
|
||||||
case 'product':
|
// Refresh product data that makes the price change.
|
||||||
// Refresh product data that makes the price change.
|
this.productQuantity = document.querySelector( 'input.qty' )?.value;
|
||||||
this.productQuantity =
|
this.products = this.contextHandler.products();
|
||||||
document.querySelector( 'input.qty' )?.value;
|
this.log( 'Products updated', this.products );
|
||||||
this.products = this.contextHandler.products();
|
|
||||||
this.log( 'Products updated', this.products );
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,8 +743,36 @@ class ApplepayButton {
|
||||||
// Payment process
|
// Payment process
|
||||||
//------------------------
|
//------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make ajax call to change the verification-status of the current domain.
|
||||||
|
*
|
||||||
|
* @param {boolean} isValid
|
||||||
|
*/
|
||||||
|
adminValidation( isValid ) {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const ignored = fetch( this.buttonConfig.ajax_url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: new URLSearchParams( {
|
||||||
|
action: 'ppcp_validate',
|
||||||
|
'woocommerce-process-checkout-nonce': this.nonce,
|
||||||
|
validation: isValid,
|
||||||
|
} ).toString(),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an event handler that Apple Pay calls when displaying the payment sheet.
|
||||||
|
*
|
||||||
|
* @see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778021-onvalidatemerchant
|
||||||
|
*
|
||||||
|
* @param {Object} session The ApplePaySession object.
|
||||||
|
*
|
||||||
|
* @return {(function(*): void)|*} Callback that runs after the merchant validation
|
||||||
|
*/
|
||||||
onValidateMerchant( session ) {
|
onValidateMerchant( session ) {
|
||||||
this.log( 'onvalidatemerchant', this.buttonConfig.ajax_url );
|
|
||||||
return ( applePayValidateMerchantEvent ) => {
|
return ( applePayValidateMerchantEvent ) => {
|
||||||
this.log( 'onvalidatemerchant call' );
|
this.log( 'onvalidatemerchant call' );
|
||||||
|
|
||||||
|
@ -479,34 +782,15 @@ class ApplepayButton {
|
||||||
validationUrl: applePayValidateMerchantEvent.validationURL,
|
validationUrl: applePayValidateMerchantEvent.validationURL,
|
||||||
} )
|
} )
|
||||||
.then( ( validateResult ) => {
|
.then( ( validateResult ) => {
|
||||||
this.log( 'onvalidatemerchant ok' );
|
|
||||||
session.completeMerchantValidation(
|
session.completeMerchantValidation(
|
||||||
validateResult.merchantSession
|
validateResult.merchantSession
|
||||||
);
|
);
|
||||||
//call backend to update validation to true
|
|
||||||
jQuery.ajax( {
|
this.adminValidation( true );
|
||||||
url: this.buttonConfig.ajax_url,
|
|
||||||
type: 'POST',
|
|
||||||
data: {
|
|
||||||
action: 'ppcp_validate',
|
|
||||||
validation: true,
|
|
||||||
'woocommerce-process-checkout-nonce': this.nonce,
|
|
||||||
},
|
|
||||||
} );
|
|
||||||
} )
|
} )
|
||||||
.catch( ( validateError ) => {
|
.catch( ( validateError ) => {
|
||||||
this.log( 'onvalidatemerchant error', validateError );
|
|
||||||
console.error( validateError );
|
console.error( validateError );
|
||||||
//call backend to update validation to false
|
this.adminValidation( false );
|
||||||
jQuery.ajax( {
|
|
||||||
url: this.buttonConfig.ajax_url,
|
|
||||||
type: 'POST',
|
|
||||||
data: {
|
|
||||||
action: 'ppcp_validate',
|
|
||||||
validation: false,
|
|
||||||
'woocommerce-process-checkout-nonce': this.nonce,
|
|
||||||
},
|
|
||||||
} );
|
|
||||||
this.log( 'onvalidatemerchant session abort' );
|
this.log( 'onvalidatemerchant session abort' );
|
||||||
session.abort();
|
session.abort();
|
||||||
} );
|
} );
|
||||||
|
@ -515,14 +799,14 @@ class ApplepayButton {
|
||||||
|
|
||||||
onShippingMethodSelected( session ) {
|
onShippingMethodSelected( session ) {
|
||||||
this.log( 'onshippingmethodselected', this.buttonConfig.ajax_url );
|
this.log( 'onshippingmethodselected', this.buttonConfig.ajax_url );
|
||||||
const ajax_url = this.buttonConfig.ajax_url;
|
const ajaxUrl = this.buttonConfig.ajax_url;
|
||||||
return ( event ) => {
|
return ( event ) => {
|
||||||
this.log( 'onshippingmethodselected call' );
|
this.log( 'onshippingmethodselected call' );
|
||||||
|
|
||||||
const data = this.getShippingMethodData( event );
|
const data = this.getShippingMethodData( event );
|
||||||
|
|
||||||
jQuery.ajax( {
|
jQuery.ajax( {
|
||||||
url: ajax_url,
|
url: ajaxUrl,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data,
|
data,
|
||||||
success: (
|
success: (
|
||||||
|
@ -537,7 +821,8 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
this.selectedShippingMethod = event.shippingMethod;
|
this.selectedShippingMethod = event.shippingMethod;
|
||||||
|
|
||||||
// Sort the response shipping methods, so that the selected shipping method is the first one.
|
// Sort the response shipping methods, so that the selected shipping method is
|
||||||
|
// the first one.
|
||||||
response.newShippingMethods =
|
response.newShippingMethods =
|
||||||
response.newShippingMethods.sort( ( a, b ) => {
|
response.newShippingMethods.sort( ( a, b ) => {
|
||||||
if (
|
if (
|
||||||
|
@ -565,7 +850,7 @@ class ApplepayButton {
|
||||||
onShippingContactSelected( session ) {
|
onShippingContactSelected( session ) {
|
||||||
this.log( 'onshippingcontactselected', this.buttonConfig.ajax_url );
|
this.log( 'onshippingcontactselected', this.buttonConfig.ajax_url );
|
||||||
|
|
||||||
const ajax_url = this.buttonConfig.ajax_url;
|
const ajaxUrl = this.buttonConfig.ajax_url;
|
||||||
|
|
||||||
return ( event ) => {
|
return ( event ) => {
|
||||||
this.log( 'onshippingcontactselected call' );
|
this.log( 'onshippingcontactselected call' );
|
||||||
|
@ -573,7 +858,7 @@ class ApplepayButton {
|
||||||
const data = this.getShippingContactData( event );
|
const data = this.getShippingContactData( event );
|
||||||
|
|
||||||
jQuery.ajax( {
|
jQuery.ajax( {
|
||||||
url: ajax_url,
|
url: ajaxUrl,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data,
|
data,
|
||||||
success: (
|
success: (
|
||||||
|
@ -603,15 +888,15 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
getShippingContactData( event ) {
|
getShippingContactData( event ) {
|
||||||
const product_id = this.buttonConfig.product.id;
|
const productId = this.buttonConfig.product.id;
|
||||||
|
|
||||||
this.refreshContextData();
|
this.refreshContextData();
|
||||||
|
|
||||||
switch ( this.context ) {
|
switch ( this.context ) {
|
||||||
case 'product':
|
case CONTEXT.Product:
|
||||||
return {
|
return {
|
||||||
action: 'ppcp_update_shipping_contact',
|
action: 'ppcp_update_shipping_contact',
|
||||||
product_id,
|
product_id: productId,
|
||||||
products: JSON.stringify( this.products ),
|
products: JSON.stringify( this.products ),
|
||||||
caller_page: 'productDetail',
|
caller_page: 'productDetail',
|
||||||
product_quantity: this.productQuantity,
|
product_quantity: this.productQuantity,
|
||||||
|
@ -619,11 +904,12 @@ class ApplepayButton {
|
||||||
need_shipping: this.shouldRequireShippingInButton(),
|
need_shipping: this.shouldRequireShippingInButton(),
|
||||||
'woocommerce-process-checkout-nonce': this.nonce,
|
'woocommerce-process-checkout-nonce': this.nonce,
|
||||||
};
|
};
|
||||||
case 'cart':
|
|
||||||
case 'checkout':
|
case CONTEXT.Cart:
|
||||||
case 'cart-block':
|
case CONTEXT.Checkout:
|
||||||
case 'checkout-block':
|
case CONTEXT.BlockCart:
|
||||||
case 'mini-cart':
|
case CONTEXT.BlockCheckout:
|
||||||
|
case CONTEXT.MiniCart:
|
||||||
return {
|
return {
|
||||||
action: 'ppcp_update_shipping_contact',
|
action: 'ppcp_update_shipping_contact',
|
||||||
simplified_contact: event.shippingContact,
|
simplified_contact: event.shippingContact,
|
||||||
|
@ -635,12 +921,12 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
getShippingMethodData( event ) {
|
getShippingMethodData( event ) {
|
||||||
const product_id = this.buttonConfig.product.id;
|
const productId = this.buttonConfig.product.id;
|
||||||
|
|
||||||
this.refreshContextData();
|
this.refreshContextData();
|
||||||
|
|
||||||
switch ( this.context ) {
|
switch ( this.context ) {
|
||||||
case 'product':
|
case CONTEXT.Product:
|
||||||
return {
|
return {
|
||||||
action: 'ppcp_update_shipping_method',
|
action: 'ppcp_update_shipping_method',
|
||||||
shipping_method: event.shippingMethod,
|
shipping_method: event.shippingMethod,
|
||||||
|
@ -650,17 +936,18 @@ class ApplepayButton {
|
||||||
? this.updatedContactInfo
|
? this.updatedContactInfo
|
||||||
: this.initialPaymentRequest?.shippingContact ??
|
: this.initialPaymentRequest?.shippingContact ??
|
||||||
this.initialPaymentRequest?.billingContact,
|
this.initialPaymentRequest?.billingContact,
|
||||||
product_id,
|
product_id: productId,
|
||||||
products: JSON.stringify( this.products ),
|
products: JSON.stringify( this.products ),
|
||||||
caller_page: 'productDetail',
|
caller_page: 'productDetail',
|
||||||
product_quantity: this.productQuantity,
|
product_quantity: this.productQuantity,
|
||||||
'woocommerce-process-checkout-nonce': this.nonce,
|
'woocommerce-process-checkout-nonce': this.nonce,
|
||||||
};
|
};
|
||||||
case 'cart':
|
|
||||||
case 'checkout':
|
case CONTEXT.Cart:
|
||||||
case 'cart-block':
|
case CONTEXT.Checkout:
|
||||||
case 'checkout-block':
|
case CONTEXT.BlockCart:
|
||||||
case 'mini-cart':
|
case CONTEXT.BlockCheckout:
|
||||||
|
case CONTEXT.MiniCart:
|
||||||
return {
|
return {
|
||||||
action: 'ppcp_update_shipping_method',
|
action: 'ppcp_update_shipping_method',
|
||||||
shipping_method: event.shippingMethod,
|
shipping_method: event.shippingMethod,
|
||||||
|
@ -681,9 +968,6 @@ class ApplepayButton {
|
||||||
return async ( event ) => {
|
return async ( event ) => {
|
||||||
this.log( 'onpaymentauthorized call' );
|
this.log( 'onpaymentauthorized call' );
|
||||||
|
|
||||||
function form() {
|
|
||||||
return document.querySelector( 'form.cart' );
|
|
||||||
}
|
|
||||||
const processInWooAndCapture = async ( data ) => {
|
const processInWooAndCapture = async ( data ) => {
|
||||||
return new Promise( ( resolve, reject ) => {
|
return new Promise( ( resolve, reject ) => {
|
||||||
try {
|
try {
|
||||||
|
@ -698,7 +982,7 @@ class ApplepayButton {
|
||||||
( this.initialPaymentRequest.shippingMethods ||
|
( this.initialPaymentRequest.shippingMethods ||
|
||||||
[] )[ 0 ];
|
[] )[ 0 ];
|
||||||
|
|
||||||
const request_data = {
|
const requestData = {
|
||||||
action: 'ppcp_create_order',
|
action: 'ppcp_create_order',
|
||||||
caller_page: this.context,
|
caller_page: this.context,
|
||||||
product_id: this.buttonConfig.product.id ?? null,
|
product_id: this.buttonConfig.product.id ?? null,
|
||||||
|
@ -723,7 +1007,7 @@ class ApplepayButton {
|
||||||
jQuery.ajax( {
|
jQuery.ajax( {
|
||||||
url: this.buttonConfig.ajax_url,
|
url: this.buttonConfig.ajax_url,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: request_data,
|
data: requestData,
|
||||||
complete: ( jqXHR, textStatus ) => {
|
complete: ( jqXHR, textStatus ) => {
|
||||||
this.log( 'onpaymentauthorized complete' );
|
this.log( 'onpaymentauthorized complete' );
|
||||||
},
|
},
|
||||||
|
@ -785,7 +1069,8 @@ class ApplepayButton {
|
||||||
if (
|
if (
|
||||||
this.shouldCompletePaymentWithContextHandler()
|
this.shouldCompletePaymentWithContextHandler()
|
||||||
) {
|
) {
|
||||||
// No shipping, expect immediate capture, ex: PayNow, Checkout with form data.
|
// No shipping, expect immediate capture, ex: PayNow, Checkout with
|
||||||
|
// form data.
|
||||||
|
|
||||||
let approveFailed = false;
|
let approveFailed = false;
|
||||||
await this.contextHandler.approveOrder(
|
await this.contextHandler.approveOrder(
|
||||||
|
@ -960,4 +1245,4 @@ class ApplepayButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ApplepayButton;
|
export default ApplePayButton;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
|
/* global paypal */
|
||||||
import ApplepayButton from './ApplepayButton';
|
|
||||||
|
|
||||||
class ApplepayManager {
|
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
|
||||||
|
import ApplePayButton from './ApplepayButton';
|
||||||
|
|
||||||
|
class ApplePayManager {
|
||||||
constructor( buttonConfig, ppcpConfig ) {
|
constructor( buttonConfig, ppcpConfig ) {
|
||||||
this.buttonConfig = buttonConfig;
|
this.buttonConfig = buttonConfig;
|
||||||
this.ppcpConfig = ppcpConfig;
|
this.ppcpConfig = ppcpConfig;
|
||||||
|
@ -9,7 +11,7 @@ class ApplepayManager {
|
||||||
this.buttons = [];
|
this.buttons = [];
|
||||||
|
|
||||||
buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => {
|
buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => {
|
||||||
const button = new ApplepayButton(
|
const button = new ApplePayButton(
|
||||||
bootstrap.context,
|
bootstrap.context,
|
||||||
bootstrap.handler,
|
bootstrap.handler,
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
|
@ -40,8 +42,7 @@ class ApplepayManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets ApplePay configuration of the PayPal merchant.
|
* Gets Apple Pay configuration of the PayPal merchant.
|
||||||
* @return {Promise<null>}
|
|
||||||
*/
|
*/
|
||||||
async config() {
|
async config() {
|
||||||
this.ApplePayConfig = await paypal.Applepay().config();
|
this.ApplePayConfig = await paypal.Applepay().config();
|
||||||
|
@ -49,4 +50,4 @@ class ApplepayManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ApplepayManager;
|
export default ApplePayManager;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import ApplepayButton from './ApplepayButton';
|
/* global paypal */
|
||||||
|
|
||||||
class ApplepayManagerBlockEditor {
|
import ApplePayButton from './ApplepayButton';
|
||||||
|
|
||||||
|
class ApplePayManagerBlockEditor {
|
||||||
constructor( buttonConfig, ppcpConfig ) {
|
constructor( buttonConfig, ppcpConfig ) {
|
||||||
this.buttonConfig = buttonConfig;
|
this.buttonConfig = buttonConfig;
|
||||||
this.ppcpConfig = ppcpConfig;
|
this.ppcpConfig = ppcpConfig;
|
||||||
|
@ -17,7 +19,7 @@ class ApplepayManagerBlockEditor {
|
||||||
try {
|
try {
|
||||||
this.applePayConfig = await paypal.Applepay().config();
|
this.applePayConfig = await paypal.Applepay().config();
|
||||||
|
|
||||||
const button = new ApplepayButton(
|
const button = new ApplePayButton(
|
||||||
this.ppcpConfig.context,
|
this.ppcpConfig.context,
|
||||||
null,
|
null,
|
||||||
this.buttonConfig,
|
this.buttonConfig,
|
||||||
|
@ -31,4 +33,4 @@ class ApplepayManagerBlockEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ApplepayManagerBlockEditor;
|
export default ApplePayManagerBlockEditor;
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import BaseHandler from './BaseHandler';
|
import BaseHandler from './BaseHandler';
|
||||||
|
|
||||||
class PreviewHandler extends BaseHandler {
|
class PreviewHandler extends BaseHandler {
|
||||||
constructor( buttonConfig, ppcpConfig, externalHandler ) {
|
|
||||||
super( buttonConfig, ppcpConfig, externalHandler );
|
|
||||||
}
|
|
||||||
|
|
||||||
transactionInfo() {
|
transactionInfo() {
|
||||||
// We need to return something as ApplePay button initialization expects valid data.
|
// We need to return something as ApplePay button initialization expects valid data.
|
||||||
return {
|
return {
|
||||||
|
@ -19,7 +15,7 @@ class PreviewHandler extends BaseHandler {
|
||||||
throw new Error( 'Create order fail. This is just a preview.' );
|
throw new Error( 'Create order fail. This is just a preview.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
approveOrder( data, actions ) {
|
approveOrder() {
|
||||||
throw new Error( 'Approve order fail. This is just a preview.' );
|
throw new Error( 'Approve order fail. This is just a preview.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const buttonID = 'applepay-container';
|
export const buttonID = 'ppc-button-applepay-container';
|
||||||
export const endpoints = {
|
export const endpoints = {
|
||||||
validation: '_apple_pay_validation',
|
validation: '_apple_pay_validation',
|
||||||
createOrderCart: '_apple_pay_create_order_cart',
|
createOrderCart: '_apple_pay_create_order_cart',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ApplepayButton from './ApplepayButton';
|
import ApplePayButton from './ApplepayButton';
|
||||||
import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton';
|
import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton';
|
||||||
import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager';
|
import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager';
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ class ApplePayPreviewButton extends PreviewButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
createButton( buttonConfig ) {
|
createButton( buttonConfig ) {
|
||||||
const button = new ApplepayButton(
|
const button = new ApplePayButton(
|
||||||
'preview',
|
'preview',
|
||||||
null,
|
null,
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Help
|
||||||
import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
|
import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
|
||||||
import { loadCustomScript } from '@paypal/paypal-js';
|
import { loadCustomScript } from '@paypal/paypal-js';
|
||||||
import CheckoutHandler from './Context/CheckoutHandler';
|
import CheckoutHandler from './Context/CheckoutHandler';
|
||||||
import ApplepayManager from './ApplepayManager';
|
import ApplePayManager from './ApplepayManager';
|
||||||
import ApplepayManagerBlockEditor from './ApplepayManagerBlockEditor';
|
import ApplePayManagerBlockEditor from './ApplepayManagerBlockEditor';
|
||||||
|
|
||||||
const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
|
const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
|
||||||
const ppcpConfig = ppcpData.scriptData;
|
const ppcpConfig = ppcpData.scriptData;
|
||||||
|
@ -24,8 +24,8 @@ const ApplePayComponent = ( props ) => {
|
||||||
|
|
||||||
const bootstrap = function () {
|
const bootstrap = function () {
|
||||||
const ManagerClass = props.isEditing
|
const ManagerClass = props.isEditing
|
||||||
? ApplepayManagerBlockEditor
|
? ApplePayManagerBlockEditor
|
||||||
: ApplepayManager;
|
: ApplePayManager;
|
||||||
const manager = new ManagerClass( buttonConfig, ppcpConfig );
|
const manager = new ManagerClass( buttonConfig, ppcpConfig );
|
||||||
manager.init();
|
manager.init();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { loadCustomScript } from '@paypal/paypal-js';
|
import { loadCustomScript } from '@paypal/paypal-js';
|
||||||
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
|
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
|
||||||
import ApplepayManager from './ApplepayManager';
|
import ApplePayManager from './ApplepayManager';
|
||||||
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
|
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
|
||||||
|
|
||||||
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
|
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
|
||||||
let manager;
|
let manager;
|
||||||
|
|
||||||
const bootstrap = function () {
|
const bootstrap = function () {
|
||||||
manager = new ApplepayManager( buttonConfig, ppcpConfig );
|
manager = new ApplePayManager( buttonConfig, ppcpConfig );
|
||||||
manager.init();
|
manager.init();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -299,5 +299,15 @@ return array(
|
||||||
esc_html( $button_text )
|
esc_html( $button_text )
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
'applepay.wc-gateway' => static function ( ContainerInterface $container ): ApplePayGateway {
|
||||||
|
return new ApplePayGateway(
|
||||||
|
$container->get( 'wcgateway.order-processor' ),
|
||||||
|
$container->get( 'api.factory.paypal-checkout-url' ),
|
||||||
|
$container->get( 'wcgateway.processor.refunds' ),
|
||||||
|
$container->get( 'wcgateway.transaction-url-provider' ),
|
||||||
|
$container->get( 'session.handler' ),
|
||||||
|
$container->get( 'applepay.url' )
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
231
modules/ppcp-applepay/src/ApplePayGateway.php
Normal file
231
modules/ppcp-applepay/src/ApplePayGateway.php
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* The Apple Pay Payment Gateway
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Applepay
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Applepay;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use WC_Order;
|
||||||
|
use WC_Payment_Gateway;
|
||||||
|
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||||
|
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Exception\PayPalOrderMissingException;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ApplePayGateway
|
||||||
|
*/
|
||||||
|
class ApplePayGateway extends WC_Payment_Gateway {
|
||||||
|
use ProcessPaymentTrait;
|
||||||
|
|
||||||
|
const ID = 'ppcp-applepay';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The processor for orders.
|
||||||
|
*
|
||||||
|
* @var OrderProcessor
|
||||||
|
*/
|
||||||
|
protected $order_processor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function return the PayPal checkout URL for the given order ID.
|
||||||
|
*
|
||||||
|
* @var callable(string):string
|
||||||
|
*/
|
||||||
|
private $paypal_checkout_url_factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Refund Processor.
|
||||||
|
*
|
||||||
|
* @var RefundProcessor
|
||||||
|
*/
|
||||||
|
private $refund_processor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service able to provide transaction url for an order.
|
||||||
|
*
|
||||||
|
* @var TransactionUrlProvider
|
||||||
|
*/
|
||||||
|
protected $transaction_url_provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Session Handler.
|
||||||
|
*
|
||||||
|
* @var SessionHandler
|
||||||
|
*/
|
||||||
|
protected $session_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL to the module.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $module_url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ApplePayGateway constructor.
|
||||||
|
*
|
||||||
|
* @param OrderProcessor $order_processor The Order Processor.
|
||||||
|
* @param callable(string):string $paypal_checkout_url_factory The function return the PayPal
|
||||||
|
* checkout URL for the given order
|
||||||
|
* ID.
|
||||||
|
* @param RefundProcessor $refund_processor The Refund Processor.
|
||||||
|
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction
|
||||||
|
* view URL based on order.
|
||||||
|
* @param SessionHandler $session_handler The Session Handler.
|
||||||
|
* @param string $module_url The URL to the module.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
OrderProcessor $order_processor,
|
||||||
|
callable $paypal_checkout_url_factory,
|
||||||
|
RefundProcessor $refund_processor,
|
||||||
|
TransactionUrlProvider $transaction_url_provider,
|
||||||
|
SessionHandler $session_handler,
|
||||||
|
string $module_url
|
||||||
|
) {
|
||||||
|
$this->id = self::ID;
|
||||||
|
|
||||||
|
$this->method_title = __( 'Apple Pay (via PayPal) ', 'woocommerce-paypal-payments' );
|
||||||
|
$this->method_description = __( 'Display Apple Pay as a standalone payment option instead of bundling it with PayPal.', 'woocommerce-paypal-payments' );
|
||||||
|
|
||||||
|
$this->title = $this->get_option( 'title', __( 'Apple Pay', 'woocommerce-paypal-payments' ) );
|
||||||
|
$this->description = $this->get_option( 'description', '' );
|
||||||
|
|
||||||
|
$this->module_url = $module_url;
|
||||||
|
$this->icon = esc_url( $this->module_url ) . 'assets/images/applepay.png';
|
||||||
|
|
||||||
|
$this->init_form_fields();
|
||||||
|
$this->init_settings();
|
||||||
|
$this->order_processor = $order_processor;
|
||||||
|
$this->paypal_checkout_url_factory = $paypal_checkout_url_factory;
|
||||||
|
$this->refund_processor = $refund_processor;
|
||||||
|
$this->transaction_url_provider = $transaction_url_provider;
|
||||||
|
$this->session_handler = $session_handler;
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'woocommerce_update_options_payment_gateways_' . $this->id,
|
||||||
|
array( $this, 'process_admin_options' )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the form fields.
|
||||||
|
*/
|
||||||
|
public function init_form_fields() {
|
||||||
|
$this->form_fields = array(
|
||||||
|
'enabled' => array(
|
||||||
|
'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => __( 'Enable Apple Pay', 'woocommerce-paypal-payments' ),
|
||||||
|
'default' => 'no',
|
||||||
|
'desc_tip' => true,
|
||||||
|
'description' => __( 'Enable/Disable Apple Pay payment gateway.', 'woocommerce-paypal-payments' ),
|
||||||
|
),
|
||||||
|
'title' => array(
|
||||||
|
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
|
||||||
|
'type' => 'text',
|
||||||
|
'default' => $this->title,
|
||||||
|
'desc_tip' => true,
|
||||||
|
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ),
|
||||||
|
),
|
||||||
|
'description' => array(
|
||||||
|
'title' => __( 'Description', 'woocommerce-paypal-payments' ),
|
||||||
|
'type' => 'text',
|
||||||
|
'default' => $this->description,
|
||||||
|
'desc_tip' => true,
|
||||||
|
'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process payment for a WooCommerce order.
|
||||||
|
*
|
||||||
|
* @param int $order_id The WooCommerce order id.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function process_payment( $order_id ) : array {
|
||||||
|
$wc_order = wc_get_order( $order_id );
|
||||||
|
if ( ! is_a( $wc_order, WC_Order::class ) ) {
|
||||||
|
return $this->handle_payment_failure(
|
||||||
|
null,
|
||||||
|
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_action( 'woocommerce_paypal_payments_before_process_order', $wc_order );
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
$this->order_processor->process( $wc_order );
|
||||||
|
|
||||||
|
do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
|
||||||
|
|
||||||
|
return $this->handle_payment_success( $wc_order );
|
||||||
|
} catch ( PayPalOrderMissingException $exc ) {
|
||||||
|
$order = $this->order_processor->create_order( $wc_order );
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'result' => 'success',
|
||||||
|
'redirect' => ( $this->paypal_checkout_url_factory )( $order->id() ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch ( PayPalApiException $error ) {
|
||||||
|
return $this->handle_payment_failure(
|
||||||
|
$wc_order,
|
||||||
|
new Exception(
|
||||||
|
Messages::generic_payment_error_message() . ' ' . $error->getMessage(),
|
||||||
|
$error->getCode(),
|
||||||
|
$error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch ( Exception $error ) {
|
||||||
|
return $this->handle_payment_failure( $wc_order, $error );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process refund.
|
||||||
|
*
|
||||||
|
* If the gateway declares 'refunds' support, this will allow it to refund.
|
||||||
|
* a passed in amount.
|
||||||
|
*
|
||||||
|
* @param int $order_id Order ID.
|
||||||
|
* @param float $amount Refund amount.
|
||||||
|
* @param string $reason Refund reason.
|
||||||
|
*
|
||||||
|
* @return boolean True or false based on success, or a WP_Error object.
|
||||||
|
*/
|
||||||
|
public function process_refund( $order_id, $amount = null, $reason = '' ) : bool {
|
||||||
|
$order = wc_get_order( $order_id );
|
||||||
|
if ( ! is_a( $order, WC_Order::class ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->refund_processor->process( $order, (float) $amount, (string) $reason );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return transaction url for this gateway and given order.
|
||||||
|
*
|
||||||
|
* @param WC_Order $order WC order to get transaction url by.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function get_transaction_url( $order ) : string {
|
||||||
|
$this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order );
|
||||||
|
|
||||||
|
return parent::get_transaction_url( $order );
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Applepay;
|
namespace WooCommerce\PayPalCommerce\Applepay;
|
||||||
|
|
||||||
|
use WC_Payment_Gateway;
|
||||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
||||||
use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton;
|
use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton;
|
||||||
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
|
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
|
||||||
|
@ -117,6 +118,48 @@ class ApplepayModule implements ModuleInterface {
|
||||||
100,
|
100,
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_filter(
|
||||||
|
'woocommerce_payment_gateways',
|
||||||
|
/**
|
||||||
|
* Param types removed to avoid third-party issues.
|
||||||
|
*
|
||||||
|
* @psalm-suppress MissingClosureParamType
|
||||||
|
*/
|
||||||
|
static function ( $methods ) use ( $c ): array {
|
||||||
|
if ( ! is_array( $methods ) ) {
|
||||||
|
return $methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
|
if ( $settings->has( 'applepay_button_enabled' ) && $settings->get( 'applepay_button_enabled' ) ) {
|
||||||
|
$applepay_gateway = $c->get( 'applepay.wc-gateway' );
|
||||||
|
assert( $applepay_gateway instanceof WC_Payment_Gateway );
|
||||||
|
|
||||||
|
$methods[] = $applepay_gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $methods;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'woocommerce_review_order_after_submit',
|
||||||
|
function () {
|
||||||
|
// Wrapper ID: #ppc-button-ppcp-applepay.
|
||||||
|
echo '<div id="ppc-button-' . esc_attr( ApplePayGateway::ID ) . '"></div>';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
add_action(
|
||||||
|
'woocommerce_pay_order_after_submit',
|
||||||
|
function () {
|
||||||
|
// Wrapper ID: #ppc-button-ppcp-applepay.
|
||||||
|
echo '<div id="ppc-button-' . esc_attr( ApplePayGateway::ID ) . '"></div>';
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -306,7 +349,7 @@ class ApplepayModule implements ModuleInterface {
|
||||||
* @param bool $is_sandbox The environment for this merchant.
|
* @param bool $is_sandbox The environment for this merchant.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function validation_string( bool $is_sandbox ) {
|
public function validation_string( bool $is_sandbox ) : string {
|
||||||
$sandbox_string = $this->sandbox_validation_string();
|
$sandbox_string = $this->sandbox_validation_string();
|
||||||
$live_string = $this->live_validation_string();
|
$live_string = $this->live_validation_string();
|
||||||
return $is_sandbox ? $sandbox_string : $live_string;
|
return $is_sandbox ? $sandbox_string : $live_string;
|
||||||
|
|
|
@ -20,12 +20,13 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
|
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
|
||||||
|
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ApplePayButton
|
* Class ApplePayButton
|
||||||
*/
|
*/
|
||||||
class ApplePayButton implements ButtonInterface {
|
class ApplePayButton implements ButtonInterface {
|
||||||
use RequestHandlerTrait;
|
use RequestHandlerTrait, ContextTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The settings.
|
* The settings.
|
||||||
|
@ -340,7 +341,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
}
|
}
|
||||||
$response = $this->response_templates->apple_formatted_response( $payment_details );
|
$response = $this->response_templates->apple_formatted_response( $payment_details );
|
||||||
$this->response_templates->response_success( $response );
|
$this->response_templates->response_success( $response );
|
||||||
} catch ( \Exception $e ) {
|
} catch ( Exception $e ) {
|
||||||
$this->response_templates->response_with_data_errors(
|
$this->response_templates->response_with_data_errors(
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
@ -382,7 +383,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
}
|
}
|
||||||
$response = $this->response_templates->apple_formatted_response( $payment_details );
|
$response = $this->response_templates->apple_formatted_response( $payment_details );
|
||||||
$this->response_templates->response_success( $response );
|
$this->response_templates->response_success( $response );
|
||||||
} catch ( \Exception $e ) {
|
} catch ( Exception $e ) {
|
||||||
$this->response_templates->response_with_data_errors(
|
$this->response_templates->response_with_data_errors(
|
||||||
array(
|
array(
|
||||||
array(
|
array(
|
||||||
|
@ -399,7 +400,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
* On error returns an array of errors to be handled by the script
|
* On error returns an array of errors to be handled by the script
|
||||||
* On success returns the new order data
|
* On success returns the new order data
|
||||||
*
|
*
|
||||||
* @throws \Exception When validation fails.
|
* @throws Exception When validation fails.
|
||||||
*/
|
*/
|
||||||
public function create_wc_order(): void {
|
public function create_wc_order(): void {
|
||||||
$applepay_request_data_object = $this->applepay_data_object_http();
|
$applepay_request_data_object = $this->applepay_data_object_http();
|
||||||
|
@ -420,15 +421,18 @@ class ApplePayButton implements ButtonInterface {
|
||||||
$applepay_request_data_object->order_data( $context );
|
$applepay_request_data_object->order_data( $context );
|
||||||
|
|
||||||
$this->update_posted_data( $applepay_request_data_object );
|
$this->update_posted_data( $applepay_request_data_object );
|
||||||
|
|
||||||
if ( $context === 'product' ) {
|
if ( $context === 'product' ) {
|
||||||
$cart_item_key = $this->prepare_cart( $applepay_request_data_object );
|
$cart_item_key = $this->prepare_cart( $applepay_request_data_object );
|
||||||
$cart = WC()->cart;
|
$cart = WC()->cart;
|
||||||
$address = $applepay_request_data_object->shipping_address();
|
$address = $applepay_request_data_object->shipping_address();
|
||||||
|
|
||||||
$this->calculate_totals_single_product(
|
$this->calculate_totals_single_product(
|
||||||
$cart,
|
$cart,
|
||||||
$address,
|
$address,
|
||||||
$applepay_request_data_object->shipping_method()
|
$applepay_request_data_object->shipping_method()
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( ! $cart_item_key ) {
|
if ( ! $cart_item_key ) {
|
||||||
$this->response_templates->response_with_data_errors(
|
$this->response_templates->response_with_data_errors(
|
||||||
array(
|
array(
|
||||||
|
@ -438,19 +442,16 @@ class ApplePayButton implements ButtonInterface {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return;
|
} else {
|
||||||
}
|
add_filter(
|
||||||
add_filter(
|
'woocommerce_payment_successful_result',
|
||||||
'woocommerce_payment_successful_result',
|
function ( array $result ) use ( $cart, $cart_item_key ) : array {
|
||||||
function ( array $result ) use ( $cart, $cart_item_key ) : array {
|
$this->clear_current_cart( $cart, $cart_item_key );
|
||||||
if ( ! is_string( $cart_item_key ) ) {
|
$this->reload_cart( $cart );
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
$this->clear_current_cart( $cart, $cart_item_key );
|
);
|
||||||
$this->reload_cart( $cart );
|
}
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WC()->checkout()->process_checkout();
|
WC()->checkout()->process_checkout();
|
||||||
|
@ -460,17 +461,20 @@ class ApplePayButton implements ButtonInterface {
|
||||||
/**
|
/**
|
||||||
* Checks if the nonce in the data object is valid
|
* Checks if the nonce in the data object is valid
|
||||||
*
|
*
|
||||||
* @return bool|int
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function is_nonce_valid(): bool {
|
protected function is_nonce_valid(): bool {
|
||||||
$nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS );
|
$nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS );
|
||||||
if ( ! $nonce ) {
|
if ( ! $nonce ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return wp_verify_nonce(
|
|
||||||
|
// Return value 1 indicates "valid nonce, generated in past 12 hours".
|
||||||
|
// Return value 2 also indicated valid nonce, but older than 12 hours.
|
||||||
|
return 1 === wp_verify_nonce(
|
||||||
$nonce,
|
$nonce,
|
||||||
'woocommerce-process_checkout'
|
'woocommerce-process_checkout'
|
||||||
) === 1;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -511,7 +515,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
$address,
|
$address,
|
||||||
$applepay_request_data_object->shipping_method()
|
$applepay_request_data_object->shipping_method()
|
||||||
);
|
);
|
||||||
if ( is_string( $cart_item_key ) ) {
|
if ( $cart_item_key ) {
|
||||||
$this->clear_current_cart( $cart, $cart_item_key );
|
$this->clear_current_cart( $cart, $cart_item_key );
|
||||||
$this->reload_cart( $cart );
|
$this->reload_cart( $cart );
|
||||||
}
|
}
|
||||||
|
@ -819,9 +823,9 @@ class ApplePayButton implements ButtonInterface {
|
||||||
/**
|
/**
|
||||||
* Removes the old cart, saves it, and creates a new one
|
* Removes the old cart, saves it, and creates a new one
|
||||||
*
|
*
|
||||||
|
* @throws Exception If it cannot be added to cart.
|
||||||
* @param ApplePayDataObjectHttp $applepay_request_data_object The request data object.
|
* @param ApplePayDataObjectHttp $applepay_request_data_object The request data object.
|
||||||
* @return bool | string The cart item key after adding to the new cart.
|
* @return string The cart item key after adding to the new cart.
|
||||||
* @throws \Exception If it cannot be added to cart.
|
|
||||||
*/
|
*/
|
||||||
public function prepare_cart( ApplePayDataObjectHttp $applepay_request_data_object ): string {
|
public function prepare_cart( ApplePayDataObjectHttp $applepay_request_data_object ): string {
|
||||||
$this->save_old_cart();
|
$this->save_old_cart();
|
||||||
|
@ -838,7 +842,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->cart_products->add_products( array( $product ) );
|
$this->cart_products->add_products( array( $product ) );
|
||||||
return $this->cart_products->cart_item_keys()[0];
|
return $this->cart_products->cart_item_keys()[0] ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -949,6 +953,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
$render_placeholder,
|
$render_placeholder,
|
||||||
function () {
|
function () {
|
||||||
$this->applepay_button();
|
$this->applepay_button();
|
||||||
|
$this->hide_gateway_until_eligible();
|
||||||
},
|
},
|
||||||
21
|
21
|
||||||
);
|
);
|
||||||
|
@ -961,6 +966,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
$render_placeholder,
|
$render_placeholder,
|
||||||
function () {
|
function () {
|
||||||
$this->applepay_button();
|
$this->applepay_button();
|
||||||
|
$this->hide_gateway_until_eligible();
|
||||||
},
|
},
|
||||||
21
|
21
|
||||||
);
|
);
|
||||||
|
@ -973,7 +979,7 @@ class ApplePayButton implements ButtonInterface {
|
||||||
add_action(
|
add_action(
|
||||||
$render_placeholder,
|
$render_placeholder,
|
||||||
function () {
|
function () {
|
||||||
echo '<span id="applepay-container-minicart" class="ppcp-button-apm ppcp-button-applepay ppcp-button-minicart"></span>';
|
echo '<span id="ppc-button-applepay-container-minicart" class="ppcp-button-apm ppcp-button-applepay ppcp-button-minicart"></span>';
|
||||||
},
|
},
|
||||||
21
|
21
|
||||||
);
|
);
|
||||||
|
@ -981,24 +987,29 @@ class ApplePayButton implements ButtonInterface {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ApplePay button markup
|
* ApplePay button markup
|
||||||
*/
|
*/
|
||||||
protected function applepay_button(): void {
|
protected function applepay_button(): void {
|
||||||
?>
|
?>
|
||||||
<div id="applepay-container" class="ppcp-button-apm ppcp-button-applepay">
|
<div id="ppc-button-applepay-container" class="ppcp-button-apm ppcp-button-applepay">
|
||||||
<?php wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); ?>
|
<?php wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the module should load the script.
|
* Outputs an inline CSS style that hides the Apple Pay gateway (on Classic Checkout).
|
||||||
|
* The style is removed by `ApplepayButton.js` once the eligibility of the payment method
|
||||||
|
* is confirmed.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function should_load_script(): bool {
|
protected function hide_gateway_until_eligible(): void {
|
||||||
return true;
|
?>
|
||||||
|
<style id="ppcp-hide-apple-pay">.wc_payment_method.payment_method_ppcp-applepay{display:none}</style>
|
||||||
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
* @package WooCommerce\PayPalCommerce\Applepay
|
* @package WooCommerce\PayPalCommerce\Applepay
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare( strict_types = 1 );
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Applepay\Assets;
|
namespace WooCommerce\PayPalCommerce\Applepay\Assets;
|
||||||
|
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
|
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||||
|
use WC_Product;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class DataToAppleButtonScripts
|
* Class DataToAppleButtonScripts
|
||||||
|
@ -33,7 +35,7 @@ class DataToAppleButtonScripts {
|
||||||
/**
|
/**
|
||||||
* DataToAppleButtonScripts constructor.
|
* DataToAppleButtonScripts constructor.
|
||||||
*
|
*
|
||||||
* @param string $sdk_url The URL to the SDK.
|
* @param string $sdk_url The URL to the SDK.
|
||||||
* @param Settings $settings The settings.
|
* @param Settings $settings The settings.
|
||||||
*/
|
*/
|
||||||
public function __construct( string $sdk_url, Settings $settings ) {
|
public function __construct( string $sdk_url, Settings $settings ) {
|
||||||
|
@ -45,57 +47,92 @@ class DataToAppleButtonScripts {
|
||||||
* Sets the appropriate data to send to ApplePay script
|
* Sets the appropriate data to send to ApplePay script
|
||||||
* Data differs between product page and cart page
|
* Data differs between product page and cart page
|
||||||
*
|
*
|
||||||
* @param bool $is_block Whether the button is in a block or not.
|
|
||||||
* @return array
|
* @return array
|
||||||
* @throws NotFoundException When the setting is not found.
|
|
||||||
*/
|
*/
|
||||||
public function apple_pay_script_data( bool $is_block = false ): array {
|
public function apple_pay_script_data() : array {
|
||||||
$base_location = wc_get_base_location();
|
|
||||||
$shop_country_code = $base_location['country'];
|
|
||||||
$currency_code = get_woocommerce_currency();
|
|
||||||
$total_label = get_bloginfo( 'name' );
|
|
||||||
if ( is_product() ) {
|
if ( is_product() ) {
|
||||||
return $this->data_for_product_page(
|
return $this->data_for_product_page();
|
||||||
$shop_country_code,
|
|
||||||
$currency_code,
|
|
||||||
$total_label
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->data_for_cart_page(
|
return $this->data_for_cart_page();
|
||||||
$shop_country_code,
|
|
||||||
$currency_code,
|
|
||||||
$total_label
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the appropriate admin data to send to ApplePay script
|
* Returns the appropriate admin data to send to ApplePay script
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
* @throws NotFoundException When the setting is not found.
|
|
||||||
*/
|
*/
|
||||||
public function apple_pay_script_data_for_admin() : array {
|
public function apple_pay_script_data_for_admin() : array {
|
||||||
|
return $this->data_for_admin_page();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full config array for the Apple Pay integration with default values.
|
||||||
|
*
|
||||||
|
* @param array $product - Optional. Product details for the payment button.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function get_apple_pay_data( array $product = array() ) : array {
|
||||||
|
// true: Use Apple Pay as distinct gateway.
|
||||||
|
// false: integrate it with the smart buttons.
|
||||||
|
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||||
|
$is_wc_gateway_enabled = isset( $available_gateways[ ApplePayGateway::ID ] );
|
||||||
|
|
||||||
|
// use_wc: Use WC checkout data
|
||||||
|
// use_applepay: Use data provided by Apple Pay.
|
||||||
|
$checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' )
|
||||||
|
? $this->settings->get( 'applepay_checkout_data_mode' )
|
||||||
|
: PropertiesDictionary::BILLING_DATA_MODE_DEFAULT;
|
||||||
|
|
||||||
|
// Store country, currency and name.
|
||||||
$base_location = wc_get_base_location();
|
$base_location = wc_get_base_location();
|
||||||
$shop_country_code = $base_location['country'];
|
$shop_country_code = $base_location['country'];
|
||||||
$currency_code = get_woocommerce_currency();
|
$currency_code = get_woocommerce_currency();
|
||||||
$total_label = get_bloginfo( 'name' );
|
$total_label = get_bloginfo( 'name' );
|
||||||
|
|
||||||
return $this->data_for_admin_page(
|
// Button layout (label, color, language).
|
||||||
$shop_country_code,
|
$type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : '';
|
||||||
$currency_code,
|
$color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : '';
|
||||||
$total_label
|
$lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : '';
|
||||||
|
$lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang );
|
||||||
|
$is_enabled = $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' );
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'sdk_url' => $this->sdk_url,
|
||||||
|
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
|
||||||
|
'is_admin' => false,
|
||||||
|
'is_enabled' => $is_enabled,
|
||||||
|
'is_wc_gateway_enabled' => $is_wc_gateway_enabled,
|
||||||
|
'preferences' => array(
|
||||||
|
'checkout_data_mode' => $checkout_data_mode,
|
||||||
|
),
|
||||||
|
'button' => array(
|
||||||
|
'wrapper' => 'ppc-button-applepay-container',
|
||||||
|
'mini_cart_wrapper' => 'ppc-button-applepay-container-minicart',
|
||||||
|
'type' => $type,
|
||||||
|
'color' => $color,
|
||||||
|
'lang' => $lang,
|
||||||
|
),
|
||||||
|
'product' => $product,
|
||||||
|
'shop' => array(
|
||||||
|
'countryCode' => $shop_country_code,
|
||||||
|
'currencyCode' => $currency_code,
|
||||||
|
'totalLabel' => $total_label,
|
||||||
|
),
|
||||||
|
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
||||||
|
'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the product needs shipping
|
* Check if the product needs shipping
|
||||||
*
|
*
|
||||||
* @param \WC_Product $product The product.
|
* @param WC_Product $product Product to check.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function check_if_need_shipping( $product ) {
|
protected function check_if_need_shipping( WC_Product $product ) : bool {
|
||||||
if (
|
if (
|
||||||
! wc_shipping_enabled()
|
! wc_shipping_enabled()
|
||||||
|| 0 === wc_get_shipping_method_count(
|
|| 0 === wc_get_shipping_method_count(
|
||||||
|
@ -104,30 +141,20 @@ class DataToAppleButtonScripts {
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$needs_shipping = false;
|
|
||||||
|
|
||||||
if ( $product->needs_shipping() ) {
|
if ( $product->needs_shipping() ) {
|
||||||
$needs_shipping = true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $needs_shipping;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the data for the product page.
|
* Prepares the data for the product page.
|
||||||
*
|
*
|
||||||
* @param string $shop_country_code The shop country code.
|
|
||||||
* @param string $currency_code The currency code.
|
|
||||||
* @param string $total_label The label for the total amount.
|
|
||||||
*
|
|
||||||
* @return array
|
* @return array
|
||||||
* @throws NotFoundException When the setting is not found.
|
|
||||||
*/
|
*/
|
||||||
protected function data_for_product_page(
|
protected function data_for_product_page() : array {
|
||||||
$shop_country_code,
|
|
||||||
$currency_code,
|
|
||||||
$total_label
|
|
||||||
) {
|
|
||||||
$product = wc_get_product( get_the_id() );
|
$product = wc_get_product( get_the_id() );
|
||||||
if ( ! $product ) {
|
if ( ! $product ) {
|
||||||
return array();
|
return array();
|
||||||
|
@ -136,146 +163,59 @@ class DataToAppleButtonScripts {
|
||||||
if ( $product->get_type() === 'variable' || $product->get_type() === 'variable-subscription' ) {
|
if ( $product->get_type() === 'variable' || $product->get_type() === 'variable-subscription' ) {
|
||||||
$is_variation = true;
|
$is_variation = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$product_need_shipping = $this->check_if_need_shipping( $product );
|
$product_need_shipping = $this->check_if_need_shipping( $product );
|
||||||
$product_id = get_the_id();
|
$product_id = get_the_id();
|
||||||
$product_price = $product->get_price();
|
$product_price = $product->get_price();
|
||||||
$product_stock = $product->get_stock_status();
|
$product_stock = $product->get_stock_status();
|
||||||
$type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : '';
|
|
||||||
$color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : '';
|
|
||||||
$lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : '';
|
|
||||||
$checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT;
|
|
||||||
|
|
||||||
return array(
|
return $this->get_apple_pay_data(
|
||||||
'sdk_url' => $this->sdk_url,
|
array(
|
||||||
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
|
|
||||||
'is_admin' => false,
|
|
||||||
'preferences' => array(
|
|
||||||
'checkout_data_mode' => $checkout_data_mode,
|
|
||||||
),
|
|
||||||
'button' => array(
|
|
||||||
'wrapper' => 'applepay-container',
|
|
||||||
'mini_cart_wrapper' => 'applepay-container-minicart',
|
|
||||||
'type' => $type,
|
|
||||||
'color' => $color,
|
|
||||||
'lang' => $lang,
|
|
||||||
),
|
|
||||||
'product' => array(
|
|
||||||
'needShipping' => $product_need_shipping,
|
'needShipping' => $product_need_shipping,
|
||||||
'id' => $product_id,
|
'id' => $product_id,
|
||||||
'price' => $product_price,
|
'price' => $product_price,
|
||||||
'isVariation' => $is_variation,
|
'isVariation' => $is_variation,
|
||||||
'stock' => $product_stock,
|
'stock' => $product_stock,
|
||||||
),
|
)
|
||||||
'shop' => array(
|
|
||||||
'countryCode' => $shop_country_code,
|
|
||||||
'currencyCode' => $currency_code,
|
|
||||||
'totalLabel' => $total_label,
|
|
||||||
),
|
|
||||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
|
||||||
'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the data for the cart page.
|
* Prepares the data for the cart page.
|
||||||
*
|
*
|
||||||
* @param string $shop_country_code The shop country code.
|
|
||||||
* @param string $currency_code The currency code.
|
|
||||||
* @param string $total_label The label for the total amount.
|
|
||||||
*
|
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function data_for_cart_page(
|
protected function data_for_cart_page() : array {
|
||||||
$shop_country_code,
|
|
||||||
$currency_code,
|
|
||||||
$total_label
|
|
||||||
) {
|
|
||||||
$cart = WC()->cart;
|
$cart = WC()->cart;
|
||||||
if ( ! $cart ) {
|
if ( ! $cart ) {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : '';
|
return $this->get_apple_pay_data(
|
||||||
$color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : '';
|
array(
|
||||||
$lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : '';
|
|
||||||
$lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang );
|
|
||||||
$checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT;
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'sdk_url' => $this->sdk_url,
|
|
||||||
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
|
|
||||||
'is_admin' => false,
|
|
||||||
'preferences' => array(
|
|
||||||
'checkout_data_mode' => $checkout_data_mode,
|
|
||||||
),
|
|
||||||
'button' => array(
|
|
||||||
'wrapper' => 'applepay-container',
|
|
||||||
'mini_cart_wrapper' => 'applepay-container-minicart',
|
|
||||||
'type' => $type,
|
|
||||||
'color' => $color,
|
|
||||||
'lang' => $lang,
|
|
||||||
),
|
|
||||||
'product' => array(
|
|
||||||
'needShipping' => $cart->needs_shipping(),
|
'needShipping' => $cart->needs_shipping(),
|
||||||
'subtotal' => $cart->get_subtotal(),
|
'subtotal' => $cart->get_subtotal(),
|
||||||
),
|
)
|
||||||
'shop' => array(
|
|
||||||
'countryCode' => $shop_country_code,
|
|
||||||
'currencyCode' => $currency_code,
|
|
||||||
'totalLabel' => $total_label,
|
|
||||||
),
|
|
||||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
|
||||||
'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the data for the cart page.
|
* Prepares the data for the cart page.
|
||||||
* Consider refactoring this method along with data_for_cart_page() and data_for_product_page() methods.
|
* Consider refactoring this method along with data_for_cart_page() and data_for_product_page()
|
||||||
*
|
* methods.
|
||||||
* @param string $shop_country_code The shop country code.
|
|
||||||
* @param string $currency_code The currency code.
|
|
||||||
* @param string $total_label The label for the total amount.
|
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function data_for_admin_page(
|
protected function data_for_admin_page() : array {
|
||||||
$shop_country_code,
|
$data = $this->get_apple_pay_data(
|
||||||
$currency_code,
|
array(
|
||||||
$total_label
|
|
||||||
) {
|
|
||||||
$type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : '';
|
|
||||||
$color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : '';
|
|
||||||
$lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : '';
|
|
||||||
$lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang );
|
|
||||||
$checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT;
|
|
||||||
$is_enabled = $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' );
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'sdk_url' => $this->sdk_url,
|
|
||||||
'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
|
|
||||||
'is_admin' => true,
|
|
||||||
'is_enabled' => $is_enabled,
|
|
||||||
'preferences' => array(
|
|
||||||
'checkout_data_mode' => $checkout_data_mode,
|
|
||||||
),
|
|
||||||
'button' => array(
|
|
||||||
'wrapper' => 'applepay-container',
|
|
||||||
'mini_cart_wrapper' => 'applepay-container-minicart',
|
|
||||||
'type' => $type,
|
|
||||||
'color' => $color,
|
|
||||||
'lang' => $lang,
|
|
||||||
),
|
|
||||||
'product' => array(
|
|
||||||
'needShipping' => false,
|
'needShipping' => false,
|
||||||
'subtotal' => 0,
|
'subtotal' => 0,
|
||||||
),
|
)
|
||||||
'shop' => array(
|
|
||||||
'countryCode' => $shop_country_code,
|
|
||||||
'currencyCode' => $currency_code,
|
|
||||||
'totalLabel' => $total_label,
|
|
||||||
),
|
|
||||||
'ajax_url' => admin_url( 'admin-ajax.php' ),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$data['is_admin'] = true;
|
||||||
|
|
||||||
|
return $data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ return array(
|
||||||
->rule()
|
->rule()
|
||||||
->condition_element( 'axo_enabled', '1' )
|
->condition_element( 'axo_enabled', '1' )
|
||||||
->action_visible( 'axo_gateway_title' )
|
->action_visible( 'axo_gateway_title' )
|
||||||
->action_visible( 'axo_checkout_config_notice' )
|
->action_visible( 'axo_main_notice' )
|
||||||
->action_visible( 'axo_privacy' )
|
->action_visible( 'axo_privacy' )
|
||||||
->action_visible( 'axo_name_on_card' )
|
->action_visible( 'axo_name_on_card' )
|
||||||
->action_visible( 'axo_style_heading' )
|
->action_visible( 'axo_style_heading' )
|
||||||
|
@ -114,9 +114,17 @@ return array(
|
||||||
),
|
),
|
||||||
'classes' => array( 'ppcp-valign-label-middle', 'ppcp-align-label-center' ),
|
'classes' => array( 'ppcp-valign-label-middle', 'ppcp-align-label-center' ),
|
||||||
),
|
),
|
||||||
'axo_checkout_config_notice' => array(
|
'axo_main_notice' => array(
|
||||||
'heading' => '',
|
'heading' => '',
|
||||||
'html' => $container->get( 'axo.checkout-config-notice' ),
|
'html' => implode(
|
||||||
|
'',
|
||||||
|
array(
|
||||||
|
$container->get( 'axo.settings-conflict-notice' ),
|
||||||
|
$container->get( 'axo.shipping-config-notice' ),
|
||||||
|
$container->get( 'axo.checkout-config-notice' ),
|
||||||
|
$container->get( 'axo.incompatible-plugins-notice' ),
|
||||||
|
)
|
||||||
|
),
|
||||||
'type' => 'ppcp-html',
|
'type' => 'ppcp-html',
|
||||||
'classes' => array( 'ppcp-field-indent' ),
|
'classes' => array( 'ppcp-field-indent' ),
|
||||||
'class' => array(),
|
'class' => array(),
|
||||||
|
|
|
@ -709,6 +709,8 @@ class AxoManager {
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.emailInput.value = this.stripSpaces( this.emailInput.value );
|
||||||
|
|
||||||
this.$( this.el.paymentContainer.selector + '-detail' ).html( '' );
|
this.$( this.el.paymentContainer.selector + '-detail' ).html( '' );
|
||||||
this.$( this.el.paymentContainer.selector + '-form' ).html( '' );
|
this.$( this.el.paymentContainer.selector + '-form' ).html( '' );
|
||||||
|
|
||||||
|
@ -1134,6 +1136,10 @@ class AxoManager {
|
||||||
return emailPattern.test( value );
|
return emailPattern.test( value );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stripSpaces( str ) {
|
||||||
|
return str.replace( /\s+/g, '' );
|
||||||
|
}
|
||||||
|
|
||||||
validateEmail( billingEmail ) {
|
validateEmail( billingEmail ) {
|
||||||
const billingEmailSelector = document.querySelector( billingEmail );
|
const billingEmailSelector = document.querySelector( billingEmail );
|
||||||
const value = document.querySelector( billingEmail + ' input' ).value;
|
const value = document.querySelector( billingEmail + ' input' ).value;
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
export function log( message, level = 'info' ) {
|
export function log( message, level = 'info' ) {
|
||||||
const wpDebug = window.wc_ppcp_axo?.wp_debug;
|
const wpDebug = window.wc_ppcp_axo?.wp_debug;
|
||||||
const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint;
|
const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint;
|
||||||
if ( ! endpoint ) {
|
const loggingEnabled = window.wc_ppcp_axo?.logging_enabled;
|
||||||
|
|
||||||
|
if ( wpDebug ) {
|
||||||
|
switch ( level ) {
|
||||||
|
case 'error':
|
||||||
|
console.error( `[AXO] ${ message }` );
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log( `[AXO] ${ message }` );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! endpoint || ! loggingEnabled ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,15 +27,5 @@ export function log( message, level = 'info' ) {
|
||||||
level,
|
level,
|
||||||
},
|
},
|
||||||
} ),
|
} ),
|
||||||
} ).then( () => {
|
|
||||||
if ( wpDebug ) {
|
|
||||||
switch ( level ) {
|
|
||||||
case 'error':
|
|
||||||
console.error( `[AXO] ${ message }` );
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log( `[AXO] ${ message }` );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ namespace WooCommerce\PayPalCommerce\Axo;
|
||||||
use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager;
|
use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager;
|
||||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||||
use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies;
|
use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies;
|
||||||
|
use WooCommerce\PayPalCommerce\Axo\Helper\SettingsNoticeGenerator;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
|
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
|
@ -25,7 +25,7 @@ return array(
|
||||||
$apm_applies = $container->get( 'axo.helpers.apm-applies' );
|
$apm_applies = $container->get( 'axo.helpers.apm-applies' );
|
||||||
assert( $apm_applies instanceof ApmApplies );
|
assert( $apm_applies instanceof ApmApplies );
|
||||||
|
|
||||||
return $apm_applies->for_country_currency() && $apm_applies->for_settings();
|
return $apm_applies->for_country_currency();
|
||||||
},
|
},
|
||||||
|
|
||||||
'axo.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
|
'axo.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
|
||||||
|
@ -36,6 +36,10 @@ return array(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'axo.helpers.settings-notice-generator' => static function ( ContainerInterface $container ) : SettingsNoticeGenerator {
|
||||||
|
return new SettingsNoticeGenerator();
|
||||||
|
},
|
||||||
|
|
||||||
// If AXO is configured and onboarded.
|
// If AXO is configured and onboarded.
|
||||||
'axo.available' => static function ( ContainerInterface $container ): bool {
|
'axo.available' => static function ( ContainerInterface $container ): bool {
|
||||||
return true;
|
return true;
|
||||||
|
@ -159,48 +163,35 @@ return array(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'axo.settings-conflict-notice' => static function ( ContainerInterface $container ) : string {
|
||||||
|
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||||
|
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||||
|
|
||||||
|
$settings = $container->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
|
return $settings_notice_generator->generate_settings_conflict_notice( $settings );
|
||||||
|
},
|
||||||
|
|
||||||
'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string {
|
'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string {
|
||||||
$checkout_page_link = esc_url( get_edit_post_link( wc_get_page_id( 'checkout' ) ) ?? '' );
|
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||||
$block_checkout_docs_link = __(
|
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||||
'https://woocommerce.com/document/cart-checkout-blocks-status/#reverting-to-the-cart-and-checkout-shortcodes',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( CartCheckoutDetector::has_elementor_checkout() ) {
|
return $settings_notice_generator->generate_checkout_notice();
|
||||||
$notice_content = sprintf(
|
},
|
||||||
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
|
||||||
__(
|
|
||||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store currently uses the <code>Elementor Checkout widget</code>. To enable Fastlane and accelerate payments, the page must include either the <code>Classic Checkout</code> or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the classic layout.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
esc_url( $checkout_page_link ),
|
|
||||||
esc_url( $block_checkout_docs_link )
|
|
||||||
);
|
|
||||||
} elseif ( CartCheckoutDetector::has_block_checkout() ) {
|
|
||||||
$notice_content = sprintf(
|
|
||||||
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
|
||||||
__(
|
|
||||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store currently uses the WooCommerce <code>Checkout</code> block. To enable Fastlane and accelerate payments, the page must include either the <code>Classic Checkout</code> or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the classic layout.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
esc_url( $checkout_page_link ),
|
|
||||||
esc_url( $block_checkout_docs_link )
|
|
||||||
);
|
|
||||||
} elseif ( ! CartCheckoutDetector::has_classic_checkout() ) {
|
|
||||||
$notice_content = sprintf(
|
|
||||||
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
|
||||||
__(
|
|
||||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store does not seem to be properly configured or uses an incompatible <code>third-party Checkout</code> solution. To enable Fastlane and accelerate payments, the page must include either the <code>Classic Checkout</code> or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the classic layout.',
|
|
||||||
'woocommerce-paypal-payments'
|
|
||||||
),
|
|
||||||
esc_url( $checkout_page_link ),
|
|
||||||
esc_url( $block_checkout_docs_link )
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<div class="ppcp-notice ppcp-notice-error"><p>' . $notice_content . '</p></div>';
|
'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string {
|
||||||
|
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||||
|
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||||
|
|
||||||
|
return $settings_notice_generator->generate_shipping_notice();
|
||||||
|
},
|
||||||
|
|
||||||
|
'axo.incompatible-plugins-notice' => static function ( ContainerInterface $container ) : string {
|
||||||
|
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||||
|
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||||
|
|
||||||
|
return $settings_notice_generator->generate_incompatible_plugins_notice();
|
||||||
},
|
},
|
||||||
|
|
||||||
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
|
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
|
||||||
|
@ -230,6 +221,7 @@ return array(
|
||||||
|
|
||||||
return '<div class="ppcp-notice ppcp-notice-warning"><p>' . $notice_content . '</p></div>';
|
return '<div class="ppcp-notice ppcp-notice-warning"><p>' . $notice_content . '</p></div>';
|
||||||
},
|
},
|
||||||
|
|
||||||
'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint {
|
'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint {
|
||||||
return new FrontendLoggerEndpoint(
|
return new FrontendLoggerEndpoint(
|
||||||
$container->get( 'button.request-data' ),
|
$container->get( 'button.request-data' ),
|
||||||
|
|
|
@ -216,6 +216,7 @@ class AxoManager {
|
||||||
'nonce' => wp_create_nonce( FrontendLoggerEndpoint::nonce() ),
|
'nonce' => wp_create_nonce( FrontendLoggerEndpoint::nonce() ),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
'logging_enabled' => $this->settings->has( 'logging_enabled' ) ? $this->settings->get( 'logging_enabled' ) : '',
|
||||||
'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
|
'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
|
||||||
'billing_email_button_text' => __( 'Continue', 'woocommerce-paypal-payments' ),
|
'billing_email_button_text' => __( 'Continue', 'woocommerce-paypal-payments' ),
|
||||||
);
|
);
|
||||||
|
|
|
@ -66,7 +66,7 @@ class AxoModule implements ModuleInterface {
|
||||||
|
|
||||||
// Add the gateway in admin area.
|
// Add the gateway in admin area.
|
||||||
if ( is_admin() ) {
|
if ( is_admin() ) {
|
||||||
$methods[] = $gateway;
|
// $methods[] = $gateway; - Temporarily remove Fastlane from the payment gateway list in admin area.
|
||||||
return $methods;
|
return $methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,9 +77,10 @@ class AxoModule implements ModuleInterface {
|
||||||
$settings = $c->get( 'wcgateway.settings' );
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
assert( $settings instanceof Settings );
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
|
$is_paypal_enabled = $settings->has( 'enabled' ) && $settings->get( 'enabled' ) ?? false;
|
||||||
$is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false;
|
$is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false;
|
||||||
|
|
||||||
if ( ! $is_dcc_enabled ) {
|
if ( ! $is_paypal_enabled || ! $is_dcc_enabled ) {
|
||||||
return $methods;
|
return $methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +88,10 @@ class AxoModule implements ModuleInterface {
|
||||||
return $methods;
|
return $methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! $this->is_compatible_shipping_config() ) {
|
||||||
|
return $methods;
|
||||||
|
}
|
||||||
|
|
||||||
$methods[] = $gateway;
|
$methods[] = $gateway;
|
||||||
return $methods;
|
return $methods;
|
||||||
},
|
},
|
||||||
|
@ -144,13 +149,20 @@ class AxoModule implements ModuleInterface {
|
||||||
function () use ( $c ) {
|
function () use ( $c ) {
|
||||||
$module = $this;
|
$module = $this;
|
||||||
|
|
||||||
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
|
$is_paypal_enabled = $settings->has( 'enabled' ) && $settings->get( 'enabled' ) ?? false;
|
||||||
|
|
||||||
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
|
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
|
||||||
assert( $subscription_helper instanceof SubscriptionHelper );
|
assert( $subscription_helper instanceof SubscriptionHelper );
|
||||||
|
|
||||||
// Check if the module is applicable, correct country, currency, ... etc.
|
// Check if the module is applicable, correct country, currency, ... etc.
|
||||||
if ( ! $c->get( 'axo.eligible' )
|
if ( ! $is_paypal_enabled
|
||||||
|
|| ! $c->get( 'axo.eligible' )
|
||||||
|| 'continuation' === $c->get( 'button.context' )
|
|| 'continuation' === $c->get( 'button.context' )
|
||||||
|| $subscription_helper->cart_contains_subscription() ) {
|
|| $subscription_helper->cart_contains_subscription()
|
||||||
|
|| ! $this->is_compatible_shipping_config() ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +206,17 @@ class AxoModule implements ModuleInterface {
|
||||||
|
|
||||||
add_action(
|
add_action(
|
||||||
'wp_head',
|
'wp_head',
|
||||||
function () {
|
function () use ( $c ) {
|
||||||
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
|
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
|
||||||
echo '<script async src="https://www.paypalobjects.com/insights/v1/paypal-insights.sandbox.min.js"></script>';
|
echo '<script async src="https://www.paypalobjects.com/insights/v1/paypal-insights.sandbox.min.js"></script>';
|
||||||
|
|
||||||
|
// Add meta tag to allow feature-detection of the site's AXO payment state.
|
||||||
|
$settings = $c->get( 'wcgateway.settings' );
|
||||||
|
assert( $settings instanceof Settings );
|
||||||
|
|
||||||
|
$this->add_feature_detection_tag(
|
||||||
|
$settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' )
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -333,6 +353,7 @@ class AxoModule implements ModuleInterface {
|
||||||
|
|
||||||
return ! is_user_logged_in()
|
return ! is_user_logged_in()
|
||||||
&& CartCheckoutDetector::has_classic_checkout()
|
&& CartCheckoutDetector::has_classic_checkout()
|
||||||
|
&& $this->is_compatible_shipping_config()
|
||||||
&& $is_axo_enabled
|
&& $is_axo_enabled
|
||||||
&& $is_dcc_enabled
|
&& $is_dcc_enabled
|
||||||
&& ! $this->is_excluded_endpoint();
|
&& ! $this->is_excluded_endpoint();
|
||||||
|
@ -388,4 +409,32 @@ class AxoModule implements ModuleInterface {
|
||||||
// Exclude the Order Pay endpoint.
|
// Exclude the Order Pay endpoint.
|
||||||
return is_wc_endpoint_url( 'order-pay' );
|
return is_wc_endpoint_url( 'order-pay' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Condition to evaluate if the shipping configuration is compatible.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function is_compatible_shipping_config(): bool {
|
||||||
|
return ! wc_shipping_enabled() || ( wc_shipping_enabled() && ! wc_ship_to_billing_address_only() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outputs a meta tag to allow feature detection on certain pages.
|
||||||
|
*
|
||||||
|
* @param bool $axo_enabled Whether the gateway is enabled.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function add_feature_detection_tag( bool $axo_enabled ) {
|
||||||
|
$show_tag = is_checkout() || is_cart() || is_shop();
|
||||||
|
|
||||||
|
if ( ! $show_tag ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<meta name="ppcp.axo" content="ppcp.axo.%s" />',
|
||||||
|
$axo_enabled ? 'enabled' : 'disabled'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ class AxoGateway extends WC_Payment_Gateway {
|
||||||
? $this->ppcp_settings->get( 'axo_gateway_title' )
|
? $this->ppcp_settings->get( 'axo_gateway_title' )
|
||||||
: $this->get_option( 'title', $this->method_title );
|
: $this->get_option( 'title', $this->method_title );
|
||||||
|
|
||||||
$this->description = __( 'Enter your email address to continue.', 'woocommerce-paypal-payments' );
|
$this->description = __( 'Enter your email address above to continue.', 'woocommerce-paypal-payments' );
|
||||||
|
|
||||||
$this->init_form_fields();
|
$this->init_form_fields();
|
||||||
$this->init_settings();
|
$this->init_settings();
|
||||||
|
|
|
@ -64,19 +64,4 @@ class ApmApplies {
|
||||||
}
|
}
|
||||||
return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true );
|
return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the settings are compatible with AXO.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function for_settings(): bool {
|
|
||||||
if ( get_option( 'woocommerce_ship_to_destination' ) === 'billing_only' ) { // Force shipping to the customer billing address.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
179
modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php
Normal file
179
modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Settings notice generator.
|
||||||
|
* Generates the settings notices.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Axo\Helper
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Axo\Helper;
|
||||||
|
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SettingsNoticeGenerator
|
||||||
|
*/
|
||||||
|
class SettingsNoticeGenerator {
|
||||||
|
/**
|
||||||
|
* Generates the full HTML of the notification.
|
||||||
|
*
|
||||||
|
* @param string $message HTML of the inner message contents.
|
||||||
|
* @param bool $is_error Whether the provided message is an error. Affects the notice color.
|
||||||
|
*
|
||||||
|
* @return string The full HTML code of the notification, or an empty string.
|
||||||
|
*/
|
||||||
|
private function render_notice( string $message, bool $is_error = false ) : string {
|
||||||
|
if ( ! $message ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<div class="ppcp-notice %1$s"><p>%2$s</p></div>',
|
||||||
|
$is_error ? 'ppcp-notice-error' : '',
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the checkout notice.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate_checkout_notice(): string {
|
||||||
|
$checkout_page_link = esc_url( get_edit_post_link( wc_get_page_id( 'checkout' ) ) ?? '' );
|
||||||
|
$block_checkout_docs_link = __(
|
||||||
|
'https://woocommerce.com/document/cart-checkout-blocks-status/#reverting-to-the-cart-and-checkout-shortcodes',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
);
|
||||||
|
|
||||||
|
$notice_content = '';
|
||||||
|
|
||||||
|
if ( CartCheckoutDetector::has_elementor_checkout() ) {
|
||||||
|
$notice_content = sprintf(
|
||||||
|
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
||||||
|
__(
|
||||||
|
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store currently uses the <code>Elementor Checkout widget</code>. To enable Fastlane and accelerate payments, the page must include either the <code>Classic Checkout</code> or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the classic layout.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
esc_url( $checkout_page_link ),
|
||||||
|
esc_url( $block_checkout_docs_link )
|
||||||
|
);
|
||||||
|
} elseif ( CartCheckoutDetector::has_block_checkout() ) {
|
||||||
|
$notice_content = sprintf(
|
||||||
|
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
||||||
|
__(
|
||||||
|
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store currently uses the WooCommerce <code>Checkout</code> block. To enable Fastlane and accelerate payments, the page must include either the <code>Classic Checkout</code> or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the classic layout.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
esc_url( $checkout_page_link ),
|
||||||
|
esc_url( $block_checkout_docs_link )
|
||||||
|
);
|
||||||
|
} elseif ( ! CartCheckoutDetector::has_classic_checkout() ) {
|
||||||
|
$notice_content = sprintf(
|
||||||
|
/* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */
|
||||||
|
__(
|
||||||
|
'<span class="highlight">Warning:</span> The <a href="%1$s">Checkout page</a> of your store does not seem to be properly configured or uses an incompatible <code>third-party Checkout</code> solution. To enable Fastlane and accelerate payments, the page must include either the <code>Classic Checkout</code> or the <code>[woocommerce_checkout]</code> shortcode. See <a href="%2$s">this page</a> for instructions on how to switch to the classic layout.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
esc_url( $checkout_page_link ),
|
||||||
|
esc_url( $block_checkout_docs_link )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $notice_content ? '<div class="ppcp-notice ppcp-notice-error"><p>' . $notice_content . '</p></div>' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the shipping notice.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate_shipping_notice(): string {
|
||||||
|
$shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' );
|
||||||
|
|
||||||
|
$notice_content = '';
|
||||||
|
|
||||||
|
if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) {
|
||||||
|
$notice_content = sprintf(
|
||||||
|
/* translators: %1$s: URL to the Shipping destination settings page. */
|
||||||
|
__(
|
||||||
|
'<span class="highlight">Warning:</span> The <a href="%1$s">Shipping destination</a> of your store is currently configured to <code>Force shipping to the customer billing address</code>. To enable Fastlane and accelerate payments, the shipping destination must be configured either to <code>Default to customer shipping address</code> or <code>Default to customer billing address</code> so buyers can set separate billing and shipping details.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
esc_url( $shipping_settings_link )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $notice_content ? '<div class="ppcp-notice ppcp-notice-error"><p>' . $notice_content . '</p></div>' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the incompatible plugins notice.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate_incompatible_plugins_notice(): string {
|
||||||
|
$incompatible_plugins = array(
|
||||||
|
'Elementor' => did_action( 'elementor/loaded' ),
|
||||||
|
'CheckoutWC' => defined( 'CFW_NAME' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$active_plugins_list = array_filter( $incompatible_plugins );
|
||||||
|
|
||||||
|
if ( empty( $active_plugins_list ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$incompatible_plugin_items = array_map(
|
||||||
|
function ( $plugin ) {
|
||||||
|
return "<li>{$plugin}</li>";
|
||||||
|
},
|
||||||
|
array_keys( $active_plugins_list )
|
||||||
|
);
|
||||||
|
|
||||||
|
$plugins_settings_link = esc_url( admin_url( 'plugins.php' ) );
|
||||||
|
$notice_content = sprintf(
|
||||||
|
/* translators: %1$s: URL to the plugins settings page. %2$s: List of incompatible plugins. */
|
||||||
|
__(
|
||||||
|
'<span class="highlight">Note:</span> The accelerated guest buyer experience provided by Fastlane may not be fully compatible with some of the following <a href="%1$s">active plugins</a>: <ul class="ppcp-notice-list">%2$s</ul>',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
),
|
||||||
|
$plugins_settings_link,
|
||||||
|
implode( '', $incompatible_plugin_items )
|
||||||
|
);
|
||||||
|
|
||||||
|
return '<div class="ppcp-notice"><p>' . $notice_content . '</p></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a warning notice with instructions on conflicting plugin-internal settings.
|
||||||
|
*
|
||||||
|
* @param Settings $settings The plugin settings container, which is checked for conflicting
|
||||||
|
* values.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generate_settings_conflict_notice( Settings $settings ) : string {
|
||||||
|
$notice_content = '';
|
||||||
|
$is_dcc_enabled = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' );
|
||||||
|
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
|
||||||
|
} catch ( NotFoundException $ignored ) {
|
||||||
|
// Never happens.
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $is_dcc_enabled ) {
|
||||||
|
$notice_content = __(
|
||||||
|
'<span class="highlight">Warning:</span> To enable Fastlane and accelerate payments, the <strong>Advanced Card Processing</strong> payment method must also be enabled.',
|
||||||
|
'woocommerce-paypal-payments'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render_notice( $notice_content, true );
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@
|
||||||
"Edge >= 14"
|
"Edge >= 14"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@paypal/react-paypal-js": "^8.3.0",
|
"@paypal/react-paypal-js": "^8.5.0",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.0",
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-dom": "^17.0.0"
|
"react-dom": "^17.0.0"
|
||||||
|
|
|
@ -7,7 +7,12 @@ import {
|
||||||
} from '@paypal/react-paypal-js';
|
} from '@paypal/react-paypal-js';
|
||||||
|
|
||||||
import { CheckoutHandler } from './checkout-handler';
|
import { CheckoutHandler } from './checkout-handler';
|
||||||
import { createOrder, onApprove } from '../card-fields-config';
|
import {
|
||||||
|
createOrder,
|
||||||
|
onApprove,
|
||||||
|
createVaultSetupToken,
|
||||||
|
onApproveSavePayment,
|
||||||
|
} from '../card-fields-config';
|
||||||
import { cartHasSubscriptionProducts } from '../Helper/Subscription';
|
import { cartHasSubscriptionProducts } from '../Helper/Subscription';
|
||||||
|
|
||||||
export function CardFields( {
|
export function CardFields( {
|
||||||
|
@ -70,8 +75,21 @@ export function CardFields( {
|
||||||
} }
|
} }
|
||||||
>
|
>
|
||||||
<PayPalCardFieldsProvider
|
<PayPalCardFieldsProvider
|
||||||
createOrder={ createOrder }
|
createVaultSetupToken={
|
||||||
onApprove={ onApprove }
|
config.scriptData.is_free_trial_cart
|
||||||
|
? createVaultSetupToken
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
createOrder={
|
||||||
|
config.scriptData.is_free_trial_cart
|
||||||
|
? undefined
|
||||||
|
: createOrder
|
||||||
|
}
|
||||||
|
onApprove={
|
||||||
|
config.scriptData.is_free_trial_cart
|
||||||
|
? onApproveSavePayment
|
||||||
|
: onApprove
|
||||||
|
}
|
||||||
onError={ ( err ) => {
|
onError={ ( err ) => {
|
||||||
console.error( err );
|
console.error( err );
|
||||||
} }
|
} }
|
||||||
|
|
|
@ -44,3 +44,61 @@ export async function onApprove( data ) {
|
||||||
console.error( err );
|
console.error( err );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createVaultSetupToken() {
|
||||||
|
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
|
||||||
|
|
||||||
|
return fetch( config.scriptData.ajax.create_setup_token.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: config.scriptData.ajax.create_setup_token.nonce,
|
||||||
|
payment_method: 'ppcp-credit-card-gateway',
|
||||||
|
} ),
|
||||||
|
} )
|
||||||
|
.then( ( response ) => response.json() )
|
||||||
|
.then( ( result ) => {
|
||||||
|
console.log( result );
|
||||||
|
return result.data.id;
|
||||||
|
} )
|
||||||
|
.catch( ( err ) => {
|
||||||
|
console.error( err );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function onApproveSavePayment( { vaultSetupToken } ) {
|
||||||
|
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
|
||||||
|
|
||||||
|
let endpoint =
|
||||||
|
config.scriptData.ajax.create_payment_token_for_guest.endpoint;
|
||||||
|
let bodyContent = {
|
||||||
|
nonce: config.scriptData.ajax.create_payment_token_for_guest.nonce,
|
||||||
|
vault_setup_token: vaultSetupToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( config.scriptData.user.is_logged_in ) {
|
||||||
|
endpoint = config.scriptData.ajax.create_payment_token.endpoint;
|
||||||
|
|
||||||
|
bodyContent = {
|
||||||
|
nonce: config.scriptData.ajax.create_payment_token.nonce,
|
||||||
|
vault_setup_token: vaultSetupToken,
|
||||||
|
is_free_trial_cart: config.scriptData.is_free_trial_cart,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch( endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( bodyContent ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.success !== true ) {
|
||||||
|
console.error( result );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -227,7 +227,7 @@ const PayPalComponent = ( {
|
||||||
throw new Error( config.scriptData.labels.error.generic );
|
throw new Error( config.scriptData.labels.error.generic );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! shouldHandleShippingInPayPal() ) {
|
if ( ! shouldskipFinalConfirmation() ) {
|
||||||
location.href = getCheckoutRedirectUrl();
|
location.href = getCheckoutRedirectUrl();
|
||||||
} else {
|
} else {
|
||||||
setGotoContinuationOnError( true );
|
setGotoContinuationOnError( true );
|
||||||
|
@ -318,7 +318,7 @@ const PayPalComponent = ( {
|
||||||
throw new Error( config.scriptData.labels.error.generic );
|
throw new Error( config.scriptData.labels.error.generic );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! shouldHandleShippingInPayPal() ) {
|
if ( ! shouldskipFinalConfirmation() ) {
|
||||||
location.href = getCheckoutRedirectUrl();
|
location.href = getCheckoutRedirectUrl();
|
||||||
} else {
|
} else {
|
||||||
setGotoContinuationOnError( true );
|
setGotoContinuationOnError( true );
|
||||||
|
@ -364,16 +364,20 @@ const PayPalComponent = ( {
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldHandleShippingInPayPal = () => {
|
const shouldHandleShippingInPayPal = () => {
|
||||||
if ( config.finalReviewEnabled ) {
|
return shouldskipFinalConfirmation() && config.needShipping
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
window.ppcpFundingSource !== 'venmo' ||
|
|
||||||
! config.scriptData.vaultingEnabled
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shouldskipFinalConfirmation = () => {
|
||||||
|
if ( config.finalReviewEnabled ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
window.ppcpFundingSource !== 'venmo' ||
|
||||||
|
! config.scriptData.vaultingEnabled
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
let handleShippingOptionsChange = null;
|
let handleShippingOptionsChange = null;
|
||||||
let handleShippingAddressChange = null;
|
let handleShippingAddressChange = null;
|
||||||
let handleSubscriptionShippingOptionsChange = null;
|
let handleSubscriptionShippingOptionsChange = null;
|
||||||
|
@ -544,7 +548,7 @@ const PayPalComponent = ( {
|
||||||
if ( config.scriptData.continuation ) {
|
if ( config.scriptData.continuation ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if ( shouldHandleShippingInPayPal() ) {
|
if ( shouldskipFinalConfirmation() ) {
|
||||||
location.href = getCheckoutRedirectUrl();
|
location.href = getCheckoutRedirectUrl();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -97,15 +97,6 @@ class UpdateShippingEndpoint implements EndpointInterface {
|
||||||
$pu = $this->purchase_unit_factory->from_wc_cart( null, true );
|
$pu = $this->purchase_unit_factory->from_wc_cart( null, true );
|
||||||
$pu_data = $pu->to_array();
|
$pu_data = $pu->to_array();
|
||||||
|
|
||||||
if ( ! isset( $pu_data['shipping']['options'] ) ) {
|
|
||||||
wp_send_json_error(
|
|
||||||
array(
|
|
||||||
'message' => 'No shipping methods.',
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe should patch only if methods changed.
|
// TODO: maybe should patch only if methods changed.
|
||||||
// But it seems a bit difficult to detect,
|
// But it seems a bit difficult to detect,
|
||||||
// e.g. ->order($id) may not have Shipping because we drop it when address or name are missing.
|
// e.g. ->order($id) may not have Shipping because we drop it when address or name are missing.
|
||||||
|
|
|
@ -254,6 +254,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'scriptData' => $script_data,
|
'scriptData' => $script_data,
|
||||||
|
'needShipping' => WC()->cart->needs_shipping(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1005,19 +1005,19 @@
|
||||||
"@jridgewell/resolve-uri" "3.1.0"
|
"@jridgewell/resolve-uri" "3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "1.4.14"
|
"@jridgewell/sourcemap-codec" "1.4.14"
|
||||||
|
|
||||||
"@paypal/paypal-js@^8.0.5":
|
"@paypal/paypal-js@^8.1.0":
|
||||||
version "8.0.5"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.0.5.tgz#77bc461b4d1e5a2c6f081269e3ef0b2e3331a68c"
|
resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.1.0.tgz#4e7d10e0a0b4164985029cfdac748e5694d117e9"
|
||||||
integrity sha512-yQNV7rOILeaVCNU4aVDRPqEnbIlzfxgQfFsxzsBuZW1ouqRD/4kYBWJDzczCiscSr2xOeA/Pkm7e3a9fRfnuMQ==
|
integrity sha512-f64bom5xYwmxyeKPJUFS/XpM0tXojQEgjRIADPqe1R9WmK+PFqL4SEkT85cGU0ZXLVx4EGbjwREHhqEOR+OstA==
|
||||||
dependencies:
|
dependencies:
|
||||||
promise-polyfill "^8.3.0"
|
promise-polyfill "^8.3.0"
|
||||||
|
|
||||||
"@paypal/react-paypal-js@^8.3.0":
|
"@paypal/react-paypal-js@^8.5.0":
|
||||||
version "8.3.0"
|
version "8.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.3.0.tgz#a103080b752766b8ff59b8620887abf802e1a01b"
|
resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.5.0.tgz#cf17483202c8fa7a33dae86798d50a102705f182"
|
||||||
integrity sha512-SX17d2h1CMNFGI+wtjb329AEDaBR8Ziy2LCV076eDcY1Q0MFKRkfQ/v0HOAvZtk3sJoydRmYez2pq47BRblwqQ==
|
integrity sha512-YIAyLw4OiUoHHoUgXvibrBDdluzqnqVMGsJXyBcoOzlWHQIe5zhh8dgYezNNRXjXwy6t22YmPljjw7lr+eD9cw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@paypal/paypal-js" "^8.0.5"
|
"@paypal/paypal-js" "^8.1.0"
|
||||||
"@paypal/sdk-constants" "^1.0.122"
|
"@paypal/sdk-constants" "^1.0.122"
|
||||||
|
|
||||||
"@paypal/sdk-constants@^1.0.122":
|
"@paypal/sdk-constants@^1.0.122":
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Renderer from './modules/Renderer/Renderer';
|
||||||
import ErrorHandler from './modules/ErrorHandler';
|
import ErrorHandler from './modules/ErrorHandler';
|
||||||
import HostedFieldsRenderer from './modules/Renderer/HostedFieldsRenderer';
|
import HostedFieldsRenderer from './modules/Renderer/HostedFieldsRenderer';
|
||||||
import CardFieldsRenderer from './modules/Renderer/CardFieldsRenderer';
|
import CardFieldsRenderer from './modules/Renderer/CardFieldsRenderer';
|
||||||
|
import CardFieldsFreeTrialRenderer from './modules/Renderer/CardFieldsFreeTrialRenderer';
|
||||||
import MessageRenderer from './modules/Renderer/MessageRenderer';
|
import MessageRenderer from './modules/Renderer/MessageRenderer';
|
||||||
import Spinner from './modules/Helper/Spinner';
|
import Spinner from './modules/Helper/Spinner';
|
||||||
import {
|
import {
|
||||||
|
@ -215,12 +216,23 @@ const bootstrap = () => {
|
||||||
spinner
|
spinner
|
||||||
);
|
);
|
||||||
if ( typeof paypal.CardFields !== 'undefined' ) {
|
if ( typeof paypal.CardFields !== 'undefined' ) {
|
||||||
creditCardRenderer = new CardFieldsRenderer(
|
if (
|
||||||
PayPalCommerceGateway,
|
PayPalCommerceGateway.is_free_trial_cart &&
|
||||||
errorHandler,
|
PayPalCommerceGateway.user?.has_wc_card_payment_tokens !== true
|
||||||
spinner,
|
) {
|
||||||
onCardFieldsBeforeSubmit
|
creditCardRenderer = new CardFieldsFreeTrialRenderer(
|
||||||
);
|
PayPalCommerceGateway,
|
||||||
|
errorHandler,
|
||||||
|
spinner
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
creditCardRenderer = new CardFieldsRenderer(
|
||||||
|
PayPalCommerceGateway,
|
||||||
|
errorHandler,
|
||||||
|
spinner,
|
||||||
|
onCardFieldsBeforeSubmit
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderer = new Renderer(
|
const renderer = new Renderer(
|
||||||
|
|
|
@ -173,61 +173,6 @@ class CheckoutActionHandler {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
addPaymentMethodConfiguration() {
|
|
||||||
return {
|
|
||||||
createVaultSetupToken: async () => {
|
|
||||||
const response = await fetch(
|
|
||||||
this.config.ajax.create_setup_token.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: this.config.ajax.create_setup_token.nonce,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
if ( result.data.id ) {
|
|
||||||
return result.data.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error( result );
|
|
||||||
},
|
|
||||||
onApprove: async ( { vaultSetupToken } ) => {
|
|
||||||
const response = await fetch(
|
|
||||||
this.config.ajax.create_payment_token_for_guest.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: this.config.ajax
|
|
||||||
.create_payment_token_for_guest.nonce,
|
|
||||||
vault_setup_token: vaultSetupToken,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
if ( result.success === true ) {
|
|
||||||
document.querySelector( '#place_order' ).click();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error( result );
|
|
||||||
},
|
|
||||||
onError: ( error ) => {
|
|
||||||
console.error( error );
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CheckoutActionHandler;
|
export default CheckoutActionHandler;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/* global PayPalCommerceGateway */
|
||||||
|
|
||||||
import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
|
import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler';
|
||||||
import { setVisible, setVisibleByClass } from '../Helper/Hiding';
|
import { setVisible, setVisibleByClass } from '../Helper/Hiding';
|
||||||
import {
|
import {
|
||||||
|
@ -7,6 +9,11 @@ import {
|
||||||
PaymentMethods,
|
PaymentMethods,
|
||||||
} from '../Helper/CheckoutMethodState';
|
} from '../Helper/CheckoutMethodState';
|
||||||
import BootstrapHelper from '../Helper/BootstrapHelper';
|
import BootstrapHelper from '../Helper/BootstrapHelper';
|
||||||
|
import { addPaymentMethodConfiguration } from '../../../../../ppcp-save-payment-methods/resources/js/Configuration';
|
||||||
|
import {
|
||||||
|
ButtonEvents,
|
||||||
|
dispatchButtonEvent,
|
||||||
|
} from '../Helper/PaymentButtonHelpers';
|
||||||
|
|
||||||
class CheckoutBootstap {
|
class CheckoutBootstap {
|
||||||
constructor( gateway, renderer, spinner, errorHandler ) {
|
constructor( gateway, renderer, spinner, errorHandler ) {
|
||||||
|
@ -68,6 +75,7 @@ class CheckoutBootstap {
|
||||||
jQuery( document.body ).on(
|
jQuery( document.body ).on(
|
||||||
'updated_checkout payment_method_selected',
|
'updated_checkout payment_method_selected',
|
||||||
() => {
|
() => {
|
||||||
|
this.invalidatePaymentMethods();
|
||||||
this.updateUi();
|
this.updateUi();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -160,7 +168,7 @@ class CheckoutBootstap {
|
||||||
PayPalCommerceGateway.vault_v3_enabled
|
PayPalCommerceGateway.vault_v3_enabled
|
||||||
) {
|
) {
|
||||||
this.renderer.render(
|
this.renderer.render(
|
||||||
actionHandler.addPaymentMethodConfiguration(),
|
addPaymentMethodConfiguration( PayPalCommerceGateway ),
|
||||||
{},
|
{},
|
||||||
actionHandler.configuration()
|
actionHandler.configuration()
|
||||||
);
|
);
|
||||||
|
@ -174,6 +182,14 @@ class CheckoutBootstap {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
invalidatePaymentMethods() {
|
||||||
|
/**
|
||||||
|
* Custom JS event to notify other modules that the payment button on the checkout page
|
||||||
|
* has become irrelevant or invalid.
|
||||||
|
*/
|
||||||
|
dispatchButtonEvent( { event: ButtonEvents.INVALIDATE } );
|
||||||
|
}
|
||||||
|
|
||||||
updateUi() {
|
updateUi() {
|
||||||
const currentPaymentMethod = getCurrentPaymentMethod();
|
const currentPaymentMethod = getCurrentPaymentMethod();
|
||||||
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
|
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
|
||||||
|
@ -183,12 +199,15 @@ class CheckoutBootstap {
|
||||||
);
|
);
|
||||||
const isGooglePayMethod =
|
const isGooglePayMethod =
|
||||||
currentPaymentMethod === PaymentMethods.GOOGLEPAY;
|
currentPaymentMethod === PaymentMethods.GOOGLEPAY;
|
||||||
|
const isApplePayMethod =
|
||||||
|
currentPaymentMethod === PaymentMethods.APPLEPAY;
|
||||||
const isSavedCard = isCard && isSavedCardSelected();
|
const isSavedCard = isCard && isSavedCardSelected();
|
||||||
const isNotOurGateway =
|
const isNotOurGateway =
|
||||||
! isPaypal &&
|
! isPaypal &&
|
||||||
! isCard &&
|
! isCard &&
|
||||||
! isSeparateButtonGateway &&
|
! isSeparateButtonGateway &&
|
||||||
! isGooglePayMethod;
|
! isGooglePayMethod &&
|
||||||
|
! isApplePayMethod;
|
||||||
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
|
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
|
||||||
const hasVaultedPaypal =
|
const hasVaultedPaypal =
|
||||||
PayPalCommerceGateway.vaulted_paypal_email !== '';
|
PayPalCommerceGateway.vaulted_paypal_email !== '';
|
||||||
|
@ -232,9 +251,20 @@ class CheckoutBootstap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod );
|
/**
|
||||||
|
* Custom JS event that is observed by the relevant payment gateway.
|
||||||
|
*
|
||||||
|
* Dynamic part of the event name is the payment method ID, for example
|
||||||
|
* "ppcp-credit-card-gateway" or "ppcp-googlepay"
|
||||||
|
*/
|
||||||
|
dispatchButtonEvent( {
|
||||||
|
event: ButtonEvents.RENDER,
|
||||||
|
paymentMethod: currentPaymentMethod,
|
||||||
|
} );
|
||||||
|
|
||||||
jQuery( document.body ).trigger( 'ppcp_checkout_rendered' );
|
setVisible( '#ppc-button-ppcp-applepay', isApplePayMethod );
|
||||||
|
|
||||||
|
document.body.dispatchEvent( new Event( 'ppcp_checkout_rendered' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldShowMessages() {
|
shouldShowMessages() {
|
||||||
|
|
|
@ -26,8 +26,11 @@ export function setupButtonEvents( refresh ) {
|
||||||
document.addEventListener( REFRESH_BUTTON_EVENT, debouncedRefresh );
|
document.addEventListener( REFRESH_BUTTON_EVENT, debouncedRefresh );
|
||||||
|
|
||||||
// Listen for cart and checkout update events.
|
// Listen for cart and checkout update events.
|
||||||
document.body.addEventListener( 'updated_cart_totals', debouncedRefresh );
|
// Note: we need jQuery here, because WooCommerce uses jQuery.trigger() to dispatch the events.
|
||||||
document.body.addEventListener( 'updated_checkout', debouncedRefresh );
|
window
|
||||||
|
.jQuery( 'body' )
|
||||||
|
.on( 'updated_cart_totals', debouncedRefresh )
|
||||||
|
.on( 'updated_checkout', debouncedRefresh );
|
||||||
|
|
||||||
// Use setTimeout for fragment events to avoid unnecessary refresh on initial render.
|
// Use setTimeout for fragment events to avoid unnecessary refresh on initial render.
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
|
|
|
@ -4,6 +4,31 @@ export const PaymentMethods = {
|
||||||
OXXO: 'ppcp-oxxo-gateway',
|
OXXO: 'ppcp-oxxo-gateway',
|
||||||
CARD_BUTTON: 'ppcp-card-button-gateway',
|
CARD_BUTTON: 'ppcp-card-button-gateway',
|
||||||
GOOGLEPAY: 'ppcp-googlepay',
|
GOOGLEPAY: 'ppcp-googlepay',
|
||||||
|
APPLEPAY: 'ppcp-applepay',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of valid context values that the button can have.
|
||||||
|
*
|
||||||
|
* The "context" describes the placement or page where a payment button might be displayed.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const PaymentContext = {
|
||||||
|
Cart: 'cart', // Classic cart.
|
||||||
|
Checkout: 'checkout', // Classic checkout.
|
||||||
|
BlockCart: 'cart-block', // Block cart.
|
||||||
|
BlockCheckout: 'checkout-block', // Block checkout.
|
||||||
|
Product: 'product', // Single product page.
|
||||||
|
MiniCart: 'mini-cart', // Mini cart available on all pages except checkout & cart.
|
||||||
|
PayNow: 'pay-now', // Pay for order, via admin generated link.
|
||||||
|
Preview: 'preview', // Layout preview on settings page.
|
||||||
|
|
||||||
|
// Contexts that use blocks to render payment methods.
|
||||||
|
Blocks: [ 'cart-block', 'checkout-block' ],
|
||||||
|
|
||||||
|
// Contexts that display "classic" payment gateways.
|
||||||
|
Gateways: [ 'checkout', 'pay-now' ],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ORDER_BUTTON_SELECTOR = '#place_order';
|
export const ORDER_BUTTON_SELECTOR = '#place_order';
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* Helper function used by PaymentButton instances.
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of recognized event names for payment button events.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const ButtonEvents = Object.freeze( {
|
||||||
|
INVALIDATE: 'ppcp_invalidate_methods',
|
||||||
|
RENDER: 'ppcp_render_method',
|
||||||
|
REDRAW: 'ppcp_redraw_method',
|
||||||
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} defaultId - Default wrapper ID.
|
||||||
|
* @param {string} miniCartId - Wrapper inside the mini-cart.
|
||||||
|
* @param {string} smartButtonId - ID of the smart button wrapper.
|
||||||
|
* @param {string} blockId - Block wrapper ID (express checkout, block cart).
|
||||||
|
* @param {string} gatewayId - Gateway wrapper ID (classic checkout).
|
||||||
|
* @return {{MiniCart, Gateway, Block, SmartButton, Default}} List of all wrapper IDs, by context.
|
||||||
|
*/
|
||||||
|
export function combineWrapperIds(
|
||||||
|
defaultId = '',
|
||||||
|
miniCartId = '',
|
||||||
|
smartButtonId = '',
|
||||||
|
blockId = '',
|
||||||
|
gatewayId = ''
|
||||||
|
) {
|
||||||
|
const sanitize = ( id ) => id.replace( /^#/, '' );
|
||||||
|
|
||||||
|
return {
|
||||||
|
Default: sanitize( defaultId ),
|
||||||
|
SmartButton: sanitize( smartButtonId ),
|
||||||
|
Block: sanitize( blockId ),
|
||||||
|
Gateway: sanitize( gatewayId ),
|
||||||
|
MiniCart: sanitize( miniCartId ),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns full payment button styles by combining the global ppcpConfig with
|
||||||
|
* payment-method-specific styling provided via buttonConfig.
|
||||||
|
*
|
||||||
|
* @param {Object} ppcpConfig - Global plugin configuration.
|
||||||
|
* @param {Object} buttonConfig - Payment method specific configuration.
|
||||||
|
* @return {{MiniCart: (*), Default: (*)}} Combined styles, separated by context.
|
||||||
|
*/
|
||||||
|
export function combineStyles( ppcpConfig, buttonConfig ) {
|
||||||
|
return {
|
||||||
|
Default: {
|
||||||
|
...ppcpConfig.style,
|
||||||
|
...buttonConfig.style,
|
||||||
|
},
|
||||||
|
MiniCart: {
|
||||||
|
...ppcpConfig.mini_cart_style,
|
||||||
|
...buttonConfig.mini_cart_style,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if the given event name is a valid Payment Button event.
|
||||||
|
*
|
||||||
|
* @param {string} event - The event name to verify.
|
||||||
|
* @return {boolean} True, if the event name is valid.
|
||||||
|
*/
|
||||||
|
export function isValidButtonEvent( event ) {
|
||||||
|
const buttonEventValues = Object.values( ButtonEvents );
|
||||||
|
|
||||||
|
return buttonEventValues.includes( event );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a payment button event.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options for dispatching the event.
|
||||||
|
* @param {string} options.event - Event to dispatch.
|
||||||
|
* @param {string} [options.paymentMethod] - Optional. Name of payment method, to target a specific button only.
|
||||||
|
* @throws {Error} Throws an error if the event is invalid.
|
||||||
|
*/
|
||||||
|
export function dispatchButtonEvent( { event, paymentMethod = '' } ) {
|
||||||
|
if ( ! isValidButtonEvent( event ) ) {
|
||||||
|
throw new Error( `Invalid event: ${ event }` );
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullEventName = paymentMethod
|
||||||
|
? `${ event }-${ paymentMethod }`
|
||||||
|
: event;
|
||||||
|
|
||||||
|
document.body.dispatchEvent( new Event( fullEventName ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an event listener for the provided button event.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The options for the event listener.
|
||||||
|
* @param {string} options.event - Event to observe.
|
||||||
|
* @param {string} [options.paymentMethod] - The payment method name (optional).
|
||||||
|
* @param {Function} options.callback - The callback function to execute when the event is triggered.
|
||||||
|
* @throws {Error} Throws an error if the event is invalid.
|
||||||
|
*/
|
||||||
|
export function observeButtonEvent( { event, paymentMethod = '', callback } ) {
|
||||||
|
if ( ! isValidButtonEvent( event ) ) {
|
||||||
|
throw new Error( `Invalid event: ${ event }` );
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullEventName = paymentMethod
|
||||||
|
? `${ event }-${ paymentMethod }`
|
||||||
|
: event;
|
||||||
|
|
||||||
|
document.body.addEventListener( fullEventName, callback );
|
||||||
|
}
|
|
@ -20,10 +20,7 @@ const onApprove = ( context, errorHandler ) => {
|
||||||
} )
|
} )
|
||||||
.then( ( data ) => {
|
.then( ( data ) => {
|
||||||
if ( ! data.success ) {
|
if ( ! data.success ) {
|
||||||
errorHandler.genericError();
|
location.href = context.config.redirect;
|
||||||
return actions.restart().catch( ( err ) => {
|
|
||||||
errorHandler.genericError();
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderReceivedUrl = data.data?.order_received_url;
|
const orderReceivedUrl = data.data?.order_received_url;
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { show } from '../Helper/Hiding';
|
||||||
|
import { renderFields } from '../../../../../ppcp-card-fields/resources/js/Render';
|
||||||
|
import {
|
||||||
|
addPaymentMethodConfiguration,
|
||||||
|
cardFieldsConfiguration,
|
||||||
|
} from '../../../../../ppcp-save-payment-methods/resources/js/Configuration';
|
||||||
|
|
||||||
|
class CardFieldsFreeTrialRenderer {
|
||||||
|
constructor( defaultConfig, errorHandler, spinner ) {
|
||||||
|
this.defaultConfig = defaultConfig;
|
||||||
|
this.errorHandler = errorHandler;
|
||||||
|
this.spinner = spinner;
|
||||||
|
}
|
||||||
|
|
||||||
|
render( wrapper, contextConfig ) {
|
||||||
|
if (
|
||||||
|
( this.defaultConfig.context !== 'checkout' &&
|
||||||
|
this.defaultConfig.context !== 'pay-now' ) ||
|
||||||
|
wrapper === null ||
|
||||||
|
document.querySelector( wrapper ) === null
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonSelector = wrapper + ' button';
|
||||||
|
|
||||||
|
const gateWayBox = document.querySelector(
|
||||||
|
'.payment_box.payment_method_ppcp-credit-card-gateway'
|
||||||
|
);
|
||||||
|
if ( ! gateWayBox ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldDisplayStyle = gateWayBox.style.display;
|
||||||
|
gateWayBox.style.display = 'block';
|
||||||
|
|
||||||
|
const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' );
|
||||||
|
if ( hideDccGateway ) {
|
||||||
|
hideDccGateway.parentNode.removeChild( hideDccGateway );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorHandler.clear();
|
||||||
|
|
||||||
|
let cardFields = paypal.CardFields(
|
||||||
|
addPaymentMethodConfiguration( this.defaultConfig )
|
||||||
|
);
|
||||||
|
if ( this.defaultConfig.user.is_logged ) {
|
||||||
|
cardFields = paypal.CardFields(
|
||||||
|
cardFieldsConfiguration( this.defaultConfig, this.errorHandler )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( cardFields.isEligible() ) {
|
||||||
|
renderFields( cardFields );
|
||||||
|
}
|
||||||
|
|
||||||
|
gateWayBox.style.display = oldDisplayStyle;
|
||||||
|
|
||||||
|
show( buttonSelector );
|
||||||
|
|
||||||
|
if ( this.defaultConfig.cart_contains_subscription ) {
|
||||||
|
const saveToAccount = document.querySelector(
|
||||||
|
'#wc-ppcp-credit-card-gateway-new-payment-method'
|
||||||
|
);
|
||||||
|
if ( saveToAccount ) {
|
||||||
|
saveToAccount.checked = true;
|
||||||
|
saveToAccount.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector( buttonSelector )
|
||||||
|
?.addEventListener( 'click', ( event ) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.spinner.block();
|
||||||
|
this.errorHandler.clear();
|
||||||
|
|
||||||
|
cardFields.submit().catch( ( error ) => {
|
||||||
|
console.error( error );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
disableFields() {}
|
||||||
|
enableFields() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CardFieldsFreeTrialRenderer;
|
|
@ -1,5 +1,5 @@
|
||||||
import { show } from '../Helper/Hiding';
|
import { show } from '../Helper/Hiding';
|
||||||
import { cardFieldStyles } from '../Helper/CardFieldsHelper';
|
import { renderFields } from '../../../../../ppcp-card-fields/resources/js/Render';
|
||||||
|
|
||||||
class CardFieldsRenderer {
|
class CardFieldsRenderer {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -45,7 +45,7 @@ class CardFieldsRenderer {
|
||||||
hideDccGateway.parentNode.removeChild( hideDccGateway );
|
hideDccGateway.parentNode.removeChild( hideDccGateway );
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardField = paypal.CardFields( {
|
const cardFields = paypal.CardFields( {
|
||||||
createOrder: contextConfig.createOrder,
|
createOrder: contextConfig.createOrder,
|
||||||
onApprove( data ) {
|
onApprove( data ) {
|
||||||
return contextConfig.onApprove( data );
|
return contextConfig.onApprove( data );
|
||||||
|
@ -56,79 +56,8 @@ class CardFieldsRenderer {
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
if ( cardField.isEligible() ) {
|
if ( cardFields.isEligible() ) {
|
||||||
const nameField = document.getElementById(
|
renderFields( cardFields );
|
||||||
'ppcp-credit-card-gateway-card-name'
|
|
||||||
);
|
|
||||||
if ( nameField ) {
|
|
||||||
const styles = cardFieldStyles( nameField );
|
|
||||||
const fieldOptions = {
|
|
||||||
style: { input: styles },
|
|
||||||
};
|
|
||||||
if ( nameField.getAttribute( 'placeholder' ) ) {
|
|
||||||
fieldOptions.placeholder =
|
|
||||||
nameField.getAttribute( 'placeholder' );
|
|
||||||
}
|
|
||||||
cardField
|
|
||||||
.NameField( fieldOptions )
|
|
||||||
.render( nameField.parentNode );
|
|
||||||
nameField.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberField = document.getElementById(
|
|
||||||
'ppcp-credit-card-gateway-card-number'
|
|
||||||
);
|
|
||||||
if ( numberField ) {
|
|
||||||
const styles = cardFieldStyles( numberField );
|
|
||||||
const fieldOptions = {
|
|
||||||
style: { input: styles },
|
|
||||||
};
|
|
||||||
if ( numberField.getAttribute( 'placeholder' ) ) {
|
|
||||||
fieldOptions.placeholder =
|
|
||||||
numberField.getAttribute( 'placeholder' );
|
|
||||||
}
|
|
||||||
cardField
|
|
||||||
.NumberField( fieldOptions )
|
|
||||||
.render( numberField.parentNode );
|
|
||||||
numberField.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const expiryField = document.getElementById(
|
|
||||||
'ppcp-credit-card-gateway-card-expiry'
|
|
||||||
);
|
|
||||||
if ( expiryField ) {
|
|
||||||
const styles = cardFieldStyles( expiryField );
|
|
||||||
const fieldOptions = {
|
|
||||||
style: { input: styles },
|
|
||||||
};
|
|
||||||
if ( expiryField.getAttribute( 'placeholder' ) ) {
|
|
||||||
fieldOptions.placeholder =
|
|
||||||
expiryField.getAttribute( 'placeholder' );
|
|
||||||
}
|
|
||||||
cardField
|
|
||||||
.ExpiryField( fieldOptions )
|
|
||||||
.render( expiryField.parentNode );
|
|
||||||
expiryField.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const cvvField = document.getElementById(
|
|
||||||
'ppcp-credit-card-gateway-card-cvc'
|
|
||||||
);
|
|
||||||
if ( cvvField ) {
|
|
||||||
const styles = cardFieldStyles( cvvField );
|
|
||||||
const fieldOptions = {
|
|
||||||
style: { input: styles },
|
|
||||||
};
|
|
||||||
if ( cvvField.getAttribute( 'placeholder' ) ) {
|
|
||||||
fieldOptions.placeholder =
|
|
||||||
cvvField.getAttribute( 'placeholder' );
|
|
||||||
}
|
|
||||||
cardField
|
|
||||||
.CVVField( fieldOptions )
|
|
||||||
.render( cvvField.parentNode );
|
|
||||||
cvvField.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.dispatchEvent( new CustomEvent( 'hosted_fields_loaded' ) );
|
document.dispatchEvent( new CustomEvent( 'hosted_fields_loaded' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +98,7 @@ class CardFieldsRenderer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cardField.submit().catch( ( error ) => {
|
cardFields.submit().catch( ( error ) => {
|
||||||
this.spinner.unblock();
|
this.spinner.unblock();
|
||||||
console.error( error );
|
console.error( error );
|
||||||
this.errorHandler.message(
|
this.errorHandler.message(
|
||||||
|
|
|
@ -0,0 +1,822 @@
|
||||||
|
import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger';
|
||||||
|
import { apmButtonsInit } from '../Helper/ApmButtons';
|
||||||
|
import {
|
||||||
|
getCurrentPaymentMethod,
|
||||||
|
PaymentContext,
|
||||||
|
PaymentMethods,
|
||||||
|
} from '../Helper/CheckoutMethodState';
|
||||||
|
import {
|
||||||
|
ButtonEvents,
|
||||||
|
dispatchButtonEvent,
|
||||||
|
observeButtonEvent,
|
||||||
|
} from '../Helper/PaymentButtonHelpers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of all available styling options for this button.
|
||||||
|
*
|
||||||
|
* @typedef {Object} StylesCollection
|
||||||
|
* @property {string} Default - Default button styling.
|
||||||
|
* @property {string} MiniCart - Styles for mini-cart button.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of all available wrapper IDs that are possible for the button.
|
||||||
|
*
|
||||||
|
* @typedef {Object} WrapperCollection
|
||||||
|
* @property {string} Default - Default button wrapper.
|
||||||
|
* @property {string} Gateway - Wrapper for separate gateway.
|
||||||
|
* @property {string} Block - Wrapper for block checkout button.
|
||||||
|
* @property {string} MiniCart - Wrapper for mini-cart button.
|
||||||
|
* @property {string} SmartButton - Wrapper for smart button container.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided PaymentButton instance to a global payment-button collection.
|
||||||
|
*
|
||||||
|
* This is debugging logic that should not be used on a production site.
|
||||||
|
*
|
||||||
|
* @param {string} methodName - Used to group the buttons.
|
||||||
|
* @param {PaymentButton} button - Appended to the button collection.
|
||||||
|
*/
|
||||||
|
const addToDebuggingCollection = ( methodName, button ) => {
|
||||||
|
window.ppcpPaymentButtonList = window.ppcpPaymentButtonList || {};
|
||||||
|
|
||||||
|
const collection = window.ppcpPaymentButtonList;
|
||||||
|
|
||||||
|
collection[ methodName ] = collection[ methodName ] || [];
|
||||||
|
collection[ methodName ].push( button );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a context-independent instance Map for `PaymentButton` components.
|
||||||
|
*
|
||||||
|
* This function addresses a potential issue in multi-context environments, such as pages using
|
||||||
|
* Block-components. In these scenarios, multiple React execution contexts can lead to duplicate
|
||||||
|
* `PaymentButton` instances. To prevent this, we store instances in a `Map` that is bound to the
|
||||||
|
* document's `body` (the rendering context) rather than to individual React components
|
||||||
|
* (execution contexts).
|
||||||
|
*
|
||||||
|
* The `Map` is created as a non-enumerable, non-writable, and non-configurable property of
|
||||||
|
* `document.body` to ensure its integrity and prevent accidental modifications.
|
||||||
|
*
|
||||||
|
* @return {Map<any, any>} A Map containing all `PaymentButton` instances for the current page.
|
||||||
|
*/
|
||||||
|
const getInstances = () => {
|
||||||
|
const collectionKey = '__ppcpPBInstances';
|
||||||
|
|
||||||
|
if ( ! document.body[ collectionKey ] ) {
|
||||||
|
Object.defineProperty( document.body, collectionKey, {
|
||||||
|
value: new Map(),
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.body[ collectionKey ];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for APM payment buttons, like GooglePay and ApplePay.
|
||||||
|
*
|
||||||
|
* This class is not intended for the PayPal button.
|
||||||
|
*/
|
||||||
|
export default class PaymentButton {
|
||||||
|
/**
|
||||||
|
* Defines the implemented payment method.
|
||||||
|
*
|
||||||
|
* Used to identify and address the button internally.
|
||||||
|
* Overwrite this in the derived class.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static methodId = 'generic';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS class that is added to the payment button wrapper.
|
||||||
|
*
|
||||||
|
* Overwrite this in the derived class.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static cssClass = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {ConsoleLogger}
|
||||||
|
*/
|
||||||
|
#logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the payment button is initialized.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#isInitialized = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The button's context.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
#context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object containing the IDs of all possible wrapper elements that might contain this
|
||||||
|
* button; only one wrapper is relevant, depending on the value of the context.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
#wrappers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {StylesCollection}
|
||||||
|
*/
|
||||||
|
#styles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps track of CSS classes that were added to the wrapper element.
|
||||||
|
* We use this list to remove CSS classes that we've added, e.g. to change shape from
|
||||||
|
* pill to rect in the preview.
|
||||||
|
*
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
#appliedClasses = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APM relevant configuration; e.g., configuration of the GooglePay button.
|
||||||
|
*/
|
||||||
|
#buttonConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin-wide configuration; i.e., PayPal client ID, shop currency, etc.
|
||||||
|
*/
|
||||||
|
#ppcpConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variation of a context bootstrap handler.
|
||||||
|
*/
|
||||||
|
#externalHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variation of a context handler object, like CheckoutHandler.
|
||||||
|
* This handler provides a standardized interface for certain standardized checks and actions.
|
||||||
|
*/
|
||||||
|
#contextHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the current browser/website support the payment method.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#isEligible = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this button is visible. Modified by `show()` and `hide()`
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#isVisible = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently visible payment button.
|
||||||
|
*
|
||||||
|
* @see {PaymentButton.insertButton}
|
||||||
|
* @type {HTMLElement|null}
|
||||||
|
*/
|
||||||
|
#button = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create a new PaymentButton while limiting a single instance per context.
|
||||||
|
*
|
||||||
|
* @param {string} context - Button context name.
|
||||||
|
* @param {unknown} externalHandler - Handler object.
|
||||||
|
* @param {Object} buttonConfig - Payment button specific configuration.
|
||||||
|
* @param {Object} ppcpConfig - Plugin wide configuration object.
|
||||||
|
* @param {unknown} contextHandler - Handler object.
|
||||||
|
* @return {PaymentButton} The button instance.
|
||||||
|
*/
|
||||||
|
static createButton(
|
||||||
|
context,
|
||||||
|
externalHandler,
|
||||||
|
buttonConfig,
|
||||||
|
ppcpConfig,
|
||||||
|
contextHandler
|
||||||
|
) {
|
||||||
|
const buttonInstances = getInstances();
|
||||||
|
const instanceKey = `${ this.methodId }.${ context }`;
|
||||||
|
|
||||||
|
if ( ! buttonInstances.has( instanceKey ) ) {
|
||||||
|
const button = new this(
|
||||||
|
context,
|
||||||
|
externalHandler,
|
||||||
|
buttonConfig,
|
||||||
|
ppcpConfig,
|
||||||
|
contextHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
buttonInstances.set( instanceKey, button );
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttonInstances.get( instanceKey );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list with all wrapper IDs for the implemented payment method, categorized by
|
||||||
|
* context.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @param {Object} buttonConfig - Payment method specific configuration.
|
||||||
|
* @param {Object} ppcpConfig - Global plugin configuration.
|
||||||
|
* @return {{MiniCart, Gateway, Block, SmartButton, Default}} The wrapper ID collection.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
static getWrappers( buttonConfig, ppcpConfig ) {
|
||||||
|
throw new Error( 'Must be implemented in the child class' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all button styles for the implemented payment method, categorized by
|
||||||
|
* context.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @param {Object} buttonConfig - Payment method specific configuration.
|
||||||
|
* @param {Object} ppcpConfig - Global plugin configuration.
|
||||||
|
* @return {{MiniCart: (*), Default: (*)}} Combined styles, separated by context.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
static getStyles( buttonConfig, ppcpConfig ) {
|
||||||
|
throw new Error( 'Must be implemented in the child class' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the payment button instance.
|
||||||
|
*
|
||||||
|
* Do not create new button instances directly; use the `createButton` method instead
|
||||||
|
* to avoid multiple button instances handling the same context.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} context - Button context name.
|
||||||
|
* @param {Object} externalHandler - Handler object.
|
||||||
|
* @param {Object} buttonConfig - Payment button specific configuration.
|
||||||
|
* @param {Object} ppcpConfig - Plugin wide configuration object.
|
||||||
|
* @param {Object} contextHandler - Handler object.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
context,
|
||||||
|
externalHandler = null,
|
||||||
|
buttonConfig = {},
|
||||||
|
ppcpConfig = {},
|
||||||
|
contextHandler = null
|
||||||
|
) {
|
||||||
|
if ( this.methodId === PaymentButton.methodId ) {
|
||||||
|
throw new Error( 'Cannot initialize the PaymentButton base class' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! buttonConfig ) {
|
||||||
|
buttonConfig = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDebugging = !! buttonConfig?.is_debug;
|
||||||
|
const methodName = this.methodId.replace( /^ppcp?-/, '' );
|
||||||
|
|
||||||
|
this.#context = context;
|
||||||
|
this.#buttonConfig = buttonConfig;
|
||||||
|
this.#ppcpConfig = ppcpConfig;
|
||||||
|
this.#externalHandler = externalHandler;
|
||||||
|
this.#contextHandler = contextHandler;
|
||||||
|
|
||||||
|
this.#logger = new ConsoleLogger( methodName, context );
|
||||||
|
|
||||||
|
if ( isDebugging ) {
|
||||||
|
this.#logger.enabled = true;
|
||||||
|
addToDebuggingCollection( methodName, this );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#wrappers = this.constructor.getWrappers(
|
||||||
|
this.#buttonConfig,
|
||||||
|
this.#ppcpConfig
|
||||||
|
);
|
||||||
|
this.applyButtonStyles( this.#buttonConfig );
|
||||||
|
|
||||||
|
apmButtonsInit( this.#ppcpConfig );
|
||||||
|
this.initEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal ID of the payment gateway.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {string} The internal gateway ID, defined in the derived class.
|
||||||
|
*/
|
||||||
|
get methodId() {
|
||||||
|
return this.constructor.methodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS class that is added to the button wrapper.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {string} CSS class, defined in the derived class.
|
||||||
|
*/
|
||||||
|
get cssClass() {
|
||||||
|
return this.constructor.cssClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the payment button was fully initialized.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {boolean} True indicates, that the button was fully initialized.
|
||||||
|
*/
|
||||||
|
get isInitialized() {
|
||||||
|
return this.#isInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The button's context.
|
||||||
|
*
|
||||||
|
* TODO: Convert the string to a context-object (primitive obsession smell)
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {string} The button context.
|
||||||
|
*/
|
||||||
|
get context() {
|
||||||
|
return this.#context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration, specific for the implemented payment button.
|
||||||
|
*
|
||||||
|
* @return {Object} Configuration object.
|
||||||
|
*/
|
||||||
|
get buttonConfig() {
|
||||||
|
return this.#buttonConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin-wide configuration; i.e., PayPal client ID, shop currency, etc.
|
||||||
|
*
|
||||||
|
* @return {Object} Configuration object.
|
||||||
|
*/
|
||||||
|
get ppcpConfig() {
|
||||||
|
return this.#ppcpConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Object} The bootstrap handler instance, or an empty object.
|
||||||
|
*/
|
||||||
|
get externalHandler() {
|
||||||
|
return this.#externalHandler || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the button's context handler.
|
||||||
|
* When no context handler was provided (like for a preview button), an empty object is
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* @return {Object} The context handler instance, or an empty object.
|
||||||
|
*/
|
||||||
|
get contextHandler() {
|
||||||
|
return this.#contextHandler || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether customers need to provide shipping details during payment.
|
||||||
|
*
|
||||||
|
* Can be extended by child classes to take method specific configuration into account.
|
||||||
|
*
|
||||||
|
* @return {boolean} True means, shipping fields are displayed and must be filled.
|
||||||
|
*/
|
||||||
|
get requiresShipping() {
|
||||||
|
// Default check: Is shipping enabled in WooCommerce?
|
||||||
|
return (
|
||||||
|
'function' === typeof this.contextHandler.shippingAllowed &&
|
||||||
|
this.contextHandler.shippingAllowed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button wrapper details.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {WrapperCollection} Wrapper IDs.
|
||||||
|
*/
|
||||||
|
get wrappers() {
|
||||||
|
return this.#wrappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the context-relevant button style object.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {string} Styling options.
|
||||||
|
*/
|
||||||
|
get style() {
|
||||||
|
if ( PaymentContext.MiniCart === this.context ) {
|
||||||
|
return this.#styles.MiniCart;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#styles.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the context-relevant wrapper ID.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {string} The wrapper-element's ID (without the `#` prefix).
|
||||||
|
*/
|
||||||
|
get wrapperId() {
|
||||||
|
if ( PaymentContext.MiniCart === this.context ) {
|
||||||
|
return this.wrappers.MiniCart;
|
||||||
|
} else if ( this.isSeparateGateway ) {
|
||||||
|
return this.wrappers.Gateway;
|
||||||
|
} else if ( PaymentContext.Blocks.includes( this.context ) ) {
|
||||||
|
return this.wrappers.Block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.wrappers.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current payment button should be rendered as a stand-alone gateway.
|
||||||
|
* The return value `false` usually means, that the payment button is bundled with all available
|
||||||
|
* payment buttons.
|
||||||
|
*
|
||||||
|
* The decision depends on the button context (placement) and the plugin settings.
|
||||||
|
*
|
||||||
|
* @return {boolean} True, if the current button represents a stand-alone gateway.
|
||||||
|
*/
|
||||||
|
get isSeparateGateway() {
|
||||||
|
return (
|
||||||
|
this.#buttonConfig.is_wc_gateway_enabled &&
|
||||||
|
PaymentContext.Gateways.includes( this.context )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the currently selected payment gateway is set to the payment method.
|
||||||
|
*
|
||||||
|
* Only relevant on checkout pages, when `this.isSeparateGateway` is true.
|
||||||
|
*
|
||||||
|
* @return {boolean} True means that this payment method is selected as current gateway.
|
||||||
|
*/
|
||||||
|
get isCurrentGateway() {
|
||||||
|
if ( ! this.isSeparateGateway ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to rely on `getCurrentPaymentMethod()` here, as the `CheckoutBootstrap.js`
|
||||||
|
* module fires the "ButtonEvents.RENDER" event before any PaymentButton instances are
|
||||||
|
* created. I.e. we cannot observe the initial gateway selection event.
|
||||||
|
*/
|
||||||
|
return this.methodId === getCurrentPaymentMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags a preview button without actual payment logic.
|
||||||
|
*
|
||||||
|
* @return {boolean} True indicates a preview instance that has no payment logic.
|
||||||
|
*/
|
||||||
|
get isPreview() {
|
||||||
|
return PaymentContext.Preview === this.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the browser can accept this payment method.
|
||||||
|
*
|
||||||
|
* @return {boolean} True, if payments are technically possible.
|
||||||
|
*/
|
||||||
|
get isEligible() {
|
||||||
|
return this.#isEligible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the eligibility state of this button component.
|
||||||
|
*
|
||||||
|
* @param {boolean} newState Whether the browser can accept payments.
|
||||||
|
*/
|
||||||
|
set isEligible( newState ) {
|
||||||
|
if ( newState === this.#isEligible ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#isEligible = newState;
|
||||||
|
this.triggerRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The visibility state of the button.
|
||||||
|
* This flag does not reflect actual visibility on the page, but rather, if the button
|
||||||
|
* is intended/allowed to be displayed, in case all other checks pass.
|
||||||
|
*
|
||||||
|
* @return {boolean} True indicates, that the button can be displayed.
|
||||||
|
*/
|
||||||
|
get isVisible() {
|
||||||
|
return this.#isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the visibility of the button.
|
||||||
|
*
|
||||||
|
* A visible button does not always force the button to render on the page. It only means, that
|
||||||
|
* the button is allowed or not allowed to render, if certain other conditions are met.
|
||||||
|
*
|
||||||
|
* @param {boolean} newState Whether rendering the button is allowed.
|
||||||
|
*/
|
||||||
|
set isVisible( newState ) {
|
||||||
|
if ( this.#isVisible === newState ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#isVisible = newState;
|
||||||
|
this.triggerRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the HTML element that wraps the current button
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {HTMLElement|null} The wrapper element, or null.
|
||||||
|
*/
|
||||||
|
get wrapperElement() {
|
||||||
|
return document.getElementById( this.wrapperId );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the main button-wrapper is present in the current DOM.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
* @return {boolean} True, if the button context (wrapper element) is found.
|
||||||
|
*/
|
||||||
|
get isPresent() {
|
||||||
|
return this.wrapperElement instanceof HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks, if the payment button is still attached to the DOM.
|
||||||
|
*
|
||||||
|
* WooCommerce performs some partial reloads in many cases, which can lead to our payment
|
||||||
|
* button
|
||||||
|
* to move into the browser's memory. In that case, we need to recreate the button in the
|
||||||
|
* updated DOM.
|
||||||
|
*
|
||||||
|
* @return {boolean} True means, the button is still present (and typically visible) on the
|
||||||
|
* page.
|
||||||
|
*/
|
||||||
|
get isButtonAttached() {
|
||||||
|
if ( ! this.#button ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = this.#button.parentElement;
|
||||||
|
while ( parent?.parentElement ) {
|
||||||
|
if ( 'BODY' === parent.tagName ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a debug detail to the browser console.
|
||||||
|
*
|
||||||
|
* @param {any} args
|
||||||
|
*/
|
||||||
|
log( ...args ) {
|
||||||
|
this.#logger.log( ...args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log an error message to the browser console.
|
||||||
|
*
|
||||||
|
* @param {any} args
|
||||||
|
*/
|
||||||
|
error( ...args ) {
|
||||||
|
this.#logger.error( ...args );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current button instance has valid and complete configuration details.
|
||||||
|
* Used during initialization to decide if the button can be initialized or should be skipped.
|
||||||
|
*
|
||||||
|
* Can be implemented by the derived class.
|
||||||
|
*
|
||||||
|
* @param {boolean} [silent=false] - Set to true to suppress console errors.
|
||||||
|
* @return {boolean} True indicates the config is valid and initialization can continue.
|
||||||
|
*/
|
||||||
|
validateConfiguration( silent = false ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyButtonStyles( buttonConfig, ppcpConfig = null ) {
|
||||||
|
if ( ! ppcpConfig ) {
|
||||||
|
ppcpConfig = this.ppcpConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#styles = this.constructor.getStyles( buttonConfig, ppcpConfig );
|
||||||
|
|
||||||
|
if ( this.isInitialized ) {
|
||||||
|
this.triggerRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the button instance. Must be called before the initial `init()`.
|
||||||
|
*
|
||||||
|
* Parameters are defined by the derived class.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
configure() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be named `init()` to simulate "protected" visibility:
|
||||||
|
* Since the derived class also implements a method with the same name, this method can only
|
||||||
|
* be called by the derived class, but not from any other code.
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
this.#isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must be named `reinit()` to simulate "protected" visibility:
|
||||||
|
* Since the derived class also implements a method with the same name, this method can only
|
||||||
|
* be called by the derived class, but not from any other code.
|
||||||
|
*/
|
||||||
|
reinit() {
|
||||||
|
this.#isInitialized = false;
|
||||||
|
this.#isEligible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerRedraw() {
|
||||||
|
this.showPaymentGateway();
|
||||||
|
|
||||||
|
dispatchButtonEvent( {
|
||||||
|
event: ButtonEvents.REDRAW,
|
||||||
|
paymentMethod: this.methodId,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches event listeners to show or hide the payment button when needed.
|
||||||
|
*/
|
||||||
|
initEventListeners() {
|
||||||
|
// Refresh the button - this might show, hide or re-create the payment button.
|
||||||
|
observeButtonEvent( {
|
||||||
|
event: ButtonEvents.REDRAW,
|
||||||
|
paymentMethod: this.methodId,
|
||||||
|
callback: () => this.refresh(),
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Events relevant for buttons inside a payment gateway.
|
||||||
|
if ( PaymentContext.Gateways.includes( this.context ) ) {
|
||||||
|
const parentMethod = this.isSeparateGateway
|
||||||
|
? this.methodId
|
||||||
|
: PaymentMethods.PAYPAL;
|
||||||
|
|
||||||
|
// Hide the button right after the user selected _any_ gateway.
|
||||||
|
observeButtonEvent( {
|
||||||
|
event: ButtonEvents.INVALIDATE,
|
||||||
|
callback: () => ( this.isVisible = false ),
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Show the button (again) when the user selected the current gateway.
|
||||||
|
observeButtonEvent( {
|
||||||
|
event: ButtonEvents.RENDER,
|
||||||
|
paymentMethod: parentMethod,
|
||||||
|
callback: () => ( this.isVisible = true ),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the payment button on the page.
|
||||||
|
*/
|
||||||
|
refresh() {
|
||||||
|
if ( ! this.isPresent ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applyWrapperStyles();
|
||||||
|
|
||||||
|
if ( this.isEligible && this.isPresent && this.isVisible ) {
|
||||||
|
if ( ! this.isButtonAttached ) {
|
||||||
|
this.log( 'refresh.addButton' );
|
||||||
|
this.addButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the custom payment gateway visible by removing initial inline styles from the DOM.
|
||||||
|
*
|
||||||
|
* Only relevant on the checkout page, i.e., when `this.isSeparateGateway` is `true`
|
||||||
|
*/
|
||||||
|
showPaymentGateway() {
|
||||||
|
if ( ! this.isSeparateGateway || ! this.isEligible ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styleSelectors = `style[data-hide-gateway="${ this.methodId }"]`;
|
||||||
|
|
||||||
|
const styles = document.querySelectorAll( styleSelectors );
|
||||||
|
|
||||||
|
if ( ! styles.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.log( 'Show gateway' );
|
||||||
|
|
||||||
|
styles.forEach( ( el ) => el.remove() );
|
||||||
|
|
||||||
|
// This code runs only once, during button initialization, and fixes the initial visibility.
|
||||||
|
this.isVisible = this.isCurrentGateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies CSS classes and inline styling to the payment button wrapper.
|
||||||
|
*/
|
||||||
|
applyWrapperStyles() {
|
||||||
|
const wrapper = this.wrapperElement;
|
||||||
|
const { shape, height } = this.style;
|
||||||
|
|
||||||
|
for ( const classItem of this.#appliedClasses ) {
|
||||||
|
wrapper.classList.remove( classItem );
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#appliedClasses = [];
|
||||||
|
|
||||||
|
const newClasses = [
|
||||||
|
`ppcp-button-${ shape }`,
|
||||||
|
'ppcp-button-apm',
|
||||||
|
this.cssClass,
|
||||||
|
];
|
||||||
|
|
||||||
|
wrapper.classList.add( ...newClasses );
|
||||||
|
this.#appliedClasses.push( ...newClasses );
|
||||||
|
|
||||||
|
if ( height ) {
|
||||||
|
wrapper.style.height = `${ height }px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the wrapper visibility.
|
||||||
|
wrapper.style.display = this.isVisible ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new payment button (HTMLElement) and must call `this.insertButton()` to display
|
||||||
|
* that button in the correct wrapper.
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
addButton() {
|
||||||
|
throw new Error( 'Must be implemented by the child class' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the button wrapper element and inserts the provided payment button into the DOM.
|
||||||
|
*
|
||||||
|
* If a payment button was previously inserted to the wrapper, calling this method again will
|
||||||
|
* first remove the previous button.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} button - The button element to inject.
|
||||||
|
*/
|
||||||
|
insertButton( button ) {
|
||||||
|
if ( ! this.isPresent ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = this.wrapperElement;
|
||||||
|
|
||||||
|
if ( this.#button ) {
|
||||||
|
this.removeButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log( 'addButton', button );
|
||||||
|
|
||||||
|
this.#button = button;
|
||||||
|
wrapper.appendChild( this.#button );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the payment button from the DOM.
|
||||||
|
*/
|
||||||
|
removeButton() {
|
||||||
|
if ( ! this.isPresent || ! this.#button ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log( 'removeButton' );
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.wrapperElement.removeChild( this.#button );
|
||||||
|
} catch ( Exception ) {
|
||||||
|
// Ignore this.
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#button = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
import { loadCustomScript } from '@paypal/paypal-js';
|
import { loadCustomScript } from '@paypal/paypal-js';
|
||||||
import widgetBuilder from './WidgetBuilder';
|
import widgetBuilder from './WidgetBuilder';
|
||||||
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
|
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
|
||||||
|
import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages all PreviewButton instances of a certain payment method on the page.
|
* Manages all PreviewButton instances of a certain payment method on the page.
|
||||||
*/
|
*/
|
||||||
class PreviewButtonManager {
|
class PreviewButtonManager {
|
||||||
|
/**
|
||||||
|
* @type {ConsoleLogger}
|
||||||
|
*/
|
||||||
|
#logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the promise.
|
* Resolves the promise.
|
||||||
* Used by `this.boostrap()` to process enqueued initialization logic.
|
* Used by `this.boostrap()` to process enqueued initialization logic.
|
||||||
|
@ -32,6 +38,9 @@ class PreviewButtonManager {
|
||||||
this.apiConfig = null;
|
this.apiConfig = null;
|
||||||
this.apiError = '';
|
this.apiError = '';
|
||||||
|
|
||||||
|
this.#logger = new ConsoleLogger( this.methodName, 'preview-manager' );
|
||||||
|
this.#logger.enabled = true; // Manually set this to true for development.
|
||||||
|
|
||||||
this.#onInit = new Promise( ( resolve ) => {
|
this.#onInit = new Promise( ( resolve ) => {
|
||||||
this.#onInitResolver = resolve;
|
this.#onInitResolver = resolve;
|
||||||
} );
|
} );
|
||||||
|
@ -61,9 +70,11 @@ class PreviewButtonManager {
|
||||||
* Responsible for fetching and returning the PayPal configuration object for this payment
|
* Responsible for fetching and returning the PayPal configuration object for this payment
|
||||||
* method.
|
* method.
|
||||||
*
|
*
|
||||||
|
* @abstract
|
||||||
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
||||||
* @return {Promise<{}>}
|
* @return {Promise<{}>}
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
async fetchConfig( payPal ) {
|
async fetchConfig( payPal ) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The "fetchConfig" method must be implemented by the derived class'
|
'The "fetchConfig" method must be implemented by the derived class'
|
||||||
|
@ -74,9 +85,11 @@ class PreviewButtonManager {
|
||||||
* Protected method that needs to be implemented by the derived class.
|
* Protected method that needs to be implemented by the derived class.
|
||||||
* This method is responsible for creating a new PreviewButton instance and returning it.
|
* This method is responsible for creating a new PreviewButton instance and returning it.
|
||||||
*
|
*
|
||||||
|
* @abstract
|
||||||
* @param {string} wrapperId - CSS ID of the wrapper element.
|
* @param {string} wrapperId - CSS ID of the wrapper element.
|
||||||
* @return {PreviewButton}
|
* @return {PreviewButton}
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
createButtonInstance( wrapperId ) {
|
createButtonInstance( wrapperId ) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The "createButtonInstance" method must be implemented by the derived class'
|
'The "createButtonInstance" method must be implemented by the derived class'
|
||||||
|
@ -90,7 +103,7 @@ class PreviewButtonManager {
|
||||||
* This dummy is only visible on the admin side, and not rendered on the front-end.
|
* This dummy is only visible on the admin side, and not rendered on the front-end.
|
||||||
*
|
*
|
||||||
* @todo Consider refactoring this into a new class that extends the PreviewButton class.
|
* @todo Consider refactoring this into a new class that extends the PreviewButton class.
|
||||||
* @param wrapperId
|
* @param {string} wrapperId
|
||||||
* @return {any}
|
* @return {any}
|
||||||
*/
|
*/
|
||||||
createDummy( wrapperId ) {
|
createDummy( wrapperId ) {
|
||||||
|
@ -128,13 +141,24 @@ class PreviewButtonManager {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output a debug message to the console, with a module-specific prefix.
|
||||||
|
*
|
||||||
|
* @param {string} message - Log message.
|
||||||
|
* @param {...any} args - Optional. Additional args to output.
|
||||||
|
*/
|
||||||
|
log( message, ...args ) {
|
||||||
|
this.#logger.log( message, ...args );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output an error message to the console, with a module-specific prefix.
|
* Output an error message to the console, with a module-specific prefix.
|
||||||
* @param message
|
*
|
||||||
* @param {...any} args
|
* @param {string} message - Log message.
|
||||||
|
* @param {...any} args - Optional. Additional args to output.
|
||||||
*/
|
*/
|
||||||
error( message, ...args ) {
|
error( message, ...args ) {
|
||||||
console.error( `${ this.methodName } ${ message }`, ...args );
|
this.#logger.error( message, ...args );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -242,21 +266,21 @@ class PreviewButtonManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! this.shouldInsertPreviewButton( id ) ) {
|
if ( ! this.shouldInsertPreviewButton( id ) ) {
|
||||||
|
this.log( 'Skip preview rendering for this preview-box', id );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! this.buttons[ id ] ) {
|
if ( ! this.buttons[ id ] ) {
|
||||||
this._addButton( id, ppcpConfig );
|
this._addButton( id, ppcpConfig );
|
||||||
} else {
|
} else {
|
||||||
// This is a debounced method, that fires after 100ms.
|
this._configureButton( id, ppcpConfig );
|
||||||
this._configureAllButtons( ppcpConfig );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the preview box supports the current button.
|
* Determines if the preview box supports the current button.
|
||||||
*
|
*
|
||||||
* When this function returns false, this manager instance does not create a new preview button.
|
* E.g. "Should the current preview-box display Google Pay buttons?"
|
||||||
*
|
*
|
||||||
* @param {string} previewId - ID of the inner preview box container.
|
* @param {string} previewId - ID of the inner preview box container.
|
||||||
* @return {boolean} True if the box is eligible for the preview button, false otherwise.
|
* @return {boolean} True if the box is eligible for the preview button, false otherwise.
|
||||||
|
@ -271,10 +295,14 @@ class PreviewButtonManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies a new configuration to an existing preview button.
|
* Applies a new configuration to an existing preview button.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
* @param id
|
* @param id
|
||||||
* @param ppcpConfig
|
* @param ppcpConfig
|
||||||
*/
|
*/
|
||||||
_configureButton( id, ppcpConfig ) {
|
_configureButton( id, ppcpConfig ) {
|
||||||
|
this.log( 'configureButton', id, ppcpConfig );
|
||||||
|
|
||||||
this.buttons[ id ]
|
this.buttons[ id ]
|
||||||
.setDynamic( this.isDynamic() )
|
.setDynamic( this.isDynamic() )
|
||||||
.setPpcpConfig( ppcpConfig )
|
.setPpcpConfig( ppcpConfig )
|
||||||
|
@ -283,9 +311,13 @@ class PreviewButtonManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apples the provided configuration to all existing preview buttons.
|
* Apples the provided configuration to all existing preview buttons.
|
||||||
* @param ppcpConfig
|
*
|
||||||
|
* @private
|
||||||
|
* @param ppcpConfig - The new styling to use for the preview buttons.
|
||||||
*/
|
*/
|
||||||
_configureAllButtons( ppcpConfig ) {
|
_configureAllButtons( ppcpConfig ) {
|
||||||
|
this.log( 'configureAllButtons', ppcpConfig );
|
||||||
|
|
||||||
Object.entries( this.buttons ).forEach( ( [ id, button ] ) => {
|
Object.entries( this.buttons ).forEach( ( [ id, button ] ) => {
|
||||||
this._configureButton( id, {
|
this._configureButton( id, {
|
||||||
...ppcpConfig,
|
...ppcpConfig,
|
||||||
|
@ -302,13 +334,20 @@ class PreviewButtonManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
|
* Creates a new preview button, that is rendered once the bootstrapping Promise resolves.
|
||||||
* @param id
|
*
|
||||||
* @param ppcpConfig
|
* @private
|
||||||
|
* @param id - The button to add.
|
||||||
|
* @param ppcpConfig - The styling to apply to the preview button.
|
||||||
*/
|
*/
|
||||||
_addButton( id, ppcpConfig ) {
|
_addButton( id, ppcpConfig ) {
|
||||||
|
this.log( 'addButton', id, ppcpConfig );
|
||||||
|
|
||||||
const createButton = () => {
|
const createButton = () => {
|
||||||
if ( ! this.buttons[ id ] ) {
|
if ( ! this.buttons[ id ] ) {
|
||||||
|
this.log( 'createButton.new', id );
|
||||||
|
|
||||||
let newInst;
|
let newInst;
|
||||||
|
|
||||||
if ( this.apiConfig && 'object' === typeof this.apiConfig ) {
|
if ( this.apiConfig && 'object' === typeof this.apiConfig ) {
|
||||||
newInst = this.createButtonInstance( id ).setButtonConfig(
|
newInst = this.createButtonInstance( id ).setButtonConfig(
|
||||||
this.buttonConfig
|
this.buttonConfig
|
||||||
|
|
|
@ -139,7 +139,7 @@ class Renderer {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check the condition and add the handler if needed
|
// Check the condition and add the handler if needed
|
||||||
if ( this.defaultSettings.should_handle_shipping_in_paypal ) {
|
if ( this.shouldEnableShippingCallback() ) {
|
||||||
options.onShippingOptionsChange = ( data, actions ) => {
|
options.onShippingOptionsChange = ( data, actions ) => {
|
||||||
let shippingOptionsChange =
|
let shippingOptionsChange =
|
||||||
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
|
! this.isVenmoButtonClickedWhenVaultingIsEnabled(
|
||||||
|
@ -227,6 +227,12 @@ class Renderer {
|
||||||
return venmoButtonClicked && this.defaultSettings.vaultingEnabled;
|
return venmoButtonClicked && this.defaultSettings.vaultingEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
shouldEnableShippingCallback = () => {
|
||||||
|
console.log(this.defaultSettings.context, this.defaultSettings)
|
||||||
|
let needShipping = this.defaultSettings.needShipping || this.defaultSettings.context === 'product'
|
||||||
|
return this.defaultSettings.should_handle_shipping_in_paypal && needShipping
|
||||||
|
};
|
||||||
|
|
||||||
isAlreadyRendered( wrapper, fundingSource ) {
|
isAlreadyRendered( wrapper, fundingSource ) {
|
||||||
return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) );
|
return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) );
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button\Assets;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use WC_Order;
|
use WC_Order;
|
||||||
|
use WC_Payment_Tokens;
|
||||||
use WC_Product;
|
use WC_Product;
|
||||||
use WC_Product_Variation;
|
use WC_Product_Variation;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
||||||
|
@ -1292,9 +1293,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
|
||||||
'early_checkout_validation_enabled' => $this->early_validation_enabled,
|
'early_checkout_validation_enabled' => $this->early_validation_enabled,
|
||||||
'funding_sources_without_redirect' => $this->funding_sources_without_redirect,
|
'funding_sources_without_redirect' => $this->funding_sources_without_redirect,
|
||||||
'user' => array(
|
'user' => array(
|
||||||
'is_logged' => is_user_logged_in(),
|
'is_logged' => is_user_logged_in(),
|
||||||
|
'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens( get_current_user_id() ),
|
||||||
),
|
),
|
||||||
'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(),
|
'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(),
|
||||||
|
'needShipping' => WC()->cart->needs_shipping(),
|
||||||
'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ),
|
'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2132,4 +2135,19 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
|
||||||
return $location;
|
return $location;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the given user has WC card payment tokens.
|
||||||
|
*
|
||||||
|
* @param int $user_id The user ID.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function user_has_wc_card_payment_tokens( int $user_id ): bool {
|
||||||
|
$tokens = WC_Payment_Tokens::get_customer_tokens( $user_id, CreditCardGateway::ID );
|
||||||
|
if ( $tokens ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,10 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use WC_Cart;
|
use WC_Cart;
|
||||||
|
use WC_Data_Exception;
|
||||||
use WC_Order;
|
use WC_Order;
|
||||||
use WC_Order_Item_Product;
|
use WC_Order_Item_Product;
|
||||||
use WC_Order_Item_Shipping;
|
use WC_Order_Item_Shipping;
|
||||||
|
@ -85,17 +87,24 @@ class WooCommerceOrderCreator {
|
||||||
throw new RuntimeException( 'Problem creating WC order.' );
|
throw new RuntimeException( 'Problem creating WC order.' );
|
||||||
}
|
}
|
||||||
|
|
||||||
$payer = $order->payer();
|
try {
|
||||||
$shipping = $order->purchase_units()[0]->shipping();
|
$payer = $order->payer();
|
||||||
|
$shipping = $order->purchase_units()[0]->shipping();
|
||||||
|
|
||||||
$this->configure_payment_source( $wc_order );
|
$this->configure_payment_source( $wc_order );
|
||||||
$this->configure_customer( $wc_order );
|
$this->configure_customer( $wc_order );
|
||||||
$this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping );
|
$this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping );
|
||||||
$this->configure_shipping( $wc_order, $payer, $shipping );
|
$this->configure_shipping( $wc_order, $payer, $shipping, $wc_cart );
|
||||||
$this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() );
|
$this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() );
|
||||||
|
|
||||||
$wc_order->calculate_totals();
|
$wc_order->calculate_totals();
|
||||||
$wc_order->save();
|
$wc_order->save();
|
||||||
|
} catch ( Exception $exception ) {
|
||||||
|
$wc_order->delete( true );
|
||||||
|
throw new RuntimeException( 'Failed to create WooCommerce order: ' . $exception->getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
do_action( 'woocommerce_paypal_payments_shipping_callback_woocommerce_order_created', $wc_order, $wc_cart );
|
||||||
|
|
||||||
return $wc_order;
|
return $wc_order;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +162,7 @@ class WooCommerceOrderCreator {
|
||||||
$item->set_total( $subscription_total );
|
$item->set_total( $subscription_total );
|
||||||
|
|
||||||
$subscription->add_product( $product );
|
$subscription->add_product( $product );
|
||||||
$this->configure_shipping( $subscription, $payer, $shipping );
|
$this->configure_shipping( $subscription, $payer, $shipping, $wc_cart );
|
||||||
$this->configure_payment_source( $subscription );
|
$this->configure_payment_source( $subscription );
|
||||||
$this->configure_coupons( $subscription, $wc_cart->get_applied_coupons() );
|
$this->configure_coupons( $subscription, $wc_cart->get_applied_coupons() );
|
||||||
|
|
||||||
|
@ -178,9 +187,11 @@ class WooCommerceOrderCreator {
|
||||||
* @param WC_Order $wc_order The WC order.
|
* @param WC_Order $wc_order The WC order.
|
||||||
* @param Payer|null $payer The payer.
|
* @param Payer|null $payer The payer.
|
||||||
* @param Shipping|null $shipping The shipping.
|
* @param Shipping|null $shipping The shipping.
|
||||||
|
* @param WC_Cart $wc_cart The Cart.
|
||||||
* @return void
|
* @return void
|
||||||
|
* @throws WC_Data_Exception|RuntimeException When failing to configure shipping.
|
||||||
*/
|
*/
|
||||||
protected function configure_shipping( WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping ): void {
|
protected function configure_shipping( WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping, WC_Cart $wc_cart ): void {
|
||||||
$shipping_address = null;
|
$shipping_address = null;
|
||||||
$billing_address = null;
|
$billing_address = null;
|
||||||
$shipping_options = null;
|
$shipping_options = null;
|
||||||
|
@ -218,6 +229,10 @@ class WooCommerceOrderCreator {
|
||||||
$shipping_options = $shipping->options()[0] ?? '';
|
$shipping_options = $shipping->options()[0] ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $wc_cart->needs_shipping() && empty( $shipping_options ) ) {
|
||||||
|
throw new RuntimeException( 'No shipping method has been selected.' );
|
||||||
|
}
|
||||||
|
|
||||||
if ( $shipping_address ) {
|
if ( $shipping_address ) {
|
||||||
$wc_order->set_shipping_address( $shipping_address );
|
$wc_order->set_shipping_address( $shipping_address );
|
||||||
}
|
}
|
||||||
|
|
31
modules/ppcp-card-fields/package.json
Normal file
31
modules/ppcp-card-fields/package.json
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "ppcp-card-fields",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"browserslist": [
|
||||||
|
"> 0.5%",
|
||||||
|
"Safari >= 8",
|
||||||
|
"Chrome >= 41",
|
||||||
|
"Firefox >= 43",
|
||||||
|
"Edge >= 14"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^3.25.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.19",
|
||||||
|
"@babel/preset-env": "^7.19",
|
||||||
|
"babel-loader": "^8.2",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"sass": "^1.42.1",
|
||||||
|
"sass-loader": "^12.1.0",
|
||||||
|
"webpack": "^5.76",
|
||||||
|
"webpack-cli": "^4.10"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||||
|
"watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch",
|
||||||
|
"dev": "cross-env BABEL_ENV=default webpack --watch"
|
||||||
|
}
|
||||||
|
}
|
47
modules/ppcp-card-fields/resources/js/Render.js
Normal file
47
modules/ppcp-card-fields/resources/js/Render.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { cardFieldStyles } from './CardFieldsHelper';
|
||||||
|
|
||||||
|
export function renderFields( cardFields ) {
|
||||||
|
const nameField = document.getElementById(
|
||||||
|
'ppcp-credit-card-gateway-card-name'
|
||||||
|
);
|
||||||
|
if ( nameField && nameField.hidden !== true ) {
|
||||||
|
const styles = cardFieldStyles( nameField );
|
||||||
|
cardFields
|
||||||
|
.NameField( { style: { input: styles } } )
|
||||||
|
.render( nameField.parentNode );
|
||||||
|
nameField.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberField = document.getElementById(
|
||||||
|
'ppcp-credit-card-gateway-card-number'
|
||||||
|
);
|
||||||
|
if ( numberField && numberField.hidden !== true ) {
|
||||||
|
const styles = cardFieldStyles( numberField );
|
||||||
|
cardFields
|
||||||
|
.NumberField( { style: { input: styles } } )
|
||||||
|
.render( numberField.parentNode );
|
||||||
|
numberField.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiryField = document.getElementById(
|
||||||
|
'ppcp-credit-card-gateway-card-expiry'
|
||||||
|
);
|
||||||
|
if ( expiryField && expiryField.hidden !== true ) {
|
||||||
|
const styles = cardFieldStyles( expiryField );
|
||||||
|
cardFields
|
||||||
|
.ExpiryField( { style: { input: styles } } )
|
||||||
|
.render( expiryField.parentNode );
|
||||||
|
expiryField.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cvvField = document.getElementById(
|
||||||
|
'ppcp-credit-card-gateway-card-cvc'
|
||||||
|
);
|
||||||
|
if ( cvvField && cvvField.hidden !== true ) {
|
||||||
|
const styles = cardFieldStyles( cvvField );
|
||||||
|
cardFields
|
||||||
|
.CVVField( { style: { input: styles } } )
|
||||||
|
.render( cvvField.parentNode );
|
||||||
|
cvvField.hidden = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,795 +17,51 @@ return array(
|
||||||
$save_payment_methods_applies = $container->get( 'card-fields.helpers.save-payment-methods-applies' );
|
$save_payment_methods_applies = $container->get( 'card-fields.helpers.save-payment-methods-applies' );
|
||||||
assert( $save_payment_methods_applies instanceof CardFieldsApplies );
|
assert( $save_payment_methods_applies instanceof CardFieldsApplies );
|
||||||
|
|
||||||
return $save_payment_methods_applies->for_country_currency();
|
return $save_payment_methods_applies->for_country();
|
||||||
},
|
},
|
||||||
'card-fields.helpers.save-payment-methods-applies' => static function ( ContainerInterface $container ) : CardFieldsApplies {
|
'card-fields.helpers.save-payment-methods-applies' => static function ( ContainerInterface $container ) : CardFieldsApplies {
|
||||||
return new CardFieldsApplies(
|
return new CardFieldsApplies(
|
||||||
$container->get( 'card-fields.supported-country-currency-matrix' ),
|
$container->get( 'card-fields.supported-country-matrix' ),
|
||||||
$container->get( 'api.shop.currency' ),
|
|
||||||
$container->get( 'api.shop.country' )
|
$container->get( 'api.shop.country' )
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'card-fields.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
|
'card-fields.supported-country-matrix' => static function ( ContainerInterface $container ) : array {
|
||||||
return apply_filters(
|
return apply_filters(
|
||||||
'woocommerce_paypal_payments_card_fields_supported_country_currency_matrix',
|
'woocommerce_paypal_payments_card_fields_supported_country_matrix',
|
||||||
array(
|
array(
|
||||||
'AU' => array(
|
'AU',
|
||||||
'AUD',
|
'AT',
|
||||||
'BRL',
|
'BE',
|
||||||
'CAD',
|
'BG',
|
||||||
'CHF',
|
'CA',
|
||||||
'CZK',
|
'CN',
|
||||||
'DKK',
|
'CY',
|
||||||
'EUR',
|
'CZ',
|
||||||
'GBP',
|
'DK',
|
||||||
'HKD',
|
'EE',
|
||||||
'HUF',
|
'FI',
|
||||||
'ILS',
|
'FR',
|
||||||
'JPY',
|
'DE',
|
||||||
'MXN',
|
'GR',
|
||||||
'NOK',
|
'HU',
|
||||||
'NZD',
|
'IE',
|
||||||
'PHP',
|
'IT',
|
||||||
'PLN',
|
'LV',
|
||||||
'SEK',
|
'LI',
|
||||||
'SGD',
|
'LT',
|
||||||
'THB',
|
'LU',
|
||||||
'TWD',
|
'MT',
|
||||||
'USD',
|
'NL',
|
||||||
),
|
'PL',
|
||||||
'AT' => array(
|
'PT',
|
||||||
'AUD',
|
'RO',
|
||||||
'BRL',
|
'SK',
|
||||||
'CAD',
|
'SI',
|
||||||
'CHF',
|
'ES',
|
||||||
'CZK',
|
'SE',
|
||||||
'DKK',
|
'GB',
|
||||||
'EUR',
|
'US',
|
||||||
'GBP',
|
'NO',
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'BE' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'BG' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'CA' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'CN' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'CY' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'CZ' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'DK' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'EE' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'FI' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'FR' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'DE' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'GR' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'HU' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'IE' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'IT' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'LV' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'LI' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'LT' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'LU' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'MT' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'NL' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'PL' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'PT' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'RO' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'SK' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'SI' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'ES' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'SE' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'GB' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'US' => array(
|
|
||||||
'AUD',
|
|
||||||
'CAD',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'JPY',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
'NO' => array(
|
|
||||||
'AUD',
|
|
||||||
'BRL',
|
|
||||||
'CAD',
|
|
||||||
'CHF',
|
|
||||||
'CZK',
|
|
||||||
'DKK',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'HKD',
|
|
||||||
'HUF',
|
|
||||||
'ILS',
|
|
||||||
'JPY',
|
|
||||||
'MXN',
|
|
||||||
'NOK',
|
|
||||||
'NZD',
|
|
||||||
'PHP',
|
|
||||||
'PLN',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'THB',
|
|
||||||
'TWD',
|
|
||||||
'USD',
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Service for checking whether Card Fields can be used in the current country and the current currency.
|
* Service for checking whether Card Fields can be used in the current country.
|
||||||
*
|
*
|
||||||
* @package WooCommerce\PayPalCommerce\CardFields\Helper
|
* @package WooCommerce\PayPalCommerce\CardFields\Helper
|
||||||
*/
|
*/
|
||||||
|
@ -15,18 +15,11 @@ namespace WooCommerce\PayPalCommerce\CardFields\Helper;
|
||||||
class CardFieldsApplies {
|
class CardFieldsApplies {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The matrix which countries and currency combinations can be used.
|
* The matrix which countries can be used.
|
||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $allowed_country_currency_matrix;
|
private $allowed_country_matrix;
|
||||||
|
|
||||||
/**
|
|
||||||
* 3-letter currency code of the shop.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $currency;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 2-letter country code of the shop.
|
* 2-letter country code of the shop.
|
||||||
|
@ -38,29 +31,23 @@ class CardFieldsApplies {
|
||||||
/**
|
/**
|
||||||
* CardFieldsApplies constructor.
|
* CardFieldsApplies constructor.
|
||||||
*
|
*
|
||||||
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used.
|
* @param array $allowed_country_matrix The matrix which countries can be used.
|
||||||
* @param string $currency 3-letter currency code of the shop.
|
|
||||||
* @param string $country 2-letter country code of the shop.
|
* @param string $country 2-letter country code of the shop.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
array $allowed_country_currency_matrix,
|
array $allowed_country_matrix,
|
||||||
string $currency,
|
|
||||||
string $country
|
string $country
|
||||||
) {
|
) {
|
||||||
$this->allowed_country_currency_matrix = $allowed_country_currency_matrix;
|
$this->allowed_country_matrix = $allowed_country_matrix;
|
||||||
$this->currency = $currency;
|
$this->country = $country;
|
||||||
$this->country = $country;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether Card Fields can be used in the current country and the current currency.
|
* Returns whether Card Fields can be used in the current country.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function for_country_currency(): bool {
|
public function for_country(): bool {
|
||||||
if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) {
|
return in_array( $this->country, $this->allowed_country_matrix, true );
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
modules/ppcp-card-fields/webpack.config.js
Normal file
38
modules/ppcp-card-fields/webpack.config.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const path = require( 'path' );
|
||||||
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||||
|
mode: isProduction ? 'production' : 'development',
|
||||||
|
target: 'web',
|
||||||
|
entry: {
|
||||||
|
render: path.resolve( './resources/js/Render.js' ),
|
||||||
|
helper: path.resolve( './resources/js/CardFieldsHelper.js' ),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.resolve( __dirname, 'assets/' ),
|
||||||
|
filename: 'js/[name].js',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: 'css/[name].css',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ loader: 'sass-loader' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
2194
modules/ppcp-card-fields/yarn.lock
Normal file
2194
modules/ppcp-card-fields/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,8 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
||||||
);
|
);
|
||||||
const wcShipmentTaxBuyLabelButtonSelector =
|
const wcShipmentTaxBuyLabelButtonSelector =
|
||||||
'.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button';
|
'.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button';
|
||||||
|
const dhlGenerateLabelButton =
|
||||||
|
document.getElementById( 'dhl-label-button' );
|
||||||
|
|
||||||
const toggleLoaderVisibility = function () {
|
const toggleLoaderVisibility = function () {
|
||||||
const loader = document.querySelector( '.ppcp-tracking-loader' );
|
const loader = document.querySelector( '.ppcp-tracking-loader' );
|
||||||
|
@ -44,6 +46,20 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const waitForButtonRemoval = function ( button ) {
|
||||||
|
if ( document.body.contains( button ) ) {
|
||||||
|
setTimeout( () => waitForButtonRemoval( button ), 100 );
|
||||||
|
} else {
|
||||||
|
jQuery( orderTrackingContainerSelector ).load(
|
||||||
|
loadLocation,
|
||||||
|
'',
|
||||||
|
function () {
|
||||||
|
toggleLoaderVisibility();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
gzdSyncEnabled &&
|
gzdSyncEnabled &&
|
||||||
typeof gzdSaveButton !== 'undefined' &&
|
typeof gzdSaveButton !== 'undefined' &&
|
||||||
|
@ -66,10 +82,30 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof dhlGenerateLabelButton !== 'undefined' &&
|
||||||
|
dhlGenerateLabelButton != null
|
||||||
|
) {
|
||||||
|
dhlGenerateLabelButton.addEventListener( 'click', function ( event ) {
|
||||||
|
toggleLoaderVisibility();
|
||||||
|
waitForButtonRemoval( dhlGenerateLabelButton );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery( document ).on(
|
||||||
|
'mouseover mouseout',
|
||||||
|
'#dhl_delete_label',
|
||||||
|
function ( event ) {
|
||||||
|
jQuery( '#ppcp-shipment-status' )
|
||||||
|
.val( 'CANCELLED' )
|
||||||
|
.trigger( 'change' );
|
||||||
|
document.querySelector( '.update_shipment' ).click();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
wcShippingTaxSyncEnabled &&
|
wcShippingTaxSyncEnabled &&
|
||||||
typeof wcShippingTaxSyncEnabled !== 'undefined' &&
|
typeof wcShippingTaxSyncEnabled !== 'undefined'
|
||||||
wcShippingTaxSyncEnabled != null
|
|
||||||
) {
|
) {
|
||||||
document.addEventListener( 'click', function ( event ) {
|
document.addEventListener( 'click', function ( event ) {
|
||||||
const wcShipmentTaxBuyLabelButton = event.target.closest(
|
const wcShipmentTaxBuyLabelButton = event.target.closest(
|
||||||
|
|
|
@ -77,6 +77,9 @@ return array(
|
||||||
'compat.ywot.is_supported_plugin_version_active' => function (): bool {
|
'compat.ywot.is_supported_plugin_version_active' => function (): bool {
|
||||||
return function_exists( 'yith_ywot_init' );
|
return function_exists( 'yith_ywot_init' );
|
||||||
},
|
},
|
||||||
|
'compat.dhl.is_supported_plugin_version_active' => function (): bool {
|
||||||
|
return function_exists( 'PR_DHL' );
|
||||||
|
},
|
||||||
'compat.shipstation.is_supported_plugin_version_active' => function (): bool {
|
'compat.shipstation.is_supported_plugin_version_active' => function (): bool {
|
||||||
return function_exists( 'woocommerce_shipstation_init' );
|
return function_exists( 'woocommerce_shipstation_init' );
|
||||||
},
|
},
|
||||||
|
@ -86,6 +89,9 @@ return array(
|
||||||
'compat.nyp.is_supported_plugin_version_active' => function (): bool {
|
'compat.nyp.is_supported_plugin_version_active' => function (): bool {
|
||||||
return function_exists( 'wc_nyp_init' );
|
return function_exists( 'wc_nyp_init' );
|
||||||
},
|
},
|
||||||
|
'compat.wc_bookings.is_supported_plugin_version_active' => function (): bool {
|
||||||
|
return class_exists( 'WC_Bookings' );
|
||||||
|
},
|
||||||
|
|
||||||
'compat.module.url' => static function ( ContainerInterface $container ): string {
|
'compat.module.url' => static function ( ContainerInterface $container ): string {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,6 +9,11 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\Compat;
|
namespace WooCommerce\PayPalCommerce\Compat;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use WC_Cart;
|
||||||
|
use WC_Order;
|
||||||
|
use WC_Order_Item_Product;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||||
|
@ -61,6 +66,13 @@ class CompatModule implements ModuleInterface {
|
||||||
if ( $is_nyp_active ) {
|
if ( $is_nyp_active ) {
|
||||||
$this->initialize_nyp_compat_layer();
|
$this->initialize_nyp_compat_layer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||||
|
|
||||||
|
$is_wc_bookings_active = $c->get( 'compat.wc_bookings.is_supported_plugin_version_active' );
|
||||||
|
if ( $is_wc_bookings_active ) {
|
||||||
|
$this->initialize_wc_bookings_compat_layer( $logger );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -412,4 +424,62 @@ class CompatModule implements ModuleInterface {
|
||||||
2
|
2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the compatibility layer for WooCommerce Bookings plugin.
|
||||||
|
*
|
||||||
|
* @param LoggerInterface $logger The logger.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function initialize_wc_bookings_compat_layer( LoggerInterface $logger ): void {
|
||||||
|
add_action(
|
||||||
|
'woocommerce_paypal_payments_shipping_callback_woocommerce_order_created',
|
||||||
|
static function ( WC_Order $wc_order, WC_Cart $wc_cart ) use ( $logger ): void {
|
||||||
|
try {
|
||||||
|
$cart_contents = $wc_cart->get_cart();
|
||||||
|
foreach ( $cart_contents as $cart_item ) {
|
||||||
|
if ( empty( $cart_item['booking'] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $wc_order->get_items() as $wc_order_item ) {
|
||||||
|
if ( ! is_a( $wc_order_item, WC_Order_Item_Product::class ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_id = $wc_order_item->get_variation_id() ?: $wc_order_item->get_product_id();
|
||||||
|
$product = wc_get_product( $product_id );
|
||||||
|
|
||||||
|
if ( ! is_wc_booking_product( $product ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$booking_data = array(
|
||||||
|
'cost' => $cart_item['booking']['_cost'] ?? 0,
|
||||||
|
'start_date' => $cart_item['booking']['_start_date'] ?? 0,
|
||||||
|
'end_date' => $cart_item['booking']['_end_date'] ?? 0,
|
||||||
|
'all_day' => $cart_item['booking']['_all_day'] ?? 0,
|
||||||
|
'local_timezone' => $cart_item['booking']['_local_timezone'] ?? 0,
|
||||||
|
'order_item_id' => $wc_order_item->get_id(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( isset( $cart_item['booking']['_resource_id'] ) ) {
|
||||||
|
$booking_data['resource_id'] = $cart_item['booking']['_resource_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $cart_item['booking']['_persons'] ) ) {
|
||||||
|
$booking_data['persons'] = $cart_item['booking']['_persons'];
|
||||||
|
}
|
||||||
|
|
||||||
|
create_wc_booking( $cart_item['product_id'], $booking_data, 'unpaid' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch ( Exception $exception ) {
|
||||||
|
$logger->warning( 'Failed to create booking for WooCommerce Bookings plugin: ' . $exception->getMessage() );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ return array(
|
||||||
'classes' => array( 'ppcp-field-indent' ),
|
'classes' => array( 'ppcp-field-indent' ),
|
||||||
'class' => array(),
|
'class' => array(),
|
||||||
'input_class' => array( 'wc-enhanced-select' ),
|
'input_class' => array( 'wc-enhanced-select' ),
|
||||||
'default' => 'pay',
|
'default' => 'plain',
|
||||||
'options' => PropertiesDictionary::button_types(),
|
'options' => PropertiesDictionary::button_types(),
|
||||||
'screens' => array( State::STATE_ONBOARDED ),
|
'screens' => array( State::STATE_ONBOARDED ),
|
||||||
'gateway' => 'dcc',
|
'gateway' => 'dcc',
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
/* Front end display */
|
||||||
|
.ppcp-button-apm .gpay-card-info-container-fill .gpay-card-info-container {
|
||||||
|
outline-offset: -1px;
|
||||||
|
border-radius: var(--apm-button-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin preview */
|
||||||
.ppcp-button-googlepay {
|
.ppcp-button-googlepay {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,120 @@
|
||||||
import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
|
import {
|
||||||
import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
|
combineStyles,
|
||||||
|
combineWrapperIds,
|
||||||
|
} from '../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers';
|
||||||
|
import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton';
|
||||||
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
|
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
|
||||||
import UpdatePaymentData from './Helper/UpdatePaymentData';
|
import UpdatePaymentData from './Helper/UpdatePaymentData';
|
||||||
import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons';
|
|
||||||
import TransactionInfo from './Helper/TransactionInfo';
|
import TransactionInfo from './Helper/TransactionInfo';
|
||||||
|
import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
|
||||||
|
|
||||||
class GooglepayButton {
|
/**
|
||||||
|
* Plugin-specific styling.
|
||||||
|
*
|
||||||
|
* Note that most properties of this object do not apply to the Google Pay button.
|
||||||
|
*
|
||||||
|
* @typedef {Object} PPCPStyle
|
||||||
|
* @property {string} shape - Outline shape.
|
||||||
|
* @property {?number} height - Button height in pixel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Style options that are defined by the Google Pay SDK and are required to render the button.
|
||||||
|
*
|
||||||
|
* @typedef {Object} GooglePayStyle
|
||||||
|
* @property {string} type - Defines the button label.
|
||||||
|
* @property {string} color - Button color
|
||||||
|
* @property {string} language - The locale; an empty string will apply the user-agent's language.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Google Pay JS SDK
|
||||||
|
*
|
||||||
|
* @see https://developers.google.com/pay/api/web/reference/request-objects
|
||||||
|
* @typedef {Object} GooglePaySDK
|
||||||
|
* @property {typeof PaymentsClient} PaymentsClient - Main API client for payment actions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Payments Client class, generated by the Google Pay SDK.
|
||||||
|
*
|
||||||
|
* @see https://developers.google.com/pay/api/web/reference/client
|
||||||
|
* @typedef {Object} PaymentsClient
|
||||||
|
* @property {Function} createButton - The convenience method is used to generate a Google Pay payment button styled with the latest Google Pay branding for insertion into a webpage.
|
||||||
|
* @property {Function} isReadyToPay - Use the isReadyToPay(isReadyToPayRequest) method to determine a user's ability to return a form of payment from the Google Pay API.
|
||||||
|
* @property {Function} loadPaymentData - This method presents a Google Pay payment sheet that allows selection of a payment method and optionally configured parameters
|
||||||
|
* @property {Function} onPaymentAuthorized - This method is called when a payment is authorized in the payment sheet.
|
||||||
|
* @property {Function} onPaymentDataChanged - This method handles payment data changes in the payment sheet such as shipping address and shipping options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This object describes the transaction details.
|
||||||
|
*
|
||||||
|
* @see https://developers.google.com/pay/api/web/reference/request-objects#TransactionInfo
|
||||||
|
* @typedef {Object} TransactionInfo
|
||||||
|
* @property {string} currencyCode - Required. The ISO 4217 alphabetic currency code.
|
||||||
|
* @property {string} countryCode - Optional. required for EEA countries,
|
||||||
|
* @property {string} transactionId - Optional. A unique ID that identifies a facilitation attempt. Highly encouraged for troubleshooting.
|
||||||
|
* @property {string} totalPriceStatus - Required. [ESTIMATED|FINAL] The status of the total price used:
|
||||||
|
* @property {string} totalPrice - Required. Total monetary value of the transaction with an optional decimal precision of two decimal places.
|
||||||
|
* @property {Array} displayItems - Optional. A list of cart items shown in the payment sheet (e.g. subtotals, sales taxes, shipping charges, discounts etc.).
|
||||||
|
* @property {string} totalPriceLabel - Optional. Custom label for the total price within the display items.
|
||||||
|
* @property {string} checkoutOption - Optional. Affects the submit button text displayed in the Google Pay payment sheet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class GooglepayButton extends PaymentButton {
|
||||||
/**
|
/**
|
||||||
* @type {TransactionInfo}
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
transactionInfo;
|
static methodId = PaymentMethods.GOOGLEPAY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
static cssClass = 'google-pay';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client reference, provided by the Google Pay JS SDK.
|
||||||
|
*/
|
||||||
|
#paymentsClient = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details about the processed transaction.
|
||||||
|
*
|
||||||
|
* @type {?TransactionInfo}
|
||||||
|
*/
|
||||||
|
#transactionInfo = null;
|
||||||
|
|
||||||
|
googlePayConfig = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
static getWrappers( buttonConfig, ppcpConfig ) {
|
||||||
|
return combineWrapperIds(
|
||||||
|
buttonConfig?.button?.wrapper || '',
|
||||||
|
buttonConfig?.button?.mini_cart_wrapper || '',
|
||||||
|
ppcpConfig?.button?.wrapper || '',
|
||||||
|
'ppc-button-googlepay-container',
|
||||||
|
'ppc-button-ppcp-googlepay'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
static getStyles( buttonConfig, ppcpConfig ) {
|
||||||
|
const styles = combineStyles(
|
||||||
|
ppcpConfig?.button || {},
|
||||||
|
buttonConfig?.button || {}
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( 'buy' === styles.MiniCart.type ) {
|
||||||
|
styles.MiniCart.type = 'pay';
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
context,
|
context,
|
||||||
|
@ -18,274 +123,257 @@ class GooglepayButton {
|
||||||
ppcpConfig,
|
ppcpConfig,
|
||||||
contextHandler
|
contextHandler
|
||||||
) {
|
) {
|
||||||
apmButtonsInit( ppcpConfig );
|
// Disable debug output in the browser console:
|
||||||
|
// buttonConfig.is_debug = false;
|
||||||
|
|
||||||
this.isInitialized = false;
|
super(
|
||||||
|
context,
|
||||||
|
externalHandler,
|
||||||
|
buttonConfig,
|
||||||
|
ppcpConfig,
|
||||||
|
contextHandler
|
||||||
|
);
|
||||||
|
|
||||||
this.context = context;
|
this.init = this.init.bind( this );
|
||||||
this.externalHandler = externalHandler;
|
this.onPaymentAuthorized = this.onPaymentAuthorized.bind( this );
|
||||||
this.buttonConfig = buttonConfig;
|
this.onPaymentDataChanged = this.onPaymentDataChanged.bind( this );
|
||||||
this.ppcpConfig = ppcpConfig;
|
this.onButtonClick = this.onButtonClick.bind( this );
|
||||||
this.contextHandler = contextHandler;
|
|
||||||
|
|
||||||
this.paymentsClient = null;
|
this.log( 'Create instance' );
|
||||||
|
}
|
||||||
|
|
||||||
this.log = function () {
|
/**
|
||||||
if ( this.buttonConfig.is_debug ) {
|
* @inheritDoc
|
||||||
//console.log('[GooglePayButton]', ...arguments);
|
*/
|
||||||
|
get requiresShipping() {
|
||||||
|
return super.requiresShipping && this.buttonConfig.shipping?.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Google Pay API.
|
||||||
|
*
|
||||||
|
* @return {?GooglePaySDK} API for the Google Pay JS SDK, or null when SDK is not ready yet.
|
||||||
|
*/
|
||||||
|
get googlePayApi() {
|
||||||
|
return window.google?.payments?.api;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Google Pay PaymentsClient instance created by this button.
|
||||||
|
* @see https://developers.google.com/pay/api/web/reference/client
|
||||||
|
*
|
||||||
|
* @return {?PaymentsClient} The SDK object, or null when SDK is not ready yet.
|
||||||
|
*/
|
||||||
|
get paymentsClient() {
|
||||||
|
return this.#paymentsClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details about the processed transaction.
|
||||||
|
*
|
||||||
|
* This object defines the price that is charged, and text that is displayed inside the
|
||||||
|
* payment sheet.
|
||||||
|
*
|
||||||
|
* @return {?TransactionInfo} The TransactionInfo object.
|
||||||
|
*/
|
||||||
|
get transactionInfo() {
|
||||||
|
return this.#transactionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign the new transaction details to the payment button.
|
||||||
|
*
|
||||||
|
* @param {TransactionInfo} newTransactionInfo - Transaction details.
|
||||||
|
*/
|
||||||
|
set transactionInfo( newTransactionInfo ) {
|
||||||
|
this.#transactionInfo = newTransactionInfo;
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
validateConfiguration( silent = false ) {
|
||||||
|
const validEnvs = [ 'PRODUCTION', 'TEST' ];
|
||||||
|
|
||||||
|
const isInvalid = ( ...args ) => {
|
||||||
|
if ( ! silent ) {
|
||||||
|
this.error( ...args );
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
init( config, transactionInfo ) {
|
if ( ! validEnvs.includes( this.buttonConfig.environment ) ) {
|
||||||
if ( this.isInitialized ) {
|
return isInvalid(
|
||||||
return;
|
'Invalid environment:',
|
||||||
}
|
this.buttonConfig.environment
|
||||||
this.isInitialized = true;
|
);
|
||||||
|
|
||||||
if ( ! this.validateConfig() ) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! this.contextHandler.validateContext() ) {
|
// Preview buttons only need a valid environment.
|
||||||
return;
|
if ( this.isPreview ) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.googlePayConfig = config;
|
|
||||||
this.transactionInfo = transactionInfo;
|
|
||||||
this.allowedPaymentMethods = config.allowedPaymentMethods;
|
|
||||||
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
|
|
||||||
|
|
||||||
this.initClient();
|
|
||||||
this.initEventHandlers();
|
|
||||||
|
|
||||||
this.paymentsClient
|
|
||||||
.isReadyToPay(
|
|
||||||
this.buildReadyToPayRequest(
|
|
||||||
this.allowedPaymentMethods,
|
|
||||||
config
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then( ( response ) => {
|
|
||||||
if ( response.result ) {
|
|
||||||
if (
|
|
||||||
( this.context === 'checkout' ||
|
|
||||||
this.context === 'pay-now' ) &&
|
|
||||||
this.buttonConfig.is_wc_gateway_enabled === '1'
|
|
||||||
) {
|
|
||||||
const wrapper = document.getElementById(
|
|
||||||
'ppc-button-ppcp-googlepay'
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( wrapper ) {
|
|
||||||
const { ppcpStyle, buttonStyle } =
|
|
||||||
this.contextConfig();
|
|
||||||
|
|
||||||
wrapper.classList.add(
|
|
||||||
`ppcp-button-${ ppcpStyle.shape }`,
|
|
||||||
'ppcp-button-apm',
|
|
||||||
'ppcp-button-googlepay'
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( ppcpStyle.height ) {
|
|
||||||
wrapper.style.height = `${ ppcpStyle.height }px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addButtonCheckout(
|
|
||||||
this.baseCardPaymentMethod,
|
|
||||||
wrapper,
|
|
||||||
buttonStyle
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addButton( this.baseCardPaymentMethod );
|
|
||||||
}
|
|
||||||
} )
|
|
||||||
.catch( function ( err ) {
|
|
||||||
console.error( err );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
reinit() {
|
|
||||||
if ( ! this.googlePayConfig ) {
|
if ( ! this.googlePayConfig ) {
|
||||||
return;
|
return isInvalid(
|
||||||
|
'No API configuration - missing configure() call?'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isInitialized = false;
|
if ( ! this.transactionInfo ) {
|
||||||
this.init( this.googlePayConfig, this.transactionInfo );
|
return isInvalid(
|
||||||
}
|
'No transactionInfo - missing configure() call?'
|
||||||
|
|
||||||
validateConfig() {
|
|
||||||
if (
|
|
||||||
[ 'PRODUCTION', 'TEST' ].indexOf(
|
|
||||||
this.buttonConfig.environment
|
|
||||||
) === -1
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
'[GooglePayButton] Invalid environment.',
|
|
||||||
this.buttonConfig.environment
|
|
||||||
);
|
);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! this.contextHandler ) {
|
if ( ! typeof this.contextHandler?.validateContext() ) {
|
||||||
console.error(
|
return isInvalid( 'Invalid context handler.', this.contextHandler );
|
||||||
'[GooglePayButton] Invalid context handler.',
|
|
||||||
this.contextHandler
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns configurations relative to this button context.
|
* Configures the button instance. Must be called before the initial `init()`.
|
||||||
|
*
|
||||||
|
* @param {Object} apiConfig - API configuration.
|
||||||
|
* @param {Object} transactionInfo - Transaction details; required before "init" call.
|
||||||
*/
|
*/
|
||||||
contextConfig() {
|
configure( apiConfig, transactionInfo ) {
|
||||||
const config = {
|
this.googlePayConfig = apiConfig;
|
||||||
wrapper: this.buttonConfig.button.wrapper,
|
this.#transactionInfo = transactionInfo;
|
||||||
ppcpStyle: this.ppcpConfig.button.style,
|
|
||||||
buttonStyle: this.buttonConfig.button.style,
|
|
||||||
ppcpButtonWrapper: this.ppcpConfig.button.wrapper,
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( this.context === 'mini-cart' ) {
|
this.allowedPaymentMethods = this.googlePayConfig.allowedPaymentMethods;
|
||||||
config.wrapper = this.buttonConfig.button.mini_cart_wrapper;
|
this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ];
|
||||||
config.ppcpStyle = this.ppcpConfig.button.mini_cart_style;
|
|
||||||
config.buttonStyle = this.buttonConfig.button.mini_cart_style;
|
|
||||||
config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper;
|
|
||||||
|
|
||||||
// Handle incompatible types.
|
|
||||||
if ( config.buttonStyle.type === 'buy' ) {
|
|
||||||
config.buttonStyle.type = 'pay';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
[ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1
|
|
||||||
) {
|
|
||||||
config.ppcpButtonWrapper =
|
|
||||||
'#express-payment-method-ppcp-gateway-paypal';
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initClient() {
|
init() {
|
||||||
const callbacks = {
|
// Use `reinit()` to force a full refresh of an initialized button.
|
||||||
onPaymentAuthorized: this.onPaymentAuthorized.bind( this ),
|
if ( this.isInitialized ) {
|
||||||
};
|
return;
|
||||||
|
|
||||||
if (
|
|
||||||
this.buttonConfig.shipping.enabled &&
|
|
||||||
this.contextHandler.shippingAllowed()
|
|
||||||
) {
|
|
||||||
callbacks.onPaymentDataChanged =
|
|
||||||
this.onPaymentDataChanged.bind( this );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.paymentsClient = new google.payments.api.PaymentsClient( {
|
// Stop, if configuration is invalid.
|
||||||
|
if ( ! this.validateConfiguration() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.init();
|
||||||
|
this.#paymentsClient = this.createPaymentsClient();
|
||||||
|
|
||||||
|
if ( ! this.isPresent ) {
|
||||||
|
this.log( 'Payment wrapper not found', this.wrapperId );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! this.paymentsClient ) {
|
||||||
|
this.log( 'Could not initialize the payments client' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.paymentsClient
|
||||||
|
.isReadyToPay(
|
||||||
|
this.buildReadyToPayRequest(
|
||||||
|
this.allowedPaymentMethods,
|
||||||
|
this.googlePayConfig
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then( ( response ) => {
|
||||||
|
this.log( 'PaymentsClient.isReadyToPay response:', response );
|
||||||
|
this.isEligible = !! response.result;
|
||||||
|
} )
|
||||||
|
.catch( ( err ) => {
|
||||||
|
this.error( err );
|
||||||
|
this.isEligible = false;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
reinit() {
|
||||||
|
// Missing (invalid) configuration indicates, that the first `init()` call did not happen yet.
|
||||||
|
if ( ! this.validateConfiguration( true ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.reinit();
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an object with relevant paymentDataCallbacks for the current button instance.
|
||||||
|
*
|
||||||
|
* @return {Object} An object containing callbacks for the current scope & configuration.
|
||||||
|
*/
|
||||||
|
preparePaymentDataCallbacks() {
|
||||||
|
const callbacks = {};
|
||||||
|
|
||||||
|
// We do not attach any callbacks to preview buttons.
|
||||||
|
if ( this.isPreview ) {
|
||||||
|
return callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
callbacks.onPaymentAuthorized = this.onPaymentAuthorized;
|
||||||
|
|
||||||
|
if ( this.requiresShipping ) {
|
||||||
|
callbacks.onPaymentDataChanged = this.onPaymentDataChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
return callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
createPaymentsClient() {
|
||||||
|
if ( ! this.googlePayApi ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callbacks = this.preparePaymentDataCallbacks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consider providing merchant info here:
|
||||||
|
*
|
||||||
|
* @see https://developers.google.com/pay/api/web/reference/request-objects#PaymentOptions
|
||||||
|
*/
|
||||||
|
return new this.googlePayApi.PaymentsClient( {
|
||||||
environment: this.buttonConfig.environment,
|
environment: this.buttonConfig.environment,
|
||||||
// add merchant info maybe
|
|
||||||
paymentDataCallbacks: callbacks,
|
paymentDataCallbacks: callbacks,
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
initEventHandlers() {
|
|
||||||
const { wrapper, ppcpButtonWrapper } = this.contextConfig();
|
|
||||||
|
|
||||||
if ( wrapper === ppcpButtonWrapper ) {
|
|
||||||
throw new Error(
|
|
||||||
`[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncButtonVisibility = () => {
|
|
||||||
const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper );
|
|
||||||
setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) );
|
|
||||||
setEnabled(
|
|
||||||
wrapper,
|
|
||||||
! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' )
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery( document ).on(
|
|
||||||
'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
|
|
||||||
( ev, data ) => {
|
|
||||||
if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) {
|
|
||||||
syncButtonVisibility();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
syncButtonVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) {
|
buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) {
|
||||||
|
this.log( 'Ready To Pay request', baseRequest, allowedPaymentMethods );
|
||||||
|
|
||||||
return Object.assign( {}, baseRequest, {
|
return Object.assign( {}, baseRequest, {
|
||||||
allowedPaymentMethods,
|
allowedPaymentMethods,
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a Google Pay purchase button
|
* Creates the payment button and calls `this.insertButton()` to make the button visible in the
|
||||||
* @param baseCardPaymentMethod
|
* correct wrapper.
|
||||||
*/
|
*/
|
||||||
addButton( baseCardPaymentMethod ) {
|
addButton() {
|
||||||
this.log( 'addButton', this.context );
|
if ( ! this.paymentsClient ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig();
|
const baseCardPaymentMethod = this.baseCardPaymentMethod;
|
||||||
|
const { color, type, language } = this.style;
|
||||||
|
|
||||||
this.waitForWrapper( wrapper, () => {
|
/**
|
||||||
jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape );
|
* @see https://developers.google.com/pay/api/web/reference/client#createButton
|
||||||
|
*/
|
||||||
if ( ppcpStyle.height ) {
|
|
||||||
jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` );
|
|
||||||
}
|
|
||||||
|
|
||||||
const button = this.paymentsClient.createButton( {
|
|
||||||
onClick: this.onButtonClick.bind( this ),
|
|
||||||
allowedPaymentMethods: [ baseCardPaymentMethod ],
|
|
||||||
buttonColor: buttonStyle.color || 'black',
|
|
||||||
buttonType: buttonStyle.type || 'pay',
|
|
||||||
buttonLocale: buttonStyle.language || 'en',
|
|
||||||
buttonSizeMode: 'fill',
|
|
||||||
} );
|
|
||||||
|
|
||||||
jQuery( wrapper ).append( button );
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) {
|
|
||||||
const button = this.paymentsClient.createButton( {
|
const button = this.paymentsClient.createButton( {
|
||||||
onClick: this.onButtonClick.bind( this ),
|
onClick: this.onButtonClick,
|
||||||
allowedPaymentMethods: [ baseCardPaymentMethod ],
|
allowedPaymentMethods: [ baseCardPaymentMethod ],
|
||||||
buttonColor: buttonStyle.color || 'black',
|
buttonColor: color || 'black',
|
||||||
buttonType: buttonStyle.type || 'pay',
|
buttonType: type || 'pay',
|
||||||
buttonLocale: buttonStyle.language || 'en',
|
buttonLocale: language || 'en',
|
||||||
buttonSizeMode: 'fill',
|
buttonSizeMode: 'fill',
|
||||||
} );
|
} );
|
||||||
|
|
||||||
wrapper.appendChild( button );
|
this.insertButton( button );
|
||||||
}
|
|
||||||
|
|
||||||
waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) {
|
|
||||||
const startTime = Date.now();
|
|
||||||
const interval = setInterval( () => {
|
|
||||||
const el = document.querySelector( selector );
|
|
||||||
const timeElapsed = Date.now() - startTime;
|
|
||||||
|
|
||||||
if ( el ) {
|
|
||||||
clearInterval( interval );
|
|
||||||
callback( el );
|
|
||||||
} else if ( timeElapsed > timeout ) {
|
|
||||||
clearInterval( interval );
|
|
||||||
}
|
|
||||||
}, delay );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------
|
//------------------------
|
||||||
|
@ -296,35 +384,49 @@ class GooglepayButton {
|
||||||
* Show Google Pay payment sheet when Google Pay payment button is clicked
|
* Show Google Pay payment sheet when Google Pay payment button is clicked
|
||||||
*/
|
*/
|
||||||
onButtonClick() {
|
onButtonClick() {
|
||||||
this.log( 'onButtonClick', this.context );
|
this.log( 'onButtonClick' );
|
||||||
|
|
||||||
const initiatePaymentRequest = () => {
|
const initiatePaymentRequest = () => {
|
||||||
window.ppcpFundingSource = 'googlepay';
|
window.ppcpFundingSource = 'googlepay';
|
||||||
|
|
||||||
const paymentDataRequest = this.paymentDataRequest();
|
const paymentDataRequest = this.paymentDataRequest();
|
||||||
|
|
||||||
this.log(
|
this.log(
|
||||||
'onButtonClick: paymentDataRequest',
|
'onButtonClick: paymentDataRequest',
|
||||||
paymentDataRequest,
|
paymentDataRequest,
|
||||||
this.context
|
this.context
|
||||||
);
|
);
|
||||||
|
|
||||||
this.paymentsClient.loadPaymentData( paymentDataRequest );
|
this.paymentsClient.loadPaymentData( paymentDataRequest );
|
||||||
};
|
};
|
||||||
|
|
||||||
if ( 'function' === typeof this.contextHandler.validateForm ) {
|
const validateForm = () => {
|
||||||
// During regular checkout, validate the checkout form before initiating the payment.
|
if ( 'function' !== typeof this.contextHandler.validateForm ) {
|
||||||
this.contextHandler
|
return Promise.resolve();
|
||||||
.validateForm()
|
}
|
||||||
.then( initiatePaymentRequest, () => {
|
|
||||||
console.error(
|
return this.contextHandler.validateForm().catch( ( error ) => {
|
||||||
'[GooglePayButton] Form validation failed.'
|
this.error( 'Form validation failed:', error );
|
||||||
);
|
throw error;
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTransactionInfo = () => {
|
||||||
|
if ( 'function' !== typeof this.contextHandler.transactionInfo ) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.contextHandler
|
||||||
|
.transactionInfo()
|
||||||
|
.then( ( transactionInfo ) => {
|
||||||
|
this.transactionInfo = transactionInfo;
|
||||||
|
} )
|
||||||
|
.catch( ( error ) => {
|
||||||
|
this.error( 'Failed to get transaction info:', error );
|
||||||
|
throw error;
|
||||||
} );
|
} );
|
||||||
} else {
|
};
|
||||||
// This is the flow on product page, cart, and other non-checkout pages.
|
|
||||||
initiatePaymentRequest();
|
validateForm()
|
||||||
}
|
.then( getTransactionInfo )
|
||||||
|
.then( initiatePaymentRequest );
|
||||||
}
|
}
|
||||||
|
|
||||||
paymentDataRequest() {
|
paymentDataRequest() {
|
||||||
|
@ -340,10 +442,7 @@ class GooglepayButton {
|
||||||
paymentDataRequest.transactionInfo = this.transactionInfo.finalObject;
|
paymentDataRequest.transactionInfo = this.transactionInfo.finalObject;
|
||||||
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
|
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
|
||||||
|
|
||||||
if (
|
if ( this.requiresShipping ) {
|
||||||
this.buttonConfig.shipping.enabled &&
|
|
||||||
this.contextHandler.shippingAllowed()
|
|
||||||
) {
|
|
||||||
paymentDataRequest.callbackIntents = [
|
paymentDataRequest.callbackIntents = [
|
||||||
'SHIPPING_ADDRESS',
|
'SHIPPING_ADDRESS',
|
||||||
'SHIPPING_OPTION',
|
'SHIPPING_OPTION',
|
||||||
|
@ -372,8 +471,7 @@ class GooglepayButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPaymentDataChanged( paymentData ) {
|
onPaymentDataChanged( paymentData ) {
|
||||||
this.log( 'onPaymentDataChanged', this.context );
|
this.log( 'onPaymentDataChanged', paymentData );
|
||||||
this.log( 'paymentData', paymentData );
|
|
||||||
|
|
||||||
return new Promise( async ( resolve, reject ) => {
|
return new Promise( async ( resolve, reject ) => {
|
||||||
try {
|
try {
|
||||||
|
@ -437,7 +535,7 @@ class GooglepayButton {
|
||||||
|
|
||||||
resolve( paymentDataRequestUpdate );
|
resolve( paymentDataRequestUpdate );
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
console.error( 'Error during onPaymentDataChanged:', error );
|
this.error( 'Error during onPaymentDataChanged:', error );
|
||||||
reject( error );
|
reject( error );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
@ -508,18 +606,18 @@ class GooglepayButton {
|
||||||
//------------------------
|
//------------------------
|
||||||
|
|
||||||
onPaymentAuthorized( paymentData ) {
|
onPaymentAuthorized( paymentData ) {
|
||||||
this.log( 'onPaymentAuthorized', this.context );
|
this.log( 'onPaymentAuthorized' );
|
||||||
return this.processPayment( paymentData );
|
return this.processPayment( paymentData );
|
||||||
}
|
}
|
||||||
|
|
||||||
async processPayment( paymentData ) {
|
async processPayment( paymentData ) {
|
||||||
this.log( 'processPayment', this.context );
|
this.log( 'processPayment' );
|
||||||
|
|
||||||
return new Promise( async ( resolve, reject ) => {
|
return new Promise( async ( resolve, reject ) => {
|
||||||
try {
|
try {
|
||||||
const id = await this.contextHandler.createOrder();
|
const id = await this.contextHandler.createOrder();
|
||||||
|
|
||||||
this.log( 'processPayment: createOrder', id, this.context );
|
this.log( 'processPayment: createOrder', id );
|
||||||
|
|
||||||
const confirmOrderResponse = await widgetBuilder.paypal
|
const confirmOrderResponse = await widgetBuilder.paypal
|
||||||
.Googlepay()
|
.Googlepay()
|
||||||
|
@ -530,8 +628,7 @@ class GooglepayButton {
|
||||||
|
|
||||||
this.log(
|
this.log(
|
||||||
'processPayment: confirmOrder',
|
'processPayment: confirmOrder',
|
||||||
confirmOrderResponse,
|
confirmOrderResponse
|
||||||
this.context
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Capture the Order on the Server */
|
/** Capture the Order on the Server */
|
||||||
|
@ -601,7 +698,7 @@ class GooglepayButton {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log( 'processPaymentResponse', response, this.context );
|
this.log( 'processPaymentResponse', response );
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ class GooglepayManager {
|
||||||
bootstrap.handler
|
bootstrap.handler
|
||||||
);
|
);
|
||||||
|
|
||||||
const button = new GooglepayButton(
|
const button = GooglepayButton.createButton(
|
||||||
bootstrap.context,
|
bootstrap.context,
|
||||||
bootstrap.handler,
|
bootstrap.handler,
|
||||||
buttonConfig,
|
buttonConfig,
|
||||||
|
@ -30,13 +30,19 @@ class GooglepayManager {
|
||||||
|
|
||||||
this.buttons.push( button );
|
this.buttons.push( button );
|
||||||
|
|
||||||
|
const initButton = () => {
|
||||||
|
button.configure( this.googlePayConfig, this.transactionInfo );
|
||||||
|
button.init();
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize button only if googlePayConfig and transactionInfo are already fetched.
|
// Initialize button only if googlePayConfig and transactionInfo are already fetched.
|
||||||
if ( this.googlePayConfig && this.transactionInfo ) {
|
if ( this.googlePayConfig && this.transactionInfo ) {
|
||||||
button.init( this.googlePayConfig, this.transactionInfo );
|
initButton();
|
||||||
} else {
|
} else {
|
||||||
await this.init();
|
await this.init();
|
||||||
|
|
||||||
if ( this.googlePayConfig && this.transactionInfo ) {
|
if ( this.googlePayConfig && this.transactionInfo ) {
|
||||||
button.init( this.googlePayConfig, this.transactionInfo );
|
initButton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
@ -53,8 +59,18 @@ class GooglepayManager {
|
||||||
this.transactionInfo = await this.fetchTransactionInfo();
|
this.transactionInfo = await this.fetchTransactionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( const button of this.buttons ) {
|
if ( ! this.googlePayConfig ) {
|
||||||
button.init( this.googlePayConfig, this.transactionInfo );
|
console.error( 'No GooglePayConfig received during init' );
|
||||||
|
} else if ( ! this.transactionInfo ) {
|
||||||
|
console.error( 'No transactionInfo found during init' );
|
||||||
|
} else {
|
||||||
|
for ( const button of this.buttons ) {
|
||||||
|
button.configure(
|
||||||
|
this.googlePayConfig,
|
||||||
|
this.transactionInfo
|
||||||
|
);
|
||||||
|
button.init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch ( error ) {
|
} catch ( error ) {
|
||||||
console.error( 'Error during initialization:', error );
|
console.error( 'Error during initialization:', error );
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton';
|
||||||
|
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
||||||
|
import GooglepayButton from './GooglepayButton';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single GooglePay preview button instance.
|
||||||
|
*/
|
||||||
|
export default class GooglePayPreviewButton extends PreviewButton {
|
||||||
|
/**
|
||||||
|
* Instance of the preview button.
|
||||||
|
*
|
||||||
|
* @type {?PaymentButton}
|
||||||
|
*/
|
||||||
|
#button = null;
|
||||||
|
|
||||||
|
constructor( args ) {
|
||||||
|
super( args );
|
||||||
|
|
||||||
|
this.selector = `${ args.selector }GooglePay`;
|
||||||
|
this.defaultAttributes = {
|
||||||
|
button: {
|
||||||
|
style: {
|
||||||
|
type: 'pay',
|
||||||
|
color: 'black',
|
||||||
|
language: 'en',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewWrapper() {
|
||||||
|
const element = super.createNewWrapper();
|
||||||
|
element.addClass( 'ppcp-button-googlepay' );
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
createButton( buttonConfig ) {
|
||||||
|
const contextHandler = ContextHandlerFactory.create(
|
||||||
|
'preview',
|
||||||
|
buttonConfig,
|
||||||
|
this.ppcpConfig,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( ! this.#button ) {
|
||||||
|
/* Intentionally using `new` keyword, instead of the `.createButton()` factory,
|
||||||
|
* as the factory is designed to only create a single button per context, while a single
|
||||||
|
* page can contain multiple instances of a preview button.
|
||||||
|
*/
|
||||||
|
this.#button = new GooglepayButton(
|
||||||
|
'preview',
|
||||||
|
null,
|
||||||
|
buttonConfig,
|
||||||
|
this.ppcpConfig,
|
||||||
|
contextHandler
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#button.configure( this.apiConfig, null );
|
||||||
|
this.#button.applyButtonStyles( buttonConfig, this.ppcpConfig );
|
||||||
|
this.#button.reinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge form details into the config object for preview.
|
||||||
|
* Mutates the previewConfig object; no return value.
|
||||||
|
*
|
||||||
|
* @param {Object} buttonConfig
|
||||||
|
* @param {Object} ppcpConfig
|
||||||
|
*/
|
||||||
|
dynamicPreviewConfig( buttonConfig, ppcpConfig ) {
|
||||||
|
// Merge the current form-values into the preview-button configuration.
|
||||||
|
if ( ppcpConfig.button && buttonConfig.button ) {
|
||||||
|
Object.assign( buttonConfig.button.style, ppcpConfig.button.style );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
import GooglepayButton from './GooglepayButton';
|
|
||||||
import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton';
|
|
||||||
import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager';
|
import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager';
|
||||||
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
import GooglePayPreviewButton from './GooglepayPreviewButton';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessor that creates and returns a single PreviewButtonManager instance.
|
* Accessor that creates and returns a single PreviewButtonManager instance.
|
||||||
|
@ -33,7 +31,7 @@ class GooglePayPreviewButtonManager extends PreviewButtonManager {
|
||||||
* method.
|
* method.
|
||||||
*
|
*
|
||||||
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
||||||
* @return {Promise<{}>}
|
* @return {Promise<{}>} Promise that resolves when API configuration is available.
|
||||||
*/
|
*/
|
||||||
async fetchConfig( payPal ) {
|
async fetchConfig( payPal ) {
|
||||||
const apiMethod = payPal?.Googlepay()?.config;
|
const apiMethod = payPal?.Googlepay()?.config;
|
||||||
|
@ -59,7 +57,7 @@ class GooglePayPreviewButtonManager extends PreviewButtonManager {
|
||||||
* This method is responsible for creating a new PreviewButton instance and returning it.
|
* This method is responsible for creating a new PreviewButton instance and returning it.
|
||||||
*
|
*
|
||||||
* @param {string} wrapperId - CSS ID of the wrapper element.
|
* @param {string} wrapperId - CSS ID of the wrapper element.
|
||||||
* @return {GooglePayPreviewButton}
|
* @return {GooglePayPreviewButton} The new preview button instance.
|
||||||
*/
|
*/
|
||||||
createButtonInstance( wrapperId ) {
|
createButtonInstance( wrapperId ) {
|
||||||
return new GooglePayPreviewButton( {
|
return new GooglePayPreviewButton( {
|
||||||
|
@ -69,64 +67,5 @@ class GooglePayPreviewButtonManager extends PreviewButtonManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A single GooglePay preview button instance.
|
|
||||||
*/
|
|
||||||
class GooglePayPreviewButton extends PreviewButton {
|
|
||||||
constructor( args ) {
|
|
||||||
super( args );
|
|
||||||
|
|
||||||
this.selector = `${ args.selector }GooglePay`;
|
|
||||||
this.defaultAttributes = {
|
|
||||||
button: {
|
|
||||||
style: {
|
|
||||||
type: 'pay',
|
|
||||||
color: 'black',
|
|
||||||
language: 'en',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
createNewWrapper() {
|
|
||||||
const element = super.createNewWrapper();
|
|
||||||
element.addClass( 'ppcp-button-googlepay' );
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
createButton( buttonConfig ) {
|
|
||||||
const contextHandler = ContextHandlerFactory.create(
|
|
||||||
'preview',
|
|
||||||
buttonConfig,
|
|
||||||
this.ppcpConfig,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
const button = new GooglepayButton(
|
|
||||||
'preview',
|
|
||||||
null,
|
|
||||||
buttonConfig,
|
|
||||||
this.ppcpConfig,
|
|
||||||
contextHandler
|
|
||||||
);
|
|
||||||
|
|
||||||
button.init( this.apiConfig, null );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge form details into the config object for preview.
|
|
||||||
* Mutates the previewConfig object; no return value.
|
|
||||||
* @param buttonConfig
|
|
||||||
* @param ppcpConfig
|
|
||||||
*/
|
|
||||||
dynamicPreviewConfig( buttonConfig, ppcpConfig ) {
|
|
||||||
// Merge the current form-values into the preview-button configuration.
|
|
||||||
if ( ppcpConfig.button && buttonConfig.button ) {
|
|
||||||
Object.assign( buttonConfig.button.style, ppcpConfig.button.style );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the preview button manager.
|
// Initialize the preview button manager.
|
||||||
buttonManager();
|
buttonManager();
|
||||||
|
|
|
@ -290,6 +290,7 @@ class Button implements ButtonInterface {
|
||||||
$render_placeholder,
|
$render_placeholder,
|
||||||
function () {
|
function () {
|
||||||
$this->googlepay_button();
|
$this->googlepay_button();
|
||||||
|
$this->hide_gateway_until_eligible();
|
||||||
},
|
},
|
||||||
21
|
21
|
||||||
);
|
);
|
||||||
|
@ -303,6 +304,7 @@ class Button implements ButtonInterface {
|
||||||
$render_placeholder,
|
$render_placeholder,
|
||||||
function () {
|
function () {
|
||||||
$this->googlepay_button();
|
$this->googlepay_button();
|
||||||
|
$this->hide_gateway_until_eligible();
|
||||||
},
|
},
|
||||||
21
|
21
|
||||||
);
|
);
|
||||||
|
@ -335,6 +337,23 @@ class Button implements ButtonInterface {
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outputs an inline CSS style that hides the Google Pay gateway (on Classic Checkout).
|
||||||
|
* The style is removed by `PaymentButton.js` once the eligibility of the payment method
|
||||||
|
* is confirmed.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function hide_gateway_until_eligible() : void {
|
||||||
|
?>
|
||||||
|
<style data-hide-gateway='<?php echo esc_attr( GooglePayGateway::ID ); ?>'>
|
||||||
|
.wc_payment_method.payment_method_ppcp-googlepay {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enqueues scripts/styles.
|
* Enqueues scripts/styles.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace WooCommerce\PayPalCommerce\OrderTracking;
|
namespace WooCommerce\PayPalCommerce\OrderTracking;
|
||||||
|
|
||||||
|
use WooCommerce\PayPalCommerce\OrderTracking\Integration\DhlShipmentIntegration;
|
||||||
use WooCommerce\PayPalCommerce\OrderTracking\Integration\GermanizedShipmentIntegration;
|
use WooCommerce\PayPalCommerce\OrderTracking\Integration\GermanizedShipmentIntegration;
|
||||||
use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipmentTrackingIntegration;
|
use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipmentTrackingIntegration;
|
||||||
use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipStationIntegration;
|
use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipStationIntegration;
|
||||||
|
@ -118,6 +119,7 @@ return array(
|
||||||
$is_gzd_active = $container->get( 'compat.gzd.is_supported_plugin_version_active' );
|
$is_gzd_active = $container->get( 'compat.gzd.is_supported_plugin_version_active' );
|
||||||
$is_wc_shipment_active = $container->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' );
|
$is_wc_shipment_active = $container->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' );
|
||||||
$is_yith_ywot_active = $container->get( 'compat.ywot.is_supported_plugin_version_active' );
|
$is_yith_ywot_active = $container->get( 'compat.ywot.is_supported_plugin_version_active' );
|
||||||
|
$is_dhl_de_active = $container->get( 'compat.dhl.is_supported_plugin_version_active' );
|
||||||
$is_ship_station_active = $container->get( 'compat.shipstation.is_supported_plugin_version_active' );
|
$is_ship_station_active = $container->get( 'compat.shipstation.is_supported_plugin_version_active' );
|
||||||
$is_wc_shipping_tax_active = $container->get( 'compat.wc_shipping_tax.is_supported_plugin_version_active' );
|
$is_wc_shipping_tax_active = $container->get( 'compat.wc_shipping_tax.is_supported_plugin_version_active' );
|
||||||
|
|
||||||
|
@ -135,6 +137,10 @@ return array(
|
||||||
$integrations[] = new YithShipmentIntegration( $shipment_factory, $logger, $endpoint );
|
$integrations[] = new YithShipmentIntegration( $shipment_factory, $logger, $endpoint );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $is_dhl_de_active ) {
|
||||||
|
$integrations[] = new DhlShipmentIntegration( $shipment_factory, $logger, $endpoint );
|
||||||
|
}
|
||||||
|
|
||||||
if ( $is_ship_station_active ) {
|
if ( $is_ship_station_active ) {
|
||||||
$integrations[] = new ShipStationIntegration( $shipment_factory, $logger, $endpoint );
|
$integrations[] = new ShipStationIntegration( $shipment_factory, $logger, $endpoint );
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* The Shipment integration for DHL Shipping Germany for WooCommerce plugin.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\OrderTracking\Integration
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\OrderTracking\Integration;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use WC_Order;
|
||||||
|
use Exception;
|
||||||
|
use WooCommerce\PayPalCommerce\Compat\Integration;
|
||||||
|
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
|
||||||
|
use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactoryInterface;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
||||||
|
use function WooCommerce\PayPalCommerce\Api\ppcp_get_paypal_order;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class DhlShipmentIntegration
|
||||||
|
*/
|
||||||
|
class DhlShipmentIntegration implements Integration {
|
||||||
|
|
||||||
|
use TransactionIdHandlingTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shipment factory.
|
||||||
|
*
|
||||||
|
* @var ShipmentFactoryInterface
|
||||||
|
*/
|
||||||
|
protected $shipment_factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logger.
|
||||||
|
*
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The order tracking endpoint.
|
||||||
|
*
|
||||||
|
* @var OrderTrackingEndpoint
|
||||||
|
*/
|
||||||
|
protected $endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The DhlShipmentIntegration constructor.
|
||||||
|
*
|
||||||
|
* @param ShipmentFactoryInterface $shipment_factory The shipment factory.
|
||||||
|
* @param LoggerInterface $logger The logger.
|
||||||
|
* @param OrderTrackingEndpoint $endpoint The order tracking endpoint.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
ShipmentFactoryInterface $shipment_factory,
|
||||||
|
LoggerInterface $logger,
|
||||||
|
OrderTrackingEndpoint $endpoint
|
||||||
|
) {
|
||||||
|
$this->shipment_factory = $shipment_factory;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->endpoint = $endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function integrate(): void {
|
||||||
|
add_action(
|
||||||
|
'pr_save_dhl_label_tracking',
|
||||||
|
function( int $order_id, array $tracking_details ) {
|
||||||
|
try {
|
||||||
|
$wc_order = wc_get_order( $order_id );
|
||||||
|
if ( ! is_a( $wc_order, WC_Order::class ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$paypal_order = ppcp_get_paypal_order( $wc_order );
|
||||||
|
$capture_id = $this->get_paypal_order_transaction_id( $paypal_order );
|
||||||
|
$tracking_number = $tracking_details['tracking_number'];
|
||||||
|
$carrier = $tracking_details['carrier'];
|
||||||
|
|
||||||
|
if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ppcp_shipment = $this->shipment_factory->create_shipment(
|
||||||
|
$order_id,
|
||||||
|
$capture_id,
|
||||||
|
$tracking_number,
|
||||||
|
'SHIPPED',
|
||||||
|
'DE_DHL',
|
||||||
|
$carrier,
|
||||||
|
array()
|
||||||
|
);
|
||||||
|
|
||||||
|
$tracking_information = $this->endpoint->get_tracking_information( $order_id, $tracking_number );
|
||||||
|
|
||||||
|
$tracking_information
|
||||||
|
? $this->endpoint->update_tracking_information( $ppcp_shipment, $order_id )
|
||||||
|
: $this->endpoint->add_tracking_information( $ppcp_shipment, $order_id );
|
||||||
|
|
||||||
|
} catch ( Exception $exception ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
600,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
230
modules/ppcp-save-payment-methods/resources/js/Configuration.js
Normal file
230
modules/ppcp-save-payment-methods/resources/js/Configuration.js
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
import {
|
||||||
|
getCurrentPaymentMethod,
|
||||||
|
PaymentMethods,
|
||||||
|
} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
|
||||||
|
|
||||||
|
export function buttonConfiguration( ppcp_add_payment_method, errorHandler ) {
|
||||||
|
return {
|
||||||
|
createVaultSetupToken: async () => {
|
||||||
|
const response = await fetch(
|
||||||
|
ppcp_add_payment_method.ajax.create_setup_token.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: ppcp_add_payment_method.ajax.create_setup_token
|
||||||
|
.nonce,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.data.id ) {
|
||||||
|
return result.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHandler.message( ppcp_add_payment_method.error_message );
|
||||||
|
},
|
||||||
|
onApprove: async ( { vaultSetupToken } ) => {
|
||||||
|
const response = await fetch(
|
||||||
|
ppcp_add_payment_method.ajax.create_payment_token.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: ppcp_add_payment_method.ajax.create_payment_token
|
||||||
|
.nonce,
|
||||||
|
vault_setup_token: vaultSetupToken,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.success === true ) {
|
||||||
|
window.location.href =
|
||||||
|
ppcp_add_payment_method.payment_methods_page;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHandler.message( ppcp_add_payment_method.error_message );
|
||||||
|
},
|
||||||
|
onError: ( error ) => {
|
||||||
|
console.error( error );
|
||||||
|
errorHandler.message( ppcp_add_payment_method.error_message );
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cardFieldsConfiguration(
|
||||||
|
ppcp_add_payment_method,
|
||||||
|
errorHandler
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
createVaultSetupToken: async () => {
|
||||||
|
const response = await fetch(
|
||||||
|
ppcp_add_payment_method.ajax.create_setup_token.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: ppcp_add_payment_method.ajax.create_setup_token
|
||||||
|
.nonce,
|
||||||
|
payment_method: PaymentMethods.CARDS,
|
||||||
|
verification_method:
|
||||||
|
ppcp_add_payment_method.verification_method,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.data.id ) {
|
||||||
|
return result.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorHandler.message( ppcp_add_payment_method.error_message );
|
||||||
|
},
|
||||||
|
onApprove: async ( { vaultSetupToken } ) => {
|
||||||
|
const isFreeTrialCart =
|
||||||
|
ppcp_add_payment_method?.is_free_trial_cart ?? false;
|
||||||
|
const response = await fetch(
|
||||||
|
ppcp_add_payment_method.ajax.create_payment_token.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: ppcp_add_payment_method.ajax.create_payment_token
|
||||||
|
.nonce,
|
||||||
|
vault_setup_token: vaultSetupToken,
|
||||||
|
payment_method: PaymentMethods.CARDS,
|
||||||
|
is_free_trial_cart: isFreeTrialCart,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.success === true ) {
|
||||||
|
const context = ppcp_add_payment_method?.context ?? '';
|
||||||
|
if ( context === 'checkout' ) {
|
||||||
|
document.querySelector( '#place_order' ).click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ppcp_add_payment_method.is_subscription_change_payment_page
|
||||||
|
) {
|
||||||
|
const subscriptionId =
|
||||||
|
ppcp_add_payment_method.subscription_id_to_change_payment;
|
||||||
|
if ( subscriptionId && result.data ) {
|
||||||
|
const req = await fetch(
|
||||||
|
ppcp_add_payment_method.ajax
|
||||||
|
.subscription_change_payment_method.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: ppcp_add_payment_method.ajax
|
||||||
|
.subscription_change_payment_method
|
||||||
|
.nonce,
|
||||||
|
subscription_id: subscriptionId,
|
||||||
|
payment_method: getCurrentPaymentMethod(),
|
||||||
|
wc_payment_token_id: result.data,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = await req.json();
|
||||||
|
if ( res.success === true ) {
|
||||||
|
window.location.href = `${ ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href =
|
||||||
|
ppcp_add_payment_method.payment_methods_page;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.errorHandler.message( ppcp_add_payment_method.error_message );
|
||||||
|
},
|
||||||
|
onError: ( error ) => {
|
||||||
|
console.error( error );
|
||||||
|
errorHandler.message( ppcp_add_payment_method.error_message );
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addPaymentMethodConfiguration( ppcp_add_payment_method ) {
|
||||||
|
return {
|
||||||
|
createVaultSetupToken: async () => {
|
||||||
|
const response = await fetch(
|
||||||
|
ppcp_add_payment_method.ajax.create_setup_token.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: ppcp_add_payment_method.ajax.create_setup_token
|
||||||
|
.nonce,
|
||||||
|
payment_method: getCurrentPaymentMethod(),
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.data.id ) {
|
||||||
|
return result.data.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error( result );
|
||||||
|
},
|
||||||
|
onApprove: async ( { vaultSetupToken } ) => {
|
||||||
|
const response = await fetch(
|
||||||
|
ppcp_add_payment_method.ajax.create_payment_token_for_guest
|
||||||
|
.endpoint,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify( {
|
||||||
|
nonce: ppcp_add_payment_method.ajax
|
||||||
|
.create_payment_token_for_guest.nonce,
|
||||||
|
vault_setup_token: vaultSetupToken,
|
||||||
|
} ),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if ( result.success === true ) {
|
||||||
|
document.querySelector( '#place_order' ).click();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error( result );
|
||||||
|
},
|
||||||
|
onError: ( error ) => {
|
||||||
|
console.error( error );
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,303 +4,108 @@ import {
|
||||||
PaymentMethods,
|
PaymentMethods,
|
||||||
} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
|
} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
|
||||||
import { loadScript } from '@paypal/paypal-js';
|
import { loadScript } from '@paypal/paypal-js';
|
||||||
|
import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
|
||||||
|
import { buttonConfiguration, cardFieldsConfiguration } from './Configuration';
|
||||||
|
import { renderFields } from '../../../ppcp-card-fields/resources/js/Render';
|
||||||
import {
|
import {
|
||||||
setVisible,
|
setVisible,
|
||||||
setVisibleByClass,
|
setVisibleByClass,
|
||||||
} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
|
} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
|
||||||
import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
|
|
||||||
import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper';
|
|
||||||
|
|
||||||
const errorHandler = new ErrorHandler(
|
( function ( { ppcp_add_payment_method, jQuery } ) {
|
||||||
ppcp_add_payment_method.labels.error.generic,
|
document.addEventListener( 'DOMContentLoaded', () => {
|
||||||
document.querySelector( '.woocommerce-notices-wrapper' )
|
jQuery( document.body ).on(
|
||||||
);
|
'click init_add_payment_method',
|
||||||
|
'.payment_methods input.input-radio',
|
||||||
const init = () => {
|
function () {
|
||||||
setVisibleByClass(
|
setVisibleByClass(
|
||||||
ORDER_BUTTON_SELECTOR,
|
ORDER_BUTTON_SELECTOR,
|
||||||
getCurrentPaymentMethod() !== PaymentMethods.PAYPAL,
|
getCurrentPaymentMethod() !== PaymentMethods.PAYPAL,
|
||||||
'ppcp-hidden'
|
'ppcp-hidden'
|
||||||
);
|
);
|
||||||
setVisible(
|
setVisible(
|
||||||
`#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`,
|
`#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`,
|
||||||
getCurrentPaymentMethod() === PaymentMethods.PAYPAL
|
getCurrentPaymentMethod() === PaymentMethods.PAYPAL
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener( 'DOMContentLoaded', () => {
|
|
||||||
jQuery( document.body ).on(
|
|
||||||
'click init_add_payment_method',
|
|
||||||
'.payment_methods input.input-radio',
|
|
||||||
function () {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( ppcp_add_payment_method.is_subscription_change_payment_page ) {
|
|
||||||
const saveToAccount = document.querySelector(
|
|
||||||
'#wc-ppcp-credit-card-gateway-new-payment-method'
|
|
||||||
);
|
|
||||||
if ( saveToAccount ) {
|
|
||||||
saveToAccount.checked = true;
|
|
||||||
saveToAccount.disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout( () => {
|
|
||||||
loadScript( {
|
|
||||||
clientId: ppcp_add_payment_method.client_id,
|
|
||||||
merchantId: ppcp_add_payment_method.merchant_id,
|
|
||||||
dataUserIdToken: ppcp_add_payment_method.id_token,
|
|
||||||
components: 'buttons,card-fields',
|
|
||||||
} ).then( ( paypal ) => {
|
|
||||||
errorHandler.clear();
|
|
||||||
|
|
||||||
const paypalButtonContainer = document.querySelector(
|
|
||||||
`#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`
|
|
||||||
);
|
|
||||||
if ( paypalButtonContainer ) {
|
|
||||||
paypal
|
|
||||||
.Buttons( {
|
|
||||||
createVaultSetupToken: async () => {
|
|
||||||
const response = await fetch(
|
|
||||||
ppcp_add_payment_method.ajax.create_setup_token
|
|
||||||
.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: ppcp_add_payment_method.ajax
|
|
||||||
.create_setup_token.nonce,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
if ( result.data.id ) {
|
|
||||||
return result.data.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorHandler.message(
|
|
||||||
ppcp_add_payment_method.error_message
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onApprove: async ( { vaultSetupToken } ) => {
|
|
||||||
const response = await fetch(
|
|
||||||
ppcp_add_payment_method.ajax
|
|
||||||
.create_payment_token.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: ppcp_add_payment_method.ajax
|
|
||||||
.create_payment_token.nonce,
|
|
||||||
vault_setup_token: vaultSetupToken,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
if ( result.success === true ) {
|
|
||||||
window.location.href =
|
|
||||||
ppcp_add_payment_method.payment_methods_page;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorHandler.message(
|
|
||||||
ppcp_add_payment_method.error_message
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onError: ( error ) => {
|
|
||||||
console.error( error );
|
|
||||||
errorHandler.message(
|
|
||||||
ppcp_add_payment_method.error_message
|
|
||||||
);
|
|
||||||
},
|
|
||||||
} )
|
|
||||||
.render(
|
|
||||||
`#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const cardField = paypal.CardFields( {
|
// TODO move to wc subscriptions module
|
||||||
createVaultSetupToken: async () => {
|
if ( ppcp_add_payment_method.is_subscription_change_payment_page ) {
|
||||||
const response = await fetch(
|
const saveToAccount = document.querySelector(
|
||||||
ppcp_add_payment_method.ajax.create_setup_token
|
'#wc-ppcp-credit-card-gateway-new-payment-method'
|
||||||
.endpoint,
|
);
|
||||||
{
|
if ( saveToAccount ) {
|
||||||
method: 'POST',
|
saveToAccount.checked = true;
|
||||||
credentials: 'same-origin',
|
saveToAccount.disabled = true;
|
||||||
headers: {
|
}
|
||||||
'Content-Type': 'application/json',
|
}
|
||||||
},
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: ppcp_add_payment_method.ajax
|
|
||||||
.create_setup_token.nonce,
|
|
||||||
payment_method: PaymentMethods.CARDS,
|
|
||||||
verification_method:
|
|
||||||
ppcp_add_payment_method.verification_method,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
setTimeout( () => {
|
||||||
if ( result.data.id ) {
|
loadScript( {
|
||||||
return result.data.id;
|
clientId: ppcp_add_payment_method.client_id,
|
||||||
}
|
merchantId: ppcp_add_payment_method.merchant_id,
|
||||||
|
dataUserIdToken: ppcp_add_payment_method.id_token,
|
||||||
|
components: 'buttons,card-fields',
|
||||||
|
} ).then( ( paypal ) => {
|
||||||
|
const errorHandler = new ErrorHandler(
|
||||||
|
ppcp_add_payment_method.labels.error.generic,
|
||||||
|
document.querySelector( '.woocommerce-notices-wrapper' )
|
||||||
|
);
|
||||||
|
errorHandler.clear();
|
||||||
|
|
||||||
errorHandler.message(
|
const paypalButtonContainer = document.querySelector(
|
||||||
ppcp_add_payment_method.error_message
|
`#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`
|
||||||
);
|
);
|
||||||
},
|
|
||||||
onApprove: async ( { vaultSetupToken } ) => {
|
|
||||||
const response = await fetch(
|
|
||||||
ppcp_add_payment_method.ajax.create_payment_token
|
|
||||||
.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: ppcp_add_payment_method.ajax
|
|
||||||
.create_payment_token.nonce,
|
|
||||||
vault_setup_token: vaultSetupToken,
|
|
||||||
payment_method: PaymentMethods.CARDS,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await response.json();
|
if ( paypalButtonContainer ) {
|
||||||
if ( result.success === true ) {
|
paypal
|
||||||
|
.Buttons(
|
||||||
|
buttonConfiguration(
|
||||||
|
ppcp_add_payment_method,
|
||||||
|
errorHandler
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.render(
|
||||||
|
`#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardFields = paypal.CardFields(
|
||||||
|
cardFieldsConfiguration(
|
||||||
|
ppcp_add_payment_method,
|
||||||
|
errorHandler
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( cardFields.isEligible() ) {
|
||||||
|
renderFields( cardFields );
|
||||||
|
}
|
||||||
|
|
||||||
|
document
|
||||||
|
.querySelector( '#place_order' )
|
||||||
|
?.addEventListener( 'click', ( event ) => {
|
||||||
|
const cardPaymentToken = document.querySelector(
|
||||||
|
'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked'
|
||||||
|
)?.value;
|
||||||
if (
|
if (
|
||||||
ppcp_add_payment_method.is_subscription_change_payment_page
|
getCurrentPaymentMethod() !==
|
||||||
|
'ppcp-credit-card-gateway' ||
|
||||||
|
( cardPaymentToken && cardPaymentToken !== 'new' )
|
||||||
) {
|
) {
|
||||||
const subscriptionId =
|
|
||||||
ppcp_add_payment_method.subscription_id_to_change_payment;
|
|
||||||
if ( subscriptionId && result.data ) {
|
|
||||||
const req = await fetch(
|
|
||||||
ppcp_add_payment_method.ajax
|
|
||||||
.subscription_change_payment_method
|
|
||||||
.endpoint,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify( {
|
|
||||||
nonce: ppcp_add_payment_method.ajax
|
|
||||||
.subscription_change_payment_method
|
|
||||||
.nonce,
|
|
||||||
subscription_id: subscriptionId,
|
|
||||||
payment_method:
|
|
||||||
getCurrentPaymentMethod(),
|
|
||||||
wc_payment_token_id: result.data,
|
|
||||||
} ),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const res = await req.json();
|
|
||||||
if ( res.success === true ) {
|
|
||||||
window.location.href = `${ ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href =
|
event.preventDefault();
|
||||||
ppcp_add_payment_method.payment_methods_page;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorHandler.message(
|
cardFields.submit().catch( ( error ) => {
|
||||||
ppcp_add_payment_method.error_message
|
console.error( error );
|
||||||
);
|
} );
|
||||||
},
|
|
||||||
onError: ( error ) => {
|
|
||||||
console.error( error );
|
|
||||||
errorHandler.message(
|
|
||||||
ppcp_add_payment_method.error_message
|
|
||||||
);
|
|
||||||
},
|
|
||||||
} );
|
|
||||||
|
|
||||||
if ( cardField.isEligible() ) {
|
|
||||||
const nameField = document.getElementById(
|
|
||||||
'ppcp-credit-card-gateway-card-name'
|
|
||||||
);
|
|
||||||
if ( nameField ) {
|
|
||||||
const styles = cardFieldStyles( nameField );
|
|
||||||
cardField
|
|
||||||
.NameField( { style: { input: styles } } )
|
|
||||||
.render( nameField.parentNode );
|
|
||||||
nameField.hidden = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const numberField = document.getElementById(
|
|
||||||
'ppcp-credit-card-gateway-card-number'
|
|
||||||
);
|
|
||||||
if ( numberField ) {
|
|
||||||
const styles = cardFieldStyles( numberField );
|
|
||||||
cardField
|
|
||||||
.NumberField( { style: { input: styles } } )
|
|
||||||
.render( numberField.parentNode );
|
|
||||||
numberField.hidden = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const expiryField = document.getElementById(
|
|
||||||
'ppcp-credit-card-gateway-card-expiry'
|
|
||||||
);
|
|
||||||
if ( expiryField ) {
|
|
||||||
const styles = cardFieldStyles( expiryField );
|
|
||||||
cardField
|
|
||||||
.ExpiryField( { style: { input: styles } } )
|
|
||||||
.render( expiryField.parentNode );
|
|
||||||
expiryField.hidden = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cvvField = document.getElementById(
|
|
||||||
'ppcp-credit-card-gateway-card-cvc'
|
|
||||||
);
|
|
||||||
if ( cvvField ) {
|
|
||||||
const styles = cardFieldStyles( cvvField );
|
|
||||||
cardField
|
|
||||||
.CVVField( { style: { input: styles } } )
|
|
||||||
.render( cvvField.parentNode );
|
|
||||||
cvvField.hidden = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document
|
|
||||||
.querySelector( '#place_order' )
|
|
||||||
?.addEventListener( 'click', ( event ) => {
|
|
||||||
const cardPaymentToken = document.querySelector(
|
|
||||||
'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked'
|
|
||||||
)?.value;
|
|
||||||
if (
|
|
||||||
getCurrentPaymentMethod() !==
|
|
||||||
'ppcp-credit-card-gateway' ||
|
|
||||||
( cardPaymentToken && cardPaymentToken !== 'new' )
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
cardField.submit().catch( ( error ) => {
|
|
||||||
console.error( error );
|
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
} );
|
}, 1000 );
|
||||||
}, 1000 );
|
} );
|
||||||
|
} )( {
|
||||||
|
ppcp_add_payment_method: window.ppcp_add_payment_method,
|
||||||
|
jQuery: window.jQuery,
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -115,6 +115,11 @@ class CreatePaymentToken implements EndpointInterface {
|
||||||
|
|
||||||
if ( isset( $result->payment_source->card ) ) {
|
if ( isset( $result->payment_source->card ) ) {
|
||||||
$wc_token_id = $this->wc_payment_tokens->create_payment_token_card( $current_user_id, $result );
|
$wc_token_id = $this->wc_payment_tokens->create_payment_token_card( $current_user_id, $result );
|
||||||
|
|
||||||
|
$is_free_trial_cart = $data['is_free_trial_cart'] ?? '';
|
||||||
|
if ( $is_free_trial_cart === '1' ) {
|
||||||
|
WC()->session->set( 'ppcp_card_payment_token_for_free_trial', $wc_token_id );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,11 @@ $background-ident-color: #fbfbfb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ppcp-notice-list {
|
||||||
|
list-style-type: disc;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
}
|
}
|
||||||
|
|
58
modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js
Normal file
58
modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Helper component to log debug details to the browser console.
|
||||||
|
*
|
||||||
|
* A utility class that is used by payment buttons on the front-end, like the GooglePayButton.
|
||||||
|
*/
|
||||||
|
export default class ConsoleLogger {
|
||||||
|
/**
|
||||||
|
* The prefix to display before every log output.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
#prefix = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether logging is enabled, disabled by default.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
#enabled = false;
|
||||||
|
|
||||||
|
constructor( ...prefixes ) {
|
||||||
|
if ( prefixes.length ) {
|
||||||
|
this.#prefix = `[${ prefixes.join( ' | ' ) }]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable logging. Only impacts `log()` output.
|
||||||
|
*
|
||||||
|
* @param {boolean} state True to enable log output.
|
||||||
|
*/
|
||||||
|
set enabled( state ) {
|
||||||
|
this.#enabled = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output log-level details to the browser console, if logging is enabled.
|
||||||
|
*
|
||||||
|
* @param {...any} args - All provided values are output to the browser console.
|
||||||
|
*/
|
||||||
|
log( ...args ) {
|
||||||
|
if ( this.#enabled ) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.log( this.#prefix, ...args );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an error message in the browser's console.
|
||||||
|
*
|
||||||
|
* Error messages are always output, even when logging is disabled.
|
||||||
|
*
|
||||||
|
* @param {...any} args - All provided values are output to the browser console.
|
||||||
|
*/
|
||||||
|
error( ...args ) {
|
||||||
|
console.error( this.#prefix, ...args );
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
|
/* global jQuery */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Map with all input fields that are relevant to render the preview of the
|
* Returns a Map with all input fields that are relevant to render the preview of the
|
||||||
* given payment button.
|
* given payment button.
|
||||||
*
|
*
|
||||||
* @param {string} apmName - Value of the custom attribute `data-ppcp-apm-name`.
|
* @param {string} apmName - Value of the custom attribute `data-ppcp-apm-name`.
|
||||||
* @return {Map<string, {val:Function, el:HTMLInputElement}>}
|
* @return {Map<string, {val:Function, el:HTMLInputElement}>} List of input elements found on the current admin page.
|
||||||
*/
|
*/
|
||||||
export function getButtonFormFields( apmName ) {
|
export function getButtonFormFields( apmName ) {
|
||||||
const inputFields = document.querySelectorAll(
|
const inputFields = document.querySelectorAll(
|
||||||
|
@ -28,9 +30,9 @@ export function getButtonFormFields( apmName ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a function that triggers an update of the specified preview button, when invoked.
|
* Returns a function that triggers an update of the specified preview button, when invoked.
|
||||||
|
*
|
||||||
* @param {string} apmName
|
* @param {string} apmName
|
||||||
* @return {((object) => void)}
|
* @return {((object) => void)} Trigger-function; updates preview buttons when invoked.
|
||||||
*/
|
*/
|
||||||
export function buttonRefreshTriggerFactory( apmName ) {
|
export function buttonRefreshTriggerFactory( apmName ) {
|
||||||
const eventName = `ppcp_paypal_render_preview_${ apmName }`;
|
const eventName = `ppcp_paypal_render_preview_${ apmName }`;
|
||||||
|
@ -44,7 +46,7 @@ export function buttonRefreshTriggerFactory( apmName ) {
|
||||||
* Returns a function that gets the current form values of the specified preview button.
|
* Returns a function that gets the current form values of the specified preview button.
|
||||||
*
|
*
|
||||||
* @param {string} apmName
|
* @param {string} apmName
|
||||||
* @return {() => {button: {wrapper:string, is_enabled:boolean, style:{}}}}
|
* @return {() => {button: {wrapper:string, is_enabled:boolean, style:{}}}} Getter-function; returns preview config details when invoked.
|
||||||
*/
|
*/
|
||||||
export function buttonSettingsGetterFactory( apmName ) {
|
export function buttonSettingsGetterFactory( apmName ) {
|
||||||
const fields = getButtonFormFields( apmName );
|
const fields = getButtonFormFields( apmName );
|
||||||
|
|
|
@ -71,6 +71,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
|
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
|
||||||
|
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway {
|
'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway {
|
||||||
|
@ -198,6 +199,7 @@ return array(
|
||||||
Settings::PAY_LATER_TAB_ID,
|
Settings::PAY_LATER_TAB_ID,
|
||||||
AxoGateway::ID,
|
AxoGateway::ID,
|
||||||
GooglePayGateway::ID,
|
GooglePayGateway::ID,
|
||||||
|
ApplePayGateway::ID,
|
||||||
),
|
),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
@ -220,6 +222,7 @@ return array(
|
||||||
Settings::PAY_LATER_TAB_ID,
|
Settings::PAY_LATER_TAB_ID,
|
||||||
Settings::CONNECTION_TAB_ID,
|
Settings::CONNECTION_TAB_ID,
|
||||||
GooglePayGateway::ID,
|
GooglePayGateway::ID,
|
||||||
|
ApplePayGateway::ID,
|
||||||
),
|
),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
@ -1411,10 +1414,10 @@ return array(
|
||||||
return $label;
|
return $label;
|
||||||
},
|
},
|
||||||
'wcgateway.enable-dcc-url-sandbox' => static function ( ContainerInterface $container ): string {
|
'wcgateway.enable-dcc-url-sandbox' => static function ( ContainerInterface $container ): string {
|
||||||
return 'https://www.sandbox.paypal.com/bizsignup/entry/product/ppcp';
|
return 'https://www.sandbox.paypal.com/bizsignup/entry?product=ppcp';
|
||||||
},
|
},
|
||||||
'wcgateway.enable-dcc-url-live' => static function ( ContainerInterface $container ): string {
|
'wcgateway.enable-dcc-url-live' => static function ( ContainerInterface $container ): string {
|
||||||
return 'https://www.paypal.com/bizsignup/entry/product/ppcp';
|
return 'https://www.paypal.com/bizsignup/entry?product=ppcp';
|
||||||
},
|
},
|
||||||
'wcgateway.enable-pui-url-sandbox' => static function ( ContainerInterface $container ): string {
|
'wcgateway.enable-pui-url-sandbox' => static function ( ContainerInterface $container ): string {
|
||||||
return 'https://www.sandbox.paypal.com/bizsignup/entry?country.x=DE&product=payment_methods&capabilities=PAY_UPON_INVOICE';
|
return 'https://www.sandbox.paypal.com/bizsignup/entry?country.x=DE&product=payment_methods&capabilities=PAY_UPON_INVOICE';
|
||||||
|
|
|
@ -426,12 +426,42 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
|
||||||
public function process_payment( $order_id ) {
|
public function process_payment( $order_id ) {
|
||||||
$wc_order = wc_get_order( $order_id );
|
$wc_order = wc_get_order( $order_id );
|
||||||
if ( ! is_a( $wc_order, WC_Order::class ) ) {
|
if ( ! is_a( $wc_order, WC_Order::class ) ) {
|
||||||
|
WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null );
|
||||||
|
|
||||||
return $this->handle_payment_failure(
|
return $this->handle_payment_failure(
|
||||||
null,
|
null,
|
||||||
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
|
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$guest_card_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null;
|
||||||
|
WC()->session->get( 'ppcp_guest_payment_for_free_trial', null );
|
||||||
|
if ( $guest_card_payment_for_free_trial ) {
|
||||||
|
$customer_id = $guest_card_payment_for_free_trial->customer->id ?? '';
|
||||||
|
if ( $customer_id ) {
|
||||||
|
update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $guest_card_payment_for_free_trial->payment_source->card ) ) {
|
||||||
|
$this->wc_payment_tokens->create_payment_token_card( $wc_order->get_customer_id(), $guest_card_payment_for_free_trial );
|
||||||
|
|
||||||
|
$wc_order->payment_complete();
|
||||||
|
return $this->handle_payment_success( $wc_order );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial' ) ?? null;
|
||||||
|
WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null );
|
||||||
|
if ( $card_payment_token_for_free_trial ) {
|
||||||
|
$tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() );
|
||||||
|
foreach ( $tokens as $token ) {
|
||||||
|
if ( $token->get_id() === (int) $card_payment_token_for_free_trial ) {
|
||||||
|
$wc_order->payment_complete();
|
||||||
|
return $this->handle_payment_success( $wc_order );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||||
$card_payment_token_id = wc_clean( wp_unslash( $_POST['wc-ppcp-credit-card-gateway-payment-token'] ?? '' ) );
|
$card_payment_token_id = wc_clean( wp_unslash( $_POST['wc-ppcp-credit-card-gateway-payment-token'] ?? '' ) );
|
||||||
|
|
||||||
|
|
|
@ -118,18 +118,15 @@ class Settings implements ContainerInterface {
|
||||||
* Stores the settings to the database.
|
* Stores the settings to the database.
|
||||||
*/
|
*/
|
||||||
public function persist() {
|
public function persist() {
|
||||||
|
|
||||||
return update_option( self::KEY, $this->settings );
|
return update_option( self::KEY, $this->settings );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the settings.
|
* Loads the settings.
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function load(): bool {
|
private function load(): bool {
|
||||||
|
|
||||||
if ( $this->settings ) {
|
if ( $this->settings ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"install:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn install",
|
"install:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn install",
|
||||||
"install:modules:ppcp-axo": "cd modules/ppcp-axo && yarn install",
|
"install:modules:ppcp-axo": "cd modules/ppcp-axo && yarn install",
|
||||||
"install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install",
|
"install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install",
|
||||||
|
"install:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn install",
|
||||||
"install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install",
|
"install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install",
|
||||||
"install:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn install",
|
"install:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn install",
|
||||||
"build:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run build",
|
"build:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run build",
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
"build:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run build",
|
"build:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run build",
|
||||||
"build:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run build",
|
"build:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run build",
|
||||||
"build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run build",
|
"build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run build",
|
||||||
|
"build:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run build",
|
||||||
"build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build",
|
"build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build",
|
||||||
"build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build",
|
"build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build",
|
||||||
"build:modules": "run-p build:modules:*",
|
"build:modules": "run-p build:modules:*",
|
||||||
|
@ -54,6 +56,7 @@
|
||||||
"watch:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run watch",
|
"watch:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run watch",
|
||||||
"watch:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run watch",
|
"watch:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run watch",
|
||||||
"watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run watch",
|
"watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run watch",
|
||||||
|
"watch:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run watch",
|
||||||
"watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch",
|
"watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch",
|
||||||
"watch:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run watch",
|
"watch:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run watch",
|
||||||
"watch:modules": "run-p watch:modules:*",
|
"watch:modules": "run-p watch:modules:*",
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
<file name=".psalm/wcblocks.php"/>
|
<file name=".psalm/wcblocks.php"/>
|
||||||
<file name=".psalm/wcs.php"/>
|
<file name=".psalm/wcs.php"/>
|
||||||
<file name=".psalm/gzd.php"/>
|
<file name=".psalm/gzd.php"/>
|
||||||
|
<file name=".psalm/wc-bookings.php"/>
|
||||||
<file name=".psalm/wpcli.php"/>
|
<file name=".psalm/wpcli.php"/>
|
||||||
<file name="vendor/php-stubs/wordpress-stubs/wordpress-stubs.php"/>
|
<file name="vendor/php-stubs/wordpress-stubs/wordpress-stubs.php"/>
|
||||||
<file name="vendor/php-stubs/woocommerce-stubs/woocommerce-stubs.php"/>
|
<file name="vendor/php-stubs/woocommerce-stubs/woocommerce-stubs.php"/>
|
||||||
|
|
|
@ -1,78 +1,78 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
const { defineConfig, devices } = require('@playwright/test');
|
const { defineConfig, devices } = require( '@playwright/test' );
|
||||||
|
|
||||||
require('dotenv').config({ path: '.env' });
|
require( 'dotenv' ).config( { path: '.env' } );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://playwright.dev/docs/test-configuration
|
* @see https://playwright.dev/docs/test-configuration
|
||||||
*/
|
*/
|
||||||
module.exports = defineConfig({
|
module.exports = defineConfig( {
|
||||||
timeout: 60000,
|
timeout: 30000,
|
||||||
testDir: './tests',
|
testDir: './tests',
|
||||||
/* Run tests in files in parallel */
|
/* Run tests in files in parallel */
|
||||||
fullyParallel: true,
|
fullyParallel: false,
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !! process.env.CI,
|
||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 2 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: [
|
reporter: [
|
||||||
[process.env.CI ? 'github' : 'list'],
|
[ process.env.CI ? 'github' : 'list' ],
|
||||||
['html', {open: 'never'}],
|
[ 'html', { open: 'never' } ],
|
||||||
],
|
],
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
baseURL: process.env.BASEURL,
|
baseURL: process.env.BASEURL,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'chromium',
|
||||||
use: { ...devices['Desktop Chrome'] },
|
use: { ...devices[ 'Desktop Chrome' ] },
|
||||||
},
|
},
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// name: 'firefox',
|
// name: 'firefox',
|
||||||
// use: { ...devices['Desktop Firefox'] },
|
// use: { ...devices['Desktop Firefox'] },
|
||||||
// },
|
// },
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
// name: 'webkit',
|
// name: 'webkit',
|
||||||
// use: { ...devices['Desktop Safari'] },
|
// use: { ...devices['Desktop Safari'] },
|
||||||
// },
|
// },
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
/* Test against mobile viewports. */
|
||||||
// {
|
// {
|
||||||
// name: 'Mobile Chrome',
|
// name: 'Mobile Chrome',
|
||||||
// use: { ...devices['Pixel 5'] },
|
// use: { ...devices['Pixel 5'] },
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// name: 'Mobile Safari',
|
// name: 'Mobile Safari',
|
||||||
// use: { ...devices['iPhone 12'] },
|
// use: { ...devices['iPhone 12'] },
|
||||||
// },
|
// },
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
/* Test against branded browsers. */
|
||||||
// {
|
// {
|
||||||
// name: 'Microsoft Edge',
|
// name: 'Microsoft Edge',
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// name: 'Google Chrome',
|
// name: 'Google Chrome',
|
||||||
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
|
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
// },
|
// },
|
||||||
],
|
],
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
/* Run your local dev server before starting the tests */
|
||||||
// webServer: {
|
// webServer: {
|
||||||
// command: 'npm run start',
|
// command: 'npm run start',
|
||||||
// url: 'http://127.0.0.1:3000',
|
// url: 'http://127.0.0.1:3000',
|
||||||
// reuseExistingServer: !process.env.CI,
|
// reuseExistingServer: !process.env.CI,
|
||||||
// },
|
// },
|
||||||
});
|
} );
|
||||||
|
|
76
tests/Playwright/tests/apms-place-order.spec.js
Normal file
76
tests/Playwright/tests/apms-place-order.spec.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
const { test, expect } = require( '@playwright/test' );
|
||||||
|
const {
|
||||||
|
fillCheckoutForm,
|
||||||
|
expectOrderReceivedPage,
|
||||||
|
acceptTerms,
|
||||||
|
} = require( './utils/checkout' );
|
||||||
|
const {
|
||||||
|
openPaypalPopup,
|
||||||
|
completePaypalPayment,
|
||||||
|
} = require( './utils/paypal-popup' );
|
||||||
|
|
||||||
|
const { PRODUCT_ID, CHECKOUT_URL, CART_URL, APM_ID } = process.env;
|
||||||
|
|
||||||
|
async function expectContinuation( page ) {
|
||||||
|
await expect(
|
||||||
|
page.locator( '#payment_method_ppcp-gateway' )
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completeContinuation( page ) {
|
||||||
|
await expectContinuation( page );
|
||||||
|
|
||||||
|
await Promise.all( [
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator( '#place_order' ).click(),
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
|
test( 'PayPal APM button place order', async ( { page } ) => {
|
||||||
|
await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID );
|
||||||
|
|
||||||
|
await page.goto( CHECKOUT_URL );
|
||||||
|
|
||||||
|
await fillCheckoutForm( page );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page, { fundingSource: APM_ID } );
|
||||||
|
|
||||||
|
await popup.getByText( 'Continue', { exact: true } ).click();
|
||||||
|
await completePaypalPayment( popup, {
|
||||||
|
selector: '[name="Successful"]',
|
||||||
|
} );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'PayPal APM button place order when redirect fails', async ( {
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID );
|
||||||
|
|
||||||
|
await page.goto( CHECKOUT_URL );
|
||||||
|
|
||||||
|
await fillCheckoutForm( page );
|
||||||
|
|
||||||
|
await page.evaluate( 'PayPalCommerceGateway.ajax.approve_order = null' );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page, { fundingSource: APM_ID } );
|
||||||
|
|
||||||
|
await popup.getByText( 'Continue', { exact: true } ).click();
|
||||||
|
await completePaypalPayment( popup, {
|
||||||
|
selector: '[name="Successful"]',
|
||||||
|
} );
|
||||||
|
|
||||||
|
await expect( page.locator( '.woocommerce-error' ) ).toBeVisible();
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
await expectContinuation( page );
|
||||||
|
|
||||||
|
await acceptTerms( page );
|
||||||
|
|
||||||
|
await completeContinuation( page );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
109
tests/Playwright/tests/blocks-place-order.spec.js
Normal file
109
tests/Playwright/tests/blocks-place-order.spec.js
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
const { expect, test } = require( '@playwright/test' );
|
||||||
|
const { serverExec } = require( './utils/server' );
|
||||||
|
const {
|
||||||
|
openPaypalPopup,
|
||||||
|
loginIntoPaypal,
|
||||||
|
completePaypalPayment,
|
||||||
|
waitForPaypalShippingList,
|
||||||
|
} = require( './utils/paypal-popup' );
|
||||||
|
const { expectOrderReceivedPage } = require( './utils/checkout' );
|
||||||
|
|
||||||
|
const {
|
||||||
|
PRODUCT_ID,
|
||||||
|
BLOCK_CHECKOUT_URL,
|
||||||
|
BLOCK_CHECKOUT_PAGE_ID,
|
||||||
|
BLOCK_CART_URL,
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
async function completeBlockContinuation( page ) {
|
||||||
|
await expect(
|
||||||
|
page.locator( '#radio-control-wc-payment-method-options-ppcp-gateway' )
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 );
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page
|
||||||
|
.locator( '.wc-block-components-checkout-place-order-button' )
|
||||||
|
.click()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeAll( async ( { browser } ) => {
|
||||||
|
await serverExec(
|
||||||
|
'wp option update woocommerce_checkout_page_id ' +
|
||||||
|
BLOCK_CHECKOUT_PAGE_ID
|
||||||
|
);
|
||||||
|
await serverExec(
|
||||||
|
'wp pcp settings update blocks_final_review_enabled true'
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'PayPal express block checkout', async ( { page } ) => {
|
||||||
|
await page.goto( '?add-to-cart=' + PRODUCT_ID );
|
||||||
|
|
||||||
|
await page.goto( BLOCK_CHECKOUT_URL );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page );
|
||||||
|
|
||||||
|
await loginIntoPaypal( popup );
|
||||||
|
|
||||||
|
await completePaypalPayment( popup );
|
||||||
|
|
||||||
|
await completeBlockContinuation( page );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'PayPal express block cart', async ( { page } ) => {
|
||||||
|
await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page );
|
||||||
|
|
||||||
|
await loginIntoPaypal( popup );
|
||||||
|
|
||||||
|
await completePaypalPayment( popup );
|
||||||
|
|
||||||
|
await completeBlockContinuation( page );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test.describe( 'Without review', () => {
|
||||||
|
test.beforeAll( async ( { browser } ) => {
|
||||||
|
await serverExec(
|
||||||
|
'wp pcp settings update blocks_final_review_enabled false'
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'PayPal express block checkout', async ( { page } ) => {
|
||||||
|
await page.goto( '?add-to-cart=' + PRODUCT_ID );
|
||||||
|
|
||||||
|
await page.goto( BLOCK_CHECKOUT_URL );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page );
|
||||||
|
|
||||||
|
await loginIntoPaypal( popup );
|
||||||
|
|
||||||
|
await waitForPaypalShippingList( popup );
|
||||||
|
|
||||||
|
await completePaypalPayment( popup );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'PayPal express block cart', async ( { page } ) => {
|
||||||
|
await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page );
|
||||||
|
|
||||||
|
await loginIntoPaypal( popup );
|
||||||
|
|
||||||
|
await waitForPaypalShippingList( popup );
|
||||||
|
|
||||||
|
await completePaypalPayment( popup );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
||||||
|
} );
|
97
tests/Playwright/tests/classic-place-order.spec.js
Normal file
97
tests/Playwright/tests/classic-place-order.spec.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
const { test, expect } = require( '@playwright/test' );
|
||||||
|
const { serverExec } = require( './utils/server' );
|
||||||
|
const {
|
||||||
|
fillCheckoutForm,
|
||||||
|
expectOrderReceivedPage,
|
||||||
|
} = require( './utils/checkout' );
|
||||||
|
const {
|
||||||
|
openPaypalPopup,
|
||||||
|
loginIntoPaypal,
|
||||||
|
completePaypalPayment,
|
||||||
|
} = require( './utils/paypal-popup' );
|
||||||
|
|
||||||
|
const {
|
||||||
|
CREDIT_CARD_NUMBER,
|
||||||
|
CREDIT_CARD_CVV,
|
||||||
|
PRODUCT_URL,
|
||||||
|
CHECKOUT_URL,
|
||||||
|
CHECKOUT_PAGE_ID,
|
||||||
|
} = process.env;
|
||||||
|
|
||||||
|
async function expectContinuation( page ) {
|
||||||
|
await expect(
|
||||||
|
page.locator( '#payment_method_ppcp-gateway' )
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completeContinuation( page ) {
|
||||||
|
await expectContinuation( page );
|
||||||
|
|
||||||
|
await Promise.all( [
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator( '#place_order' ).click(),
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeAll( async ( { browser } ) => {
|
||||||
|
await serverExec(
|
||||||
|
'wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'PayPal button place order from Product page', async ( { page } ) => {
|
||||||
|
await serverExec(
|
||||||
|
'wp pcp settings update blocks_final_review_enabled true'
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.goto( PRODUCT_URL );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page );
|
||||||
|
|
||||||
|
await loginIntoPaypal( popup );
|
||||||
|
|
||||||
|
await completePaypalPayment( popup );
|
||||||
|
|
||||||
|
await fillCheckoutForm( page );
|
||||||
|
|
||||||
|
await completeContinuation( page );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'Advanced Credit and Debit Card place order from Checkout page', async ( {
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
await page.goto( PRODUCT_URL );
|
||||||
|
await page.locator( '.single_add_to_cart_button' ).click();
|
||||||
|
|
||||||
|
await page.goto( CHECKOUT_URL );
|
||||||
|
await fillCheckoutForm( page );
|
||||||
|
|
||||||
|
await page.click( 'text=Credit Cards' );
|
||||||
|
|
||||||
|
const expirationDate = await page
|
||||||
|
.frameLocator( 'iframe[title="paypal_card_expiry_field"]' )
|
||||||
|
.locator( 'input.card-field-expiry' );
|
||||||
|
await expirationDate.click();
|
||||||
|
await page.keyboard.type( '01/42' );
|
||||||
|
|
||||||
|
const creditCardNumber = await page
|
||||||
|
.frameLocator( '[title="paypal_card_number_field"]' )
|
||||||
|
.locator( '.card-field-number' );
|
||||||
|
await creditCardNumber.fill( CREDIT_CARD_NUMBER );
|
||||||
|
|
||||||
|
const cvv = await page
|
||||||
|
.frameLocator( '[title="paypal_card_cvv_field"]' )
|
||||||
|
.locator( '.card-field-cvv' );
|
||||||
|
await cvv.fill( CREDIT_CARD_CVV );
|
||||||
|
|
||||||
|
await Promise.all( [
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator( '.ppcp-dcc-order-button' ).click(),
|
||||||
|
] );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
97
tests/Playwright/tests/free-trial-subscriptions.spec.js
Normal file
97
tests/Playwright/tests/free-trial-subscriptions.spec.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
const { test, expect } = require( '@playwright/test' );
|
||||||
|
const { loginAsCustomer } = require( './utils/user' );
|
||||||
|
const { openPaypalPopup, loginIntoPaypal } = require( './utils/paypal-popup' );
|
||||||
|
const { serverExec } = require( './utils/server' );
|
||||||
|
const { expectOrderReceivedPage } = require( './utils/checkout' );
|
||||||
|
|
||||||
|
const { CREDIT_CARD_NUMBER, CREDIT_CARD_CVV } = process.env;
|
||||||
|
|
||||||
|
test( 'PayPal logged-in user free trial subscription without payment token with shipping callback enabled', async ( {
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
await serverExec(
|
||||||
|
'wp pcp settings update blocks_final_review_enabled false'
|
||||||
|
);
|
||||||
|
|
||||||
|
await loginAsCustomer( page );
|
||||||
|
await page.goto( '/product/free-trial' );
|
||||||
|
await page.click( 'text=Sign up now' );
|
||||||
|
await page.goto( '/classic-checkout' );
|
||||||
|
|
||||||
|
const popup = await openPaypalPopup( page );
|
||||||
|
await loginIntoPaypal( popup );
|
||||||
|
popup.locator( '#consentButton' ).click();
|
||||||
|
|
||||||
|
await page.waitForURL( '**/order-received/**' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'ACDC logged-in user free trial subscription without payment token', async ( {
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
await loginAsCustomer( page );
|
||||||
|
await page.goto( '/product/free-trial' );
|
||||||
|
await page.click( 'text=Sign up now' );
|
||||||
|
await page.goto( '/classic-checkout' );
|
||||||
|
|
||||||
|
await page.click( 'text=Credit Cards' );
|
||||||
|
|
||||||
|
const creditCardNumber = await page
|
||||||
|
.frameLocator( '[title="paypal_card_number_field"]' )
|
||||||
|
.locator( '.card-field-number' );
|
||||||
|
await creditCardNumber.fill( CREDIT_CARD_NUMBER );
|
||||||
|
|
||||||
|
const expirationDate = await page
|
||||||
|
.frameLocator( 'iframe[title="paypal_card_expiry_field"]' )
|
||||||
|
.locator( 'input.card-field-expiry' );
|
||||||
|
await expirationDate.click();
|
||||||
|
await page.keyboard.type( '01/42' );
|
||||||
|
|
||||||
|
const cvv = await page
|
||||||
|
.frameLocator( '[title="paypal_card_cvv_field"]' )
|
||||||
|
.locator( '.card-field-cvv' );
|
||||||
|
await cvv.fill( CREDIT_CARD_CVV );
|
||||||
|
|
||||||
|
await Promise.all( [
|
||||||
|
page.waitForNavigation(),
|
||||||
|
page.locator( '.ppcp-dcc-order-button' ).click(),
|
||||||
|
] );
|
||||||
|
|
||||||
|
await expectOrderReceivedPage( page );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'ACDC purchase free trial in Block checkout page as logged-in without saved card payments', async ( {
|
||||||
|
page,
|
||||||
|
} ) => {
|
||||||
|
await loginAsCustomer( page );
|
||||||
|
await page.goto( '/product/free-trial' );
|
||||||
|
await page.click( 'text=Sign up now' );
|
||||||
|
await page.goto( '/checkout' );
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator(
|
||||||
|
'#radio-control-wc-payment-method-options-ppcp-credit-card-gateway'
|
||||||
|
)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
const expirationDate = await page
|
||||||
|
.frameLocator( 'iframe[title="paypal_card_expiry_field"]' )
|
||||||
|
.locator( 'input.card-field-expiry' );
|
||||||
|
await expirationDate.click();
|
||||||
|
await page.keyboard.type( '01/42' );
|
||||||
|
|
||||||
|
const creditCardNumber = await page
|
||||||
|
.frameLocator( '[title="paypal_card_number_field"]' )
|
||||||
|
.locator( '.card-field-number' );
|
||||||
|
await creditCardNumber.fill( CREDIT_CARD_NUMBER );
|
||||||
|
|
||||||
|
const cvv = await page
|
||||||
|
.frameLocator( '[title="paypal_card_cvv_field"]' )
|
||||||
|
.locator( '.card-field-cvv' );
|
||||||
|
await cvv.fill( CREDIT_CARD_CVV );
|
||||||
|
|
||||||
|
await page
|
||||||
|
.locator( '.wc-block-components-checkout-place-order-button' )
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await page.waitForURL( '**/order-received/**' );
|
||||||
|
} );
|
|
@ -1,207 +0,0 @@
|
||||||
const {test, expect} = require('@playwright/test');
|
|
||||||
const {serverExec} = require("./utils/server");
|
|
||||||
const {fillCheckoutForm, expectOrderReceivedPage, acceptTerms} = require("./utils/checkout");
|
|
||||||
const {openPaypalPopup, loginIntoPaypal, waitForPaypalShippingList, completePaypalPayment} = require("./utils/paypal-popup");
|
|
||||||
|
|
||||||
const {
|
|
||||||
CREDIT_CARD_NUMBER,
|
|
||||||
CREDIT_CARD_EXPIRATION,
|
|
||||||
CREDIT_CARD_CVV,
|
|
||||||
PRODUCT_URL,
|
|
||||||
PRODUCT_ID,
|
|
||||||
CHECKOUT_URL,
|
|
||||||
CHECKOUT_PAGE_ID,
|
|
||||||
CART_URL,
|
|
||||||
BLOCK_CHECKOUT_URL,
|
|
||||||
BLOCK_CHECKOUT_PAGE_ID,
|
|
||||||
BLOCK_CART_URL,
|
|
||||||
APM_ID,
|
|
||||||
} = process.env;
|
|
||||||
|
|
||||||
async function completeBlockContinuation(page) {
|
|
||||||
await expect(page.locator('#radio-control-wc-payment-method-options-ppcp-gateway')).toBeChecked();
|
|
||||||
|
|
||||||
await expect(page.locator('.component-frame')).toHaveCount(0);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
page.waitForNavigation(),
|
|
||||||
page.locator('.wc-block-components-checkout-place-order-button').click(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function expectContinuation(page) {
|
|
||||||
await expect(page.locator('#payment_method_ppcp-gateway')).toBeChecked();
|
|
||||||
|
|
||||||
await expect(page.locator('.component-frame')).toHaveCount(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function completeContinuation(page) {
|
|
||||||
await expectContinuation(page);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation(),
|
|
||||||
page.locator('#place_order').click(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
test.describe('Classic checkout', () => {
|
|
||||||
test.beforeAll(async ({ browser }) => {
|
|
||||||
await serverExec('wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal button place order from Product page', async ({page}) => {
|
|
||||||
await page.goto(PRODUCT_URL);
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page);
|
|
||||||
|
|
||||||
await loginIntoPaypal(popup);
|
|
||||||
|
|
||||||
await completePaypalPayment(popup);
|
|
||||||
|
|
||||||
await fillCheckoutForm(page);
|
|
||||||
|
|
||||||
await completeContinuation(page);
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Advanced Credit and Debit Card place order from Checkout page', async ({page}) => {
|
|
||||||
await page.goto(PRODUCT_URL);
|
|
||||||
await page.locator('.single_add_to_cart_button').click();
|
|
||||||
|
|
||||||
await page.goto(CHECKOUT_URL);
|
|
||||||
await fillCheckoutForm(page);
|
|
||||||
|
|
||||||
await page.click("text=Credit Cards");
|
|
||||||
|
|
||||||
const creditCardNumber = page.frameLocator('#braintree-hosted-field-number').locator('#credit-card-number');
|
|
||||||
await creditCardNumber.fill(CREDIT_CARD_NUMBER);
|
|
||||||
|
|
||||||
const expirationDate = page.frameLocator('#braintree-hosted-field-expirationDate').locator('#expiration');
|
|
||||||
await expirationDate.fill(CREDIT_CARD_EXPIRATION);
|
|
||||||
|
|
||||||
const cvv = page.frameLocator('#braintree-hosted-field-cvv').locator('#cvv');
|
|
||||||
await cvv.fill(CREDIT_CARD_CVV);
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
page.waitForNavigation(),
|
|
||||||
page.locator('.ppcp-dcc-order-button').click(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal APM button place order', async ({page}) => {
|
|
||||||
await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID);
|
|
||||||
|
|
||||||
await page.goto(CHECKOUT_URL);
|
|
||||||
|
|
||||||
await fillCheckoutForm(page);
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page, {fundingSource: APM_ID});
|
|
||||||
|
|
||||||
await popup.getByText('Continue', { exact: true }).click();
|
|
||||||
await completePaypalPayment(popup, {selector: '[name="Successful"]'});
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal APM button place order when redirect fails', async ({page}) => {
|
|
||||||
await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID);
|
|
||||||
|
|
||||||
await page.goto(CHECKOUT_URL);
|
|
||||||
|
|
||||||
await fillCheckoutForm(page);
|
|
||||||
|
|
||||||
await page.evaluate('PayPalCommerceGateway.ajax.approve_order = null');
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page, {fundingSource: APM_ID});
|
|
||||||
|
|
||||||
await popup.getByText('Continue', { exact: true }).click();
|
|
||||||
await completePaypalPayment(popup, {selector: '[name="Successful"]'});
|
|
||||||
|
|
||||||
await expect(page.locator('.woocommerce-error')).toBeVisible();
|
|
||||||
|
|
||||||
await page.reload();
|
|
||||||
await expectContinuation(page);
|
|
||||||
|
|
||||||
await acceptTerms(page);
|
|
||||||
|
|
||||||
await completeContinuation(page);
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe('Block checkout', () => {
|
|
||||||
test.beforeAll(async ({browser}) => {
|
|
||||||
await serverExec('wp option update woocommerce_checkout_page_id ' + BLOCK_CHECKOUT_PAGE_ID);
|
|
||||||
await serverExec('wp pcp settings update blocks_final_review_enabled true');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal express block checkout', async ({page}) => {
|
|
||||||
await page.goto('?add-to-cart=' + PRODUCT_ID);
|
|
||||||
|
|
||||||
await page.goto(BLOCK_CHECKOUT_URL)
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page);
|
|
||||||
|
|
||||||
await loginIntoPaypal(popup);
|
|
||||||
|
|
||||||
await completePaypalPayment(popup);
|
|
||||||
|
|
||||||
await completeBlockContinuation(page);
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal express block cart', async ({page}) => {
|
|
||||||
await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID)
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page);
|
|
||||||
|
|
||||||
await loginIntoPaypal(popup);
|
|
||||||
|
|
||||||
await completePaypalPayment(popup);
|
|
||||||
|
|
||||||
await completeBlockContinuation(page);
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test.describe('Without review', () => {
|
|
||||||
test.beforeAll(async ({browser}) => {
|
|
||||||
await serverExec('wp pcp settings update blocks_final_review_enabled false');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal express block checkout', async ({page}) => {
|
|
||||||
await page.goto('?add-to-cart=' + PRODUCT_ID);
|
|
||||||
|
|
||||||
await page.goto(BLOCK_CHECKOUT_URL)
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page);
|
|
||||||
|
|
||||||
await loginIntoPaypal(popup);
|
|
||||||
|
|
||||||
await waitForPaypalShippingList(popup);
|
|
||||||
|
|
||||||
await completePaypalPayment(popup);
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal express block cart', async ({page}) => {
|
|
||||||
await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID)
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page);
|
|
||||||
|
|
||||||
await loginIntoPaypal(popup);
|
|
||||||
|
|
||||||
await waitForPaypalShippingList(popup);
|
|
||||||
|
|
||||||
await completePaypalPayment(popup);
|
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,89 +1,84 @@
|
||||||
const {test, expect} = require('@playwright/test');
|
const { test, expect } = require( '@playwright/test' );
|
||||||
const {loginAsCustomer} = require("./utils/user");
|
const { loginAsCustomer } = require( './utils/user' );
|
||||||
const {openPaypalPopup, loginIntoPaypal, completePaypalPayment} = require("./utils/paypal-popup");
|
|
||||||
const {fillCheckoutForm, expectOrderReceivedPage} = require("./utils/checkout");
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
PRODUCT_URL,
|
openPaypalPopup,
|
||||||
} = process.env;
|
loginIntoPaypal,
|
||||||
|
completePaypalPayment,
|
||||||
|
} = require( './utils/paypal-popup' );
|
||||||
|
const {
|
||||||
|
fillCheckoutForm,
|
||||||
|
expectOrderReceivedPage,
|
||||||
|
} = require( './utils/checkout' );
|
||||||
|
|
||||||
async function expectContinuation(page) {
|
const { PRODUCT_URL } = process.env;
|
||||||
await expect(page.locator('#payment_method_ppcp-gateway')).toBeChecked();
|
|
||||||
|
|
||||||
await expect(page.locator('.component-frame')).toHaveCount(0);
|
async function expectContinuation( page ) {
|
||||||
|
await expect(
|
||||||
|
page.locator( '#payment_method_ppcp-gateway' )
|
||||||
|
).toBeChecked();
|
||||||
|
|
||||||
|
await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
async function completeContinuation(page) {
|
async function completeContinuation( page ) {
|
||||||
await expectContinuation(page);
|
await expectContinuation( page );
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all( [
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
page.locator('#place_order').click(),
|
page.locator( '#place_order' ).click(),
|
||||||
]);
|
] );
|
||||||
}
|
}
|
||||||
|
|
||||||
test('Save during purchase', async ({page}) => {
|
// preconditions: shipping callback disabled and no saved payments
|
||||||
await loginAsCustomer(page)
|
test( 'Save during purchase', async ( { page } ) => {
|
||||||
|
await loginAsCustomer( page );
|
||||||
|
|
||||||
await page.goto(PRODUCT_URL);
|
await page.goto( PRODUCT_URL );
|
||||||
const popup = await openPaypalPopup(page);
|
const popup = await openPaypalPopup( page );
|
||||||
|
|
||||||
await loginIntoPaypal(popup);
|
await loginIntoPaypal( popup );
|
||||||
await completePaypalPayment(popup);
|
await completePaypalPayment( popup );
|
||||||
await fillCheckoutForm(page);
|
await fillCheckoutForm( page );
|
||||||
|
|
||||||
await completeContinuation(page);
|
await completeContinuation( page );
|
||||||
|
|
||||||
await expectOrderReceivedPage(page);
|
await expectOrderReceivedPage( page );
|
||||||
});
|
} );
|
||||||
|
|
||||||
test('PayPal add payment method', async ({page}) => {
|
test( 'PayPal add payment method', async ( { page } ) => {
|
||||||
await loginAsCustomer(page);
|
await loginAsCustomer( page );
|
||||||
await page.goto('/my-account/add-payment-method');
|
await page.goto( '/my-account/add-payment-method' );
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page);
|
const popup = await openPaypalPopup( page );
|
||||||
await loginIntoPaypal(popup);
|
await loginIntoPaypal( popup );
|
||||||
popup.locator('#consentButton').click();
|
popup.locator( '#consentButton' ).click();
|
||||||
|
|
||||||
await page.waitForURL('/my-account/payment-methods');
|
await page.waitForURL( '/my-account/payment-methods' );
|
||||||
});
|
} );
|
||||||
|
|
||||||
test('ACDC add payment method', async ({page}) => {
|
test( 'ACDC add payment method', async ( { page } ) => {
|
||||||
await loginAsCustomer(page);
|
await loginAsCustomer( page );
|
||||||
await page.goto('/my-account/add-payment-method');
|
await page.goto( '/my-account/add-payment-method' );
|
||||||
|
|
||||||
await page.click("text=Debit & Credit Cards");
|
await page.click( 'text=Debit & Credit Cards' );
|
||||||
|
|
||||||
const creditCardNumber = await page.frameLocator('[title="paypal_card_number_field"]').locator('.card-field-number');
|
|
||||||
await creditCardNumber.fill('4005519200000004');
|
|
||||||
|
|
||||||
const expirationDate = await page.frameLocator('[title="paypal_card_expiry_field"]').locator('.card-field-expiry');
|
|
||||||
await expirationDate.fill('01/25');
|
|
||||||
|
|
||||||
const cvv = await page.frameLocator('[title="paypal_card_cvv_field"]').locator('.card-field-cvv');
|
|
||||||
await cvv.fill('123');
|
|
||||||
|
|
||||||
await page.waitForURL('/my-account/payment-methods');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('PayPal logged-in user free trial subscription without payment token', async ({page}) => {
|
|
||||||
await loginAsCustomer(page);
|
|
||||||
|
|
||||||
await page.goto('/shop');
|
|
||||||
await page.click("text=Sign up now");
|
|
||||||
await page.goto('/classic-checkout');
|
|
||||||
|
|
||||||
const popup = await openPaypalPopup(page);
|
|
||||||
await loginIntoPaypal(popup);
|
|
||||||
popup.locator('#consentButton').click();
|
|
||||||
|
|
||||||
await page.click("text=Proceed to PayPal");
|
|
||||||
|
|
||||||
const title = await page.locator('.entry-title');
|
|
||||||
await expect(title).toHaveText('Order received');
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const creditCardNumber = await page
|
||||||
|
.frameLocator( '[title="paypal_card_number_field"]' )
|
||||||
|
.locator( '.card-field-number' );
|
||||||
|
await creditCardNumber.fill( '4005519200000004' );
|
||||||
|
|
||||||
|
const expirationDate = await page
|
||||||
|
.frameLocator( 'iframe[title="paypal_card_expiry_field"]' )
|
||||||
|
.locator( 'input.card-field-expiry' );
|
||||||
|
await expirationDate.click();
|
||||||
|
await page.keyboard.type( '12/25' );
|
||||||
|
|
||||||
|
const cvv = await page
|
||||||
|
.frameLocator( '[title="paypal_card_cvv_field"]' )
|
||||||
|
.locator( '.card-field-cvv' );
|
||||||
|
await cvv.fill( '123' );
|
||||||
|
|
||||||
|
await page.getByRole( 'button', { name: 'Add payment method' } ).click();
|
||||||
|
|
||||||
|
await page.waitForURL( '/my-account/payment-methods' );
|
||||||
|
} );
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue