Merge remote-tracking branch 'origin/trunk' into modularity-module-migration

# Conflicts:
#	modules/ppcp-admin-notices/src/AdminNotices.php
#	modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
#	modules/ppcp-wc-gateway/src/WCGatewayModule.php
#	modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php
#	tests/PHPUnit/WcSubscriptions/RenewalHandlerTest.php
This commit is contained in:
Pedro Silva 2024-03-27 18:42:21 +00:00
commit 392d27f08b
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
163 changed files with 13687 additions and 1746 deletions

View file

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

View file

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

View file

@ -0,0 +1,48 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "woocommerce-paypal-payments/paylater-messages",
"title": "PayPal Pay Later messaging",
"category": "woocommerce",
"description": "PayPal Pay Later messaging will be displayed for eligible customers. Customers automatically see the most relevant Pay Later offering.",
"example": {},
"attributes": {
"id": {
"type": "string"
},
"layout": {
"type": "string",
"default": "flex"
},
"logo": {
"type": "string",
"default": "inline"
},
"position": {
"type": "string",
"default": "left"
},
"color": {
"type": "string",
"default": "black"
},
"flexColor": {
"type": "string",
"default": "white"
},
"flexRatio": {
"type": "string",
"default": "8x1"
},
"placement": {
"type": "string",
"default": "auto"
}
},
"supports": {
"html": false
},
"textdomain": "woocommerce-paypal-payments",
"editorScript": "ppcp-paylater-block",
"editorStyle": "file:./assets/css/edit.css"
}

View file

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

View file

@ -0,0 +1,12 @@
<?php
/**
* The Pay Later block module extensions.
*
* @package WooCommerce\PayPalCommerce\PayLaterBlock
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayLaterBlock;
return array();

View file

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

View file

@ -0,0 +1,33 @@
{
"name": "ppcp-paylater-block",
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"browserslist": [
"> 0.5%",
"Safari >= 8",
"Chrome >= 41",
"Firefox >= 43",
"Edge >= 14"
],
"dependencies": {
"core-js": "^3.25.0"
},
"devDependencies": {
"@babel/core": "^7.19",
"@babel/preset-env": "^7.19",
"@babel/preset-react": "^7.18.6",
"@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
"babel-loader": "^8.2",
"cross-env": "^7.0.3",
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
"webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
"watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch",
"dev": "cross-env BABEL_ENV=default webpack --watch"
}
}

View file

@ -0,0 +1,19 @@
.ppcp-overlay-parent {
display: grid;
grid-template-columns: 1fr;
}
.ppcp-overlay-child {
grid-row-start: 1;
grid-column-start: 1;
}
.ppcp-unclicable-overlay {
z-index: 10;
}
.ppcp-paylater-unavailable {
p.block-editor-warning__message {
margin-bottom: 10px;
}
}

View file

@ -0,0 +1,29 @@
import { useRef, useEffect } from '@wordpress/element';
export default function PayPalMessages({
amount,
style,
onRender,
}) {
const containerRef = useRef(null);
useEffect(() => {
const messages = paypal.Messages({
amount,
style,
onRender,
});
messages.render(containerRef.current)
.catch(err => {
// Ignore when component destroyed.
if (!containerRef.current || containerRef.current.children.length === 0) {
return;
}
console.error(err);
});
}, [amount, style, onRender]);
return <div ref={containerRef}/>
}

View file

@ -0,0 +1,223 @@
import { __ } from '@wordpress/i18n';
import { useState, useEffect } from '@wordpress/element';
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, SelectControl, Spinner } from '@wordpress/components';
import { useScriptParams } from "./hooks/script-params";
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
import PayPalMessages from "./components/PayPalMessages";
export default function Edit( { attributes, clientId, setAttributes } ) {
const { layout, logo, position, color, size, flexColor, flexRatio, placement, id } = attributes;
const isFlex = layout === 'flex';
const [paypalScriptState, setPaypalScriptState] = useState(null);
const [rendered, setRendered] = useState(false);
let amount = undefined;
const postContent = String(wp.data.select('core/editor')?.getEditedPostContent());
if (postContent.includes('woocommerce/checkout') || postContent.includes('woocommerce/cart')) {
amount = 50.0;
}
const previewStyle = {
layout,
logo: {
position,
type: logo,
},
color: flexColor,
ratio: flexRatio,
text: {
color,
size
},
};
let classes = ['ppcp-paylater-block-preview', 'ppcp-overlay-parent'];
if (PcpPayLaterBlock.vaultingEnabled || !PcpPayLaterBlock.placementEnabled) {
classes = ['ppcp-paylater-block-preview', 'ppcp-paylater-unavailable', 'block-editor-warning'];
}
const props = useBlockProps({className: classes});
const loadingElement = <div {...props}><Spinner/></div>;
useEffect(() => {
if (!id) {
setAttributes({id: 'ppcp-' + clientId});
}
}, []);
if (PcpPayLaterBlock.vaultingEnabled) {
return <div {...props}>
<div className={'block-editor-warning__contents'}>
<h3>{__('PayPal Pay Later Messaging', 'woocommerce-paypal-payments')}</h3>
<p className={'block-editor-warning__message'}>{__('Pay Later Messaging cannot be used while PayPal Vaulting is active. Disable PayPal Vaulting in the PayPal Payment settings to reactivate this block', 'woocommerce-paypal-payments')}</p>
<div className={'class="block-editor-warning__actions"'}>
<span className={'block-editor-warning__action'}>
<a href={PcpPayLaterBlock.settingsUrl} className={'components-button is-primary'}>
{__('PayPal Payments Settings', 'woocommerce-paypal-payments')}
</a>
</span>
<span className={'block-editor-warning__action'}>
<button onClick={() => wp.data.dispatch( 'core/block-editor' ).removeBlock(clientId)} type={'button'} className={'components-button is-secondary'}>
{__('Remove Block', 'woocommerce-paypal-payments')}
</button>
</span>
</div>
</div>
</div>
}
if (!PcpPayLaterBlock.placementEnabled) {
return <div {...props}>
<div className={'block-editor-warning__contents'}>
<h3>{__('PayPal Pay Later Messaging', 'woocommerce-paypal-payments')}</h3>
<p className={'block-editor-warning__message'}>{__('Pay Later Messaging cannot be used while the “WooCommerce Block” messaging placement is disabled. Enable the placement in the PayPal Payments Pay Later settings to reactivate this block.', 'woocommerce-paypal-payments')}</p>
<div className={'class="block-editor-warning__actions"'}>
<span className={'block-editor-warning__action'}>
<a href={PcpPayLaterBlock.payLaterSettingsUrl} className={'components-button is-primary'}>
{__('PayPal Payments Settings', 'woocommerce-paypal-payments')}
</a>
</span>
<span className={'block-editor-warning__action'}>
<button onClick={() => wp.data.dispatch( 'core/block-editor' ).removeBlock(clientId)} type={'button'} className={'components-button is-secondary'}>
{__('Remove Block', 'woocommerce-paypal-payments')}
</button>
</span>
</div>
</div>
</div>
}
let scriptParams = useScriptParams(PcpPayLaterBlock.ajax.cart_script_params);
if (scriptParams === null) {
return loadingElement;
}
if (scriptParams === false) {
scriptParams = {
url_params: {
clientId: 'test',
}
}
}
scriptParams.url_params.components = 'messages,buttons,funding-eligibility';
if (!paypalScriptState) {
loadPaypalScript(scriptParams, () => {
setPaypalScriptState('loaded')
}, () => {
setPaypalScriptState('failed')
});
}
if (paypalScriptState !== 'loaded') {
return loadingElement;
}
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'woocommerce-paypal-payments' ) }>
<SelectControl
label={ __( 'Layout', 'woocommerce-paypal-payments' ) }
options={ [
{ label: __( 'Text', 'woocommerce-paypal-payments' ), value: 'text' },
{ label: __( 'Banner', 'woocommerce-paypal-payments' ), value: 'flex' },
] }
value={ layout }
onChange={ ( value ) => setAttributes( { layout: value } ) }
/>
{ !isFlex && (<SelectControl
label={__('Logo', 'woocommerce-paypal-payments')}
options={[
{ label: __('Full logo', 'woocommerce-paypal-payments'), value: 'primary' },
{ label: __('Monogram', 'woocommerce-paypal-payments'), value: 'alternative' },
{ label: __('Inline', 'woocommerce-paypal-payments'), value: 'inline' },
{ label: __('Message only', 'woocommerce-paypal-payments'), value: 'none' },
]}
value={logo}
onChange={(value) => setAttributes({logo: value})}
/>)}
{ !isFlex && logo === 'primary' && (<SelectControl
label={__('Logo Position', 'woocommerce-paypal-payments')}
options={[
{ label: __( 'Left', 'woocommerce-paypal-payments' ), value: 'left' },
{ label: __( 'Right', 'woocommerce-paypal-payments' ), value: 'right' },
{ label: __( 'Top', 'woocommerce-paypal-payments' ), value: 'top' },
]}
value={position}
onChange={(value) => setAttributes({position: value})}
/>)}
{ !isFlex && (<SelectControl
label={__('Text Color', 'woocommerce-paypal-payments')}
options={[
{ label: __( 'Black / Blue logo', 'woocommerce-paypal-payments' ), value: 'black' },
{ label: __( 'White / White logo', 'woocommerce-paypal-payments' ), value: 'white' },
{ label: __( 'Monochrome', 'woocommerce-paypal-payments' ), value: 'monochrome' },
{ label: __( 'Black / Gray logo', 'woocommerce-paypal-payments' ), value: 'grayscale' },
]}
value={color}
onChange={(value) => setAttributes({color: value})}
/>)}
{ !isFlex && (<SelectControl
label={__('Text Size', 'woocommerce-paypal-payments')}
options={[
{ label: __( 'Small', 'woocommerce-paypal-payments' ), value: '12' },
{ label: __( 'Medium', 'woocommerce-paypal-payments' ), value: '14' },
{ label: __( 'Large', 'woocommerce-paypal-payments' ), value: '16' },
]}
value={size}
onChange={(value) => setAttributes({size: value})}
/>)}
{ isFlex && (<SelectControl
label={__('Color', 'woocommerce-paypal-payments')}
options={[
{ label: __( 'Blue', 'woocommerce-paypal-payments' ), value: 'blue' },
{ label: __( 'Black', 'woocommerce-paypal-payments' ), value: 'black' },
{ label: __( 'White', 'woocommerce-paypal-payments' ), value: 'white' },
{ label: __( 'White (no border)', 'woocommerce-paypal-payments' ), value: 'white-no-border' },
]}
value={flexColor}
onChange={(value) => setAttributes({flexColor: value})}
/>)}
{ isFlex && (<SelectControl
label={__('Ratio', 'woocommerce-paypal-payments')}
options={[
{ label: __( '8x1', 'woocommerce-paypal-payments' ), value: '8x1' },
{ label: __( '20x1', 'woocommerce-paypal-payments' ), value: '20x1' },
]}
value={flexRatio}
onChange={(value) => setAttributes({flexRatio: value})}
/>)}
<SelectControl
label={ __( 'Placement page', 'woocommerce-paypal-payments' ) }
help={ __( 'Used for the analytics dashboard in the merchant account.', 'woocommerce-paypal-payments' ) }
options={ [
{ label: __( 'Detect automatically', 'woocommerce-paypal-payments' ), value: 'auto' },
{ label: __( 'Cart', 'woocommerce-paypal-payments' ), value: 'cart' },
{ label: __( 'Payment', 'woocommerce-paypal-payments' ), value: 'payment' },
{ label: __( 'Product', 'woocommerce-paypal-payments' ), value: 'product' },
{ label: __( 'Product list', 'woocommerce-paypal-payments' ), value: 'product-list' },
{ label: __( 'Home', 'woocommerce-paypal-payments' ), value: 'home' },
{ label: __( 'Category', 'woocommerce-paypal-payments' ), value: 'category' },
] }
value={ placement }
onChange={ ( value ) => setAttributes( { placement: value } ) }
/>
</PanelBody>
</InspectorControls>
<div {...props}>
<div className={'ppcp-overlay-child'}>
<PayPalMessages
style={previewStyle}
amount={amount}
onRender={() => setRendered(true)}
/>
</div>
<div className={'ppcp-overlay-child ppcp-unclicable-overlay'}> {/* make the message not clickable */}
{!rendered && (<Spinner/>)}
</div>
</div>
</>
);
}

View file

@ -0,0 +1,24 @@
import { useState, useEffect } from '@wordpress/element';
export const useScriptParams = (requestConfig) => {
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
try {
const response = await fetch(requestConfig.endpoint);
const json = await response.json();
if (json.success && json?.data?.url_params) {
setData(json.data);
} else {
setData(false);
}
} catch (e) {
console.error(e);
setData(false);
}
})();
}, [requestConfig]);
return data;
};

View file

@ -0,0 +1,36 @@
import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import save from './save';
const paypalIcon = (
<svg width="584.798" height="720" viewBox="0 0 154.728 190.5">
<g transform="translate(898.192 276.071)">
<path clipPath="none" d="M-837.663-237.968a5.49 5.49 0 0 0-5.423 4.633l-9.013 57.15-8.281 52.514-.005.044.01-.044 8.281-52.514c.421-2.669 2.719-4.633 5.42-4.633h26.404c26.573 0 49.127-19.387 53.246-45.658.314-1.996.482-3.973.52-5.924v-.003h-.003c-6.753-3.543-14.683-5.565-23.372-5.565z" fill="#001c64"/>
<path clipPath="none" d="M-766.506-232.402c-.037 1.951-.207 3.93-.52 5.926-4.119 26.271-26.673 45.658-53.246 45.658h-26.404c-2.701 0-4.999 1.964-5.42 4.633l-8.281 52.514-5.197 32.947a4.46 4.46 0 0 0 4.405 5.153h28.66a5.49 5.49 0 0 0 5.423-4.633l7.55-47.881c.423-2.669 2.722-4.636 5.423-4.636h16.876c26.573 0 49.124-19.386 53.243-45.655 2.924-18.649-6.46-35.614-22.511-44.026z" fill="#0070e0"/>
<path clipPath="none" d="M-870.225-276.071a5.49 5.49 0 0 0-5.423 4.636l-22.489 142.608a4.46 4.46 0 0 0 4.405 5.156h33.351l8.281-52.514 9.013-57.15a5.49 5.49 0 0 1 5.423-4.633h47.782c8.691 0 16.621 2.025 23.375 5.563.46-23.917-19.275-43.666-46.412-43.666z" fill="#003087"/>
</g>
</svg>
)
const blockId = 'woocommerce-paypal-payments/paylater-messages';
registerBlockType( blockId, {
icon: paypalIcon,
edit: Edit,
save,
} );
document.addEventListener( 'DOMContentLoaded', () => {
const { registerCheckoutFilters } = window.wc.blocksCheckout;
// allow to add this block inside WC cart/checkout blocks
registerCheckoutFilters( blockId, {
additionalCartCheckoutInnerBlockTypes: (
defaultValue
) => {
defaultValue.push( blockId );
return defaultValue;
},
} );
} );

View file

@ -0,0 +1,26 @@
import { useBlockProps } from '@wordpress/block-editor';
export default function save( { attributes } ) {
const { layout, logo, position, color, size, flexColor, flexRatio, placement, id } = attributes;
const paypalAttributes = layout === 'flex' ? {
'data-pp-style-layout': 'flex',
'data-pp-style-color': flexColor,
'data-pp-style-ratio': flexRatio,
} : {
'data-pp-style-layout': 'text',
'data-pp-style-logo-type': logo,
'data-pp-style-logo-position': position,
'data-pp-style-text-color': color,
'data-pp-style-text-size': size,
};
if (placement && placement !== 'auto') {
paypalAttributes['data-pp-placement'] = placement;
}
const props = {
className: 'ppcp-paylater-message-block',
id,
...paypalAttributes,
};
return <div { ...useBlockProps.save(props) }></div>;
}

View file

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

View file

@ -0,0 +1,124 @@
<?php
/**
* The Pay Later block module.
*
* @package WooCommerce\PayPalCommerce\PayLaterBlock
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\PayLaterBlock;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
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\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class PayLaterBlockModule
*/
class PayLaterBlockModule implements ServiceModule, ExtendingModule, ExecutableModule {
use ModuleClassNameIdTrait;
/**
* Returns whether the block module should be loaded.
*/
public static function is_module_loading_required(): bool {
return apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.paylater_block_enabled',
getenv( 'PCP_PAYLATER_BLOCK' ) !== '0'
);
}
/**
* Returns whether the block is enabled.
*
* @param SettingsStatus $settings_status The Settings status helper.
* @return bool true if the block is enabled, otherwise false.
*/
public static function is_block_enabled( SettingsStatus $settings_status ): bool {
return self::is_module_loading_required() && $settings_status->is_pay_later_messaging_enabled_for_location( 'woocommerceBlock' );
}
/**
* {@inheritDoc}
*/
public function services(): array {
return require __DIR__ . '/../services.php';
}
/**
* {@inheritDoc}
*/
public function extensions(): array {
return require __DIR__ . '/../extensions.php';
}
/**
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): bool {
$messages_apply = $c->get( 'button.helper.messages-apply' );
assert( $messages_apply instanceof MessagesApply );
if ( ! $messages_apply->for_country() ) {
return true;
}
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
add_action(
'init',
function () use ( $c, $settings ): void {
$script_handle = 'ppcp-paylater-block';
wp_register_script(
$script_handle,
$c->get( 'paylater-block.url' ) . '/assets/js/paylater-block.js',
array(),
$c->get( 'ppcp.asset-version' ),
true
);
wp_localize_script(
$script_handle,
'PcpPayLaterBlock',
array(
'ajax' => array(
'cart_script_params' => array(
'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
),
),
'settingsUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' ),
'vaultingEnabled' => $settings->has( 'vault_enabled' ) && $settings->get( 'vault_enabled' ),
'placementEnabled' => self::is_block_enabled( $c->get( 'wcgateway.settings.status' ) ),
'payLaterSettingsUrl' => admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-pay-later' ),
)
);
/**
* Cannot return false for this path.
*
* @psalm-suppress PossiblyFalseArgument
*/
register_block_type( dirname( realpath( __FILE__ ), 2 ) );
},
20
);
return true;
}
/**
* Returns the key for the module.
*
* @return string|void
*/
public function getKey() {
}
}

View file

@ -0,0 +1,39 @@
const path = require('path');
const isProduction = process.env.NODE_ENV === 'production';
const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
module.exports = {
devtool: isProduction ? 'source-map' : 'eval-source-map',
mode: isProduction ? 'production' : 'development',
target: 'web',
plugins: [ new DependencyExtractionWebpackPlugin() ],
entry: {
'paylater-block': path.resolve('./resources/js/paylater-block.js'),
'edit': path.resolve('./resources/css/edit.scss'),
},
output: {
path: path.resolve(__dirname, 'assets/'),
filename: 'js/[name].js',
},
module: {
rules: [{
test: /\.js?$/,
exclude: /node_modules/,
loader: 'babel-loader',
},
{
test: /\.scss$/,
exclude: /node_modules/,
use: [
{
loader: 'file-loader',
options: {
name: 'css/[name].css',
}
},
{loader:'sass-loader'}
]
}]
}
};

File diff suppressed because it is too large Load diff