';
+ return $insert_after(
+ $fields,
+ 'allow_card_button_gateway',
+ array(
+ 'googlepay_button_enabled' => array(
+ 'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'class' => array( 'ppcp-grayed-out-text' ),
+ 'input_class' => array( 'ppcp-disabled-checkbox' ),
+ 'label' => __( 'Enable Google Pay button', 'woocommerce-paypal-payments' )
+ . ''
+ . sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Your PayPal account %1$srequires additional permissions%2$s to enable Google Pay.', 'woocommerce-paypal-payments' ),
+ $connection_link,
+ ''
+ )
+ . '
',
+ 'default' => 'yes',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ 'custom_attributes' => array(
+ 'data-ppcp-display' => wp_json_encode(
+ array(
+ $display_manager
+ ->rule()
+ ->condition_is_true( false )
+ ->action_enable( 'googlepay_button_enabled' )
+ ->to_array(),
+ )
+ ),
+ ),
+ ),
+ )
+ );
+ }
+
+ // Standard Payments tab fields.
return $insert_after(
$fields,
'allow_card_button_gateway',
diff --git a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js
index 73f4785c9..1074c94ec 100644
--- a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js
+++ b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js
@@ -10,6 +10,10 @@ class BaseHandler {
this.externalHandler = externalHandler;
}
+ shippingAllowed() {
+ return true;
+ }
+
transactionInfo() {
return new Promise((resolve, reject) => {
diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js
index 9295c4302..3d24df9aa 100644
--- a/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js
+++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js
@@ -2,6 +2,10 @@ import BaseHandler from "./BaseHandler";
class CheckoutBlockHandler extends BaseHandler{
+ shippingAllowed() {
+ return false;
+ }
+
createOrder() {
return this.externalHandler.createOrder();
}
diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js
index ed8323a60..3f773a875 100644
--- a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js
+++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js
@@ -6,6 +6,10 @@ import FormValidator from "../../../../ppcp-button/resources/js/modules/Helper/F
class CheckoutHandler extends BaseHandler {
+ shippingAllowed() {
+ return false;
+ }
+
transactionInfo() {
return new Promise(async (resolve, reject) => {
diff --git a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js
index 4d2db4260..8c6bc261d 100644
--- a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js
+++ b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js
@@ -4,6 +4,8 @@ import CheckoutHandler from "./CheckoutHandler";
import CartBlockHandler from "./CartBlockHandler";
import CheckoutBlockHandler from "./CheckoutBlockHandler";
import MiniCartHandler from "./MiniCartHandler";
+import PayNowHandler from "./PayNowHandler";
+import PreviewHandler from "./PreviewHandler";
class ContextHandlerFactory {
@@ -14,14 +16,17 @@ class ContextHandlerFactory {
case 'cart':
return new CartHandler(buttonConfig, ppcpConfig, externalActionHandler);
case 'checkout':
- case 'pay-now': // same as checkout
return new CheckoutHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'pay-now':
+ return new PayNowHandler(buttonConfig, ppcpConfig, externalActionHandler);
case 'mini-cart':
return new MiniCartHandler(buttonConfig, ppcpConfig, externalActionHandler);
case 'cart-block':
return new CartBlockHandler(buttonConfig, ppcpConfig, externalActionHandler);
case 'checkout-block':
return new CheckoutBlockHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'preview':
+ return new PreviewHandler(buttonConfig, ppcpConfig, externalActionHandler);
}
}
}
diff --git a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js
new file mode 100644
index 000000000..add275608
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js
@@ -0,0 +1,35 @@
+import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner";
+import BaseHandler from "./BaseHandler";
+import CheckoutActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
+
+class PayNowHandler extends BaseHandler {
+
+ shippingAllowed() {
+ return false;
+ }
+
+ transactionInfo() {
+ return new Promise(async (resolve, reject) => {
+ const data = this.ppcpConfig['pay_now'];
+
+ resolve({
+ countryCode: data.country_code,
+ currencyCode: data.currency_code,
+ totalPriceStatus: 'FINAL',
+ totalPrice: data.total_str
+ });
+ });
+ }
+
+ actionHandler() {
+ return new CheckoutActionHandler(
+ this.ppcpConfig,
+ this.errorHandler(),
+ new Spinner()
+ );
+ }
+
+}
+
+export default PayNowHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js
new file mode 100644
index 000000000..a637f078d
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js
@@ -0,0 +1,31 @@
+import BaseHandler from "./BaseHandler";
+
+class CartHandler extends BaseHandler {
+
+ constructor(buttonConfig, ppcpConfig, externalHandler) {
+ super(buttonConfig, ppcpConfig, externalHandler);
+ }
+
+ transactionInfo() {
+ throw new Error('Transaction info fail. This is just a preview.');
+ }
+
+ createOrder() {
+ throw new Error('Create order fail. This is just a preview.');
+ }
+
+ approveOrder(data, actions) {
+ throw new Error('Approve order fail. This is just a preview.');
+ }
+
+ actionHandler() {
+ throw new Error('Action handler fail. This is just a preview.');
+ }
+
+ errorHandler() {
+ throw new Error('Error handler fail. This is just a preview.');
+ }
+
+}
+
+export default CartHandler;
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
index 6cd97302f..57d29a75d 100644
--- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js
+++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
@@ -1,6 +1,8 @@
import ContextHandlerFactory from "./Context/ContextHandlerFactory";
import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
+import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
+import UpdatePaymentData from "./Helper/UpdatePaymentData";
class GooglepayButton {
@@ -21,7 +23,11 @@ class GooglepayButton {
this.externalHandler
);
- console.log('[GooglePayButton] new Button', this);
+ this.log = function() {
+ if ( this.buttonConfig.is_debug ) {
+ console.log('[GooglePayButton]', ...arguments);
+ }
+ }
}
init(config) {
@@ -54,6 +60,15 @@ class GooglepayButton {
});
}
+ reinit() {
+ if (!this.googlePayConfig) {
+ return;
+ }
+
+ this.isInitialized = false;
+ this.init(this.googlePayConfig);
+ }
+
validateConfig() {
if ( ['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) {
console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment);
@@ -99,13 +114,18 @@ class GooglepayButton {
}
initClient() {
+ const callbacks = {
+ onPaymentAuthorized: this.onPaymentAuthorized.bind(this)
+ }
+
+ if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) {
+ callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this);
+ }
+
this.paymentsClient = new google.payments.api.PaymentsClient({
environment: this.buttonConfig.environment,
// add merchant info maybe
- paymentDataCallbacks: {
- //onPaymentDataChanged: onPaymentDataChanged,
- onPaymentAuthorized: this.onPaymentAuthorized.bind(this),
- }
+ paymentDataCallbacks: callbacks
});
}
@@ -137,22 +157,41 @@ class GooglepayButton {
* Add a Google Pay purchase button
*/
addButton(baseCardPaymentMethod) {
- console.log('[GooglePayButton] addButton', this.context);
+ this.log('addButton', this.context);
const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig();
- jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape);
+ this.waitForWrapper(wrapper, () => {
+ jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape);
- 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);
+ 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);
+ });
+ }
+
+ 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);
+ console.error('Waiting for wrapper timed out.', selector);
+ }
+ }, delay);
}
//------------------------
@@ -163,10 +202,10 @@ class GooglepayButton {
* Show Google Pay payment sheet when Google Pay payment button is clicked
*/
async onButtonClick() {
- console.log('[GooglePayButton] onButtonClick', this.context);
+ this.log('onButtonClick', this.context);
const paymentDataRequest = await this.paymentDataRequest();
- console.log('[GooglePayButton] onButtonClick: paymentDataRequest', paymentDataRequest, this.context);
+ this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context);
window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler.
@@ -184,35 +223,111 @@ class GooglepayButton {
paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods;
paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo();
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
- paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION'];
+
+ if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) {
+ paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"];
+ paymentDataRequest.shippingAddressRequired = true;
+ paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters();
+ paymentDataRequest.shippingOptionRequired = true;
+ } else {
+ paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION'];
+ }
+
return paymentDataRequest;
}
+ //------------------------
+ // Shipping processing
+ //------------------------
+
+ shippingAddressParameters() {
+ return {
+ allowedCountryCodes: this.buttonConfig.shipping.countries,
+ phoneNumberRequired: true
+ };
+ }
+
+ onPaymentDataChanged(paymentData) {
+ this.log('onPaymentDataChanged', this.context);
+ this.log('paymentData', paymentData);
+
+ return new Promise(async (resolve, reject) => {
+ let paymentDataRequestUpdate = {};
+
+ const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData);
+ const transactionInfo = await this.contextHandler.transactionInfo();
+
+ this.log('onPaymentDataChanged:updatedData', updatedData);
+ this.log('onPaymentDataChanged:transactionInfo', transactionInfo);
+
+ updatedData.country_code = transactionInfo.countryCode;
+ updatedData.currency_code = transactionInfo.currencyCode;
+ updatedData.total_str = transactionInfo.totalPrice;
+
+ // Handle unserviceable address.
+ if(!updatedData.shipping_options || !updatedData.shipping_options.shippingOptions.length) {
+ paymentDataRequestUpdate.error = this.unserviceableShippingAddressError();
+ resolve(paymentDataRequestUpdate);
+ return;
+ }
+
+ switch (paymentData.callbackTrigger) {
+ case 'INITIALIZE':
+ case 'SHIPPING_ADDRESS':
+ paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options;
+ paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData);
+ break;
+ case 'SHIPPING_OPTION':
+ paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData);
+ break;
+ }
+
+ resolve(paymentDataRequestUpdate);
+ });
+ }
+
+ unserviceableShippingAddressError() {
+ return {
+ reason: "SHIPPING_ADDRESS_UNSERVICEABLE",
+ message: "Cannot ship to the selected address",
+ intent: "SHIPPING_ADDRESS"
+ };
+ }
+
+ calculateNewTransactionInfo(updatedData) {
+ return {
+ countryCode: updatedData.country_code,
+ currencyCode: updatedData.currency_code,
+ totalPriceStatus: 'FINAL',
+ totalPrice: updatedData.total_str
+ };
+ }
+
//------------------------
// Payment process
//------------------------
onPaymentAuthorized(paymentData) {
- console.log('[GooglePayButton] onPaymentAuthorized', this.context);
+ this.log('onPaymentAuthorized', this.context);
return this.processPayment(paymentData);
}
async processPayment(paymentData) {
- console.log('[GooglePayButton] processPayment', this.context);
+ this.log('processPayment', this.context);
return new Promise(async (resolve, reject) => {
try {
let id = await this.contextHandler.createOrder();
- console.log('[GooglePayButton] processPayment: createOrder', id, this.context);
+ this.log('processPayment: createOrder', id, this.context);
- const confirmOrderResponse = await paypal.Googlepay().confirmOrder({
+ const confirmOrderResponse = await widgetBuilder.paypal.Googlepay().confirmOrder({
orderId: id,
paymentMethodData: paymentData.paymentMethodData
});
- console.log('[GooglePayButton] processPayment: confirmOrder', confirmOrderResponse, this.context);
+ this.log('processPayment: confirmOrder', confirmOrderResponse, this.context);
/** Capture the Order on the Server */
if (confirmOrderResponse.status === "APPROVED") {
@@ -259,7 +374,7 @@ class GooglepayButton {
}
}
- console.log('[GooglePayButton] processPaymentResponse', response, this.context);
+ this.log('processPaymentResponse', response, this.context);
return response;
}
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js
index 95aac2c2d..72475cfe5 100644
--- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js
+++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js
@@ -38,6 +38,12 @@ class GooglepayManager {
})();
}
+ reinit() {
+ for (const button of this.buttons) {
+ button.reinit();
+ }
+ }
+
}
export default GooglepayManager;
diff --git a/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js
new file mode 100644
index 000000000..3d56d9316
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js
@@ -0,0 +1,38 @@
+
+class UpdatePaymentData {
+
+ constructor(config) {
+ this.config = config;
+ }
+
+ update(paymentData) {
+ return new Promise((resolve, reject) => {
+ fetch(
+ this.config.endpoint,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.config.nonce,
+ paymentData: paymentData,
+ })
+
+ }
+ )
+ .then(result => result.json())
+ .then(result => {
+ if (!result.success) {
+ return;
+ }
+
+ resolve(result.data);
+ });
+ });
+ }
+
+}
+
+export default UpdatePaymentData;
diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js
new file mode 100644
index 000000000..577733b6b
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/boot-admin.js
@@ -0,0 +1,146 @@
+import {loadCustomScript} from "@paypal/paypal-js";
+import GooglepayButton from "./GooglepayButton";
+import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
+
+(function ({
+ buttonConfig,
+ jQuery
+}) {
+
+ let googlePayConfig;
+ let buttonQueue = [];
+ let activeButtons = {};
+ let bootstrapped = false;
+
+ // React to PayPal config changes.
+ jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => {
+ if (bootstrapped) {
+ createButton(ppcpConfig);
+ } else {
+ buttonQueue.push({
+ ppcpConfig: JSON.parse(JSON.stringify(ppcpConfig))
+ });
+ }
+ });
+
+ // React to GooglePay config changes.
+ jQuery([
+ '#ppcp-googlepay_button_enabled',
+ '#ppcp-googlepay_button_type',
+ '#ppcp-googlepay_button_color',
+ '#ppcp-googlepay_button_language',
+ '#ppcp-googlepay_button_shipping_enabled'
+ ].join(',')).on('change', () => {
+ for (const [selector, ppcpConfig] of Object.entries(activeButtons)) {
+ createButton(ppcpConfig);
+ }
+ });
+
+ // Maybe we can find a more elegant reload method when transitioning from styling modes.
+ jQuery([
+ '#ppcp-smart_button_enable_styling_per_location'
+ ].join(',')).on('change', () => {
+ setTimeout(() => {
+ for (const [selector, ppcpConfig] of Object.entries(activeButtons)) {
+ createButton(ppcpConfig);
+ }
+ }, 100);
+ });
+
+ const applyConfigOptions = function (buttonConfig) {
+ buttonConfig.button = buttonConfig.button || {};
+ buttonConfig.button.style = buttonConfig.button.style || {};
+ buttonConfig.button.style.type = jQuery('#ppcp-googlepay_button_type').val();
+ buttonConfig.button.style.color = jQuery('#ppcp-googlepay_button_color').val();
+ buttonConfig.button.style.language = jQuery('#ppcp-googlepay_button_language').val();
+ }
+
+ const createButton = function (ppcpConfig) {
+ const selector = ppcpConfig.button.wrapper + 'GooglePay';
+
+ if (!jQuery('#ppcp-googlepay_button_enabled').is(':checked')) {
+ jQuery(selector).remove();
+ return;
+ }
+
+ buttonConfig = JSON.parse(JSON.stringify(buttonConfig));
+ buttonConfig.button.wrapper = selector;
+ applyConfigOptions(buttonConfig);
+
+ const wrapperElement = ``;
+
+ if (!jQuery(selector).length) {
+ jQuery(ppcpConfig.button.wrapper).after(wrapperElement);
+ } else {
+ jQuery(selector).replaceWith(wrapperElement);
+ }
+
+ const button = new GooglepayButton(
+ 'preview',
+ null,
+ buttonConfig,
+ ppcpConfig,
+ );
+
+ button.init(googlePayConfig);
+
+ activeButtons[selector] = ppcpConfig;
+ }
+
+ const bootstrap = async function () {
+ if (!widgetBuilder.paypal) {
+ return;
+ }
+
+ googlePayConfig = await widgetBuilder.paypal.Googlepay().config();
+
+ // We need to set bootstrapped here otherwise googlePayConfig may not be set.
+ bootstrapped = true;
+
+ let options;
+ while (options = buttonQueue.pop()) {
+ createButton(options.ppcpConfig);
+ }
+ };
+
+ document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+
+ if (typeof (buttonConfig) === 'undefined') {
+ console.error('PayPal button could not be configured.');
+ return;
+ }
+
+ let paypalLoaded = false;
+ let googlePayLoaded = false;
+
+ const tryToBoot = () => {
+ if (!bootstrapped && paypalLoaded && googlePayLoaded) {
+ bootstrap();
+ }
+ }
+
+ // Load GooglePay SDK
+ loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
+ googlePayLoaded = true;
+ tryToBoot();
+ });
+
+ // Wait for PayPal to be loaded externally
+ if (typeof widgetBuilder.paypal !== 'undefined') {
+ paypalLoaded = true;
+ tryToBoot();
+ }
+
+ jQuery(document).on('ppcp-paypal-loaded', () => {
+ paypalLoaded = true;
+ tryToBoot();
+ });
+ },
+ );
+
+})({
+ buttonConfig: window.wc_ppcp_googlepay_admin,
+ jQuery: window.jQuery
+});
diff --git a/modules/ppcp-googlepay/resources/js/boot.js b/modules/ppcp-googlepay/resources/js/boot.js
index e6390c8dd..387822e67 100644
--- a/modules/ppcp-googlepay/resources/js/boot.js
+++ b/modules/ppcp-googlepay/resources/js/boot.js
@@ -8,11 +8,17 @@ import GooglepayManager from "./GooglepayManager";
jQuery
}) {
+ let manager;
+
const bootstrap = function () {
- const manager = new GooglepayManager(buttonConfig, ppcpConfig);
+ manager = new GooglepayManager(buttonConfig, ppcpConfig);
manager.init();
};
+ jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
+ manager.reinit();
+ });
+
document.addEventListener(
'DOMContentLoaded',
() => {
@@ -20,12 +26,7 @@ import GooglepayManager from "./GooglepayManager";
(typeof (buttonConfig) === 'undefined') ||
(typeof (ppcpConfig) === 'undefined')
) {
- console.error('PayPal button could not be configured.');
- return;
- }
-
- // If button wrapper is not present then there is no need to load the scripts.
- if (!jQuery(buttonConfig.button.wrapper).length) {
+ // No PayPal buttons present on this page.
return;
}
diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php
index a7944a952..02a9ccc9d 100644
--- a/modules/ppcp-googlepay/services.php
+++ b/modules/ppcp-googlepay/services.php
@@ -11,10 +11,15 @@ namespace WooCommerce\PayPalCommerce\Googlepay;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
+use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Googlepay\Assets\BlocksPaymentMethod;
use WooCommerce\PayPalCommerce\Googlepay\Assets\Button;
+use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
+use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice;
+use WooCommerce\PayPalCommerce\Onboarding\Environment;
+use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
@@ -35,9 +40,9 @@ return array(
);
},
- // If GooglePay is configured.
+ // If GooglePay is configured and onboarded.
'googlepay.available' => static function ( ContainerInterface $container ): bool {
- if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', false ) ) {
+ if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', true ) ) {
$status = $container->get( 'googlepay.helpers.apm-product-status' );
assert( $status instanceof ApmProductStatus );
/**
@@ -48,14 +53,33 @@ return array(
return true;
},
- 'googlepay.helpers.apm-product-status' => static function( ContainerInterface $container ): ApmProductStatus {
- return new ApmProductStatus(
- $container->get( 'wcgateway.settings' ),
- $container->get( 'api.endpoint.partners' ),
- $container->get( 'onboarding.state' )
+ // We assume it's a referral if we can check product status without API request failures.
+ 'googlepay.is_referral' => static function ( ContainerInterface $container ): bool {
+ $status = $container->get( 'googlepay.helpers.apm-product-status' );
+ assert( $status instanceof ApmProductStatus );
+
+ return ! $status->has_request_failure();
+ },
+
+ 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice {
+ return new AvailabilityNotice(
+ $container->get( 'googlepay.helpers.apm-product-status' ),
+ $container->get( 'wcgateway.is-wc-gateways-list-page' ),
+ $container->get( 'wcgateway.is-ppcp-settings-page' )
);
},
+ 'googlepay.helpers.apm-product-status' => SingletonDecorator::make(
+ static function( ContainerInterface $container ): ApmProductStatus {
+ return new ApmProductStatus(
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'api.endpoint.partners' ),
+ $container->get( 'onboarding.state' ),
+ $container->get( 'api.helper.failure-registry' )
+ );
+ }
+ ),
+
/**
* The matrix which countries and currency combinations can be used for GooglePay.
*/
@@ -153,4 +177,58 @@ return array(
return 'https://pay.google.com/gp/p/js/pay.js';
},
+ 'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint {
+ return new UpdatePaymentDataEndpoint(
+ $container->get( 'button.request-data' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+
+ 'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string {
+ return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY';
+ },
+
+ 'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string {
+ return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY';
+ },
+
+ 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string {
+ $state = $container->get( 'onboarding.state' );
+ if ( $state->current_state() < State::STATE_ONBOARDED ) {
+ return '';
+ }
+
+ $product_status = $container->get( 'googlepay.helpers.apm-product-status' );
+ assert( $product_status instanceof ApmProductStatus );
+
+ $environment = $container->get( 'onboarding.environment' );
+ assert( $environment instanceof Environment );
+
+ $enabled = $product_status->is_active();
+
+ $enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
+ $disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
+
+ $button_text = $enabled
+ ? esc_html__( 'Settings', 'woocommerce-paypal-payments' )
+ : esc_html__( 'Enable Google Pay', 'woocommerce-paypal-payments' );
+
+ $enable_url = $environment->current_environment_is( Environment::PRODUCTION )
+ ? $container->get( 'googlepay.enable-url-live' )
+ : $container->get( 'googlepay.enable-url-sandbox' );
+
+ $button_url = $enabled
+ ? admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway#field-alternative_payment_methods' )
+ : $enable_url;
+
+ return sprintf(
+ '%1$s %2$s
%5$s
',
+ $enabled ? $enabled_status_text : $disabled_status_text,
+ $enabled ? '' : '',
+ $enabled ? '_self' : '_blank',
+ esc_url( $button_url ),
+ esc_html( $button_text )
+ );
+ },
+
);
diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php
index 48848af56..b4ad9c297 100644
--- a/modules/ppcp-googlepay/src/Assets/Button.php
+++ b/modules/ppcp-googlepay/src/Assets/Button.php
@@ -11,7 +11,9 @@ namespace WooCommerce\PayPalCommerce\Googlepay\Assets;
use Exception;
use Psr\Log\LoggerInterface;
+use WC_Countries;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
+use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
@@ -127,6 +129,7 @@ class Button implements ButtonInterface {
*/
public function initialize(): void {
add_filter( 'ppcp_onboarding_options', array( $this, 'add_onboarding_options' ), 10, 1 );
+ add_filter( 'ppcp_partner_referrals_option', array( $this, 'filter_partner_referrals_option' ), 10, 1 );
add_filter( 'ppcp_partner_referrals_data', array( $this, 'add_partner_referrals_data' ), 10, 1 );
}
@@ -139,6 +142,10 @@ class Button implements ButtonInterface {
* @psalm-suppress MissingClosureParamType
*/
public function add_onboarding_options( $options ): string {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_google_pay_onboarding_option', false ) ) {
+ return $options;
+ }
+
$checked = '';
try {
$onboard_with_google = $this->settings->get( 'ppcp-onboarding-google' );
@@ -150,11 +157,28 @@ class Button implements ButtonInterface {
}
return $options
- . '