🔀 Merge branch 'trunk'

# Conflicts:
#	modules/ppcp-settings/resources/js/Components/Screens/Onboarding/StepWelcome.js
This commit is contained in:
Philipp Stracker 2024-11-06 18:14:25 +01:00
commit cafe60904c
No known key found for this signature in database
119 changed files with 30406 additions and 63017 deletions

View file

@ -1,5 +1,28 @@
*** Changelog ***
= 2.9.4 - xxxx-xx-xx =
* 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 - Extend Advanced Card Processing country/currency feature availability #2754
* 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

View file

@ -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

View file

@ -716,6 +716,7 @@ return array(
'FR' => $mastercard_visa_amex,
'GB' => $mastercard_visa_amex,
'GR' => $mastercard_visa_amex,
'HK' => $mastercard_visa_amex,
'HU' => $mastercard_visa_amex,
'IE' => $mastercard_visa_amex,
'IT' => $mastercard_visa_amex,
@ -745,6 +746,7 @@ return array(
'SE' => $mastercard_visa_amex,
'SI' => $mastercard_visa_amex,
'SK' => $mastercard_visa_amex,
'SG' => $mastercard_visa_amex,
'JP' => array(
'mastercard' => array(),
'visa' => array(),

View file

@ -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,

View file

@ -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 );
}

View 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;
}
}

View file

@ -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",

View file

@ -57,7 +57,3 @@
}
}
}
#ppc-button-ppcp-applepay {
display: none;
}

File diff suppressed because it is too large Load diff

View file

@ -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();
}
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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();
}
/**

View file

@ -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 } />,

View file

@ -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,
} );

View file

@ -191,6 +191,7 @@ return array(
'FR', // France
'DE', // Germany
'GR', // Greece
'HK', // Hong Kong
'HU', // Hungary
'IE', // Ireland
'IT', // Italy
@ -204,6 +205,7 @@ return array(
'PL', // Poland
'PT', // Portugal
'RO', // Romania
'SG', // Singapore
'SK', // Slovakia
'SI', // Slovenia
'ES', // Spain

View file

@ -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' );

View file

@ -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;
}

View file

@ -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
}

File diff suppressed because it is too large Load diff

View file

@ -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",

File diff suppressed because it is too large Load diff

View file

@ -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",

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -0,0 +1,3 @@
li[id^="express-payment-method-ppcp-"] div[class^="ppc-button-container-"] {
display: flex;
}

View file

@ -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,20 +698,46 @@ const PayPalComponent = ( {
);
};
const BlockEditorPayPalComponent = ({ fundingSource } ) => {
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>
);
@ -787,7 +823,7 @@ if ( block_enabled ) {
name: config.id,
label: <div dangerouslySetInnerHTML={ { __html: config.title } } />,
content: <PayPalComponent isEditing={ false } />,
edit: <BlockEditorPayPalComponent fundingSource={ 'paypal' }/>,
edit: <BlockEditorPayPalComponent fundingSource={ 'paypal' } />,
ariaLabel: config.title,
canMakePayment: () => {
return true;
@ -819,9 +855,11 @@ if ( block_enabled ) {
fundingSource={ fundingSource }
/>
),
edit: <BlockEditorPayPalComponent
edit: (
<BlockEditorPayPalComponent
fundingSource={ fundingSource }
/>,
/>
),
ariaLabel: config.title,
canMakePayment: async () => {
if ( ! paypalScriptPromise ) {
@ -845,6 +883,7 @@ if ( block_enabled ) {
},
supports: {
features,
style: [ 'height', 'borderRadius' ],
},
} );
}

View file

@ -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 ) {

View file

@ -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

View file

@ -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",

View file

@ -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.
@ -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();
}
);
}
}
/**
@ -812,6 +966,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 );
}
/**

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -43,6 +43,7 @@ return array(
'FR',
'DE',
'GR',
'HK',
'HU',
'IE',
'IT',
@ -55,6 +56,7 @@ return array(
'PL',
'PT',
'RO',
'SG',
'SK',
'SI',
'ES',

File diff suppressed because it is too large Load diff

View file

@ -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",

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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.`
);
}
/**

View file

@ -106,6 +106,7 @@ return array(
'FR', // France
'DE', // Germany
'GR', // Greece
'HK', // Hong Kong
'HU', // Hungary
'IE', // Ireland
'IT', // Italy
@ -119,6 +120,7 @@ return array(
'PL', // Poland
'PT', // Portugal
'RO', // Romania
'SG', // Singapore
'SK', // Slovakia
'SI', // Slovenia
'ES', // Spain

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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' );

View file

@ -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

View file

@ -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",

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -10,21 +10,21 @@
"Edge >= 14"
],
"dependencies": {
"core-js": "^3.25.0",
"core-js": "^3.39",
"@paypal/paypal-js": "^6.0.0"
},
"devDependencies": {
"@babel/core": "^7.19",
"@babel/preset-env": "^7.19",
"@babel/preset-react": "^7.18.6",
"@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
"babel-loader": "^8.2",
"@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",

View file

@ -72,6 +72,7 @@ return array(
'FR' => $default_currencies,
'DE' => $default_currencies,
'GR' => $default_currencies,
'HK' => $default_currencies,
'HU' => $default_currencies,
'IE' => $default_currencies,
'IT' => $default_currencies,
@ -85,19 +86,13 @@ return array(
'PL' => $default_currencies,
'PT' => $default_currencies,
'RO' => $default_currencies,
'SG' => $default_currencies,
'SK' => $default_currencies,
'SI' => $default_currencies,
'ES' => $default_currencies,
'SE' => $default_currencies,
'GB' => $default_currencies,
'US' => array(
'AUD',
'CAD',
'EUR',
'GBP',
'JPY',
'USD',
),
'US' => $default_currencies,
)
);
},

File diff suppressed because it is too large Load diff

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 568 B

After

Width:  |  Height:  |  Size: 568 B

Before After
Before After

View file

@ -0,0 +1,9 @@
<svg width="58" height="58" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M55.5833 43.4999C55.5833 44.8346 54.5013 45.9166 53.1666 45.9166L19.3333 45.9166C17.9986 45.9166 16.9166 44.8346 16.9166 43.4999L16.9166 26.5833C16.9166 25.2486 17.9986 24.1666 19.3333 24.1666L53.1666 24.1666C54.5013 24.1666 55.5833 25.2486 55.5833 26.5833V43.4999Z" fill="#5BBBFC"/>
<path d="M2.41663 14.4999C2.41663 13.1652 3.4986 12.0833 4.83329 12.0833H33.8333C35.168 12.0833 36.25 13.1652 36.25 14.4999V33.8333C36.25 35.1679 35.168 36.2499 33.8333 36.2499H4.83329C3.49861 36.2499 2.41663 35.1679 2.41663 33.8333V14.4999Z" fill="#DD7F57"/>
<path d="M36.25 24.1666V33.8333C36.25 35.1679 35.168 36.2499 33.8333 36.2499H16.9166L16.9166 26.5833C16.9166 25.2486 17.9986 24.1666 19.3333 24.1666L36.25 24.1666Z" fill="#FAF8F5"/>
<path d="M27.7916 35.0416C27.7916 30.3702 31.5785 26.5833 36.25 26.5833C40.9214 26.5833 44.7083 30.3702 44.7083 35.0416C44.7083 39.713 40.9214 43.4999 36.25 43.4999C31.5785 43.4999 27.7916 39.713 27.7916 35.0416Z" fill="#001C64"/>
<path d="M2.41663 16.9166H36.25V21.7499H2.41663V16.9166Z" fill="#4F2825"/>
<path d="M14.5 31.7187C14.5 32.0523 14.2295 32.3228 13.8958 32.3228H5.43746C5.10379 32.3228 4.83329 32.0523 4.83329 31.7187V30.8124C4.83329 30.4787 5.10379 30.2083 5.43746 30.2083H13.8958C14.2295 30.2083 14.5 30.4787 14.5 30.8124V31.7187Z" fill="#4F2825"/>
<path d="M27.7916 35.0416C27.7916 30.3702 31.5785 26.5833 36.25 26.5833V33.8333C36.25 35.1679 35.168 36.2499 33.8333 36.2499H27.8773C27.8208 35.8553 27.7916 35.4518 27.7916 35.0416Z" fill="#4F2825"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,9 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.3334 27.0004C21.3334 25.5277 22.5273 24.3338 24.0001 24.3338H58.6667C60.1395 24.3338 61.3334 25.5277 61.3334 27.0004V48.3338C61.3334 49.8065 60.1395 51.0004 58.6667 51.0004H24.0001C22.5273 51.0004 21.3334 49.8065 21.3334 48.3338V27.0004Z" fill="#6FC400"/>
<path d="M2.66675 16.3338C2.66675 14.861 3.86066 13.6671 5.33341 13.6671H40.0001C41.4728 13.6671 42.6667 14.861 42.6667 16.3338V37.6671C42.6667 39.1399 41.4728 40.3338 40.0001 40.3338H5.33341C3.86066 40.3338 2.66675 39.1399 2.66675 37.6671V16.3338Z" fill="#5BBBFC"/>
<path d="M42.6667 24.3338V37.6671C42.6667 39.1399 41.4728 40.3338 40.0001 40.3338H21.3334V27.0004C21.3334 25.5277 22.5273 24.3338 24.0001 24.3338H42.6667Z" fill="#FAF8F5"/>
<path d="M61.3334 29.6671H42.6667V35.0004H61.3334V29.6671Z" fill="#005400"/>
<path d="M36.6667 46.6671C37.0349 46.6671 37.3334 46.3686 37.3334 46.0004V45.0004C37.3334 44.6323 37.0349 44.3338 36.6667 44.3338H24.6667C24.2986 44.3338 24.0001 44.6323 24.0001 45.0004V46.0004C24.0001 46.3686 24.2986 46.6671 24.6667 46.6671H36.6667Z" fill="#005400"/>
<path d="M8.66675 19.0004C8.29856 19.0004 8.00008 19.2989 8.00008 19.6671V26.3338C8.00008 26.702 8.29856 27.0004 8.66675 27.0004H15.3334C15.7016 27.0004 16.0001 26.702 16.0001 26.3338V19.6671C16.0001 19.2989 15.7016 19.0004 15.3334 19.0004H8.66675Z" fill="#001C64"/>
<path d="M36.9312 29.6514C37.1625 29.3649 37.1177 28.9451 36.8312 28.7139L36.053 28.0858C35.7665 27.8546 35.3468 27.8994 35.1155 28.1859L30.8233 33.5038C30.5842 33.8001 30.1459 33.8363 29.8614 33.5833L28.0489 31.9712C27.7738 31.7265 27.3524 31.7511 27.1077 32.0263L26.4431 32.7735C26.1984 33.0486 26.2231 33.47 26.4982 33.7147L30.1398 36.9535C30.4242 37.2066 30.8625 37.1704 31.1016 36.8741L36.9312 29.6514Z" fill="#001C64"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,9 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M61.3334 48.0003C61.3334 49.473 60.1395 50.667 58.6667 50.667L21.3334 50.6669C19.8607 50.6669 18.6667 49.473 18.6667 48.0003L18.6667 29.3336C18.6667 27.8609 19.8607 26.667 21.3334 26.667L58.6667 26.667C60.1395 26.667 61.3334 27.8609 61.3334 29.3336V48.0003Z" fill="#5BBBFC"/>
<path d="M2.66675 16.0003C2.66675 14.5275 3.86066 13.3336 5.33341 13.3336H37.3334C38.8062 13.3336 40.0001 14.5275 40.0001 16.0003V37.3336C40.0001 38.8064 38.8062 40.0003 37.3334 40.0003H5.33342C3.86066 40.0003 2.66675 38.8064 2.66675 37.3336V16.0003Z" fill="#DD7F57"/>
<path d="M40.0001 26.667V37.3336C40.0001 38.8064 38.8062 40.0003 37.3334 40.0003H18.6667L18.6667 29.3336C18.6667 27.8609 19.8607 26.667 21.3334 26.667L40.0001 26.667Z" fill="#FAF8F5"/>
<path d="M30.6667 38.667C30.6667 33.5123 34.8454 29.3336 40.0001 29.3336C45.1547 29.3336 49.3334 33.5123 49.3334 38.667C49.3334 43.8216 45.1547 48.0003 40.0001 48.0003C34.8454 48.0003 30.6667 43.8216 30.6667 38.667Z" fill="#001C64"/>
<path d="M2.66675 18.667H40.0001V24.0003H2.66675V18.667Z" fill="#4F2825"/>
<path d="M16.0001 35.0003C16.0001 35.3685 15.7016 35.667 15.3334 35.667H6.00008C5.63189 35.667 5.33341 35.3685 5.33341 35.0003V34.0003C5.33341 33.6321 5.63189 33.3336 6.00008 33.3336H15.3334C15.7016 33.3336 16.0001 33.6321 16.0001 34.0003V35.0003Z" fill="#4F2825"/>
<path d="M30.6667 38.667C30.6667 33.5123 34.8454 29.3336 40.0001 29.3336V37.3336C40.0001 38.8064 38.8062 40.0003 37.3334 40.0003H30.7613C30.699 39.5648 30.6667 39.1196 30.6667 38.667Z" fill="#4F2825"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,8 @@
<svg width="58" height="58" viewBox="0 0 58 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M50.75 48.3333C50.75 49.668 49.668 50.75 48.3333 50.75L14.5 50.75C13.1653 50.75 12.0833 49.668 12.0833 48.3333L12.0833 14.5C12.0833 13.1653 13.1653 12.0833 14.5 12.0833L48.3333 12.0833C49.668 12.0833 50.75 13.1653 50.75 14.5L50.75 48.3333Z" fill="#5BBBFC"/>
<path d="M45.9167 43.5C45.9167 44.8347 44.8347 45.9167 43.5 45.9167L9.66666 45.9167C8.33198 45.9167 7.25 44.8347 7.25 43.5L7.25 9.66667C7.25 8.33198 8.33198 7.25 9.66667 7.25L43.5 7.25C44.8347 7.25 45.9167 8.33198 45.9167 9.66667L45.9167 43.5Z" fill="#6FC400"/>
<path d="M45.9167 12.0833V43.5C45.9167 44.8347 44.8347 45.9167 43.5 45.9167L12.0833 45.9167L12.0833 14.5C12.0833 13.1653 13.1653 12.0833 14.5 12.0833H45.9167Z" fill="#FAF8F5"/>
<path d="M26.8638 21.6503C26.7443 21.7523 26.665 21.8937 26.6404 22.0491L24.3562 36.5038H20.3333C20.2478 36.504 20.1633 36.4852 20.086 36.4487C20.0087 36.4122 19.9405 36.3588 19.8864 36.2926C19.8322 36.2263 19.7935 36.1488 19.7731 36.0657C19.7526 35.9826 19.7509 35.896 19.7681 35.8122L22.6558 17.5222C22.6801 17.3594 22.76 17.2101 22.8819 17.0997C23.0039 16.9893 23.1603 16.9246 23.3245 16.9167H30.2633C33.2628 16.9015 35.8475 19.0533 35.8375 21.9633C35.0658 21.5647 34.0719 21.4928 33.0285 21.4928H27.2926C27.1355 21.4925 26.9834 21.5483 26.8638 21.6503Z" fill="#001C64"/>
<path d="M26.8482 28.8223L27.7143 23.3339C27.7621 23.0432 27.9243 22.7524 28.3021 22.7524H33.1428C34.0632 22.7607 35.0331 22.8438 35.7514 23.2194C35.7473 23.4716 35.7288 23.7232 35.6962 23.9733C35.4865 25.3236 34.8016 26.5545 33.765 27.4437C32.7284 28.3329 31.4084 28.8218 30.0433 28.8223H26.8482Z" fill="#001C64"/>
<path d="M36.9243 23.8817C36.9174 24.1202 36.8966 24.3581 36.8621 24.5942C36.3674 27.7527 33.6214 30.0834 30.4302 30.0834H27.2647C27.0937 30.0835 26.9284 30.1446 26.7984 30.2558C26.6684 30.3671 26.5823 30.521 26.5556 30.6901L25.0157 40.409C25.0026 40.4923 25.0077 40.5774 25.0307 40.6585C25.0536 40.7397 25.0938 40.8148 25.1486 40.8789C25.2033 40.943 25.2713 40.9944 25.3478 41.0297C25.4243 41.0649 25.5075 41.0832 25.5917 41.0832H28.9829C29.1516 41.0831 29.3148 41.0237 29.4441 40.9152C29.5733 40.8067 29.6603 40.6562 29.6898 40.49L30.5289 35.2197C30.5535 35.0642 30.6327 34.9227 30.7522 34.8206C30.8718 34.7184 31.0239 34.6624 31.1811 34.6625H33.1979C36.3891 34.6625 39.1351 32.3314 39.6299 29.1733C39.9803 26.9314 38.8529 24.8914 36.9243 23.8817Z" fill="#001C64"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

0
modules/ppcp-settings/node_modules/.gitkeep generated vendored Normal file
View file

View file

@ -0,0 +1,48 @@
.ppcp-r-payment-method-item {
display: flex;
align-items: center;
&:not(:last-child) {
padding-bottom: 32px;
}
&:not(:first-child) {
border-top: 1px solid $color-gray-400;
padding-top: 32px;
}
&__checkbox-wrap {
position: relative;
display: flex;
align-items: center;
.ppcp-r__checkbox {
margin-right: 24px;
}
}
&__icon-wrap {
margin-right: 18px;
}
&__content {
padding-right: 24px;
p {
margin: 0;
}
}
&__title {
@include font(16, 20, 600);
color: $color-black;
margin: 0 0 8px 0;
display: block;
}
button {
margin-left: auto;
@include font(13, 20, 400);
padding: 6px 12px !important;
}
}

View file

@ -0,0 +1,123 @@
.ppcp-r-modal {
@import "../../global";
@import './button';
@import './fields';
&__container {
max-width: 100%;
width: 400px;
&--small {
padding: 0 50px;
@media screen and (max-width: 480px){
padding:0;
}
}
}
.components-modal {
&__header {
height: 52px;
padding: 20px 20px 0 20px;
button {
padding: 4px;
}
}
&__content {
margin-top: 48px;
padding: 0 50px 48px 50px;
}
}
&__header {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
border-bottom: 1px solid $color-gray-500;
padding-bottom: 12px;
margin-bottom: 24px;
}
&__title {
@include font(16, 20, 600);
color: $color-black;
}
&__content {
width: 400px;
max-width: 100%;
}
&__inverted-toggle-control {
.components-form-toggle {
order: 1;
}
}
&__field-row {
display: flex;
flex-direction: column;
gap: 8px;
&--save {
margin-top: 24px;
align-items: flex-end;
}
input[type='text'] {
padding: 7px 11px;
@include font(14, 20, 400);
margin: 0;
border-color: $color-gray-700;
}
label {
@include font(14, 16, 400);
color: $color-gray-900;
}
}
&__field-rows {
display: flex;
flex-direction: column;
gap: 24px;
&--acdc {
gap: 18px;
}
}
.ppcp-r-modal__field-row--save button.is-primary {
border-radius: 2px;
padding: 6px 12px;
@include font(13, 20, 400);
}
&__content-title {
@include font(14, 20, 600);
color: $color-black;
display: block;
margin: 0 0 4px 0;
}
&__description {
@include font(14, 20, 400);
margin: 0 0 24px 0;
color: $color-black;
}
&__field-rdb {
display: flex;
gap: 18px;
align-items: center;
position: relative;
label {
@include font(14, 20, 400);
color: $color-black;
}
}
}

View file

@ -0,0 +1,5 @@
.ppcp-r-payment-methods{
display: flex;
flex-direction: column;
gap:48px;
}

View file

@ -9,6 +9,7 @@
@import './components/reusable-components/text-control';
@import './components/reusable-components/separator';
@import './components/reusable-components/payment-method-icons';
@import "./components/reusable-components/payment-method-item";
@import './components/reusable-components/settings-wrapper';
@import './components/reusable-components/select-box';
@import './components/reusable-components/tab-navigation';
@ -19,4 +20,7 @@
@import './components/reusable-components/badge-box';
@import './components/screens/onboarding';
@import './components/screens/dashboard/tab-dashboard';
@import './components/screens/dashboard/tab-payment-methods';
}
@import './components/reusable-components/payment-method-modal';

View file

@ -1,38 +1,36 @@
import data from '../../utils/data';
import TitleBadge, {TITLE_BADGE_INFO} from "./TitleBadge";
import {__} from "@wordpress/i18n";
import TitleBadge, { TITLE_BADGE_INFO } from "./TitleBadge";
import { __ } from "@wordpress/i18n";
const BadgeBox = ( props ) => {
const titleSize = props.titleType && props.titleType === BADGE_BOX_TITLE_BIG ? BADGE_BOX_TITLE_BIG : BADGE_BOX_TITLE_SMALL
const titleTextClassName = 'ppcp-r-badge-box__title-text ' + `ppcp-r-badge-box__title-text--${titleSize}`;
const titleTextClassName = 'ppcp-r-badge-box__title-text ' + `ppcp-r-badge-box__title-text--${ titleSize }`;
const titleBaseClassName = 'ppcp-r-badge-box__title';
const titleClassName = props.imageBadge ? `${titleBaseClassName} ppcp-r-badge-box__title--has-image-badge` : titleBaseClassName;
const titleClassName = props.imageBadge ? `${ titleBaseClassName } ppcp-r-badge-box__title--has-image-badge` : titleBaseClassName;
return (
<div className="ppcp-r-badge-box">
<span className={titleClassName}>
<span className={titleTextClassName}>{props.title}</span>
<span className={ titleClassName }>
<span className={ titleTextClassName }>{props.title}</span>
{props.imageBadge && (
{ props.imageBadge && (
<span className="ppcp-r-badge-box__title-image-badge">
{props.imageBadge.map((badge) => data().getImage(badge))}
{ props.imageBadge.map( ( badge ) => data().getImage( badge ) ) }
</span>
)}
) }
{props.textBadge && (
<TitleBadge type={TITLE_BADGE_INFO} text={props.textBadge}/>
)}
{ props.textBadge && (
<TitleBadge type={ TITLE_BADGE_INFO } text={ props.textBadge }/>
) }
</span>
<div className="ppcp-r-badge-box__description">
{props?.description && (
{ props?.description && (
<p
className="ppcp-r-badge-box__description"
dangerouslySetInnerHTML={{
__html: props.description,
}}
dangerouslySetInnerHTML={ { __html: props.description, } }
></p>
)}
) }
</div>
</div>
);

View file

@ -24,6 +24,7 @@ export const PayPalRdb = ( props ) => {
return (
<div className="ppcp-r__radio">
<input
id={ props?.id ? props.id : null }
className="ppcp-r__radio-value"
type="radio"
checked={ props.value === props.currentValue }

View file

@ -0,0 +1,62 @@
import { Button } from '@wordpress/components';
import PaymentMethodIcon from './PaymentMethodIcon';
import { PayPalCheckbox } from './Fields';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
const PaymentMethodItem = ( props ) => {
const [ paymentMethodState, setPaymentMethodState ] = useState();
const [ modalIsVisible, setModalIsVisible ] = useState( false );
let Modal = null;
if ( props?.modal ) {
Modal = props.modal;
}
const handleCheckboxState = ( checked ) => {
if ( checked ) {
setPaymentMethodState( props.payment_method_id );
} else {
setPaymentMethodState( null );
}
};
return (
<>
<div className="ppcp-r-payment-method-item">
<div className="ppcp-r-payment-method-item__checkbox-wrap">
<PayPalCheckbox
currentValue={ [ paymentMethodState ] }
name="payment_method_status"
value={ props.payment_method_id }
handleCheckboxState={ handleCheckboxState }
/>
<div className="ppcp-r-payment-method-item__icon-wrap">
<PaymentMethodIcon
icons={ [ props.icon ] }
type={ props.icon }
/>
</div>
<div className="ppcp-r-payment-method-item__content">
<span className="ppcp-r-payment-method-item__title">
{ props.title }
</span>
<p>{ props.description }</p>
</div>
</div>
{ Modal && (
<Button
variant="secondary"
onClick={ () => {
setModalIsVisible( true );
} }
>
{ __( 'Modify', 'woocommerce-paypal-payments' ) }
</Button>
) }
</div>
{ Modal && modalIsVisible && (
<Modal setModalIsVisible={ setModalIsVisible } />
) }
</>
);
};
export default PaymentMethodItem;

View file

@ -0,0 +1,35 @@
import { Modal } from '@wordpress/components';
import PaymentMethodIcon from './PaymentMethodIcon';
const PaymentMethodModal = ( props ) => {
let className = 'ppcp-r-modal';
let classNameContainer = 'ppcp-r-modal__container';
if ( props?.className ) {
className += ' ' + props.className;
}
if ( props?.container && props.container === 'small' ) {
classNameContainer += ' ppcp-r-modal__container--small';
}
return (
<Modal
className={ className }
onRequestClose={ () => props.setModalIsVisible( false ) }
>
<div className={ classNameContainer }>
<div className="ppcp-r-modal__header">
<PaymentMethodIcon
icons={ [ props.icon ] }
type={ props.icon }
/>
<span className="ppcp-r-modal__title">{ props.title }</span>
</div>
<div className="ppcp-r-modal__content">{ props.children }</div>
</div>
</Modal>
);
};
export default PaymentMethodModal;

View file

@ -0,0 +1,84 @@
import PaymentMethodModal from '../../../ReusableComponents/PaymentMethodModal';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { PayPalRdb } from '../../../ReusableComponents/Fields';
import { useState } from '@wordpress/element';
const THREED_SECURE_GROUP_NAME = 'threed-secure';
const ModalAcdc = ( { setModalIsVisible } ) => {
const [ threeDSecure, setThreeDSecure ] = useState( 'no-3d-secure' );
return (
<PaymentMethodModal
setModalIsVisible={ setModalIsVisible }
icon="payment-method-cards"
title={ __(
'Advanced Credit and Debit Card Payments',
'woocommerce-paypal-payments'
) }
>
<strong className="ppcp-r-modal__content-title">
{ __( '3D Secure', 'woocommerce-paypal-payments' ) }
</strong>
<p className="ppcp-r-modal__description">
{ __(
'Authenticate cardholders through their card issuers to reduce fraud and improve transaction security. Successful 3D Secure authentication can shift liability for fraudulent chargebacks to the card issuer.',
'woocommerce-paypal-payments'
) }
</p>
<div className="ppcp-r-modal__field-rows ppcp-r-modal__field-rows--acdc">
<div className="ppcp-r-modal__field-rdb">
<PayPalRdb
id="no-3d-secure"
name={ THREED_SECURE_GROUP_NAME }
value="no-3d-secure"
currentValue={ threeDSecure }
handleRdbState={ setThreeDSecure }
/>
<label htmlFor="no-3d-secure">
{ __( 'No 3D Secure', 'woocommerce-paypal-payments' ) }
</label>
</div>
<div className="ppcp-r-modal__field-rdb">
<PayPalRdb
id="only-required-3d-secure"
name={ THREED_SECURE_GROUP_NAME }
value="only-required-3d-secure"
currentValue={ threeDSecure }
handleRdbState={ setThreeDSecure }
/>
<label htmlFor="only-required-3d-secure">
{ __(
'Only when required',
'woocommerce-paypal-payments'
) }
</label>
</div>
<div className="ppcp-r-modal__field-rdb">
<PayPalRdb
id="always-3d-secure"
name={ THREED_SECURE_GROUP_NAME }
value="always-3d-secure"
currentValue={ threeDSecure }
handleRdbState={ setThreeDSecure }
/>
<label htmlFor="always-3d-secure">
{ __(
'Always require 3D Secure',
'woocommerce-paypal-payments'
) }
</label>
</div>
</div>
<div className="ppcp-r-modal__field-rows">
<div className="ppcp-r-modal__field-row ppcp-r-modal__field-row--save">
<Button variant="primary">
{ __( 'Save changes', 'woocommerce-paypal-payments' ) }
</Button>
</div>
</div>
</PaymentMethodModal>
);
};
export default ModalAcdc;

View file

@ -0,0 +1,62 @@
import PaymentMethodModal from '../../../ReusableComponents/PaymentMethodModal';
import { __ } from '@wordpress/i18n';
import { Button, ToggleControl } from '@wordpress/components';
import { PayPalRdb } from '../../../ReusableComponents/Fields';
import { useState } from '@wordpress/element';
const ModalFastlane = ( { setModalIsVisible } ) => {
const [ fastlaneSettings, setFastlaneSettings ] = useState( {
cardholderName: false,
displayWatermark: false,
} );
const updateFormValue = ( key, value ) => {
setFastlaneSettings( { ...fastlaneSettings, [ key ]: value } );
};
return (
<PaymentMethodModal
setModalIsVisible={ setModalIsVisible }
icon="payment-method-fastlane"
title={ __( 'Fastlane by PayPal', 'woocommerce-paypal-payments' ) }
>
<div className="ppcp-r-modal__field-rows ppcp-r-modal__field-rows--fastlane">
<div className="ppcp-r-modal__field-row">
<ToggleControl
className="ppcp-r-modal__inverted-toggle-control"
checked={ fastlaneSettings.cardholderName }
onChange={ ( newValue ) =>
updateFormValue( 'cardholderName', newValue )
}
label={ __(
'Display cardholder name',
'woocommerce-paypal-payments'
) }
id="ppcp-r-fastlane-settings-cardholder"
/>
</div>
<div className="ppcp-r-modal__field-row">
<ToggleControl
className="ppcp-r-modal__inverted-toggle-control"
checked={ fastlaneSettings.displayWatermark }
onChange={ ( newValue ) =>
updateFormValue( 'displayWatermark', newValue )
}
label={ __(
'Display Fastlane Watermark',
'woocommerce-paypal-payments'
) }
id="ppcp-r-fastlane-settings-watermark"
/>
</div>
<div className="ppcp-r-modal__field-row ppcp-r-modal__field-row--save">
<Button variant="primary">
{ __( 'Save changes', 'woocommerce-paypal-payments' ) }
</Button>
</div>
</div>
</PaymentMethodModal>
);
};
export default ModalFastlane;

View file

@ -0,0 +1,87 @@
import PaymentMethodModal from '../../../ReusableComponents/PaymentMethodModal';
import { __ } from '@wordpress/i18n';
import { ToggleControl, Button } from '@wordpress/components';
import { useState } from '@wordpress/element';
const ModalPayPal = ( { setModalIsVisible } ) => {
const [ paypalSettings, setPaypalSettings ] = useState( {
checkoutPageTitle: 'PayPal',
checkoutPageDescription: 'Pay via PayPal',
showLogo: false,
} );
const updateFormValue = ( key, value ) => {
setPaypalSettings( { ...paypalSettings, [ key ]: value } );
};
return (
<PaymentMethodModal
setModalIsVisible={ setModalIsVisible }
icon="payment-method-paypal"
container="small"
title={ __( 'PayPal', 'woocommerce-paypal-payments' ) }
>
<div className="ppcp-r-modal__field-rows">
<div className="ppcp-r-modal__field-row">
<label htmlFor="ppcp-r-paypal-settings-checkout-title">
{ __(
'Checkout page title',
'woocommerce-paypal-payments'
) }
</label>
<input
type="text"
id="ppcp-r-paypal-settings-checkout-title"
value={ paypalSettings.checkoutPageTitle }
onInput={ ( e ) =>
updateFormValue(
'checkoutPageTitle',
e.target.value
)
}
/>
</div>
<div className="ppcp-r-modal__field-row">
<label htmlFor="ppcp-r-paypal-settings-checkout-page-description">
{ __(
'Checkout page description',
'woocommerce-paypal-payments'
) }
</label>
<input
type="text"
id="ppcp-r-paypal-settings-checkout-page-description"
value={ paypalSettings.checkoutPageDescription }
onInput={ ( e ) =>
updateFormValue(
'checkoutPageDescription',
e.target.value
)
}
/>
</div>
<div className="ppcp-r-modal__field-row">
<ToggleControl
className="ppcp-r-modal__inverted-toggle-control"
label={ __(
'Show logo',
'woocommerce-paypal-payments'
) }
id="ppcp-r-paypal-settings-show-logo"
checked={ paypalSettings.showLogo }
onChange={ ( newValue ) => {
updateFormValue( 'showLogo', newValue );
} }
/>
</div>
<div className="ppcp-r-modal__field-row ppcp-r-modal__field-row--save">
<Button variant="primary">
{ __( 'Save changes', 'woocommerce-paypal-payments' ) }
</Button>
</div>
</div>
</PaymentMethodModal>
);
};
export default ModalPayPal;

View file

@ -1,5 +1,185 @@
import SettingsCard from '../../ReusableComponents/SettingsCard';
import { __ } from '@wordpress/i18n';
import PaymentMethodItem from '../../ReusableComponents/PaymentMethodItem';
import ModalPayPal from './Modals/ModalPayPal';
import ModalFastlane from './Modals/ModalFastlane';
import ModalAcdc from './Modals/ModalAcdc';
const TabPaymentMethods = () => {
return <div>PaymentMethods tab</div>;
const renderPaymentMethods = ( data ) => {
return data.map( ( paymentMethod ) => (
<PaymentMethodItem key={ paymentMethod.id } { ...paymentMethod } />
) );
};
return (
<div className="ppcp-r-payment-methods">
<SettingsCard
title={ __( 'PayPal Checkout', 'woocommerce-paypal-payments' ) }
description={ __(
'Select your preferred checkout option with PayPal for easy payment processing.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-standard.svg"
>
{ renderPaymentMethods( paymentMethodsPayPalCheckoutDefault ) }
</SettingsCard>
<SettingsCard
title={ __(
'Online Card Payments',
'woocommerce-paypal-payments'
) }
description={ __(
'Select your preferred card payment options for efficient payment processing.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-online-methods.svg"
>
{ renderPaymentMethods(
paymentMethodsOnlineCardPaymentsDefault
) }
</SettingsCard>
<SettingsCard
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
description={ __(
'With alternative payment methods, customers across the globe can pay with their bank accounts and other local payment methods.',
'woocommerce-paypal-payments'
) }
icon="icon-checkout-alternative-methods.svg"
>
{ renderPaymentMethods( paymentMethodsAlternativeDefault ) }
</SettingsCard>
</div>
);
};
const paymentMethodsPayPalCheckoutDefault = [
{
id: 'paypal',
title: __( 'PayPal', 'woocommerce-paypal-payments' ),
description: __(
'Our all-in-one checkout solution lets you offer PayPal, Venmo, Pay Later options, and more to help maximize conversion.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-paypal',
modal: ModalPayPal,
},
{
id: 'venmo',
title: __( 'Venmo', 'woocommerce-paypal-payments' ),
description: __(
'Offer Venmo at checkout to millions of active users.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-venmo',
},
{
id: 'paypal_credit',
title: __( 'PayPal Credit', 'woocommerce-paypal-payments' ),
description: __(
'Get paid in full at checkout while giving your customers the option to pay interest free if paid within 6 months on orders over $99.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-paypal',
},
{
id: 'credit_and_debit_card_payments',
title: __(
'Credit and debit card payments',
'woocommerce-paypal-payments'
),
description: __(
"Accept all major credit and debit cards - even if your customer doesn't have a PayPal account.",
'woocommerce-paypal-payments'
),
icon: 'payment-method-cards',
},
];
const paymentMethodsOnlineCardPaymentsDefault = [
{
id: 'advanced_credit_and_debit_card_payments',
title: __(
'Advanced Credit and Debit Card Payments',
'woocommerce-paypal-payments'
),
description: __(
"Present custom credit and debit card fields to your payers so they can pay with credit and debit cards using your site's branding.",
'woocommerce-paypal-payments'
),
icon: 'payment-method-cards',
modal: ModalAcdc,
},
{
id: 'fastlane',
title: __( 'Fastlane by PayPal', 'woocommerce-paypal-payments' ),
description: __(
'Tap into the scale and trust of PayPals customer network to recognize shoppers and make guest checkout more seamless than ever.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-fastlane',
modal: ModalFastlane,
},
{
id: 'apply_pay',
title: __( 'Apple Pay', 'woocommerce-paypal-payments' ),
description: __(
'Allow customers to pay via their Apple Pay digital wallet.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-apple-pay',
},
{
id: 'google_pay',
title: __( 'Google Pay', 'woocommerce-paypal-payments' ),
description: __(
'Allow customers to pay via their Google Pay digital wallet.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-google-pay',
},
];
const paymentMethodsAlternativeDefault = [
{
id: 'bancontact',
title: __( 'Bancontact', 'woocommerce-paypal-payments' ),
description: __(
'Bancontact is the most widely used, accepted and trusted electronic payment method in Belgium. Bancontact makes it possible to pay directly through the online payment systems of all major Belgian banks.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-bancontact',
},
{
id: 'ideal',
title: __( 'iDEAL', 'woocommerce-paypal-payments' ),
description: __(
'iDEAL is a payment method in the Netherlands that allows buyers to select their issuing bank from a list of options.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-ideal',
},
{
id: 'eps',
title: __( 'eps', 'woocommerce-paypal-payments' ),
description: __(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum porttitor massa ex, eget luctus lacus iaculis at.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-eps',
},
{
id: 'blik',
title: __( 'BLIK', 'woocommerce-paypal-payments' ),
description: __(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum porttitor massa ex, eget luctus lacus iaculis at.',
'woocommerce-paypal-payments'
),
icon: 'payment-method-blik',
},
];
export default TabPaymentMethods;

View file

@ -101,41 +101,23 @@ const WelcomeDocs = () => {
return (
<div className="ppcp-r-welcome-docs">
<h2 className="ppcp-r-welcome-docs__title">
{ __(
`Want to know more about PayPal Payments?`,
'woocommerce-paypal-payments'
) }
</h2>
<h2 className="ppcp-r-welcome-docs__title">{ __( `Want to know more about PayPal Payments?`, 'woocommerce-paypal-payments' ) }</h2>
<div className="ppcp-r-welcome-docs__wrapper">
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'PayPal Checkout',
'woocommerce-paypal-payments'
) }
title={ __( 'PayPal Checkout', 'woocommerce-paypal-payments' ) }
titleType={ BADGE_BOX_TITLE_BIG }
textBadge={ __(
'from 3.49% + $0.49 USD<sup>1</sup>',
'woocommerce-paypal-payments'
) }
textBadge={ __( 'from 3.49% + $0.49 USD<sup>1</sup>', 'woocommerce-paypal-payments' ) }
description={ __(
'Our all-in-one checkout solution lets you offer PayPal, Venmo, Pay Later options, and more to help maximise conversion',
'woocommerce-paypal-payments'
) }
/>
<BadgeBox
title={ __(
'Included in PayPal Checkout',
'woocommerce-paypal-payments'
) }
titleType={ BADGE_BOX_TITLE_BIG }
/>
title={ __( 'Included in PayPal Checkout', 'woocommerce-paypal-payments' ) }
titleType={ BADGE_BOX_TITLE_BIG }/>
<BadgeBox
title={ __(
'Pay with PayPal',
'woocommerce-paypal-payments'
) }
title={ __( 'Pay with PayPal', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-paypal.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
@ -146,15 +128,10 @@ const WelcomeDocs = () => {
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<Separator className="ppcp-r-page-welcome-mode-separator"/>
<BadgeBox
title={ __(
'Pay Later',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-payment-method-paypal-small.svg',
] }
title={ __( 'Pay Later', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-paypal-small.svg' ] }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
@ -164,7 +141,7 @@ const WelcomeDocs = () => {
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<Separator className="ppcp-r-page-welcome-mode-separator"/>
<BadgeBox
title={ __( 'Venmo', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-venmo.svg' ] }
@ -177,7 +154,7 @@ const WelcomeDocs = () => {
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<Separator className="ppcp-r-page-welcome-mode-separator"/>
<BadgeBox
title={ __( 'Crypto', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-payment-method-crypto.svg' ] }
@ -193,31 +170,14 @@ const WelcomeDocs = () => {
</div>
<div className="ppcp-r-welcome-docs__col">
<BadgeBox
title={ __(
'Optional payment methods',
'woocommerce-paypal-payments'
) }
title={ __( 'Optional payment methods', 'woocommerce-paypal-payments' ) }
titleType={ BADGE_BOX_TITLE_BIG }
description={ __(
'with additional application',
'woocommerce-paypal-payments'
) }
description={ __( 'with additional application', 'woocommerce-paypal-payments' ) }
/>
<BadgeBox
title={ __(
'Custom Card Fields',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-visa.svg',
'icon-button-mastercard.svg',
'icon-button-amex.svg',
'icon-button-discover.svg',
] }
textBadge={ __(
'from 2.59% + $0.49 USD<sup>1</sup>',
'woocommerce-paypal-payments'
) }
title={ __( 'Custom Card Fields', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-visa.svg', 'icon-button-mastercard.svg', 'icon-button-amex.svg', 'icon-button-discover.svg' ] }
textBadge={ __( 'from 2.59% + $0.49 USD<sup>1</sup>', 'woocommerce-paypal-payments' ) }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
@ -227,20 +187,11 @@ const WelcomeDocs = () => {
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<Separator className="ppcp-r-page-welcome-mode-separator"/>
<BadgeBox
title={ __(
'Digital Wallets',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-apple-pay.svg',
'icon-button-google-pay.svg',
] }
textBadge={ __(
'from 2.59% + $0.49 USD<sup>1</sup>',
'woocommerce-paypal-payments'
) }
title={ __( 'Digital Wallets', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-apple-pay.svg', 'icon-button-google-pay.svg' ] }
textBadge={ __( 'from 2.59% + $0.49 USD<sup>1</sup>', 'woocommerce-paypal-payments' ) }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
@ -250,22 +201,11 @@ const WelcomeDocs = () => {
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<Separator className="ppcp-r-page-welcome-mode-separator"/>
<BadgeBox
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
imageBadge={ [
'icon-button-sepa.svg',
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
] }
textBadge={ __(
'from 3.49% + $0.49 USD<sup>1</sup>',
'woocommerce-paypal-payments'
) }
title={ __( 'Alternative Payment Methods', 'woocommerce-paypal-payments' ) }
imageBadge={ [ 'icon-button-sepa.svg', 'icon-button-ideal.svg', 'icon-button-blik.svg', 'icon-button-bancontact.svg' ] }
textBadge={ __( 'from 3.49% + $0.49 USD<sup>1</sup>', 'woocommerce-paypal-payments' ) }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
@ -275,16 +215,11 @@ const WelcomeDocs = () => {
'https://woocommerce.com/document/woocommerce-paypal-payments/#manual-credential-input '
) }
/>
<Separator className="ppcp-r-page-welcome-mode-separator" />
<Separator className="ppcp-r-page-welcome-mode-separator"/>
<BadgeBox
title={ __( '', 'woocommerce-paypal-payments' ) }
imageBadge={ [
'icon-payment-method-fastlane-small.svg',
] }
textBadge={ __(
'from 2.59% + $0.49 USD<sup>1</sup>',
'woocommerce-paypal-payments'
) }
imageBadge={ [ 'icon-payment-method-fastlane-small.svg' ] }
textBadge={ __( 'from 2.59% + $0.49 USD<sup>1</sup>', 'woocommerce-paypal-payments' ) }
description={ sprintf(
// translators: %s: Link to PayPal REST application guide
__(
@ -298,7 +233,7 @@ const WelcomeDocs = () => {
</div>
<p
className="ppcp-r-welcome-docs__description"
dangerouslySetInnerHTML={ { __html: pricesBasedDescription } }
dangerouslySetInnerHTML={ { __html: pricesBasedDescription, } }
></p>
</div>
);

View file

@ -47,7 +47,11 @@ return array(
return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) );
},
'settings.rest.connect_manual' => static function ( ContainerInterface $container ) : ConnectManualRestEndpoint {
return new ConnectManualRestEndpoint();
return new ConnectManualRestEndpoint(
$container->get( 'api.paypal-host-production' ),
$container->get( 'api.paypal-host-sandbox' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'settings.casual-selling.supported-countries' => static function ( ContainerInterface $container ) : array {
return array(

View file

@ -9,6 +9,15 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
use RuntimeException;
use stdClass;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Helper\InMemoryCache;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WP_REST_Server;
use WP_REST_Response;
use WP_REST_Request;
@ -17,6 +26,28 @@ use WP_REST_Request;
* REST controller for connection via manual credentials input.
*/
class ConnectManualRestEndpoint extends RestEndpoint {
/**
* The API host for the live mode.
*
* @var string
*/
private string $live_host;
/**
* The API host for the sandbox mode.
*
* @var string
*/
private string $sandbox_host;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* The base path for this REST controller.
*
@ -44,6 +75,24 @@ class ConnectManualRestEndpoint extends RestEndpoint {
),
);
/**
* ConnectManualRestEndpoint constructor.
*
* @param string $live_host The API host for the live mode.
* @param string $sandbox_host The API host for the sandbox mode.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
string $live_host,
string $sandbox_host,
LoggerInterface $logger
) {
$this->live_host = $live_host;
$this->sandbox_host = $sandbox_host;
$this->logger = $logger;
}
/**
* Configure REST API routes.
*/
@ -72,21 +121,116 @@ class ConnectManualRestEndpoint extends RestEndpoint {
$this->field_map
);
if ( ! isset( $data['client_id'] ) || empty( $data['client_id'] )
|| ! isset( $data['client_secret'] ) || empty( $data['client_secret'] ) ) {
$client_id = $data['client_id'] ?? '';
$client_secret = $data['client_secret'] ?? '';
$use_sandbox = (bool) ( $data['use_sandbox'] ?? false );
if ( empty( $client_id ) || empty( $client_secret ) ) {
return rest_ensure_response(
array(
'success' => false,
'message' => 'No client ID or secret provided.',
)
);
}
try {
$payee = $this->request_payee( $client_id, $client_secret, $use_sandbox );
} catch ( Exception $exception ) {
return rest_ensure_response(
array(
'success' => false,
'message' => $exception->getMessage(),
)
);
}
$result = array(
'merchantId' => 'bt_us@woocommerce.com',
'email' => 'AT45V2DGMKLRY',
'merchantId' => $payee->merchant_id,
'email' => $payee->email_address,
'success' => true,
);
return rest_ensure_response( $result );
}
/**
* Retrieves the payee object with the merchant data
* by creating a minimal PayPal order.
*
* @param string $client_id The client ID.
* @param string $client_secret The client secret.
* @param bool $use_sandbox Whether to use the sandbox mode.
* @return stdClass The payee object.
* @throws Exception When failed to retrieve payee.
*
* phpcs:disable Squiz.Commenting
* phpcs:disable Generic.Commenting
*/
private function request_payee(
string $client_id,
string $client_secret,
bool $use_sandbox
) : stdClass {
$host = $use_sandbox ? $this->sandbox_host : $this->live_host;
$empty_settings = new class() implements ContainerInterface
{
public function get( string $id ) {
throw new NotFoundException();
}
public function has( string $id ) {
return false;
}
};
$bearer = new PayPalBearer(
new InMemoryCache(),
$host,
$client_id,
$client_secret,
$this->logger,
$empty_settings
);
$orders = new Orders(
$host,
$bearer,
$this->logger
);
$request_body = array(
'intent' => 'CAPTURE',
'purchase_units' => array(
array(
'amount' => array(
'currency_code' => 'USD',
'value' => 1.0,
),
),
),
);
$response = $orders->create( $request_body );
$body = json_decode( $response['body'] );
$order_id = $body->id;
$order_response = $orders->order( $order_id );
$order_body = json_decode( $order_response['body'] );
$pu = $order_body->purchase_units[0];
$payee = $pu->payee;
if ( ! is_object( $payee ) ) {
throw new RuntimeException( 'Payee not found.' );
}
if ( ! isset( $payee->merchant_id ) || ! isset( $payee->email_address ) ) {
throw new RuntimeException( 'Payee info not found.' );
}
return $payee;
}
}

File diff suppressed because it is too large Load diff

View file

@ -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",

Some files were not shown because too many files have changed in this diff Show more