mirror of
https://gh.wpcy.net/https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2026-04-25 01:02:18 +08:00
484 lines
16 KiB
Markdown
484 lines
16 KiB
Markdown
# Plugin Architecture Documentation
|
|
|
|
This document provides a comprehensive overview of the WooCommerce PayPal Payments plugin architecture, explaining its modular design and how the various components work together.
|
|
|
|
## Overview
|
|
|
|
The WooCommerce PayPal Payments plugin is built using a modular architecture powered by the [Syde Modularity](https://github.com/inpsyde/modularity) framework. This design provides:
|
|
|
|
- **Modular Structure**: Each feature is contained within its own module with clear boundaries
|
|
- **Dependency Injection**: PSR-11 container for service management and dependency resolution
|
|
- **Feature Flags**: Dynamic module loading based on environment variables and filters
|
|
- **Extensibility**: Well-defined extension points for customization and enhancement
|
|
- **Maintainability**: Clear separation of concerns and consistent patterns
|
|
|
|
## Core Components
|
|
|
|
### Main Plugin File
|
|
|
|
The plugin initialization begins in `woocommerce-paypal-payments.php`, which:
|
|
- Loads the Composer autoloader if needed (e.g. may be already loaded in some tests)
|
|
- Contains plugin metadata and constants definitions
|
|
- It starts the bootstrap process, in `plugins_loaded` hook.
|
|
|
|
### Bootstrap System
|
|
|
|
The bootstrap process is handled by `bootstrap.php`, which:
|
|
|
|
```php
|
|
return function (
|
|
string $root_dir,
|
|
array $additional_containers = array(),
|
|
array $additional_modules = array()
|
|
): ContainerInterface {
|
|
// Load modules from modules.php
|
|
$modules = ( require "$root_dir/modules.php" )( $root_dir );
|
|
|
|
// Apply filters for customization
|
|
$modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules );
|
|
|
|
// Initialize plugin with Syde Modularity
|
|
$properties = PluginProperties::new( "$root_dir/woocommerce-paypal-payments.php" );
|
|
$bootstrap = Package::new( $properties );
|
|
|
|
foreach ( $modules as $module ) {
|
|
$bootstrap->addModule( $module );
|
|
}
|
|
|
|
$bootstrap->boot();
|
|
return $bootstrap->container();
|
|
};
|
|
```
|
|
|
|
### PPCP Container
|
|
|
|
The global `PPCP` class (`src/PPCP.php`) provides access to the dependency injection container:
|
|
|
|
```php
|
|
class PPCP {
|
|
private static $container = null;
|
|
|
|
public static function container(): ContainerInterface {
|
|
if ( ! self::$container ) {
|
|
throw new LogicException( 'No PPCP container, probably called too early when the plugin is not initialized yet.' );
|
|
}
|
|
return self::$container;
|
|
}
|
|
}
|
|
```
|
|
|
|
This allows third-party access services easily, such as in `api/order-functions.php`.
|
|
|
|
## Module System
|
|
|
|
### Module Definition
|
|
|
|
Modules are defined in `modules.php` with both core and conditional modules:
|
|
|
|
```php
|
|
$modules = array(
|
|
new PluginModule(),
|
|
( require "$modules_dir/woocommerce-logging/module.php" )(),
|
|
( require "$modules_dir/ppcp-admin-notices/module.php" )(),
|
|
( require "$modules_dir/ppcp-api-client/module.php" )(),
|
|
// ... more core modules
|
|
);
|
|
```
|
|
|
|
### Feature-Flag Controlled Modules
|
|
|
|
Conditional modules are loaded based on environment variables and filters (`modules.php`):
|
|
|
|
```php
|
|
if ( apply_filters(
|
|
'woocommerce.feature-flags.woocommerce_paypal_payments.applepay_enabled',
|
|
getenv( 'PCP_APPLEPAY_ENABLED' ) !== '0'
|
|
) ) {
|
|
$modules[] = ( require "$modules_dir/ppcp-applepay/module.php" )();
|
|
}
|
|
```
|
|
|
|
This pattern allows for:
|
|
- **Environment-based control**: Use `PCP_*_ENABLED` environment variables
|
|
- **Runtime filtering**: Apply WordPress filters to override defaults
|
|
- **Graceful degradation**: Missing features don't break core functionality
|
|
|
|
### Module Structure
|
|
|
|
Each module follows a consistent directory structure:
|
|
|
|
```
|
|
modules/ppcp-example/
|
|
├── module.php # Module factory function
|
|
├── composer.json # PHP dependencies
|
|
├── package.json # JavaScript dependencies
|
|
├── webpack.config.js # Asset building configuration
|
|
├── services.php # Service definitions
|
|
├── extensions.php # Service extensions/modifications
|
|
├── src/ # PHP source code
|
|
│ └── ExampleModule.php
|
|
├── resources/ # Source assets
|
|
│ ├── js/
|
|
│ └── css/
|
|
└── assets/ # Built assets
|
|
├── js/
|
|
└── css/
|
|
```
|
|
|
|
### Module Interface Implementation
|
|
|
|
Most modules implement the Syde Modularity interfaces. For example in `modules/ppcp-api-client/src/ApiModule.php`:
|
|
|
|
```php
|
|
class ApiModule implements ServiceModule, FactoryModule, ExtendingModule, ExecutableModule {
|
|
use ModuleClassNameIdTrait;
|
|
|
|
public function services(): array {
|
|
return require __DIR__ . '/../services.php';
|
|
}
|
|
|
|
public function factories(): array {
|
|
return require __DIR__ . '/../factories.php';
|
|
}
|
|
|
|
public function extensions(): array {
|
|
return require __DIR__ . '/../extensions.php';
|
|
}
|
|
|
|
public function run( ContainerInterface $c ): bool {
|
|
// Module initialization logic
|
|
return true;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Key Modules
|
|
|
|
### Core Infrastructure Modules
|
|
|
|
- **PluginModule** (`src/PluginModule.php`): Root module providing core services
|
|
- **woocommerce-logging**: Logging infrastructure integration
|
|
- **ppcp-api-client**: PayPal API integration, entities, and authentication
|
|
- **ppcp-session**: Session management for payment flows
|
|
- **ppcp-webhooks**: PayPal webhook handling
|
|
|
|
### Payment & Checkout Modules
|
|
|
|
- **ppcp-button**: PayPal Smart Payment Buttons and Advanced Credit and Debit Cards functionality
|
|
- **ppcp-blocks**: WooCommerce Blocks integration
|
|
- **ppcp-wc-gateway**: WooCommerce gateway integration
|
|
- **ppcp-axo**: PayPal Fastlane (Accelerated Checkout) implementation
|
|
|
|
### Feature Modules
|
|
|
|
- **ppcp-settings**: New React-based admin settings interface
|
|
- **ppcp-vaulting**: Saved payment methods functionality
|
|
- **ppcp-onboarding**: Merchant onboarding flow
|
|
|
|
### Alternative Payment Methods
|
|
|
|
- **ppcp-applepay/ppcp-googlepay**: Digital wallet integrations
|
|
- **ppcp-local-alternative-payment-methods**: Regional payment options
|
|
|
|
## Dependency Injection & Services
|
|
|
|
### Service Definition
|
|
|
|
Services are defined in each module's `services.php` file using factory functions:
|
|
|
|
```php
|
|
return array(
|
|
'example.service' => static function ( ContainerInterface $container ): ExampleService {
|
|
return new ExampleService(
|
|
$container->get( 'dependency.service' )
|
|
);
|
|
},
|
|
|
|
'example.config' => static function (): array {
|
|
return array(
|
|
'setting' => 'value',
|
|
);
|
|
},
|
|
);
|
|
```
|
|
|
|
### Service Extensions
|
|
|
|
The `extensions.php` files allow modules to modify or extend existing services:
|
|
|
|
```php
|
|
return array(
|
|
'existing.service' => static function ( ContainerInterface $container, ExistingService $service ): ExistingService {
|
|
// Modify or wrap the existing service
|
|
return new EnhancedService( $service );
|
|
},
|
|
);
|
|
```
|
|
|
|
### Container Access Patterns
|
|
|
|
Services can be accessed in multiple ways:
|
|
|
|
```php
|
|
// In our modules/services/extensions (also often passed to hook handlers via `use`)
|
|
$service = $container->get( 'service.id' );
|
|
|
|
// In third-party plugins etc. (if not adding a custom module via the `woocommerce_paypal_payments_modules` filter)
|
|
$service = PPCP::container()->get( 'service.id' );
|
|
|
|
// Check for service availability
|
|
if ( $container->has( 'optional.service' ) ) {
|
|
$service = $container->get( 'optional.service' );
|
|
}
|
|
```
|
|
|
|
### Plugin Feature Definition
|
|
|
|
The plugin has different features that can be enabled or disabled depending on assertions such as:
|
|
|
|
```php
|
|
// WooCommerce country location.
|
|
$container->get( 'api.shop.country' );
|
|
|
|
// PayPal merchant country location (notice this will fallback to WooCommerce country location if the user is not onboarded.
|
|
$container->get( 'api.merchant.country' );
|
|
|
|
// Currency
|
|
$currency = $container->get( 'api.shop.currency.getter' );
|
|
$currency->get(); // USD
|
|
|
|
// PayPal API Feature flags
|
|
$product_status = $container->get( 'applepay.apple-product-status' );
|
|
assert( $product_status instanceof AppleProductStatus );
|
|
$apple_pay_enabled = $product_status->is_active();
|
|
|
|
// Any other feature dependency. For instance, checking if own_brand_only is no enabled.
|
|
$is_enabled => $feature_is_enabled && ! $general_settings->own_brand_only(),
|
|
```
|
|
|
|
The `FeaturesDefinition.php` file is used to define these features so they can be used in other services.
|
|
|
|
For instance, they are used in places as:
|
|
|
|
- Endpoint serving the UI to know which features to show under the Features section under the Overview tab
|
|
- Define the TODO list in the UI Overview tab
|
|
|
|
The features should be defined as public constants for easy access an prefixed as `FEATURE_`
|
|
|
|
```php
|
|
// Defining Pay with Crypto feature
|
|
public const FEATURE_PAY_WITH_CRYPTO = 'pwc';
|
|
```
|
|
|
|
The features have different fields and a status field (enabled/disabled)
|
|
```php
|
|
self::FEATURE_PAY_WITH_CRYPTO => array(
|
|
'title' => __( 'Pay with Crypto', 'woocommerce-paypal-payments' ),
|
|
'description' => __( 'Enable customers to pay with cryptocurrency, and receive payments in USD in your PayPal balance.', 'woocommerce-paypal-payments' ),
|
|
'enabled' => $this->merchant_capabilities[ self::FEATURE_PAY_WITH_CRYPTO ],
|
|
'buttons' => array(
|
|
array(
|
|
'type' => 'secondary',
|
|
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
|
|
'action' => array(
|
|
'type' => 'tab',
|
|
'tab' => 'payment_methods',
|
|
'section' => 'ppcp-pay-with-crypto',
|
|
),
|
|
'showWhen' => 'enabled',
|
|
'class' => 'small-button',
|
|
),
|
|
array(
|
|
'type' => 'secondary',
|
|
'text' => __( 'Sign up', 'woocommerce-paypal-payments' ),
|
|
'urls' => array(
|
|
'sandbox' => 'https://www.sandbox.paypal.com/bizsignup/add-product?product=CRYPTO_PYMTS',
|
|
'live' => 'https://www.paypal.com/bizsignup/add-product?product=CRYPTO_PYMTS',
|
|
),
|
|
'showWhen' => 'disabled',
|
|
'class' => 'small-button',
|
|
),
|
|
array(
|
|
'type' => 'tertiary',
|
|
'text' => __( 'Learn more', 'woocommerce-paypal-payments' ),
|
|
'url' => 'https://www.paypal.com/us/digital-wallet/manage-money/crypto',
|
|
'class' => 'small-button',
|
|
),
|
|
),
|
|
),
|
|
```
|
|
|
|
There is a hook named `woocommerce_paypal_payments_rest_common_merchant_features`
|
|
allowing to define which features are enabled or disabled:
|
|
|
|
```php
|
|
// Enable Google Pay Feature
|
|
add_filter(
|
|
'woocommerce_paypal_payments_rest_common_merchant_features',
|
|
function ( array $features ) use ( $container ): array {
|
|
$product_status = $container->get( 'googlepay.helpers.apm-product-status' );
|
|
assert( $product_status instanceof ApmProductStatus );
|
|
$google_pay_enabled = $product_status->is_active();
|
|
|
|
$features[ FeaturesDefinition::FEATURE_GOOGLE_PAY ] = array(
|
|
'enabled' => $google_pay_enabled,
|
|
);
|
|
|
|
return $features;
|
|
}
|
|
);
|
|
```
|
|
|
|
The FeaturesDefinition class is initialized in ```settings.data.definition.features```
|
|
|
|
Another important part of the Features is the `FeaturesEligibilityService.php`
|
|
|
|
This service defines different callbacks to check if a feature is eligible for the merchant
|
|
in runtime.
|
|
|
|
When using `FeaturesDefinition::get()` the plugin will run the registered eligibility callbacks
|
|
checks for each Feature. The plugin will unset those Features without check or returning false in the eligibility check.
|
|
|
|
|
|
### Payment Method Definition
|
|
|
|
Besides PayPal itself, we have different Payment methods such as Apple Pay, Pay with Crypto...etc.
|
|
|
|
These methods are defined in `PaymentMethodsDefinition.php` and they are divided in several groups:
|
|
|
|
**PayPal methods**
|
|
|
|
- PayPal
|
|
- Venmo
|
|
- PayPal PayLater.
|
|
- CardButtonGateway. Only in Own Brand Mode. It allows the user to pay with card even if the customer doesn't have a PayPal account.
|
|
|
|
Filterable via `woocommerce_paypal_payments_gateway_group_paypal` hook.
|
|
|
|
**Card Methods**
|
|
|
|
- Advanced Credit and Debit Card Payments
|
|
- Fastlane by PayPal
|
|
- Apple Pay
|
|
- Google Pay
|
|
|
|
Filterable via `woocommerce_paypal_payments_gateway_group_cards` hook.
|
|
|
|
**Alternative Payment Methods**
|
|
|
|
- Pay with Crypto
|
|
- Bancontact
|
|
- Blik
|
|
- EPS
|
|
- iDeal
|
|
- MyBank
|
|
- Przelewy24
|
|
- Trustly
|
|
- Multibanco
|
|
- Pay upon Invoice
|
|
- OXXO
|
|
|
|
Filterable via `woocommerce_paypal_payments_gateway_group_apm` hook.
|
|
|
|
As in FeaturesDefinition, Payment Methods has also an eligibility service.
|
|
This service is defined in `PaymentMethodsEligibilityService.php`
|
|
|
|
It creates different callbacks that unset the Payment methods based on the eligibility checks:
|
|
|
|
```php
|
|
public function get_eligibility_checks(): array {
|
|
return array(
|
|
BancontactGateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
BlikGateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
EPSGateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
IDealGateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
MyBankGateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
P24Gateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
TrustlyGateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
MultibancoGateway::ID => fn() => ! $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
OXXO::ID => fn() => $this->is_mexico_merchant() && $this->is_apm_eligible,
|
|
PWCGateway::ID => fn() => $this->has_pwc_capability() && $this->is_apm_eligible,
|
|
PayUponInvoiceGateway::ID => fn() => $this->merchant_country === 'DE',
|
|
CreditCardGateway::ID => fn() => $this->is_mexico_merchant() || $this->is_card_fields_supported(),
|
|
CardButtonGateway::ID => fn() => $this->is_mexico_merchant() || ! $this->is_card_fields_supported(),
|
|
GooglePayGateway::ID => fn() => $this->google_pay_available,
|
|
ApplePayGateway::ID => fn() => $this->apple_pay_available,
|
|
AxoGateway::ID => fn() => $this->dcc_product_status->is_active() && call_user_func( $this->axo_eligible ),
|
|
'venmo' => fn() => $this->merchant_country === 'US',
|
|
);
|
|
}
|
|
```
|
|
|
|
## Asset Management
|
|
|
|
### Webpack Configuration
|
|
|
|
Each module with JavaScript assets includes a `webpack.config.js`:
|
|
|
|
```javascript
|
|
const path = require('path');
|
|
const defaultConfig = require('@wordpress/scripts/config/webpack.config');
|
|
|
|
module.exports = {
|
|
...defaultConfig,
|
|
entry: {
|
|
'boot': path.resolve(process.cwd(), 'resources/js', 'boot.js'),
|
|
},
|
|
output: {
|
|
path: path.resolve(process.cwd(), 'assets/js'),
|
|
filename: '[name].js',
|
|
},
|
|
};
|
|
```
|
|
|
|
### Build Process
|
|
|
|
Assets are built using the shared configuration:
|
|
|
|
- **Individual builds**: `npm run build:modules:ppcp-{module-name}`
|
|
- **Watch mode**: `npm run watch:modules:ppcp-{module-name}` or `npm run watch:modules` (all modules)
|
|
- **All modules**: `npm run build:modules` (parallel builds)
|
|
|
|
### Asset Registration
|
|
|
|
Built assets are registered through module services and enqueued conditionally:
|
|
|
|
```php
|
|
'asset.example-script' => static function( ContainerInterface $container ): Asset {
|
|
return new Asset(
|
|
'example-script',
|
|
plugin_dir_url( __DIR__ ) . 'assets/js/example.js',
|
|
array( 'wp-element' ), // dependencies
|
|
'1.0.0'
|
|
);
|
|
},
|
|
```
|
|
|
|
## Extension Points
|
|
|
|
### WordPress Hooks
|
|
|
|
The plugin provides numerous action and filter hooks:
|
|
|
|
```php
|
|
// Allow modification of order request data
|
|
apply_filters( 'ppcp_create_order_request_body_data', $data );
|
|
|
|
// PayPal order creation notification
|
|
do_action( 'woocommerce_paypal_payments_paypal_order_created', $order );
|
|
|
|
// API cache clearing
|
|
do_action( 'woocommerce_paypal_payments_flush_api_cache' );
|
|
```
|
|
|
|
### Module Filters
|
|
|
|
Modules can be modified via filters:
|
|
|
|
```php
|
|
// Add or remove modules
|
|
$modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules );
|
|
|
|
// Feature flag overrides
|
|
apply_filters( 'woocommerce.feature-flags.woocommerce_paypal_payments.applepay_enabled', $default );
|
|
```
|