From 47ae8ff670e084ab51da6000fa4b7ac8ca3aeb9b Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 14 Jan 2025 17:59:35 +0100 Subject: [PATCH 1/9] Add data store boilerplace and default data --- .../ppcp-settings/resources/js/data/debug.js | 12 +- .../ppcp-settings/resources/js/data/index.js | 6 +- .../resources/js/data/payment/README.md | 45 +++++ .../resources/js/data/payment/action-types.js | 18 ++ .../resources/js/data/payment/actions.js | 71 +++++++ .../resources/js/data/payment/constants.js | 28 +++ .../resources/js/data/payment/controls.js | 23 +++ .../resources/js/data/payment/hooks.js | 65 +++++++ .../resources/js/data/payment/index.js | 24 +++ .../resources/js/data/payment/reducer.js | 56 ++++++ .../resources/js/data/payment/resolvers.js | 37 ++++ .../resources/js/data/payment/selectors.js | 21 ++ modules/ppcp-settings/services.php | 8 + .../src/Data/PaymentSettings.php | 182 +++++++++++++++++- .../src/Endpoint/PaymentRestEndpoint.php | 132 +++++++++++++ modules/ppcp-settings/src/SettingsModule.php | 1 + 16 files changed, 723 insertions(+), 6 deletions(-) create mode 100644 modules/ppcp-settings/resources/js/data/payment/README.md create mode 100644 modules/ppcp-settings/resources/js/data/payment/action-types.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/actions.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/constants.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/controls.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/hooks.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/index.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/reducer.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/resolvers.js create mode 100644 modules/ppcp-settings/resources/js/data/payment/selectors.js create mode 100644 modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php diff --git a/modules/ppcp-settings/resources/js/data/debug.js b/modules/ppcp-settings/resources/js/data/debug.js index 6380c6d6a..56fc31993 100644 --- a/modules/ppcp-settings/resources/js/data/debug.js +++ b/modules/ppcp-settings/resources/js/data/debug.js @@ -1,4 +1,8 @@ -import { OnboardingStoreName, CommonStoreName } from './index'; +import { + OnboardingStoreName, + CommonStoreName, + PaymentStoreName, +} from './index'; export const addDebugTools = ( context, modules ) => { if ( ! context || ! context?.debug ) { @@ -33,7 +37,11 @@ export const addDebugTools = ( context, modules ) => { }; context.resetStore = () => { - const stores = [ OnboardingStoreName, CommonStoreName ]; + const stores = [ + OnboardingStoreName, + CommonStoreName, + PaymentStoreName, + ]; stores.forEach( ( storeName ) => { const store = wp.data.dispatch( storeName ); diff --git a/modules/ppcp-settings/resources/js/data/index.js b/modules/ppcp-settings/resources/js/data/index.js index 274aac790..587ef036e 100644 --- a/modules/ppcp-settings/resources/js/data/index.js +++ b/modules/ppcp-settings/resources/js/data/index.js @@ -1,16 +1,20 @@ import { addDebugTools } from './debug'; import * as Onboarding from './onboarding'; import * as Common from './common'; +import * as Payment from './payment'; Onboarding.initStore(); Common.initStore(); +Payment.initStore(); export const OnboardingHooks = Onboarding.hooks; export const CommonHooks = Common.hooks; +export const PaymentHooks = Payment.hooks; export const OnboardingStoreName = Onboarding.STORE_NAME; export const CommonStoreName = Common.STORE_NAME; +export const PaymentStoreName = Payment.STORE_NAME; export * from './constants'; -addDebugTools( window.ppcpSettings, [ Onboarding, Common ] ); +addDebugTools( window.ppcpSettings, [ Onboarding, Common, Payment ] ); diff --git a/modules/ppcp-settings/resources/js/data/payment/README.md b/modules/ppcp-settings/resources/js/data/payment/README.md new file mode 100644 index 000000000..b97f6ca4c --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/README.md @@ -0,0 +1,45 @@ +# Store template + +This template contains all files for a Redux store. + +## New Store: Redux integration + +1. Copy this folder, give it a correct name. +2. Check each file for `` placeholders and `TODO` remarks. +3. Edit the main store-index file and add the relevant store integration there. +4. Check the debug-module, and add relevant debug code. + - Register the store in the `reset()` method. + +--- + +Main store-index: +`modules/ppcp-settings/resources/js/data/index.js` + +Sample store integration: +```js +import * as YourStore from './yourStore'; +// ... +YourStore.initStore(); +// ... +export const YourStoreHooks = YourStore.hooks; +// ... +export const YourStoreName = YourStore.STORE_NAME; +// ... +addDebugTools( window.ppcpSettings, [ ..., YourStoreName ] ); +``` + +--- + +### New Store: PHP integration + +1. Create the **REST endpoint** for hydrating and persisting data. + - `modules/ppcp-settings/src/Endpoint/YourStoreRestEndpoint.php` + - Extend from base class `RestEndpoint` +2. Create the **data model** class to manage the DB interaction. + - `modules/ppcp-settings/src/Data/YourStoreSettings.php` + - Extend from base class `AbstractDataModel` +3. Create relevant **DI services** for both files. + - `modules/ppcp-settings/services.php` +4. Register the REST endpoint in the **service module**. + - `modules/ppcp-settings/src/SettingsModule.php` + - Find the action `rest_api_init` diff --git a/modules/ppcp-settings/resources/js/data/payment/action-types.js b/modules/ppcp-settings/resources/js/data/payment/action-types.js new file mode 100644 index 000000000..e68253c5d --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/action-types.js @@ -0,0 +1,18 @@ +/** + * Action Types: Define unique identifiers for actions across all store modules. + * + * @file + */ + +export default { + // Transient data. + SET_TRANSIENT: 'PAYMENT:SET_TRANSIENT', + + // Persistent data. + SET_PERSISTENT: 'PAYMENT:SET_PERSISTENT', + RESET: 'PAYMENT:RESET', + HYDRATE: 'PAYMENT:HYDRATE', + + // Controls - always start with "DO_". + DO_PERSIST_DATA: 'PAYMENT:DO_PERSIST_DATA', +}; diff --git a/modules/ppcp-settings/resources/js/data/payment/actions.js b/modules/ppcp-settings/resources/js/data/payment/actions.js new file mode 100644 index 000000000..7360424f4 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/actions.js @@ -0,0 +1,71 @@ +/** + * Action Creators: Define functions to create action objects. + * + * These functions update state or trigger side effects (e.g., async operations). + * Actions are categorized as Transient, Persistent, or Side effect. + * + * @file + */ + +import { select } from '@wordpress/data'; + +import ACTION_TYPES from './action-types'; +import { STORE_NAME } from './constants'; + +/** + * @typedef {Object} Action An action object that is handled by a reducer or control. + * @property {string} type - The action type. + * @property {Object?} payload - Optional payload for the action. + */ + +/** + * Special. Resets all values in the store to initial defaults. + * + * @return {Action} The action. + */ +export const reset = () => ( { type: ACTION_TYPES.RESET } ); + +/** + * Persistent. Set the full store details during app initialization. + * + * @param {{data: {}, flags?: {}}} payload + * @return {Action} The action. + */ +export const hydrate = ( payload ) => ( { + type: ACTION_TYPES.HYDRATE, + payload, +} ); + +/** + * Transient. Marks the store as "ready", i.e., fully initialized. + * + * @param {boolean} isReady + * @return {Action} The action. + */ +export const setIsReady = ( isReady ) => ( { + type: ACTION_TYPES.SET_TRANSIENT, + payload: { isReady }, +} ); + +/** + * Persistent. Sets a sample value. + * TODO: Replace with a real action/property. + * + * @param {string} value + * @return {Action} The action. + */ +export const setSampleValue = ( value ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { sampleValue: value }, +} ); + +/** + * Side effect. Triggers the persistence of store data to the server. + * + * @return {Action} The action. + */ +export const persist = function* () { + const data = yield select( STORE_NAME ).persistentData(); + + yield { type: ACTION_TYPES.DO_PERSIST_DATA, data }; +}; diff --git a/modules/ppcp-settings/resources/js/data/payment/constants.js b/modules/ppcp-settings/resources/js/data/payment/constants.js new file mode 100644 index 000000000..82c428074 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/constants.js @@ -0,0 +1,28 @@ +/** + * Name of the Redux store module. + * + * Used by: Reducer, Selector, Index + * + * @type {string} + */ +export const STORE_NAME = 'wc/paypal/payment'; + +/** + * REST path to hydrate data of this module by loading data from the WP DB. + * + * Used by: Resolvers + * See: payment.php + * + * @type {string} + */ +export const REST_HYDRATE_PATH = '/wc/v3/wc_paypal/payment'; + +/** + * REST path to persist data of this module to the WP DB. + * + * Used by: Controls + * See: payment.php + * + * @type {string} + */ +export const REST_PERSIST_PATH = '/wc/v3/wc_paypal/payment'; diff --git a/modules/ppcp-settings/resources/js/data/payment/controls.js b/modules/ppcp-settings/resources/js/data/payment/controls.js new file mode 100644 index 000000000..9295b62bc --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/controls.js @@ -0,0 +1,23 @@ +/** + * Controls: Implement side effects, typically asynchronous operations. + * + * Controls use ACTION_TYPES keys as identifiers. + * They are triggered by corresponding actions and handle external interactions. + * + * @file + */ + +import apiFetch from '@wordpress/api-fetch'; + +import { REST_PERSIST_PATH } from './constants'; +import ACTION_TYPES from './action-types'; + +export const controls = { + async [ ACTION_TYPES.DO_PERSIST_DATA ]( { data } ) { + return await apiFetch( { + path: REST_PERSIST_PATH, + method: 'POST', + data, + } ); + }, +}; diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js new file mode 100644 index 000000000..394fceb7e --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -0,0 +1,65 @@ +/** + * Hooks: Provide the main API for components to interact with the store. + * + * These encapsulate store interactions, offering a consistent interface. + * Hooks simplify data access and manipulation for components. + * + * @file + */ + +import { useSelect, useDispatch } from '@wordpress/data'; + +import { STORE_NAME } from './constants'; + +const useTransient = ( key ) => + useSelect( + ( select ) => select( STORE_NAME ).transientData()?.[ key ], + [ key ] + ); + +const usePersistent = ( key ) => + useSelect( + ( select ) => select( STORE_NAME ).persistentData()?.[ key ], + [ key ] + ); + +const useHooks = () => { + const { + persist, + + // TODO: Replace with real property. + setSampleValue, + } = useDispatch( STORE_NAME ); + + // Read-only flags and derived state. + // Nothing here yet. + + // Transient accessors. + const isReady = useTransient( 'isReady' ); + + // Persistent accessors. + // TODO: Replace with real property. + const sampleValue = usePersistent( 'sampleValue' ); + + return { + persist, + isReady, + sampleValue, + setSampleValue, + }; +}; + +export const useState = () => { + const { persist, isReady } = useHooks(); + return { persist, isReady }; +}; + +// TODO: Replace with real hook. +export const useSampleValue = () => { + const { sampleValue, setSampleValue } = useHooks(); + + return { + sampleValue, + setSampleValue, + }; +}; diff --git a/modules/ppcp-settings/resources/js/data/payment/index.js b/modules/ppcp-settings/resources/js/data/payment/index.js new file mode 100644 index 000000000..28c162f98 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/index.js @@ -0,0 +1,24 @@ +import { createReduxStore, register } from '@wordpress/data'; +import { controls as wpControls } from '@wordpress/data-controls'; + +import { STORE_NAME } from './constants'; +import reducer from './reducer'; +import * as selectors from './selectors'; +import * as actions from './actions'; +import * as hooks from './hooks'; +import { resolvers } from './resolvers'; +import { controls } from './controls'; + +export const initStore = () => { + const store = createReduxStore( STORE_NAME, { + reducer, + controls: { ...wpControls, ...controls }, + actions, + selectors, + resolvers, + } ); + + register( store ); +}; + +export { hooks, selectors, STORE_NAME }; diff --git a/modules/ppcp-settings/resources/js/data/payment/reducer.js b/modules/ppcp-settings/resources/js/data/payment/reducer.js new file mode 100644 index 000000000..a858b719a --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/reducer.js @@ -0,0 +1,56 @@ +/** + * Reducer: Defines store structure and state updates for this module. + * + * Manages both transient (temporary) and persistent (saved) state. + * The initial state must define all properties, as dynamic additions are not supported. + * + * @file + */ + +import { createReducer, createSetters } from '../utils'; +import ACTION_TYPES from './action-types'; + +// Store structure. + +// Transient: Values that are _not_ saved to the DB (like app lifecycle-flags). +const defaultTransient = Object.freeze( { + isReady: false, +} ); + +// Persistent: Values that are loaded from the DB. +const defaultPersistent = Object.freeze( { + // TODO: Add real DB properties here. + sampleValue: 'foo', +} ); + +// Reducer logic. + +const [ setTransient, setPersistent ] = createSetters( + defaultTransient, + defaultPersistent +); + +const reducer = createReducer( defaultTransient, defaultPersistent, { + [ ACTION_TYPES.SET_TRANSIENT ]: ( state, payload ) => + setTransient( state, payload ), + + [ ACTION_TYPES.SET_PERSISTENT ]: ( state, payload ) => + setPersistent( state, payload ), + + [ ACTION_TYPES.RESET ]: ( state ) => { + const cleanState = setTransient( + setPersistent( state, defaultPersistent ), + defaultTransient + ); + + // Keep "read-only" details and initialization flags. + cleanState.isReady = true; + + return cleanState; + }, + + [ ACTION_TYPES.HYDRATE ]: ( state, payload ) => + setPersistent( state, payload.data ), +} ); + +export default reducer; diff --git a/modules/ppcp-settings/resources/js/data/payment/resolvers.js b/modules/ppcp-settings/resources/js/data/payment/resolvers.js new file mode 100644 index 000000000..321ddfee8 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/resolvers.js @@ -0,0 +1,37 @@ +/** + * Resolvers: Handle asynchronous data fetching for the store. + * + * These functions update store state with data from external sources. + * Each resolver corresponds to a specific selector (selector with same name must exist). + * Resolvers are called automatically when selectors request unavailable data. + * + * @file + */ + +import { dispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { apiFetch } from '@wordpress/data-controls'; + +import { STORE_NAME, REST_HYDRATE_PATH } from './constants'; + +export const resolvers = { + /** + * Retrieve settings from the site's REST API. + */ + *persistentData() { + try { + const result = yield apiFetch( { path: REST_HYDRATE_PATH } ); + + yield dispatch( STORE_NAME ).hydrate( result ); + yield dispatch( STORE_NAME ).setIsReady( true ); + } catch ( e ) { + yield dispatch( 'core/notices' ).createErrorNotice( + // TODO: Add the module name to the error message. + __( + 'Error retrieving payment details.', + 'woocommerce-paypal-payments' + ) + ); + } + }, +}; diff --git a/modules/ppcp-settings/resources/js/data/payment/selectors.js b/modules/ppcp-settings/resources/js/data/payment/selectors.js new file mode 100644 index 000000000..14334fcf3 --- /dev/null +++ b/modules/ppcp-settings/resources/js/data/payment/selectors.js @@ -0,0 +1,21 @@ +/** + * Selectors: Extract specific pieces of state from the store. + * + * These functions provide a consistent interface for accessing store data. + * They allow components to retrieve data without knowing the store structure. + * + * @file + */ + +const EMPTY_OBJ = Object.freeze( {} ); + +const getState = ( state ) => state || EMPTY_OBJ; + +export const persistentData = ( state ) => { + return getState( state ).data || EMPTY_OBJ; +}; + +export const transientData = ( state ) => { + const { data, ...transientState } = getState( state ); + return transientState || EMPTY_OBJ; +}; diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 6fc9d67e3..838ebe3d5 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -13,10 +13,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\Settings\Ajax\SwitchSettingsUiEndpoint; use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings; use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile; +use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings; use WooCommerce\PayPalCommerce\Settings\Endpoint\AuthenticationRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\LoginLinkRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\OnboardingRestEndpoint; +use WooCommerce\PayPalCommerce\Settings\Endpoint\PaymentRestEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\RefreshFeatureStatusEndpoint; use WooCommerce\PayPalCommerce\Settings\Endpoint\WebhookSettingsEndpoint; use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener; @@ -64,12 +66,18 @@ return array( $container->get( 'wcgateway.is-send-only-country' ) ); }, + 'settings.data.payment' => static function ( ContainerInterface $container ) : PaymentSettings { + return new PaymentSettings(); + }, 'settings.rest.onboarding' => static function ( ContainerInterface $container ) : OnboardingRestEndpoint { return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) ); }, 'settings.rest.common' => static function ( ContainerInterface $container ) : CommonRestEndpoint { return new CommonRestEndpoint( $container->get( 'settings.data.general' ) ); }, + 'settings.rest.payment' => static function ( ContainerInterface $container ) : PaymentRestEndpoint { + return new PaymentRestEndpoint( $container->get( 'settings.data.payment' ) ); + }, 'settings.rest.refresh_feature_status' => static function ( ContainerInterface $container ) : RefreshFeatureStatusEndpoint { return new RefreshFeatureStatusEndpoint( $container->get( 'wcgateway.settings' ), diff --git a/modules/ppcp-settings/src/Data/PaymentSettings.php b/modules/ppcp-settings/src/Data/PaymentSettings.php index 0180150a2..d603a082f 100644 --- a/modules/ppcp-settings/src/Data/PaymentSettings.php +++ b/modules/ppcp-settings/src/Data/PaymentSettings.php @@ -1,6 +1,6 @@ array( + array( + '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', + ), + array( + 'id' => 'venmo', + 'title' => __( 'Venmo', 'woocommerce-paypal-payments' ), + 'description' => __( + 'Offer Venmo at checkout to millions of active users.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-venmo', + ), + array( + 'id' => 'paypal_credit', + 'title' => __( 'Pay Later', 'woocommerce-paypal-payments' ), + 'description' => __( + 'Get paid in full at checkout while giving your customers the flexibility to pay in installments over time with no late fees.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-paypal', + ), + array( + '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', + ), + ), + 'paymentMethodsOnlineCardPayments' => array( + array( + '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-advanced-cards', + ), + array( + 'id' => 'fastlane', + 'title' => __( 'Fastlane by PayPal', 'woocommerce-paypal-payments' ), + 'description' => __( + "Tap into the scale and trust of PayPal's customer network to recognize shoppers and make guest checkout more seamless than ever.", + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-fastlane', + ), + array( + 'id' => 'apple_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', + ), + array( + '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', + ), + ), + 'paymentMethodsAlternative' => array( + array( + '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', + ), + array( + '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', + ), + array( + 'id' => 'eps', + 'title' => __( 'eps', 'woocommerce-paypal-payments' ), + 'description' => __( + 'An online payment method in Austria, enabling Austrian buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-eps', + ), + array( + 'id' => 'blik', + 'title' => __( 'BLIK', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A widely used mobile payment method in Poland, allowing Polish customers to pay directly via their banking apps. Transactions are processed in PLN.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-blik', + ), + array( + 'id' => 'mybank', + 'title' => __( 'MyBank', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A European online banking payment solution primarily used in Italy, enabling customers to make secure bank transfers during checkout. Transactions are processed in EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-mybank', + ), + array( + 'id' => 'przelewy24', + 'title' => __( 'Przelewy24', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A popular online payment gateway in Poland, offering various payment options for Polish customers. Transactions can be processed in PLN or EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-przelewy24', + ), + array( + 'id' => 'trustly', + 'title' => __( 'Trustly', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A European payment method that allows buyers to make payments directly from their bank accounts, suitable for customers across multiple European countries. Supported currencies include EUR, DKK, SEK, GBP, and NOK.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-trustly', + ), + array( + 'id' => 'multibanco', + 'title' => __( 'Multibanco', 'woocommerce-paypal-payments' ), + 'description' => __( + 'An online payment method in Portugal, enabling Portuguese buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-multibanco', + ), + array( + 'id' => 'pui', + 'title' => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), + 'description' => __( + 'Pay upon Invoice is an invoice payment method in Germany. It is a local buy now, pay later payment method that allows the buyer to place an order, receive the goods, try them, verify they are in good order, and then pay the invoice within 30 days.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-ratepay', + + ), + array( + 'id' => 'oxxo', + 'title' => __( 'OXXO', 'woocommerce-paypal-payments' ), + 'description' => __( + 'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 800–925–0304', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-oxxo', + ), + ), + ); } } diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php new file mode 100644 index 000000000..4df0b2607 --- /dev/null +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -0,0 +1,132 @@ + array( + 'js_name' => 'paymentMethodsPayPalCheckout', + ), + 'paymentMethodsOnlineCardPayments' => array( + 'js_name' => 'paymentMethodsOnlineCardPayments', + ), + 'paymentMethodsAlternative' => array( + 'js_name' => 'paymentMethodsAlternative', + ), + ); + + /** + * Constructor. + * + * @param PaymentSettings $settings The settings instance. + */ + public function __construct( PaymentSettings $settings ) { + $this->settings = $settings; + } + + /** + * Configure REST API routes. + */ + public function register_routes() : void { + /** + * GET wc/v3/wc_paypal/payment + */ + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_details' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + + /** + * POST wc/v3/wc_paypal/payment + * { + * // Fields mentioned in $field_map[]['js_name'] + * } + */ + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_details' ), + 'permission_callback' => array( $this, 'check_permission' ), + ) + ); + } + + /** + * Returns all payment methods details. + * + * @return WP_REST_Response The current payment methods details. + */ + public function get_details() : WP_REST_Response { + $js_data = $this->sanitize_for_javascript( + $this->settings->to_array(), + $this->field_map + ); + + return $this->return_success( + $js_data + ); + } + + /** + * Updates payment methods details based on the request. + * + * @param WP_REST_Request $request Full data about the request. + * + * @return WP_REST_Response The updated payment methods details. + */ + public function update_details( WP_REST_Request $request ) : WP_REST_Response { + $wp_data = $this->sanitize_for_wordpress( + $request->get_params(), + $this->field_map + ); + + $this->settings->from_array( $wp_data ); + $this->settings->save(); + + return $this->get_details(); + } +} diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 2d9356ab7..ce8146a21 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -207,6 +207,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { $endpoints = array( $container->get( 'settings.rest.onboarding' ), $container->get( 'settings.rest.common' ), + $container->get( 'settings.rest.payment' ), $container->get( 'settings.rest.connect_manual' ), $container->get( 'settings.rest.login_link' ), $container->get( 'settings.rest.webhooks' ), From a14a87b05c39a43b6d86eda98e6edff2f51f335e Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 15 Jan 2025 12:55:30 +0100 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=9A=A7=20Idea=20for=20REST=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Endpoint/PaymentRestEndpoint.php | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php index 4df0b2607..2786c6510 100644 --- a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -12,7 +12,8 @@ namespace WooCommerce\PayPalCommerce\Settings\Endpoint; use WP_REST_Server; use WP_REST_Response; use WP_REST_Request; -use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings; +use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\EPSGateway; /** * REST controller for the "Payment Methods" settings tab. @@ -28,37 +29,24 @@ class PaymentRestEndpoint extends RestEndpoint { */ protected $rest_base = 'payment'; - /** - * The settings instance. - * - * @var PaymentSettings - */ - protected PaymentSettings $settings; - /** * Field mapping for request to profile transformation. * * @var array */ - private array $field_map = array( - 'paymentMethodsPayPalCheckout' => array( - 'js_name' => 'paymentMethodsPayPalCheckout', - ), - 'paymentMethodsOnlineCardPayments' => array( - 'js_name' => 'paymentMethodsOnlineCardPayments', - ), - 'paymentMethodsAlternative' => array( - 'js_name' => 'paymentMethodsAlternative', - ), + private array $gateway_ids = array( + 'ppcp-gateway', + 'ppcp-credit-card-gateway', + ApplePayGateway::ID, + EPSGateway::ID, + // Todo: Add all payment methods. Maybe via a filter instead of hard-coding it? ); /** * Constructor. - * - * @param PaymentSettings $settings The settings instance. */ - public function __construct( PaymentSettings $settings ) { - $this->settings = $settings; + public function __construct() { + // Todo: Add DI instead of using `WC()->payment_gateways->payment_gateways()`? } /** @@ -81,7 +69,11 @@ class PaymentRestEndpoint extends RestEndpoint { /** * POST wc/v3/wc_paypal/payment * { - * // Fields mentioned in $field_map[]['js_name'] + * [gateway_id]: { + * enabled + * title + * description + * } * } */ register_rest_route( @@ -101,14 +93,28 @@ class PaymentRestEndpoint extends RestEndpoint { * @return WP_REST_Response The current payment methods details. */ public function get_details() : WP_REST_Response { - $js_data = $this->sanitize_for_javascript( - $this->settings->to_array(), - $this->field_map - ); + // Todo: Change this to DI? + $all_gateways = WC()->payment_gateways->payment_gateways(); - return $this->return_success( - $js_data - ); + $gateway_settings = array(); + + foreach ( $this->gateway_ids as $gateway_id ) { + if ( ! isset( $all_gateways[ $gateway_id ] ) ) { + continue; + } + + $gateway = $all_gateways[ $gateway_id ]; + + $gateway_settings[ $gateway_id ] = array( + 'enabled' => 'yes' === $gateway->enabled, + 'title' => $gateway->get_title(), + 'description' => $gateway->get_description(), + 'method_title' => $gateway->get_method_title(), + 'icon' => $gateway->get_icon(), + ); + } + + return $this->return_success( $gateway_settings ); } /** @@ -119,13 +125,32 @@ class PaymentRestEndpoint extends RestEndpoint { * @return WP_REST_Response The updated payment methods details. */ public function update_details( WP_REST_Request $request ) : WP_REST_Response { - $wp_data = $this->sanitize_for_wordpress( - $request->get_params(), - $this->field_map - ); + // Todo: Change this to DI? + $all_gateways = WC()->payment_gateways->payment_gateways(); - $this->settings->from_array( $wp_data ); - $this->settings->save(); + $request_data = $request->get_params(); + + foreach ( $this->gateway_ids as $gateway_id ) { + // Check if the REST body contains details for this gateway. + if ( ! isset( $request_data[ $gateway_id ] ) || ! isset( $all_gateways[ $gateway_id ] ) ) { + continue; + } + + $gateway = $all_gateways[ $gateway_id ]; + $new_data = $request_data[ $gateway_id ]; + + if ( isset( $new_data['enabled'] ) ) { + $gateway->update_option( 'enabled', $new_data['enabled'] ? 'yes' : 'no' ); + } + if ( isset( $new_data['title'] ) ) { + $gateway->update_option( 'title', sanitize_text_field( $new_data['title'] ) ); + } + if ( isset( $new_data['description'] ) ) { + $gateway->update_option( 'description', wp_kses_post( $new_data['description'] ) ); + } + + $gateway->process_admin_options(); + } return $this->get_details(); } From d51577723fe8377f25515e2556833b45557a8a68 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 15 Jan 2025 15:10:24 +0100 Subject: [PATCH 3/9] Add more payment gateways --- .../resources/js/data/payment/resolvers.js | 1 - .../src/Endpoint/PaymentRestEndpoint.php | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/payment/resolvers.js b/modules/ppcp-settings/resources/js/data/payment/resolvers.js index 321ddfee8..ebc6832bb 100644 --- a/modules/ppcp-settings/resources/js/data/payment/resolvers.js +++ b/modules/ppcp-settings/resources/js/data/payment/resolvers.js @@ -26,7 +26,6 @@ export const resolvers = { yield dispatch( STORE_NAME ).setIsReady( true ); } catch ( e ) { yield dispatch( 'core/notices' ).createErrorNotice( - // TODO: Add the module name to the error message. __( 'Error retrieving payment details.', 'woocommerce-paypal-payments' diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php index 2786c6510..d985f07c1 100644 --- a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -9,6 +9,17 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Settings\Endpoint; +use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BancontactGateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BlikGateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\IDealGateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\MultibancoGateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\MyBankGateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\P24Gateway; +use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\TrustlyGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WP_REST_Server; use WP_REST_Response; use WP_REST_Request; @@ -35,11 +46,21 @@ class PaymentRestEndpoint extends RestEndpoint { * @var array */ private array $gateway_ids = array( - 'ppcp-gateway', - 'ppcp-credit-card-gateway', + PayPalGateway::ID, + CardButtonGateway::ID, + + CreditCardGateway::ID, ApplePayGateway::ID, + GooglePayGateway::ID, + + BancontactGateway::ID, + BlikGateway::ID, EPSGateway::ID, - // Todo: Add all payment methods. Maybe via a filter instead of hard-coding it? + IDealGateway::ID, + MyBankGateway::ID, + P24Gateway::ID, + TrustlyGateway::ID, + MultibancoGateway::ID, ); /** From 5243ef10dbfcf1c41f78323b8e9acca86f5112c0 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 16 Jan 2025 14:48:04 +0100 Subject: [PATCH 4/9] Get payment methods default data from data store --- .../Screens/Overview/TabPaymentMethods.js | 231 ++---------------- .../resources/js/data/payment/hooks.js | 75 +++++- .../resources/js/data/payment/reducer.js | 18 +- .../src/Endpoint/PaymentRestEndpoint.php | 80 ++++-- 4 files changed, 164 insertions(+), 240 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabPaymentMethods.js b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabPaymentMethods.js index 63d627692..3216faf40 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabPaymentMethods.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabPaymentMethods.js @@ -1,44 +1,30 @@ import { __ } from '@wordpress/i18n'; -import { useMemo } from '@wordpress/element'; import SettingsCard from '../../ReusableComponents/SettingsCard'; import PaymentMethodsBlock from '../../ReusableComponents/SettingsBlocks/PaymentMethodsBlock'; -import { CommonHooks } from '../../../data'; +import { PaymentHooks } from '../../../data'; import { useActiveModal } from '../../../data/common/hooks'; import Modal from './TabSettingsElements/Blocks/Modal'; const TabPaymentMethods = () => { - const { storeCountry, storeCurrency } = CommonHooks.useWooSettings(); + const { paymentMethodsPayPalCheckout } = + PaymentHooks.usePaymentMethodsPayPalCheckout(); + const { paymentMethodsOnlineCardPayments } = + PaymentHooks.usePaymentMethodsOnlineCardPayments(); + const { paymentMethodsAlternative } = + PaymentHooks.usePaymentMethodsAlternative(); + const { activeModal, setActiveModal } = useActiveModal(); - const filteredPaymentMethods = useMemo( () => { - const contextProps = { storeCountry, storeCurrency }; - - return { - payPalCheckout: filterPaymentMethods( - paymentMethodsPayPalCheckout, - contextProps - ), - onlineCardPayments: filterPaymentMethods( - paymentMethodsOnlineCardPayments, - contextProps - ), - alternative: filterPaymentMethods( - paymentMethodsAlternative, - contextProps - ), - }; - }, [ storeCountry, storeCurrency ] ); - const getActiveMethod = () => { if ( ! activeModal ) { return null; } const allMethods = [ - ...filteredPaymentMethods.payPalCheckout, - ...filteredPaymentMethods.onlineCardPayments, - ...filteredPaymentMethods.alternative, + ...paymentMethodsPayPalCheckout, + ...paymentMethodsOnlineCardPayments, + ...paymentMethodsAlternative, ]; return allMethods.find( ( method ) => method.id === activeModal ); @@ -57,7 +43,7 @@ const TabPaymentMethods = () => { contentContainer={ false } > @@ -75,7 +61,7 @@ const TabPaymentMethods = () => { contentContainer={ false } > @@ -93,7 +79,7 @@ const TabPaymentMethods = () => { contentContainer={ false } > @@ -116,193 +102,4 @@ const TabPaymentMethods = () => { ); }; -function filterPaymentMethods( paymentMethods, contextProps ) { - return paymentMethods.filter( ( method ) => - typeof method.condition === 'function' - ? method.condition( contextProps ) - : true - ); -} - -const paymentMethodsPayPalCheckout = [ - { - 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', - }, - { - 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: __( 'Pay Later', 'woocommerce-paypal-payments' ), - description: __( - 'Get paid in full at checkout while giving your customers the flexibility to pay in installments over time with no late fees.', - '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 paymentMethodsOnlineCardPayments = [ - { - 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-advanced-cards', - }, - { - id: 'fastlane', - title: __( 'Fastlane by PayPal', 'woocommerce-paypal-payments' ), - description: __( - "Tap into the scale and trust of PayPal's customer network to recognize shoppers and make guest checkout more seamless than ever.", - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-fastlane', - }, - { - id: 'apple_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 paymentMethodsAlternative = [ - { - 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: __( - 'An online payment method in Austria, enabling Austrian buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-eps', - }, - { - id: 'blik', - title: __( 'BLIK', 'woocommerce-paypal-payments' ), - description: __( - 'A widely used mobile payment method in Poland, allowing Polish customers to pay directly via their banking apps. Transactions are processed in PLN.', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-blik', - }, - { - id: 'mybank', - title: __( 'MyBank', 'woocommerce-paypal-payments' ), - description: __( - 'A European online banking payment solution primarily used in Italy, enabling customers to make secure bank transfers during checkout. Transactions are processed in EUR.', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-mybank', - }, - { - id: 'przelewy24', - title: __( 'Przelewy24', 'woocommerce-paypal-payments' ), - description: __( - 'A popular online payment gateway in Poland, offering various payment options for Polish customers. Transactions can be processed in PLN or EUR.', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-przelewy24', - }, - { - id: 'trustly', - title: __( 'Trustly', 'woocommerce-paypal-payments' ), - description: __( - 'A European payment method that allows buyers to make payments directly from their bank accounts, suitable for customers across multiple European countries. Supported currencies include EUR, DKK, SEK, GBP, and NOK.', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-trustly', - }, - { - id: 'multibanco', - title: __( 'Multibanco', 'woocommerce-paypal-payments' ), - description: __( - 'An online payment method in Portugal, enabling Portuguese buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-multibanco', - }, - { - id: 'pui', - title: __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), - description: __( - 'Pay upon Invoice is an invoice payment method in Germany. It is a local buy now, pay later payment method that allows the buyer to place an order, receive the goods, try them, verify they are in good order, and then pay the invoice within 30 days.', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-ratepay', - condition: ( { storeCountry, storeCurrency } ) => - storeCountry === 'DE' && storeCurrency === 'EUR', - }, - { - id: 'oxxo', - title: __( 'OXXO', 'woocommerce-paypal-payments' ), - description: __( - 'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 800–925–0304', - 'woocommerce-paypal-payments' - ), - icon: 'payment-method-oxxo', - condition: ( { storeCountry, storeCurrency } ) => - storeCountry === 'MX' && storeCurrency === 'MXN', - }, -]; - export default TabPaymentMethods; diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js index 394fceb7e..fbae1e603 100644 --- a/modules/ppcp-settings/resources/js/data/payment/hooks.js +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -38,14 +38,37 @@ const useHooks = () => { const isReady = useTransient( 'isReady' ); // Persistent accessors. - // TODO: Replace with real property. const sampleValue = usePersistent( 'sampleValue' ); + const paypal = usePersistent( 'ppcp-gateway' ); + const advancedCreditCard = usePersistent( 'ppcp-credit-card-gateway' ); + const bancontact = usePersistent( 'ppcp-bancontact' ); + const blik = usePersistent( 'ppcp-blik' ); + const eps = usePersistent( 'ppcp-eps' ); + const ideal = usePersistent( 'ppcp-ideal' ); + const mybank = usePersistent( 'ppcp-mybank' ); + const p24 = usePersistent( 'ppcp-p24' ); + const trustly = usePersistent( 'ppcp-trustly' ); + const multibanco = usePersistent( 'ppcp-multibanco' ); + const pui = usePersistent( 'ppcp-pay-upon-invoice-gateway' ); + const oxxo = usePersistent( 'ppcp-oxxo-gateway' ); return { persist, isReady, sampleValue, setSampleValue, + paypal, + advancedCreditCard, + bancontact, + blik, + eps, + ideal, + mybank, + p24, + trustly, + multibanco, + pui, + oxxo, }; }; @@ -63,3 +86,53 @@ export const useSampleValue = () => { setSampleValue, }; }; + +export const usePaymentMethodsPayPalCheckout = () => { + const { paypal } = useHooks(); + const paymentMethodsPayPalCheckout = [ paypal ]; + + return { + paymentMethodsPayPalCheckout, + }; +}; + +export const usePaymentMethodsOnlineCardPayments = () => { + const { advancedCreditCard } = useHooks(); + const paymentMethodsOnlineCardPayments = [ advancedCreditCard ]; + + return { + paymentMethodsOnlineCardPayments, + }; +}; + +export const usePaymentMethodsAlternative = () => { + const { + bancontact, + blik, + eps, + ideal, + mybank, + p24, + trustly, + multibanco, + pui, + oxxo, + } = useHooks(); + + const paymentMethodsAlternative = [ + bancontact, + blik, + eps, + ideal, + mybank, + p24, + trustly, + multibanco, + pui, + oxxo, + ]; + + return { + paymentMethodsAlternative, + }; +}; diff --git a/modules/ppcp-settings/resources/js/data/payment/reducer.js b/modules/ppcp-settings/resources/js/data/payment/reducer.js index a858b719a..0ce37c18c 100644 --- a/modules/ppcp-settings/resources/js/data/payment/reducer.js +++ b/modules/ppcp-settings/resources/js/data/payment/reducer.js @@ -19,8 +19,22 @@ const defaultTransient = Object.freeze( { // Persistent: Values that are loaded from the DB. const defaultPersistent = Object.freeze( { - // TODO: Add real DB properties here. - sampleValue: 'foo', + 'ppcp-gateway': {}, + 'ppcp-card-button-gateway': {}, + 'ppcp-credit-card-gateway': {}, + 'ppcp-axo-gateway': {}, + 'ppcp-applepay': {}, + 'ppcp-googlepay': {}, + 'ppcp-bancontact': {}, + 'ppcp-blik': {}, + 'ppcp-eps': {}, + 'ppcp-ideal': {}, + 'ppcp-mybank': {}, + 'ppcp-p24': {}, + 'ppcp-trustly': {}, + 'ppcp-multibanco': {}, + 'ppcp-pay-upon-invoice-gateway': {}, + 'ppcp-oxxo-gateway': {}, } ); // Reducer logic. diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php index d985f07c1..52f083228 100644 --- a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -9,6 +9,7 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Settings\Endpoint; +use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BancontactGateway; use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BlikGateway; @@ -19,7 +20,9 @@ use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\P24Gateway; use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\TrustlyGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WP_REST_Server; use WP_REST_Response; use WP_REST_Request; @@ -45,22 +48,58 @@ class PaymentRestEndpoint extends RestEndpoint { * * @var array */ - private array $gateway_ids = array( - PayPalGateway::ID, - CardButtonGateway::ID, + private array $gateways = array( + PayPalGateway::ID => array( + 'id' => 'paypal', + 'icon' => 'payment-method-paypal', + ), + CardButtonGateway::ID => array( + 'icon' => 'payment-method-cards', + ), - CreditCardGateway::ID, - ApplePayGateway::ID, - GooglePayGateway::ID, + CreditCardGateway::ID => array( + 'icon' => 'payment-method-advanced-cards', + ), + AxoGateway::ID => array( + 'icon' => 'payment-method-fastlane', + ), + ApplePayGateway::ID => array( + 'icon' => 'payment-method-apple-pay', + ), + GooglePayGateway::ID => array( + 'icon' => 'payment-method-google-pay', + ), - BancontactGateway::ID, - BlikGateway::ID, - EPSGateway::ID, - IDealGateway::ID, - MyBankGateway::ID, - P24Gateway::ID, - TrustlyGateway::ID, - MultibancoGateway::ID, + BancontactGateway::ID => array( + 'icon' => 'payment-method-bancontact', + ), + BlikGateway::ID => array( + 'icon' => 'payment-method-blik', + ), + EPSGateway::ID => array( + 'icon' => 'payment-method-eps', + ), + IDealGateway::ID => array( + 'icon' => 'payment-method-ideal', + ), + MyBankGateway::ID => array( + 'icon' => 'payment-method-mybank', + ), + P24Gateway::ID => array( + 'icon' => 'payment-method-przelewy24', + ), + TrustlyGateway::ID => array( + 'icon' => 'payment-method-trustly', + ), + MultibancoGateway::ID => array( + 'icon' => 'payment-method-multibanco', + ), + PayUponInvoiceGateway::ID => array( + 'icon' => 'payment-method-multibanco', + ), + OXXO::ID => array( + 'icon' => 'payment-method-multibanco', + ), ); /** @@ -119,19 +158,20 @@ class PaymentRestEndpoint extends RestEndpoint { $gateway_settings = array(); - foreach ( $this->gateway_ids as $gateway_id ) { - if ( ! isset( $all_gateways[ $gateway_id ] ) ) { + foreach ( $this->gateways as $key => $value ) { + if ( ! isset( $all_gateways[ $key ] ) ) { continue; } - $gateway = $all_gateways[ $gateway_id ]; + $gateway = $all_gateways[ $key ]; - $gateway_settings[ $gateway_id ] = array( + $gateway_settings[ $key ] = array( 'enabled' => 'yes' === $gateway->enabled, 'title' => $gateway->get_title(), 'description' => $gateway->get_description(), 'method_title' => $gateway->get_method_title(), - 'icon' => $gateway->get_icon(), + 'id' => $this->gateways[ $key ]['id'] ?? $key, + 'icon' => $this->gateways[ $key ]['icon'] ?? '', ); } @@ -151,7 +191,7 @@ class PaymentRestEndpoint extends RestEndpoint { $request_data = $request->get_params(); - foreach ( $this->gateway_ids as $gateway_id ) { + foreach ( $this->gateways as $gateway_id ) { // Check if the REST body contains details for this gateway. if ( ! isset( $request_data[ $gateway_id ] ) || ! isset( $all_gateways[ $gateway_id ] ) ) { continue; From afad57da5dc659e71b90d8d1a19b29687cb2e818 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 16 Jan 2025 15:48:40 +0100 Subject: [PATCH 5/9] Add non payment gateway items --- .../resources/js/data/payment/hooks.js | 36 ++- .../resources/js/data/payment/reducer.js | 2 + .../src/Endpoint/PaymentRestEndpoint.php | 252 +++++++++++++----- 3 files changed, 222 insertions(+), 68 deletions(-) diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js index fbae1e603..54b6d9863 100644 --- a/modules/ppcp-settings/resources/js/data/payment/hooks.js +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -39,8 +39,20 @@ const useHooks = () => { // Persistent accessors. const sampleValue = usePersistent( 'sampleValue' ); + + // PayPal checkout. const paypal = usePersistent( 'ppcp-gateway' ); + const venmo = usePersistent( 'venmo' ); + const payLater = usePersistent( 'pay-later' ); + const creditCard = usePersistent( 'ppcp-card-button-gateway' ); + + // Online card Payments. const advancedCreditCard = usePersistent( 'ppcp-credit-card-gateway' ); + const fastlane = usePersistent( 'ppcp-axo-gateway' ); + const applePay = usePersistent( 'ppcp-applepay' ); + const googlePay = usePersistent( 'ppcp-googlepay' ); + + // Alternative payment methods. const bancontact = usePersistent( 'ppcp-bancontact' ); const blik = usePersistent( 'ppcp-blik' ); const eps = usePersistent( 'ppcp-eps' ); @@ -58,7 +70,13 @@ const useHooks = () => { sampleValue, setSampleValue, paypal, + venmo, + payLater, + creditCard, advancedCreditCard, + fastlane, + applePay, + googlePay, bancontact, blik, eps, @@ -88,8 +106,13 @@ export const useSampleValue = () => { }; export const usePaymentMethodsPayPalCheckout = () => { - const { paypal } = useHooks(); - const paymentMethodsPayPalCheckout = [ paypal ]; + const { paypal, venmo, payLater, creditCard } = useHooks(); + const paymentMethodsPayPalCheckout = [ + paypal, + venmo, + payLater, + creditCard, + ]; return { paymentMethodsPayPalCheckout, @@ -97,8 +120,13 @@ export const usePaymentMethodsPayPalCheckout = () => { }; export const usePaymentMethodsOnlineCardPayments = () => { - const { advancedCreditCard } = useHooks(); - const paymentMethodsOnlineCardPayments = [ advancedCreditCard ]; + const { advancedCreditCard, fastlane, applePay, googlePay } = useHooks(); + const paymentMethodsOnlineCardPayments = [ + advancedCreditCard, + fastlane, + applePay, + googlePay, + ]; return { paymentMethodsOnlineCardPayments, diff --git a/modules/ppcp-settings/resources/js/data/payment/reducer.js b/modules/ppcp-settings/resources/js/data/payment/reducer.js index 0ce37c18c..4894c9996 100644 --- a/modules/ppcp-settings/resources/js/data/payment/reducer.js +++ b/modules/ppcp-settings/resources/js/data/payment/reducer.js @@ -20,6 +20,8 @@ const defaultTransient = Object.freeze( { // Persistent: Values that are loaded from the DB. const defaultPersistent = Object.freeze( { 'ppcp-gateway': {}, + venmo: {}, + 'pay-later': {}, 'ppcp-card-button-gateway': {}, 'ppcp-credit-card-gateway': {}, 'ppcp-axo-gateway': {}, diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php index 52f083228..19c5b7d08 100644 --- a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -46,67 +46,184 @@ class PaymentRestEndpoint extends RestEndpoint { /** * Field mapping for request to profile transformation. * - * @var array + * @return array[] */ - private array $gateways = array( - PayPalGateway::ID => array( - 'id' => 'paypal', - 'icon' => 'payment-method-paypal', - ), - CardButtonGateway::ID => array( - 'icon' => 'payment-method-cards', - ), + protected function gateways():array { + return array( + // PayPal checkout. + PayPalGateway::ID => array( + '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', + ), + 'venmo' => array( + 'id' => 'venmo', + 'title' => __( 'Venmo', 'woocommerce-paypal-payments' ), + 'description' => __( + 'Offer Venmo at checkout to millions of active users.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-venmo', + ), + 'pay-later' => array( + 'id' => 'paypal_credit', + 'title' => __( 'Pay Later', 'woocommerce-paypal-payments' ), + 'description' => __( + 'Get paid in full at checkout while giving your customers the flexibility to pay in installments over time with no late fees.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-paypal', + ), + CardButtonGateway::ID => array( + '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', + ), - CreditCardGateway::ID => array( - 'icon' => 'payment-method-advanced-cards', - ), - AxoGateway::ID => array( - 'icon' => 'payment-method-fastlane', - ), - ApplePayGateway::ID => array( - 'icon' => 'payment-method-apple-pay', - ), - GooglePayGateway::ID => array( - 'icon' => 'payment-method-google-pay', - ), + // Online card Payments. + CreditCardGateway::ID => array( + '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-advanced-cards', + ), + AxoGateway::ID => array( + 'id' => 'fastlane', + 'title' => __( 'Fastlane by PayPal', 'woocommerce-paypal-payments' ), + 'description' => __( + "Tap into the scale and trust of PayPal's customer network to recognize shoppers and make guest checkout more seamless than ever.", + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-fastlane', + ), + ApplePayGateway::ID => array( + 'id' => 'apple_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', + ), + GooglePayGateway::ID => array( + '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', + ), - BancontactGateway::ID => array( - 'icon' => 'payment-method-bancontact', - ), - BlikGateway::ID => array( - 'icon' => 'payment-method-blik', - ), - EPSGateway::ID => array( - 'icon' => 'payment-method-eps', - ), - IDealGateway::ID => array( - 'icon' => 'payment-method-ideal', - ), - MyBankGateway::ID => array( - 'icon' => 'payment-method-mybank', - ), - P24Gateway::ID => array( - 'icon' => 'payment-method-przelewy24', - ), - TrustlyGateway::ID => array( - 'icon' => 'payment-method-trustly', - ), - MultibancoGateway::ID => array( - 'icon' => 'payment-method-multibanco', - ), - PayUponInvoiceGateway::ID => array( - 'icon' => 'payment-method-multibanco', - ), - OXXO::ID => array( - 'icon' => 'payment-method-multibanco', - ), - ); - - /** - * Constructor. - */ - public function __construct() { - // Todo: Add DI instead of using `WC()->payment_gateways->payment_gateways()`? + // Alternative payment methods. + BancontactGateway::ID => array( + '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', + ), + BlikGateway::ID => array( + 'id' => 'blik', + 'title' => __( 'BLIK', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A widely used mobile payment method in Poland, allowing Polish customers to pay directly via their banking apps. Transactions are processed in PLN.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-blik', + ), + EPSGateway::ID => array( + 'id' => 'eps', + 'title' => __( 'eps', 'woocommerce-paypal-payments' ), + 'description' => __( + 'An online payment method in Austria, enabling Austrian buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-eps', + ), + IDealGateway::ID => array( + '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', + ), + MyBankGateway::ID => array( + 'id' => 'mybank', + 'title' => __( 'MyBank', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A European online banking payment solution primarily used in Italy, enabling customers to make secure bank transfers during checkout. Transactions are processed in EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-mybank', + ), + P24Gateway::ID => array( + 'id' => 'przelewy24', + 'title' => __( 'Przelewy24', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A popular online payment gateway in Poland, offering various payment options for Polish customers. Transactions can be processed in PLN or EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-przelewy24', + ), + TrustlyGateway::ID => array( + 'id' => 'trustly', + 'title' => __( 'Trustly', 'woocommerce-paypal-payments' ), + 'description' => __( + 'A European payment method that allows buyers to make payments directly from their bank accounts, suitable for customers across multiple European countries. Supported currencies include EUR, DKK, SEK, GBP, and NOK.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-trustly', + ), + MultibancoGateway::ID => array( + 'id' => 'multibanco', + 'title' => __( 'Multibanco', 'woocommerce-paypal-payments' ), + 'description' => __( + 'An online payment method in Portugal, enabling Portuguese buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', + 'woocommerce-paypal-payments' + ), + 'icon' => 'payment-method-multibanco', + ), + PayUponInvoiceGateway::ID => array( + 'id' => 'pui', + 'title' => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), + 'description' => __( + 'Pay upon Invoice is an invoice payment method in Germany. It is a local buy now, pay later payment method that allows the buyer to place an order, receive the goods, try them, verify they are in good order, and then pay the invoice within 30 days.', + 'woocommerce-paypal-payments' + ), + 'icon' => '', + ), + OXXO::ID => array( + 'id' => 'oxxo', + 'title' => __( 'OXXO', 'woocommerce-paypal-payments' ), + 'description' => __( + 'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 800–925–0304', + 'woocommerce-paypal-payments' + ), + 'icon' => '', + ), + ); } /** @@ -153,13 +270,20 @@ class PaymentRestEndpoint extends RestEndpoint { * @return WP_REST_Response The current payment methods details. */ public function get_details() : WP_REST_Response { - // Todo: Change this to DI? $all_gateways = WC()->payment_gateways->payment_gateways(); $gateway_settings = array(); - foreach ( $this->gateways as $key => $value ) { + foreach ( $this->gateways() as $key => $value ) { if ( ! isset( $all_gateways[ $key ] ) ) { + $gateway_settings[ $key ] = array( + 'id' => $this->gateways()[ $key ]['id'] ?? '', + 'title' => $this->gateways()[ $key ]['title'] ?? '', + 'description' => $this->gateways()[ $key ]['description'] ?? '', + 'enabled' => false, + 'icon' => $this->gateways()[ $key ]['icon'] ?? '', + ); + continue; } @@ -167,11 +291,11 @@ class PaymentRestEndpoint extends RestEndpoint { $gateway_settings[ $key ] = array( 'enabled' => 'yes' === $gateway->enabled, - 'title' => $gateway->get_title(), - 'description' => $gateway->get_description(), + 'title' => $this->gateways()[ $key ]['title'] ?? $gateway->get_title(), + 'description' => $this->gateways()[ $key ]['description'] ?? $gateway->get_description(), 'method_title' => $gateway->get_method_title(), - 'id' => $this->gateways[ $key ]['id'] ?? $key, - 'icon' => $this->gateways[ $key ]['icon'] ?? '', + 'id' => $this->gateways()[ $key ]['id'] ?? $key, + 'icon' => $this->gateways()[ $key ]['icon'] ?? '', ); } From 58777d6f9137026b0a305c39cfdf24ce478ae7dd Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 16 Jan 2025 16:11:15 +0100 Subject: [PATCH 6/9] Fix update details endpoint --- .../ppcp-settings/src/Endpoint/PaymentRestEndpoint.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php index 19c5b7d08..ea692242a 100644 --- a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -310,19 +310,18 @@ class PaymentRestEndpoint extends RestEndpoint { * @return WP_REST_Response The updated payment methods details. */ public function update_details( WP_REST_Request $request ) : WP_REST_Response { - // Todo: Change this to DI? $all_gateways = WC()->payment_gateways->payment_gateways(); $request_data = $request->get_params(); - foreach ( $this->gateways as $gateway_id ) { + foreach ( $this->gateways() as $key => $value) { // Check if the REST body contains details for this gateway. - if ( ! isset( $request_data[ $gateway_id ] ) || ! isset( $all_gateways[ $gateway_id ] ) ) { + if ( ! isset( $request_data[ $key ] ) || ! isset( $all_gateways[ $key ] ) ) { continue; } - $gateway = $all_gateways[ $gateway_id ]; - $new_data = $request_data[ $gateway_id ]; + $gateway = $all_gateways[ $key ]; + $new_data = $request_data[ $key ]; if ( isset( $new_data['enabled'] ) ) { $gateway->update_option( 'enabled', $new_data['enabled'] ? 'yes' : 'no' ); From 2bea225d9b831a07f4748298583526d25a7bd8f5 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 20 Jan 2025 16:04:35 +0100 Subject: [PATCH 7/9] Connect enable payment method to data store --- .../SettingsBlocks/PaymentMethodsBlock.js | 24 +- .../resources/js/data/payment/actions.js | 24 +- .../resources/js/data/payment/hooks.js | 21 +- modules/ppcp-settings/services.php | 5 +- .../src/Data/PaymentSettings.php | 210 ------------------ .../src/Endpoint/PaymentRestEndpoint.php | 2 +- 6 files changed, 30 insertions(+), 256 deletions(-) delete mode 100644 modules/ppcp-settings/src/Data/PaymentSettings.php diff --git a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/PaymentMethodsBlock.js b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/PaymentMethodsBlock.js index 17f610660..1e1e9ee30 100644 --- a/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/PaymentMethodsBlock.js +++ b/modules/ppcp-settings/resources/js/Components/ReusableComponents/SettingsBlocks/PaymentMethodsBlock.js @@ -1,25 +1,25 @@ -import { useState, useCallback } from '@wordpress/element'; import SettingsBlock from './SettingsBlock'; import PaymentMethodItemBlock from './PaymentMethodItemBlock'; +import { usePaymentMethods } from '../../../data/payment/hooks'; const PaymentMethodsBlock = ( { paymentMethods, className = '', onTriggerModal, } ) => { - const [ selectedMethods, setSelectedMethods ] = useState( {} ); - - const handleSelect = useCallback( ( methodId, isSelected ) => { - setSelectedMethods( ( prev ) => ( { - ...prev, - [ methodId ]: isSelected, - } ) ); - }, [] ); + const { setPersistent } = usePaymentMethods(); if ( ! paymentMethods?.length ) { return null; } + const handleSelect = ( paymentMethod, isSelected ) => { + setPersistent( paymentMethod.id, { + ...paymentMethod, + enabled: isSelected, + } ); + }; + return ( - handleSelect( paymentMethod.id, checked ) + handleSelect( paymentMethod, checked ) } onTriggerModal={ () => onTriggerModal?.( paymentMethod.id ) diff --git a/modules/ppcp-settings/resources/js/data/payment/actions.js b/modules/ppcp-settings/resources/js/data/payment/actions.js index 7360424f4..95c0235b1 100644 --- a/modules/ppcp-settings/resources/js/data/payment/actions.js +++ b/modules/ppcp-settings/resources/js/data/payment/actions.js @@ -47,18 +47,6 @@ export const setIsReady = ( isReady ) => ( { payload: { isReady }, } ); -/** - * Persistent. Sets a sample value. - * TODO: Replace with a real action/property. - * - * @param {string} value - * @return {Action} The action. - */ -export const setSampleValue = ( value ) => ( { - type: ACTION_TYPES.SET_PERSISTENT, - payload: { sampleValue: value }, -} ); - /** * Side effect. Triggers the persistence of store data to the server. * @@ -69,3 +57,15 @@ export const persist = function* () { yield { type: ACTION_TYPES.DO_PERSIST_DATA, data }; }; + +/** + * Generic persistent-data updater. + * + * @param {string} prop Name of the property to update. + * @param {any} value The new value of the property. + * @return {Action} The action. + */ +export const setPersistent = ( prop, value ) => ( { + type: ACTION_TYPES.SET_PERSISTENT, + payload: { [ prop ]: value }, +} ); diff --git a/modules/ppcp-settings/resources/js/data/payment/hooks.js b/modules/ppcp-settings/resources/js/data/payment/hooks.js index 54b6d9863..c10f0969e 100644 --- a/modules/ppcp-settings/resources/js/data/payment/hooks.js +++ b/modules/ppcp-settings/resources/js/data/payment/hooks.js @@ -24,12 +24,7 @@ const usePersistent = ( key ) => ); const useHooks = () => { - const { - persist, - - // TODO: Replace with real property. - setSampleValue, - } = useDispatch( STORE_NAME ); + const { persist, setPersistent } = useDispatch( STORE_NAME ); // Read-only flags and derived state. // Nothing here yet. @@ -37,9 +32,6 @@ const useHooks = () => { // Transient accessors. const isReady = useTransient( 'isReady' ); - // Persistent accessors. - const sampleValue = usePersistent( 'sampleValue' ); - // PayPal checkout. const paypal = usePersistent( 'ppcp-gateway' ); const venmo = usePersistent( 'venmo' ); @@ -67,8 +59,7 @@ const useHooks = () => { return { persist, isReady, - sampleValue, - setSampleValue, + setPersistent, paypal, venmo, payLater, @@ -95,13 +86,11 @@ export const useState = () => { return { persist, isReady }; }; -// TODO: Replace with real hook. -export const useSampleValue = () => { - const { sampleValue, setSampleValue } = useHooks(); +export const usePaymentMethods = () => { + const { setPersistent } = useHooks(); return { - sampleValue, - setSampleValue, + setPersistent, }; }; diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 838ebe3d5..e66a919d5 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -66,9 +66,6 @@ return array( $container->get( 'wcgateway.is-send-only-country' ) ); }, - 'settings.data.payment' => static function ( ContainerInterface $container ) : PaymentSettings { - return new PaymentSettings(); - }, 'settings.rest.onboarding' => static function ( ContainerInterface $container ) : OnboardingRestEndpoint { return new OnboardingRestEndpoint( $container->get( 'settings.data.onboarding' ) ); }, @@ -76,7 +73,7 @@ return array( return new CommonRestEndpoint( $container->get( 'settings.data.general' ) ); }, 'settings.rest.payment' => static function ( ContainerInterface $container ) : PaymentRestEndpoint { - return new PaymentRestEndpoint( $container->get( 'settings.data.payment' ) ); + return new PaymentRestEndpoint(); }, 'settings.rest.refresh_feature_status' => static function ( ContainerInterface $container ) : RefreshFeatureStatusEndpoint { return new RefreshFeatureStatusEndpoint( diff --git a/modules/ppcp-settings/src/Data/PaymentSettings.php b/modules/ppcp-settings/src/Data/PaymentSettings.php deleted file mode 100644 index d603a082f..000000000 --- a/modules/ppcp-settings/src/Data/PaymentSettings.php +++ /dev/null @@ -1,210 +0,0 @@ - array( - array( - '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', - ), - array( - 'id' => 'venmo', - 'title' => __( 'Venmo', 'woocommerce-paypal-payments' ), - 'description' => __( - 'Offer Venmo at checkout to millions of active users.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-venmo', - ), - array( - 'id' => 'paypal_credit', - 'title' => __( 'Pay Later', 'woocommerce-paypal-payments' ), - 'description' => __( - 'Get paid in full at checkout while giving your customers the flexibility to pay in installments over time with no late fees.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-paypal', - ), - array( - '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', - ), - ), - 'paymentMethodsOnlineCardPayments' => array( - array( - '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-advanced-cards', - ), - array( - 'id' => 'fastlane', - 'title' => __( 'Fastlane by PayPal', 'woocommerce-paypal-payments' ), - 'description' => __( - "Tap into the scale and trust of PayPal's customer network to recognize shoppers and make guest checkout more seamless than ever.", - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-fastlane', - ), - array( - 'id' => 'apple_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', - ), - array( - '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', - ), - ), - 'paymentMethodsAlternative' => array( - array( - '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', - ), - array( - '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', - ), - array( - 'id' => 'eps', - 'title' => __( 'eps', 'woocommerce-paypal-payments' ), - 'description' => __( - 'An online payment method in Austria, enabling Austrian buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-eps', - ), - array( - 'id' => 'blik', - 'title' => __( 'BLIK', 'woocommerce-paypal-payments' ), - 'description' => __( - 'A widely used mobile payment method in Poland, allowing Polish customers to pay directly via their banking apps. Transactions are processed in PLN.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-blik', - ), - array( - 'id' => 'mybank', - 'title' => __( 'MyBank', 'woocommerce-paypal-payments' ), - 'description' => __( - 'A European online banking payment solution primarily used in Italy, enabling customers to make secure bank transfers during checkout. Transactions are processed in EUR.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-mybank', - ), - array( - 'id' => 'przelewy24', - 'title' => __( 'Przelewy24', 'woocommerce-paypal-payments' ), - 'description' => __( - 'A popular online payment gateway in Poland, offering various payment options for Polish customers. Transactions can be processed in PLN or EUR.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-przelewy24', - ), - array( - 'id' => 'trustly', - 'title' => __( 'Trustly', 'woocommerce-paypal-payments' ), - 'description' => __( - 'A European payment method that allows buyers to make payments directly from their bank accounts, suitable for customers across multiple European countries. Supported currencies include EUR, DKK, SEK, GBP, and NOK.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-trustly', - ), - array( - 'id' => 'multibanco', - 'title' => __( 'Multibanco', 'woocommerce-paypal-payments' ), - 'description' => __( - 'An online payment method in Portugal, enabling Portuguese buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-multibanco', - ), - array( - 'id' => 'pui', - 'title' => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), - 'description' => __( - 'Pay upon Invoice is an invoice payment method in Germany. It is a local buy now, pay later payment method that allows the buyer to place an order, receive the goods, try them, verify they are in good order, and then pay the invoice within 30 days.', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-ratepay', - - ), - array( - 'id' => 'oxxo', - 'title' => __( 'OXXO', 'woocommerce-paypal-payments' ), - 'description' => __( - 'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 800–925–0304', - 'woocommerce-paypal-payments' - ), - 'icon' => 'payment-method-oxxo', - ), - ), - ); - } -} diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php index ea692242a..7e5ea161e 100644 --- a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -52,7 +52,7 @@ class PaymentRestEndpoint extends RestEndpoint { return array( // PayPal checkout. PayPalGateway::ID => array( - 'id' => 'paypal', + 'id' => 'ppcp-gateway', '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.', From 7af348772daec710c59a7c348ded154d541479be Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 21 Jan 2025 10:40:08 +0100 Subject: [PATCH 8/9] Remove settings cogwheels from venmo and pay later --- .../Overview/TabSettingsElements/Blocks/PaymentMethods.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettingsElements/Blocks/PaymentMethods.js b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettingsElements/Blocks/PaymentMethods.js index 21ae3f028..658ec17d1 100644 --- a/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettingsElements/Blocks/PaymentMethods.js +++ b/modules/ppcp-settings/resources/js/Components/Screens/Overview/TabSettingsElements/Blocks/PaymentMethods.js @@ -19,7 +19,7 @@ const createStandardFields = ( methodId, defaultTitle ) => ( { const paymentMethods = { // PayPal Checkout methods - paypal: { + 'ppcp-gateway': { fields: { ...createStandardFields( 'paypal', 'PayPal' ), showLogo: { @@ -29,12 +29,6 @@ const paymentMethods = { }, }, }, - venmo: { - fields: createStandardFields( 'venmo', 'Venmo' ), - }, - paypal_credit: { - fields: createStandardFields( 'paypal_credit', 'PayPal Credit' ), - }, credit_and_debit_card_payments: { fields: createStandardFields( 'credit_and_debit_card_payments', From ea6685d0d55daae5f2b46c9327acfcf6d9226bfa Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 21 Jan 2025 11:33:35 +0100 Subject: [PATCH 9/9] Fix phpcs --- .../src/Endpoint/PaymentRestEndpoint.php | 48 +++++++++---------- modules/ppcp-settings/src/SettingsModule.php | 2 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php index 7e5ea161e..29516107f 100644 --- a/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php +++ b/modules/ppcp-settings/src/Endpoint/PaymentRestEndpoint.php @@ -52,15 +52,15 @@ class PaymentRestEndpoint extends RestEndpoint { return array( // PayPal checkout. PayPalGateway::ID => array( - 'id' => 'ppcp-gateway', + 'id' => 'ppcp-gateway', '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', + 'icon' => 'payment-method-paypal', ), - 'venmo' => array( + 'venmo' => array( 'id' => 'venmo', 'title' => __( 'Venmo', 'woocommerce-paypal-payments' ), 'description' => __( @@ -69,7 +69,7 @@ class PaymentRestEndpoint extends RestEndpoint { ), 'icon' => 'payment-method-venmo', ), - 'pay-later' => array( + 'pay-later' => array( 'id' => 'paypal_credit', 'title' => __( 'Pay Later', 'woocommerce-paypal-payments' ), 'description' => __( @@ -88,7 +88,7 @@ class PaymentRestEndpoint extends RestEndpoint { "Accept all major credit and debit cards - even if your customer doesn't have a PayPal account.", 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-cards', + 'icon' => 'payment-method-cards', ), // Online card Payments. @@ -102,7 +102,7 @@ class PaymentRestEndpoint extends RestEndpoint { "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-advanced-cards', + 'icon' => 'payment-method-advanced-cards', ), AxoGateway::ID => array( 'id' => 'fastlane', @@ -120,7 +120,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'Allow customers to pay via their Apple Pay digital wallet.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-apple-pay', + 'icon' => 'payment-method-apple-pay', ), GooglePayGateway::ID => array( 'id' => 'google_pay', @@ -129,7 +129,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'Allow customers to pay via their Google Pay digital wallet.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-google-pay', + 'icon' => 'payment-method-google-pay', ), // Alternative payment methods. @@ -140,7 +140,7 @@ class PaymentRestEndpoint extends RestEndpoint { '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', + 'icon' => 'payment-method-bancontact', ), BlikGateway::ID => array( 'id' => 'blik', @@ -149,7 +149,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'A widely used mobile payment method in Poland, allowing Polish customers to pay directly via their banking apps. Transactions are processed in PLN.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-blik', + 'icon' => 'payment-method-blik', ), EPSGateway::ID => array( 'id' => 'eps', @@ -158,7 +158,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'An online payment method in Austria, enabling Austrian buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-eps', + 'icon' => 'payment-method-eps', ), IDealGateway::ID => array( 'id' => 'ideal', @@ -167,7 +167,7 @@ class PaymentRestEndpoint extends RestEndpoint { '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', + 'icon' => 'payment-method-ideal', ), MyBankGateway::ID => array( 'id' => 'mybank', @@ -176,7 +176,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'A European online banking payment solution primarily used in Italy, enabling customers to make secure bank transfers during checkout. Transactions are processed in EUR.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-mybank', + 'icon' => 'payment-method-mybank', ), P24Gateway::ID => array( 'id' => 'przelewy24', @@ -185,7 +185,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'A popular online payment gateway in Poland, offering various payment options for Polish customers. Transactions can be processed in PLN or EUR.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-przelewy24', + 'icon' => 'payment-method-przelewy24', ), TrustlyGateway::ID => array( 'id' => 'trustly', @@ -194,7 +194,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'A European payment method that allows buyers to make payments directly from their bank accounts, suitable for customers across multiple European countries. Supported currencies include EUR, DKK, SEK, GBP, and NOK.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-trustly', + 'icon' => 'payment-method-trustly', ), MultibancoGateway::ID => array( 'id' => 'multibanco', @@ -203,7 +203,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'An online payment method in Portugal, enabling Portuguese buyers to make secure payments directly through their bank accounts. Transactions are processed in EUR.', 'woocommerce-paypal-payments' ), - 'icon' => 'payment-method-multibanco', + 'icon' => 'payment-method-multibanco', ), PayUponInvoiceGateway::ID => array( 'id' => 'pui', @@ -212,7 +212,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'Pay upon Invoice is an invoice payment method in Germany. It is a local buy now, pay later payment method that allows the buyer to place an order, receive the goods, try them, verify they are in good order, and then pay the invoice within 30 days.', 'woocommerce-paypal-payments' ), - 'icon' => '', + 'icon' => '', ), OXXO::ID => array( 'id' => 'oxxo', @@ -221,7 +221,7 @@ class PaymentRestEndpoint extends RestEndpoint { 'OXXO is a Mexican chain of convenience stores. *Get PayPal account permission to use OXXO payment functionality by contacting us at (+52) 800–925–0304', 'woocommerce-paypal-payments' ), - 'icon' => '', + 'icon' => '', ), ); } @@ -277,11 +277,11 @@ class PaymentRestEndpoint extends RestEndpoint { foreach ( $this->gateways() as $key => $value ) { if ( ! isset( $all_gateways[ $key ] ) ) { $gateway_settings[ $key ] = array( - 'id' => $this->gateways()[ $key ]['id'] ?? '', - 'title' => $this->gateways()[ $key ]['title'] ?? '', - 'description' => $this->gateways()[ $key ]['description'] ?? '', - 'enabled' => false, - 'icon' => $this->gateways()[ $key ]['icon'] ?? '', + 'id' => $this->gateways()[ $key ]['id'] ?? '', + 'title' => $this->gateways()[ $key ]['title'] ?? '', + 'description' => $this->gateways()[ $key ]['description'] ?? '', + 'enabled' => false, + 'icon' => $this->gateways()[ $key ]['icon'] ?? '', ); continue; @@ -314,7 +314,7 @@ class PaymentRestEndpoint extends RestEndpoint { $request_data = $request->get_params(); - foreach ( $this->gateways() as $key => $value) { + foreach ( $this->gateways() as $key => $value ) { // Check if the REST body contains details for this gateway. if ( ! isset( $request_data[ $key ] ) || ! isset( $all_gateways[ $key ] ) ) { continue; diff --git a/modules/ppcp-settings/src/SettingsModule.php b/modules/ppcp-settings/src/SettingsModule.php index 3d91e8596..402379d8c 100644 --- a/modules/ppcp-settings/src/SettingsModule.php +++ b/modules/ppcp-settings/src/SettingsModule.php @@ -211,7 +211,7 @@ class SettingsModule implements ServiceModule, ExecutableModule { 'login_link' => $container->get( 'settings.rest.login_link' ), 'webhooks' => $container->get( 'settings.rest.webhooks' ), 'refresh_feature_status' => $container->get( 'settings.rest.refresh_feature_status' ), - 'payment' => $container->get( 'settings.rest.payment' ), + 'payment' => $container->get( 'settings.rest.payment' ), 'settings' => $container->get( 'settings.rest.settings' ), 'styling' => $container->get( 'settings.rest.styling' ), );