mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-01 07:02:48 +08:00
Merge branch 'trunk' of github.com:woocommerce/woocommerce-paypal-payments into PCP-3783-fastlane-allow-merchants-to-disable-specific-card-types
This commit is contained in:
commit
f3c32dbe6c
270 changed files with 48005 additions and 70038 deletions
|
@ -14,6 +14,7 @@ nfs_mount_enabled: false
|
|||
mutagen_enabled: false
|
||||
use_dns_when_possible: true
|
||||
composer_version: "2"
|
||||
nodejs_version: "22"
|
||||
hooks:
|
||||
pre-start:
|
||||
- exec-host: "mkdir -p .ddev/wordpress/wp-content/plugins/${DDEV_PROJECT}"
|
||||
|
|
1
.github/workflows/package-new.yml
vendored
1
.github/workflows/package-new.yml
vendored
|
@ -30,6 +30,7 @@ jobs:
|
|||
uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@main
|
||||
with:
|
||||
PHP_VERSION: 7.4
|
||||
NODE_VERSION: 22
|
||||
PLUGIN_MAIN_FILE: ./woocommerce-paypal-payments.php
|
||||
PLUGIN_VERSION: ${{ needs.check_version.outputs.version }}
|
||||
PLUGIN_FOLDER_NAME: woocommerce-paypal-payments
|
||||
|
|
|
@ -2120,3 +2120,40 @@ class WC_Product_Subscription_Variation extends WC_Product_Variation {}
|
|||
*
|
||||
*/
|
||||
class WC_Product_Variable_Subscription extends WC_Product_Variable {}
|
||||
|
||||
class WCS_Manual_Renewal_Manager {
|
||||
|
||||
/**
|
||||
* Initalise the class and attach callbacks.
|
||||
*/
|
||||
public static function init() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the manual renewal settings.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @param $settings The full subscription settings array.
|
||||
* @return $settings.
|
||||
*/
|
||||
public static function add_settings( $settings ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if manual renewals are required - automatic renewals are disabled.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return bool Weather manual renewal is required.
|
||||
*/
|
||||
public static function is_manual_renewal_required() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if manual renewals are enabled.
|
||||
*
|
||||
* @since 4.0.0
|
||||
* @return bool Weather manual renewal is enabled.
|
||||
*/
|
||||
public static function is_manual_renewal_enabled() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,27 @@
|
|||
*** Changelog ***
|
||||
|
||||
= 2.9.4 - 2024-11-11 =
|
||||
* Fix - Apple Pay button preview missing in Standard payment and Advanced Processing tabs #2755
|
||||
* Fix - Set "Sold individually" only for subscription connected to PayPal #2710
|
||||
* Fix - Ensure Google Pay button does not appear for subscriptions #2718
|
||||
* Fix - PayPal Subscriptions API renewal order not created in WooCommerce #2612
|
||||
* Fix - Apple Pay button disappears on Classic Checkout #2722
|
||||
* Fix - Google Pay and Apple Pay as separate gateways does not show button when checkout remove from button locations #2756
|
||||
* Fix - Add GW refund support for Apple Pay #2746
|
||||
* Fix - PayPal Subscriptions cancel and suspend from Subscriptions list page does not work #2632
|
||||
* Fix - Displaying of HTML tags in product title on choosing a product for tracking (2801) #2701
|
||||
* Fix - Payment with OXXO cause continuation state for next payment #2702
|
||||
* Fix - Fix problems with autoptimize plugin #2705
|
||||
* Fix - Missing custom field PayPal Transaction Fee for OXXO #2700
|
||||
* Enhancement - Add void button #2678
|
||||
* Enhancement - Use basic redirect gateway when checkout smart buttons disabled #2714
|
||||
* Enhancement - Receive button properties from the Checkout Block #2448
|
||||
* Enhancement - Run PPEC\DeactivateNote query only in backend #2719
|
||||
* Enhancement - Prevent plugin use for "Send only" countries #2721
|
||||
* Enhancement - Do not add pay later button in editor #2570
|
||||
* Enhancement - Axo: Remove the submit button when Fastlane is disabled #2720
|
||||
* Enhancement - Sync the PayPal product page button state to Apple/Google Pay buttons, show alerts #2742
|
||||
|
||||
= 2.9.3 - 2024-10-15 =
|
||||
* Fix - Multi-currency support #2667
|
||||
* Fix - "0.00" amount in Google Pay for virtual products #2636
|
||||
|
|
|
@ -90,5 +90,12 @@ return function ( string $root_dir ): iterable {
|
|||
$modules[] = ( require "$modules_dir/ppcp-axo-block/module.php" )();
|
||||
}
|
||||
|
||||
if ( apply_filters(
|
||||
'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled',
|
||||
getenv( 'PCP_SETTINGS_ENABLED' ) === '1'
|
||||
) ) {
|
||||
$modules[] = ( require "$modules_dir/ppcp-settings/module.php" )();
|
||||
}
|
||||
|
||||
return $modules;
|
||||
};
|
||||
|
|
|
@ -10,18 +10,18 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -572,7 +572,6 @@ return array(
|
|||
'CZK',
|
||||
'DKK',
|
||||
'EUR',
|
||||
'HKD',
|
||||
'HUF',
|
||||
'ILS',
|
||||
'JPY',
|
||||
|
@ -585,7 +584,6 @@ return array(
|
|||
'PLN',
|
||||
'GBP',
|
||||
'RUB',
|
||||
'SGD',
|
||||
'SEK',
|
||||
'CHF',
|
||||
'THB',
|
||||
|
@ -606,7 +604,6 @@ return array(
|
|||
'DKK',
|
||||
'EUR',
|
||||
'GBP',
|
||||
'HKD',
|
||||
'HUF',
|
||||
'ILS',
|
||||
'JPY',
|
||||
|
@ -616,7 +613,6 @@ return array(
|
|||
'PHP',
|
||||
'PLN',
|
||||
'SEK',
|
||||
'SGD',
|
||||
'THB',
|
||||
'TWD',
|
||||
'USD',
|
||||
|
@ -642,7 +638,6 @@ return array(
|
|||
'FR' => $default_currencies,
|
||||
'DE' => $default_currencies,
|
||||
'GR' => $default_currencies,
|
||||
'HK' => $default_currencies,
|
||||
'HU' => $default_currencies,
|
||||
'IE' => $default_currencies,
|
||||
'IT' => $default_currencies,
|
||||
|
@ -660,7 +655,6 @@ return array(
|
|||
'PT' => $default_currencies,
|
||||
'RO' => $default_currencies,
|
||||
'SK' => $default_currencies,
|
||||
'SG' => $default_currencies,
|
||||
'SI' => $default_currencies,
|
||||
'ES' => $default_currencies,
|
||||
'SE' => $default_currencies,
|
||||
|
|
|
@ -14,7 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Token;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class PayPalBearer
|
||||
|
@ -28,7 +28,7 @@ class PayPalBearer implements Bearer {
|
|||
/**
|
||||
* The settings.
|
||||
*
|
||||
* @var Settings
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
|
@ -75,7 +75,7 @@ class PayPalBearer implements Bearer {
|
|||
* @param string $key The key.
|
||||
* @param string $secret The secret.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param Settings $settings The settings.
|
||||
* @param ContainerInterface $settings The settings.
|
||||
*/
|
||||
public function __construct(
|
||||
Cache $cache,
|
||||
|
@ -83,7 +83,7 @@ class PayPalBearer implements Bearer {
|
|||
string $key,
|
||||
string $secret,
|
||||
LoggerInterface $logger,
|
||||
Settings $settings
|
||||
ContainerInterface $settings
|
||||
) {
|
||||
|
||||
$this->cache = $cache;
|
||||
|
@ -136,8 +136,7 @@ class PayPalBearer implements Bearer {
|
|||
$error = new RuntimeException(
|
||||
__( 'Could not create token.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$this->logger->warning(
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
|
|
|
@ -67,7 +67,7 @@ class BillingSubscriptions {
|
|||
*/
|
||||
public function suspend( string $id ):void {
|
||||
$data = array(
|
||||
'reason' => 'Suspended by customer',
|
||||
'reason' => sprintf( 'Suspended by %s.', is_admin() ? 'merchant' : 'customer' ),
|
||||
);
|
||||
|
||||
$bearer = $this->bearer->bearer();
|
||||
|
@ -107,7 +107,7 @@ class BillingSubscriptions {
|
|||
*/
|
||||
public function activate( string $id ): void {
|
||||
$data = array(
|
||||
'reason' => 'Reactivated by customer',
|
||||
'reason' => sprintf( 'Reactivated by %s.', is_admin() ? 'merchant' : 'customer' ),
|
||||
);
|
||||
|
||||
$bearer = $this->bearer->bearer();
|
||||
|
@ -148,7 +148,7 @@ class BillingSubscriptions {
|
|||
*/
|
||||
public function cancel( string $id ): void {
|
||||
$data = array(
|
||||
'reason' => 'Cancelled by customer',
|
||||
'reason' => sprintf( 'Cancelled by %s.', is_admin() ? 'merchant' : 'customer' ),
|
||||
);
|
||||
|
||||
$bearer = $this->bearer->bearer();
|
||||
|
|
|
@ -58,7 +58,7 @@ class Cache {
|
|||
*
|
||||
* @param string $key The key.
|
||||
*/
|
||||
public function delete( string $key ) {
|
||||
public function delete( string $key ): void {
|
||||
delete_transient( $this->prefix . $key );
|
||||
}
|
||||
|
||||
|
|
66
modules/ppcp-api-client/src/Helper/InMemoryCache.php
Normal file
66
modules/ppcp-api-client/src/Helper/InMemoryCache.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* An in-memory version of Cache.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
|
||||
*/
|
||||
|
||||
declare( strict_types=1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
|
||||
|
||||
/**
|
||||
* An in-memory version of Cache. The data is kept only within the class instance.
|
||||
*/
|
||||
class InMemoryCache extends Cache {
|
||||
/**
|
||||
* The in-memory storage.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $data = array();
|
||||
|
||||
/**
|
||||
* InMemoryCache constructor
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct( '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value.
|
||||
*
|
||||
* @param string $key The key under which the value is stored.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( string $key ) {
|
||||
if ( ! array_key_exists( $key, $this->data ) ) {
|
||||
return false;
|
||||
}
|
||||
return $this->data[ $key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a cache.
|
||||
*
|
||||
* @param string $key The key.
|
||||
*/
|
||||
public function delete( string $key ): void {
|
||||
unset( $this->data[ $key ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches a value.
|
||||
*
|
||||
* @param string $key The key under which the value should be cached.
|
||||
* @param mixed $value The value to cache.
|
||||
* @param int $expiration Unused.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set( string $key, $value, int $expiration = 0 ): bool {
|
||||
$this->data[ $key ] = $value;
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -11,20 +11,20 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@paypal/paypal-js": "^6.0.0",
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"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",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -57,7 +57,3 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ppc-button-ppcp-applepay {
|
||||
display: none;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,54 +1,90 @@
|
|||
import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher';
|
||||
import ApplePayButton from './ApplepayButton';
|
||||
import ContextHandlerFactory from './Context/ContextHandlerFactory';
|
||||
|
||||
class ApplePayManager {
|
||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
||||
this.namespace = namespace;
|
||||
this.buttonConfig = buttonConfig;
|
||||
this.ppcpConfig = ppcpConfig;
|
||||
this.ApplePayConfig = null;
|
||||
this.buttons = [];
|
||||
#namespace = '';
|
||||
#buttonConfig = null;
|
||||
#ppcpConfig = null;
|
||||
#applePayConfig = null;
|
||||
#contextHandler = null;
|
||||
#transactionInfo = null;
|
||||
#buttons = [];
|
||||
|
||||
buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => {
|
||||
const button = new ApplePayButton(
|
||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
||||
this.#namespace = namespace;
|
||||
this.#buttonConfig = buttonConfig;
|
||||
this.#ppcpConfig = ppcpConfig;
|
||||
|
||||
this.onContextBootstrap = this.onContextBootstrap.bind( this );
|
||||
buttonModuleWatcher.watchContextBootstrap( this.onContextBootstrap );
|
||||
}
|
||||
|
||||
async onContextBootstrap( bootstrap ) {
|
||||
this.#contextHandler = ContextHandlerFactory.create(
|
||||
bootstrap.context,
|
||||
bootstrap.handler,
|
||||
buttonConfig,
|
||||
ppcpConfig
|
||||
this.#buttonConfig,
|
||||
this.#ppcpConfig,
|
||||
bootstrap.handler
|
||||
);
|
||||
|
||||
this.buttons.push( button );
|
||||
const button = ApplePayButton.createButton(
|
||||
bootstrap.context,
|
||||
bootstrap.handler,
|
||||
this.#buttonConfig,
|
||||
this.#ppcpConfig,
|
||||
this.#contextHandler
|
||||
);
|
||||
|
||||
if ( this.ApplePayConfig ) {
|
||||
button.init( this.ApplePayConfig );
|
||||
}
|
||||
} );
|
||||
this.#buttons.push( button );
|
||||
|
||||
// Ensure ApplePayConfig is loaded before proceeding.
|
||||
await this.init();
|
||||
|
||||
button.configure( this.#applePayConfig, this.#transactionInfo );
|
||||
button.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
( async () => {
|
||||
await this.config();
|
||||
for ( const button of this.buttons ) {
|
||||
button.init( this.ApplePayConfig );
|
||||
}
|
||||
} )();
|
||||
}
|
||||
|
||||
reinit() {
|
||||
for ( const button of this.buttons ) {
|
||||
button.reinit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets Apple Pay configuration of the PayPal merchant.
|
||||
*/
|
||||
async config() {
|
||||
this.ApplePayConfig = await window[ this.namespace ]
|
||||
async init() {
|
||||
try {
|
||||
if ( ! this.#applePayConfig ) {
|
||||
this.#applePayConfig = await window[ this.#namespace ]
|
||||
.Applepay()
|
||||
.config();
|
||||
|
||||
return this.ApplePayConfig;
|
||||
if ( ! this.#applePayConfig ) {
|
||||
console.error( 'No ApplePayConfig received during init' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! this.#transactionInfo ) {
|
||||
this.#transactionInfo = await this.fetchTransactionInfo();
|
||||
|
||||
if ( ! this.#applePayConfig ) {
|
||||
console.error( 'No transactionInfo found during init' );
|
||||
}
|
||||
}
|
||||
} catch ( error ) {
|
||||
console.error( 'Error during initialization:', error );
|
||||
}
|
||||
}
|
||||
|
||||
async fetchTransactionInfo() {
|
||||
try {
|
||||
if ( ! this.#contextHandler ) {
|
||||
throw new Error( 'ContextHandler is not initialized' );
|
||||
}
|
||||
return await this.#contextHandler.transactionInfo();
|
||||
} catch ( error ) {
|
||||
console.error( 'Error fetching transaction info:', error );
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
reinit() {
|
||||
for ( const button of this.#buttons ) {
|
||||
button.reinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +1,15 @@
|
|||
import ApplePayButton from './ApplepayButton';
|
||||
import ApplepayButton from './Block/components/ApplePayButton';
|
||||
|
||||
class ApplePayManagerBlockEditor {
|
||||
constructor( namespace, buttonConfig, ppcpConfig ) {
|
||||
this.namespace = namespace;
|
||||
this.buttonConfig = buttonConfig;
|
||||
this.ppcpConfig = ppcpConfig;
|
||||
|
||||
/*
|
||||
* On the front-end, the init method is called when a new button context was detected
|
||||
* via `buttonModuleWatcher`. In the block editor, we do not need to wait for the
|
||||
* context, but can initialize the button in the next event loop.
|
||||
*/
|
||||
setTimeout( () => this.init() );
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
this.applePayConfig = await window[ this.namespace ]
|
||||
.Applepay()
|
||||
.config();
|
||||
|
||||
const button = new ApplePayButton(
|
||||
this.ppcpConfig.context,
|
||||
null,
|
||||
this.buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
|
||||
button.init( this.applePayConfig );
|
||||
} catch ( error ) {
|
||||
console.error( 'Failed to initialize Apple Pay:', error );
|
||||
}
|
||||
}
|
||||
}
|
||||
const ApplePayManagerBlockEditor = ( {
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig,
|
||||
} ) => (
|
||||
<ApplepayButton
|
||||
namespace={ namespace }
|
||||
buttonConfig={ buttonConfig }
|
||||
ppcpConfig={ ppcpConfig }
|
||||
/>
|
||||
);
|
||||
|
||||
export default ApplePayManagerBlockEditor;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
import useApiToGenerateButton from '../hooks/useApiToGenerateButton';
|
||||
import usePayPalScript from '../hooks/usePayPalScript';
|
||||
import useApplepayScript from '../hooks/useApplepayScript';
|
||||
import useApplepayConfig from '../hooks/useApplepayConfig';
|
||||
|
||||
const ApplepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => {
|
||||
const [ buttonHtml, setButtonHtml ] = useState( '' );
|
||||
const [ buttonElement, setButtonElement ] = useState( null );
|
||||
const [ componentFrame, setComponentFrame ] = useState( null );
|
||||
const isPayPalLoaded = usePayPalScript( namespace, ppcpConfig );
|
||||
|
||||
const isApplepayLoaded = useApplepayScript(
|
||||
componentFrame,
|
||||
buttonConfig,
|
||||
isPayPalLoaded
|
||||
);
|
||||
|
||||
const applepayConfig = useApplepayConfig( namespace, isApplepayLoaded );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! buttonElement ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setComponentFrame( buttonElement.ownerDocument );
|
||||
}, [ buttonElement ] );
|
||||
|
||||
const applepayButton = useApiToGenerateButton(
|
||||
componentFrame,
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig,
|
||||
applepayConfig
|
||||
);
|
||||
|
||||
useEffect( () => {
|
||||
if ( applepayButton ) {
|
||||
setButtonHtml( applepayButton.outerHTML );
|
||||
}
|
||||
}, [ applepayButton ] );
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ setButtonElement }
|
||||
dangerouslySetInnerHTML={ { __html: buttonHtml } }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApplepayButton;
|
|
@ -0,0 +1,37 @@
|
|||
import { useEffect, useState } from '@wordpress/element';
|
||||
import useButtonStyles from './useButtonStyles';
|
||||
|
||||
const useApiToGenerateButton = (
|
||||
componentDocument,
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig,
|
||||
applepayConfig
|
||||
) => {
|
||||
const [ applepayButton, setApplepayButton ] = useState( null );
|
||||
const buttonStyles = useButtonStyles( buttonConfig, ppcpConfig );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! buttonConfig || ! applepayConfig ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const button = document.createElement( 'apple-pay-button' );
|
||||
button.setAttribute(
|
||||
'buttonstyle',
|
||||
buttonConfig.buttonColor || 'black'
|
||||
);
|
||||
button.setAttribute( 'type', buttonConfig.buttonType || 'pay' );
|
||||
button.setAttribute( 'locale', buttonConfig.buttonLocale || 'en' );
|
||||
|
||||
setApplepayButton( button );
|
||||
|
||||
return () => {
|
||||
setApplepayButton( null );
|
||||
};
|
||||
}, [ namespace, buttonConfig, ppcpConfig, applepayConfig, buttonStyles ] );
|
||||
|
||||
return applepayButton;
|
||||
};
|
||||
|
||||
export default useApiToGenerateButton;
|
|
@ -0,0 +1,26 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
|
||||
const useApplepayConfig = ( namespace, isApplepayLoaded ) => {
|
||||
const [ applePayConfig, setApplePayConfig ] = useState( null );
|
||||
|
||||
useEffect( () => {
|
||||
const fetchConfig = async () => {
|
||||
if ( ! isApplepayLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await window[ namespace ].Applepay().config();
|
||||
setApplePayConfig( config );
|
||||
} catch ( error ) {
|
||||
console.error( 'Failed to fetch Apple Pay config:', error );
|
||||
}
|
||||
};
|
||||
|
||||
fetchConfig();
|
||||
}, [ namespace, isApplepayLoaded ] );
|
||||
|
||||
return applePayConfig;
|
||||
};
|
||||
|
||||
export default useApplepayConfig;
|
|
@ -0,0 +1,65 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
import { loadCustomScript } from '@paypal/paypal-js';
|
||||
|
||||
const useApplepayScript = (
|
||||
componentDocument,
|
||||
buttonConfig,
|
||||
isPayPalLoaded
|
||||
) => {
|
||||
const [ isApplepayLoaded, setIsApplepayLoaded ] = useState( false );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! componentDocument ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const injectScriptToFrame = ( scriptSrc ) => {
|
||||
if ( document === componentDocument ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.querySelector(
|
||||
`script[src^="${ scriptSrc }"]`
|
||||
);
|
||||
|
||||
if ( script ) {
|
||||
const newScript = componentDocument.createElement( 'script' );
|
||||
newScript.src = script.src;
|
||||
newScript.async = script.async;
|
||||
newScript.type = script.type;
|
||||
|
||||
componentDocument.head.appendChild( newScript );
|
||||
} else {
|
||||
console.error( 'Script not found in the document:', scriptSrc );
|
||||
}
|
||||
};
|
||||
|
||||
const loadApplepayScript = async () => {
|
||||
if ( ! isPayPalLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! buttonConfig || ! buttonConfig.sdk_url ) {
|
||||
console.error( 'Invalid buttonConfig or missing sdk_url' );
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await loadCustomScript( { url: buttonConfig.sdk_url } ).then(
|
||||
() => {
|
||||
injectScriptToFrame( buttonConfig.sdk_url );
|
||||
}
|
||||
);
|
||||
setIsApplepayLoaded( true );
|
||||
} catch ( error ) {
|
||||
console.error( 'Failed to load Applepay script:', error );
|
||||
}
|
||||
};
|
||||
|
||||
loadApplepayScript();
|
||||
}, [ componentDocument, buttonConfig, isPayPalLoaded ] );
|
||||
|
||||
return isApplepayLoaded;
|
||||
};
|
||||
|
||||
export default useApplepayScript;
|
|
@ -0,0 +1,19 @@
|
|||
import { useMemo } from '@wordpress/element';
|
||||
import { combineStyles } from '../../../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers';
|
||||
|
||||
const useButtonStyles = ( buttonConfig, ppcpConfig ) => {
|
||||
return useMemo( () => {
|
||||
const styles = combineStyles(
|
||||
ppcpConfig?.button || {},
|
||||
buttonConfig?.button || {}
|
||||
);
|
||||
|
||||
if ( styles.MiniCart && styles.MiniCart.type === 'buy' ) {
|
||||
styles.MiniCart.type = 'pay';
|
||||
}
|
||||
|
||||
return styles;
|
||||
}, [ buttonConfig, ppcpConfig ] );
|
||||
};
|
||||
|
||||
export default useButtonStyles;
|
|
@ -0,0 +1,25 @@
|
|||
import { useState, useEffect } from '@wordpress/element';
|
||||
import { loadPayPalScript } from '../../../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
||||
|
||||
const usePayPalScript = ( namespace, ppcpConfig ) => {
|
||||
const [ isPayPalLoaded, setIsPayPalLoaded ] = useState( false );
|
||||
|
||||
ppcpConfig.url_params.components += ',applepay';
|
||||
|
||||
useEffect( () => {
|
||||
const loadScript = async () => {
|
||||
try {
|
||||
await loadPayPalScript( namespace, ppcpConfig );
|
||||
setIsPayPalLoaded( true );
|
||||
} catch ( error ) {
|
||||
console.error( `Error loading PayPal script: ${ error }` );
|
||||
}
|
||||
};
|
||||
|
||||
loadScript();
|
||||
}, [ namespace, ppcpConfig ] );
|
||||
|
||||
return isPayPalLoaded;
|
||||
};
|
||||
|
||||
export default usePayPalScript;
|
|
@ -5,6 +5,11 @@ import PreviewButton from '../../../../ppcp-button/resources/js/modules/Preview/
|
|||
* A single Apple Pay preview button instance.
|
||||
*/
|
||||
export default class ApplePayPreviewButton extends PreviewButton {
|
||||
/**
|
||||
* @type {?PaymentButton}
|
||||
*/
|
||||
#button = null;
|
||||
|
||||
constructor( args ) {
|
||||
super( args );
|
||||
|
||||
|
@ -19,14 +24,18 @@ export default class ApplePayPreviewButton extends PreviewButton {
|
|||
}
|
||||
|
||||
createButton( buttonConfig ) {
|
||||
const button = new ApplepayButton(
|
||||
if ( ! this.#button ) {
|
||||
this.#button = new ApplepayButton(
|
||||
'preview',
|
||||
null,
|
||||
buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
}
|
||||
|
||||
button.init( this.apiConfig );
|
||||
this.#button.configure( this.apiConfig, null );
|
||||
this.#button.applyButtonStyles( buttonConfig, this.ppcpConfig );
|
||||
this.#button.reinit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { useEffect, useRef, useState } from '@wordpress/element';
|
||||
import { registerExpressPaymentMethod } from '@woocommerce/blocks-registry';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
|
||||
import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription';
|
||||
import { loadCustomScript } from '@paypal/paypal-js';
|
||||
|
@ -18,20 +19,16 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
|
|||
window.PayPalCommerceGateway = ppcpConfig;
|
||||
}
|
||||
|
||||
const ApplePayComponent = ( props ) => {
|
||||
const [ bootstrapped, setBootstrapped ] = useState( false );
|
||||
const ApplePayComponent = ( { isEditing } ) => {
|
||||
const [ paypalLoaded, setPaypalLoaded ] = useState( false );
|
||||
const [ applePayLoaded, setApplePayLoaded ] = useState( false );
|
||||
|
||||
const bootstrap = function () {
|
||||
const ManagerClass = props.isEditing
|
||||
? ApplePayManagerBlockEditor
|
||||
: ApplePayManager;
|
||||
const manager = new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
||||
manager.init();
|
||||
};
|
||||
const wrapperRef = useRef( null );
|
||||
|
||||
useEffect( () => {
|
||||
if ( isEditing ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load ApplePay SDK
|
||||
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
|
||||
setApplePayLoaded( true );
|
||||
|
@ -47,17 +44,35 @@ const ApplePayComponent = ( props ) => {
|
|||
.catch( ( error ) => {
|
||||
console.error( 'Failed to load PayPal script: ', error );
|
||||
} );
|
||||
}, [] );
|
||||
}, [ isEditing ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( ! bootstrapped && paypalLoaded && applePayLoaded ) {
|
||||
setBootstrapped( true );
|
||||
bootstrap();
|
||||
if ( isEditing || ! paypalLoaded || ! applePayLoaded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ManagerClass = isEditing
|
||||
? ApplePayManagerBlockEditor
|
||||
: ApplePayManager;
|
||||
|
||||
buttonConfig.reactWrapper = wrapperRef.current;
|
||||
|
||||
new ManagerClass( namespace, buttonConfig, ppcpConfig );
|
||||
}, [ paypalLoaded, applePayLoaded, isEditing ] );
|
||||
|
||||
if ( isEditing ) {
|
||||
return (
|
||||
<ApplePayManagerBlockEditor
|
||||
namespace={ namespace }
|
||||
buttonConfig={ buttonConfig }
|
||||
ppcpConfig={ ppcpConfig }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}, [ paypalLoaded, applePayLoaded ] );
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ wrapperRef }
|
||||
id={ buttonConfig.button.wrapper.replace( '#', '' ) }
|
||||
className="ppcp-button-apm ppcp-button-applepay"
|
||||
></div>
|
||||
|
@ -75,6 +90,11 @@ if (
|
|||
|
||||
registerExpressPaymentMethod( {
|
||||
name: buttonData.id,
|
||||
title: `PayPal - ${ buttonData.title }`,
|
||||
description: __(
|
||||
'Eligible users will see the PayPal button.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
label: <div dangerouslySetInnerHTML={ { __html: buttonData.title } } />,
|
||||
content: <ApplePayComponent isEditing={ false } />,
|
||||
edit: <ApplePayComponent isEditing={ true } />,
|
||||
|
|
|
@ -3,33 +3,49 @@ import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Help
|
|||
import ApplePayManager from './ApplepayManager';
|
||||
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
|
||||
|
||||
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
|
||||
( function ( { buttonConfig, ppcpConfig } ) {
|
||||
const namespace = 'ppcpPaypalApplepay';
|
||||
let manager;
|
||||
|
||||
const bootstrap = function () {
|
||||
manager = new ApplePayManager( namespace, buttonConfig, ppcpConfig );
|
||||
manager.init();
|
||||
};
|
||||
|
||||
setupButtonEvents( function () {
|
||||
if ( manager ) {
|
||||
manager.reinit();
|
||||
}
|
||||
} );
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
if (
|
||||
typeof buttonConfig === 'undefined' ||
|
||||
typeof ppcpConfig === 'undefined'
|
||||
) {
|
||||
function bootstrapPayButton() {
|
||||
if ( ! buttonConfig || ! ppcpConfig ) {
|
||||
return;
|
||||
}
|
||||
const isMiniCart = ppcpConfig.mini_cart_buttons_enabled;
|
||||
const isButton = jQuery( '#' + buttonConfig.button.wrapper ).length > 0;
|
||||
|
||||
const manager = new ApplePayManager(
|
||||
namespace,
|
||||
buttonConfig,
|
||||
ppcpConfig
|
||||
);
|
||||
|
||||
setupButtonEvents( function () {
|
||||
manager.reinit();
|
||||
} );
|
||||
}
|
||||
|
||||
function bootstrap() {
|
||||
bootstrapPayButton();
|
||||
// Other Apple Pay bootstrapping could happen here.
|
||||
}
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
if ( ! buttonConfig || ! ppcpConfig ) {
|
||||
/*
|
||||
* No PayPal buttons present on this page, but maybe a bootstrap module needs to be
|
||||
* initialized. Skip loading the SDK or gateway configuration, and directly initialize
|
||||
* the module.
|
||||
*/
|
||||
bootstrap();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const usedInMiniCart = ppcpConfig.mini_cart_buttons_enabled;
|
||||
const pageHasButton =
|
||||
null !== document.getElementById( buttonConfig.button.wrapper );
|
||||
|
||||
// If button wrapper is not present then there is no need to load the scripts.
|
||||
// minicart loads later?
|
||||
if ( ! isMiniCart && ! isButton ) {
|
||||
if ( ! usedInMiniCart && ! pageHasButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -63,5 +79,4 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel
|
|||
} )( {
|
||||
buttonConfig: window.wc_ppcp_applepay,
|
||||
ppcpConfig: window.PayPalCommerceGateway,
|
||||
jQuery: window.jQuery,
|
||||
} );
|
||||
|
|
|
@ -234,7 +234,6 @@ return array(
|
|||
'DKK', // Danish Krone
|
||||
'EUR', // Euro
|
||||
'GBP', // British Pound Sterling
|
||||
'HKD', // Hong Kong Dollar
|
||||
'HUF', // Hungarian Forint
|
||||
'ILS', // Israeli New Shekel
|
||||
'JPY', // Japanese Yen
|
||||
|
@ -244,7 +243,6 @@ return array(
|
|||
'PHP', // Philippine Peso
|
||||
'PLN', // Polish Zloty
|
||||
'SEK', // Swedish Krona
|
||||
'SGD', // Singapore Dollar
|
||||
'THB', // Thai Baht
|
||||
'TWD', // New Taiwan Dollar
|
||||
'USD', // United States Dollar
|
||||
|
|
|
@ -105,6 +105,11 @@ class ApplePayGateway extends WC_Payment_Gateway {
|
|||
) {
|
||||
$this->id = self::ID;
|
||||
|
||||
$this->supports = array(
|
||||
'refunds',
|
||||
'products',
|
||||
);
|
||||
|
||||
$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' );
|
||||
|
||||
|
|
|
@ -168,6 +168,20 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
|
|||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'woocommerce_paypal_payments_selected_button_locations',
|
||||
function( array $locations, string $setting_name ): array {
|
||||
$gateway = WC()->payment_gateways()->payment_gateways()[ ApplePayGateway::ID ] ?? '';
|
||||
if ( $gateway && $gateway->enabled === 'yes' && $setting_name === 'smart_button_locations' ) {
|
||||
$locations[] = 'checkout';
|
||||
}
|
||||
|
||||
return $locations;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1008,7 +1008,7 @@ class ApplePayButton implements ButtonInterface {
|
|||
*/
|
||||
protected function hide_gateway_until_eligible(): void {
|
||||
?>
|
||||
<style id="ppcp-hide-apple-pay">.wc_payment_method.payment_method_ppcp-applepay{display:none}</style>
|
||||
<style data-hide-gateway="ppcp-applepay">.wc_payment_method.payment_method_ppcp-applepay{display:none}</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ class BlocksPaymentMethod extends AbstractPaymentMethodType {
|
|||
'id' => $this->name,
|
||||
'title' => $paypal_data['title'], // TODO : see if we should use another.
|
||||
'description' => $paypal_data['description'], // TODO : see if we should use another.
|
||||
'enabled' => $paypal_data['enabled'], // This button is enabled when PayPal buttons are.
|
||||
'enabled' => $paypal_data['smartButtonsEnabled'], // This button is enabled when PayPal buttons are.
|
||||
'scriptData' => $script_data,
|
||||
);
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
7142
modules/ppcp-axo-block/package-lock.json
generated
7142
modules/ppcp-axo-block/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -12,22 +12,22 @@
|
|||
"dependencies": {
|
||||
"@paypal/paypal-js": "^8.1.1",
|
||||
"@paypal/react-paypal-js": "^8.5.0",
|
||||
"core-js": "^3.25.0",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0"
|
||||
"core-js": "^3.39",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^8.2",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useMemo } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Custom hook returning the allowed shipping locations based on configuration.
|
||||
*
|
||||
* @param {Object} axoConfig - The AXO configuration object.
|
||||
* @param {Array|undefined} axoConfig.enabled_shipping_locations - The list of enabled shipping locations.
|
||||
* @return {Array} The final list of allowed shipping locations.
|
||||
*/
|
||||
const useAllowedLocations = ( axoConfig ) => {
|
||||
return useMemo( () => {
|
||||
const enabledShippingLocations =
|
||||
axoConfig.enabled_shipping_locations || [];
|
||||
|
||||
return Array.isArray( enabledShippingLocations )
|
||||
? enabledShippingLocations
|
||||
: [];
|
||||
}, [ axoConfig.enabled_shipping_locations ] );
|
||||
};
|
||||
|
||||
export default useAllowedLocations;
|
|
@ -4,6 +4,7 @@ import Fastlane from '../../../../ppcp-axo/resources/js/Connection/Fastlane';
|
|||
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
|
||||
import { useDeleteEmptyKeys } from './useDeleteEmptyKeys';
|
||||
import useCardOptions from './useCardOptions';
|
||||
import useAllowedLocations from './useAllowedLocations';
|
||||
import { STORE_NAME } from '../stores/axoStore';
|
||||
|
||||
/**
|
||||
|
@ -33,6 +34,8 @@ const useFastlaneSdk = ( namespace, axoConfig, ppcpConfig ) => {
|
|||
return deleteEmptyKeys( configRef.current.axoConfig.style_options );
|
||||
}, [ deleteEmptyKeys ] );
|
||||
|
||||
const allowedLocations = useAllowedLocations( axoConfig );
|
||||
|
||||
// Effect to initialize Fastlane SDK
|
||||
useEffect( () => {
|
||||
const initFastlane = async () => {
|
||||
|
@ -58,6 +61,9 @@ const useFastlaneSdk = ( namespace, axoConfig, ppcpConfig ) => {
|
|||
cardOptions: {
|
||||
allowedBrands: cardOptions,
|
||||
},
|
||||
shippingAddressOptions: {
|
||||
allowedLocations,
|
||||
},
|
||||
} );
|
||||
|
||||
// Set locale (hardcoded to 'en_us' for now)
|
||||
|
@ -72,7 +78,14 @@ const useFastlaneSdk = ( namespace, axoConfig, ppcpConfig ) => {
|
|||
};
|
||||
|
||||
initFastlane();
|
||||
}, [ fastlaneSdk, styleOptions, isPayPalLoaded, namespace, cardOptions ] );
|
||||
}, [
|
||||
fastlaneSdk,
|
||||
styleOptions,
|
||||
isPayPalLoaded,
|
||||
namespace,
|
||||
cardOptions,
|
||||
allowedLocations,
|
||||
] );
|
||||
|
||||
// Effect to update the config ref when configs change
|
||||
useEffect( () => {
|
||||
|
|
|
@ -39,6 +39,7 @@ return array(
|
|||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'wcgateway.url' ),
|
||||
$container->get( 'axo.supported-country-card-type-matrix' ),
|
||||
$container->get( 'axo.shipping-wc-enabled-locations' )
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -86,6 +86,13 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
*/
|
||||
private $supported_country_card_type_matrix;
|
||||
|
||||
/**
|
||||
* The list of WooCommerce enabled shipping locations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $enabled_shipping_locations;
|
||||
|
||||
/**
|
||||
* AdvancedCardPaymentMethod constructor.
|
||||
*
|
||||
|
@ -99,6 +106,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
* @param Environment $environment The environment object.
|
||||
* @param string $wcgateway_module_url The WcGateway module URL.
|
||||
* @param array $supported_country_card_type_matrix The supported country card type matrix for Axo.
|
||||
* @param array $enabled_shipping_locations The list of WooCommerce enabled shipping locations.
|
||||
*/
|
||||
public function __construct(
|
||||
string $module_url,
|
||||
|
@ -109,7 +117,8 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
DCCGatewayConfiguration $dcc_configuration,
|
||||
Environment $environment,
|
||||
string $wcgateway_module_url,
|
||||
array $supported_country_card_type_matrix
|
||||
array $supported_country_card_type_matrix,
|
||||
array $enabled_shipping_locations
|
||||
) {
|
||||
$this->name = AxoGateway::ID;
|
||||
$this->module_url = $module_url;
|
||||
|
@ -121,6 +130,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
$this->environment = $environment;
|
||||
$this->wcgateway_module_url = $wcgateway_module_url;
|
||||
$this->supported_country_card_type_matrix = $supported_country_card_type_matrix;
|
||||
$this->enabled_shipping_locations = $enabled_shipping_locations;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,6 +228,7 @@ class AxoBlockPaymentMethod extends AbstractPaymentMethodType {
|
|||
),
|
||||
'allowed_cards' => $this->supported_country_card_type_matrix,
|
||||
'disable_cards' => $this->settings->has( 'disable_cards' ) ? (array) $this->settings->get( 'disable_cards' ) : array(),
|
||||
'enabled_shipping_locations' => $this->enabled_shipping_locations,
|
||||
'style_options' => array(
|
||||
'root' => array(
|
||||
'backgroundColor' => $this->settings->has( 'axo_style_root_bg_color' ) ? $this->settings->get( 'axo_style_root_bg_color' ) : '',
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,20 +11,20 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@paypal/paypal-js": "^6.0.0",
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"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",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -87,6 +87,9 @@ class AxoManager {
|
|||
|
||||
this.cardOptions = this.getCardOptions();
|
||||
|
||||
this.enabledShippingLocations =
|
||||
this.axoConfig.enabled_shipping_locations;
|
||||
|
||||
this.registerEventHandlers();
|
||||
|
||||
this.shippingView = new ShippingView(
|
||||
|
@ -666,6 +669,9 @@ class AxoManager {
|
|||
cardOptions: {
|
||||
allowedBrands: this.cardOptions,
|
||||
},
|
||||
shippingAddressOptions: {
|
||||
allowedLocations: this.enabledShippingLocations,
|
||||
},
|
||||
} );
|
||||
|
||||
this.fastlane.setLocale( 'en_us' );
|
||||
|
|
|
@ -68,7 +68,8 @@ return array(
|
|||
$container->get( 'api.shop.currency.getter' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||
$container->get( 'wcgateway.url' ),
|
||||
$container->get( 'axo.supported-country-card-type-matrix' )
|
||||
$container->get( 'axo.supported-country-card-type-matrix' ),
|
||||
$container->get( 'axo.shipping-wc-enabled-locations' )
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -276,4 +277,46 @@ return array(
|
|||
$active_plugins_list
|
||||
);
|
||||
},
|
||||
|
||||
'axo.shipping-wc-enabled-locations' => static function ( ContainerInterface $container ): array {
|
||||
$default_zone = new \WC_Shipping_Zone( 0 );
|
||||
|
||||
$is_method_enabled = fn( \WC_Shipping_Method $method): bool => $method->enabled === 'yes';
|
||||
|
||||
$is_default_zone_enabled = ! empty(
|
||||
array_filter(
|
||||
$default_zone->get_shipping_methods(),
|
||||
$is_method_enabled
|
||||
)
|
||||
);
|
||||
|
||||
if ( $is_default_zone_enabled ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$shipping_zones = \WC_Shipping_Zones::get_zones();
|
||||
|
||||
$get_zone_locations = fn( \WC_Shipping_Zone $zone): array =>
|
||||
! empty( array_filter( $zone->get_shipping_methods(), $is_method_enabled ) )
|
||||
? array_map(
|
||||
fn( object $location): string => $location->code,
|
||||
$zone->get_zone_locations()
|
||||
)
|
||||
: array();
|
||||
|
||||
$enabled_locations = array_unique(
|
||||
array_merge(
|
||||
...array_map(
|
||||
$get_zone_locations,
|
||||
array_map(
|
||||
fn( $zone): \WC_Shipping_Zone =>
|
||||
$zone instanceof \WC_Shipping_Zone ? $zone : new \WC_Shipping_Zone( $zone['id'] ),
|
||||
$shipping_zones
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $enabled_locations;
|
||||
},
|
||||
);
|
||||
|
|
|
@ -92,6 +92,13 @@ class AxoManager {
|
|||
* @var array
|
||||
*/
|
||||
private array $supported_country_card_type_matrix;
|
||||
/**
|
||||
* The list of WooCommerce enabled shipping locations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private array $enabled_shipping_locations;
|
||||
|
||||
/**
|
||||
* AxoManager constructor.
|
||||
*
|
||||
|
@ -105,6 +112,7 @@ class AxoManager {
|
|||
* @param LoggerInterface $logger The logger.
|
||||
* @param string $wcgateway_module_url The WcGateway module URL.
|
||||
* @param array $supported_country_card_type_matrix The supported country card type matrix for Axo.
|
||||
* @param array $enabled_shipping_locations The list of WooCommerce enabled shipping locations.
|
||||
*/
|
||||
public function __construct(
|
||||
string $module_url,
|
||||
|
@ -116,7 +124,8 @@ class AxoManager {
|
|||
CurrencyGetter $currency,
|
||||
LoggerInterface $logger,
|
||||
string $wcgateway_module_url,
|
||||
array $supported_country_card_type_matrix
|
||||
array $supported_country_card_type_matrix,
|
||||
array $enabled_shipping_locations
|
||||
) {
|
||||
|
||||
$this->module_url = $module_url;
|
||||
|
@ -129,6 +138,7 @@ class AxoManager {
|
|||
$this->logger = $logger;
|
||||
$this->wcgateway_module_url = $wcgateway_module_url;
|
||||
$this->supported_country_card_type_matrix = $supported_country_card_type_matrix;
|
||||
$this->enabled_shipping_locations = $enabled_shipping_locations;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -193,6 +203,7 @@ class AxoManager {
|
|||
),
|
||||
'allowed_cards' => $this->supported_country_card_type_matrix,
|
||||
'disable_cards' => $this->settings->has( 'disable_cards' ) ? (array) $this->settings->get( 'disable_cards' ) : array(),
|
||||
'enabled_shipping_locations' => $this->enabled_shipping_locations,
|
||||
'style_options' => array(
|
||||
'root' => array(
|
||||
'backgroundColor' => $this->settings->has( 'axo_style_root_bg_color' ) ? $this->settings->get( 'axo_style_root_bg_color' ) : '',
|
||||
|
|
|
@ -222,9 +222,11 @@ class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
|||
// Render submit button.
|
||||
add_action(
|
||||
$manager->checkout_button_renderer_hook(),
|
||||
static function () use ( $c, $manager ) {
|
||||
static function () use ( $c, $manager, $module ) {
|
||||
if ( $module->should_render_fastlane( $c ) ) {
|
||||
$manager->render_checkout_button();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,23 +11,23 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@paypal/react-paypal-js": "^8.5.0",
|
||||
"core-js": "^3.25.0",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0"
|
||||
"core-js": "^3.39",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@wordpress/i18n": "^5.6.0",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@wordpress/i18n": "^5.11",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^8.2",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
3
modules/ppcp-blocks/resources/css/gateway-editor.scss
Normal file
3
modules/ppcp-blocks/resources/css/gateway-editor.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
li[id^="express-payment-method-ppcp-"] div[class^="ppc-button-container-"] {
|
||||
display: flex;
|
||||
}
|
|
@ -19,11 +19,9 @@ export function CardFields( {
|
|||
config,
|
||||
eventRegistration,
|
||||
emitResponse,
|
||||
components,
|
||||
} ) {
|
||||
const { onPaymentSetup } = eventRegistration;
|
||||
const { responseTypes } = emitResponse;
|
||||
const { PaymentMethodIcons } = components;
|
||||
|
||||
const [ cardFieldsForm, setCardFieldsForm ] = useState();
|
||||
const getCardFieldsForm = ( cardFieldsForm ) => {
|
||||
|
@ -95,10 +93,6 @@ export function CardFields( {
|
|||
} }
|
||||
>
|
||||
<PayPalCardFieldsForm />
|
||||
<PaymentMethodIcons
|
||||
icons={ config.card_icons }
|
||||
align="left"
|
||||
/>
|
||||
<CheckoutHandler
|
||||
getCardFieldsForm={ getCardFieldsForm }
|
||||
getSavePayment={ getSavePayment }
|
||||
|
|
|
@ -1,9 +1,44 @@
|
|||
export const debounce = ( callback, delayMs ) => {
|
||||
let timeoutId = null;
|
||||
return ( ...args ) => {
|
||||
window.clearTimeout( timeoutId );
|
||||
timeoutId = window.setTimeout( () => {
|
||||
callback.apply( null, args );
|
||||
}, delayMs );
|
||||
const state = {
|
||||
timeoutId: null,
|
||||
args: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancels any pending debounced execution.
|
||||
*/
|
||||
const cancel = () => {
|
||||
if ( state.timeoutId ) {
|
||||
window.clearTimeout( state.timeoutId );
|
||||
}
|
||||
|
||||
state.timeoutId = null;
|
||||
state.args = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Immediately executes the debounced function if there's a pending execution.
|
||||
* @return {void}
|
||||
*/
|
||||
const flush = () => {
|
||||
// If there's nothing pending, return early.
|
||||
if ( ! state.timeoutId ) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback.apply( null, state.args || [] );
|
||||
cancel();
|
||||
};
|
||||
|
||||
const debouncedFunc = ( ...args ) => {
|
||||
cancel();
|
||||
state.args = args;
|
||||
state.timeoutId = window.setTimeout( flush, delayMs );
|
||||
};
|
||||
|
||||
// Attach utility methods
|
||||
debouncedFunc.cancel = cancel;
|
||||
debouncedFunc.flush = flush;
|
||||
|
||||
return debouncedFunc;
|
||||
};
|
||||
|
|
|
@ -1,13 +1,24 @@
|
|||
import { registerPaymentMethod } from '@woocommerce/blocks-registry';
|
||||
import { CardFields } from './Components/card-fields';
|
||||
import {registerPaymentMethod} from '@woocommerce/blocks-registry';
|
||||
import {CardFields} from './Components/card-fields';
|
||||
|
||||
const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' );
|
||||
const config = wc.wcSettings.getSetting('ppcp-credit-card-gateway_data');
|
||||
|
||||
registerPaymentMethod( {
|
||||
const Label = ({components, config}) => {
|
||||
const {PaymentMethodIcons} = components;
|
||||
return <>
|
||||
<span dangerouslySetInnerHTML={{__html: config.title}}/>
|
||||
<PaymentMethodIcons
|
||||
icons={ config.card_icons }
|
||||
align="right"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
registerPaymentMethod({
|
||||
name: config.id,
|
||||
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
|
||||
content: <CardFields config={ config } />,
|
||||
edit: <CardFields config={ config } />,
|
||||
label: <Label config={config}/>,
|
||||
content: <CardFields config={config}/>,
|
||||
edit: <CardFields config={config}/>,
|
||||
ariaLabel: config.title,
|
||||
canMakePayment: () => {
|
||||
return true;
|
||||
|
@ -16,4 +27,4 @@ registerPaymentMethod( {
|
|||
showSavedCards: true,
|
||||
features: config.supports,
|
||||
},
|
||||
} );
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from '@wordpress/element';
|
||||
import { useEffect, useState, useMemo } from '@wordpress/element';
|
||||
import {
|
||||
registerExpressPaymentMethod,
|
||||
registerPaymentMethod,
|
||||
|
@ -41,6 +41,7 @@ const PayPalComponent = ( {
|
|||
shippingData,
|
||||
isEditing,
|
||||
fundingSource,
|
||||
buttonAttributes,
|
||||
} ) => {
|
||||
const { onPaymentSetup, onCheckoutFail, onCheckoutValidation } =
|
||||
eventRegistration;
|
||||
|
@ -614,6 +615,15 @@ const PayPalComponent = ( {
|
|||
fundingSource
|
||||
);
|
||||
|
||||
if ( typeof buttonAttributes !== 'undefined' ) {
|
||||
style.height = buttonAttributes?.height
|
||||
? Number( buttonAttributes.height )
|
||||
: style.height;
|
||||
style.borderRadius = buttonAttributes?.borderRadius
|
||||
? Number( buttonAttributes.borderRadius )
|
||||
: style.borderRadius;
|
||||
}
|
||||
|
||||
if ( ! paypalScriptLoaded ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -688,19 +698,46 @@ const PayPalComponent = ( {
|
|||
);
|
||||
};
|
||||
|
||||
const BlockEditorPayPalComponent = () => {
|
||||
const urlParams = {
|
||||
const BlockEditorPayPalComponent = ( { fundingSource, buttonAttributes } ) => {
|
||||
const urlParams = useMemo(
|
||||
() => ( {
|
||||
clientId: 'test',
|
||||
...config.scriptData.url_params,
|
||||
dataNamespace: 'ppcp-blocks-editor-paypal-buttons',
|
||||
components: 'buttons',
|
||||
} ),
|
||||
[]
|
||||
);
|
||||
|
||||
const style = useMemo( () => {
|
||||
const configStyle = normalizeStyleForFundingSource(
|
||||
config.scriptData.button.style,
|
||||
fundingSource
|
||||
);
|
||||
|
||||
if ( buttonAttributes ) {
|
||||
return {
|
||||
...configStyle,
|
||||
height: buttonAttributes.height
|
||||
? Number( buttonAttributes.height )
|
||||
: configStyle.height,
|
||||
borderRadius: buttonAttributes.borderRadius
|
||||
? Number( buttonAttributes.borderRadius )
|
||||
: configStyle.borderRadius,
|
||||
};
|
||||
}
|
||||
|
||||
return configStyle;
|
||||
}, [ fundingSource, buttonAttributes ] );
|
||||
|
||||
return (
|
||||
<PayPalScriptProvider options={ urlParams }>
|
||||
<PayPalButtons
|
||||
onClick={ ( data, actions ) => {
|
||||
return false;
|
||||
} }
|
||||
className={ `ppc-button-container-${ fundingSource }` }
|
||||
fundingSource={ fundingSource }
|
||||
style={ style }
|
||||
forceReRender={ [ buttonAttributes || {} ] }
|
||||
onClick={ () => false }
|
||||
/>
|
||||
</PayPalScriptProvider>
|
||||
);
|
||||
|
@ -739,11 +776,8 @@ if ( cartHasSubscriptionProducts( config.scriptData ) ) {
|
|||
features.push( 'subscriptions' );
|
||||
}
|
||||
|
||||
if ( block_enabled && config.enabled ) {
|
||||
if (
|
||||
( config.addPlaceOrderMethod || config.usePlaceOrder ) &&
|
||||
! config.scriptData.continuation
|
||||
) {
|
||||
if ( block_enabled ) {
|
||||
if ( config.placeOrderEnabled && ! config.scriptData.continuation ) {
|
||||
let descriptionElement = (
|
||||
<div
|
||||
dangerouslySetInnerHTML={ { __html: config.description } }
|
||||
|
@ -776,7 +810,7 @@ if ( block_enabled && config.enabled ) {
|
|||
placeOrderButtonLabel: config.placeOrderButtonText,
|
||||
ariaLabel: config.title,
|
||||
canMakePayment: () => {
|
||||
return config.enabled;
|
||||
return true;
|
||||
},
|
||||
supports: {
|
||||
features,
|
||||
|
@ -789,7 +823,7 @@ if ( block_enabled && config.enabled ) {
|
|||
name: config.id,
|
||||
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
|
||||
content: <PayPalComponent isEditing={ false } />,
|
||||
edit: <BlockEditorPayPalComponent />,
|
||||
edit: <BlockEditorPayPalComponent fundingSource={ 'paypal' } />,
|
||||
ariaLabel: config.title,
|
||||
canMakePayment: () => {
|
||||
return true;
|
||||
|
@ -798,7 +832,7 @@ if ( block_enabled && config.enabled ) {
|
|||
features: [ ...features, 'ppcp_continuation' ],
|
||||
},
|
||||
} );
|
||||
} else if ( ! config.usePlaceOrder ) {
|
||||
} else if ( config.smartButtonsEnabled ) {
|
||||
for ( const fundingSource of [
|
||||
'paypal',
|
||||
...config.enabledFundingSources,
|
||||
|
@ -821,7 +855,11 @@ if ( block_enabled && config.enabled ) {
|
|||
fundingSource={ fundingSource }
|
||||
/>
|
||||
),
|
||||
edit: <BlockEditorPayPalComponent />,
|
||||
edit: (
|
||||
<BlockEditorPayPalComponent
|
||||
fundingSource={ fundingSource }
|
||||
/>
|
||||
),
|
||||
ariaLabel: config.title,
|
||||
canMakePayment: async () => {
|
||||
if ( ! paypalScriptPromise ) {
|
||||
|
@ -845,6 +883,7 @@ if ( block_enabled && config.enabled ) {
|
|||
},
|
||||
supports: {
|
||||
features,
|
||||
style: [ 'height', 'borderRadius' ],
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ return array(
|
|||
$container->get( 'blocks.settings.final_review_enabled' ),
|
||||
$container->get( 'session.cancellation.view' ),
|
||||
$container->get( 'session.handler' ),
|
||||
$container->get( 'wc-subscriptions.helper' ),
|
||||
$container->get( 'blocks.add-place-order-method' ),
|
||||
$container->get( 'wcgateway.use-place-order-button' ),
|
||||
$container->get( 'wcgateway.place-order-button-text' ),
|
||||
|
|
|
@ -126,6 +126,23 @@ class BlocksModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
|||
}
|
||||
);
|
||||
|
||||
// Enqueue editor styles.
|
||||
add_action(
|
||||
'enqueue_block_editor_assets',
|
||||
static function () use ( $c ) {
|
||||
$module_url = $c->get( 'blocks.url' );
|
||||
$asset_version = $c->get( 'ppcp.asset-version' );
|
||||
|
||||
wp_register_style(
|
||||
'wc-ppcp-blocks-editor',
|
||||
untrailingslashit( $module_url ) . '/assets/css/gateway-editor.css',
|
||||
array(),
|
||||
$asset_version
|
||||
);
|
||||
wp_enqueue_style( 'wc-ppcp-blocks-editor' );
|
||||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'woocommerce_paypal_payments_sdk_components_hook',
|
||||
function( array $components ) {
|
||||
|
|
|
@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
|
||||
/**
|
||||
* Class PayPalPaymentMethod
|
||||
|
@ -87,6 +88,13 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* The Subscription Helper.
|
||||
*
|
||||
* @var SubscriptionHelper
|
||||
*/
|
||||
private $subscription_helper;
|
||||
|
||||
/**
|
||||
* Whether to create a non-express method with the standard "Place order" button.
|
||||
*
|
||||
|
@ -141,6 +149,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
* @param bool $final_review_enabled Whether the final review is enabled.
|
||||
* @param CancelView $cancellation_view The cancellation view.
|
||||
* @param SessionHandler $session_handler The Session handler.
|
||||
* @param SubscriptionHelper $subscription_helper The subscription helper.
|
||||
* @param bool $add_place_order_method Whether to create a non-express method with the standard "Place order" button.
|
||||
* @param bool $use_place_order Whether to use the standard "Place order" button instead of PayPal buttons.
|
||||
* @param string $place_order_button_text The text for the standard "Place order" button.
|
||||
|
@ -158,6 +167,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
bool $final_review_enabled,
|
||||
CancelView $cancellation_view,
|
||||
SessionHandler $session_handler,
|
||||
SubscriptionHelper $subscription_helper,
|
||||
bool $add_place_order_method,
|
||||
bool $use_place_order,
|
||||
string $place_order_button_text,
|
||||
|
@ -175,6 +185,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
$this->final_review_enabled = $final_review_enabled;
|
||||
$this->cancellation_view = $cancellation_view;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->subscription_helper = $subscription_helper;
|
||||
$this->add_place_order_method = $add_place_order_method;
|
||||
$this->use_place_order = $use_place_order;
|
||||
$this->place_order_button_text = $place_order_button_text;
|
||||
|
@ -195,9 +206,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
// Do not load when definitely not needed,
|
||||
// but we still need to check the locations later and handle in JS
|
||||
// because has_block cannot be called here (too early).
|
||||
return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' )
|
||||
&& ( $this->settings_status->is_smart_button_enabled_for_location( 'checkout-block-express' ) ||
|
||||
$this->settings_status->is_smart_button_enabled_for_location( 'cart-block' ) );
|
||||
return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,15 +254,19 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
|||
);
|
||||
}
|
||||
|
||||
$smart_buttons_enabled = ! $this->use_place_order
|
||||
&& $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ?? 'block-checkout' );
|
||||
$place_order_enabled = ( $this->use_place_order || $this->add_place_order_method )
|
||||
&& ! $this->subscription_helper->cart_contains_subscription();
|
||||
|
||||
return array(
|
||||
'id' => $this->gateway->id,
|
||||
'title' => $this->gateway->title,
|
||||
'description' => $this->gateway->description,
|
||||
'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ?? 'checkout' ),
|
||||
'smartButtonsEnabled' => $smart_buttons_enabled,
|
||||
'placeOrderEnabled' => $place_order_enabled,
|
||||
'fundingSource' => $this->session_handler->funding_source(),
|
||||
'finalReviewEnabled' => $this->final_review_enabled,
|
||||
'addPlaceOrderMethod' => $this->add_place_order_method,
|
||||
'usePlaceOrder' => $this->use_place_order,
|
||||
'placeOrderButtonText' => $this->place_order_button_text,
|
||||
'placeOrderButtonDescription' => $this->place_order_button_description,
|
||||
'enabledFundingSources' => $funding_sources,
|
||||
|
|
|
@ -11,7 +11,8 @@ module.exports = {
|
|||
entry: {
|
||||
'checkout-block': path.resolve('./resources/js/checkout-block.js'),
|
||||
'advanced-card-checkout-block': path.resolve('./resources/js/advanced-card-checkout-block.js'),
|
||||
"gateway": path.resolve('./resources/css/gateway.scss')
|
||||
"gateway": path.resolve('./resources/css/gateway.scss'),
|
||||
"gateway-editor": path.resolve('./resources/css/gateway-editor.scss')
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'assets/'),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,20 +12,20 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@paypal/paypal-js": "^6.0.0",
|
||||
"core-js": "^3.25.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"core-js": "^3.39",
|
||||
"deepmerge": "^4.3",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -233,6 +233,15 @@ class SingleProductBootstap {
|
|||
this.form(),
|
||||
this.errorHandler
|
||||
);
|
||||
if (
|
||||
! this.gateway.vaultingEnabled &&
|
||||
[ 'subscription', 'variable-subscription' ].includes(
|
||||
this.gateway.productType
|
||||
) &&
|
||||
this.gateway.manualRenewalEnabled !== '1'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
PayPalCommerceGateway.data_client_id.has_subscriptions &&
|
||||
|
|
|
@ -38,6 +38,12 @@ class CardFieldsFreeTrialRenderer {
|
|||
if ( hideDccGateway ) {
|
||||
hideDccGateway.parentNode.removeChild( hideDccGateway );
|
||||
}
|
||||
const dccGatewayLi = document.querySelector(
|
||||
'.wc_payment_method.payment_method_ppcp-credit-card-gateway'
|
||||
);
|
||||
if (dccGatewayLi.style.display === 'none' || dccGatewayLi.style.display === '') {
|
||||
dccGatewayLi.style.display = 'block';
|
||||
}
|
||||
|
||||
this.errorHandler.clear();
|
||||
|
||||
|
|
|
@ -44,6 +44,12 @@ class CardFieldsRenderer {
|
|||
if ( hideDccGateway ) {
|
||||
hideDccGateway.parentNode.removeChild( hideDccGateway );
|
||||
}
|
||||
const dccGatewayLi = document.querySelector(
|
||||
'.wc_payment_method.payment_method_ppcp-credit-card-gateway'
|
||||
);
|
||||
if (dccGatewayLi.style.display === 'none' || dccGatewayLi.style.display === '') {
|
||||
dccGatewayLi.style.display = 'block';
|
||||
}
|
||||
|
||||
const cardFields = paypal.CardFields( {
|
||||
createOrder: contextConfig.createOrder,
|
||||
|
|
|
@ -52,6 +52,12 @@ class HostedFieldsRenderer {
|
|||
if ( hideDccGateway ) {
|
||||
hideDccGateway.parentNode.removeChild( hideDccGateway );
|
||||
}
|
||||
const dccGatewayLi = document.querySelector(
|
||||
'.wc_payment_method.payment_method_ppcp-credit-card-gateway'
|
||||
);
|
||||
if (dccGatewayLi.style.display === 'none' || dccGatewayLi.style.display === '') {
|
||||
dccGatewayLi.style.display = 'block';
|
||||
}
|
||||
|
||||
const cardNumberField = document.querySelector(
|
||||
'#ppcp-credit-card-gateway-card-number'
|
||||
|
|
|
@ -10,6 +10,8 @@ import {
|
|||
dispatchButtonEvent,
|
||||
observeButtonEvent,
|
||||
} from '../Helper/PaymentButtonHelpers';
|
||||
import { isVisible } from '../Helper/Hiding';
|
||||
import { isDisabled, setEnabled } from '../Helper/ButtonDisabler';
|
||||
|
||||
/**
|
||||
* Collection of all available styling options for this button.
|
||||
|
@ -173,9 +175,9 @@ export default class PaymentButton {
|
|||
/**
|
||||
* Whether the current browser/website support the payment method.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @type {?boolean}
|
||||
*/
|
||||
#isEligible = false;
|
||||
#isEligible = null;
|
||||
|
||||
/**
|
||||
* Whether this button is visible. Modified by `show()` and `hide()`
|
||||
|
@ -184,6 +186,13 @@ export default class PaymentButton {
|
|||
*/
|
||||
#isVisible = true;
|
||||
|
||||
/**
|
||||
* Whether this button is enabled (can be clicked).
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
#isEnabled = true;
|
||||
|
||||
/**
|
||||
* The currently visible payment button.
|
||||
*
|
||||
|
@ -192,6 +201,13 @@ export default class PaymentButton {
|
|||
*/
|
||||
#button = null;
|
||||
|
||||
/**
|
||||
* List of checks to perform to verify the PaymentButton has is configured correctly.
|
||||
*
|
||||
* @type {{check, errorMessage, shouldPass}[]}
|
||||
*/
|
||||
#validationChecks = [];
|
||||
|
||||
/**
|
||||
* Factory method to create a new PaymentButton while limiting a single instance per context.
|
||||
*
|
||||
|
@ -305,6 +321,11 @@ export default class PaymentButton {
|
|||
);
|
||||
this.applyButtonStyles( this.#buttonConfig );
|
||||
|
||||
this.registerValidationRules(
|
||||
this.#assertIsInvalid.bind( this ),
|
||||
this.#assertIsValid.bind( this )
|
||||
);
|
||||
|
||||
apmButtonsInit( this.#ppcpConfig );
|
||||
this.initEventListeners();
|
||||
}
|
||||
|
@ -559,6 +580,29 @@ export default class PaymentButton {
|
|||
this.triggerRedraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* The enabled/disabled state of the button (whether it can be clicked).
|
||||
*
|
||||
* @return {boolean} True indicates, that the button is enabled.
|
||||
*/
|
||||
get isEnabled() {
|
||||
return this.#isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the enabled/disabled state of the button.
|
||||
*
|
||||
* @param {boolean} newState Whether the button is enabled.
|
||||
*/
|
||||
set isEnabled( newState ) {
|
||||
if ( this.#isEnabled === newState ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#isEnabled = newState;
|
||||
this.triggerRedraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTML element that wraps the current button
|
||||
*
|
||||
|
@ -569,6 +613,23 @@ export default class PaymentButton {
|
|||
return document.getElementById( this.wrapperId );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard PayPal smart button selector for the current context.
|
||||
*
|
||||
* @return {string | null} The selector, or null if not available.
|
||||
*/
|
||||
get ppcpButtonWrapperSelector() {
|
||||
if ( PaymentContext.Blocks.includes( this.context ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( this.context === PaymentContext.MiniCart ) {
|
||||
return this.ppcpConfig?.button?.mini_cart_wrapper;
|
||||
}
|
||||
|
||||
return this.ppcpConfig?.button?.wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the main button-wrapper is present in the current DOM.
|
||||
*
|
||||
|
@ -634,16 +695,75 @@ export default class PaymentButton {
|
|||
this.#logger.group( label );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a validation check that marks the configuration as invalid when passed.
|
||||
*
|
||||
* @param {Function} check - A function that returns a truthy value if the check passes.
|
||||
* @param {string} errorMessage - The error message to display if the check fails.
|
||||
*/
|
||||
#assertIsInvalid( check, errorMessage ) {
|
||||
this.#validationChecks.push( {
|
||||
check,
|
||||
errorMessage,
|
||||
shouldPass: false,
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a validation check that instantly marks the configuration as valid when passed.
|
||||
*
|
||||
* @param {Function} check - A function that returns a truthy value if the check passes.
|
||||
*/
|
||||
#assertIsValid( check ) {
|
||||
this.#validationChecks.push( { check, shouldPass: true } );
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a series of validation steps to ensure the payment button is configured correctly.
|
||||
*
|
||||
* Each validation step is executed in the order they are defined within this method.
|
||||
*
|
||||
* If a validation step using `invalidIf` returns true, the configuration is immediately considered
|
||||
* invalid, and an error message is logged. Conversely, if a validation step using `validIf`
|
||||
* returns true, the configuration is immediately considered valid.
|
||||
*
|
||||
* If no validation step returns true, the configuration is assumed to be valid by default.
|
||||
*
|
||||
* @param {(condition: () => boolean, errorMessage: string) => void} invalidIf - Registers a validation step that fails if the condition returns true.
|
||||
* @param {(condition: () => boolean) => void} validIf - Registers a validation step that passes if the condition returns true.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
registerValidationRules( invalidIf, validIf ) {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* All required validation steps must be registered in the constructor of the derived class
|
||||
* using `this.addValidationFailure()` or `this.addValidationSuccess()`.
|
||||
*
|
||||
* @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 ) {
|
||||
for ( const step of this.#validationChecks ) {
|
||||
const result = step.check();
|
||||
|
||||
if ( step.shouldPass && result ) {
|
||||
// If a success check passes, mark as valid immediately.
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! step.shouldPass && result ) {
|
||||
// If a failure check passes, mark as invalid.
|
||||
if ( ! silent && step.errorMessage ) {
|
||||
this.error( step.errorMessage );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -697,7 +817,23 @@ export default class PaymentButton {
|
|||
}
|
||||
|
||||
/**
|
||||
* Attaches event listeners to show or hide the payment button when needed.
|
||||
* Applies the visibility and enabled state from the PayPal button.
|
||||
* Intended for the product page, may not work correctly on the checkout page.
|
||||
*/
|
||||
syncProductButtonsState() {
|
||||
const ppcpButton = document.querySelector(
|
||||
this.ppcpButtonWrapperSelector
|
||||
);
|
||||
if ( ! ppcpButton ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isVisible = isVisible( ppcpButton );
|
||||
this.isEnabled = ! isDisabled( ppcpButton );
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches event listeners to show/hide or enable/disable the payment button when needed.
|
||||
*/
|
||||
initEventListeners() {
|
||||
// Refresh the button - this might show, hide or re-create the payment button.
|
||||
|
@ -726,6 +862,24 @@ export default class PaymentButton {
|
|||
callback: () => ( this.isVisible = true ),
|
||||
} );
|
||||
}
|
||||
|
||||
// On the product page, copy the visibility and enabled state from the PayPal button.
|
||||
if ( this.context === PaymentContext.Product ) {
|
||||
jQuery( document ).on(
|
||||
'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled',
|
||||
( ev, data ) => {
|
||||
if (
|
||||
! jQuery( data.selector ).is(
|
||||
this.ppcpButtonWrapperSelector
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncProductButtonsState();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -735,6 +889,10 @@ export default class PaymentButton {
|
|||
if ( ! this.isPresent ) {
|
||||
return;
|
||||
}
|
||||
if ( ! this.isEligible ) {
|
||||
this.wrapperElement.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
this.applyWrapperStyles();
|
||||
|
||||
|
@ -763,11 +921,16 @@ export default class PaymentButton {
|
|||
|
||||
const styleSelector = `style[data-hide-gateway="${ this.methodId }"]`;
|
||||
const wrapperSelector = `#${ this.wrappers.Default }`;
|
||||
const paymentMethodLi = document.querySelector(`.wc_payment_method.payment_method_${ this.methodId }`);
|
||||
|
||||
document
|
||||
.querySelectorAll( styleSelector )
|
||||
.forEach( ( el ) => el.remove() );
|
||||
|
||||
if (paymentMethodLi.style.display === 'none' || paymentMethodLi.style.display === '') {
|
||||
paymentMethodLi.style.display = 'block';
|
||||
}
|
||||
|
||||
document
|
||||
.querySelectorAll( wrapperSelector )
|
||||
.forEach( ( el ) => el.remove() );
|
||||
|
@ -807,6 +970,12 @@ export default class PaymentButton {
|
|||
|
||||
// Apply the wrapper visibility.
|
||||
wrapper.style.display = this.isVisible ? 'block' : 'none';
|
||||
|
||||
// Apply the enabled/disabled state.
|
||||
// On the product page, use the form to display error messages if clicked while disabled.
|
||||
const form =
|
||||
this.context === PaymentContext.Product ? 'form.cart' : null;
|
||||
setEnabled( wrapper, this.isEnabled, form );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,6 +37,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
|
|||
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
|
||||
return array(
|
||||
'button.client_id' => static function ( ContainerInterface $container ): string {
|
||||
|
@ -101,12 +102,20 @@ return array(
|
|||
return $obj->get_context();
|
||||
},
|
||||
'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface {
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
$context = $container->get( 'button.context' );
|
||||
|
||||
$settings_status = $container->get( 'wcgateway.settings.status' );
|
||||
assert( $settings_status instanceof SettingsStatus );
|
||||
|
||||
if ( in_array( $context, array( 'checkout', 'pay-now' ), true ) ) {
|
||||
if ( $container->get( 'wcgateway.use-place-order-button' )
|
||||
&& in_array( $container->get( 'button.context' ), array( 'checkout', 'pay-now' ), true )
|
||||
|| ! $settings_status->is_smart_button_enabled_for_location( $context )
|
||||
) {
|
||||
return new DisabledSmartButton();
|
||||
}
|
||||
}
|
||||
|
||||
$state = $container->get( 'onboarding.state' );
|
||||
if ( $state->current_state() !== State::STATE_ONBOARDED ) {
|
||||
return new DisabledSmartButton();
|
||||
}
|
||||
|
@ -125,7 +134,6 @@ return array(
|
|||
$messages_apply = $container->get( 'button.helper.messages-apply' );
|
||||
$environment = $container->get( 'onboarding.environment' );
|
||||
$payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
|
||||
$settings_status = $container->get( 'wcgateway.settings.status' );
|
||||
return new SmartButton(
|
||||
$container->get( 'button.url' ),
|
||||
$container->get( 'ppcp.asset-version' ),
|
||||
|
|
|
@ -1031,8 +1031,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
|
|||
* @return bool
|
||||
*/
|
||||
private function has_subscriptions(): bool {
|
||||
if ( ! $this->subscription_helper->plugin_is_active() ) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
! $this->subscription_helper->accept_only_automatic_payment_gateways()
|
||||
$this->subscription_helper->accept_manual_renewals()
|
||||
&& $this->paypal_subscriptions_enabled() !== true
|
||||
) {
|
||||
return false;
|
||||
|
@ -1318,8 +1321,17 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
|
|||
'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(),
|
||||
'needShipping' => $this->need_shipping(),
|
||||
'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ),
|
||||
'productType' => null,
|
||||
'manualRenewalEnabled' => $this->subscription_helper->accept_manual_renewals(),
|
||||
);
|
||||
|
||||
if ( is_product() ) {
|
||||
$product = wc_get_product( get_the_ID() );
|
||||
if ( is_a( $product, \WC_Product::class ) ) {
|
||||
$localize['productType'] = $product->get_type();
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'pay-now' === $this->context() ) {
|
||||
$localize['pay_now'] = $this->pay_now_script_data();
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,18 +10,18 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,18 +11,18 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -101,7 +101,7 @@ class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
|||
add_action(
|
||||
'woocommerce_init',
|
||||
function() {
|
||||
if ( is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
|
||||
if ( is_admin() && is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
|
||||
PPEC\DeactivateNote::init();
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,21 +11,21 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@paypal/paypal-js": "^6.0.0",
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@wordpress/i18n": "^5.6.0",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@wordpress/i18n": "^5.11",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -215,45 +215,31 @@ class GooglepayButton extends PaymentButton {
|
|||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
validateConfiguration( silent = false ) {
|
||||
const validEnvs = [ 'PRODUCTION', 'TEST' ];
|
||||
|
||||
const isInvalid = ( ...args ) => {
|
||||
if ( ! silent ) {
|
||||
this.error( ...args );
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if ( ! validEnvs.includes( this.buttonConfig.environment ) ) {
|
||||
return isInvalid(
|
||||
'Invalid environment:',
|
||||
registerValidationRules( invalidIf, validIf ) {
|
||||
invalidIf(
|
||||
() =>
|
||||
! [ 'TEST', 'PRODUCTION' ].includes(
|
||||
this.buttonConfig.environment
|
||||
),
|
||||
`Invalid environment: ${ this.buttonConfig.environment }`
|
||||
);
|
||||
}
|
||||
|
||||
// Preview buttons only need a valid environment.
|
||||
if ( this.isPreview ) {
|
||||
return true;
|
||||
}
|
||||
validIf( () => this.isPreview );
|
||||
|
||||
if ( ! this.googlePayConfig ) {
|
||||
return isInvalid(
|
||||
invalidIf(
|
||||
() => ! this.googlePayConfig,
|
||||
'No API configuration - missing configure() call?'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! this.transactionInfo ) {
|
||||
return isInvalid(
|
||||
invalidIf(
|
||||
() => ! this.transactionInfo,
|
||||
'No transactionInfo - missing configure() call?'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! typeof this.contextHandler?.validateContext() ) {
|
||||
return isInvalid( 'Invalid context handler.', this.contextHandler );
|
||||
}
|
||||
|
||||
return true;
|
||||
invalidIf(
|
||||
() => ! this.contextHandler?.validateContext(),
|
||||
`Invalid context handler.`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -149,7 +149,6 @@ return array(
|
|||
'DKK', // Danish Krone
|
||||
'EUR', // Euro
|
||||
'GBP', // British Pound Sterling
|
||||
'HKD', // Hong Kong Dollar
|
||||
'HUF', // Hungarian Forint
|
||||
'ILS', // Israeli New Shekel
|
||||
'JPY', // Japanese Yen
|
||||
|
@ -159,7 +158,6 @@ return array(
|
|||
'PHP', // Philippine Peso
|
||||
'PLN', // Polish Zloty
|
||||
'SEK', // Swedish Krona
|
||||
'SGD', // Singapore Dollar
|
||||
'THB', // Thai Baht
|
||||
'TWD', // New Taiwan Dollar
|
||||
'USD', // United States Dollar
|
||||
|
@ -174,6 +172,7 @@ return array(
|
|||
$container->get( 'googlepay.sdk_url' ),
|
||||
$container->get( 'ppcp.asset-version' ),
|
||||
$container->get( 'session.handler' ),
|
||||
$container->get( 'wc-subscriptions.helper' ),
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'onboarding.environment' ),
|
||||
$container->get( 'wcgateway.settings.status' ),
|
||||
|
|
|
@ -107,7 +107,7 @@ class BlocksPaymentMethod extends AbstractPaymentMethodType {
|
|||
'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.
|
||||
'enabled' => $paypal_data['smartButtonsEnabled'], // This button is enabled when PayPal buttons are.
|
||||
'scriptData' => $this->button->script_data(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
|
||||
/**
|
||||
* Class Button
|
||||
|
@ -85,6 +86,13 @@ class Button implements ButtonInterface {
|
|||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* The Subscription Helper.
|
||||
*
|
||||
* @var SubscriptionHelper
|
||||
*/
|
||||
private $subscription_helper;
|
||||
|
||||
/**
|
||||
* SmartButton constructor.
|
||||
*
|
||||
|
@ -92,6 +100,7 @@ class Button implements ButtonInterface {
|
|||
* @param string $sdk_url The URL to the SDK.
|
||||
* @param string $version The assets version.
|
||||
* @param SessionHandler $session_handler The Session handler.
|
||||
* @param SubscriptionHelper $subscription_helper The subscription helper.
|
||||
* @param Settings $settings The Settings.
|
||||
* @param Environment $environment The environment object.
|
||||
* @param SettingsStatus $settings_status The Settings status helper.
|
||||
|
@ -102,6 +111,7 @@ class Button implements ButtonInterface {
|
|||
string $sdk_url,
|
||||
string $version,
|
||||
SessionHandler $session_handler,
|
||||
SubscriptionHelper $subscription_helper,
|
||||
Settings $settings,
|
||||
Environment $environment,
|
||||
SettingsStatus $settings_status,
|
||||
|
@ -112,6 +122,7 @@ class Button implements ButtonInterface {
|
|||
$this->sdk_url = $sdk_url;
|
||||
$this->version = $version;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->subscription_helper = $subscription_helper;
|
||||
$this->settings = $settings;
|
||||
$this->environment = $environment;
|
||||
$this->settings_status = $settings_status;
|
||||
|
@ -233,6 +244,21 @@ class Button implements ButtonInterface {
|
|||
$button_enabled_payorder = true;
|
||||
$button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
|
||||
|
||||
if (
|
||||
$this->subscription_helper->plugin_is_active()
|
||||
&& ! $this->subscription_helper->accept_manual_renewals()
|
||||
) {
|
||||
if ( is_product() && $this->subscription_helper->current_product_is_subscription() ) {
|
||||
return false;
|
||||
}
|
||||
if ( $this->subscription_helper->order_pay_contains_subscription() ) {
|
||||
return false;
|
||||
}
|
||||
if ( $this->subscription_helper->cart_contains_subscription() ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
|
|
|
@ -218,6 +218,20 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
|||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'woocommerce_paypal_payments_selected_button_locations',
|
||||
function( array $locations, string $setting_name ): array {
|
||||
$gateway = WC()->payment_gateways()->payment_gateways()[ GooglePayGateway::ID ] ?? '';
|
||||
if ( $gateway && $gateway->enabled === 'yes' && $setting_name === 'smart_button_locations' ) {
|
||||
$locations[] = 'checkout';
|
||||
}
|
||||
|
||||
return $locations;
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,20 +10,20 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^8.2",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,18 +11,18 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import {
|
||||
setVisible,
|
||||
setVisibleByClass,
|
||||
} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
const payLaterMessagingSelectableLocations = [
|
||||
'product',
|
||||
|
@ -216,6 +221,18 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
replace();
|
||||
};
|
||||
|
||||
const hideElements = ( selectorGroup ) => {
|
||||
selectorGroup.forEach( ( selector ) =>
|
||||
setVisibleByClass( selector, false, 'hide' )
|
||||
);
|
||||
};
|
||||
|
||||
const showElements = ( selectorGroup ) => {
|
||||
selectorGroup.forEach( ( selector ) =>
|
||||
setVisibleByClass( selector, true, 'hide' )
|
||||
);
|
||||
};
|
||||
|
||||
const toggleInputsBySelectedLocations = (
|
||||
stylingPerSelector,
|
||||
locationsSelector,
|
||||
|
@ -226,30 +243,30 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
const payLaterMessagingEnabled = document.querySelector(
|
||||
payLaterMessagingEnabledSelector
|
||||
);
|
||||
const stylingPerElement = document.querySelector( stylingPerSelector );
|
||||
const locationsElement = document.querySelector( locationsSelector );
|
||||
const stylingPerElementWrapper = stylingPerElement?.closest( 'tr' );
|
||||
const stylingPerElementWrapperSelector =
|
||||
'#' + stylingPerElementWrapper?.getAttribute( 'id' );
|
||||
|
||||
const stylingPerElement = document.querySelector( stylingPerSelector );
|
||||
if ( ! stylingPerElement ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stylingPerElementWrapper = stylingPerElement.closest( 'tr' );
|
||||
|
||||
const toggleElementsBySelectedLocations = () => {
|
||||
stylingPerElementWrapper.style.display = '';
|
||||
const selectedLocations = getSelectedLocations( locationsSelector );
|
||||
const emptySmartButtonLocationMessage = jQuery(
|
||||
'.ppcp-empty-smart-button-location'
|
||||
|
||||
setVisibleByClass(
|
||||
stylingPerElementWrapper,
|
||||
selectedLocations.length > 0,
|
||||
'hide'
|
||||
);
|
||||
|
||||
if ( selectedLocations.length === 0 ) {
|
||||
hideElements(
|
||||
groupToHideOnChecked.concat(
|
||||
stylingPerElementWrapperSelector
|
||||
)
|
||||
hideElements( groupToHideOnChecked );
|
||||
|
||||
const emptySmartButtonLocationMessage = document.querySelector(
|
||||
'.ppcp-empty-smart-button-location'
|
||||
);
|
||||
if ( emptySmartButtonLocationMessage.length === 0 ) {
|
||||
if ( ! emptySmartButtonLocationMessage ) {
|
||||
jQuery(
|
||||
PayPalCommerceSettings.empty_smart_button_location_message
|
||||
).insertAfter(
|
||||
|
@ -277,11 +294,11 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
);
|
||||
|
||||
groupToShowOnChecked.forEach( ( element ) => {
|
||||
if ( inputSelectors.includes( element ) ) {
|
||||
document.querySelector( element ).style.display = '';
|
||||
return;
|
||||
}
|
||||
document.querySelector( element ).style.display = 'none';
|
||||
setVisibleByClass(
|
||||
element,
|
||||
inputSelectors.includes( element ),
|
||||
'hide'
|
||||
);
|
||||
} );
|
||||
|
||||
if ( inputType === 'messages' ) {
|
||||
|
@ -289,18 +306,6 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
}
|
||||
};
|
||||
|
||||
const hideElements = ( selectroGroup ) => {
|
||||
selectroGroup.forEach( ( elementToHide ) => {
|
||||
document.querySelector( elementToHide ).style.display = 'none';
|
||||
} );
|
||||
};
|
||||
|
||||
const showElements = ( selectroGroup ) => {
|
||||
selectroGroup.forEach( ( elementToShow ) => {
|
||||
document.querySelector( elementToShow ).style.display = '';
|
||||
} );
|
||||
};
|
||||
|
||||
groupToggle( stylingPerSelector, groupToShowOnChecked );
|
||||
toggleElementsBySelectedLocations();
|
||||
|
||||
|
@ -327,7 +332,7 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
} );
|
||||
|
||||
// We need to use jQuery here as the select might be a select2 element, which doesn't use native events.
|
||||
jQuery( locationsElement ).on( 'change', function () {
|
||||
jQuery( locationsSelector ).on( 'change', function () {
|
||||
const emptySmartButtonLocationMessage = jQuery(
|
||||
'.ppcp-empty-smart-button-location'
|
||||
);
|
||||
|
@ -457,6 +462,38 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the subscription settings when smart buttons are disabled for checkout,
|
||||
* since the basic redirect gateway is disabled for subscriptions.
|
||||
*/
|
||||
const initSettingsHidingForPlaceOrderGateway = () => {
|
||||
const selectors = [
|
||||
'#field-paypal_saved_payments',
|
||||
'#field-subscriptions_mode',
|
||||
'#field-vault_enabled',
|
||||
];
|
||||
|
||||
const updateSettingsVisibility = () => {
|
||||
const selectedLocations = getSelectedLocations(
|
||||
smartButtonLocationsSelect
|
||||
);
|
||||
const hasCheckoutSmartButtons =
|
||||
selectedLocations.includes( 'checkout' ) ||
|
||||
selectedLocations.includes( 'checkout-block-express' );
|
||||
|
||||
selectors.forEach( ( selector ) => {
|
||||
setVisibleByClass( selector, hasCheckoutSmartButtons, 'hide' );
|
||||
} );
|
||||
};
|
||||
|
||||
updateSettingsVisibility();
|
||||
|
||||
jQuery( smartButtonLocationsSelect ).on(
|
||||
'change',
|
||||
updateSettingsVisibility
|
||||
);
|
||||
};
|
||||
|
||||
( () => {
|
||||
removeDisabledCardIcons(
|
||||
'select[name="ppcp[disable_cards][]"]',
|
||||
|
@ -488,6 +525,8 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
|
||||
toggleMessagingEnabled();
|
||||
|
||||
initSettingsHidingForPlaceOrderGateway();
|
||||
|
||||
groupToggle( '#ppcp-vault_enabled', [
|
||||
'#field-subscription_behavior_when_vault_fails',
|
||||
] );
|
||||
|
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Onboarding;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||
|
@ -19,7 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
|||
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingSendOnlyNoticeRenderer;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController;
|
||||
|
||||
|
@ -239,6 +240,11 @@ return array(
|
|||
'sandbox-express_checkout',
|
||||
);
|
||||
},
|
||||
'onboarding.render-send-only-notice' => static function( ContainerInterface $container ) {
|
||||
return new OnboardingSendOnlyNoticeRenderer(
|
||||
$container->get( 'wcgateway.send-only-message' )
|
||||
);
|
||||
},
|
||||
'onboarding.render' => static function ( ContainerInterface $container ) : OnboardingRenderer {
|
||||
$partner_referrals = $container->get( 'api.endpoint.partner-referrals-production' );
|
||||
$partner_referrals_sandbox = $container->get( 'api.endpoint.partner-referrals-sandbox' );
|
||||
|
|
|
@ -117,7 +117,7 @@ class OnboardingAssets {
|
|||
array(
|
||||
'empty_smart_button_location_message' => sprintf(
|
||||
'<p class="description ppcp-empty-smart-button-location">%1$s</p>',
|
||||
__( 'Note: If no button location is selected, the PayPal gateway will not be available.', 'woocommerce-paypal-payments' )
|
||||
__( 'Note: PayPal buttons and advanced payment features (Alternative Payment Methods, Subscriptions, etc.) are unavailable if no Smart Button Location is configured.', 'woocommerce-paypal-payments' )
|
||||
),
|
||||
)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
/**
|
||||
* Creates an admin message that notifies user about send only country while onboarding.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Onboarding\Render
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Onboarding\Render;
|
||||
|
||||
/**
|
||||
* Class OnboardingRenderer
|
||||
*/
|
||||
class OnboardingSendOnlyNoticeRenderer {
|
||||
|
||||
/**
|
||||
* The notice message.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $message;
|
||||
|
||||
/**
|
||||
* AdminNotice constructor.
|
||||
*
|
||||
* @param string $message The notice message.
|
||||
*/
|
||||
public function __construct( string $message ) {
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the notice.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string {
|
||||
return '<div class="notice notice-warning ppcp-notice-wrapper inline"><p>' . $this->message . '</p></div>';
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -11,18 +11,18 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -124,7 +124,7 @@ class MetaBoxRenderer {
|
|||
<label for="ppcp-tracking-items"><?php echo esc_html__( 'Select items for this shipment', 'woocommerce-paypal-payments' ); ?></label>
|
||||
<select multiple class="wc-enhanced-select ppcp-tracking-items" id="ppcp-tracking-items" name="ppcp-tracking[items]">
|
||||
<?php foreach ( $order_items as $item ) : ?>
|
||||
<option value="<?php echo intval( $item->get_id() ); ?>"><?php echo esc_html( $item->get_name() ); ?></option>
|
||||
<option value="<?php echo intval( $item->get_id() ); ?>"><?php echo esc_attr( wp_strip_all_tags( $item->get_name() ) ); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,22 +11,22 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@paypal/react-paypal-js": "^8.2.0",
|
||||
"core-js": "^3.25.0",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0"
|
||||
"core-js": "^3.39",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^8.2",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,20 +10,20 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"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",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -11,22 +11,22 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@paypal/react-paypal-js": "^8.2.0",
|
||||
"core-js": "^3.25.0",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0"
|
||||
"core-js": "^3.39",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"@babel/preset-react": "^7.25",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
|
||||
"babel-loader": "^8.2",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,18 +10,18 @@
|
|||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
"core-js": "^3.39"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"@babel/core": "^7.26",
|
||||
"@babel/preset-env": "^7.26",
|
||||
"babel-loader": "^9.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"
|
||||
"sass": "^1.80",
|
||||
"sass-loader": "^16",
|
||||
"webpack": "^5.96",
|
||||
"webpack-cli": "^5"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
|
|
|
@ -40,4 +40,13 @@ return array(
|
|||
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
|
||||
);
|
||||
},
|
||||
'paypal-subscriptions.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler {
|
||||
return new RenewalHandler( $container->get( 'woocommerce.logger.woocommerce' ) );
|
||||
},
|
||||
'paypal-subscriptions.status' => static function ( ContainerInterface $container ): SubscriptionStatus {
|
||||
return new SubscriptionStatus(
|
||||
$container->get( 'api.endpoint.billing-subscriptions' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -187,6 +187,9 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
|||
30
|
||||
);
|
||||
|
||||
/**
|
||||
* Executed when updating WC Subscription.
|
||||
*/
|
||||
add_action(
|
||||
'woocommerce_process_shop_subscription_meta',
|
||||
/**
|
||||
|
@ -194,65 +197,41 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
|||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $id, $post ) use ( $c ) {
|
||||
function( $id ) use ( $c ) {
|
||||
$subscription = wcs_get_subscription( $id );
|
||||
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
|
||||
if ( $subscription === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( ! $subscription_id ) {
|
||||
return;
|
||||
}
|
||||
$subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
|
||||
assert( $subscriptions_endpoint instanceof BillingSubscriptions );
|
||||
|
||||
if ( $subscription->get_status() === 'cancelled' ) {
|
||||
try {
|
||||
$subscriptions_endpoint->cancel( $subscription_id );
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
$subscription_status = $c->get( 'paypal-subscriptions.status' );
|
||||
assert( $subscription_status instanceof SubscriptionStatus );
|
||||
|
||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||
$logger->error( 'Could not cancel subscription product on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $subscription->get_status() === 'pending-cancel' ) {
|
||||
try {
|
||||
$subscriptions_endpoint->suspend( $subscription_id );
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
|
||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||
$logger->error( 'Could not suspend subscription product on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $subscription->get_status() === 'active' ) {
|
||||
try {
|
||||
$current_subscription = $subscriptions_endpoint->subscription( $subscription_id );
|
||||
if ( $current_subscription->status === 'SUSPENDED' ) {
|
||||
$subscriptions_endpoint->activate( $subscription_id );
|
||||
}
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
|
||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||
$logger->error( 'Could not reactivate subscription product on PayPal. ' . $error );
|
||||
}
|
||||
}
|
||||
$subscription_status->update_status( $subscription->get_status(), $subscription_id );
|
||||
},
|
||||
20,
|
||||
2
|
||||
20
|
||||
);
|
||||
|
||||
/**
|
||||
* Update subscription status from WC Subscriptions list page action link.
|
||||
*/
|
||||
add_action(
|
||||
'woocommerce_subscription_status_updated',
|
||||
function( WC_Subscription $subscription ) use ( $c ) {
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( ! $subscription_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_status = $c->get( 'paypal-subscriptions.status' );
|
||||
assert( $subscription_status instanceof SubscriptionStatus );
|
||||
|
||||
$subscription_status->update_status( $subscription->get_status(), $subscription_id );
|
||||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
|
@ -717,11 +696,18 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
|||
* @param WC_Product $product The product.
|
||||
* @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler.
|
||||
* @return void
|
||||
*
|
||||
* @psalm-suppress PossiblyInvalidCast
|
||||
*/
|
||||
private function update_subscription_product_meta( WC_Product $product, SubscriptionsApiHandler $subscriptions_api_handler ): void {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
|
||||
$product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
|
||||
$enable_subscription_product = wc_string_to_bool( (string) wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) ) );
|
||||
$product->update_meta_data( '_ppcp_enable_subscription_product', wc_bool_to_string( $enable_subscription_product ) );
|
||||
|
||||
if ( ! $enable_subscription_product ) {
|
||||
$product->save();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $product->get_sold_individually() ) {
|
||||
$product->set_sold_individually( true );
|
||||
|
@ -729,7 +715,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
|
|||
|
||||
$product->save();
|
||||
|
||||
if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
|
||||
if ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) {
|
||||
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
$subscriptions_api_handler->update_product( $product );
|
||||
$subscriptions_api_handler->update_plan( $product );
|
||||
|
|
107
modules/ppcp-paypal-subscriptions/src/RenewalHandler.php
Normal file
107
modules/ppcp-paypal-subscriptions/src/RenewalHandler.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* Subscriptions renewal handler.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcSubscriptions
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Data_Exception;
|
||||
use WC_Order;
|
||||
use WC_Subscription;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
||||
|
||||
/**
|
||||
* Class RenewalHandler
|
||||
*/
|
||||
class RenewalHandler {
|
||||
|
||||
use TransactionIdHandlingTrait;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* RenewalHandler constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger ) {
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process subscription renewal.
|
||||
*
|
||||
* @param WC_Subscription[] $subscriptions WC Subscriptions.
|
||||
* @param string $transaction_id PayPal transaction ID.
|
||||
* @return void
|
||||
* @throws WC_Data_Exception If something goes wrong while setting payment method.
|
||||
*/
|
||||
public function process( array $subscriptions, string $transaction_id ): void {
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
if ( $this->is_for_renewal_order( $subscription ) ) {
|
||||
$renewal_order = wcs_create_renewal_order( $subscription );
|
||||
if ( is_a( $renewal_order, WC_Order::class ) ) {
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Processing renewal order #%s for subscription #%s',
|
||||
$renewal_order->get_id(),
|
||||
$subscription->get_id()
|
||||
)
|
||||
);
|
||||
|
||||
$renewal_order->set_payment_method( $subscription->get_payment_method() );
|
||||
$renewal_order->payment_complete();
|
||||
$this->update_transaction_id( $transaction_id, $renewal_order, $this->logger );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$parent_order = wc_get_order( $subscription->get_parent() );
|
||||
if ( is_a( $parent_order, WC_Order::class ) ) {
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Processing parent order #%s for subscription #%s',
|
||||
$parent_order->get_id(),
|
||||
$subscription->get_id()
|
||||
)
|
||||
);
|
||||
|
||||
$subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' );
|
||||
$subscription->save_meta_data();
|
||||
$this->update_transaction_id( $transaction_id, $parent_order, $this->logger );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether subscription order is for renewal or not.
|
||||
*
|
||||
* @param WC_Subscription $subscription WC Subscription.
|
||||
* @return bool
|
||||
*/
|
||||
private function is_for_renewal_order( WC_Subscription $subscription ): bool {
|
||||
$subscription_renewal_meta = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? '';
|
||||
if ( $subscription_renewal_meta === 'true' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
time() >= $subscription->get_time( 'start' )
|
||||
&& ( time() - $subscription->get_time( 'start' ) ) <= ( 8 * HOUR_IN_SECONDS )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
144
modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php
Normal file
144
modules/ppcp-paypal-subscriptions/src/SubscriptionStatus.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles PayPal subscription status.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcSubscriptions
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\PayPalSubscriptions;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class SubscriptionStatus
|
||||
*/
|
||||
class SubscriptionStatus {
|
||||
|
||||
/**
|
||||
* Billing subscriptions endpoint.
|
||||
*
|
||||
* @var BillingSubscriptions
|
||||
*/
|
||||
private $subscriptions_endpoint;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* SubscriptionStatus constructor.
|
||||
*
|
||||
* @param BillingSubscriptions $subscriptions_endpoint Billing subscriptions endpoint.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
BillingSubscriptions $subscriptions_endpoint,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->subscriptions_endpoint = $subscriptions_endpoint;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates PayPal subscription status from the given WC Subscription status.
|
||||
*
|
||||
* @param string $subscription_status The WC Subscription status.
|
||||
* @param string $subscription_id The PayPal Subscription ID.
|
||||
* @return void
|
||||
*/
|
||||
public function update_status( string $subscription_status, string $subscription_id ): void {
|
||||
if ( $subscription_status === 'pending-cancel' || $subscription_status === 'cancelled' ) {
|
||||
try {
|
||||
$current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id );
|
||||
if ( $current_subscription->status === 'CANCELLED' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Canceling PayPal subscription #%s.',
|
||||
$subscription_id
|
||||
)
|
||||
);
|
||||
|
||||
$this->subscriptions_endpoint->cancel( $subscription_id );
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'Could not cancel PayPal subscription #%s. %s',
|
||||
$subscription_id,
|
||||
$this->get_error( $exception )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $subscription_status === 'on-hold' ) {
|
||||
try {
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Suspending PayPal subscription #%s.',
|
||||
$subscription_id
|
||||
)
|
||||
);
|
||||
|
||||
$this->subscriptions_endpoint->suspend( $subscription_id );
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'Could not suspend PayPal subscription #%s. %s',
|
||||
$subscription_id,
|
||||
$this->get_error( $exception )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $subscription_status === 'active' ) {
|
||||
try {
|
||||
$current_subscription = $this->subscriptions_endpoint->subscription( $subscription_id );
|
||||
if ( $current_subscription->status === 'SUSPENDED' ) {
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Activating suspended PayPal subscription #%s.',
|
||||
$subscription_id
|
||||
)
|
||||
);
|
||||
|
||||
$this->subscriptions_endpoint->activate( $subscription_id );
|
||||
}
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$this->logger->error(
|
||||
sprintf(
|
||||
'Could not reactivate PayPal subscription #%s. %s',
|
||||
$subscription_id,
|
||||
$this->get_error( $exception )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error from exception.
|
||||
*
|
||||
* @param RuntimeException $exception The exception.
|
||||
* @return string
|
||||
*/
|
||||
private function get_error( RuntimeException $exception ): string {
|
||||
$error = $exception->getMessage();
|
||||
if ( is_a( $exception, PayPalApiException::class ) ) {
|
||||
$error = $exception->get_details( $error );
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue