🔀 Merge branch 'trunk'

This commit is contained in:
Philipp Stracker 2024-10-21 15:46:27 +02:00
commit 32d870bd6b
No known key found for this signature in database
39 changed files with 11104 additions and 307 deletions

View file

@ -1,5 +1,30 @@
*** Changelog ***
= 2.9.3 - 2024-10-15 =
* Fix - Multi-currency support #2667
* Fix - "0.00" amount in Google Pay for virtual products #2636
* Fix - Unsuccessfully payment from product page with Apple Pay button #2643
* Fix - Button Unlinking PayPal Subscriptions plan does not showing for simple subscription #2618
* Fix - Declare tokenization for ACDC only when vaulting enabled #2581
* Fix - Classic shortcode block type checks #2608
* Fix - PUI error in editor #2580
* Fix - Add a new namespaced script loader for ApplePay #2682 #2675
* Fix - Axo Block: Fix the Fastlane modal info message text overflow issue #2663
* Fix - Add Custom Placeholder Handling when rendering the card fields #2651
* Fix - Use the PayPal icons instead of WC ones #2639
* Fix - Google Pay preview config and style #2661
* Fix - Improve context detection #2631
* Fix - Check that get_the_ID is valid before using #2573
* Fix - Axo Block: Always display the Fastlane watermark in the includeAdditionalInfo mode #2690
* Fix - Axo Block: Display card fields for authenticated cardless profiles #2672
* Fix - Google Pay: Fix button preview in the editor #2688
* Fix - ACDC gateway not visible on the block Checkout for logged-out users #2693
* Enhancement - Enhancement - Add Fastlane support for Checkout block
* Enhancement - Multiple calls to POST /v1/oauth2/token?grant_type=client_credentials&response_type=id_token #2671
* Enhancement - Fastlane update shipping options & taxes when changing address #2665
* Enhancement - Axo: Remove Axo from the Checkout block in the editor and add an ACDC card preview #2662
* Enhancement - Set email when creating order for express payment #2577
= 2.9.2 - 2024-10-01 =
* Enhancement - Add Fastlane support for Classic Checkout
* Fix - Fatal error when Pay Later messaging configurator was disabled with a code snippet

View file

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

View file

@ -716,7 +716,6 @@ 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,
@ -746,7 +745,6 @@ 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

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

View file

@ -174,9 +174,6 @@ $fast-transition-duration: 0.5s;
grid-area: watermark;
justify-self: end;
grid-column: 1;
}
&:not(.wc-block-axo-is-authenticated) .wc-block-checkout-axo-block-watermark-container {
margin-top: 0;
}
@ -259,8 +256,8 @@ a.wc-block-axo-change-link {
.wp-block-woocommerce-checkout-fields-block:not(.wc-block-axo-is-loaded) {
.wc-block-checkout-axo-block-watermark-container {
display: flex;
justify-content: left;
margin-left: 10px;
justify-content: right;
margin-right: 10px;
align-items: center;
position: relative;

View file

@ -1,6 +1,7 @@
import { useEffect, useCallback, useState } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { log } from '../../../../../ppcp-axo/resources/js/Helper/Debug';
import { Card } from '../Card';
import { STORE_NAME } from '../../stores/axoStore';
@ -16,27 +17,48 @@ export const Payment = ( { fastlaneSdk, onPaymentLoad } ) => {
const [ isCardElementReady, setIsCardElementReady ] = useState( false );
// Select relevant states from the AXO store
const { isGuest, isEmailLookupCompleted } = useSelect(
const { isGuest, isEmailLookupCompleted, cardDetails } = useSelect(
( select ) => ( {
isGuest: select( STORE_NAME ).getIsGuest(),
isEmailLookupCompleted:
select( STORE_NAME ).getIsEmailLookupCompleted(),
cardDetails: select( STORE_NAME ).getCardDetails(),
} ),
[]
);
/**
* Loads and renders the Fastlane card fields component when necessary.
* This function is called for:
* 1. Guest users who have completed email lookup
* 2. Authenticated users who are missing card details
*
* The component allows users to enter new card details for payment.
*/
const loadPaymentComponent = useCallback( async () => {
if ( isGuest && isEmailLookupCompleted && isCardElementReady ) {
const paymentComponent = await fastlaneSdk.FastlaneCardComponent(
{}
);
paymentComponent.render( `#fastlane-card` );
if (
( isGuest && isEmailLookupCompleted && isCardElementReady ) ||
( ! isGuest && ! cardDetails )
) {
try {
const paymentComponent =
await fastlaneSdk.FastlaneCardComponent( {} );
// Check if the container exists before rendering
const cardContainer =
document.querySelector( '#fastlane-card' );
if ( cardContainer ) {
paymentComponent.render( '#fastlane-card' );
onPaymentLoad( paymentComponent );
}
} catch ( error ) {
log( `Error loading payment component: ${ error }`, 'error' );
}
}
}, [
isGuest,
isEmailLookupCompleted,
isCardElementReady,
cardDetails,
fastlaneSdk,
onPaymentLoad,
] );
@ -48,19 +70,27 @@ export const Payment = ( { fastlaneSdk, onPaymentLoad } ) => {
}
}, [ isGuest, isEmailLookupCompleted ] );
// Load payment component when dependencies change
// Load payment component when card element is ready
useEffect( () => {
if ( isCardElementReady ) {
loadPaymentComponent();
}, [ loadPaymentComponent ] );
// Conditional rendering based on user state:
// 1. If authenticated: Render the Card component
// 2. If guest with completed email lookup: Render the card fields
// 3. If guest without completed email lookup: Render a message to enter email
if ( isGuest ) {
if ( isEmailLookupCompleted ) {
return <div id="fastlane-card" key="fastlane-card" />;
}
}, [ isCardElementReady, loadPaymentComponent ] );
/**
* Determines which component to render based on the current state.
*
* Rendering logic:
* 1. For guests without completed email lookup: Show message to enter email
* 2. For guests with completed email lookup: Render Fastlane card fields
* 3. For authenticated users without card details: Render Fastlane card fields
* 4. For authenticated users with card details: Render Card component
*
* @return {JSX.Element} The appropriate component based on the current state
*/
const renderPaymentComponent = () => {
// Case 1: Guest user without completed email lookup
if ( isGuest && ! isEmailLookupCompleted ) {
return (
<div id="ppcp-axo-block-radio-content">
{ __(
@ -70,5 +100,18 @@ export const Payment = ( { fastlaneSdk, onPaymentLoad } ) => {
</div>
);
}
// Case 2 & 3: Guest with completed email lookup or authenticated user without card details
if (
( isGuest && isEmailLookupCompleted ) ||
( ! isGuest && ! cardDetails )
) {
return <div id="fastlane-card" />;
}
// Case 4: Authenticated user with card details
return <Card fastlaneSdk={ fastlaneSdk } showWatermark={ ! isGuest } />;
};
return renderPaymentComponent();
};

View file

@ -16,9 +16,6 @@ import {
*/
const WatermarkManager = ( { fastlaneSdk } ) => {
// Select relevant states from the AXO store
const isGuest = useSelect( ( select ) =>
select( STORE_NAME ).getIsGuest()
);
const isAxoActive = useSelect( ( select ) =>
select( STORE_NAME ).getIsAxoActive()
);
@ -34,7 +31,6 @@ const WatermarkManager = ( { fastlaneSdk } ) => {
isAxoActive,
isAxoScriptLoaded,
fastlaneSdk,
isGuest,
} );
} else {
// Remove watermark when AXO is inactive and not loading
@ -43,7 +39,7 @@ const WatermarkManager = ( { fastlaneSdk } ) => {
// Cleanup function to remove watermark on unmount
return removeWatermark;
}, [ fastlaneSdk, isGuest, isAxoActive, isAxoScriptLoaded ] );
}, [ fastlaneSdk, isAxoActive, isAxoScriptLoaded ] );
// This component doesn't render anything directly
return null;

View file

@ -112,13 +112,11 @@ export const renderWatermarkContent = ( content ) => {
* @param {boolean} params.isAxoActive - Whether AXO is active.
* @param {boolean} params.isAxoScriptLoaded - Whether AXO script is loaded.
* @param {Object} params.fastlaneSdk - The Fastlane SDK instance.
* @param {boolean} params.isGuest - Whether the user is a guest.
*/
export const updateWatermarkContent = ( {
isAxoActive,
isAxoScriptLoaded,
fastlaneSdk,
isGuest,
} ) => {
if ( ! isAxoActive && ! isAxoScriptLoaded ) {
// Show loading spinner
@ -134,7 +132,7 @@ export const updateWatermarkContent = ( {
createElement( Watermark, {
fastlaneSdk,
name: 'fastlane-watermark-email',
includeAdditionalInfo: isGuest,
includeAdditionalInfo: true,
} )
);
} else {

View file

@ -18,7 +18,8 @@ import useCustomerData from './useCustomerData';
*/
const useAxoCleanup = () => {
// Get dispatch functions from the AXO store
const { setIsAxoActive, setIsGuest } = useDispatch( STORE_NAME );
const { setIsAxoActive, setIsGuest, setIsEmailLookupCompleted } =
useDispatch( STORE_NAME );
// Get functions to update WooCommerce shipping and billing addresses
const {
@ -45,6 +46,7 @@ const useAxoCleanup = () => {
// Reset AXO state
setIsAxoActive( false );
setIsGuest( true );
setIsEmailLookupCompleted( false );
// Remove AXO UI elements
removeShippingChangeButton();

View file

@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class BlocksModule
@ -69,8 +70,11 @@ class BlocksModule implements ServiceModule, ExtendingModule, ExecutableModule {
function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
$payment_method_registry->register( $c->get( 'blocks.method' ) );
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
// Include ACDC in the Block Checkout only in case Axo doesn't exist or is not available or the user is logged in.
if ( ! $c->has( 'axoblock.available' ) || ! $c->get( 'axoblock.available' ) || is_user_logged_in() ) {
if ( ( $settings->has( 'axo_enabled' ) && ! $settings->get( 'axo_enabled' ) ) || is_user_logged_in() ) {
$payment_method_registry->register( $c->get( 'blocks.advanced-card-method' ) );
}
}

View file

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

View file

@ -0,0 +1,53 @@
import { useState, useEffect } from '@wordpress/element';
import useGooglepayApiToGenerateButton from '../hooks/useGooglepayApiToGenerateButton';
import usePayPalScript from '../hooks/usePayPalScript';
import useGooglepayScript from '../hooks/useGooglepayScript';
import useGooglepayConfig from '../hooks/useGooglepayConfig';
const GooglepayButton = ( { namespace, buttonConfig, ppcpConfig } ) => {
const [ buttonHtml, setButtonHtml ] = useState( '' );
const [ buttonElement, setButtonElement ] = useState( null );
const [ componentFrame, setComponentFrame ] = useState( null );
const isPayPalLoaded = usePayPalScript( namespace, ppcpConfig );
const isGooglepayLoaded = useGooglepayScript(
componentFrame,
buttonConfig,
isPayPalLoaded
);
const googlepayConfig = useGooglepayConfig( namespace, isGooglepayLoaded );
useEffect( () => {
if ( ! buttonElement ) {
return;
}
setComponentFrame( buttonElement.ownerDocument );
}, [ buttonElement ] );
const googlepayButton = useGooglepayApiToGenerateButton(
componentFrame,
namespace,
buttonConfig,
ppcpConfig,
googlepayConfig
);
useEffect( () => {
if ( googlepayButton ) {
const hideLoader =
'<style>.block-editor-iframe__html .gpay-card-info-animated-progress-bar-container {display:none !important}</style>';
setButtonHtml( googlepayButton.outerHTML + hideLoader );
}
}, [ googlepayButton ] );
return (
<div
ref={ setButtonElement }
dangerouslySetInnerHTML={ { __html: buttonHtml } }
/>
);
};
export default GooglepayButton;

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,57 @@
import { useEffect, useState } from '@wordpress/element';
import useButtonStyles from './useButtonStyles';
const useGooglepayApiToGenerateButton = (
componentDocument,
namespace,
buttonConfig,
ppcpConfig,
googlepayConfig
) => {
const [ googlepayButton, setGooglepayButton ] = useState( null );
const buttonStyles = useButtonStyles( buttonConfig, ppcpConfig );
useEffect( () => {
if (
! componentDocument?.defaultView ||
! buttonConfig ||
! googlepayConfig
) {
return;
}
const api = componentDocument.defaultView.google?.payments?.api;
if ( ! api ) {
return;
}
const paymentsClient = new api.PaymentsClient( {
environment: 'TEST',
} );
const googlePayButtonOptions = {
allowedPaymentMethods: googlepayConfig.allowedPaymentMethods,
buttonColor: buttonConfig.buttonColor || 'black',
buttonType: buttonConfig.buttonType || 'pay',
buttonLocale: buttonConfig.buttonLocale || 'en',
buttonSizeMode: 'fill',
};
const button = paymentsClient.createButton( {
...googlePayButtonOptions,
onClick: ( event ) => {
event.preventDefault();
},
} );
setGooglepayButton( button );
return () => {
setGooglepayButton( null );
};
}, [ namespace, buttonConfig, ppcpConfig, googlepayConfig, buttonStyles ] );
return googlepayButton;
};
export default useGooglepayApiToGenerateButton;

View file

@ -0,0 +1,26 @@
import { useState, useEffect } from '@wordpress/element';
const useGooglepayConfig = ( namespace, isGooglepayLoaded ) => {
const [ googlePayConfig, setGooglePayConfig ] = useState( null );
useEffect( () => {
const fetchConfig = async () => {
if ( ! isGooglepayLoaded ) {
return;
}
try {
const config = await window[ namespace ].Googlepay().config();
setGooglePayConfig( config );
} catch ( error ) {
console.error( 'Failed to fetch Google Pay config:', error );
}
};
fetchConfig();
}, [ namespace, isGooglepayLoaded ] );
return googlePayConfig;
};
export default useGooglepayConfig;

View file

@ -0,0 +1,65 @@
import { useState, useEffect } from '@wordpress/element';
import { loadCustomScript } from '@paypal/paypal-js';
const useGooglepayScript = (
componentDocument,
buttonConfig,
isPayPalLoaded
) => {
const [ isGooglepayLoaded, setIsGooglepayLoaded ] = 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 loadGooglepayScript = 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 );
}
);
setIsGooglepayLoaded( true );
} catch ( error ) {
console.error( 'Failed to load Googlepay script:', error );
}
};
loadGooglepayScript();
}, [ componentDocument, buttonConfig, isPayPalLoaded ] );
return isGooglepayLoaded;
};
export default useGooglepayScript;

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 += ',googlepay';
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

@ -1,62 +1,15 @@
import GooglepayButton from './GooglepayButton';
import ContextHandlerFactory from './Context/ContextHandlerFactory';
import GooglepayButton from './Block/components/GooglepayButton';
class GooglepayManagerBlockEditor {
constructor( namespace, buttonConfig, ppcpConfig ) {
this.namespace = namespace;
this.buttonConfig = buttonConfig;
this.ppcpConfig = ppcpConfig;
this.googlePayConfig = null;
this.transactionInfo = null;
this.contextHandler = null;
}
init() {
( async () => {
await this.config();
} )();
}
async config() {
try {
// Gets GooglePay configuration of the PayPal merchant.
this.googlePayConfig = await window[ this.namespace ]
.Googlepay()
.config();
// Fetch transaction information.
this.transactionInfo = await this.fetchTransactionInfo();
const button = new GooglepayButton(
this.ppcpConfig.context,
null,
this.buttonConfig,
this.ppcpConfig,
this.contextHandler
);
button.init( this.googlePayConfig, this.transactionInfo );
} catch ( error ) {
console.error( 'Failed to initialize Google Pay:', error );
}
}
async fetchTransactionInfo() {
try {
if ( ! this.contextHandler ) {
this.contextHandler = ContextHandlerFactory.create(
this.ppcpConfig.context,
this.buttonConfig,
this.ppcpConfig,
null
);
}
return null;
} catch ( error ) {
console.error( 'Error fetching transaction info:', error );
throw error;
}
}
}
const GooglepayManagerBlockEditor = ( {
namespace,
buttonConfig,
ppcpConfig,
} ) => (
<GooglepayButton
namespace={ namespace }
buttonConfig={ buttonConfig }
ppcpConfig={ ppcpConfig }
/>
);
export default GooglepayManagerBlockEditor;

View file

@ -1,4 +1,5 @@
import { useEffect, useState } from '@wordpress/element';
import { loadCustomScript } from '@paypal/paypal-js';
import {
registerExpressPaymentMethod,
registerPaymentMethod,
@ -6,7 +7,6 @@ import {
import { __ } from '@wordpress/i18n';
import { loadPayPalScript } from '../../../ppcp-button/resources/js/modules/Helper/PayPalScriptLoading';
import GooglepayManager from './GooglepayManager';
import { loadCustomScript } from '@paypal/paypal-js';
import GooglepayManagerBlockEditor from './GooglepayManagerBlockEditor';
const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' );
@ -20,21 +20,19 @@ if ( typeof window.PayPalCommerceGateway === 'undefined' ) {
window.PayPalCommerceGateway = ppcpConfig;
}
const GooglePayComponent = ( props ) => {
const [ bootstrapped, setBootstrapped ] = useState( false );
const GooglePayComponent = ( { isEditing } ) => {
const [ paypalLoaded, setPaypalLoaded ] = useState( false );
const [ googlePayLoaded, setGooglePayLoaded ] = useState( false );
const [ manager, setManager ] = useState( null );
useEffect( () => {
// Load GooglePay SDK
if ( ! isEditing ) {
loadCustomScript( { url: buttonConfig.sdk_url } ).then( () => {
setGooglePayLoaded( true );
} );
ppcpConfig.url_params.components += ',googlepay';
// Load PayPal
loadPayPalScript( namespace, ppcpConfig )
.then( () => {
setPaypalLoaded( true );
@ -42,34 +40,35 @@ const GooglePayComponent = ( props ) => {
.catch( ( error ) => {
console.error( 'Failed to load PayPal script: ', error );
} );
}, [] );
}
}, [ isEditing, buttonConfig, ppcpConfig ] );
useEffect( () => {
if ( paypalLoaded && googlePayLoaded && ! manager ) {
const ManagerClass = props.isEditing
? GooglepayManagerBlockEditor
: GooglepayManager;
const newManager = new ManagerClass(
if ( ! isEditing && paypalLoaded && googlePayLoaded && ! manager ) {
const newManager = new GooglepayManager(
namespace,
buttonConfig,
ppcpConfig
);
setManager( newManager );
}
}, [ paypalLoaded, googlePayLoaded, props.isEditing ] );
}, [ paypalLoaded, googlePayLoaded, isEditing, manager ] );
useEffect( () => {
if ( manager && ! bootstrapped ) {
setBootstrapped( true );
manager.init();
if ( isEditing ) {
return (
<GooglepayManagerBlockEditor
namespace={ namespace }
buttonConfig={ buttonConfig }
ppcpConfig={ ppcpConfig }
/>
);
}
}, [ manager, bootstrapped ] );
return (
<div
id={ buttonConfig.button.wrapper.replace( '#', '' ) }
className="ppcp-button-apm ppcp-button-googlepay"
></div>
/>
);
};

View file

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

View file

@ -72,7 +72,6 @@ return array(
'FR' => $default_currencies,
'DE' => $default_currencies,
'GR' => $default_currencies,
'HK' => $default_currencies,
'HU' => $default_currencies,
'IE' => $default_currencies,
'IT' => $default_currencies,
@ -87,7 +86,6 @@ return array(
'PT' => $default_currencies,
'RO' => $default_currencies,
'SK' => $default_currencies,
'SG' => $default_currencies,
'SI' => $default_currencies,
'ES' => $default_currencies,
'SE' => $default_currencies,

View file

@ -0,0 +1,16 @@
{
"name": "woocommerce/ppcp-settings",
"type": "inpsyde-module",
"description": "Settings module",
"license": "GPL-2.0",
"require": {
"php": "^7.4 | ^8.0"
},
"autoload": {
"psr-4": {
"WooCommerce\\PayPalCommerce\\Settings\\": "src"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View file

@ -0,0 +1,14 @@
<?php
/**
* The Settings module.
*
* @package WooCommerce\PayPalCommerce\Settings
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings;
return static function () : SettingsModule {
return new SettingsModule();
};

View file

@ -0,0 +1,12 @@
{
"name": "ppcp-settings",
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"scripts": {
"watch": "wp-scripts start --webpack-src-dir=resources/js --output-path=assets",
"build": "wp-scripts build --webpack-src-dir=resources/js --output-path=assets"
},
"devDependencies": {
"@wordpress/scripts": "^30.3.0"
}
}

View file

@ -0,0 +1 @@
.red {color:red;}

View file

@ -0,0 +1,3 @@
export function App() {
return <div className="red">App</div>;
}

View file

@ -0,0 +1,7 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
createRoot( document.getElementById( 'ppcp-settings-container' ) ).render(
<App />
);

View file

@ -0,0 +1,26 @@
<?php
/**
* The Settings module services.
*
* @package WooCommerce\PayPalCommerce\Settings
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\Settings;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
'settings.url' => static function ( ContainerInterface $container ) : string {
/**
* The path cannot be false.
*
* @psalm-suppress PossiblyFalseArgument
*/
return plugins_url(
'/modules/ppcp-settings/',
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
);

View file

@ -0,0 +1,95 @@
<?php
/**
* The Settings module.
*
* @package WooCommerce\PayPalCommerce\AxoBlock
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Settings;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class SettingsModule
*/
class SettingsModule implements ServiceModule, ExecutableModule {
use ModuleClassNameIdTrait;
/**
* {@inheritDoc}
*/
public function services(): array {
return require __DIR__ . '/../services.php';
}
/**
* {@inheritDoc}
*/
public function run( ContainerInterface $container ): bool {
add_action(
'admin_enqueue_scripts',
/**
* Param types removed to avoid third-party issues.
*
* @psalm-suppress MissingClosureParamType
*/
static function( $hook_suffix ) use ( $container ) {
if ( 'woocommerce_page_wc-settings' !== $hook_suffix ) {
return;
}
/**
* Require resolves.
*
* @psalm-suppress UnresolvableInclude
*/
$script_asset_file = require dirname( realpath( __FILE__ ) ?: '', 2 ) . '/assets/index.asset.php';
$module_url = $container->get( 'settings.url' );
wp_register_script(
'ppcp-admin-settings',
$module_url . '/assets/index.js',
$script_asset_file['dependencies'],
$script_asset_file['version'],
true
);
wp_enqueue_script( 'ppcp-admin-settings' );
/**
* Require resolves.
*
* @psalm-suppress UnresolvableInclude
*/
$style_asset_file = require dirname( realpath( __FILE__ ) ?: '', 2 ) . '/assets/style.asset.php';
wp_register_style(
'ppcp-admin-settings',
$module_url . '/assets/style-style.css',
$style_asset_file['dependencies'],
$style_asset_file['version']
);
wp_enqueue_style( 'ppcp-admin-settings' );
}
);
add_action(
'woocommerce_paypal_payments_gateway_admin_options_wrapper',
static function(): void {
global $hide_save_button;
$hide_save_button = true;
echo '<div id="ppcp-settings-container"></div>';
}
);
return true;
}
}

View file

@ -0,0 +1,12 @@
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
const path = require( 'path' );
module.exports = {
...defaultConfig,
...{
entry: {
index: path.resolve( process.cwd(), 'resources/js', 'index.js' ),
style: path.resolve( process.cwd(), 'resources/css', 'style.scss' ),
},
},
};

File diff suppressed because it is too large Load diff

View file

@ -118,7 +118,8 @@ return array(
$container->get( 'wcgateway.place-order-button-text' ),
$container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'vaulting.vault-v3-enabled' ),
$container->get( 'vaulting.wc-payment-tokens' )
$container->get( 'vaulting.wc-payment-tokens' ),
$container->get( 'wcgateway.settings.admin-settings-enabled' )
);
},
'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway {
@ -1914,4 +1915,8 @@ return array(
return $simple_redirect_tasks;
},
'wcgateway.settings.admin-settings-enabled' => static function( ContainerInterface $container ): bool {
return $container->has( 'settings.url' );
},
);

View file

@ -202,6 +202,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
private $wc_payment_tokens;
/**
* Whether settings module is enabled.
*
* @var bool
*/
private $admin_settings_enabled;
/**
* PayPalGateway constructor.
*
@ -225,6 +232,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
* @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payment tokens.
* @param bool $admin_settings_enabled Whether settings module is enabled.
*/
public function __construct(
SettingsRenderer $settings_renderer,
@ -246,7 +254,8 @@ class PayPalGateway extends \WC_Payment_Gateway {
string $place_order_button_text,
PaymentTokensEndpoint $payment_tokens_endpoint,
bool $vault_v3_enabled,
WooCommercePaymentTokens $wc_payment_tokens
WooCommercePaymentTokens $wc_payment_tokens,
bool $admin_settings_enabled
) {
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
@ -270,6 +279,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
$this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->vault_v3_enabled = $vault_v3_enabled;
$this->wc_payment_tokens = $wc_payment_tokens;
$this->admin_settings_enabled = $admin_settings_enabled;
$default_support = array(
'products',
@ -745,6 +755,17 @@ class PayPalGateway extends \WC_Payment_Gateway {
return $ret;
}
/**
* Override the parent admin_options method.
*/
public function admin_options() {
if ( ! $this->admin_settings_enabled ) {
parent::admin_options();
}
do_action( 'woocommerce_paypal_payments_gateway_admin_options_wrapper', $this );
}
/**
* Returns the settings renderer.
*

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "woocommerce-paypal-payments",
"version": "2.9.2",
"version": "2.9.3",
"lockfileVersion": 2,
"requires": true,
"packages": {

View file

@ -1,6 +1,6 @@
{
"name": "woocommerce-paypal-payments",
"version": "2.9.2",
"version": "2.9.3",
"description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0",
@ -26,6 +26,7 @@
"install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install",
"install:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn install",
"install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install",
"install:modules:ppcp-settings": "cd modules/ppcp-settings && yarn install",
"install:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn install",
"build:modules:ppcp-admin-notices": "cd modules/ppcp-admin-notices && yarn run build",
"build:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run build",
@ -47,6 +48,7 @@
"build:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run build",
"build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build",
"build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build",
"build:modules:ppcp-settings": "cd modules/ppcp-settings && yarn run build",
"build:modules": "run-p build:modules:*",
"watch:modules:ppcp-admin-notices": "cd modules/ppcp-admin-notices && yarn run watch",
"watch:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run watch",
@ -68,6 +70,7 @@
"watch:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run watch",
"watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch",
"watch:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run watch",
"watch:modules:ppcp-settings": "cd modules/ppcp-settings && yarn run watch",
"watch:modules": "run-p watch:modules:*",
"ddev:setup": "ddev start && ddev orchestrate",
"ddev:start": "ddev start",

View file

@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, credit card
Requires at least: 6.3
Tested up to: 6.6
Requires PHP: 7.4
Stable tag: 2.9.2
Stable tag: 2.9.3
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -179,6 +179,31 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
== Changelog ==
= 2.9.3 - 2024-10-15 =
* Fix - Multi-currency support #2667
* Fix - "0.00" amount in Google Pay for virtual products #2636
* Fix - Unsuccessfully payment from product page with Apple Pay button #2643
* Fix - Button Unlinking PayPal Subscriptions plan does not showing for simple subscription #2618
* Fix - Declare tokenization for ACDC only when vaulting enabled #2581
* Fix - Classic shortcode block type checks #2608
* Fix - PUI error in editor #2580
* Fix - Add a new namespaced script loader for ApplePay #2682 #2675
* Fix - Axo Block: Fix the Fastlane modal info message text overflow issue #2663
* Fix - Add Custom Placeholder Handling when rendering the card fields #2651
* Fix - Use the PayPal icons instead of WC ones #2639
* Fix - Google Pay preview config and style #2661
* Fix - Improve context detection #2631
* Fix - Check that get_the_ID is valid before using #2573
* Fix - Axo Block: Always display the Fastlane watermark in the includeAdditionalInfo mode #2690
* Fix - Axo Block: Display card fields for authenticated cardless profiles #2672
* Fix - Google Pay: Fix button preview in the editor #2688
* Fix - ACDC gateway not visible on the block Checkout for logged-out users #2693
* Enhancement - Enhancement - Add Fastlane support for Checkout block
* Enhancement - Multiple calls to POST /v1/oauth2/token?grant_type=client_credentials&response_type=id_token #2671
* Enhancement - Fastlane update shipping options & taxes when changing address #2665
* Enhancement - Axo: Remove Axo from the Checkout block in the editor and add an ACDC card preview #2662
* Enhancement - Set email when creating order for express payment #2577
= 2.9.2 - 2024-10-01 =
* Enhancement - Add Fastlane support for Classic Checkout
* Fix - Fatal error when Pay Later messaging configurator was disabled with a code snippet

View file

@ -1,156 +0,0 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use Mockery;
use Psr\Log\LoggerInterface;
use Requests_Utility_CaseInsensitiveDictionary;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Token;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\TestCase;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PaymentSource;
use function Brain\Monkey\Functions\expect;
use function Brain\Monkey\Functions\when;
class PayUponInvoiceOrderEndpointTest extends TestCase
{
private $bearer;
private $orderFactory;
private $fraudnet;
private $logger;
private $testee;
public function setUp(): void
{
parent::setUp();
$this->bearer = Mockery::mock(Bearer::class);
$token = Mockery::mock(Token::class);
$token->shouldReceive('token')->andReturn('');
$this->bearer->shouldReceive('bearer')->andReturn($token);
$this->orderFactory = Mockery::mock(OrderFactory::class);
$this->fraudnet = Mockery::mock(FraudNet::class);
$this->logger = Mockery::mock(LoggerInterface::class);
$this->testee = new PayUponInvoiceOrderEndpoint(
'',
$this->bearer,
$this->orderFactory,
$this->fraudnet,
$this->logger
);
}
public function testCreateOrder()
{
$this->markTestSkipped('must be revisited.');
list($items, $paymentSource, $headers) = $this->setStubs();
$response = [
'body' => '{"is_correct":true}',
'headers' => $headers,
];
expect('wp_remote_get')->andReturn($response);
expect('wp_remote_retrieve_response_code')->with($response)->andReturn(200);
$this->logger->shouldReceive('debug');
$wc_order = Mockery::mock(WC_Order::class);
$result = $this->testee->create($items, $paymentSource, $wc_order );
$this->assertInstanceOf(Order::class, $result);
}
public function testCreateOrderWpError()
{
$this->markTestSkipped('must be revisited.');
list($items, $paymentSource) = $this->setStubsForError();
$wpError = Mockery::mock(\WP_Error::class);
$wpError->shouldReceive('get_error_messages')->andReturn(['foo']);
$wpError->shouldReceive('get_error_message')->andReturn('foo');
expect('wp_remote_get')->andReturn($wpError);
$this->logger->shouldReceive('debug');
$wc_order = Mockery::mock(WC_Order::class);
$this->expectException(\RuntimeException::class);
$this->testee->create($items, $paymentSource, $wc_order);
}
public function testCreateOrderApiError()
{
$this->markTestSkipped('must be revisited.');
list($items, $paymentSource) = $this->setStubsForError();
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$response = [
'body' => '{"is_correct":true}',
'headers' => $headers,
];
when('get_bloginfo')->justReturn('de-DE');
expect('wp_remote_get')->andReturn($response);
expect('wp_remote_retrieve_response_code')->with($response)->andReturn(500);
$this->logger->shouldReceive('debug');
$wc_order = Mockery::mock(WC_Order::class);
$this->expectException(PayPalApiException::class);
$this->testee->create($items, $paymentSource, $wc_order);
}
/**
* @return array
*/
private function setStubs(): array
{
$order = Mockery::mock(Order::class);
$this->orderFactory
->expects('from_paypal_response')
->andReturnUsing(function (\stdClass $object) use ($order): ?Order {
return ($object->is_correct) ? $order : null;
});
$this->fraudnet->shouldReceive('session_id')->andReturn('');
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnit->shouldReceive('to_array')->andReturn([
'items' => [],
]);
$items = [$purchaseUnit];
$paymentSource = Mockery::mock(PaymentSource::class);
$paymentSource->shouldReceive('to_array')->andReturn([]);
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
return array($items, $paymentSource, $headers);
}
/**
* @return array
*/
private function setStubsForError(): array
{
$this->fraudnet->shouldReceive('session_id')->andReturn('');
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
$purchaseUnit->shouldReceive('to_array')->andReturn([
'items' => [],
]);
$items = [$purchaseUnit];
$paymentSource = Mockery::mock(PaymentSource::class);
$paymentSource->shouldReceive('to_array')->andReturn([]);
return array($items, $paymentSource);
}
}

View file

@ -123,7 +123,8 @@ class WcGatewayTest extends TestCase
'Pay via PayPal',
$this->paymentTokensEndpoint,
$this->vaultV3Enabled,
$this->wcPaymentTokens
$this->wcPaymentTokens,
false
);
}

View file

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
* Version: 2.9.2
* Version: 2.9.3
* Author: WooCommerce
* Author URI: https://woocommerce.com/
* License: GPL-2.0
@ -26,7 +26,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
define( 'PAYPAL_URL', 'https://www.paypal.com' );
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
define( 'PAYPAL_INTEGRATION_DATE', '2024-09-30' );
define( 'PAYPAL_INTEGRATION_DATE', '2024-10-11' );
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );