Merge branch 'PCP-1744-google-pay-integration' into wip/apple-google-combined

# Conflicts:
#	modules.php
This commit is contained in:
Pedro Silva 2023-09-11 10:50:34 +01:00
commit 2e7f9b46b0
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
36 changed files with 4240 additions and 7 deletions

View file

@ -35,5 +35,13 @@ return function ( string $root_dir ): iterable {
$modules[] = ( require "$modules_dir/ppcp-applepay/module.php" )(); $modules[] = ( require "$modules_dir/ppcp-applepay/module.php" )();
} }
if ( apply_filters(
//phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.googlepay_enabled',
getenv( 'PCP_GOOGLEPAY_ENABLED' ) === '1'
) ) {
$modules[] = ( require "$modules_dir/ppcp-googlepay/module.php" )();
}
return $modules; return $modules;
}; };

View file

@ -109,12 +109,14 @@ const PayPalComponent = ({
setPaypalOrder(order); setPaypalOrder(order);
if (config.finalReviewEnabled) { if (config.finalReviewEnabled) {
const addresses = paypalOrderToWcAddresses(order); if (order) {
const addresses = paypalOrderToWcAddresses(order);
await wp.data.dispatch('wc/store/cart').updateCustomerData({ await wp.data.dispatch('wc/store/cart').updateCustomerData({
billing_address: addresses.billingAddress, billing_address: addresses.billingAddress,
shipping_address: addresses.shippingAddress, shipping_address: addresses.shippingAddress,
}); });
}
const checkoutUrl = new URL(config.scriptData.redirect); const checkoutUrl = new URL(config.scriptData.redirect);
// sometimes some browsers may load some kind of cached version of the page, // sometimes some browsers may load some kind of cached version of the page,
// so adding a parameter to avoid that // so adding a parameter to avoid that

View file

@ -0,0 +1,14 @@
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": "3.25.0"
}
],
[
"@babel/preset-react"
]
]
}

3
modules/ppcp-googlepay/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules
assets/js
assets/css

View file

@ -0,0 +1,17 @@
{
"name": "woocommerce/ppcp-googlepay",
"type": "dhii-mod",
"description": "Googlepay module for PPCP",
"license": "GPL-2.0",
"require": {
"php": "^7.2 | ^8.0",
"dhii/module-interface": "^0.3.0-alpha1"
},
"autoload": {
"psr-4": {
"WooCommerce\\PayPalCommerce\\Googlepay\\": "src"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View file

@ -0,0 +1,94 @@
<?php
/**
* The Googlepay module extensions.
*
* @package WooCommerce\PayPalCommerce\Googlepay
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay;
use WooCommerce\PayPalCommerce\Googlepay\Helper\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
$insert_after = function( array $array, string $key, array $new ): array {
$keys = array_keys( $array );
$index = array_search( $key, $keys, true );
$pos = false === $index ? count( $array ) : $index + 1;
return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
};
return $insert_after(
$fields,
'allow_card_button_gateway',
array(
'googlepay_button_enabled' => array(
'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'label' => __( 'Enable Google Pay button', 'woocommerce-paypal-payments' ),
'default' => 'yes',
'screens' => array( State::STATE_ONBOARDED ),
'gateway' => 'paypal',
'requirements' => array(),
'custom_attributes' => array(
'data-ppcp-handlers' => wp_json_encode(
array(
array(
'handler' => 'SubElementsHandler',
'options' => array(
'values' => array( '1' ),
'elements' => array(
'#field-googlepay_button_color',
'#field-googlepay_button_type',
'#field-googlepay_button_shipping_enabled',
),
),
),
)
),
),
),
'googlepay_button_color' => array(
'title' => str_repeat( '&nbsp;', 6 ) . __( 'Button Color', 'woocommerce-paypal-payments' ),
'type' => 'select',
'label' => '',
'input_class' => array( 'wc-enhanced-select' ),
'class' => array(),
'default' => 'black',
'options' => PropertiesDictionary::button_colors(),
'screens' => array( State::STATE_ONBOARDED ),
'gateway' => 'paypal',
'requirements' => array(),
),
'googlepay_button_type' => array(
'title' => str_repeat( '&nbsp;', 6 ) . __( 'Button Type', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
'default' => 'pay',
'options' => PropertiesDictionary::button_types(),
'screens' => array( State::STATE_ONBOARDED ),
'gateway' => 'paypal',
'requirements' => array(),
),
'googlepay_button_shipping_enabled' => array(
'title' => str_repeat( '&nbsp;', 6 ) . __( 'Shipping Callback', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'label' => __( 'Enable Google Pay shipping callback', 'woocommerce-paypal-payments' ),
'default' => 'no',
'screens' => array( State::STATE_ONBOARDED ),
'gateway' => 'paypal',
'requirements' => array(),
),
)
);
},
);

View file

@ -0,0 +1,16 @@
<?php
/**
* The Googlepay module.
*
* @package WooCommerce\PayPalCommerce\Googlepay
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
return static function (): ModuleInterface {
return new GooglepayModule();
};

View file

@ -0,0 +1,34 @@
{
"name": "ppcp-googlepay",
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"browserslist": [
"> 0.5%",
"Safari >= 8",
"Chrome >= 41",
"Firefox >= 43",
"Edge >= 14"
],
"dependencies": {
"@paypal/paypal-js": "^6.0.0",
"core-js": "^3.25.0"
},
"devDependencies": {
"@babel/core": "^7.19",
"@babel/preset-env": "^7.19",
"@babel/preset-react": "^7.18.6",
"@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
"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"
}
}

View file

@ -0,0 +1,46 @@
.ppcp-button-googlepay {
margin: 7px 0;
overflow: hidden;
min-height: 40px;
height: 45px;
&.ppcp-button-pill {
border-radius: 50px;
}
&.ppcp-button-minicart {
display: block;
height: 40px;
}
}
.woocommerce-checkout {
.ppcp-button-googlepay {
margin-top: 0;
}
}
.ppcp-has-googlepay-block {
.wp-block-woocommerce-checkout {
.ppcp-button-googlepay {
margin: 0;
height: 40px;
}
}
.wp-block-woocommerce-cart {
.ppcp-button-googlepay {
margin: 0;
height: 40px;
}
/* Workaround for blocks grid */
.wc-block-components-express-payment__event-buttons {
display: block;
li[id*="express-payment-method-ppcp-"] {
padding-bottom: 0;
}
}
}
}

View file

@ -0,0 +1,67 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import CartActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
class BaseHandler {
constructor(buttonConfig, ppcpConfig, externalHandler) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.externalHandler = externalHandler;
}
transactionInfo() {
return new Promise((resolve, reject) => {
fetch(
this.ppcpConfig.ajax.cart_script_params.endpoint,
{
method: 'GET',
credentials: 'same-origin',
}
)
.then(result => result.json())
.then(result => {
if (! result.success) {
return;
}
// handle script reload
const data = result.data;
resolve({
countryCode: data.country_code,
currencyCode: data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.total_str
});
});
});
}
createOrder() {
return this.actionHandler().configuration().createOrder(null, null);
}
approveOrder(data, actions) {
return this.actionHandler().configuration().onApprove(data, actions);
}
actionHandler() {
return new CartActionHandler(
this.ppcpConfig,
this.errorHandler(),
);
}
errorHandler() {
return new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
}
}
export default BaseHandler;

View file

@ -0,0 +1,15 @@
import BaseHandler from "./BaseHandler";
class CartBlockHandler extends BaseHandler {
createOrder() {
return this.externalHandler.createOrder();
}
approveOrder(data, actions) {
return this.externalHandler.onApprove(data, actions);
}
}
export default CartBlockHandler;

View file

@ -0,0 +1,7 @@
import BaseHandler from "./BaseHandler";
class CartHandler extends BaseHandler {
}
export default CartHandler;

View file

@ -0,0 +1,15 @@
import BaseHandler from "./BaseHandler";
class CheckoutBlockHandler extends BaseHandler{
createOrder() {
return this.externalHandler.createOrder();
}
approveOrder(data, actions) {
return this.externalHandler.onApprove(data, actions);
}
}
export default CheckoutBlockHandler;

View file

@ -0,0 +1,60 @@
import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner";
import BaseHandler from "./BaseHandler";
import CheckoutActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
import FormValidator from "../../../../ppcp-button/resources/js/modules/Helper/FormValidator";
class CheckoutHandler extends BaseHandler {
transactionInfo() {
return new Promise(async (resolve, reject) => {
try {
const spinner = new Spinner();
const errorHandler = this.errorHandler();
const formSelector = this.ppcpConfig.context === 'checkout' ? 'form.checkout' : 'form#order_review';
const formValidator = this.ppcpConfig.early_checkout_validation_enabled ?
new FormValidator(
this.ppcpConfig.ajax.validate_checkout.endpoint,
this.ppcpConfig.ajax.validate_checkout.nonce,
) : null;
if (!formValidator) {
resolve(super.transactionInfo());
return;
}
formValidator.validate(document.querySelector(formSelector)).then((errors) => {
if (errors.length > 0) {
spinner.unblock();
errorHandler.clear();
errorHandler.messages(errors);
// fire WC event for other plugins
jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
reject();
} else {
resolve(super.transactionInfo());
}
});
} catch (error) {
console.error(error);
reject();
}
});
}
actionHandler() {
return new CheckoutActionHandler(
this.ppcpConfig,
this.errorHandler(),
new Spinner()
);
}
}
export default CheckoutHandler;

View file

@ -0,0 +1,29 @@
import SingleProductHandler from "./SingleProductHandler";
import CartHandler from "./CartHandler";
import CheckoutHandler from "./CheckoutHandler";
import CartBlockHandler from "./CartBlockHandler";
import CheckoutBlockHandler from "./CheckoutBlockHandler";
import MiniCartHandler from "./MiniCartHandler";
class ContextHandlerFactory {
static create(context, buttonConfig, ppcpConfig, externalActionHandler) {
switch (context) {
case 'product':
return new SingleProductHandler(buttonConfig, ppcpConfig, externalActionHandler);
case 'cart':
return new CartHandler(buttonConfig, ppcpConfig, externalActionHandler);
case 'checkout':
case 'pay-now': // same as checkout
return new CheckoutHandler(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);
}
}
}
export default ContextHandlerFactory;

View file

@ -0,0 +1,7 @@
import BaseHandler from "./BaseHandler";
class MiniCartHandler extends BaseHandler {
}
export default MiniCartHandler;

View file

@ -0,0 +1,65 @@
import SingleProductActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler";
import SimulateCart from "../../../../ppcp-button/resources/js/modules/Helper/SimulateCart";
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import UpdateCart from "../../../../ppcp-button/resources/js/modules/Helper/UpdateCart";
import BaseHandler from "./BaseHandler";
class SingleProductHandler extends BaseHandler {
transactionInfo() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
function form() {
return document.querySelector('form.cart');
}
const actionHandler = new SingleProductActionHandler(
null,
null,
form(),
errorHandler,
);
const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
const products = hasSubscriptions
? actionHandler.getSubscriptionProducts()
: actionHandler.getProducts();
return new Promise((resolve, reject) => {
(new SimulateCart(
this.ppcpConfig.ajax.simulate_cart.endpoint,
this.ppcpConfig.ajax.simulate_cart.nonce,
)).simulate((data) => {
resolve({
countryCode: data.country_code,
currencyCode: data.currency_code,
totalPriceStatus: 'FINAL',
totalPrice: data.total_str
});
}, products);
});
}
actionHandler() {
return new SingleProductActionHandler(
this.ppcpConfig,
new UpdateCart(
this.ppcpConfig.ajax.change_cart.endpoint,
this.ppcpConfig.ajax.change_cart.nonce,
),
document.querySelector('form.cart'),
this.errorHandler(),
);
}
}
export default SingleProductHandler;

View file

@ -0,0 +1,268 @@
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';
class GooglepayButton {
constructor(context, externalHandler, buttonConfig, ppcpConfig) {
this.isInitialized = false;
this.context = context;
this.externalHandler = externalHandler;
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.paymentsClient = null;
this.contextHandler = ContextHandlerFactory.create(
this.context,
this.buttonConfig,
this.ppcpConfig,
this.externalHandler
);
console.log('[GooglePayButton] new Button', this);
}
init(config) {
if (this.isInitialized) {
return;
}
this.isInitialized = true;
if (!this.validateConfig()) {
return;
}
this.googlePayConfig = config;
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) {
this.addButton(this.baseCardPaymentMethod);
}
})
.catch(function(err) {
console.error(err);
});
}
validateConfig() {
if ( ['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) {
console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment);
return false;
}
if ( !this.contextHandler ) {
console.error('[GooglePayButton] Invalid context handler.', this.contextHandler);
return false;
}
return true;
}
/**
* Returns configurations relative to this button context.
*/
contextConfig() {
let config = {
wrapper: this.buttonConfig.button.wrapper,
ppcpStyle: this.ppcpConfig.button.style,
buttonStyle: this.buttonConfig.button.style,
ppcpButtonWrapper: this.ppcpConfig.button.wrapper
}
if (this.context === 'mini-cart') {
config.wrapper = this.buttonConfig.button.mini_cart_wrapper;
config.ppcpStyle = this.ppcpConfig.button.mini_cart_style;
config.buttonStyle = this.buttonConfig.button.mini_cart_style;
config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper;
// 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';
}
return config;
}
initClient() {
this.paymentsClient = new google.payments.api.PaymentsClient({
environment: this.buttonConfig.environment,
// add merchant info maybe
paymentDataCallbacks: {
//onPaymentDataChanged: onPaymentDataChanged,
onPaymentAuthorized: this.onPaymentAuthorized.bind(this),
}
});
}
initEventHandlers() {
const { wrapper, ppcpButtonWrapper } = this.contextConfig();
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) {
return Object.assign({}, baseRequest, {
allowedPaymentMethods: allowedPaymentMethods,
});
}
/**
* Add a Google Pay purchase button
*/
addButton(baseCardPaymentMethod) {
console.log('[GooglePayButton] addButton', this.context);
const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig();
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',
buttonSizeMode: 'fill',
});
jQuery(wrapper).append(button);
}
//------------------------
// Button click
//------------------------
/**
* Show Google Pay payment sheet when Google Pay payment button is clicked
*/
async onButtonClick() {
console.log('[GooglePayButton] onButtonClick', this.context);
const paymentDataRequest = await this.paymentDataRequest();
console.log('[GooglePayButton] onButtonClick: paymentDataRequest', paymentDataRequest, this.context);
window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler.
this.paymentsClient.loadPaymentData(paymentDataRequest);
}
async paymentDataRequest() {
let baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
}
const googlePayConfig = this.googlePayConfig;
const paymentDataRequest = Object.assign({}, baseRequest);
paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods;
paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo();
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION'];
return paymentDataRequest;
}
//------------------------
// Payment process
//------------------------
onPaymentAuthorized(paymentData) {
console.log('[GooglePayButton] onPaymentAuthorized', this.context);
return this.processPayment(paymentData);
}
async processPayment(paymentData) {
console.log('[GooglePayButton] processPayment', this.context);
return new Promise(async (resolve, reject) => {
try {
let id = await this.contextHandler.createOrder();
console.log('[GooglePayButton] processPayment: createOrder', id, this.context);
const confirmOrderResponse = await paypal.Googlepay().confirmOrder({
orderId: id,
paymentMethodData: paymentData.paymentMethodData
});
console.log('[GooglePayButton] processPayment: confirmOrder', confirmOrderResponse, this.context);
/** Capture the Order on the Server */
if (confirmOrderResponse.status === "APPROVED") {
let approveFailed = false;
await this.contextHandler.approveOrder({
orderID: id
}, { // actions mock object.
restart: () => new Promise((resolve, reject) => {
approveFailed = true;
resolve();
}),
order: {
get: () => new Promise((resolve, reject) => {
resolve(null);
})
}
});
if (!approveFailed) {
resolve(this.processPaymentResponse('SUCCESS'));
} else {
resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'FAILED TO APPROVE'));
}
} else {
resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'TRANSACTION FAILED'));
}
} catch(err) {
resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', err.message));
}
});
}
processPaymentResponse(state, intent = null, message = null) {
let response = {
transactionState: state,
}
if (intent || message) {
response.error = {
intent: intent,
message: message,
}
}
console.log('[GooglePayButton] processPaymentResponse', response, this.context);
return response;
}
}
export default GooglepayButton;

View file

@ -0,0 +1,43 @@
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
import GooglepayButton from "./GooglepayButton";
class GooglepayManager {
constructor(buttonConfig, ppcpConfig) {
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.googlePayConfig = null;
this.buttons = [];
buttonModuleWatcher.watchContextBootstrap((bootstrap) => {
const button = new GooglepayButton(
bootstrap.context,
bootstrap.handler,
buttonConfig,
ppcpConfig,
);
this.buttons.push(button);
if (this.googlePayConfig) {
button.init(this.googlePayConfig);
}
});
}
init() {
(async () => {
// Gets GooglePay configuration of the PayPal merchant.
this.googlePayConfig = await paypal.Googlepay().config();
for (const button of this.buttons) {
button.init(this.googlePayConfig);
}
})();
}
}
export default GooglepayManager;

View file

@ -0,0 +1,71 @@
import {useEffect, useState} from '@wordpress/element';
import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
import GooglepayManager from "./GooglepayManager";
import {loadCustomScript} from "@paypal/paypal-js";
const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data');
const ppcpConfig = ppcpData.scriptData;
const buttonData = wc.wcSettings.getSetting('ppcp-googlepay_data');
const buttonConfig = buttonData.scriptData;
if (typeof window.PayPalCommerceGateway === 'undefined') {
window.PayPalCommerceGateway = ppcpConfig;
}
const GooglePayComponent = () => {
const [bootstrapped, setBootstrapped] = useState(false);
const [paypalLoaded, setPaypalLoaded] = useState(false);
const [googlePayLoaded, setGooglePayLoaded] = useState(false);
const bootstrap = function () {
const manager = new GooglepayManager(buttonConfig, ppcpConfig);
manager.init();
};
useEffect(() => {
const bodyClass = 'ppcp-has-googlepay-block';
if (!document.body.classList.contains(bodyClass)) {
document.body.classList.add(bodyClass);
}
}, []);
useEffect(() => {
// Load GooglePay SDK
loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
setGooglePayLoaded(true);
});
// Load PayPal
loadPaypalScript(ppcpConfig, () => {
setPaypalLoaded(true);
});
}, []);
useEffect(() => {
if (!bootstrapped && paypalLoaded && googlePayLoaded) {
setBootstrapped(true);
bootstrap();
}
}, [paypalLoaded, googlePayLoaded]);
return (
<div id={buttonConfig.button.wrapper.replace('#', '')} className="ppcp-button-googlepay"></div>
);
}
const features = ['products'];
let registerMethod = registerExpressPaymentMethod;
registerMethod({
name: buttonData.id,
label: <div dangerouslySetInnerHTML={{__html: buttonData.title}}/>,
content: <GooglePayComponent isEditing={false}/>,
edit: <GooglePayComponent isEditing={true}/>,
ariaLabel: buttonData.title,
canMakePayment: () => buttonData.enabled,
supports: {
features: features,
},
});

View file

@ -0,0 +1,61 @@
import {loadCustomScript} from "@paypal/paypal-js";
import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
import GooglepayManager from "./GooglepayManager";
(function ({
buttonConfig,
ppcpConfig,
jQuery
}) {
const bootstrap = function () {
const manager = new GooglepayManager(buttonConfig, ppcpConfig);
manager.init();
};
document.addEventListener(
'DOMContentLoaded',
() => {
if (
(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) {
return;
}
let bootstrapped = false;
let paypalLoaded = false;
let googlePayLoaded = false;
const tryToBoot = () => {
if (!bootstrapped && paypalLoaded && googlePayLoaded) {
bootstrapped = true;
bootstrap();
}
}
// Load GooglePay SDK
loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
googlePayLoaded = true;
tryToBoot();
});
// Load PayPal
loadPaypalScript(ppcpConfig, () => {
paypalLoaded = true;
tryToBoot();
});
},
);
})({
buttonConfig: window.wc_ppcp_googlepay,
ppcpConfig: window.PayPalCommerceGateway,
jQuery: window.jQuery
});

View file

@ -0,0 +1,120 @@
<?php
/**
* The Googlepay module services.
*
* @package WooCommerce\PayPalCommerce\Googlepay
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Googlepay\Assets\BlocksPaymentMethod;
use WooCommerce\PayPalCommerce\Googlepay\Assets\Button;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmApplies;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
// If GooglePay can be configured.
'googlepay.eligible' => static function ( ContainerInterface $container ): bool {
$apm_applies = $container->get( 'googlepay.helpers.apm-applies' );
assert( $apm_applies instanceof ApmApplies );
return $apm_applies->for_country_currency();
},
'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
return new ApmApplies(
$container->get( 'googlepay.supported-country-currency-matrix' ),
$container->get( 'api.shop.currency' ),
$container->get( 'api.shop.country' )
);
},
// If GooglePay is configured.
'googlepay.available' => static function ( ContainerInterface $container ): bool {
if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', false ) ) {
$status = $container->get( 'googlepay.helpers.apm-product-status' );
assert( $status instanceof ApmProductStatus );
/**
* If merchant isn't onboarded via /v1/customer/partner-referrals this returns false as the API call fails.
*/
return apply_filters( 'woocommerce_paypal_payments_googlepay_product_status', $status->is_active() );
}
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' )
);
},
/**
* The matrix which countries and currency combinations can be used for GooglePay.
*/
'googlepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
/**
* Returns which countries and currency combinations can be used for GooglePay.
*/
return apply_filters(
'woocommerce_paypal_payments_googlepay_supported_country_currency_matrix',
array(
'US' => array(
'AUD',
'CAD',
'EUR',
'GBP',
'JPY',
'USD',
),
)
);
},
'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface {
return new Button(
$container->get( 'googlepay.url' ),
$container->get( 'googlepay.sdk_url' ),
$container->get( 'ppcp.asset-version' ),
$container->get( 'session.handler' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.settings.status' ),
$container->get( 'api.shop.currency' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface {
return new BlocksPaymentMethod(
'ppcp-googlepay',
$container->get( 'googlepay.url' ),
$container->get( 'ppcp.asset-version' ),
$container->get( 'googlepay.button' ),
$container->get( 'blocks.method' )
);
},
'googlepay.url' => static function ( ContainerInterface $container ): string {
$path = realpath( __FILE__ );
if ( false === $path ) {
return '';
}
return plugins_url(
'/modules/ppcp-googlepay/',
dirname( $path, 3 ) . '/woocommerce-paypal-payments.php'
);
},
'googlepay.sdk_url' => static function ( ContainerInterface $container ): string {
return 'https://pay.google.com/gp/p/js/pay.js';
},
);

View file

@ -0,0 +1,114 @@
<?php
/**
* The googlepay blocks module.
*
* @package WooCommerce\PayPalCommerce\Googlepay
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay\Assets;
use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
/**
* Class BlocksPaymentMethod
*/
class BlocksPaymentMethod extends AbstractPaymentMethodType {
/**
* The URL of this module.
*
* @var string
*/
private $module_url;
/**
* The assets version.
*
* @var string
*/
private $version;
/**
* The button.
*
* @var ButtonInterface
*/
private $button;
/**
* The paypal payment method.
*
* @var PaymentMethodTypeInterface
*/
private $paypal_payment_method;
/**
* Assets constructor.
*
* @param string $name The name of this module.
* @param string $module_url The url of this module.
* @param string $version The assets version.
* @param ButtonInterface $button The button.
* @param PaymentMethodTypeInterface $paypal_payment_method The paypal payment method.
*/
public function __construct(
string $name,
string $module_url,
string $version,
ButtonInterface $button,
PaymentMethodTypeInterface $paypal_payment_method
) {
$this->name = $name;
$this->module_url = $module_url;
$this->version = $version;
$this->button = $button;
$this->paypal_payment_method = $paypal_payment_method;
}
/**
* {@inheritDoc}
*/
public function initialize() { }
/**
* {@inheritDoc}
*/
public function is_active() {
return $this->paypal_payment_method->is_active();
}
/**
* {@inheritDoc}
*/
public function get_payment_method_script_handles() {
$handle = $this->name . '-block';
wp_register_script(
$handle,
trailingslashit( $this->module_url ) . 'assets/js/boot-block.js',
array(),
$this->version,
true
);
return array( $handle );
}
/**
* {@inheritDoc}
*/
public function get_payment_method_data() {
$paypal_data = $this->paypal_payment_method->get_payment_method_data();
return array(
'id' => $this->name,
'title' => $paypal_data['title'], // See if we should use another.
'description' => $paypal_data['description'], // See if we should use another.
'enabled' => $paypal_data['enabled'], // This button is enabled when PayPal buttons are.
'scriptData' => $this->button->script_data(),
);
}
}

View file

@ -0,0 +1,406 @@
<?php
/**
* Registers and configures the necessary Javascript for the button, credit messaging and DCC fields.
*
* @package WooCommerce\PayPalCommerce\Googlepay\Assets
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay\Assets;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class Button
*/
class Button implements ButtonInterface {
/**
* The URL to the module.
*
* @var string
*/
private $module_url;
/**
* The URL to the SDK.
*
* @var string
*/
private $sdk_url;
/**
* The assets version.
*
* @var string
*/
private $version;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The environment object.
*
* @var Environment
*/
private $environment;
/**
* The Settings status helper.
*
* @var SettingsStatus
*/
private $settings_status;
/**
* 3-letter currency code of the shop.
*
* @var string
*/
private $currency;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* Session handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* SmartButton constructor.
*
* @param string $module_url The URL to the module.
* @param string $sdk_url The URL to the SDK.
* @param string $version The assets version.
* @param SessionHandler $session_handler The Session handler.
* @param Settings $settings The Settings.
* @param Environment $environment The environment object.
* @param SettingsStatus $settings_status The Settings status helper.
* @param string $currency 3-letter currency code of the shop.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
string $module_url,
string $sdk_url,
string $version,
SessionHandler $session_handler,
Settings $settings,
Environment $environment,
SettingsStatus $settings_status,
string $currency,
LoggerInterface $logger
) {
$this->module_url = $module_url;
$this->sdk_url = $sdk_url;
$this->version = $version;
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->environment = $environment;
$this->settings_status = $settings_status;
$this->currency = $currency;
$this->logger = $logger;
}
/**
* Initializes the button.
*/
public function initialize(): void {
add_filter( 'ppcp_onboarding_options', array( $this, 'add_onboarding_options' ), 10, 1 );
add_filter( 'ppcp_partner_referrals_data', array( $this, 'add_partner_referrals_data' ), 10, 1 );
}
/**
* Adds the GooglePay onboarding option.
*
* @param string $options The options.
* @return string
*
* @psalm-suppress MissingClosureParamType
*/
public function add_onboarding_options( $options ): string {
$checked = '';
try {
$onboard_with_google = $this->settings->get( 'ppcp-onboarding-google' );
if ( $onboard_with_google === '1' ) {
$checked = 'checked';
}
} catch ( NotFoundException $exception ) {
$checked = '';
}
return $options
. '<li><label><input type="checkbox" id="ppcp-onboarding-google" ' . $checked . '> '
. __( 'Onboard with GooglePay', 'woocommerce-paypal-payments' )
. '</label></li>';
}
/**
* Adds to partner referrals data.
*
* @param array $data The referrals data.
* @return array
*/
public function add_partner_referrals_data( array $data ): array {
try {
$onboard_with_google = $this->settings->get( 'ppcp-onboarding-google' );
if ( ! wc_string_to_bool( $onboard_with_google ) ) {
return $data;
}
} catch ( NotFoundException $exception ) {
return $data;
}
if ( ! in_array( 'PAYMENT_METHODS', $data['products'], true ) ) {
if ( in_array( 'PPCP', $data['products'], true ) ) {
$data['products'][] = 'PAYMENT_METHODS';
} elseif ( in_array( 'EXPRESS_CHECKOUT', $data['products'], true ) ) { // A bit sketchy, maybe replace on the EXPRESS_CHECKOUT index.
$data['products'][0] = 'PAYMENT_METHODS';
}
}
$data['capabilities'][] = 'GOOGLE_PAY';
$nonce = $data['operations'][0]['api_integration_preference']['rest_api_integration']['first_party_details']['seller_nonce'];
$data['operations'][] = array(
'operation' => 'API_INTEGRATION',
'api_integration_preference' => array(
'rest_api_integration' => array(
'integration_method' => 'PAYPAL',
'integration_type' => 'THIRD_PARTY',
'third_party_details' => array(
'features' => array(
'PAYMENT',
'REFUND',
),
'seller_nonce' => $nonce,
),
),
),
);
return $data;
}
/**
* Returns if Google Pay button is enabled
*
* @return bool
*/
public function is_enabled(): bool {
try {
return $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' );
} catch ( Exception $e ) {
return false;
}
}
/**
* Registers the necessary action hooks to render the HTML depending on the settings.
*
* @return bool
*
* @psalm-suppress RedundantCondition
*/
public function render(): bool {
if ( ! $this->is_enabled() ) {
return false;
}
$button_enabled_product = $this->settings_status->is_smart_button_enabled_for_location( 'product' );
$button_enabled_cart = $this->settings_status->is_smart_button_enabled_for_location( 'cart' );
$button_enabled_checkout = true;
$button_enabled_payorder = true;
$button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
add_filter(
'woocommerce_paypal_payments_sdk_components_hook',
function( $components ) {
$components[] = 'googlepay';
return $components;
}
);
if ( $button_enabled_product ) {
$default_hook_name = 'woocommerce_paypal_payments_single_product_button_render';
$render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_single_product_button_render_hook', $default_hook_name );
$render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
add_action(
$render_placeholder,
function () {
$this->googlepay_button();
},
32
);
}
if ( $button_enabled_cart ) {
$default_hook_name = 'woocommerce_paypal_payments_cart_button_render';
$render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_cart_button_render_hook', $default_hook_name );
$render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
add_action(
$render_placeholder,
function () {
$this->googlepay_button();
},
21
);
}
if ( $button_enabled_checkout ) {
$default_hook_name = 'woocommerce_paypal_payments_checkout_button_render';
$render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_checkout_button_render_hook', $default_hook_name );
$render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
add_action(
$render_placeholder,
function () {
$this->googlepay_button();
},
21
);
}
if ( $button_enabled_payorder ) {
$default_hook_name = 'woocommerce_paypal_payments_payorder_button_render';
$render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_payorder_button_render_hook', $default_hook_name );
$render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
add_action(
$render_placeholder,
function () {
$this->googlepay_button();
},
21
);
}
if ( $button_enabled_minicart ) {
$default_hook_name = 'woocommerce_paypal_payments_minicart_button_render';
$render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_minicart_button_render_hook', $default_hook_name );
$render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
add_action(
$render_placeholder,
function () {
echo '<span id="ppc-button-googlepay-container-minicart" class="ppcp-button-googlepay ppcp-button-minicart"></span>';
},
21
);
}
return true;
}
/**
* GooglePay button markup
*/
private function googlepay_button(): void {
?>
<div id="ppc-button-googlepay-container" class="ppcp-button-googlepay">
<?php wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' ); ?>
</div>
<?php
}
/**
* Enqueues scripts/styles.
*/
public function enqueue(): void {
if ( ! $this->is_enabled() ) {
return;
}
wp_register_script(
'wc-ppcp-googlepay',
untrailingslashit( $this->module_url ) . '/assets/js/boot.js',
array(),
$this->version,
true
);
wp_enqueue_script( 'wc-ppcp-googlepay' );
wp_register_style(
'wc-ppcp-googlepay',
untrailingslashit( $this->module_url ) . '/assets/css/styles.css',
array(),
$this->version
);
wp_enqueue_style( 'wc-ppcp-googlepay' );
wp_localize_script(
'wc-ppcp-googlepay',
'wc_ppcp_googlepay',
$this->script_data()
);
}
/**
* The configuration for the smart buttons.
*
* @return array
*/
public function script_data(): array {
return array(
'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION',
'sdk_url' => $this->sdk_url,
'button' => array(
'wrapper' => '#ppc-button-googlepay-container',
'style' => $this->button_styles_for_context( 'cart' ), // For now use cart. Pass the context if necessary.
'mini_cart_wrapper' => '#ppc-button-googlepay-container-minicart',
'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ),
),
);
}
/**
* Determines the style for a given indicator in a given context.
*
* @param string $context The context.
*
* @return array
*/
private function button_styles_for_context( string $context ): array {
// Use the cart/checkout styles for blocks.
$context = str_replace( '-block', '', $context );
$values = array(
'color' => 'black',
'type' => 'pay',
);
foreach ( $values as $style => $value ) {
if ( $this->settings->has( 'googlepay_button_' . $context . '_' . $style ) ) {
$values[ $style ] = $this->settings->get( 'googlepay_button_' . $context . '_' . $style );
} elseif ( $this->settings->has( 'googlepay_button_' . $style ) ) {
$values[ $style ] = $this->settings->get( 'googlepay_button_' . $style );
}
}
return $values;
}
}

View file

@ -0,0 +1,102 @@
<?php
/**
* The Googlepay module.
*
* @package WooCommerce\PayPalCommerce\Googlepay
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class GooglepayModule
*/
class GooglepayModule implements ModuleInterface {
/**
* {@inheritDoc}
*/
public function setup(): ServiceProviderInterface {
return new ServiceProvider(
require __DIR__ . '/../services.php',
require __DIR__ . '/../extensions.php'
);
}
/**
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): void {
if ( ! $c->get( 'googlepay.eligible' ) ) {
return;
}
$button = $c->get( 'googlepay.button' );
assert( $button instanceof ButtonInterface );
$button->initialize();
if ( ! $c->get( 'googlepay.available' ) ) {
return;
}
add_action(
'wp',
static function () use ( $c, $button ) {
if ( is_admin() ) {
return;
}
$button->render();
}
);
add_action(
'wp_enqueue_scripts',
static function () use ( $c, $button ) {
$button->enqueue();
}
);
add_action(
'woocommerce_blocks_payment_method_type_registration',
function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void {
if ( $button->is_enabled() ) {
$payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) );
}
}
);
// Clear product status handling.
add_action(
'woocommerce_paypal_payments_clear_apm_product_status',
function( Settings $settings = null ) use ( $c ): void {
$apm_status = $c->get( 'googlepay.helpers.apm-product-status' );
assert( $apm_status instanceof ApmProductStatus );
if ( ! $settings instanceof Settings ) {
$settings = null;
}
$apm_status->clear( $settings );
}
);
}
/**
* Returns the key for the module.
*
* @return string|void
*/
public function getKey() {
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* Properties of the GooglePay module.
*
* @package WooCommerce\PayPalCommerce\Googlepay\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay\Helper;
/**
* Class ApmApplies
*/
class ApmApplies {
/**
* The matrix which countries and currency combinations can be used for DCC.
*
* @var array
*/
private $allowed_country_currency_matrix;
/**
* 3-letter currency code of the shop.
*
* @var string
*/
private $currency;
/**
* 2-letter country code of the shop.
*
* @var string
*/
private $country;
/**
* DccApplies constructor.
*
* @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for DCC.
* @param string $currency 3-letter currency code of the shop.
* @param string $country 2-letter country code of the shop.
*/
public function __construct(
array $allowed_country_currency_matrix,
string $currency,
string $country
) {
$this->allowed_country_currency_matrix = $allowed_country_currency_matrix;
$this->currency = $currency;
$this->country = $country;
}
/**
* Returns whether GooglePay can be used in the current country and the current currency used.
*
* @return bool
*/
public function for_country_currency(): bool {
if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) {
return false;
}
return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true );
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* Status of the GooglePay merchant connection.
*
* @package WooCommerce\PayPalCommerce\Googlepay\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay\Helper;
use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class ApmProductStatus
*/
class ApmProductStatus {
const CAPABILITY_NAME = 'GOOGLE_PAY';
const SETTINGS_KEY = 'products_googlepay_enabled';
const SETTINGS_VALUE_ENABLED = 'yes';
const SETTINGS_VALUE_DISABLED = 'no';
const SETTINGS_VALUE_UNDEFINED = '';
/**
* The current status stored in memory.
*
* @var bool|null
*/
private $current_status = null;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The partners endpoint.
*
* @var PartnersEndpoint
*/
private $partners_endpoint;
/**
* The onboarding status
*
* @var State
*/
private $onboarding_state;
/**
* ApmProductStatus constructor.
*
* @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param State $onboarding_state The onboarding state.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
State $onboarding_state
) {
$this->settings = $settings;
$this->partners_endpoint = $partners_endpoint;
$this->onboarding_state = $onboarding_state;
}
/**
* Whether the active/subscribed products support Googlepay.
*
* @return bool
*/
public function is_active() : bool {
if ( $this->onboarding_state->current_state() < State::STATE_ONBOARDED ) {
return false;
}
if ( null !== $this->current_status ) {
return $this->current_status;
}
if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
$this->current_status = wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
return $this->current_status;
}
try {
$seller_status = $this->partners_endpoint->seller_status();
} catch ( Throwable $error ) {
// It may be a transitory error, don't persist the status.
$this->current_status = false;
return $this->current_status;
}
foreach ( $seller_status->products() as $product ) {
if ( $product->name() !== 'PAYMENT_METHODS' ) {
continue;
}
if ( in_array( self::CAPABILITY_NAME, $product->capabilities(), true ) ) {
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
$this->settings->persist();
$this->current_status = true;
return $this->current_status;
}
}
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
$this->settings->persist();
$this->current_status = false;
return $this->current_status;
}
/**
* Clears the persisted result to force a recheck.
*
* @param Settings|null $settings The settings object.
* We accept a Settings object to don't override other sequential settings that are being updated elsewhere.
* @return void
*/
public function clear( Settings $settings = null ): void {
if ( null === $settings ) {
$settings = $this->settings;
}
$this->current_status = null;
$settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
$settings->persist();
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* Properties of the GooglePay module.
*
* @package WooCommerce\PayPalCommerce\Googlepay\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Googlepay\Helper;
/**
* Class Button
*/
class PropertiesDictionary {
/**
* Returns the possible list of button colors.
*
* @return array
*/
public static function button_colors(): array {
return array(
'white' => __( 'White', 'woocommerce-paypal-payments' ),
'black' => __( 'Black', 'woocommerce-paypal-payments' ),
);
}
/**
* Returns the possible list of button types.
*
* @return array
*/
public static function button_types(): array {
return array(
'book' => __( 'Book', 'woocommerce-paypal-payments' ),
'buy' => __( 'Buy', 'woocommerce-paypal-payments' ),
'checkout' => __( 'Checkout', 'woocommerce-paypal-payments' ),
'donate' => __( 'Donate', 'woocommerce-paypal-payments' ),
'order' => __( 'Order', 'woocommerce-paypal-payments' ),
'pay' => __( 'Pay', 'woocommerce-paypal-payments' ),
'plain' => __( 'Plain', 'woocommerce-paypal-payments' ),
'subscribe' => __( 'Book', 'woocommerce-paypal-payments' ),
);
}
}

View file

@ -0,0 +1,40 @@
const path = require('path');
const isProduction = process.env.NODE_ENV === 'production';
const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
module.exports = {
devtool: isProduction ? 'source-map' : 'eval-source-map',
mode: isProduction ? 'production' : 'development',
target: 'web',
plugins: [ new DependencyExtractionWebpackPlugin() ],
entry: {
'boot': path.resolve('./resources/js/boot.js'),
'boot-block': path.resolve('./resources/js/boot-block.js'),
"styles": path.resolve('./resources/css/styles.scss')
},
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'}
]
}]
}
};

File diff suppressed because it is too large Load diff

View file

@ -134,6 +134,7 @@ class LoginSellerEndpoint implements EndpointInterface {
$this->settings->set( 'products_dcc_enabled', null ); $this->settings->set( 'products_dcc_enabled', null );
$this->settings->set( 'products_pui_enabled', null ); $this->settings->set( 'products_pui_enabled', null );
$this->settings->persist(); $this->settings->persist();
do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $this->settings );
$endpoint = $is_sandbox ? $this->login_seller_sandbox : $this->login_seller_production; $endpoint = $is_sandbox ? $this->login_seller_sandbox : $this->login_seller_production;
$credentials = $endpoint->credentials_for( $credentials = $endpoint->credentials_for(

View file

@ -238,6 +238,7 @@ class OnboardingRESTController {
$settings->set( 'products_dcc_enabled', null ); $settings->set( 'products_dcc_enabled', null );
$settings->set( 'products_pui_enabled', null ); $settings->set( 'products_pui_enabled', null );
do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $settings );
if ( ! $settings->persist() ) { if ( ! $settings->persist() ) {
return new \WP_Error( return new \WP_Error(

View file

@ -703,7 +703,6 @@ return array(
'requirements' => array(), 'requirements' => array(),
'gateway' => 'paypal', 'gateway' => 'paypal',
), ),
'disable_cards' => array( 'disable_cards' => array(
'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ), 'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect', 'type' => 'ppcp-multiselect',
@ -950,6 +949,13 @@ return array(
'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ), 'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
); );
}, },
'wcgateway.extra-funding-sources' => static function( ContainerInterface $container ): array {
return array(
'googlepay' => _x( 'Google Pay', 'Name of payment method', 'woocommerce-paypal-payments' ),
);
},
/** /**
* The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back. * The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
*/ */
@ -1051,7 +1057,10 @@ return array(
'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer { 'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer {
return new FundingSourceRenderer( return new FundingSourceRenderer(
$container->get( 'wcgateway.settings' ), $container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.all-funding-sources' ) array_merge(
$container->get( 'wcgateway.all-funding-sources' ),
$container->get( 'wcgateway.extra-funding-sources' )
)
); );
}, },

View file

@ -391,6 +391,7 @@ class SettingsListener {
if ( self::CREDENTIALS_UNCHANGED !== $credentials_change_status ) { if ( self::CREDENTIALS_UNCHANGED !== $credentials_change_status ) {
$this->settings->set( 'products_dcc_enabled', null ); $this->settings->set( 'products_dcc_enabled', null );
$this->settings->set( 'products_pui_enabled', null ); $this->settings->set( 'products_pui_enabled', null );
do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $this->settings );
} }
if ( in_array( if ( in_array(

View file

@ -288,6 +288,7 @@ class WCGatewayModule implements ModuleInterface {
$settings->set( 'products_dcc_enabled', false ); $settings->set( 'products_dcc_enabled', false );
$settings->set( 'products_pui_enabled', false ); $settings->set( 'products_pui_enabled', false );
$settings->persist(); $settings->persist();
do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $settings );
// Update caches. // Update caches.
$dcc_status = $c->get( 'wcgateway.helper.dcc-product-status' ); $dcc_status = $c->get( 'wcgateway.helper.dcc-product-status' );

View file

@ -10,6 +10,7 @@
"install:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn install", "install:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn install",
"install:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn install", "install:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn install",
"install:modules:ppcp-button": "cd modules/ppcp-button && yarn install", "install:modules:ppcp-button": "cd modules/ppcp-button && yarn install",
"install:modules:ppcp-googlepay": "cd modules/ppcp-googlepay && yarn install",
"install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install", "install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install",
"install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install", "install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install",
"install:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn install", "install:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn install",
@ -20,6 +21,7 @@
"build:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run build", "build:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run build",
"build:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run build", "build:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run build",
"build:modules:ppcp-button": "cd modules/ppcp-button && yarn run build", "build:modules:ppcp-button": "cd modules/ppcp-button && yarn run build",
"build:modules:ppcp-googlepay": "cd modules/ppcp-googlepay && yarn run build",
"build:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run build", "build:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run build",
"build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build", "build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build",
"build:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run build", "build:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run build",
@ -31,6 +33,7 @@
"watch:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run watch", "watch:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run watch",
"watch:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run watch", "watch:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run watch",
"watch:modules:ppcp-button": "cd modules/ppcp-button && yarn run watch", "watch:modules:ppcp-button": "cd modules/ppcp-button && yarn run watch",
"watch:modules:ppcp-googlepay": "cd modules/ppcp-googlepay && yarn run watch",
"watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch", "watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch",
"watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch", "watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch",
"watch:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run watch", "watch:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run watch",