Merge branch 'trunk' of github.com:woocommerce/woocommerce-paypal-payments into PCP-4487-fastlane-uk

This commit is contained in:
Daniel Dudzic 2025-06-19 14:04:40 +02:00
commit 5d8822090f
No known key found for this signature in database
GPG key ID: 31B40D33E3465483
103 changed files with 2668 additions and 1633 deletions

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\AdminNotices;
use WooCommerce\PayPalCommerce\AdminNotices\Notes\MexicoInstallmentsNote;
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
@ -110,6 +111,16 @@ class AdminNotices implements ServiceModule, ExtendingModule, ExecutableModule {
}
);
add_action(
'woocommerce_init',
function() {
if ( is_admin() && is_callable( array( WC(), 'is_wc_admin_active' ) ) && WC()->is_wc_admin_active() && class_exists( 'Automattic\WooCommerce\Admin\Notes\Notes' ) ) {
MexicoInstallmentsNote::init();
}
}
);
return true;
}
}

View file

@ -0,0 +1,122 @@
<?php
/**
* Inbox Note for Mexico Installments feature.
*
* @package WooCommerce\PayPalCommerce\AdminNotices\Notes
*/
declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\AdminNotices\Notes;
use Automattic\WooCommerce\Admin\Notes\Note;
use Automattic\WooCommerce\Admin\Notes\NotesUnavailableException;
use Automattic\WooCommerce\Admin\Notes\NoteTraits;
use Exception;
/**
* Class MexicoInstallmentsNote
*/
class MexicoInstallmentsNote {
use NoteTraits;
/**
* Name of the note for use in the database.
*/
const NOTE_NAME = 'ppcp-mexico-installments-note';
/**
* Note initialization.
*/
public static function init(): void {
try {
/**
* The method exists in the NoteTraits trait.
*
* @psalm-suppress UndefinedMethod
*/
self::possibly_add_note();
} catch ( Exception $e ) {
return;
}
}
/**
* Add the note if it passes predefined conditions.
*
* @throws NotesUnavailableException Throws exception when notes are unavailable.
*/
public static function possibly_add_note(): void {
$note = self::get_note();
if ( ! self::can_be_added() ) {
return;
}
$note->save();
}
/**
* Returns a new Note.
*
* @return Note
*/
public static function get_note(): Note {
$note = new Note();
$note->set_name( self::NOTE_NAME );
$note->set_type( Note::E_WC_ADMIN_NOTE_INFORMATIONAL );
$note->set_source( 'woocommerce-paypal-payments' );
$note->set_title(
__( 'Enable Installments with PayPal', 'woocommerce-paypal-payments' )
);
$note->set_content(
sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag. %3$s and %4$s are the opening and closing of HTML <p> tag.
__(
'Allow your customers to pay in installments without interest while you receive the full payment in a single transaction.*
%3$sActivate your Installments without interest with PayPal.%4$s
%3$sYou will receive the full payment minus the applicable PayPal fee. See %1$sterms and conditions%2$s.%4$s',
'woocommerce-paypal-payments'
),
'<a href="https://www.paypal.com/mx/webapps/mpp/merchant-fees" target="_blank">',
'</a>',
'<p>',
'</p>'
)
);
$note->add_action(
'enable-installments-action-link',
__( 'Enable Installments', 'woocommerce-paypal-payments' ),
esc_url( 'https://www.paypal.com/businessmanage/preferences/installmentplan' ),
Note::E_WC_ADMIN_NOTE_UNACTIONED,
true
);
return $note;
}
/**
* Checks if a note can and should be added.
*
* @return bool
* @throws NotesUnavailableException Throws exception when notes are unavailable.
*/
public static function can_be_added(): bool {
$country = wc_get_base_location()['country'] ?? '';
if ( $country !== 'MX' ) {
return false;
}
/**
* The method exists in the NoteTraits trait.
*
* @psalm-suppress UndefinedMethod
*/
if ( self::note_exists() ) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,22 @@
<?php
/**
* The factories of the API client.
*
* @package WooCommerce\PayPalCommerce\ApiClient
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return array(
'wcgateway.builder.experience-context' => static function ( ContainerInterface $container ): ExperienceContextBuilder {
return new ExperienceContextBuilder(
$container->get( 'wcgateway.settings' )
);
},
);

View file

@ -47,7 +47,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokenEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\AddressFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\AmountFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ApplicationContextFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExchangeRateFactory;
@ -74,7 +73,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
@ -254,7 +252,6 @@ return array(
assert( $settings instanceof Settings );
$intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$application_context_repository = $container->get( 'api.repository.application-context' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
return new OrderEndpoint(
$container->get( 'api.host' ),
@ -263,7 +260,6 @@ return array(
$patch_collection_factory,
$intent,
$logger,
$application_context_repository,
$subscription_helper,
$container->get( 'wcgateway.is-fraudnet-enabled' ),
$container->get( 'wcgateway.fraudnet' ),
@ -315,11 +311,6 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'api.repository.application-context' => static function( ContainerInterface $container ) : ApplicationContextRepository {
$settings = $container->get( 'wcgateway.settings' );
return new ApplicationContextRepository( $settings );
},
'api.repository.partner-referrals-data' => static function ( ContainerInterface $container ) : PartnerReferralsData {
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
@ -339,9 +330,6 @@ return array(
$container->get( 'api.endpoint.order' )
);
},
'api.factory.application-context' => static function ( ContainerInterface $container ) : ApplicationContextFactory {
return new ApplicationContextFactory();
},
'api.factory.payment-token' => static function ( ContainerInterface $container ) : PaymentTokenFactory {
return new PaymentTokenFactory();
},
@ -440,13 +428,9 @@ return array(
'api.factory.order' => static function ( ContainerInterface $container ): OrderFactory {
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
$payer_factory = $container->get( 'api.factory.payer' );
$application_context_repository = $container->get( 'api.repository.application-context' );
$application_context_factory = $container->get( 'api.factory.application-context' );
return new OrderFactory(
$purchase_unit_factory,
$payer_factory,
$application_context_repository,
$application_context_factory
$payer_factory
);
},
'api.factory.payments' => static function ( ContainerInterface $container ): PaymentsFactory {
@ -698,6 +682,11 @@ return array(
'GB' => $default_currencies,
'US' => $default_currencies,
'NO' => $default_currencies,
'YT' => $default_currencies,
'RE' => $default_currencies,
'GP' => $default_currencies,
'GF' => $default_currencies,
'MQ' => $default_currencies,
)
);
},

View file

@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\FactoryModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@ -28,7 +29,7 @@ use Psr\Log\LoggerInterface;
/**
* Class ApiModule
*/
class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule {
class ApiModule implements ServiceModule, FactoryModule, ExtendingModule, ExecutableModule {
use ModuleClassNameIdTrait;
/**
@ -38,6 +39,13 @@ class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule {
return require __DIR__ . '/../services.php';
}
/**
* {@inheritDoc}
*/
public function factories(): array {
return require __DIR__ . '/../factories.php';
}
/**
* {@inheritDoc}
*/

View file

@ -11,22 +11,20 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
use stdClass;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ErrorResponse;
use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
@ -88,13 +86,6 @@ class OrderEndpoint {
*/
private $logger;
/**
* The application context repository.
*
* @var ApplicationContextRepository
*/
private $application_context_repository;
/**
* True if FraudNet support is enabled in settings, otherwise false.
*
@ -119,17 +110,16 @@ class OrderEndpoint {
/**
* OrderEndpoint constructor.
*
* @param string $host The host.
* @param Bearer $bearer The bearer.
* @param OrderFactory $order_factory The order factory.
* @param PatchCollectionFactory $patch_collection_factory The patch collection factory.
* @param string $intent The intent.
* @param LoggerInterface $logger The logger.
* @param ApplicationContextRepository $application_context_repository The application context repository.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param bool $is_fraudnet_enabled true if FraudNet support is enabled in settings, otherwise false.
* @param FraudNet $fraudnet The FraudNet entity.
* @param string $bn_code The BN Code.
* @param string $host The host.
* @param Bearer $bearer The bearer.
* @param OrderFactory $order_factory The order factory.
* @param PatchCollectionFactory $patch_collection_factory The patch collection factory.
* @param string $intent The intent.
* @param LoggerInterface $logger The logger.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param bool $is_fraudnet_enabled true if FraudNet support is enabled in settings, otherwise false.
* @param FraudNet $fraudnet The FraudNet entity.
* @param string $bn_code The BN Code.
*/
public function __construct(
string $host,
@ -138,24 +128,22 @@ class OrderEndpoint {
PatchCollectionFactory $patch_collection_factory,
string $intent,
LoggerInterface $logger,
ApplicationContextRepository $application_context_repository,
SubscriptionHelper $subscription_helper,
bool $is_fraudnet_enabled,
FraudNet $fraudnet,
string $bn_code = ''
) {
$this->host = $host;
$this->bearer = $bearer;
$this->order_factory = $order_factory;
$this->patch_collection_factory = $patch_collection_factory;
$this->intent = $intent;
$this->logger = $logger;
$this->application_context_repository = $application_context_repository;
$this->bn_code = $bn_code;
$this->is_fraudnet_enabled = $is_fraudnet_enabled;
$this->subscription_helper = $subscription_helper;
$this->fraudnet = $fraudnet;
$this->host = $host;
$this->bearer = $bearer;
$this->order_factory = $order_factory;
$this->patch_collection_factory = $patch_collection_factory;
$this->intent = $intent;
$this->logger = $logger;
$this->bn_code = $bn_code;
$this->is_fraudnet_enabled = $is_fraudnet_enabled;
$this->subscription_helper = $subscription_helper;
$this->fraudnet = $fraudnet;
}
/**
@ -176,11 +164,8 @@ class OrderEndpoint {
* Creates an order.
*
* @param PurchaseUnit[] $items The purchase unit items for the order.
* @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values.
* @param string $shipping_preference One of ExperienceContext::SHIPPING_PREFERENCE_ values.
* @param Payer|null $payer The payer off the order.
* @param PaymentToken|null $payment_token The payment token.
* @param string $paypal_request_id The PayPal request id.
* @param string $user_action The user action.
* @param string $payment_method WC payment method.
* @param array $request_data Request data.
* @param PaymentSource|null $payment_source The payment source.
@ -192,21 +177,18 @@ class OrderEndpoint {
array $items,
string $shipping_preference,
Payer $payer = null,
PaymentToken $payment_token = null,
string $paypal_request_id = '',
string $user_action = ApplicationContext::USER_ACTION_CONTINUE,
string $payment_method = '',
array $request_data = array(),
PaymentSource $payment_source = null
): Order {
$bearer = $this->bearer->bearer();
$data = array(
'intent' => apply_filters( 'woocommerce_paypal_payments_order_intent', $this->intent ),
'purchase_units' => array_map(
'intent' => apply_filters( 'woocommerce_paypal_payments_order_intent', $this->intent ),
'purchase_units' => array_map(
static function ( PurchaseUnit $item ) use ( $shipping_preference ): array {
$data = $item->to_array();
if ( $shipping_preference !== ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE ) {
if ( $shipping_preference !== ExperienceContext::SHIPPING_PREFERENCE_GET_FROM_FILE ) {
// Shipping options are not allowed to be sent when not getting the address from PayPal.
unset( $data['shipping']['options'] );
}
@ -215,15 +197,10 @@ class OrderEndpoint {
},
$items
),
'application_context' => $this->application_context_repository
->current_context( $shipping_preference, $user_action )->to_array(),
);
if ( $payer && ! empty( $payer->email_address() ) ) {
$data['payer'] = $payer->to_array();
}
if ( $payment_token ) {
$data['payment_source']['token'] = $payment_token->to_array();
}
if ( $payment_source ) {
$data['payment_source'] = array(
$payment_source->name() => $payment_source->properties(),
@ -259,11 +236,8 @@ class OrderEndpoint {
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
$error = new RuntimeException(
__( 'Could not create order.', 'woocommerce-paypal-payments' )
);
$this->logger->log(
'warning',
$error = new RuntimeException( 'Could not create order.' );
$this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@ -332,8 +306,7 @@ class OrderEndpoint {
$error = new RuntimeException(
__( 'Could not capture order.', 'woocommerce-paypal-payments' )
);
$this->logger->log(
'warning',
$this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@ -354,8 +327,7 @@ class OrderEndpoint {
if ( strpos( $response['body'], ErrorResponse::ORDER_ALREADY_CAPTURED ) !== false ) {
return $this->order( $order->id() );
}
$this->logger->log(
'warning',
$this->logger->warning(
sprintf(
'Failed to capture order. PayPal API response: %1$s',
$error->getMessage()
@ -409,8 +381,7 @@ class OrderEndpoint {
'woocommerce-paypal-payments'
)
);
$this->logger->log(
'warning',
$this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@ -429,8 +400,7 @@ class OrderEndpoint {
$json,
$status_code
);
$this->logger->log(
'warning',
$this->logger->warning(
sprintf(
'Failed to authorize order. PayPal API response: %1$s',
$error->getMessage()
@ -489,8 +459,7 @@ class OrderEndpoint {
__( 'Could not retrieve order.', 'woocommerce-paypal-payments' ),
404
);
$this->logger->log(
'warning',
$this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@ -505,8 +474,7 @@ class OrderEndpoint {
$json,
$status_code
);
$this->logger->log(
'warning',
$this->logger->warning(
$error->getMessage(),
array(
'args' => $args,

View file

@ -1,252 +0,0 @@
<?php
/**
* The application context object.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
* Class ApplicationContext
*/
class ApplicationContext {
const LANDING_PAGE_LOGIN = 'LOGIN';
const LANDING_PAGE_BILLING = 'BILLING';
const LANDING_PAGE_NO_PREFERENCE = 'NO_PREFERENCE';
const VALID_LANDING_PAGE_VALUES = array(
self::LANDING_PAGE_LOGIN,
self::LANDING_PAGE_BILLING,
self::LANDING_PAGE_NO_PREFERENCE,
);
const SHIPPING_PREFERENCE_GET_FROM_FILE = 'GET_FROM_FILE';
const SHIPPING_PREFERENCE_NO_SHIPPING = 'NO_SHIPPING';
const SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS = 'SET_PROVIDED_ADDRESS';
const VALID_SHIPPING_PREFERENCE_VALUES = array(
self::SHIPPING_PREFERENCE_GET_FROM_FILE,
self::SHIPPING_PREFERENCE_NO_SHIPPING,
self::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS,
);
const USER_ACTION_CONTINUE = 'CONTINUE';
const USER_ACTION_PAY_NOW = 'PAY_NOW';
const VALID_USER_ACTION_VALUES = array(
self::USER_ACTION_CONTINUE,
self::USER_ACTION_PAY_NOW,
);
const PAYMENT_METHOD_UNRESTRICTED = 'UNRESTRICTED';
const PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED = 'IMMEDIATE_PAYMENT_REQUIRED';
/**
* The brand name.
*
* @var string
*/
private $brand_name;
/**
* The locale.
*
* @var string
*/
private $locale;
/**
* The landing page.
*
* @var string
*/
private $landing_page;
/**
* The shipping preference.
*
* @var string
*/
private $shipping_preference;
/**
* The user action.
*
* @var string
*/
private $user_action;
/**
* The return url.
*
* @var string
*/
private $return_url;
/**
* The cancel url.
*
* @var string
*/
private $cancel_url;
/**
* The payment method preference.
*
* @var string
*/
private $payment_method_preference;
/**
* ApplicationContext constructor.
*
* @param string $return_url The return URL.
* @param string $cancel_url The cancel URL.
* @param string $brand_name The brand name.
* @param string $locale The locale.
* @param string $landing_page The landing page.
* @param string $shipping_preference The shipping preference.
* @param string $user_action The user action.
* @param string $payment_method_preference The payment method preference.
*
* @throws RuntimeException When values are not valid.
*/
public function __construct(
string $return_url = '',
string $cancel_url = '',
string $brand_name = '',
string $locale = '',
string $landing_page = self::LANDING_PAGE_NO_PREFERENCE,
string $shipping_preference = self::SHIPPING_PREFERENCE_NO_SHIPPING,
string $user_action = self::USER_ACTION_CONTINUE,
string $payment_method_preference = self::PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED
) {
if ( ! in_array( $landing_page, self::VALID_LANDING_PAGE_VALUES, true ) ) {
throw new RuntimeException( 'Landingpage not correct' );
}
if ( ! in_array( $shipping_preference, self::VALID_SHIPPING_PREFERENCE_VALUES, true ) ) {
throw new RuntimeException( 'Shipping preference not correct' );
}
if ( ! in_array( $user_action, self::VALID_USER_ACTION_VALUES, true ) ) {
throw new RuntimeException( 'User action preference not correct' );
}
$this->return_url = $return_url;
$this->cancel_url = $cancel_url;
$this->brand_name = $brand_name;
$this->locale = $locale;
$this->landing_page = $landing_page;
$this->shipping_preference = $shipping_preference;
$this->user_action = $user_action;
$this->payment_method_preference = $payment_method_preference;
}
/**
* Returns the brand name.
*
* @return string
*/
public function brand_name(): string {
return $this->brand_name;
}
/**
* Returns the locale.
*
* @return string
*/
public function locale(): string {
return $this->locale;
}
/**
* Returns the landing page.
*
* @return string
*/
public function landing_page(): string {
return $this->landing_page;
}
/**
* Returns the shipping preference.
*
* @return string
*/
public function shipping_preference(): string {
return $this->shipping_preference;
}
/**
* Returns the user action.
*
* @return string
*/
public function user_action(): string {
return $this->user_action;
}
/**
* Returns the return URL.
*
* @return string
*/
public function return_url(): string {
return $this->return_url;
}
/**
* Returns the cancel URL.
*
* @return string
*/
public function cancel_url(): string {
return $this->cancel_url;
}
/**
* Returns the payment method preference.
*/
public function payment_method_preference(): string {
return $this->payment_method_preference;
}
/**
* Returns the object as array.
*
* @return array
*/
public function to_array(): array {
$data = array();
if ( $this->user_action() ) {
$data['user_action'] = $this->user_action();
}
if ( $this->shipping_preference() ) {
$data['shipping_preference'] = $this->shipping_preference();
}
if ( $this->landing_page() ) {
$data['landing_page'] = $this->landing_page();
}
if ( $this->locale() ) {
$data['locale'] = $this->locale();
}
if ( $this->brand_name() ) {
$data['brand_name'] = $this->brand_name();
}
if ( $this->return_url() ) {
$data['return_url'] = $this->return_url();
}
if ( $this->cancel_url() ) {
$data['cancel_url'] = $this->cancel_url();
}
if ( $this->payment_method_preference ) {
$data['payment_method'] = array(
'payee_preferred' => $this->payment_method_preference,
);
}
return $data;
}
}

View file

@ -0,0 +1,244 @@
<?php
/**
* The experience_context object.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
use ReflectionClass;
/**
* Class ExperienceContext
*/
class ExperienceContext {
public const LANDING_PAGE_LOGIN = 'LOGIN';
public const LANDING_PAGE_GUEST_CHECKOUT = 'GUEST_CHECKOUT';
public const LANDING_PAGE_NO_PREFERENCE = 'NO_PREFERENCE';
public const SHIPPING_PREFERENCE_GET_FROM_FILE = 'GET_FROM_FILE';
public const SHIPPING_PREFERENCE_NO_SHIPPING = 'NO_SHIPPING';
public const SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS = 'SET_PROVIDED_ADDRESS';
public const USER_ACTION_CONTINUE = 'CONTINUE';
public const USER_ACTION_PAY_NOW = 'PAY_NOW';
public const PAYMENT_METHOD_UNRESTRICTED = 'UNRESTRICTED';
public const PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED = 'IMMEDIATE_PAYMENT_REQUIRED';
/**
* The return url.
*/
private ?string $return_url = null;
/**
* The cancel url.
*/
private ?string $cancel_url = null;
/**
* The brand name.
*/
private ?string $brand_name = null;
/**
* The locale.
*/
private ?string $locale = null;
/**
* The landing page.
*/
private ?string $landing_page = null;
/**
* The shipping preference.
*/
private ?string $shipping_preference = null;
/**
* The user action.
*/
private ?string $user_action = null;
/**
* The payment method preference.
*/
private ?string $payment_method_preference = null;
/**
* Returns the return URL.
*/
public function return_url(): ?string {
return $this->return_url;
}
/**
* Sets the return URL.
*
* @param string|null $new_value The value to set.
*/
public function with_return_url( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->return_url = $new_value;
return $obj;
}
/**
* Returns the cancel URL.
*/
public function cancel_url(): ?string {
return $this->cancel_url;
}
/**
* Sets the cancel URL.
*
* @param string|null $new_value The value to set.
*/
public function with_cancel_url( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->cancel_url = $new_value;
return $obj;
}
/**
* Returns the brand name.
*
* @return string
*/
public function brand_name(): ?string {
return $this->brand_name;
}
/**
* Sets the brand name.
*
* @param string|null $new_value The value to set.
*/
public function with_brand_name( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->brand_name = $new_value;
return $obj;
}
/**
* Returns the locale.
*/
public function locale(): ?string {
return $this->locale;
}
/**
* Sets the locale.
*
* @param string|null $new_value The value to set.
*/
public function with_locale( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->locale = $new_value;
return $obj;
}
/**
* Returns the landing page.
*/
public function landing_page(): ?string {
return $this->landing_page;
}
/**
* Sets the landing page.
*
* @param string|null $new_value The value to set.
*/
public function with_landing_page( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->landing_page = $new_value;
return $obj;
}
/**
* Returns the shipping preference.
*/
public function shipping_preference(): ?string {
return $this->shipping_preference;
}
/**
* Sets the shipping preference.
*
* @param string|null $new_value The value to set.
*/
public function with_shipping_preference( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->shipping_preference = $new_value;
return $obj;
}
/**
* Returns the user action.
*/
public function user_action(): ?string {
return $this->user_action;
}
/**
* Sets the user action.
*
* @param string|null $new_value The value to set.
*/
public function with_user_action( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->user_action = $new_value;
return $obj;
}
/**
* Returns the payment method preference.
*/
public function payment_method_preference(): ?string {
return $this->payment_method_preference;
}
/**
* Sets the payment method preference.
*
* @param string|null $new_value The value to set.
*/
public function with_payment_method_preference( ?string $new_value ): ExperienceContext {
$obj = clone $this;
$obj->payment_method_preference = $new_value;
return $obj;
}
/**
* Returns the object as array.
*/
public function to_array(): array {
$data = array();
$class = new ReflectionClass( $this );
foreach ( $class->getProperties() as $prop ) {
$value = $this->{$prop->getName()};
if ( $value === null ) {
continue;
}
$data[ $prop->getName() ] = $value;
}
return $data;
}
}

View file

@ -64,13 +64,6 @@ class Order {
*/
private $update_time;
/**
* The application context.
*
* @var ApplicationContext|null
*/
private $application_context;
/**
* The payment source.
*
@ -83,21 +76,19 @@ class Order {
*
* @see https://developer.paypal.com/docs/api/orders/v2/#orders-create-response
*
* @param string $id The ID.
* @param PurchaseUnit[] $purchase_units The purchase units.
* @param OrderStatus $order_status The order status.
* @param ApplicationContext|null $application_context The application context.
* @param PaymentSource|null $payment_source The payment source.
* @param Payer|null $payer The payer.
* @param string $intent The intent.
* @param \DateTime|null $create_time The create time.
* @param \DateTime|null $update_time The update time.
* @param string $id The ID.
* @param PurchaseUnit[] $purchase_units The purchase units.
* @param OrderStatus $order_status The order status.
* @param PaymentSource|null $payment_source The payment source.
* @param Payer|null $payer The payer.
* @param string $intent The intent.
* @param \DateTime|null $create_time The create time.
* @param \DateTime|null $update_time The update time.
*/
public function __construct(
string $id,
array $purchase_units,
OrderStatus $order_status,
ApplicationContext $application_context = null,
PaymentSource $payment_source = null,
Payer $payer = null,
string $intent = 'CAPTURE',
@ -105,15 +96,14 @@ class Order {
\DateTime $update_time = null
) {
$this->id = $id;
$this->application_context = $application_context;
$this->payer = $payer;
$this->order_status = $order_status;
$this->intent = ( 'CAPTURE' === $intent ) ? 'CAPTURE' : 'AUTHORIZE';
$this->purchase_units = $purchase_units;
$this->create_time = $create_time;
$this->update_time = $update_time;
$this->payment_source = $payment_source;
$this->id = $id;
$this->payer = $payer;
$this->order_status = $order_status;
$this->intent = ( 'CAPTURE' === $intent ) ? 'CAPTURE' : 'AUTHORIZE';
$this->purchase_units = $purchase_units;
$this->create_time = $create_time;
$this->update_time = $update_time;
$this->payment_source = $payment_source;
}
/**
@ -179,16 +169,6 @@ class Order {
return $this->order_status;
}
/**
* Returns the application context.
*
* @return ApplicationContext|null
*/
public function application_context() {
return $this->application_context;
}
/**
* Returns the payment source.
*
@ -225,9 +205,6 @@ class Order {
if ( $this->update_time() ) {
$order['update_time'] = $this->update_time()->format( 'Y-m-d\TH:i:sO' );
}
if ( $this->application_context() ) {
$order['application_context'] = $this->application_context()->to_array();
}
return $order;
}

View file

@ -99,6 +99,16 @@ class PaymentToken {
);
}
/**
* Returns the PaymentSource object.
*/
public function to_payment_source(): PaymentSource {
return new PaymentSource(
'token',
(object) $this->to_array()
);
}
/**
* Returns a list of valid token types.
* Can be modified through the `woocommerce_paypal_payments_valid_payment_token_types` filter.

View file

@ -1,44 +0,0 @@
<?php
/**
* The ApplicationContext factory.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
/**
* Class ApplicationContextFactory
*/
class ApplicationContextFactory {
/**
* Returns an Application Context based off a PayPal Response.
*
* @param \stdClass $data The JSON object.
*
* @return ApplicationContext
*/
public function from_paypal_response( \stdClass $data ): ApplicationContext {
return new ApplicationContext(
isset( $data->return_url ) ?
$data->return_url : '',
isset( $data->cancel_url ) ?
$data->cancel_url : '',
isset( $data->brand_name ) ?
$data->brand_name : '',
isset( $data->locale ) ?
$data->locale : '',
isset( $data->landing_page ) ?
$data->landing_page : ApplicationContext::LANDING_PAGE_NO_PREFERENCE,
isset( $data->shipping_preference ) ?
$data->shipping_preference : ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE,
isset( $data->user_action ) ?
$data->user_action : ApplicationContext::USER_ACTION_CONTINUE
);
}
}

View file

@ -0,0 +1,193 @@
<?php
/**
* The ExperienceContext builder.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WC_AJAX;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
/**
* Class ExperienceContextBuilder
*/
class ExperienceContextBuilder {
/**
* The object being built.
*/
private ExperienceContext $experience_context;
/**
* The Settings.
*/
private ContainerInterface $settings;
/**
* ExperienceContextBuilder constructor.
*
* @param ContainerInterface $settings The settings.
*/
public function __construct( ContainerInterface $settings ) {
$this->experience_context = new ExperienceContext();
$this->settings = $settings;
}
/**
* Uses the default config for the PayPal buttons.
*
* @param string $shipping_preference One of the ExperienceContext::SHIPPING_PREFERENCE_* values.
* @param string $user_action One of the ExperienceContext::USER_ACTION_* values.
*/
public function with_default_paypal_config(
string $shipping_preference = ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING,
string $user_action = ExperienceContext::USER_ACTION_CONTINUE
): ExperienceContextBuilder {
$builder = clone $this;
$builder = $builder
->with_current_locale()
->with_current_brand_name()
->with_current_landing_page()
->with_current_payment_method_preference()
->with_endpoint_return_urls();
$builder->experience_context = $builder->experience_context
->with_shipping_preference( $shipping_preference )
->with_user_action( $user_action );
return $builder;
}
/**
* Uses the ReturnUrlEndpoint return URL.
*/
public function with_endpoint_return_urls(): ExperienceContextBuilder {
$builder = clone $this;
$builder->experience_context = $builder->experience_context
->with_return_url( home_url( WC_AJAX::get_endpoint( ReturnUrlEndpoint::ENDPOINT ) ) )
->with_cancel_url( wc_get_checkout_url() );
return $builder;
}
/**
* Uses the WC order return URLs.
*
* @param WC_Order $wc_order The WC order.
*/
public function with_order_return_urls( WC_Order $wc_order ): ExperienceContextBuilder {
$builder = clone $this;
$url = $wc_order->get_checkout_order_received_url();
$builder->experience_context = $builder->experience_context
->with_return_url( $url )
->with_cancel_url( add_query_arg( 'cancelled', 'true', $url ) );
return $builder;
}
/**
* Uses the current brand name from the settings.
*/
public function with_current_brand_name(): ExperienceContextBuilder {
$builder = clone $this;
$brand_name = $this->settings->has( 'brand_name' ) ? (string) $this->settings->get( 'brand_name' ) : '';
if ( empty( $brand_name ) ) {
$brand_name = null;
}
$builder->experience_context = $builder->experience_context
->with_brand_name( $brand_name );
return $builder;
}
/**
* Uses the current user locale.
*/
public function with_current_locale(): ExperienceContextBuilder {
$builder = clone $this;
$builder->experience_context = $builder->experience_context
->with_locale( $this->locale_to_bcp47( get_user_locale() ) );
return $builder;
}
/**
* Uses the current landing page from the settings.
*/
public function with_current_landing_page(): ExperienceContextBuilder {
$builder = clone $this;
$landing_page = $this->settings->has( 'landing_page' ) ?
(string) $this->settings->get( 'landing_page' )
: ExperienceContext::LANDING_PAGE_NO_PREFERENCE;
if ( empty( $landing_page ) ) {
$landing_page = ExperienceContext::LANDING_PAGE_NO_PREFERENCE;
}
$builder->experience_context = $builder->experience_context
->with_landing_page( $landing_page );
return $builder;
}
/**
* Uses the payment method preference from the settings.
*/
public function with_current_payment_method_preference(): ExperienceContextBuilder {
$builder = clone $this;
$builder->experience_context = $builder->experience_context
->with_payment_method_preference(
$this->settings->has( 'payee_preferred' ) && $this->settings->get( 'payee_preferred' ) ?
ExperienceContext::PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED
: ExperienceContext::PAYMENT_METHOD_UNRESTRICTED
);
return $builder;
}
/**
* Returns the ExperienceContext.
*/
public function build(): ExperienceContext {
return $this->experience_context;
}
/**
* Returns BCP-47 code supported by PayPal, for example de-DE-formal becomes de-DE.
*
* @param string $locale The locale, e.g. from get_user_locale.
*/
protected function locale_to_bcp47( string $locale ): string {
$locale = str_replace( '_', '-', $locale );
if ( preg_match( '/^[a-z]{2}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}))?$/', $locale ) ) {
return $locale;
}
$parts = explode( '-', $locale );
if ( count( $parts ) === 3 ) {
$ret = substr( $locale, 0, (int) strrpos( $locale, '-' ) );
if ( false !== $ret ) {
return $ret;
}
}
return 'en';
}
}

View file

@ -14,7 +14,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
/**
* Class OrderFactory
@ -35,39 +34,19 @@ class OrderFactory {
*/
private $payer_factory;
/**
* The ApplicationContext repository.
*
* @var ApplicationContextRepository
*/
private $application_context_repository;
/**
* The ApplicationContext factory.
*
* @var ApplicationContextFactory
*/
private $application_context_factory;
/**
* OrderFactory constructor.
*
* @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory.
* @param PayerFactory $payer_factory The Payer factory.
* @param ApplicationContextRepository $application_context_repository The Application Context repository.
* @param ApplicationContextFactory $application_context_factory The Application Context factory.
* @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory.
* @param PayerFactory $payer_factory The Payer factory.
*/
public function __construct(
PurchaseUnitFactory $purchase_unit_factory,
PayerFactory $payer_factory,
ApplicationContextRepository $application_context_repository,
ApplicationContextFactory $application_context_factory
PayerFactory $payer_factory
) {
$this->purchase_unit_factory = $purchase_unit_factory;
$this->payer_factory = $payer_factory;
$this->application_context_repository = $application_context_repository;
$this->application_context_factory = $application_context_factory;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->payer_factory = $payer_factory;
}
/**
@ -85,7 +64,6 @@ class OrderFactory {
$order->id(),
$purchase_units,
$order->status(),
$order->application_context(),
$order->payment_source(),
$order->payer(),
$order->intent(),
@ -131,18 +109,15 @@ class OrderFactory {
$order_data->purchase_units
);
$create_time = ( isset( $order_data->create_time ) ) ?
$create_time = ( isset( $order_data->create_time ) ) ?
\DateTime::createFromFormat( 'Y-m-d\TH:i:sO', $order_data->create_time )
: null;
$update_time = ( isset( $order_data->update_time ) ) ?
$update_time = ( isset( $order_data->update_time ) ) ?
\DateTime::createFromFormat( 'Y-m-d\TH:i:sO', $order_data->update_time )
: null;
$payer = ( isset( $order_data->payer ) ) ?
$payer = ( isset( $order_data->payer ) ) ?
$this->payer_factory->from_paypal_response( $order_data->payer )
: null;
$application_context = ( isset( $order_data->application_context ) ) ?
$this->application_context_factory->from_paypal_response( $order_data->application_context )
: null;
$payment_source = null;
if ( isset( $order_data->payment_source ) ) {
@ -165,7 +140,6 @@ class OrderFactory {
$order_data->id,
$purchase_units,
new OrderStatus( $order_data->status ),
$application_context,
$payment_source,
$payer,
$order_data->intent,

View file

@ -10,7 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WC_Cart;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
/**
@ -35,7 +35,7 @@ class ShippingPreferenceFactory {
): string {
$contains_physical_goods = $purchase_unit->contains_physical_goods();
if ( ! $contains_physical_goods ) {
return ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING;
return ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING;
}
$has_shipping = null !== $purchase_unit->shipping();
@ -45,20 +45,20 @@ class ShippingPreferenceFactory {
if ( $shipping_address_is_fixed ) {
// Checkout + no address given? Probably something weird happened, like no form validation?
if ( ! $has_shipping ) {
return ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING;
return ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING;
}
return ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS;
return ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS;
}
if ( 'card' === $funding_source ) {
if ( ! $has_shipping ) {
return ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING;
return ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING;
}
// Looks like GET_FROM_FILE does not work for the vaulted card button.
return ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS;
return ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS;
}
return ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE;
return ExperienceContext::SHIPPING_PREFERENCE_GET_FROM_FILE;
}
}

View file

@ -1,91 +0,0 @@
<?php
/**
* Returns the current application context.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Repository
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Repository;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* Class ApplicationContextRepository
*/
class ApplicationContextRepository {
/**
* The Settings.
*
* @var ContainerInterface
*/
private $settings;
/**
* ApplicationContextRepository constructor.
*
* @param ContainerInterface $settings The settings.
*/
public function __construct( ContainerInterface $settings ) {
$this->settings = $settings;
}
/**
* Returns the current application context.
*
* @param string $shipping_preferences The shipping preferences.
* @param string $user_action The user action.
*
* @return ApplicationContext
*/
public function current_context(
string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING,
string $user_action = ApplicationContext::USER_ACTION_CONTINUE
): ApplicationContext {
$brand_name = $this->settings->has( 'brand_name' ) ? $this->settings->get( 'brand_name' ) : '';
$locale = $this->valid_bcp47_code();
$landingpage = $this->settings->has( 'landing_page' ) ?
$this->settings->get( 'landing_page' ) : ApplicationContext::LANDING_PAGE_NO_PREFERENCE;
$payment_preference = $this->settings->has( 'payee_preferred' ) && $this->settings->get( 'payee_preferred' ) ?
ApplicationContext::PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED : ApplicationContext::PAYMENT_METHOD_UNRESTRICTED;
$context = new ApplicationContext(
home_url( \WC_AJAX::get_endpoint( ReturnUrlEndpoint::ENDPOINT ) ),
(string) wc_get_checkout_url(),
(string) $brand_name,
$locale,
(string) $landingpage,
$shipping_preferences,
$user_action,
$payment_preference
);
return $context;
}
/**
* Returns a PayPal-supported BCP-47 code, for example de-DE-formal becomes de-DE.
*
* @return string
*/
protected function valid_bcp47_code() {
$locale = str_replace( '_', '-', get_user_locale() );
if ( preg_match( '/^[a-z]{2}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}))?$/', $locale ) ) {
return $locale;
}
$parts = explode( '-', $locale );
if ( count( $parts ) === 3 ) {
$ret = substr( $locale, 0, strrpos( $locale, '-' ) );
if ( false !== $ret ) {
return $ret;
}
}
return 'en';
}
}

View file

@ -55,9 +55,11 @@ class PartnerReferralsData {
* @return array
*/
public function data( array $products = array(), string $onboarding_token = '', bool $use_subscriptions = null, bool $use_card_payments = true ) : array {
$in_acdc_country = $this->dcc_applies->for_country_currency();
if ( ! $products ) {
$products = array(
$this->dcc_applies->for_country_currency() ? 'PPCP' : 'EXPRESS_CHECKOUT',
$in_acdc_country ? 'PPCP' : 'EXPRESS_CHECKOUT',
);
}
@ -87,27 +89,16 @@ class PartnerReferralsData {
'TRACKING_SHIPMENT_READWRITE',
);
if ( true === $use_subscriptions ) {
if ( $this->dcc_applies->for_country_currency() ) {
$capabilities[] = 'PAYPAL_WALLET_VAULTING_ADVANCED';
}
$first_party_features[] = 'BILLING_AGREEMENT';
if ( $in_acdc_country ) {
$products = array( 'PPCP', 'ADVANCED_VAULTING' );
$capabilities[] = 'PAYPAL_WALLET_VAULTING_ADVANCED';
}
// Backwards compatibility. Keep those features in the #legacy-ui (null-value).
// Move this into the previous condition, once legacy code is removed.
if ( false !== $use_subscriptions ) {
$first_party_features[] = 'FUTURE_PAYMENT';
$first_party_features[] = 'BILLING_AGREEMENT';
if ( $use_card_payments !== false ) {
$first_party_features[] = 'VAULT';
}
if ( false === $use_subscriptions ) {
// Only use "ADVANCED_VAULTING" product for onboarding with subscriptions.
$products = array_filter(
$products,
static fn( $product ) => $product !== 'ADVANCED_VAULTING'
);
$first_party_features[] = 'FUTURE_PAYMENT';
}
$payload = array(

View file

@ -219,6 +219,11 @@ return array(
'SE', // Sweden
'US', // United States
'GB', // United Kingdom
'YT', // Mayotte
'RE', // Reunion
'GP', // Guadelope
'GF', // French Guiana
'MQ', // Martinique
)
// phpcs:enable Squiz.Commenting.InlineComment
);

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Applepay;
use WC_Payment_Gateway;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton;
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
@ -198,6 +199,31 @@ class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule
}
);
add_filter(
'ppcp_create_order_request_body_data',
static function ( array $data, string $payment_method, array $request ) use ( $c ) : array {
if ( $payment_method !== ApplePayGateway::ID ) {
return $data;
}
$experience_context_builder = $c->get( 'wcgateway.builder.experience-context' );
assert( $experience_context_builder instanceof ExperienceContextBuilder );
$data['payment_source'] = array(
'apple_pay' => array(
'experience_context' => $experience_context_builder
->with_endpoint_return_urls()
->build()->to_array(),
),
);
return $data;
},
10,
3
);
return true;
}

View file

@ -14,7 +14,6 @@ use Exception;
use WC_Order;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
@ -307,9 +306,6 @@ class AxoGateway extends WC_Payment_Gateway {
array( $purchase_unit ),
$shipping_preference,
null,
null,
'',
ApplicationContext::USER_ACTION_CONTINUE,
'',
array(),
$payment_source

View file

@ -162,12 +162,16 @@ class SingleProductBootstrap {
},
]
.map( ( f ) => f() )
.sort((a, b) => {
if (parseInt(a.replace(/\D/g, '')) < parseInt(b.replace(/\D/g, '')) ) {
return 1;
}
return -1;
})
.filter( ( val ) => val !== null && val !== undefined )
.sort( ( a, b ) => {
if (
parseInt( a.replace( /\D/g, '' ) ) <
parseInt( b.replace( /\D/g, '' ) )
) {
return 1;
}
return -1;
} )
.find( ( val ) => val );
if ( typeof priceText === 'undefined' ) {

View file

@ -227,6 +227,7 @@ return array(
$request_data,
$purchase_unit_factory,
$container->get( 'api.factory.shipping-preference' ),
$container->get( 'wcgateway.builder.experience-context' ),
$order_endpoint,
$payer_factory,
$session_handler,
@ -346,7 +347,8 @@ return array(
return new DisabledFundingSources(
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.all-funding-sources' ),
$container->get( 'wcgateway.configuration.card-configuration' )
$container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'api.shop.country' )
);
},
'button.is-logged-in' => static function ( ContainerInterface $container ): bool {

View file

@ -15,13 +15,15 @@ use stdClass;
use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
@ -66,6 +68,11 @@ class CreateOrderEndpoint implements EndpointInterface {
*/
private $shipping_preference_factory;
/**
* The ExperienceContextBuilder.
*/
private ExperienceContextBuilder $experience_context_builder;
/**
* The order endpoint.
*
@ -177,6 +184,7 @@ class CreateOrderEndpoint implements EndpointInterface {
* @param RequestData $request_data The RequestData object.
* @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
* @param OrderEndpoint $order_endpoint The OrderEndpoint object.
* @param PayerFactory $payer_factory The PayerFactory object.
* @param SessionHandler $session_handler The SessionHandler object.
@ -194,6 +202,7 @@ class CreateOrderEndpoint implements EndpointInterface {
RequestData $request_data,
PurchaseUnitFactory $purchase_unit_factory,
ShippingPreferenceFactory $shipping_preference_factory,
ExperienceContextBuilder $experience_context_builder,
OrderEndpoint $order_endpoint,
PayerFactory $payer_factory,
SessionHandler $session_handler,
@ -211,6 +220,7 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->request_data = $request_data;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->experience_context_builder = $experience_context_builder;
$this->api_endpoint = $order_endpoint;
$this->payer_factory = $payer_factory;
$this->session_handler = $session_handler;
@ -382,50 +392,6 @@ class CreateOrderEndpoint implements EndpointInterface {
return false;
}
/**
* Once the checkout has been validated we execute this method.
*
* @param array $data The data.
* @param \WP_Error $errors The errors, which occurred.
*
* @return array
* @throws Exception On Error.
*/
public function after_checkout_validation( array $data, \WP_Error $errors ): array {
if ( ! $errors->errors ) {
try {
$order = $this->create_paypal_order();
} catch ( Exception $exception ) {
$this->logger->error( 'Order creation failed: ' . $exception->getMessage() );
throw $exception;
}
/**
* In case we are onboarded and everything is fine with the \WC_Order
* we want this order to be created. We will intercept it and leave it
* in the "Pending payment" status though, which than later will change
* during the "onApprove"-JS callback or the webhook listener.
*/
if ( ! $this->early_order_handler->should_create_early_order() ) {
wp_send_json_success( $this->make_response( $order ) );
}
$this->early_order_handler->register_for_order( $order );
return $data;
}
$this->logger->error( 'Checkout validation failed: ' . $errors->get_error_message() );
wp_send_json_error(
array(
'name' => '',
'message' => $errors->get_error_message(),
'code' => (int) $errors->get_error_code(),
'details' => array(),
)
);
return $data;
}
/**
* Creates the order in the PayPal, uses data from WC order if provided.
*
@ -454,16 +420,16 @@ class CreateOrderEndpoint implements EndpointInterface {
);
$action = in_array( $this->parsed_request_data['context'], $this->pay_now_contexts, true ) ?
ApplicationContext::USER_ACTION_PAY_NOW : ApplicationContext::USER_ACTION_CONTINUE;
ExperienceContext::USER_ACTION_PAY_NOW : ExperienceContext::USER_ACTION_CONTINUE;
if ( 'card' === $funding_source ) {
if ( CardBillingMode::MINIMAL_INPUT === $this->card_billing_data_mode ) {
if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) {
if ( ExperienceContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) {
if ( $payer ) {
$payer->set_address( null );
}
}
if ( ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING === $shipping_preference ) {
if ( ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING === $shipping_preference ) {
if ( $payer ) {
$payer->set_name( null );
}
@ -475,16 +441,28 @@ class CreateOrderEndpoint implements EndpointInterface {
}
}
$payment_source_key = 'paypal';
if ( in_array( $funding_source, array( 'venmo' ), true ) ) {
$payment_source_key = $funding_source;
}
$payment_source = new PaymentSource(
$payment_source_key,
(object) array(
'experience_context' => $this->experience_context_builder
->with_default_paypal_config( $shipping_preference, $action )
->build()->to_array(),
)
);
try {
return $this->api_endpoint->create(
array( $this->purchase_unit ),
$shipping_preference,
$payer,
null,
'',
$action,
$payment_method,
$data
$data,
$payment_source
);
} catch ( PayPalApiException $exception ) {
// Looks like currently there is no proper way to validate the shipping address for PayPal,
@ -505,7 +483,9 @@ class CreateOrderEndpoint implements EndpointInterface {
array( $this->purchase_unit ),
$shipping_preference,
$payer,
null
$payment_method,
$data,
$payment_source
);
}

View file

@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
/**
* Class DisabledFundingSources
@ -42,17 +43,26 @@ class DisabledFundingSources {
*/
private CardPaymentsConfiguration $dcc_configuration;
/**
* Merchant Country
*
* @var string
*/
private string $merchant_country;
/**
* DisabledFundingSources constructor.
*
* @param Settings $settings The settings.
* @param array $all_funding_sources All existing funding sources.
* @param CardPaymentsConfiguration $dcc_configuration DCC gateway configuration.
* @param string $merchant_country Merchant country.
*/
public function __construct( Settings $settings, array $all_funding_sources, CardPaymentsConfiguration $dcc_configuration ) {
public function __construct( Settings $settings, array $all_funding_sources, CardPaymentsConfiguration $dcc_configuration, string $merchant_country ) {
$this->settings = $settings;
$this->all_funding_sources = $all_funding_sources;
$this->dcc_configuration = $dcc_configuration;
$this->merchant_country = $merchant_country;
}
/**
@ -145,11 +155,13 @@ class DisabledFundingSources {
* @return array
*/
private function apply_context_rules( array $disable_funding ) : array {
if ( 'MX' === $this->merchant_country && $this->dcc_configuration->is_bcdc_enabled() && CartCheckoutDetector::has_classic_checkout() && is_checkout() ) {
return $disable_funding;
}
if ( ! is_checkout() || $this->dcc_configuration->use_acdc() ) {
// Non-checkout pages, or ACDC capability: Don't load card button.
$disable_funding[] = 'card';
return $disable_funding;
}
return $disable_funding;

View file

@ -61,6 +61,7 @@ return array(
'LT',
'LU',
'MT',
'MX',
'NL',
'PL',
'PT',
@ -73,6 +74,11 @@ return array(
'GB',
'US',
'NO',
'YT',
'RE',
'GP',
'GF',
'MQ',
)
);
},

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\CardFields;
use DomainException;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\CardFields\Service\CardCaptureValidator;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
@ -150,6 +151,15 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$experience_context_builder = $c->get( 'wcgateway.builder.experience-context' );
assert( $experience_context_builder instanceof ExperienceContextBuilder );
$payment_source_data = array(
'experience_context' => $experience_context_builder
->with_endpoint_return_urls()
->build()->to_array(),
);
$three_d_secure_contingency =
$settings->has( '3d_secure_contingency' )
? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) )
@ -159,15 +169,15 @@ class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModu
$three_d_secure_contingency === 'SCA_ALWAYS'
|| $three_d_secure_contingency === 'SCA_WHEN_REQUIRED'
) {
$data['payment_source']['card'] = array(
'attributes' => array(
'verification' => array(
'method' => $three_d_secure_contingency,
),
$payment_source_data['attributes'] = array(
'verification' => array(
'method' => $three_d_secure_contingency,
),
);
}
$data['payment_source'] = array( 'card' => $payment_source_data );
return $data;
},
10,

View file

@ -9,7 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
@ -125,7 +125,7 @@ class SettingsTabMapHelper {
* Retrieves the mapped value for the 'landing_page' from the new settings.
*
* @param array<string, scalar|array> $settings_model The new settings model data as an array.
* @return 'LOGIN'|'BILLING'|'NO_PREFERENCE'|null The mapped 'landing_page' setting value.
* @return 'LOGIN'|'GUEST_CHECKOUT'|'NO_PREFERENCE'|null The mapped 'landing_page' setting value.
*/
protected function mapped_landing_page_value( array $settings_model ): ?string {
$landing_page = $settings_model['landing_page'] ?? false;
@ -135,10 +135,10 @@ class SettingsTabMapHelper {
}
return $landing_page === 'login'
? ApplicationContext::LANDING_PAGE_LOGIN
? ExperienceContext::LANDING_PAGE_LOGIN
: ( $landing_page === 'guest_checkout'
? ApplicationContext::LANDING_PAGE_BILLING
: ApplicationContext::LANDING_PAGE_NO_PREFERENCE
? ExperienceContext::LANDING_PAGE_GUEST_CHECKOUT
: ExperienceContext::LANDING_PAGE_NO_PREFERENCE
);
}

View file

@ -132,6 +132,11 @@ return array(
'SE', // Sweden
'US', // United States
'GB', // United Kingdom
'YT', // Mayotte
'RE', // Reunion
'GP', // Guadelope
'GF', // French Guiana
'MQ', // Martinique
)
// phpcs:enable Squiz.Commenting.InlineComment
);

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Googlepay;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint;
@ -261,6 +262,15 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$experience_context_builder = $c->get( 'wcgateway.builder.experience-context' );
assert( $experience_context_builder instanceof ExperienceContextBuilder );
$payment_source_data = array(
'experience_context' => $experience_context_builder
->with_endpoint_return_urls()
->build()->to_array(),
);
$three_d_secure_contingency =
$settings->has( '3d_secure_contingency' )
? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) )
@ -270,15 +280,15 @@ class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModul
$three_d_secure_contingency === 'SCA_ALWAYS'
|| $three_d_secure_contingency === 'SCA_WHEN_REQUIRED'
) {
$data['payment_source']['google_pay'] = array(
'attributes' => array(
'verification' => array(
'method' => $three_d_secure_contingency,
),
$payment_source_data['attributes'] = array(
'verification' => array(
'method' => $three_d_secure_contingency,
),
);
}
$data['payment_source'] = array( 'google_pay' => $payment_source_data );
return $data;
},
10,

View file

@ -81,7 +81,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.blik.wc-gateway' => static function ( ContainerInterface $container ): BlikGateway {
@ -89,7 +90,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.eps.wc-gateway' => static function ( ContainerInterface $container ): EPSGateway {
@ -97,7 +99,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.ideal.wc-gateway' => static function ( ContainerInterface $container ): IDealGateway {
@ -105,7 +108,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.mybank.wc-gateway' => static function ( ContainerInterface $container ): MyBankGateway {
@ -113,7 +117,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.p24.wc-gateway' => static function ( ContainerInterface $container ): P24Gateway {
@ -121,7 +126,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.trustly.wc-gateway' => static function ( ContainerInterface $container ): TrustlyGateway {
@ -129,7 +135,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.multibanco.wc-gateway' => static function ( ContainerInterface $container ): MultibancoGateway {
@ -137,7 +144,8 @@ return array(
$container->get( 'api.endpoint.orders' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'wcgateway.processor.refunds' ),
$container->get( 'wcgateway.transaction-url-provider' )
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'ppcp-local-apms.bancontact.payment-method' => static function( ContainerInterface $container ): BancontactPaymentMethod {

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class BancontactGateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* BancontactGateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class BancontactGateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -139,8 +148,13 @@ class BancontactGateway extends WC_Payment_Gateway {
'intent' => 'CAPTURE',
'payment_source' => array(
'bancontact' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->build()
->with_locale( 'en-BE' )
->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
@ -155,11 +169,6 @@ class BancontactGateway extends WC_Payment_Gateway {
'invoice_id' => $purchase_unit->invoice_id(),
),
),
'application_context' => array(
'locale' => 'en-BE',
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
try {

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class BlikGateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* BlikGateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class BlikGateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -139,9 +148,14 @@ class BlikGateway extends WC_Payment_Gateway {
'intent' => 'CAPTURE',
'payment_source' => array(
'blik' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->build()
->with_locale( 'en-PL' )
->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
@ -156,11 +170,6 @@ class BlikGateway extends WC_Payment_Gateway {
'invoice_id' => $purchase_unit->invoice_id(),
),
),
'application_context' => array(
'locale' => 'en-PL',
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
try {

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class EPSGateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* EPSGateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class EPSGateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -139,8 +148,13 @@ class EPSGateway extends WC_Payment_Gateway {
'intent' => 'CAPTURE',
'payment_source' => array(
'eps' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->build()
->with_locale( 'en-AT' )
->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
@ -155,11 +169,6 @@ class EPSGateway extends WC_Payment_Gateway {
'invoice_id' => $purchase_unit->invoice_id(),
),
),
'application_context' => array(
'locale' => 'en-AT',
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
try {

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class IDealGateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* IDealGateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class IDealGateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -139,8 +148,11 @@ class IDealGateway extends WC_Payment_Gateway {
'intent' => 'CAPTURE',
'payment_source' => array(
'ideal' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->build()->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
@ -155,10 +167,6 @@ class IDealGateway extends WC_Payment_Gateway {
'invoice_id' => $purchase_unit->invoice_id(),
),
),
'application_context' => array(
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
try {

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class MultibancoGateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* MultibancoGateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class MultibancoGateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -157,16 +166,16 @@ class MultibancoGateway extends WC_Payment_Gateway {
$request_body = array(
'payment_source' => array(
'multibanco' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->build()
->with_locale( 'en-PT' )
->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
'application_context' => array(
'locale' => 'en-PT',
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
$response = $this->orders_endpoint->confirm_payment_source( $request_body, $body->id );

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class MyBankGateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* MyBankGateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class MyBankGateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -139,8 +148,13 @@ class MyBankGateway extends WC_Payment_Gateway {
'intent' => 'CAPTURE',
'payment_source' => array(
'mybank' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->build()
->with_locale( 'en-IT' )
->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
@ -155,11 +169,6 @@ class MyBankGateway extends WC_Payment_Gateway {
'invoice_id' => $purchase_unit->invoice_id(),
),
),
'application_context' => array(
'locale' => 'en-IT',
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
try {

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class P24Gateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* P24Gateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class P24Gateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -139,9 +148,14 @@ class P24Gateway extends WC_Payment_Gateway {
'intent' => 'CAPTURE',
'payment_source' => array(
'p24' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->build()
->with_locale( 'en-PL' )
->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
@ -156,11 +170,6 @@ class P24Gateway extends WC_Payment_Gateway {
'invoice_id' => $purchase_unit->invoice_id(),
),
),
'application_context' => array(
'locale' => 'en-PL',
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
try {

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -52,19 +53,26 @@ class TrustlyGateway extends WC_Payment_Gateway {
*/
protected $transaction_url_provider;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* TrustlyGateway constructor.
*
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param Orders $orders_endpoint PayPal Orders endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory Purchase unit factory.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
Orders $orders_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
RefundProcessor $refund_processor,
TransactionUrlProvider $transaction_url_provider
TransactionUrlProvider $transaction_url_provider,
ExperienceContextBuilder $experience_context_builder
) {
$this->id = self::ID;
@ -86,10 +94,11 @@ class TrustlyGateway extends WC_Payment_Gateway {
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->orders_endpoint = $orders_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -139,9 +148,13 @@ class TrustlyGateway extends WC_Payment_Gateway {
'intent' => 'CAPTURE',
'payment_source' => array(
'trustly' => array(
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'country_code' => $wc_order->get_billing_country(),
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'experience_context' => $this->experience_context_builder
->with_order_return_urls( $wc_order )
->with_current_locale()
->build()->to_array(),
),
),
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
@ -156,11 +169,6 @@ class TrustlyGateway extends WC_Payment_Gateway {
'invoice_id' => $purchase_unit->invoice_id(),
),
),
'application_context' => array(
'locale' => $this->valid_bcp47_code(),
'return_url' => $this->get_return_url( $wc_order ),
'cancel_url' => add_query_arg( 'cancelled', 'true', $this->get_return_url( $wc_order ) ),
),
);
try {
@ -228,27 +236,4 @@ class TrustlyGateway extends WC_Payment_Gateway {
return parent::get_transaction_url( $order );
}
/**
* Returns a PayPal-supported BCP-47 code, for example de-DE-formal becomes de-DE.
*
* @return string
*/
private function valid_bcp47_code() {
$locale = str_replace( '_', '-', get_user_locale() );
if ( preg_match( '/^[a-z]{2}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}))?$/', $locale ) ) {
return $locale;
}
$parts = explode( '-', $locale );
if ( count( $parts ) === 3 ) {
$ret = substr( $locale, 0, strrpos( $locale, '-' ) );
if ( false !== $ret ) {
return $ret;
}
}
return 'en';
}
}

View file

@ -681,7 +681,7 @@ class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, Execu
'<label for="ppcp_enable_subscription_product-' . esc_attr( (string) $product->get_id() ) . '" style="' . esc_attr( $style ) . '">',
'</label>'
);
$plan_id = isset( $subscription_plan['id'] ) ?? '';
$plan_id = $subscription_plan['id'] ?? '';
echo '<input type="checkbox" id="ppcp_enable_subscription_product-' . esc_attr( (string) $product->get_id() ) . '" data-subs-plan="' . esc_attr( (string) $plan_id ) . '" name="_ppcp_enable_subscription_product" value="yes" ' . checked( $enable_subscription_product, 'yes', false ) . '/>';
echo sprintf(
// translators: %1$s and %2$s are label open and close tags.

View file

@ -78,6 +78,11 @@ return array(
'SE',
'GB',
'US',
'YT',
'RE',
'GP',
'GF',
'MQ',
)
);
},

View file

@ -115,87 +115,67 @@ class SavePaymentMethodsModule implements ServiceModule, ExtendingModule, Execut
function ( array $data, string $payment_method, array $request_data ) use ( $c ): array {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
$new_attributes = array(
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
),
);
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
if ( ! $target_customer_id ) {
$target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true );
}
if ( $target_customer_id ) {
$new_attributes['customer'] = array(
'id' => $target_customer_id,
);
}
$funding_source = (string) ( $request_data['funding_source'] ?? '' );
if ( $payment_method === CreditCardGateway::ID ) {
if ( ! $settings->has( 'vault_enabled_dcc' ) || ! $settings->get( 'vault_enabled_dcc' ) ) {
return $data;
}
$save_payment_method = $request_data['save_payment_method'] ?? false;
if ( $save_payment_method ) {
$data['payment_source'] = array(
'card' => array(
'attributes' => array(
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
),
),
),
);
$target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true );
if ( ! $target_customer_id ) {
$target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true );
}
if ( $target_customer_id ) {
$data['payment_source']['card']['attributes']['customer'] = array(
'id' => $target_customer_id,
);
}
if ( ! $save_payment_method ) {
return $data;
}
}
if ( $payment_method === PayPalGateway::ID ) {
} elseif ( $payment_method === PayPalGateway::ID ) {
if ( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) ) {
return $data;
}
$funding_source = $request_data['funding_source'] ?? null;
if ( $funding_source && $funding_source === 'venmo' ) {
$data['payment_source'] = array(
'venmo' => array(
'attributes' => array(
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
'usage_type' => 'MERCHANT',
'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
),
),
),
);
} elseif ( $funding_source && $funding_source === 'apple_pay' ) {
$data['payment_source'] = array(
'apple_pay' => array(
'stored_credential' => array(
'payment_initiator' => 'CUSTOMER',
'payment_type' => 'RECURRING',
),
'attributes' => array(
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
),
),
),
);
} else {
$data['payment_source'] = array(
'paypal' => array(
'attributes' => array(
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
'usage_type' => 'MERCHANT',
'permit_multiple_payment_tokens' => apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false ),
),
),
),
);
if ( ! in_array( $funding_source, array( 'paypal', 'venmo' ), true ) ) {
return $data;
}
$new_attributes['vault']['usage_type'] = 'MERCHANT';
$new_attributes['vault']['permit_multiple_payment_tokens'] = apply_filters( 'woocommerce_paypal_payments_permit_multiple_payment_tokens', false );
} else {
return $data;
}
$payment_source = (array) ( $data['payment_source'] ?? array() );
$key = array_key_first( $payment_source );
if ( ! is_string( $key ) || empty( $key ) ) {
$key = $payment_method;
if ( $payment_method === PayPalGateway::ID && $funding_source ) {
$key = $funding_source;
}
$payment_source[ $key ] = array();
}
$payment_source[ $key ] = (array) $payment_source[ $key ];
$attributes = (array) ( $payment_source[ $key ]['attributes'] ?? array() );
$payment_source[ $key ]['attributes'] = array_merge( $attributes, $new_attributes );
$data['payment_source'] = $payment_source;
return $data;
},
10,
20,
3
);

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="30px" height="30px" viewBox="0 0 42 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>OXXO</title>
<defs/>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="SPB_&amp;_AltPay_NewAssets" transform="translate(-100.000000, -159.000000)">
<g id="logo-OXXO" transform="translate(100.000000, 159.000000)">
<path d="M0.142456528,1.48437917 C0.142456528,0.77043992 0.728159303,0.186243119 1.44446761,0.186243119 L40.6503931,0.186243119 C41.3667014,0.186243119 41.9524042,0.77043992 41.9524042,1.48437917 L41.9524042,18.1011373 C41.9524042,18.8150765 41.3667014,19.3990362 40.6503931,19.3990362 L1.44446761,19.3990362 C0.728159303,19.3990362 0.142456528,18.8150765 0.142456528,18.1011373 L0.142456528,1.48437917 Z" id="Fill-2" fill="#EDA42D"/>
<polygon id="Fill-4" fill="#FEFEFE" points="0.142480318 17.5124813 41.952428 17.5124813 41.952428 2.07265562 0.142480318 2.07265562"/>
<path d="M35.5752619,6.08262231 C33.662331,6.08262231 32.1029152,7.63763417 32.1029152,9.54463469 C32.1029152,11.4511608 33.662331,13.0064099 35.5752619,13.0064099 C37.4877171,13.0064099 39.0471329,11.4511608 39.0471329,9.54463469 C39.0471329,7.63763417 37.4877171,6.08262231 35.5752619,6.08262231" id="Fill-6" fill="#EC1D24"/>
<path d="M6.95585459,6.08262231 C5.04268574,6.08262231 3.48326994,7.63763417 3.48326994,9.54463469 C3.48326994,11.4511608 5.04268574,13.0064099 6.95585459,13.0064099 C8.86807185,13.0064099 10.4277255,11.4511608 10.4277255,9.54463469 C10.4277255,7.63763417 8.86807185,6.08262231 6.95585459,6.08262231" id="Fill-7" fill="#EC1D24"/>
<path d="M35.5752619,15.0141446 C32.5537303,15.0141446 30.0893537,12.5573397 30.0893537,9.54480072 C30.0893537,6.53155015 32.5537303,4.07521964 35.5752619,4.07521964 C38.5970315,4.07521964 41.0609322,6.53155015 41.0609322,9.54480072 C41.0609322,12.5573397 38.5970315,15.0141446 35.5752619,15.0141446 Z M12.4411918,9.54480072 C12.4411918,12.5573397 9.97729109,15.0141446 6.95575943,15.0141446 C3.93351408,15.0141446 1.46985124,12.5573397 1.46985124,9.54480072 C1.46985124,6.53155015 3.93351408,4.07521964 6.95575943,4.07521964 C9.97729109,4.07521964 12.4411918,6.53155015 12.4411918,9.54480072 Z M35.3028697,3.03585692 C32.0884035,2.9620911 30.5772808,5.01709763 28.384107,7.55170056 L26.3151155,9.94232969 L29.591435,13.8526295 C30.3719756,15.0542296 28.8822636,16.2465793 27.9580332,15.1472077 L24.9288888,11.5447794 L21.9772989,14.9562705 C21.0373673,16.0421223 19.5645461,14.8288999 20.3617394,13.6386849 L23.5659761,9.92382894 L21.4667717,7.42693908 L22.8173138,5.75949957 L24.9522028,8.31639828 L26.7923372,6.18217058 C27.6953948,5.13569219 28.6162946,3.74884741 29.8098246,3.03585692 L0.142385159,3.03585692 L0.142385159,16.549707 L7.07875226,16.549707 C10.2934564,16.549707 11.7529554,14.6332189 13.8866549,12.0492806 L15.8999784,9.61097649 L12.5334959,5.77752594 C11.726073,4.59418943 13.1874752,3.36815887 14.1371606,4.44594623 L17.2483795,7.9779294 L20.1209875,4.49931378 C21.0354641,3.39164059 22.5356435,4.57118208 21.7662842,5.77942346 L18.6486421,9.56757088 L20.8051797,12.0153626 L19.4463112,13.6197098 L17.2997653,11.2058361 L15.5095892,13.3813347 C14.6310351,14.4484486 13.7415376,15.8094397 12.5646605,16.549707 L41.9523328,16.549707 L41.9523328,3.03585692 L35.3028697,3.03585692 Z" id="Fill-8" fill="#EC1D24"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -5,7 +5,7 @@
.ppcp-r-welcome-docs__title {
text-align: center;
@include font(20, 28, 700);
margin: 32px 0 32px 0;
margin: 32px 0 32px 0 !important;
}
.ppcp-r-welcome-docs__wrapper {

View file

@ -2,6 +2,12 @@ body:has(.ppcp-r-container--settings),
body:has(.ppcp-r-container--onboarding) {
background-color: var(--ppcp-color-app-bg) !important;
// WC 9.9+.
#mainform,
#wpcontent {
background-color: var(--ppcp-color-app-bg) !important;
}
.woocommerce-layout,
#woocommerce-layout__primary {
padding: 0 !important;

View file

@ -42,7 +42,8 @@ const PaymentFlow = ( {
/>
);
}
const description = useAcdc ? optionalDescription : '';
const description =
useAcdc && 'MX' !== storeCountry ? optionalDescription : '';
return (
<div className="ppcp-r-welcome-docs__wrapper">
<DefaultMethodsSection

View file

@ -1,20 +1,29 @@
import { __ } from '@wordpress/i18n';
import { useWooSettings } from '../../../../../data/common/hooks';
import PricingTitleBadge from '../../../../ReusableComponents/PricingTitleBadge';
import BadgeBox from '../../../../ReusableComponents/BadgeBox';
const AlternativePaymentMethods = ( { learnMore = '' } ) => {
const { storeCountry } = useWooSettings();
// Determine which icons to display based on the country code.
const imageBadges =
storeCountry === 'MX'
? [ 'icon-button-oxxo.svg' ]
: [
// 'icon-button-sepa.svg', // Enable this when the SEPA-Gateway is ready.
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
];
return (
<BadgeBox
title={ __(
'Alternative Payment Methods',
'woocommerce-paypal-payments'
) }
imageBadge={ [
// 'icon-button-sepa.svg', // Enable this when the SEPA-Gateway is ready.
'icon-button-ideal.svg',
'icon-button-blik.svg',
'icon-button-bancontact.svg',
] }
imageBadge={ imageBadges }
textBadge={ <PricingTitleBadge item="apm" /> }
description={ __(
'Seamless payments for customers across the globe using their preferred payment methods.',

View file

@ -10,13 +10,13 @@ import PaymentFlow from '../Components/PaymentFlow';
const StepPaymentMethods = () => {
const { optionalMethods, setOptionalMethods } =
OnboardingHooks.useOptionalPaymentMethods();
const { ownBrandOnly } = CommonHooks.useWooSettings();
const { ownBrandOnly, storeCountry } = CommonHooks.useWooSettings();
const { isCasualSeller } = OnboardingHooks.useBusiness();
const { canUseCardPayments } = OnboardingHooks.useFlags();
const optionalMethodTitle = useMemo( () => {
// The BCDC flow does not show a title. !acdc does not show a title.
if ( isCasualSeller || ! canUseCardPayments ) {
// The BCDC flow does not show a title. !acdc does not show a title. Mexico does not show a title.
if ( isCasualSeller || ! canUseCardPayments || 'MX' === storeCountry ) {
return null;
}
@ -24,7 +24,7 @@ const StepPaymentMethods = () => {
'Available with additional application',
'woocommerce-paypal-payments'
);
}, [ isCasualSeller, canUseCardPayments ] );
}, [ isCasualSeller, canUseCardPayments, storeCountry ] );
const methodChoices = [
{
@ -34,7 +34,7 @@ const StepPaymentMethods = () => {
},
{
title:
ownBrandOnly || ! canUseCardPayments
ownBrandOnly || ! canUseCardPayments || 'MX' === storeCountry
? __(
'No thanks, I prefer to use a different provider for local payment methods',
'woocommerce-paypal-payments'
@ -87,7 +87,9 @@ const OptionalMethodDescription = () => {
return (
<PaymentFlow
onlyOptional={ true }
useAcdc={ ! isCasualSeller && canUseCardPayments }
useAcdc={
! isCasualSeller && canUseCardPayments && 'MX' !== storeCountry
}
isFastlane={ true }
isPayLater={ true }
ownBrandOnly={ ownBrandOnly }

View file

@ -14,6 +14,7 @@ import { usePaymentConfig } from '../hooks/usePaymentConfig';
const StepWelcome = ( { setStep, currentStep } ) => {
const { storeCountry, ownBrandOnly } = CommonHooks.useWooSettings();
const { canUseCardPayments, canUseFastlane } = OnboardingHooks.useFlags();
const { isCasualSeller } = OnboardingHooks.useBusiness();
const { icons } = usePaymentConfig(
storeCountry,
@ -23,7 +24,7 @@ const StepWelcome = ( { setStep, currentStep } ) => {
);
const onboardingHeaderDescription =
canUseCardPayments && ! ownBrandOnly
canUseCardPayments && ! ownBrandOnly && 'MX' !== storeCountry
? __(
'Your all-in-one integration for PayPal checkout solutions that enable buyers to pay via PayPal, Pay Later, all major credit/debit cards, Apple Pay, Google Pay, and more.',
'woocommerce-paypal-payments'

View file

@ -28,6 +28,7 @@ const PAYMENT_ICONS = [
{ name: 'blik', isOwnBrand: true, onlyAcdc: true },
{ name: 'ideal', isOwnBrand: true, onlyAcdc: true },
{ name: 'bancontact', isOwnBrand: true, onlyAcdc: true },
{ name: 'oxxo', isOwnBrand: true, onlyAcdc: false, countries: [ 'MX' ] },
];
// Default configuration, used for all countries, unless they override individual attributes below.
@ -116,16 +117,16 @@ const COUNTRY_CONFIGS = {
MX: {
extendedMethods: [
{
name: 'CardFields',
Component: CardFields,
name: 'CreditDebitCards',
Component: CreditDebitCards,
isOwnBrand: false,
isAcdc: true,
isAcdc: false,
},
{
name: 'APMs',
Component: AlternativePaymentMethods,
isOwnBrand: true,
isAcdc: true,
isAcdc: false,
},
],
},
@ -211,6 +212,11 @@ const getRelevantIcons = ( country, includeAcdc, onlyBranded ) =>
return true;
}
// If we're in Mexico, only show OXXO from the APMs.
if ( country === 'MX' && onlyAcdc ) {
return false;
}
if ( onlyBranded && ! isOwnBrand ) {
return false;
}
@ -277,8 +283,11 @@ export const usePaymentConfig = (
const availableOptionalMethods = filterMethods(
config.extendedMethods,
[
// Either include Acdc or non-Acdc methods.
( method ) => method.isAcdc === canUseCardPayments,
// Either include Acdc or non-Acdc methods except for Mexico.
( method ) =>
country === 'MX'
? ! method.isAcdc || canUseCardPayments
: method.isAcdc === canUseCardPayments,
// Only include own-brand methods when ownBrandOnly is true.
( method ) => ! ownBrandOnly || method.isOwnBrand === true,
// Only include Fastlane when hasFastlane is true.

View file

@ -6,7 +6,10 @@ import FeatureItem from './FeatureItem';
import FeatureDescription from './FeatureDescription';
import { ContentWrapper } from '../../../../../ReusableComponents/Elements';
import SettingsCard from '../../../../../ReusableComponents/SettingsCard';
import { useMerchantInfo } from '../../../../../../data/common/hooks';
import {
useMerchantInfo,
useWooSettings,
} from '../../../../../../data/common/hooks';
import { STORE_NAME as COMMON_STORE_NAME } from '../../../../../../data/common';
import {
NOTIFICATION_ERROR,
@ -17,6 +20,7 @@ import { useFeatures } from '../../../../../../data/features/hooks';
const Features = () => {
const [ isRefreshing, setIsRefreshing ] = useState( false );
const { merchant } = useMerchantInfo();
const { storeCountry } = useWooSettings();
const { features, fetchFeatures } = useFeatures();
const { refreshFeatureStatuses } = useDispatch( COMMON_STORE_NAME );
const { createSuccessNotice, createErrorNotice } =
@ -26,6 +30,13 @@ const Features = () => {
return null;
}
// Filter out ACDC for Mexico (when disabled).
const filteredFeatures = features.filter(
( feature ) =>
feature.id !== 'advanced_credit_and_debit_cards' ||
storeCountry !== 'MX'
);
const refreshHandler = async () => {
setIsRefreshing( true );
try {
@ -86,7 +97,7 @@ const Features = () => {
aria-busy={ isRefreshing }
>
<ContentWrapper>
{ features.map( ( { id, enabled, ...feature } ) => (
{ filteredFeatures.map( ( { id, enabled, ...feature } ) => (
<FeatureItem
key={ id }
isBusy={ isRefreshing }

View file

@ -4,9 +4,8 @@ import { PaymentMethodsBlock } from '../../../../ReusableComponents/SettingsBloc
import usePaymentDependencyState from '../../../../../hooks/usePaymentDependencyState';
import useSettingDependencyState from '../../../../../hooks/useSettingDependencyState';
import usePaymentMethodsToggle from '../../../../../hooks/usePaymentMethodsToggle';
import useDependencyMessages from '../../../../../hooks/useDependencyMessages';
import BulkPaymentToggle from './BulkPaymentToggle';
import PaymentDependencyMessage from './PaymentDependencyMessage';
import SettingDependencyMessage from './SettingDependencyMessage';
import SpinnerOverlay from '../../../../ReusableComponents/SpinnerOverlay';
import {
PaymentHooks,
@ -60,6 +59,13 @@ const PaymentMethodCard = ( {
const settingDependencies = useSettingDependencyState( methods );
const dependencyMessagesMap = useDependencyMessages(
methods,
paymentDependencies,
settingDependencies,
isDisabled
);
// Initialize the bulk toggle functionality.
const { allEnabled, toggleAllMethods, methodCount } =
usePaymentMethodsToggle( {
@ -86,37 +92,17 @@ const PaymentMethodCard = ( {
return <SpinnerOverlay asModal={ true } />;
}
// Process methods with dependencies.
// Process methods with dependencies from the pre-computed map.
const processedMethods = methods.map( ( method ) => {
const paymentDependency = paymentDependencies?.[ method.id ];
const settingDependency = settingDependencies?.[ method.id ];
let dependencyMessage = null;
let isMethodDisabled = method.isDisabled || isDisabled;
if ( paymentDependency ) {
dependencyMessage = (
<PaymentDependencyMessage
parentId={ paymentDependency.parentId }
parentName={ paymentDependency.parentName }
/>
);
isMethodDisabled = true;
} else if ( settingDependency?.isDisabled ) {
dependencyMessage = (
<SettingDependencyMessage
settingId={ settingDependency.settingId }
requiredValue={ settingDependency.requiredValue }
methodId={ method.id }
/>
);
isMethodDisabled = true;
}
const dependencyInfo = dependencyMessagesMap[ method.id ] || {};
return {
...method,
isDisabled: isMethodDisabled,
disabledMessage: dependencyMessage,
isDisabled:
dependencyInfo.isMethodDisabled ||
method.isDisabled ||
isDisabled,
disabledMessage: dependencyInfo.dependencyMessage,
};
} );

View file

@ -0,0 +1,49 @@
import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { scrollAndHighlight } from '../../../../../utils/scrollAndHighlight';
/**
* Component to display a payment method value dependency message
*
* @param {Object} props - Component props
* @param {string} props.dependentMethodId - ID of the dependent payment method
* @param {string} props.dependentMethodName - Display name of the dependent payment method
* @param {boolean} props.requiredValue - Required value (enabled/disabled state) for the dependent method
* @return {JSX.Element} The formatted message with link
*/
const PaymentMethodValueDependencyMessage = ( {
dependentMethodId,
dependentMethodName,
requiredValue,
} ) => {
const displayName = dependentMethodName || dependentMethodId;
// Determine appropriate message template based on the required value
const template = requiredValue
? __(
'Enable <methodLink /> to use this method.',
'woocommerce-paypal-payments'
)
: __(
'Disable <methodLink /> to use this method.',
'woocommerce-paypal-payments'
);
return createInterpolateElement( template, {
methodLink: (
<strong>
<a
href="#"
onClick={ ( e ) => {
e.preventDefault();
scrollAndHighlight( dependentMethodId );
} }
>
{ displayName }
</a>
</strong>
),
} );
};
export default PaymentMethodValueDependencyMessage;

View file

@ -0,0 +1,25 @@
import { __ } from '@wordpress/i18n';
import { ControlToggleButton } from '../../../../../ReusableComponents/Controls';
import { SettingsHooks } from '../../../../../../data';
import SettingsBlock from '../../../../../ReusableComponents/SettingsBlock';
const StayUpdated = () => {
const { stayUpdated, setStayUpdated } = SettingsHooks.useSettings();
return (
<SettingsBlock className="ppcp--pay-now-experience">
<ControlToggleButton
label={ __( 'Stay Updated', 'woocommerce-paypal-payments' ) }
description={ __(
'Get the latest PayPal features and capabilities as they are released. When the extension is updated, new features, payment methods, styling options, and more will automatically update.',
'woocommerce-paypal-payments'
) }
onChange={ setStayUpdated }
value={ stayUpdated }
/>
</SettingsBlock>
);
};
export default StayUpdated;

View file

@ -5,6 +5,7 @@ import OrderIntent from './Blocks/OrderIntent';
import SavePaymentMethods from './Blocks/SavePaymentMethods';
import InvoicePrefix from './Blocks/InvoicePrefix';
import PayNowExperience from './Blocks/PayNowExperience';
import StayUpdated from './Blocks/StayUpdated';
const CommonSettings = ( { ownBradOnly } ) => (
<SettingsCard
@ -20,6 +21,7 @@ const CommonSettings = ( { ownBradOnly } ) => (
<OrderIntent />
<SavePaymentMethods ownBradOnly={ ownBradOnly } />
<PayNowExperience />
<StayUpdated />
</SettingsCard>
);

View file

@ -2,15 +2,17 @@ import { __ } from '@wordpress/i18n';
import { useCallback } from '@wordpress/element';
import { CommonHooks, OnboardingHooks, PaymentHooks } from '../../../../data';
import { useActiveModal } from '../../../../data/common/hooks';
import { useActiveModal, useWooSettings } from '../../../../data/common/hooks';
import Modal from '../Components/Payment/Modal';
import PaymentMethodCard from '../Components/Payment/PaymentMethodCard';
import { useFeatures } from '../../../../data/features/hooks';
const TabPaymentMethods = () => {
const methods = PaymentHooks.usePaymentMethods();
const store = PaymentHooks.useStore();
const { setPersistent, changePaymentSettings } = store;
const { activeModal, setActiveModal } = useActiveModal();
const { features } = useFeatures();
// Get all methods as a map for dependency checking
const methodsMap = {};
@ -52,12 +54,31 @@ const TabPaymentMethods = () => {
);
const merchant = CommonHooks.useMerchant();
const { storeCountry } = useWooSettings();
const { canUseCardPayments } = OnboardingHooks.useFlags();
const showCardPayments =
methods.cardPayment.length > 0 &&
merchant.isBusinessSeller &&
canUseCardPayments;
canUseCardPayments &&
// Show ACDC if the merchant has the feature enabled in PayPal account.
features.some(
( feature ) =>
feature.id === 'advanced_credit_and_debit_cards' &&
feature.enabled
);
// Hide BCDC for all countries except Mexico when ACDC is turned on.
const filteredPayPalMethods = methods.paypal.filter(
( method ) =>
method.id !== 'ppcp-card-button-gateway' ||
storeCountry === 'MX' ||
! features.some(
( feature ) =>
feature.id === 'advanced_credit_and_debit_cards' &&
feature.enabled === true
)
);
const showApms = methods.apm.length > 0 && merchant.isBusinessSeller;
return (
@ -70,7 +91,7 @@ const TabPaymentMethods = () => {
'woocommerce-paypal-payments'
) }
icon="icon-checkout-standard.svg"
methods={ methods.paypal }
methods={ filteredPayPalMethods }
onTriggerModal={ setActiveModal }
methodsMap={ methodsMap }
/>

View file

@ -162,14 +162,15 @@ export const useNavigationState = () => {
};
};
export const useDetermineProducts = ( ownBrandOnly ) => {
export const useDetermineProducts = ( ownBrandOnly, storeCountry ) => {
return useSelect(
( select ) => {
return select( STORE_NAME ).determineProductsAndCaps(
ownBrandOnly
ownBrandOnly,
storeCountry
);
},
[ ownBrandOnly ]
[ ownBrandOnly, storeCountry ]
);
};

View file

@ -35,9 +35,14 @@ export const flags = ( state ) => {
*
* @param {{}} state
* @param {boolean} ownBrandOnly
* @param {string} storeCountry
* @return {{products:string[], options:{}}} The ISU products, based on choices made in the onboarding wizard.
*/
export const determineProductsAndCaps = ( state, ownBrandOnly ) => {
export const determineProductsAndCaps = (
state,
ownBrandOnly,
storeCountry
) => {
/**
* An array of product-names that are used to build an onboarding URL via the
* PartnerReferrals API. To avoid confusion with the "products" property from the
@ -80,7 +85,7 @@ export const determineProductsAndCaps = ( state, ownBrandOnly ) => {
if ( canUseVaulting ) {
apiModules.push( PAYPAL_PRODUCTS.VAULTING );
}
} else if ( isCasualSeller ) {
} else if ( isCasualSeller || 'MX' === storeCountry ) {
/**
* Branch 2: Merchant has no business.
* The store uses the Express-checkout product.

View file

@ -63,6 +63,7 @@ const useHooks = () => {
const [ payNowExperience, setPayNowExperience ] =
usePersistent( 'enablePayNow' );
const [ logging, setLogging ] = usePersistent( 'enableLogging' );
const [ stayUpdated, setStayUpdated ] = usePersistent( 'stayUpdated' );
const [ disabledCards, setDisabledCards ] =
usePersistent( 'disabledCards' );
@ -84,6 +85,8 @@ const useHooks = () => {
setPayNowExperience,
logging,
setLogging,
stayUpdated,
setStayUpdated,
subtotalAdjustment,
setSubtotalAdjustment,
brandName,
@ -130,6 +133,8 @@ export const useSettings = () => {
setPayNowExperience,
logging,
setLogging,
stayUpdated,
setStayUpdated,
subtotalAdjustment,
setSubtotalAdjustment,
brandName,
@ -161,6 +166,8 @@ export const useSettings = () => {
setPayNowExperience,
logging,
setLogging,
stayUpdated,
setStayUpdated,
subtotalAdjustment,
setSubtotalAdjustment,
brandName,

View file

@ -43,6 +43,7 @@ const defaultPersistent = Object.freeze( {
saveCardDetails: false, // Enable card vaulting
enablePayNow: false, // Enable Pay Now experience
enableLogging: false, // Enable debug logging
stayUpdated: false, // Enable to get the latest PayPal features
// String arrays.
disabledCards: [], // Disabled credit card types

View file

@ -0,0 +1,83 @@
// hooks/useDependencyMessages.js
import { useMemo } from '@wordpress/element';
import PaymentDependencyMessage from '../Components/Screens/Settings/Components/Payment/PaymentDependencyMessage';
import PaymentMethodValueDependencyMessage from '../Components/Screens/Settings/Components/Payment/PaymentMethodValueDependencyMessage';
import SettingDependencyMessage from '../Components/Screens/Settings/Components/Payment/SettingDependencyMessage';
/**
* Hook to process dependency messages for all methods
*
* @param {Array} methods - List of payment methods
* @param {Object} paymentDependencies - Payment method dependencies
* @param {Object} settingDependencies - Setting dependencies
* @param {boolean} isDisabled - Whether methods are globally disabled
* @return {Object} Map of method IDs to their dependency messages and disabled states
*/
const useDependencyMessages = (
methods,
paymentDependencies,
settingDependencies,
isDisabled = false
) => {
return useMemo( () => {
const result = {};
if ( ! methods || ! methods.length ) {
return result;
}
// Process each method once to create their dependency messages.
methods.forEach( ( method ) => {
if ( ! method || ! method.id ) {
return;
}
let dependencyMessage = null;
let isMethodDisabled = method.isDisabled || isDisabled;
// Check payment dependencies
const dependency = paymentDependencies?.[ method.id ];
if ( dependency ) {
if ( dependency.type === 'parent' ) {
dependencyMessage = (
<PaymentDependencyMessage
parentId={ dependency.parentId }
parentName={ dependency.parentName }
/>
);
} else if ( dependency.type === 'value' ) {
dependencyMessage = (
<PaymentMethodValueDependencyMessage
dependentMethodId={ dependency.dependentId }
dependentMethodName={ dependency.dependentName }
requiredValue={ dependency.requiredValue }
/>
);
}
isMethodDisabled = true;
}
// Check setting dependencies
else if ( settingDependencies?.[ method.id ]?.isDisabled ) {
const settingDependency = settingDependencies[ method.id ];
dependencyMessage = (
<SettingDependencyMessage
settingId={ settingDependency.settingId }
requiredValue={ settingDependency.requiredValue }
methodId={ method.id }
/>
);
isMethodDisabled = true;
}
// Store the results for this method
result[ method.id ] = {
dependencyMessage,
isMethodDisabled,
};
} );
return result;
}, [ methods, paymentDependencies, settingDependencies, isDisabled ] );
};
export default useDependencyMessages;

View file

@ -31,9 +31,11 @@ export const useHandleOnboardingButton = ( isSandbox ) => {
const { onboardingUrl } = isSandbox
? CommonHooks.useSandbox()
: CommonHooks.useProduction();
const { ownBrandOnly } = CommonHooks.useWooSettings();
const { products, options } =
OnboardingHooks.useDetermineProducts( ownBrandOnly );
const { ownBrandOnly, storeCountry } = CommonHooks.useWooSettings();
const { products, options } = OnboardingHooks.useDetermineProducts(
ownBrandOnly,
storeCountry
);
const { startActivity } = CommonHooks.useBusyState();
const { authenticateWithOAuth } = CommonHooks.useAuthentication();
const [ onboardingUrlState, setOnboardingUrl ] = useState( '' );

View file

@ -6,15 +6,13 @@ import { useSelect } from '@wordpress/data';
/**
* Gets the display name for a parent payment method
*
* @param {string} parentId - ID of the parent payment method
* @param {string} methodId - ID of the payment method
* @param {Object} methodsMap - Map of all payment methods by ID
* @return {string} The display name to use for the parent method
* @return {string} The display name of the method
*/
const getParentMethodName = ( parentId, methodsMap ) => {
const parentMethod = methodsMap[ parentId ];
return parentMethod
? parentMethod.itemTitle || parentMethod.title || ''
: '';
const getMethodName = ( methodId, methodsMap ) => {
const method = methodsMap[ methodId ];
return method ? method.itemTitle || method.title || '' : '';
};
/**
@ -38,7 +36,51 @@ const findDisabledParents = ( method, methodsMap ) => {
};
/**
* Hook to evaluate payment method dependencies
* Checks if method should be disabled due to value dependencies
*
* @param {Object} method - The payment method to check
* @param {Object} methodsMap - Map of all payment methods by ID
* @return {Object|null} Value dependency info if should be disabled, null otherwise
*/
const checkValueDependencies = ( method, methodsMap ) => {
const valueDependencies = method.depends_on_payment_methods_values;
if ( ! valueDependencies ) {
return null;
}
// Check each dependency against the actual state of the dependent method.
for ( const [ dependentId, requiredValue ] of Object.entries(
valueDependencies
) ) {
const dependent = methodsMap[ dependentId ];
if ( ! dependent ) {
continue;
}
// Example: card-button-gateway depends on credit-card-gateway being FALSE.
// So if credit-card-gateway is TRUE (enabled), card-button-gateway should be disabled.
// If the dependency requires a method to be false but it's enabled (or vice versa).
if (
typeof requiredValue === 'boolean' &&
dependent.enabled !== requiredValue
) {
// This dependency is violated - the dependent method is in the wrong state.
return {
dependentId,
dependentName: getMethodName( dependentId, methodsMap ),
requiredValue,
};
}
}
return null;
};
/**
* Hook to evaluate all payment method dependencies
*
* @param {Array} methods - List of payment methods
* @param {Object} methodsMap - Map of payment methods by ID
@ -51,6 +93,7 @@ const usePaymentDependencyState = ( methods, methodsMap ) => {
if ( methods && methodsMap && Object.keys( methodsMap ).length > 0 ) {
methods.forEach( ( method ) => {
if ( method && method.id ) {
// Check regular parent-child dependencies first.
const disabledParents = findDisabledParents(
method,
methodsMap
@ -59,18 +102,32 @@ const usePaymentDependencyState = ( methods, methodsMap ) => {
if ( disabledParents.length > 0 ) {
const parentId = disabledParents[ 0 ];
result[ method.id ] = {
type: 'parent',
isDisabled: true,
parentId,
parentName: getParentMethodName(
parentId,
methodsMap
),
parentName: getMethodName( parentId, methodsMap ),
};
return; // Skip other checks if already disabled.
}
// Check value dependencies.
const valueDependency = checkValueDependencies(
method,
methodsMap
);
if ( valueDependency ) {
result[ method.id ] = {
type: 'value',
isDisabled: true,
dependentId: valueDependency.dependentId,
dependentName: valueDependency.dependentName,
requiredValue: valueDependency.requiredValue,
};
}
}
} );
}
return result;
}, [ methods, methodsMap ] );
};

View file

@ -120,8 +120,12 @@ return array(
return new PaymentSettings();
},
'settings.data.settings' => static function ( ContainerInterface $container ) : SettingsModel {
$environment = $container->get( 'settings.environment' );
assert( $environment instanceof Environment );
return new SettingsModel(
$container->get( 'settings.service.sanitizer' )
$container->get( 'settings.service.sanitizer' ),
$environment->is_sandbox() ? $container->get( 'wcgateway.settings.invoice-prefix-random' ) : $container->get( 'wcgateway.settings.invoice-prefix' )
);
},
'settings.data.paylater-messaging' => static function ( ContainerInterface $container ) : array {
@ -481,12 +485,13 @@ return array(
assert( $general_settings instanceof GeneralSettings );
return array(
'apple_pay' => ( $features['apple_pay']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
'google_pay' => ( $features['google_pay']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
'acdc' => ( $features['advanced_credit_and_debit_cards']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
'apm' => $features['alternative_payment_methods']['enabled'] ?? false,
'paylater' => $features['pay_later_messaging']['enabled'] ?? false,
'apple_pay' => ( $features['apple_pay']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
'google_pay' => ( $features['google_pay']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
'acdc' => ( $features['advanced_credit_and_debit_cards']['enabled'] ?? false ) && ! $general_settings->own_brand_only(),
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
'apm' => $features['alternative_payment_methods']['enabled'] ?? false,
'paylater' => $features['pay_later_messaging']['enabled'] ?? false,
'installments' => $features['installments']['enabled'] ?? false,
);
},
@ -545,6 +550,7 @@ return array(
$container->get( 'googlepay.eligible' ) && $capabilities['acdc'] && ! $capabilities['google_pay'], // Add Google Pay to your account.
$container->get( 'applepay.eligible' ) && $capabilities['apple_pay'] && ! $gateways['apple_pay'], // Enable Apple Pay.
$container->get( 'googlepay.eligible' ) && $capabilities['google_pay'] && ! $gateways['google_pay'],
! $capabilities['installments'] && 'MX' === $container->get( 'settings.data.general' )->get_merchant_country() // Enable Installments for Mexico.
);
},
'settings.rest.features' => static function ( ContainerInterface $container ) : FeaturesRestEndpoint {
@ -568,19 +574,22 @@ return array(
);
// Merchant capabilities, serve to show active or inactive badge and buttons.
$capabilities = array(
'apple_pay' => $features['apple_pay']['enabled'] ?? false,
'google_pay' => $features['google_pay']['enabled'] ?? false,
'acdc' => $features['advanced_credit_and_debit_cards']['enabled'] ?? false,
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
'apple_pay' => $features['apple_pay']['enabled'] ?? false,
'google_pay' => $features['google_pay']['enabled'] ?? false,
'acdc' => $features['advanced_credit_and_debit_cards']['enabled'] ?? false,
'save_paypal' => $features['save_paypal_and_venmo']['enabled'] ?? false,
'alternative_payment_methods' => $features['alternative_payment_methods']['enabled'] ?? false,
'installments' => $features['installments']['enabled'] ?? false,
);
$merchant_capabilities = array(
'save_paypal' => $capabilities['save_paypal'], // Save PayPal and Venmo eligibility.
'acdc' => $capabilities['acdc'] && ! $gateways['card-button'], // Advanced credit and debit cards eligibility.
'apm' => ! $gateways['card-button'], // Alternative payment methods eligibility.
'google_pay' => $capabilities['acdc'] && $capabilities['google_pay'], // Google Pay eligibility.
'apple_pay' => $capabilities['acdc'] && $capabilities['apple_pay'], // Apple Pay eligibility.
'pay_later' => $capabilities['acdc'] && ! $gateways['card-button'], // Pay Later eligibility.
'save_paypal' => $capabilities['save_paypal'], // Save PayPal and Venmo eligibility.
'acdc' => $capabilities['acdc'], // Advanced credit and debit cards eligibility.
'apm' => $capabilities['alternative_payment_methods'], // Alternative payment methods eligibility.
'google_pay' => $capabilities['acdc'] && $capabilities['google_pay'], // Google Pay eligibility.
'apple_pay' => $capabilities['acdc'] && $capabilities['apple_pay'], // Apple Pay eligibility.
'pay_later' => $capabilities['acdc'] && ! $gateways['card-button'], // Pay Later eligibility.
'installments' => $capabilities['installments'], // Installments eligibility.
);
return new FeaturesDefinition(
$container->get( 'settings.service.features_eligibilities' ),
@ -606,6 +615,7 @@ return array(
$container->get( 'googlepay.eligibility.check' ), // Google Pay eligibility.
$container->get( 'applepay.eligibility.check' ), // Apple Pay eligibility.
$pay_later_eligible, // Pay Later eligibility.
'MX' === $container->get( 'settings.data.general' )->get_merchant_country(), // Installments eligibility.
);
},
'settings.service.todos_sorting' => static function ( ContainerInterface $container ) : TodosSortingAndFilteringService {

View file

@ -317,6 +317,34 @@ class FeaturesDefinition {
),
),
),
'installments' => array(
'title' => __( 'Installments', 'woocommerce-paypal-payments' ),
'description' =>
__( 'Allow your customers to pay in installments without interest while you receive the full payment.*', 'woocommerce-paypal-payments' ) .
'<p>' . __( 'Activate your Installments without interest with PayPal.', 'woocommerce-paypal-payments' ) . '</p>' .
'<p>' . sprintf(
/* translators: %s: Link to terms and conditions */
__( '*You will receive the full payment minus the applicable PayPal fee. See %s.', 'woocommerce-paypal-payments' ),
'<a href="https://www.paypal.com/mx/webapps/mpp/merchant-fees">' . __( 'terms and conditions', 'woocommerce-paypal-payments' ) . '</a>'
) . '</p>',
'enabled' => $this->merchant_capabilities['installments'],
'buttons' => array(
array(
'type' => 'secondary',
'text' => __( 'Configure', 'woocommerce-paypal-payments' ),
'url' => 'https://www.paypal.com/businessmanage/preferences/installmentplan',
'showWhen' => 'enabled',
'class' => 'small-button',
),
array(
'type' => 'secondary',
'text' => __( 'Sign up', 'woocommerce-paypal-payments' ),
'url' => 'https://www.paypal.com/businessmanage/preferences/installmentplan',
'showWhen' => 'disabled',
'class' => 'small-button',
),
),
),
);
}
}

View file

@ -107,6 +107,27 @@ class PaymentMethodsDependenciesDefinition {
);
}
/**
* Get payment method value dependencies for a specific method
*
* @return array Value dependencies for the method or empty array if none exist
*/
public function get_payment_method_value_dependencies(): array {
$dependencies = array(
CardButtonGateway::ID => array(
CreditCardGateway::ID => false,
),
CreditCardGateway::ID => array(
CardButtonGateway::ID => false,
),
);
return apply_filters(
'woocommerce_paypal_payments_method_value_dependencies',
$dependencies
);
}
/**
* Get method setting dependencies
*
@ -139,6 +160,15 @@ class PaymentMethodsDependenciesDefinition {
$method['depends_on_payment_methods'] = $payment_method_dependencies;
}
// Add payment method value dependency info if applicable.
$all_payment_method_value_dependencies = $this->get_payment_method_value_dependencies();
// Only add dependencies that directly apply to this method.
if ( isset( $all_payment_method_value_dependencies[ $method_id ] ) ) {
// Direct dependencies on other method values.
$method['depends_on_payment_methods_values'] = $all_payment_method_value_dependencies[ $method_id ];
}
// Check if this method has setting dependencies.
$method_setting_dependencies = $this->get_method_setting_dependencies( $method_id );
if ( ! empty( $method_setting_dependencies ) ) {

View file

@ -217,6 +217,16 @@ class TodosDefinition {
),
'priority' => 12,
),
'enable_installments' => array(
'title' => __( 'Enable Installments', 'woocommerce-paypal-payments' ),
'description' => __( 'Allow your customers to pay in installments without interest while you receive the full payment in a single transaction', 'woocommerce-paypal-payments' ),
'isEligible' => $eligibility_checks['enable_installments'],
'action' => array(
'type' => 'external',
'url' => 'https://www.paypal.com/businessmanage/preferences/installmentplan',
),
'priority' => 13,
),
);
}
}

View file

@ -54,14 +54,24 @@ class SettingsModel extends AbstractDataModel {
*/
protected DataSanitizer $sanitizer;
/**
* Invoice prefix.
*
* @var string
*/
private string $invoice_prefix;
/**
* Constructor.
*
* @param DataSanitizer $sanitizer Data sanitizer service.
* @param string $invoice_prefix Invoice prefix.
* @throws RuntimeException If the OPTION_KEY is not defined in the child class.
*/
public function __construct( DataSanitizer $sanitizer ) {
$this->sanitizer = $sanitizer;
public function __construct( DataSanitizer $sanitizer, string $invoice_prefix ) {
$this->sanitizer = $sanitizer;
$this->invoice_prefix = $invoice_prefix;
parent::__construct();
}
@ -73,7 +83,7 @@ class SettingsModel extends AbstractDataModel {
protected function get_defaults() : array {
return array(
// Free-form string values.
'invoice_prefix' => '',
'invoice_prefix' => $this->invoice_prefix,
'brand_name' => '',
'soft_descriptor' => '',
@ -90,6 +100,7 @@ class SettingsModel extends AbstractDataModel {
'save_card_details' => false,
'enable_pay_now' => false,
'enable_logging' => false,
'stay_updated' => true,
// Array of string values.
'disabled_cards' => array(),

View file

@ -193,6 +193,10 @@ class PaymentRestEndpoint extends RestEndpoint {
$gateway_settings[ $key ]['depends_on_payment_methods'] = $payment_method['depends_on_payment_methods'];
}
if ( isset( $payment_method['depends_on_payment_methods_values'] ) ) {
$gateway_settings[ $key ]['depends_on_payment_methods_values'] = $payment_method['depends_on_payment_methods_values'];
}
if ( isset( $payment_method['depends_on_settings'] ) ) {
$gateway_settings[ $key ]['depends_on_settings'] = $payment_method['depends_on_settings'];
}

View file

@ -86,6 +86,10 @@ class SettingsRestEndpoint extends RestEndpoint {
'js_name' => 'enableLogging',
'sanitize' => 'to_boolean',
),
'stay_updated' => array(
'js_name' => 'stayUpdated',
'sanitize' => 'to_boolean',
),
'disabled_cards' => array(
'js_name' => 'disabledCards',
),

View file

@ -59,6 +59,13 @@ class FeaturesEligibilityService {
*/
private bool $is_pay_later_eligible;
/**
* Whether Installments is eligible.
*
* @var bool
*/
private bool $is_installments_eligible;
/**
* Constructor.
*
@ -68,6 +75,7 @@ class FeaturesEligibilityService {
* @param callable $check_google_pay_eligible If Google Pay is eligible.
* @param callable $check_apple_pay_eligible If Apple Pay is eligible.
* @param bool $is_pay_later_eligible If Pay Later is eligible.
* @param bool $is_installments_eligible If Installments is eligible.
*/
public function __construct(
bool $is_save_paypal_eligible,
@ -75,7 +83,8 @@ class FeaturesEligibilityService {
bool $is_apm_eligible,
callable $check_google_pay_eligible,
callable $check_apple_pay_eligible,
bool $is_pay_later_eligible
bool $is_pay_later_eligible,
bool $is_installments_eligible
) {
$this->is_save_paypal_eligible = $is_save_paypal_eligible;
$this->check_acdc_eligible = $check_acdc_eligible;
@ -83,6 +92,7 @@ class FeaturesEligibilityService {
$this->check_google_pay_eligible = $check_google_pay_eligible;
$this->check_apple_pay_eligible = $check_apple_pay_eligible;
$this->is_pay_later_eligible = $is_pay_later_eligible;
$this->is_installments_eligible = $is_installments_eligible;
}
/**
@ -98,6 +108,7 @@ class FeaturesEligibilityService {
'google_pay' => $this->check_google_pay_eligible,
'apple_pay' => $this->check_apple_pay_eligible,
'pay_later' => fn() => $this->is_pay_later_eligible,
'installments' => fn() => $this->is_installments_eligible,
);
}
}

View file

@ -269,11 +269,14 @@ class SettingsDataManager {
// Enable BCDC for business sellers without ACDC.
$this->payment_methods->toggle_method_state( CardButtonGateway::ID, true );
}
// Enable all APM methods.
foreach ( $methods_apm as $method ) {
$this->payment_methods->toggle_method_state( $method['id'], true );
}
/**
* Allow plugins to modify apm payment gateway states before saving.
*
* @param PaymentSettings $payment_methods The payment methods object.
* @param PaymentSettings $methods_apm List of APM methods.
* @param ConfigurationFlagsDTO $flags Configuration flags that determine which gateways to enable.
*/
do_action( 'woocommerce_paypal_payments_toggle_payment_gateways_apms', $this->payment_methods, $methods_apm, $flags );
}
/**

View file

@ -122,6 +122,13 @@ class TodosEligibilityService {
*/
private bool $is_enable_google_pay_eligible;
/**
* Whether Enabling Installments is eligible.
*
* @var bool
*/
private bool $is_enable_installments_eligible;
/**
* Constructor.
*
@ -140,6 +147,7 @@ class TodosEligibilityService {
* @param bool $is_google_pay_eligible Whether Google Pay is eligible.
* @param bool $is_enable_apple_pay_eligible Whether enabling Apple Pay is eligible.
* @param bool $is_enable_google_pay_eligible Whether enabling Google Pay is eligible.
* @param bool $is_enable_installments_eligible Whether enabling Installments is eligible.
*/
public function __construct(
bool $is_fastlane_eligible,
@ -156,7 +164,8 @@ class TodosEligibilityService {
bool $is_apple_pay_eligible,
bool $is_google_pay_eligible,
bool $is_enable_apple_pay_eligible,
bool $is_enable_google_pay_eligible
bool $is_enable_google_pay_eligible,
bool $is_enable_installments_eligible
) {
$this->is_fastlane_eligible = $is_fastlane_eligible;
$this->is_pay_later_messaging_eligible = $is_pay_later_messaging_eligible;
@ -173,6 +182,7 @@ class TodosEligibilityService {
$this->is_google_pay_eligible = $is_google_pay_eligible;
$this->is_enable_apple_pay_eligible = $is_enable_apple_pay_eligible;
$this->is_enable_google_pay_eligible = $is_enable_google_pay_eligible;
$this->is_enable_installments_eligible = $is_enable_installments_eligible;
}
/**
@ -197,6 +207,7 @@ class TodosEligibilityService {
'add_google_pay' => fn() => $this->is_google_pay_eligible,
'enable_apple_pay' => fn() => $this->is_enable_apple_pay_eligible,
'enable_google_pay' => fn() => $this->is_enable_google_pay_eligible,
'enable_installments' => fn() => $this->is_enable_installments_eligible,
);
}
}

View file

@ -13,8 +13,10 @@ use WC_Payment_Gateway;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BancontactGateway;
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\BlikGateway;
@ -321,11 +323,13 @@ class SettingsModule implements ServiceModule, ExecutableModule {
// Unset BCDC if merchant is eligible for ACDC and country is eligible for card fields.
$card_fields_eligible = $container->get( 'card-fields.eligible' );
if ( $dcc_product_status->is_active() && $card_fields_eligible ) {
unset( $payment_methods[ CardButtonGateway::ID ] );
} else {
// For non-ACDC regions unset ACDC.
unset( $payment_methods[ CreditCardGateway::ID ] );
if ( 'MX' !== $container->get( 'api.shop.country' ) ) {
if ( $dcc_product_status->is_active() && $card_fields_eligible ) {
unset( $payment_methods[ CardButtonGateway::ID ] );
} else {
// For non-ACDC regions unset ACDC.
unset( $payment_methods[ CreditCardGateway::ID ] );
}
}
// Unset Venmo when store location is not United States.
@ -358,6 +362,18 @@ class SettingsModule implements ServiceModule, ExecutableModule {
unset( $payment_methods[ PayUponInvoiceGateway::ID ] );
}
// Unset all APMs other than OXXO for Mexico.
if ( 'MX' === $merchant_country ) {
unset( $payment_methods[ BancontactGateway::ID ] );
unset( $payment_methods[ BlikGateway::ID ] );
unset( $payment_methods[ EPSGateway::ID ] );
unset( $payment_methods[ IDealGateway::ID ] );
unset( $payment_methods[ MyBankGateway::ID ] );
unset( $payment_methods[ P24Gateway::ID ] );
unset( $payment_methods[ TrustlyGateway::ID ] );
unset( $payment_methods[ MultibancoGateway::ID ] );
}
return $payment_methods;
}
);
@ -539,11 +555,65 @@ class SettingsModule implements ServiceModule, ExecutableModule {
$payment_methods->toggle_method_state( AxoGateway::ID, true );
}
}
$general_settings = $container->get( 'settings.data.general' );
assert( $general_settings instanceof GeneralSettings );
$merchant_data = $general_settings->get_merchant_data();
$merchant_country = $merchant_data->merchant_country;
// Disable all extended checkout card methods if the store is in Mexico.
if ( 'MX' === $merchant_country ) {
$payment_methods->toggle_method_state( CreditCardGateway::ID, false );
$payment_methods->toggle_method_state( ApplePayGateway::ID, false );
$payment_methods->toggle_method_state( GooglePayGateway::ID, false );
}
},
10,
2
);
// Enable APMs after onboarding if the country is compatible.
add_action(
'woocommerce_paypal_payments_toggle_payment_gateways_apms',
function ( PaymentSettings $payment_methods, array $methods_apm, ConfigurationFlagsDTO $flags ) use ( $container ) {
$general_settings = $container->get( 'settings.data.general' );
assert( $general_settings instanceof GeneralSettings );
$merchant_data = $general_settings->get_merchant_data();
$merchant_country = $merchant_data->merchant_country;
// Enable all APM methods.
foreach ( $methods_apm as $method ) {
if ( $flags->use_card_payments === false ) {
$payment_methods->toggle_method_state( $method['id'], $flags->use_card_payments );
continue;
}
// Skip PayUponInvoice if merchant is not in Germany.
if ( PayUponInvoiceGateway::ID === $method['id'] && 'DE' !== $merchant_country ) {
continue;
}
// For OXXO: enable ONLY if merchant is in Mexico.
if ( OXXO::ID === $method['id'] ) {
if ( 'MX' === $merchant_country ) {
$payment_methods->toggle_method_state( $method['id'], true );
}
continue;
}
// For all other APMs: enable only if merchant is NOT in Mexico.
if ( 'MX' !== $merchant_country ) {
$payment_methods->toggle_method_state( $method['id'], true );
}
}
},
10,
3
);
// Toggle payment gateways after onboarding based on flags.
add_action(
'woocommerce_paypal_payments_sync_gateways',

View file

@ -167,7 +167,9 @@ class VaultedCreditCardHandler {
array( $purchase_unit ),
$shipping_preference,
$payer,
$selected_token
'',
array(),
$selected_token->to_payment_source()
);
$this->add_paypal_meta( $wc_order, $order, $this->environment );

View file

@ -13,7 +13,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
@ -67,6 +67,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\InstallmentsProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
@ -397,7 +398,6 @@ return array(
'ML',
'MH',
'MR',
'YT',
'FM',
'MN',
'ME',
@ -569,7 +569,8 @@ return array(
$order_helper,
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'api.factory.payer' ),
$container->get( 'api.factory.shipping-preference' )
$container->get( 'api.factory.shipping-preference' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor {
@ -691,7 +692,7 @@ return array(
assert( $dcc_configuration instanceof CardPaymentsConfiguration );
$fields = array(
'checkout_settings_heading' => array(
'checkout_settings_heading' => array(
'heading' => __( 'Standard Payments Settings', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array(
@ -701,7 +702,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'title' => array(
'title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'description' => __(
@ -717,7 +718,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'dcc_block_checkout_notice' => array(
'dcc_block_checkout_notice' => array(
'heading' => '',
'html' => $container->get( 'wcgateway.notice.checkout-blocks' ),
'type' => 'ppcp-html',
@ -728,7 +729,7 @@ return array(
'requirements' => array( 'dcc' ),
'gateway' => 'dcc',
),
'dcc_enabled' => array(
'dcc_enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ),
'desc_tip' => true,
'description' => __( 'Once enabled, the Credit Card option will show up in the checkout.', 'woocommerce-paypal-payments' ),
@ -743,7 +744,7 @@ return array(
State::STATE_ONBOARDED,
),
),
'dcc_gateway_title' => array(
'dcc_gateway_title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'description' => __(
@ -760,7 +761,7 @@ return array(
),
'gateway' => 'dcc',
),
'description' => array(
'description' => array(
'title' => __( 'Description', 'woocommerce-paypal-payments' ),
'type' => 'text',
'desc_tip' => true,
@ -779,7 +780,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'intent' => array(
'intent' => array(
'title' => __( 'Intent', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
@ -801,7 +802,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'capture_on_status_change' => array(
'capture_on_status_change' => array(
'title' => __( 'Capture On Status Change', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@ -818,7 +819,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'capture_for_virtual_only' => array(
'capture_for_virtual_only' => array(
'title' => __( 'Capture Virtual-Only Orders ', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@ -835,7 +836,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'payee_preferred' => array(
'payee_preferred' => array(
'title' => __( 'Instant Payments ', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@ -852,7 +853,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'brand_name' => array(
'brand_name' => array(
'title' => __( 'Brand Name', 'woocommerce-paypal-payments' ),
'type' => 'text',
'default' => get_bloginfo( 'name' ),
@ -868,20 +869,20 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'landing_page' => array(
'landing_page' => array(
'title' => __( 'Landing Page', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
'default' => ApplicationContext::LANDING_PAGE_LOGIN,
'default' => ExperienceContext::LANDING_PAGE_LOGIN,
'desc_tip' => true,
'description' => __(
'Type of PayPal page to display.',
'woocommerce-paypal-payments'
),
'options' => array(
ApplicationContext::LANDING_PAGE_LOGIN => __( 'Login (PayPal account login)', 'woocommerce-paypal-payments' ),
ApplicationContext::LANDING_PAGE_BILLING => __( 'Billing (Non-PayPal account)', 'woocommerce-paypal-payments' ),
ExperienceContext::LANDING_PAGE_LOGIN => __( 'Login (PayPal account login)', 'woocommerce-paypal-payments' ),
ExperienceContext::LANDING_PAGE_GUEST_CHECKOUT => __( 'Billing (Non-PayPal account)', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
@ -890,7 +891,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'alternative_payment_methods' => array(
'alternative_payment_methods' => array(
'heading' => __( 'Alternative Payment Methods', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s, %2$s, %3$s and %4$s are a link tags.
@ -906,7 +907,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'disable_funding' => array(
'disable_funding' => array(
'title' => __( 'Disable Alternative Payment Methods', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@ -930,7 +931,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'card_billing_data_mode' => array(
'card_billing_data_mode' => array(
'title' => __( 'Send checkout billing data to card fields', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
@ -950,7 +951,7 @@ return array(
'requirements' => array(),
'gateway' => array( 'paypal', CardButtonGateway::ID ),
),
'allow_card_button_gateway' => array(
'allow_card_button_gateway' => array(
'title' => __( 'Create gateway for Standard Card Button', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
@ -964,7 +965,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'allow_local_apm_gateways' => array(
'allow_local_apm_gateways' => array(
'title' => __( 'Create gateway for alternative payment methods', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
@ -978,7 +979,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'disable_cards' => array(
'disable_cards' => array(
'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@ -1006,7 +1007,7 @@ return array(
),
'gateway' => 'dcc',
),
'card_icons' => array(
'card_icons' => array(
'title' => __( 'Show logo of the following credit cards', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@ -1045,7 +1046,7 @@ return array(
),
'gateway' => 'dcc',
),
'dcc_name_on_card' => array(
'dcc_name_on_card' => array(
'title' => __( 'Cardholder Name', 'woocommerce-paypal-payments' ),
'type' => 'select',
'default' => $dcc_configuration->show_name_on_card(),
@ -1059,7 +1060,7 @@ return array(
'gateway' => array( 'dcc', 'axo' ),
'requirements' => array( 'axo' ),
),
'3d_secure_heading' => array(
'3d_secure_heading' => array(
'heading' => __( '3D Secure', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'description' => wp_kses_post(
@ -1087,7 +1088,7 @@ return array(
),
'gateway' => 'dcc',
),
'3d_secure_contingency' => array(
'3d_secure_contingency' => array(
'title' => __( 'Contingency for 3D Secure', 'woocommerce-paypal-payments' ),
'type' => 'select',
'description' => sprintf(
@ -1115,7 +1116,7 @@ return array(
),
'gateway' => 'dcc',
),
'saved_payments_heading' => array(
'saved_payments_heading' => array(
'heading' => __( 'Saved Payments', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'description' => wp_kses_post(
@ -1142,7 +1143,7 @@ return array(
),
'gateway' => 'dcc',
),
'vault_enabled_dcc' => array(
'vault_enabled_dcc' => array(
'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
@ -1161,7 +1162,38 @@ return array(
'gateway' => 'dcc',
'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
),
'paypal_saved_payments' => array(
'mexico_installments' => array(
'heading' => __( 'Installments', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => 'paypal',
'description' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag. %3$s and %4$s are the opening and closing of HTML <p> tag.
__(
'Allow your customers to pay in installments without interest while you receive the full payment in a single transaction.*
%3$sTerms and conditions: *You will receive the full payment minus the applicable PayPal fee. See %1$sterms and conditions%2$s.%4$s',
'woocommerce-paypal-payments'
),
'<a href="https://www.paypal.com/mx/webapps/mpp/merchant-fees" target="_blank">',
'</a>',
'<p class="description">',
'</p>'
),
),
'mexico_installments_action_link' => array(
'title' => __( 'Activate your Installments', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-text',
'text' => '<a href="https://www.paypal.com/businessmanage/preferences/installmentplan" target="_blank" class="button ppcp-refresh-feature-status">' . esc_html__( 'Enable Installments', 'woocommerce-paypal-payments' ) . '</a>',
'screens' => array(
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => 'paypal',
),
'paypal_saved_payments' => array(
'heading' => __( 'Saved payments', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s, %2$s, %3$s and %4$s are a link tags.
@ -1179,8 +1211,8 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
'subscriptions_mode' => $container->get( 'wcgateway.settings.fields.subscriptions_mode' ),
'vault_enabled' => array(
'subscriptions_mode' => $container->get( 'wcgateway.settings.fields.subscriptions_mode' ),
'vault_enabled' => array(
'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
@ -1199,7 +1231,7 @@ return array(
'gateway' => 'paypal',
'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
),
'digital_wallet_heading' => array(
'digital_wallet_heading' => array(
'heading' => __( 'Digital Wallet Services', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'description' => wp_kses_post(
@ -1254,6 +1286,11 @@ return array(
$fields['disable_cards']['options'] = $card_options;
$fields['card_icons']['options'] = array_merge( $dark_versions, $card_options );
if ( $container->get( 'api.shop.country' ) !== 'MX' ) {
unset( $fields['mexico_installments'] );
unset( $fields['mexico_installments_action_link'] );
}
return $fields;
},
@ -1366,7 +1403,9 @@ return array(
return new CardPaymentsConfiguration(
$container->get( 'settings.connection-state' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'api.helpers.dccapplies' )
$container->get( 'api.helpers.dccapplies' ),
$container->get( 'wcgateway.helper.dcc-product-status' ),
$container->get( 'api.shop.country' )
);
},
@ -1467,6 +1506,15 @@ return array(
$container->get( 'api.helper.failure-registry' )
);
},
'wcgateway.installments-product-status' => static function ( ContainerInterface $container ): InstallmentsProductStatus {
return new InstallmentsProductStatus(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
$container->get( 'installments.status-cache' ),
$container->get( 'settings.flag.is-connected' ),
$container->get( 'api.helper.failure-registry' )
);
},
'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice {
return new PayUponInvoice(
$container->get( 'wcgateway.pay-upon-invoice-order-endpoint' ),
@ -1495,22 +1543,33 @@ return array(
$container->get( 'api.endpoint.order' ),
$container->get( 'api.factory.purchase-unit' ),
$container->get( 'api.factory.shipping-preference' ),
$container->get( 'wcgateway.builder.experience-context' ),
$container->get( 'wcgateway.url' ),
$container->get( 'wcgateway.transaction-url-provider' ),
$container->get( 'settings.environment' ),
$container->get( 'woocommerce.logger.woocommerce' )
);
},
'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool {
'wcgateway.logging.is-enabled' => static function ( ContainerInterface $container ) : bool {
$settings = $container->get( 'wcgateway.settings' );
// Check if logging was enabled in plugin settings.
$is_enabled = $settings->has( 'logging_enabled' ) && $settings->get( 'logging_enabled' );
// If not enabled, check if plugin is in onboarding mode.
if ( ! $is_enabled ) {
$state = $container->get( 'settings.connection-state' );
assert( $state instanceof ConnectionState );
$is_enabled = $state->is_onboarding();
}
/**
* Whether the logging of the plugin errors/events is enabled.
*
* @param bool $is_enabled Whether the logging is enabled.
*/
return apply_filters(
'woocommerce_paypal_payments_is_logging_enabled',
$settings->has( 'logging_enabled' ) && $settings->get( 'logging_enabled' )
);
return apply_filters( 'woocommerce_paypal_payments_is_logging_enabled', $is_enabled );
},
'wcgateway.use-place-order-button' => function ( ContainerInterface $container ) : bool {
@ -1776,6 +1835,9 @@ return array(
'pui.status-cache' => static function( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-paypal-pui-status-cache' );
},
'installments.status-cache' => static function( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-paypal-installments-status-cache' );
},
'dcc.status-cache' => static function( ContainerInterface $container ): Cache {
return new Cache( 'ppcp-paypal-dcc-status-cache' );
},
@ -1959,6 +2021,49 @@ return array(
return new TaskRegistrar();
},
'wcgateway.settings.wc-tasks.pay-later-task-config' => static function( ContainerInterface $container ): array {
$section_id = PayPalGateway::ID;
$pay_later_tab_id = Settings::PAY_LATER_TAB_ID;
if ( $container->has( 'paylater-configurator.is-available' ) && $container->get( 'paylater-configurator.is-available' ) ) {
return array(
array(
'id' => 'pay-later-messaging-task',
'title' => __( 'Configure PayPal Pay Later messaging', 'woocommerce-paypal-payments' ),
'description' => __( 'Decide where you want dynamic Pay Later messaging to show up and how you want it to look on your site.', 'woocommerce-paypal-payments' ),
'redirect_url' => admin_url( "admin.php?page=wc-settings&tab=checkout&section={$section_id}&ppcp-tab={$pay_later_tab_id}" ),
),
);
}
return array();
},
'wcgateway.settings.wc-tasks.connect-task-config' => static function( ContainerInterface $container ): array {
$is_connected = $container->get( 'settings.flag.is-connected' );
$is_current_country_send_only = $container->get( 'wcgateway.is-send-only-country' );
if ( ! $is_connected && ! $is_current_country_send_only ) {
return array(
array(
'id' => 'connect-to-paypal-task',
'title' => __( 'Connect PayPal to complete setup', 'woocommerce-paypal-payments' ),
'description' => __( 'PayPal Payments is almost ready. To get started, connect your account with the Activate PayPal Payments button.', 'woocommerce-paypal-payments' ),
'redirect_url' => admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=' . Settings::CONNECTION_TAB_ID ),
),
);
}
return array();
},
'wcgateway.settings.wc-tasks.task-config-services' => static function(): array {
return array(
'wcgateway.settings.wc-tasks.pay-later-task-config',
'wcgateway.settings.wc-tasks.connect-task-config',
);
},
/**
* A configuration for simple redirect wc tasks.
*
@ -1970,18 +2075,14 @@ return array(
* }>
*/
'wcgateway.settings.wc-tasks.simple-redirect-tasks-config' => static function( ContainerInterface $container ): array {
$section_id = PayPalGateway::ID;
$pay_later_tab_id = Settings::PAY_LATER_TAB_ID;
$list_of_config = array();
$task_config_services = $container->get( 'wcgateway.settings.wc-tasks.task-config-services' );
if ( $container->has( 'paylater-configurator.is-available' ) && $container->get( 'paylater-configurator.is-available' ) ) {
$list_of_config[] = array(
'id' => 'pay-later-messaging-task',
'title' => __( 'Configure PayPal Pay Later messaging', 'woocommerce-paypal-payments' ),
'description' => __( 'Decide where you want dynamic Pay Later messaging to show up and how you want it to look on your site.', 'woocommerce-paypal-payments' ),
'redirect_url' => admin_url( "admin.php?page=wc-settings&tab=checkout&section={$section_id}&ppcp-tab={$pay_later_tab_id}" ),
);
foreach ( $task_config_services as $service_id ) {
if ( $container->has( $service_id ) ) {
$task_config = $container->get( $service_id );
$list_of_config = array_merge( $list_of_config, $task_config );
}
}
return $list_of_config;
@ -2030,4 +2131,29 @@ return array(
'wcgateway.settings.admin-settings-enabled' => static function( ContainerInterface $container ): bool {
return $container->has( 'settings.url' ) && ! SettingsModule::should_use_the_old_ui();
},
/**
* Returns a prefix for the site, ensuring the same site always gets the same prefix (unless the URL changes).
*/
'wcgateway.settings.invoice-prefix' => static function( ContainerInterface $container ): string {
$site_url = get_site_url( get_current_blog_id() );
$hash = md5( $site_url );
$letters = preg_replace( '~\d~', '', $hash ) ?? '';
$prefix = substr( $letters, 0, 6 );
return $prefix ? $prefix . '-' : '';
},
/**
* Returns random 6 characters length alphabetic prefix, followed by a hyphen.
*/
'wcgateway.settings.invoice-prefix-random' => static function( ContainerInterface $container ): string {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$prefix = '';
for ( $i = 0; $i < 6; $i++ ) {
$prefix .= $characters[ wp_rand( 0, strlen( $characters ) - 1 ) ];
}
return $prefix . '-';
},
);

View file

@ -13,8 +13,10 @@ use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
@ -79,12 +81,18 @@ class OXXOGateway extends WC_Payment_Gateway {
*/
protected $logger;
/**
* The ExperienceContextBuilder.
*/
protected ExperienceContextBuilder $experience_context_builder;
/**
* OXXOGateway constructor.
*
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping preference factory.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
* @param string $module_url The URL to the module.
* @param TransactionUrlProvider $transaction_url_provider The transaction url provider.
* @param Environment $environment The environment.
@ -94,6 +102,7 @@ class OXXOGateway extends WC_Payment_Gateway {
OrderEndpoint $order_endpoint,
PurchaseUnitFactory $purchase_unit_factory,
ShippingPreferenceFactory $shipping_preference_factory,
ExperienceContextBuilder $experience_context_builder,
string $module_url,
TransactionUrlProvider $transaction_url_provider,
Environment $environment,
@ -121,6 +130,7 @@ class OXXOGateway extends WC_Payment_Gateway {
$this->order_endpoint = $order_endpoint;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->experience_context_builder = $experience_context_builder;
$this->module_url = $module_url;
$this->logger = $logger;
@ -176,17 +186,29 @@ class OXXOGateway extends WC_Payment_Gateway {
'checkout'
);
$order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference );
$payment_source_data = array(
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'country_code' => $wc_order->get_billing_country(),
'experience_context' => $this->experience_context_builder
->with_default_paypal_config( $shipping_preference )
->build()->to_array(),
);
$order = $this->order_endpoint->create(
array( $purchase_unit ),
$shipping_preference,
null,
'',
array(),
new PaymentSource(
'oxxo',
(object) $payment_source_data
)
);
$this->add_paypal_meta( $wc_order, $order, $this->environment );
$payment_source = array(
'oxxo' => array(
'name' => $wc_order->get_billing_first_name() . ' ' . $wc_order->get_billing_last_name(),
'email' => $wc_order->get_billing_email(),
'country_code' => $wc_order->get_billing_country(),
),
);
$payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source );
$payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), array( 'oxxo' => $payment_source_data ) );
foreach ( $payment_method->links as $link ) {
if ( $link->rel === 'payer-action' ) {
$payer_action = $link->href;

View file

@ -13,6 +13,7 @@ declare( strict_types = 1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
@ -62,6 +63,20 @@ class CardPaymentsConfiguration {
*/
private DccApplies $dcc_applies;
/**
* Manages the Seller status.
*
* @var DCCProductStatus
*/
private DCCProductStatus $dcc_status;
/**
* Store country.
*
* @var string
*/
private string $store_country;
/**
* This classes lazily resolves settings on first access. This flag indicates
* whether the setting values were resolved, or still need to be evaluated.
@ -120,18 +135,27 @@ class CardPaymentsConfiguration {
*/
private bool $hide_fastlane_watermark = false;
/**
* Initializes the gateway details based on the provided Settings instance.
*
* @param ConnectionState $connection_state Connection state instance.
* @param Settings $settings Plugin settings instance.
* @param DccApplies $dcc_applies DCC eligibility helper.
* @param ConnectionState $connection_state Connection state instance.
* @param Settings $settings Plugin settings instance.
* @param DccApplies $dcc_applies DCC eligibility helper.
* @param DCCProductStatus $dcc_status Manages the Seller status.
* @param string $store_country The shop's country code.
*/
public function __construct( ConnectionState $connection_state, Settings $settings, DccApplies $dcc_applies ) {
public function __construct(
ConnectionState $connection_state,
Settings $settings,
DccApplies $dcc_applies,
DCCProductStatus $dcc_status,
string $store_country
) {
$this->connection_state = $connection_state;
$this->settings = $settings;
$this->dcc_applies = $dcc_applies;
$this->dcc_status = $dcc_status;
$this->store_country = $store_country;
$this->is_resolved = false;
}
@ -241,7 +265,7 @@ class CardPaymentsConfiguration {
*/
$this->use_acdc = (bool) apply_filters(
'woocommerce_paypal_payments_is_acdc_active',
$this->dcc_applies->for_country_currency()
$this->dcc_applies->for_country_currency() && $this->dcc_status->is_active()
);
/**
@ -306,6 +330,11 @@ class CardPaymentsConfiguration {
* @return bool
*/
public function is_bcdc_enabled() : bool {
if ( 'MX' === $this->store_country ) {
$bcdc_setting = get_option( 'woocommerce_ppcp-card-button-gateway_settings' );
return 'yes' === $bcdc_setting['enabled'];
}
return $this->is_enabled() && ! $this->use_acdc();
}

View file

@ -0,0 +1,113 @@
<?php
/**
* Manage the Seller status for Installments.
*
* @package WooCommerce\PayPalCommerce\WcGateway\Helper
*/
declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ProductStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
/**
* Class InstallmentsProductStatus
*/
class InstallmentsProductStatus extends ProductStatus {
public const SETTINGS_KEY = 'products_installments_enabled';
public const INSTALLMENTS_STATUS_CACHE_KEY = 'installments_status_cache';
public const SETTINGS_VALUE_ENABLED = 'yes';
public const SETTINGS_VALUE_DISABLED = 'no';
public const SETTINGS_VALUE_UNDEFINED = '';
/**
* The Cache.
*
* @var Cache
*/
protected Cache $cache;
/**
* The settings.
*
* @var Settings
*/
private Settings $settings;
/**
* InstallmentsProductStatus constructor.
*
* @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param Cache $cache The cache.
* @param bool $is_connected The onboarding state.
* @param FailureRegistry $api_failure_registry The API failure registry.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
Cache $cache,
bool $is_connected,
FailureRegistry $api_failure_registry
) {
parent::__construct( $is_connected, $partners_endpoint, $api_failure_registry );
$this->settings = $settings;
$this->cache = $cache;
}
/** {@inheritDoc} */
protected function check_local_state() : ?bool {
if ( $this->cache->has( self::INSTALLMENTS_STATUS_CACHE_KEY ) ) {
return wc_string_to_bool( $this->cache->get( self::INSTALLMENTS_STATUS_CACHE_KEY ) );
}
if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
return wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
}
return null;
}
/** {@inheritDoc} */
protected function check_active_state( SellerStatus $seller_status ) : bool {
foreach ( $seller_status->capabilities() as $capability ) {
if ( $capability->name() !== 'INSTALLMENTS' ) {
continue;
}
if ( $capability->status() === 'ACTIVE' ) {
$this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
$this->settings->persist();
$this->cache->set( self::INSTALLMENTS_STATUS_CACHE_KEY, self::SETTINGS_VALUE_ENABLED, MONTH_IN_SECONDS );
return true;
}
}
$this->cache->set( self::INSTALLMENTS_STATUS_CACHE_KEY, self::SETTINGS_VALUE_DISABLED, MONTH_IN_SECONDS );
return false;
}
/** {@inheritDoc} */
protected function clear_state( Settings $settings = null ) : void {
if ( null === $settings ) {
$settings = $this->settings;
}
if ( $settings->has( self::SETTINGS_KEY ) ) {
$settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
$settings->persist();
}
$this->cache->delete( self::INSTALLMENTS_STATUS_CACHE_KEY );
}
}

View file

@ -69,7 +69,7 @@ class ConnectAdminNotice {
$message = sprintf(
/* translators: %1$s the gateway name. */
__(
'PayPal Payments is almost ready. To get started, connect your account with the <b>Activate PayPal</b> button <a href="%1$s">on the Account Setup page</a>.',
'PayPal Payments is almost ready. To get started, connect your account with the <b>Activate PayPal Payments</b> button <a href="%1$s">on the Account Setup page</a>.',
'woocommerce-paypal-payments'
),
admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=' . Settings::CONNECTION_TAB_ID )
@ -77,6 +77,16 @@ class ConnectAdminNotice {
return new Message( $message, 'warning' );
}
/**
* Returns whether the current page is plugins.php.
*
* @return bool
*/
private function is_current_page_plugins_page(): bool {
global $pagenow;
return isset( $pagenow ) && $pagenow === 'plugins.php';
}
/**
* Whether the message should display.
*
@ -87,6 +97,8 @@ class ConnectAdminNotice {
* @return bool
*/
protected function should_display(): bool {
return ! $this->is_connected && ! $this->is_current_country_send_only;
return $this->is_current_page_plugins_page()
&& ! $this->is_connected
&& ! $this->is_current_country_send_only;
}
}

View file

@ -13,11 +13,12 @@ use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
@ -144,6 +145,11 @@ class OrderProcessor {
*/
private $restore_order_data = array();
/**
* The ExperienceContextBuilder.
*/
private ExperienceContextBuilder $experience_context_builder;
/**
* OrderProcessor constructor.
*
@ -160,6 +166,7 @@ class OrderProcessor {
* @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory.
* @param PayerFactory $payer_factory The payer factory.
* @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
SessionHandler $session_handler,
@ -174,7 +181,8 @@ class OrderProcessor {
OrderHelper $order_helper,
PurchaseUnitFactory $purchase_unit_factory,
PayerFactory $payer_factory,
ShippingPreferenceFactory $shipping_preference_factory
ShippingPreferenceFactory $shipping_preference_factory,
ExperienceContextBuilder $experience_context_builder
) {
$this->session_handler = $session_handler;
@ -190,6 +198,7 @@ class OrderProcessor {
$this->purchase_unit_factory = $purchase_unit_factory;
$this->payer_factory = $payer_factory;
$this->shipping_preference_factory = $shipping_preference_factory;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -228,6 +237,13 @@ class OrderProcessor {
}
}
// Do not continue if PayPal order status is completed.
$order = $this->order_endpoint->order( $order->id() );
if ( $order->status()->is( OrderStatus::COMPLETED ) ) {
$this->logger->warning( 'Could not process PayPal completed order #' . $order->id() . ', Status: ' . $order->status()->name() );
return;
}
$this->add_paypal_meta( $wc_order, $order, $this->environment );
if ( $this->order_helper->contains_physical_goods( $order ) && ! $this->order_is_ready_for_process( $order ) ) {
@ -318,9 +334,16 @@ class OrderProcessor {
array( $pu ),
$shipping_preference,
$this->payer_factory->from_wc_order( $wc_order ),
null,
'',
ApplicationContext::USER_ACTION_PAY_NOW
array(),
new PaymentSource(
'paypal',
(object) array(
'experience_context' => $this->experience_context_builder
->with_default_paypal_config( $shipping_preference, ExperienceContext::USER_ACTION_PAY_NOW )
->build()->to_array(),
)
)
);
return $order;

View file

@ -49,6 +49,9 @@ return function ( ContainerInterface $container, array $fields ): array {
$onboarding_send_only_notice_renderer = $container->get( 'onboarding.render-send-only-notice' );
assert( $onboarding_send_only_notice_renderer instanceof OnboardingSendOnlyNoticeRenderer );
$environment = $container->get( 'settings.environment' );
assert( $environment instanceof Environment );
$is_send_only_country = $container->get( 'wcgateway.is-send-only-country' );
$onboarding_elements_class = $is_send_only_country ? 'hide' : 'ppcp-onboarding-element';
$send_only_country_notice_class = $is_send_only_country ? 'ppcp-onboarding-element' : 'hide';
@ -510,13 +513,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'custom_attributes' => array(
'pattern' => '[a-zA-Z_\\-]+',
),
'default' => ( static function (): string {
$site_url = get_site_url( get_current_blog_id() );
$hash = md5( $site_url );
$letters = preg_replace( '~\d~', '', $hash ) ?? '';
$prefix = substr( $letters, 0, 6 );
return $prefix ? $prefix . '-' : '';
} )(),
'default' => $environment->is_sandbox() ? $container->get( 'wcgateway.settings.invoice-prefix-random' ) : $container->get( 'wcgateway.settings.invoice-prefix' ),
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
@ -539,6 +536,20 @@ return function ( ContainerInterface $container, array $fields ): array {
'requirements' => array(),
'gateway' => Settings::CONNECTION_TAB_ID,
),
'stay_updated' => array(
'title' => __( 'Stay Updated', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
'label' => __( 'Update to the latest features when available. ', 'woocommerce-paypal-payments' ),
'description' => __( 'Get the latest PayPal features and capabilities as they are released. When the extension is updated, new features, payment methods, styling options, and more will automatically update.', 'woocommerce-paypal-payments' ),
'default' => true,
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => Settings::CONNECTION_TAB_ID,
),
'subtotal_mismatch_behavior' => array(
'title' => __( 'Subtotal mismatch behavior', 'woocommerce-paypal-payments' ),
'type' => 'select',

View file

@ -24,6 +24,7 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Helper\InstallmentsProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\SendOnlyCountryNotice;
use WooCommerce\PayPalCommerce\WcGateway\Processor\CreditCardOrderInfoHandlingTrait;
use WC_Order;
@ -569,6 +570,9 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
$apms_product_status = $c->get( 'ppcp-local-apms.product-status' );
assert( $apms_product_status instanceof LocalApmProductStatus );
$installments_product_status = $c->get( 'wcgateway.installments-product-status' );
assert( $installments_product_status instanceof InstallmentsProductStatus );
$features['save_paypal_and_venmo'] = array(
'enabled' => $billing_agreements_endpoint->reference_transaction_enabled(),
);
@ -584,6 +588,10 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
// When local APMs are available, then PayLater messaging is also available.
$features['pay_later_messaging'] = $features['alternative_payment_methods'];
$features['installments'] = array(
'enabled' => $installments_product_status->is_active(),
);
return $features;
}
);

View file

@ -46,7 +46,8 @@ return array(
$container->get( 'wc-subscriptions.helpers.real-time-account-updater' ),
$container->get( 'wc-subscriptions.helper' ),
$container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'vaulting.wc-payment-tokens' )
$container->get( 'vaulting.wc-payment-tokens' ),
$container->get( 'wcgateway.builder.experience-context' )
);
},
'wc-subscriptions.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {

View file

@ -15,11 +15,11 @@ use WC_Payment_Tokens;
use WC_Subscription;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
@ -148,6 +148,11 @@ class RenewalHandler {
*/
private $wc_payment_tokens;
/**
* The ExperienceContextBuilder.
*/
private ExperienceContextBuilder $experience_context_builder;
/**
* RenewalHandler constructor.
*
@ -165,6 +170,7 @@ class RenewalHandler {
* @param SubscriptionHelper $subscription_helper Subscription helper.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param WooCommercePaymentTokens $wc_payment_tokens WooCommerce payments tokens factory.
* @param ExperienceContextBuilder $experience_context_builder The ExperienceContextBuilder.
*/
public function __construct(
LoggerInterface $logger,
@ -180,7 +186,8 @@ class RenewalHandler {
RealTimeAccountUpdaterHelper $real_time_account_updater_helper,
SubscriptionHelper $subscription_helper,
PaymentTokensEndpoint $payment_tokens_endpoint,
WooCommercePaymentTokens $wc_payment_tokens
WooCommercePaymentTokens $wc_payment_tokens,
ExperienceContextBuilder $experience_context_builder
) {
$this->logger = $logger;
@ -197,6 +204,7 @@ class RenewalHandler {
$this->subscription_helper = $subscription_helper;
$this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->wc_payment_tokens = $wc_payment_tokens;
$this->experience_context_builder = $experience_context_builder;
}
/**
@ -344,9 +352,6 @@ class RenewalHandler {
array( $purchase_unit ),
$shipping_preference,
$payer,
null,
'',
ApplicationContext::USER_ACTION_CONTINUE,
'',
array(),
$payment_source
@ -388,9 +393,6 @@ class RenewalHandler {
array( $purchase_unit ),
$shipping_preference,
$payer,
null,
'',
ApplicationContext::USER_ACTION_CONTINUE,
'',
array(),
$payment_source
@ -413,7 +415,9 @@ class RenewalHandler {
array( $purchase_unit ),
$shipping_preference,
$payer,
$token
'',
array(),
$token->to_payment_source()
);
$this->handle_paypal_order( $wc_order, $order );
@ -543,12 +547,15 @@ class RenewalHandler {
*/
private function card_payment_source( string $token, WC_Order $wc_order ): PaymentSource {
$properties = array(
'vault_id' => $token,
'stored_credential' => array(
'vault_id' => $token,
'stored_credential' => array(
'payment_initiator' => 'MERCHANT',
'payment_type' => 'RECURRING',
'usage' => 'SUBSEQUENT',
),
'experience_context' => $this->experience_context_builder
->with_endpoint_return_urls()
->build()->to_array(),
);
$subscriptions = wcs_get_subscriptions_for_renewal_order( $wc_order );