mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge branch 'trunk' into PCP-4970-add-defensive-code-for-realpath-failures-in-module-service-files
# Conflicts: # composer.lock
This commit is contained in:
commit
709e014db4
44 changed files with 1851 additions and 627 deletions
|
@ -91,12 +91,9 @@ return function ( string $root_dir ): iterable {
|
|||
$modules[] = ( require "$modules_dir/ppcp-axo-block/module.php" )();
|
||||
}
|
||||
|
||||
$show_new_ux = '1' === get_option( 'woocommerce-ppcp-is-new-merchant' );
|
||||
$preview_new_ux = '1' === getenv( 'PCP_SETTINGS_ENABLED' );
|
||||
|
||||
if ( apply_filters(
|
||||
'woocommerce.feature-flags.woocommerce_paypal_payments.settings_enabled',
|
||||
$show_new_ux || $preview_new_ux
|
||||
true
|
||||
) ) {
|
||||
$modules[] = ( require "$modules_dir/ppcp-settings/module.php" )();
|
||||
}
|
||||
|
|
|
@ -9,45 +9,33 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ClientCredentials;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
|
||||
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PlanFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundPayerFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerPayableBreakdownFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\IdentityToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokenEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\AddressFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\AmountFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ContactPreferenceFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExchangeRateFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\FraudProcessorResponseFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ItemFactory;
|
||||
|
@ -56,33 +44,45 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayeeFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentsFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenActionLinksFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PlanFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PlatformFeeFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundPayerFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerPayableBreakdownFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerReceivableBreakdownFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerStatusFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\ConnectBearer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\Settings\Enum\InstallationPathEnum;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ContactPreferenceFactory;
|
||||
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
||||
use WooCommerce\PayPalCommerce\Settings\Enum\InstallationPathEnum;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
return array(
|
||||
'api.host' => static function( ContainerInterface $container ) : string {
|
||||
|
@ -274,13 +274,10 @@ return array(
|
|||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'api.endpoint.billing-agreements' => static function ( ContainerInterface $container ): BillingAgreementsEndpoint {
|
||||
return new BillingAgreementsEndpoint(
|
||||
$container->get( 'api.host' ),
|
||||
$container->get( 'api.bearer' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'api.reference-transaction-status' => static fn ( ContainerInterface $container ): ReferenceTransactionStatus => new ReferenceTransactionStatus(
|
||||
$container->get( 'api.endpoint.partners' ),
|
||||
$container->get( 'api.reference-transaction-status-cache' )
|
||||
),
|
||||
'api.endpoint.catalog-products' => static function ( ContainerInterface $container ): CatalogProducts {
|
||||
return new CatalogProducts(
|
||||
$container->get( 'api.host' ),
|
||||
|
@ -875,6 +872,9 @@ return array(
|
|||
'api.user-id-token-cache' => static function( ContainerInterface $container ): Cache {
|
||||
return new Cache( 'ppcp-id-token-cache' );
|
||||
},
|
||||
'api.reference-transaction-status-cache' => static function( ContainerInterface $container ): Cache {
|
||||
return new Cache( 'ppcp-reference-transaction-status-cache' );
|
||||
},
|
||||
'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken {
|
||||
return new UserIdToken(
|
||||
$container->get( 'api.host' ),
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* The billing agreements endpoint.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use stdClass;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Class BillingAgreementsEndpoint
|
||||
*/
|
||||
class BillingAgreementsEndpoint {
|
||||
use RequestTrait;
|
||||
|
||||
/**
|
||||
* The host.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $host;
|
||||
|
||||
/**
|
||||
* The bearer.
|
||||
*
|
||||
* @var Bearer
|
||||
*/
|
||||
private $bearer;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* BillingAgreementsEndpoint constructor.
|
||||
*
|
||||
* @param string $host The host.
|
||||
* @param Bearer $bearer The bearer.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
string $host,
|
||||
Bearer $bearer,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->host = $host;
|
||||
$this->bearer = $bearer;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a billing agreement token.
|
||||
*
|
||||
* @param string $description The description.
|
||||
* @param string $return_url The return URL.
|
||||
* @param string $cancel_url The cancel URL.
|
||||
*
|
||||
* @throws RuntimeException If the request fails.
|
||||
* @throws PayPalApiException If the request fails.
|
||||
*/
|
||||
public function create_token( string $description, string $return_url, string $cancel_url ): stdClass {
|
||||
$data = array(
|
||||
'description' => $description,
|
||||
'payer' => array(
|
||||
'payment_method' => 'PAYPAL',
|
||||
),
|
||||
'plan' => array(
|
||||
'type' => 'MERCHANT_INITIATED_BILLING',
|
||||
'merchant_preferences' => array(
|
||||
'return_url' => $return_url,
|
||||
'cancel_url' => $cancel_url,
|
||||
'skip_shipping_address' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$bearer = $this->bearer->bearer();
|
||||
$url = trailingslashit( $this->host ) . 'v1/billing-agreements/agreement-tokens';
|
||||
$args = array(
|
||||
'method' => 'POST',
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $bearer->token(),
|
||||
'Content-Type' => 'application/json',
|
||||
),
|
||||
'body' => wp_json_encode( $data ),
|
||||
);
|
||||
$response = $this->request( $url, $args );
|
||||
|
||||
if ( is_wp_error( $response ) || ! is_array( $response ) ) {
|
||||
throw new RuntimeException( 'Not able to create a billing agreement token.' );
|
||||
}
|
||||
|
||||
$json = json_decode( $response['body'] );
|
||||
$status_code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( 201 !== $status_code ) {
|
||||
throw new PayPalApiException(
|
||||
$json,
|
||||
$status_code
|
||||
);
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if reference transactions are enabled in account.
|
||||
*
|
||||
* @throws RuntimeException If the request fails (no auth, no connection, etc.).
|
||||
*/
|
||||
public function reference_transaction_enabled(): bool {
|
||||
try {
|
||||
if ( wc_string_to_bool( get_transient( 'ppcp_reference_transaction_enabled' ) ) === true ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->is_request_logging_enabled = false;
|
||||
|
||||
try {
|
||||
$this->create_token(
|
||||
'Checking if reference transactions are enabled',
|
||||
'https://example.com/return',
|
||||
'https://example.com/cancel'
|
||||
);
|
||||
} finally {
|
||||
$this->is_request_logging_enabled = true;
|
||||
}
|
||||
|
||||
set_transient( 'ppcp_reference_transaction_enabled', true, MONTH_IN_SECONDS );
|
||||
return true;
|
||||
} catch ( Exception $exception ) {
|
||||
set_transient( 'ppcp_reference_transaction_enabled', false, HOUR_IN_SECONDS );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -70,6 +70,10 @@ class Order {
|
|||
* @var PaymentSource|null
|
||||
*/
|
||||
private $payment_source;
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
private $links;
|
||||
|
||||
/**
|
||||
* Order constructor.
|
||||
|
@ -93,7 +97,8 @@ class Order {
|
|||
Payer $payer = null,
|
||||
string $intent = 'CAPTURE',
|
||||
\DateTime $create_time = null,
|
||||
\DateTime $update_time = null
|
||||
\DateTime $update_time = null,
|
||||
$links = null
|
||||
) {
|
||||
|
||||
$this->id = $id;
|
||||
|
@ -104,6 +109,7 @@ class Order {
|
|||
$this->create_time = $create_time;
|
||||
$this->update_time = $update_time;
|
||||
$this->payment_source = $payment_source;
|
||||
$this->links = $links;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,6 +185,15 @@ class Order {
|
|||
return $this->payment_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the links.
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function links() {
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
*
|
||||
|
@ -206,6 +221,10 @@ class Order {
|
|||
$order['update_time'] = $this->update_time()->format( 'Y-m-d\TH:i:sO' );
|
||||
}
|
||||
|
||||
if ( $this->links ) {
|
||||
$order['links'] = $this->links();
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,12 +198,15 @@ class AmountFactory {
|
|||
/**
|
||||
* Returns an Amount object based off a PayPal Response.
|
||||
*
|
||||
* @param \stdClass $data The JSON object.
|
||||
* @param mixed $data The JSON object.
|
||||
*
|
||||
* @return Amount
|
||||
* @throws RuntimeException When JSON object is malformed.
|
||||
* @return Amount|null
|
||||
*/
|
||||
public function from_paypal_response( \stdClass $data ): Amount {
|
||||
public function from_paypal_response( $data ) {
|
||||
if ( null === $data || ! $data instanceof \stdClass ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$money = $this->money_factory->from_paypal_response( $data );
|
||||
$breakdown = ( isset( $data->breakdown ) ) ? $this->break_down( $data->breakdown ) : null;
|
||||
return new Amount( $money, $breakdown );
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatusDetails;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class CaptureFactory
|
||||
|
@ -63,6 +64,7 @@ class CaptureFactory {
|
|||
* @param \stdClass $data The PayPal response.
|
||||
*
|
||||
* @return Capture
|
||||
* @throws RuntimeException When capture amount data is invalid.
|
||||
*/
|
||||
public function from_paypal_response( \stdClass $data ) : Capture {
|
||||
$reason = $data->status_details->reason ?? null;
|
||||
|
@ -74,13 +76,18 @@ class CaptureFactory {
|
|||
$this->fraud_processor_response_factory->from_paypal_response( $data->processor_response )
|
||||
: null;
|
||||
|
||||
$amount = $this->amount_factory->from_paypal_response( $data->amount );
|
||||
if ( null === $amount ) {
|
||||
throw new RuntimeException( __( 'Invalid capture amount data.', 'woocommerce-paypal-payments' ) );
|
||||
}
|
||||
|
||||
return new Capture(
|
||||
(string) $data->id,
|
||||
new CaptureStatus(
|
||||
(string) $data->status,
|
||||
$reason ? new CaptureStatusDetails( $reason ) : null
|
||||
),
|
||||
$this->amount_factory->from_paypal_response( $data->amount ),
|
||||
$amount,
|
||||
(bool) $data->final_capture,
|
||||
(string) $data->seller_protection->status,
|
||||
(string) $data->invoice_id,
|
||||
|
|
|
@ -68,7 +68,8 @@ class OrderFactory {
|
|||
$order->payer(),
|
||||
$order->intent(),
|
||||
$order->create_time(),
|
||||
$order->update_time()
|
||||
$order->update_time(),
|
||||
$order->links()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -81,70 +82,155 @@ class OrderFactory {
|
|||
* @throws RuntimeException When JSON object is malformed.
|
||||
*/
|
||||
public function from_paypal_response( \stdClass $order_data ): Order {
|
||||
$this->validate_order_id( $order_data );
|
||||
|
||||
$purchase_units = $this->create_purchase_units( $order_data );
|
||||
$status = $this->create_order_status( $order_data );
|
||||
$intent = $this->get_intent( $order_data );
|
||||
$timestamps = $this->create_timestamps( $order_data );
|
||||
$payer = $this->create_payer( $order_data );
|
||||
$payment_source = $this->create_payment_source( $order_data );
|
||||
$links = $order_data->links ?? null;
|
||||
|
||||
return new Order(
|
||||
$order_data->id,
|
||||
$purchase_units,
|
||||
$status,
|
||||
$payment_source,
|
||||
$payer,
|
||||
$intent,
|
||||
$timestamps['create_time'],
|
||||
$timestamps['update_time'],
|
||||
$links
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the order data contains a required ID.
|
||||
*
|
||||
* @param \stdClass $order_data The order data.
|
||||
*
|
||||
* @throws RuntimeException When ID is missing.
|
||||
*/
|
||||
private function validate_order_id( \stdClass $order_data ): void {
|
||||
if ( ! isset( $order_data->id ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'Order does not contain an id.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates purchase units from order data.
|
||||
*
|
||||
* @param \stdClass $order_data The order data.
|
||||
*
|
||||
* @return array Array of PurchaseUnit objects.
|
||||
*/
|
||||
private function create_purchase_units( \stdClass $order_data ): array {
|
||||
if ( ! isset( $order_data->purchase_units ) || ! is_array( $order_data->purchase_units ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'Order does not contain items.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
if ( ! isset( $order_data->status ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'Order does not contain status.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
if ( ! isset( $order_data->intent ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'Order does not contain intent.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
$purchase_units = array_map(
|
||||
function ( \stdClass $data ): PurchaseUnit {
|
||||
return $this->purchase_unit_factory->from_paypal_response( $data );
|
||||
},
|
||||
$order_data->purchase_units
|
||||
);
|
||||
|
||||
$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 ) ) ?
|
||||
\DateTime::createFromFormat( 'Y-m-d\TH:i:sO', $order_data->update_time )
|
||||
: null;
|
||||
$payer = ( isset( $order_data->payer ) ) ?
|
||||
$this->payer_factory->from_paypal_response( $order_data->payer )
|
||||
: null;
|
||||
|
||||
$payment_source = null;
|
||||
if ( isset( $order_data->payment_source ) ) {
|
||||
$json_encoded_payment_source = wp_json_encode( $order_data->payment_source );
|
||||
if ( $json_encoded_payment_source ) {
|
||||
$payment_source_as_array = json_decode( $json_encoded_payment_source, true );
|
||||
if ( $payment_source_as_array ) {
|
||||
$name = array_key_first( $payment_source_as_array );
|
||||
if ( $name ) {
|
||||
$payment_source = new PaymentSource(
|
||||
$name,
|
||||
$order_data->payment_source->$name
|
||||
);
|
||||
}
|
||||
}
|
||||
$purchase_units = array();
|
||||
foreach ( $order_data->purchase_units as $data ) {
|
||||
$purchase_unit = $this->purchase_unit_factory->from_paypal_response( $data );
|
||||
if ( null !== $purchase_unit ) {
|
||||
$purchase_units[] = $purchase_unit;
|
||||
}
|
||||
}
|
||||
|
||||
return new Order(
|
||||
$order_data->id,
|
||||
$purchase_units,
|
||||
new OrderStatus( $order_data->status ),
|
||||
$payment_source,
|
||||
$payer,
|
||||
$order_data->intent,
|
||||
$create_time,
|
||||
$update_time
|
||||
return $purchase_units;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates order status from order data.
|
||||
*
|
||||
* @param \stdClass $order_data The order data.
|
||||
*
|
||||
* @return OrderStatus
|
||||
*/
|
||||
private function create_order_status( \stdClass $order_data ): OrderStatus {
|
||||
$status_value = $order_data->status ?? 'PAYER_ACTION_REQUIRED';
|
||||
return new OrderStatus( $status_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the intent from order data.
|
||||
*
|
||||
* @param \stdClass $order_data The order data.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_intent( \stdClass $order_data ): string {
|
||||
return $order_data->intent ?? 'CAPTURE';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates timestamps from order data.
|
||||
*
|
||||
* @param \stdClass $order_data The order data.
|
||||
*
|
||||
* @return array Array with 'create_time' and 'update_time' keys.
|
||||
*/
|
||||
private function create_timestamps( \stdClass $order_data ): array {
|
||||
$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 ) ?
|
||||
\DateTime::createFromFormat( 'Y-m-d\TH:i:sO', $order_data->update_time ) :
|
||||
null;
|
||||
|
||||
return array(
|
||||
'create_time' => $create_time,
|
||||
'update_time' => $update_time,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates payer from order data.
|
||||
*
|
||||
* @param \stdClass $order_data The order data.
|
||||
*
|
||||
* @return mixed Payer object or null.
|
||||
*/
|
||||
private function create_payer( \stdClass $order_data ) {
|
||||
return isset( $order_data->payer ) ?
|
||||
$this->payer_factory->from_paypal_response( $order_data->payer ) :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates payment source from order data.
|
||||
*
|
||||
* @param \stdClass $order_data The order data.
|
||||
*
|
||||
* @return PaymentSource|null
|
||||
*/
|
||||
private function create_payment_source( \stdClass $order_data ): ?PaymentSource {
|
||||
if ( ! isset( $order_data->payment_source ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$json_encoded_payment_source = wp_json_encode( $order_data->payment_source );
|
||||
if ( ! $json_encoded_payment_source ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$payment_source_as_array = json_decode( $json_encoded_payment_source, true );
|
||||
if ( ! $payment_source_as_array ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$source_name = array_key_first( $payment_source_as_array );
|
||||
if ( ! $source_name ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PaymentSource(
|
||||
$source_name,
|
||||
$order_data->payment_source->$source_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -219,17 +219,22 @@ class PurchaseUnitFactory {
|
|||
*
|
||||
* @param \stdClass $data The JSON object.
|
||||
*
|
||||
* @return PurchaseUnit
|
||||
* @return ?PurchaseUnit
|
||||
* @throws RuntimeException When JSON object is malformed.
|
||||
*/
|
||||
public function from_paypal_response( \stdClass $data ): PurchaseUnit {
|
||||
public function from_paypal_response( \stdClass $data ): ?PurchaseUnit {
|
||||
if ( ! isset( $data->reference_id ) || ! is_string( $data->reference_id ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'No reference ID given.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
|
||||
$amount = $this->amount_factory->from_paypal_response( $data->amount );
|
||||
$amount_data = $data->amount ?? null;
|
||||
$amount = $this->amount_factory->from_paypal_response( $amount_data );
|
||||
if ( null === $amount ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$description = ( isset( $data->description ) ) ? $data->description : '';
|
||||
$custom_id = ( isset( $data->custom_id ) ) ? $data->custom_id : '';
|
||||
$invoice_id = ( isset( $data->invoice_id ) ) ? $data->invoice_id : '';
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundStatusDetails;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class RefundFactory
|
||||
|
@ -62,6 +63,7 @@ class RefundFactory {
|
|||
* @param \stdClass $data The PayPal response.
|
||||
*
|
||||
* @return Refund
|
||||
* @throws RuntimeException When refund amount data is invalid.
|
||||
*/
|
||||
public function from_paypal_response( \stdClass $data ) : Refund {
|
||||
$reason = $data->status_details->reason ?? null;
|
||||
|
@ -73,13 +75,18 @@ class RefundFactory {
|
|||
$this->refund_payer_factory->from_paypal_response( $data->payer )
|
||||
: null;
|
||||
|
||||
$amount = $this->amount_factory->from_paypal_response( $data->amount );
|
||||
if ( null === $amount ) {
|
||||
throw new RuntimeException( __( 'Invalid refund amount data.', 'woocommerce-paypal-payments' ) );
|
||||
}
|
||||
|
||||
return new Refund(
|
||||
(string) $data->id,
|
||||
new RefundStatus(
|
||||
(string) $data->status,
|
||||
$reason ? new RefundStatusDetails( $reason ) : null
|
||||
),
|
||||
$this->amount_factory->from_paypal_response( $data->amount ),
|
||||
$amount,
|
||||
(string) ( $data->invoice_id ?? '' ),
|
||||
(string) ( $data->custom_id ?? '' ),
|
||||
$seller_payable_breakdown,
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
/**
|
||||
* Reference transaction status helper class.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class ReferenceTransactionStatus
|
||||
*
|
||||
* Helper class to check reference transaction capabilities for PayPal merchant accounts.
|
||||
*/
|
||||
class ReferenceTransactionStatus {
|
||||
|
||||
public const CACHE_KEY = 'ppcp_reference_transaction_enabled';
|
||||
|
||||
|
||||
protected PartnersEndpoint $partners_endpoint;
|
||||
protected Cache $cache;
|
||||
|
||||
public function __construct( PartnersEndpoint $partners_endpoint, Cache $cache ) {
|
||||
$this->partners_endpoint = $partners_endpoint;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if reference transactions are enabled in the merchant account.
|
||||
*
|
||||
* This method verifies if the merchant has the PAYPAL_WALLET_VAULTING_ADVANCED
|
||||
* capability active, which is required for processing reference transactions.
|
||||
*
|
||||
* @return bool True if reference transactions are enabled, false otherwise.
|
||||
*/
|
||||
public function reference_transaction_enabled(): bool {
|
||||
if ( $this->cache->has( self::CACHE_KEY ) ) {
|
||||
return (bool) $this->cache->get( self::CACHE_KEY );
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ( $this->partners_endpoint->seller_status()->capabilities() as $capability ) {
|
||||
if (
|
||||
$capability->name() === 'PAYPAL_WALLET_VAULTING_ADVANCED' &&
|
||||
$capability->status() === 'ACTIVE'
|
||||
) {
|
||||
$this->cache->set( self::CACHE_KEY, true, MONTH_IN_SECONDS );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch ( RuntimeException $exception ) {
|
||||
$this->cache->set( self::CACHE_KEY, false, HOUR_IN_SECONDS );
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->cache->set( self::CACHE_KEY, false, HOUR_IN_SECONDS );
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -92,7 +92,9 @@ return array(
|
|||
$container->get( 'api.factory.shipping-preference' ),
|
||||
$container->get( 'wcgateway.transaction-url-provider' ),
|
||||
$container->get( 'settings.environment' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||
$container->get( 'wcgateway.builder.experience-context' ),
|
||||
$container->get( 'settings.data.settings' )
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -149,46 +151,63 @@ return array(
|
|||
* The matrix which countries and currency combinations can be used for AXO.
|
||||
*/
|
||||
'axo.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
|
||||
$matrix = array(
|
||||
'US' => array(
|
||||
'AUD',
|
||||
'CAD',
|
||||
'EUR',
|
||||
'GBP',
|
||||
'JPY',
|
||||
'USD',
|
||||
),
|
||||
);
|
||||
|
||||
if ( $container->get( 'axo.uk.enabled' ) ) {
|
||||
$matrix['GB'] = array( 'GBP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns which countries and currency combinations can be used for AXO.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_axo_supported_country_currency_matrix',
|
||||
array(
|
||||
'US' => array(
|
||||
'AUD',
|
||||
'CAD',
|
||||
'EUR',
|
||||
'GBP',
|
||||
'JPY',
|
||||
'USD',
|
||||
),
|
||||
)
|
||||
$matrix
|
||||
);
|
||||
},
|
||||
/**
|
||||
* The matrix which countries and card type combinations can be used for AXO.
|
||||
*/
|
||||
'axo.supported-country-card-type-matrix' => static function ( ContainerInterface $container ) : array {
|
||||
$matrix = array(
|
||||
'US' => array(
|
||||
'VISA',
|
||||
'MASTERCARD',
|
||||
'AMEX',
|
||||
'DISCOVER',
|
||||
),
|
||||
'CA' => array(
|
||||
'VISA',
|
||||
'MASTERCARD',
|
||||
'AMEX',
|
||||
'DISCOVER',
|
||||
),
|
||||
);
|
||||
|
||||
if ( $container->get( 'axo.uk.enabled' ) ) {
|
||||
$matrix['GB'] = array(
|
||||
'VISA',
|
||||
'MASTERCARD',
|
||||
'AMEX',
|
||||
'DISCOVER',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns which countries and card type combinations can be used for AXO.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_axo_supported_country_card_type_matrix',
|
||||
array(
|
||||
'US' => array(
|
||||
'VISA',
|
||||
'MASTERCARD',
|
||||
'AMEX',
|
||||
'DISCOVER',
|
||||
),
|
||||
'CA' => array(
|
||||
'VISA',
|
||||
'MASTERCARD',
|
||||
'AMEX',
|
||||
'DISCOVER',
|
||||
),
|
||||
)
|
||||
$matrix
|
||||
);
|
||||
},
|
||||
'axo.settings-conflict-notice' => static function ( ContainerInterface $container ) : string {
|
||||
|
@ -372,4 +391,17 @@ return array(
|
|||
)
|
||||
);
|
||||
},
|
||||
'axo.uk.enabled' => static function ( ContainerInterface $container ): bool {
|
||||
// phpcs:disable WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
/**
|
||||
* Filter to determine if Fastlane UK with 3D Secure should be enabled.
|
||||
*
|
||||
* @param bool $enabled Whether Fastlane UK is enabled.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce.feature-flags.woocommerce_paypal_payments.axo_uk_enabled',
|
||||
getenv( 'PCP_AXO_UK_ENABLED' ) !== '0'
|
||||
);
|
||||
// phpcs:enable WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
},
|
||||
);
|
||||
|
|
|
@ -11,13 +11,17 @@ namespace WooCommerce\PayPalCommerce\Axo\Gateway;
|
|||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Exception;
|
||||
use WC_AJAX;
|
||||
use WC_Order;
|
||||
use WC_Payment_Gateway;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextBuilder;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewaySettingsRendererTrait;
|
||||
|
@ -29,6 +33,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
||||
use DomainException;
|
||||
|
||||
/**
|
||||
* Class AXOGateway.
|
||||
|
@ -129,6 +135,20 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
*/
|
||||
protected $session_handler;
|
||||
|
||||
/**
|
||||
* The experience context builder.
|
||||
*
|
||||
* @var ExperienceContextBuilder
|
||||
*/
|
||||
protected $experience_context_builder;
|
||||
|
||||
/**
|
||||
* The settings model.
|
||||
*
|
||||
* @var SettingsModel
|
||||
*/
|
||||
protected $settings_model;
|
||||
|
||||
/**
|
||||
* AXOGateway constructor.
|
||||
*
|
||||
|
@ -145,6 +165,8 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
* @param TransactionUrlProvider $transaction_url_provider The transaction url provider.
|
||||
* @param Environment $environment The environment.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param ExperienceContextBuilder $experience_context_builder The experience context builder.
|
||||
* @param SettingsModel $settings_model The settings model.
|
||||
*/
|
||||
public function __construct(
|
||||
SettingsRenderer $settings_renderer,
|
||||
|
@ -159,17 +181,21 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
ShippingPreferenceFactory $shipping_preference_factory,
|
||||
TransactionUrlProvider $transaction_url_provider,
|
||||
Environment $environment,
|
||||
LoggerInterface $logger
|
||||
LoggerInterface $logger,
|
||||
ExperienceContextBuilder $experience_context_builder,
|
||||
SettingsModel $settings_model
|
||||
) {
|
||||
$this->id = self::ID;
|
||||
|
||||
$this->settings_renderer = $settings_renderer;
|
||||
$this->ppcp_settings = $ppcp_settings;
|
||||
$this->dcc_configuration = $dcc_configuration;
|
||||
$this->wcgateway_module_url = $wcgateway_module_url;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->order_processor = $order_processor;
|
||||
$this->card_icons = $card_icons;
|
||||
$this->settings_renderer = $settings_renderer;
|
||||
$this->ppcp_settings = $ppcp_settings;
|
||||
$this->dcc_configuration = $dcc_configuration;
|
||||
$this->wcgateway_module_url = $wcgateway_module_url;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->order_processor = $order_processor;
|
||||
$this->card_icons = $card_icons;
|
||||
$this->experience_context_builder = $experience_context_builder;
|
||||
$this->settings_model = $settings_model;
|
||||
|
||||
$this->method_title = __( 'Fastlane Debit & Credit Cards', 'woocommerce-paypal-payments' );
|
||||
$this->method_description = __( 'Fastlane accelerates the checkout experience for guest shoppers and autofills their details so they can pay in seconds. When enabled, Fastlane is presented as the default payment method for guests.', 'woocommerce-paypal-payments' );
|
||||
|
@ -234,10 +260,19 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
if ( ! is_a( $wc_order, WC_Order::class ) ) {
|
||||
return $this->handle_payment_failure(
|
||||
null,
|
||||
new GatewayGenericException( new Exception( 'WC order was not found.' ) )
|
||||
new GatewayGenericException( new Exception( 'WC order was not found.' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$axo_nonce = wc_clean( wp_unslash( $_POST['axo_nonce'] ?? '' ) );
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$token_param = wc_clean( wp_unslash( $_GET['token'] ?? '' ) );
|
||||
|
||||
if ( empty( $axo_nonce ) && ! empty( $token_param ) ) {
|
||||
return $this->process_3ds_return( $wc_order, $token_param );
|
||||
}
|
||||
|
||||
try {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$fastlane_member = wc_clean( wp_unslash( $_POST['fastlane_member'] ?? '' ) );
|
||||
|
@ -248,10 +283,37 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
}
|
||||
|
||||
// The `axo_nonce` is not a WP nonce, but a card-token generated by the JS SDK.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$token = wc_clean( wp_unslash( $_POST['axo_nonce'] ?? '' ) );
|
||||
if ( empty( $axo_nonce ) ) {
|
||||
return array(
|
||||
'result' => 'failure',
|
||||
'message' => __( 'No payment token provided. Please try again.', 'woocommerce-paypal-payments' ),
|
||||
);
|
||||
}
|
||||
|
||||
$order = $this->create_paypal_order( $wc_order, $token );
|
||||
$order = $this->create_paypal_order( $wc_order, $axo_nonce );
|
||||
|
||||
// Check if 3DS verification is required.
|
||||
$payer_action = $this->get_payer_action_url( $order );
|
||||
|
||||
// If 3DS verification is required, redirect with token in return URL.
|
||||
if ( $payer_action ) {
|
||||
$return_url = add_query_arg(
|
||||
'token',
|
||||
$order->id(),
|
||||
home_url( WC_AJAX::get_endpoint( ReturnUrlEndpoint::ENDPOINT ) )
|
||||
);
|
||||
|
||||
$redirect_url = add_query_arg(
|
||||
'redirect_uri',
|
||||
rawurlencode( $return_url ),
|
||||
$payer_action
|
||||
);
|
||||
|
||||
return array(
|
||||
'result' => 'success',
|
||||
'redirect' => $redirect_url,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter controls if the method 'process()' from OrderProcessor will be called.
|
||||
|
@ -266,7 +328,11 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
$this->order_processor->process_captured_and_authorized( $wc_order, $order );
|
||||
}
|
||||
} catch ( Exception $exception ) {
|
||||
return $this->handle_payment_failure( $wc_order, $exception );
|
||||
$this->logger->error( '[AXO] Payment processing failed: ' . $exception->getMessage() );
|
||||
return array(
|
||||
'result' => 'failure',
|
||||
'message' => $this->get_user_friendly_error_message( $exception ),
|
||||
);
|
||||
}
|
||||
|
||||
WC()->cart->empty_cart();
|
||||
|
@ -277,6 +343,103 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process 3DS return scenario.
|
||||
*
|
||||
* @param WC_Order $wc_order The WooCommerce order.
|
||||
* @param string $token The PayPal order token.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function process_3ds_return( WC_Order $wc_order, string $token ) : array {
|
||||
try {
|
||||
$paypal_order = $this->order_endpoint->order( $token );
|
||||
|
||||
if ( ! $paypal_order->status()->is( OrderStatus::COMPLETED ) ) {
|
||||
return array(
|
||||
'result' => 'failure',
|
||||
'message' => __( '3D Secure authentication was not completed successfully. Please try again.', 'woocommerce-paypal-payments' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter controls if the method 'process()' from OrderProcessor will be called.
|
||||
* So you can implement your own for example on subscriptions
|
||||
*
|
||||
* - true bool controls execution of 'OrderProcessor::process()'
|
||||
* - $this \WC_Payment_Gateway
|
||||
* - $wc_order \WC_Order
|
||||
*/
|
||||
$process = apply_filters( 'woocommerce_paypal_payments_before_order_process', true, $this, $wc_order );
|
||||
if ( $process ) {
|
||||
$this->order_processor->process_captured_and_authorized( $wc_order, $paypal_order );
|
||||
}
|
||||
} catch ( Exception $exception ) {
|
||||
$this->logger->error( '[AXO] 3DS return processing failed: ' . $exception->getMessage() );
|
||||
return array(
|
||||
'result' => 'failure',
|
||||
'message' => $this->get_user_friendly_error_message( $exception ),
|
||||
);
|
||||
}
|
||||
|
||||
WC()->cart->empty_cart();
|
||||
|
||||
return array(
|
||||
'result' => 'success',
|
||||
'redirect' => $this->get_return_url( $wc_order ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert exceptions to user-friendly messages.
|
||||
*/
|
||||
private function get_user_friendly_error_message( Exception $exception ) {
|
||||
$error_message = $exception->getMessage();
|
||||
|
||||
// Handle specific error types with user-friendly messages.
|
||||
if ( $exception instanceof DomainException ) {
|
||||
if ( strpos( $error_message, 'Could not capture' ) !== false ) {
|
||||
return __( '3D Secure authentication was unavailable or failed. Please try a different payment method or contact your bank.', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( strpos( $error_message, 'declined' ) !== false ||
|
||||
strpos( $error_message, 'PAYMENT_DENIED' ) !== false ||
|
||||
strpos( $error_message, 'INSTRUMENT_DECLINED' ) !== false ||
|
||||
strpos( $error_message, 'Payment provider declined' ) !== false ) {
|
||||
return __( 'Your payment was declined after 3D Secure verification. Please try a different payment method or contact your bank.', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
|
||||
if ( strpos( $error_message, 'session' ) !== false ||
|
||||
strpos( $error_message, 'expired' ) !== false ) {
|
||||
return __( 'Payment session expired. Please try your payment again.', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
|
||||
return __( 'There was an error processing your payment. Please try again or contact support.', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract payer action URL from PayPal order.
|
||||
*
|
||||
* @param Order $order The PayPal order.
|
||||
* @return string The payer action URL or an empty string if not found.
|
||||
*/
|
||||
private function get_payer_action_url( Order $order ) : string {
|
||||
$links = $order->links();
|
||||
|
||||
if ( ! $links ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
foreach ( $links as $link ) {
|
||||
if ( isset( $link->rel ) && $link->rel === 'payer-action' ) {
|
||||
return $link->href ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PayPal order from the existing WC_Order instance.
|
||||
*
|
||||
|
@ -293,9 +456,8 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
'checkout'
|
||||
);
|
||||
|
||||
$payment_source_properties = (object) array(
|
||||
'single_use_token' => $payment_token,
|
||||
);
|
||||
// Build payment source with 3DS verification if needed.
|
||||
$payment_source_properties = $this->build_payment_source_properties( $payment_token );
|
||||
|
||||
$payment_source = new PaymentSource(
|
||||
'card',
|
||||
|
@ -306,12 +468,64 @@ class AxoGateway extends WC_Payment_Gateway {
|
|||
array( $purchase_unit ),
|
||||
$shipping_preference,
|
||||
null,
|
||||
'',
|
||||
array(),
|
||||
$payment_source
|
||||
self::ID,
|
||||
$this->build_order_data(),
|
||||
$payment_source,
|
||||
$wc_order
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build payment source properties.
|
||||
*
|
||||
* @param string $payment_token The payment token.
|
||||
* @return object The payment source properties.
|
||||
*/
|
||||
protected function build_payment_source_properties( string $payment_token ): object {
|
||||
$properties = array(
|
||||
'single_use_token' => $payment_token,
|
||||
);
|
||||
|
||||
$three_d_secure = $this->settings_model->get_three_d_secure_enum();
|
||||
|
||||
if ( 'SCA_ALWAYS' === $three_d_secure || 'SCA_WHEN_REQUIRED' === $three_d_secure ) {
|
||||
$properties['attributes'] = array(
|
||||
'verification' => array(
|
||||
'method' => $three_d_secure,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return (object) $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build additional order data for experience context and 3DS verification.
|
||||
*
|
||||
* @return array The order data.
|
||||
*/
|
||||
protected function build_order_data(): array {
|
||||
$data = array();
|
||||
|
||||
$experience_context = $this->experience_context_builder
|
||||
->with_endpoint_return_urls()
|
||||
->with_current_brand_name()
|
||||
->with_current_locale()
|
||||
->build();
|
||||
|
||||
$data['experience_context'] = $experience_context->to_array();
|
||||
|
||||
$three_d_secure = $this->settings_model->get_three_d_secure_enum();
|
||||
|
||||
if ( $three_d_secure === 'SCA_ALWAYS' || $three_d_secure === 'SCA_WHEN_REQUIRED' ) {
|
||||
$data['transaction_context'] = array(
|
||||
'soft_descriptor' => __( 'Card verification hold', 'woocommerce-paypal-payments' ),
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the icons of the gateway.
|
||||
*
|
||||
|
|
|
@ -232,24 +232,24 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
$this->request_data = $request_data;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->shipping_preference_factory = $shipping_preference_factory;
|
||||
$this->contact_preference_factory = $contact_preference_factory;
|
||||
$this->experience_context_builder = $experience_context_builder;
|
||||
$this->api_endpoint = $order_endpoint;
|
||||
$this->payer_factory = $payer_factory;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->early_order_handler = $early_order_handler;
|
||||
$this->registration_needed = $registration_needed;
|
||||
$this->card_billing_data_mode = $card_billing_data_mode;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->handle_shipping_in_paypal = $handle_shipping_in_paypal;
|
||||
$this->request_data = $request_data;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->shipping_preference_factory = $shipping_preference_factory;
|
||||
$this->contact_preference_factory = $contact_preference_factory;
|
||||
$this->experience_context_builder = $experience_context_builder;
|
||||
$this->api_endpoint = $order_endpoint;
|
||||
$this->payer_factory = $payer_factory;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->early_order_handler = $early_order_handler;
|
||||
$this->registration_needed = $registration_needed;
|
||||
$this->card_billing_data_mode = $card_billing_data_mode;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->handle_shipping_in_paypal = $handle_shipping_in_paypal;
|
||||
$this->server_side_shipping_callback_enabled = $server_side_shipping_callback_enabled;
|
||||
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
|
||||
$this->logger = $logger;
|
||||
$this->funding_sources_without_redirect = $funding_sources_without_redirect;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,7 @@ class SettingsTabMapHelper {
|
|||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected const THREE_D_SECURE_VALUES_MAP = array(
|
||||
public const THREE_D_SECURE_VALUES_MAP = array(
|
||||
'no-3d-secure' => 'NO_3D_SECURE',
|
||||
'only-required-3d-secure' => 'SCA_WHEN_REQUIRED',
|
||||
'always-3d-secure' => 'SCA_ALWAYS',
|
||||
|
@ -54,7 +54,7 @@ class SettingsTabMapHelper {
|
|||
'blocks_final_review_enabled' => 'enable_pay_now',
|
||||
'logging_enabled' => 'enable_logging',
|
||||
'vault_enabled' => 'save_paypal_and_venmo',
|
||||
'3d_secure_contingency' => 'threeDSecure',
|
||||
'3d_secure_contingency' => 'three_d_secure',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ class SettingsTabMapHelper {
|
|||
* @return string|null The mapped '3d_secure_contingency' setting value.
|
||||
*/
|
||||
protected function mapped_3d_secure_value( array $settings_model ): ?string {
|
||||
$three_d_secure = $settings_model['threeDSecure'] ?? null;
|
||||
$three_d_secure = $settings_model['three_d_secure'] ?? null;
|
||||
|
||||
if ( ! is_string( $three_d_secure ) ) {
|
||||
return null;
|
||||
|
|
|
@ -12,21 +12,21 @@ namespace WooCommerce\PayPalCommerce\SavePaymentMethods;
|
|||
use Psr\Log\LoggerInterface;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken;
|
||||
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentTokenForGuest;
|
||||
use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vaulting\WooCommercePaymentTokens;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
@ -69,10 +69,10 @@ class SavePaymentMethodsModule implements ServiceModule, ExtendingModule, Execut
|
|||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
|
||||
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
||||
$reference_transaction_enabled = $billing_agreements_endpoint->reference_transaction_enabled();
|
||||
if ( $reference_transaction_enabled !== true ) {
|
||||
$reference_transaction_status = $c->get( 'api.reference-transaction-status' );
|
||||
assert( $reference_transaction_status instanceof ReferenceTransactionStatus );
|
||||
|
||||
if ( ! $reference_transaction_status->reference_transaction_enabled() ) {
|
||||
$settings->set( 'vault_enabled', false );
|
||||
$settings->persist();
|
||||
}
|
||||
|
|
|
@ -3,12 +3,21 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
const button = document.querySelector(
|
||||
'.button.button-settings-switch-ui'
|
||||
);
|
||||
const link = document.querySelector( 'a.settings-switch-ui' );
|
||||
|
||||
if ( ! typeof config || ! button ) {
|
||||
if ( typeof config === 'undefined' || ( ! button && ! link ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.addEventListener( 'click', () => {
|
||||
const handleClick = ( event ) => {
|
||||
event.preventDefault();
|
||||
|
||||
const confirmed = confirm( config.confirmMessage );
|
||||
|
||||
if ( ! confirmed ) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch( config.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -30,5 +39,13 @@ document.addEventListener( 'DOMContentLoaded', () => {
|
|||
.catch( ( error ) => {
|
||||
console.error( 'Error:', error );
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
if ( button ) {
|
||||
button.addEventListener( 'click', handleClick );
|
||||
}
|
||||
|
||||
if ( link ) {
|
||||
link.addEventListener( 'click', handleClick );
|
||||
}
|
||||
} );
|
||||
|
|
|
@ -52,6 +52,11 @@ use WooCommerce\PayPalCommerce\Settings\Service\ConnectionUrlGenerator;
|
|||
use WooCommerce\PayPalCommerce\Settings\Service\FeaturesEligibilityService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\GatewayRedirectService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\LoadingScreenService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\Migration\SettingsMigration;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\Migration\MigrationManager;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\Migration\PaymentSettingsMigration;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\Migration\SettingsTabMigration;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\Migration\StylingSettingsMigration;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\OnboardingUrlManager;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\ScriptDataHandler;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\TodosEligibilityService;
|
||||
|
@ -73,7 +78,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
|||
use WooCommerce\PayPalCommerce\Settings\Service\InternalRestService;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\MerchantDetails;
|
||||
|
||||
return array(
|
||||
$services = array(
|
||||
'settings.url' => static function ( ContainerInterface $container ) : string {
|
||||
return plugins_url( '/modules/ppcp-settings/', $container->get( 'ppcp.path-to-plugin-main-file' ) );
|
||||
},
|
||||
|
@ -140,20 +145,6 @@ return array(
|
|||
'save' => $save_config,
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Merchant connection details, which includes the connection status
|
||||
* (onboarding/connected) and connection-aware environment checks.
|
||||
* This is the preferred solution to check environment and connection state.
|
||||
*/
|
||||
'settings.connection-state' => static function ( ContainerInterface $container ) : ConnectionState {
|
||||
$data = $container->get( 'settings.data.general' );
|
||||
assert( $data instanceof GeneralSettings );
|
||||
|
||||
$is_connected = $data->is_merchant_connected();
|
||||
$environment = new Environment( $data->is_sandbox_merchant() );
|
||||
|
||||
return new ConnectionState( $is_connected, $environment );
|
||||
},
|
||||
/**
|
||||
* Returns details about the connected environment (production/sandbox).
|
||||
*
|
||||
|
@ -373,14 +364,38 @@ return array(
|
|||
$path_to_module_assets_folder = $container->get( 'ppcp.path-to-plugin-folder' ) . 'modules/ppcp-settings/assets';
|
||||
return new ScriptDataHandler( $settings, $settings_url, $paylater_is_available, $store_country, $merchant_id, $button_language_choices, $partner_attribution, $path_to_module_assets_folder );
|
||||
},
|
||||
'settings.ajax.switch_ui' => static function ( ContainerInterface $container ) : SwitchSettingsUiEndpoint {
|
||||
return new SwitchSettingsUiEndpoint(
|
||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||
$container->get( 'button.request-data' ),
|
||||
$container->get( 'settings.data.onboarding' ),
|
||||
$container->get( 'api.merchant_id' ) !== ''
|
||||
);
|
||||
},
|
||||
'settings.service.data-migration' => static fn( ContainerInterface $c ): MigrationManager => new MigrationManager(
|
||||
$c->get( 'settings.service.data-migration.general-settings' ),
|
||||
$c->get( 'settings.service.data-migration.settings-tab' ),
|
||||
$c->get( 'settings.service.data-migration.styling' ),
|
||||
$c->get( 'settings.service.data-migration.payment-settings' ),
|
||||
),
|
||||
'settings.service.data-migration.settings-tab' => static fn( ContainerInterface $c ): SettingsTabMigration => new SettingsTabMigration(
|
||||
$c->get( 'wcgateway.settings' ),
|
||||
$c->get( 'settings.data.settings' ),
|
||||
$c->get( 'compat.settings.settings_tab_map_helper' ),
|
||||
),
|
||||
'settings.service.data-migration.styling' => static fn( ContainerInterface $c ): StylingSettingsMigration => new StylingSettingsMigration(
|
||||
$c->get( 'wcgateway.settings' ),
|
||||
$c->get( 'settings.data.styling' ),
|
||||
),
|
||||
'settings.service.data-migration.payment-settings' => static fn( ContainerInterface $c ): PaymentSettingsMigration => new PaymentSettingsMigration(
|
||||
$c->get( 'wcgateway.settings' ),
|
||||
$c->get( 'settings.data.payment' ),
|
||||
$c->get( 'ppcp-local-apms.payment-methods' ),
|
||||
),
|
||||
'settings.service.data-migration.general-settings' => static fn( ContainerInterface $c ): SettingsMigration => new SettingsMigration(
|
||||
$c->get( 'wcgateway.settings' ),
|
||||
$c->get( 'settings.data.general' ),
|
||||
$c->get( 'api.endpoint.partners' ),
|
||||
),
|
||||
'settings.ajax.switch_ui' => static fn ( ContainerInterface $c ): SwitchSettingsUiEndpoint => new SwitchSettingsUiEndpoint(
|
||||
$c->get( 'woocommerce.logger.woocommerce' ),
|
||||
$c->get( 'button.request-data' ),
|
||||
$c->get( 'settings.data.onboarding' ),
|
||||
$c->get( 'settings.service.data-migration' ),
|
||||
$c->get( 'api.merchant_id' ) !== ''
|
||||
),
|
||||
'settings.rest.todos' => static function ( ContainerInterface $container ) : TodosRestEndpoint {
|
||||
return new TodosRestEndpoint(
|
||||
$container->get( 'settings.data.todos' ),
|
||||
|
@ -670,3 +685,22 @@ return array(
|
|||
return new MerchantDetails( $merchant_country, $woo_data['country'], $eligibility_checks );
|
||||
},
|
||||
);
|
||||
|
||||
if ( ! SettingsModule::should_use_the_old_ui() ) {
|
||||
/**
|
||||
* Merchant connection details, which includes the connection status
|
||||
* (onboarding/connected) and connection-aware environment checks.
|
||||
* This is the preferred solution to check environment and connection state.
|
||||
*/
|
||||
$services['settings.connection-state'] = static function ( ContainerInterface $container ) : ConnectionState {
|
||||
$data = $container->get( 'settings.data.general' );
|
||||
assert( $data instanceof GeneralSettings );
|
||||
|
||||
$is_connected = $data->is_merchant_connected();
|
||||
$environment = new Environment( $data->is_sandbox_merchant() );
|
||||
|
||||
return new ConnectionState( $is_connected, $environment );
|
||||
};
|
||||
}
|
||||
|
||||
return $services;
|
||||
|
|
|
@ -13,6 +13,7 @@ use Exception;
|
|||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\OnboardingProfile;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\Migration\MigrationManager;
|
||||
|
||||
/**
|
||||
* Class SwitchSettingsUiEndpoint
|
||||
|
@ -24,26 +25,10 @@ class SwitchSettingsUiEndpoint {
|
|||
public const ENDPOINT = 'ppcp-settings-switch-ui';
|
||||
public const OPTION_NAME_SHOULD_USE_OLD_UI = 'woocommerce_ppcp-settings-should-use-old-ui';
|
||||
|
||||
/**
|
||||
* The RequestData.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
protected RequestData $request_data;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* The Onboarding profile.
|
||||
*
|
||||
* @var OnboardingProfile
|
||||
*/
|
||||
protected OnboardingProfile $onboarding_profile;
|
||||
protected MigrationManager $settings_data_migration;
|
||||
|
||||
/**
|
||||
* True if the merchant is onboarded, otherwise false.
|
||||
|
@ -52,24 +37,18 @@ class SwitchSettingsUiEndpoint {
|
|||
*/
|
||||
protected bool $is_onboarded;
|
||||
|
||||
/**
|
||||
* SwitchSettingsUiEndpoint constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param RequestData $request_data The Request data.
|
||||
* @param OnboardingProfile $onboarding_profile The Onboarding profile.
|
||||
* @param bool $is_onboarded True if the merchant is onboarded, otherwise false.
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
RequestData $request_data,
|
||||
OnboardingProfile $onboarding_profile,
|
||||
MigrationManager $settings_data_migration,
|
||||
bool $is_onboarded
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->request_data = $request_data;
|
||||
$this->onboarding_profile = $onboarding_profile;
|
||||
$this->is_onboarded = $is_onboarded;
|
||||
$this->logger = $logger;
|
||||
$this->request_data = $request_data;
|
||||
$this->onboarding_profile = $onboarding_profile;
|
||||
$this->settings_data_migration = $settings_data_migration;
|
||||
$this->is_onboarded = $is_onboarded;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,11 +64,12 @@ class SwitchSettingsUiEndpoint {
|
|||
$this->request_data->read_request( $this->nonce() );
|
||||
update_option( self::OPTION_NAME_SHOULD_USE_OLD_UI, 'no' );
|
||||
|
||||
if ( $this->is_onboarded ) {
|
||||
$this->onboarding_profile->set_completed( true );
|
||||
$this->onboarding_profile->save();
|
||||
}
|
||||
$this->onboarding_profile->set_completed( true );
|
||||
$this->onboarding_profile->set_gateways_refreshed( true );
|
||||
$this->onboarding_profile->set_gateways_synced( true );
|
||||
$this->onboarding_profile->save();
|
||||
|
||||
$this->settings_data_migration->migrate();
|
||||
wp_send_json_success();
|
||||
} catch ( Exception $error ) {
|
||||
wp_send_json_error( array( 'message' => $error->getMessage() ), 500 );
|
||||
|
|
66
modules/ppcp-settings/src/DTO/OAuthConnectionDTO.php
Normal file
66
modules/ppcp-settings/src/DTO/OAuthConnectionDTO.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* Data transfer object. Hold the one-time connection details for the OAuth authentication flow.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\DTO;
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\DTO;
|
||||
|
||||
/**
|
||||
* DTO that holds OAuth connection details, that are used to retrieve a
|
||||
* permanent client ID/secret later.
|
||||
*
|
||||
* Intentionally has no internal logic, sanitation or validation.
|
||||
*/
|
||||
class OAuthConnectionDTO {
|
||||
/**
|
||||
* Whether this connection is a sandbox account.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public bool $is_sandbox = false;
|
||||
|
||||
/**
|
||||
* The shared authentication ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $shared_id = '';
|
||||
|
||||
/**
|
||||
* The authentication token.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $auth_token = '';
|
||||
|
||||
/**
|
||||
* Timestamp when the OAuth details were generated.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public int $timestamp = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param bool $is_sandbox Whether this connection is a sandbox account.
|
||||
* @param string $shared_id The shared oauth ID.
|
||||
* @param string $auth_token The authentication token.
|
||||
* @param int $timestamp Optional. When the credentials were generated.
|
||||
*/
|
||||
public function __construct(
|
||||
bool $is_sandbox,
|
||||
string $shared_id,
|
||||
string $auth_token,
|
||||
int $timestamp = 0
|
||||
) {
|
||||
$this->is_sandbox = $is_sandbox;
|
||||
$this->shared_id = $shared_id;
|
||||
$this->auth_token = $auth_token;
|
||||
$this->timestamp = 0 === $timestamp ? time() : $timestamp;
|
||||
}
|
||||
}
|
|
@ -230,6 +230,27 @@ class SettingsModel extends AbstractDataModel {
|
|||
return $this->data['three_d_secure'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the 3D Secure setting value to the corresponding API enum string.
|
||||
*
|
||||
* @param string|null $three_d_secure The 3D Secure setting ('no-3d-secure', 'only-required-3d-secure', 'always-3d-secure').
|
||||
* @return string The corresponding API enum string ('NO_3D_SECURE', 'SCA_WHEN_REQUIRED', 'SCA_ALWAYS').
|
||||
*/
|
||||
public function get_three_d_secure_enum( string $three_d_secure = null ): string {
|
||||
// If no value is provided, use the current setting.
|
||||
if ( $three_d_secure === null ) {
|
||||
$three_d_secure = $this->get_three_d_secure();
|
||||
}
|
||||
|
||||
$map = array(
|
||||
'no-3d-secure' => 'NO_3D_SECURE',
|
||||
'only-required-3d-secure' => 'SCA_WHEN_REQUIRED',
|
||||
'always-3d-secure' => 'SCA_ALWAYS',
|
||||
);
|
||||
|
||||
return $map[ $three_d_secure ] ?? 'SCA_WHEN_REQUIRED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 3D Secure setting.
|
||||
*
|
||||
|
|
|
@ -204,17 +204,9 @@ class AuthenticationRestEndpoint extends RestEndpoint {
|
|||
$auth_code = $request->get_param( 'authCode' );
|
||||
$use_sandbox = $request->get_param( 'useSandbox' );
|
||||
|
||||
try {
|
||||
$this->authentication_manager->validate_id_and_auth_code( $shared_id, $auth_code );
|
||||
$this->authentication_manager->authenticate_via_oauth( $use_sandbox, $shared_id, $auth_code );
|
||||
} catch ( Exception $exception ) {
|
||||
return $this->return_error( $exception->getMessage() );
|
||||
}
|
||||
$this->authentication_manager->remember_oauth_connection_details( $shared_id, $auth_code, $use_sandbox );
|
||||
|
||||
$account = $this->authentication_manager->get_account_details();
|
||||
$response = $this->sanitize_for_javascript( $this->response_map, $account );
|
||||
|
||||
return $this->return_success( $response );
|
||||
return $this->return_success( true );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,6 +28,17 @@ use WooCommerce\PayPalCommerce\Settings\Enum\SellerTypeEnum;
|
|||
* Clicking that button triggers the merchant-connection request.
|
||||
*/
|
||||
class ConnectionListener {
|
||||
/**
|
||||
* Token processing states
|
||||
*/
|
||||
private const TOKEN_STATE_PROCESSING = 'processing';
|
||||
private const TOKEN_STATE_PROCESSED = 'processed';
|
||||
|
||||
/**
|
||||
* Transient key for storing token state.
|
||||
*/
|
||||
private const TOKEN_STATE_TRANSIENT = 'ppcp_auth_token_state';
|
||||
|
||||
/**
|
||||
* ID of the current settings page; empty if not on a PayPal settings page.
|
||||
*
|
||||
|
@ -133,34 +144,65 @@ class ConnectionListener {
|
|||
* @return void
|
||||
*/
|
||||
private function process_oauth_token( string $token ) : void {
|
||||
// The request contains OAuth details: To avoid abuse we'll slow down the processing.
|
||||
sleep( 2 );
|
||||
|
||||
if ( ! $token ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$log_token = ( (string) substr( $token, 0, 2 ) ) . '...' . ( (string) substr( $token, - 6 ) );
|
||||
|
||||
if ( $this->was_token_processed( $token ) ) {
|
||||
/*
|
||||
* Token already processed:
|
||||
* Do nothing as the DB already contains the full connection details.
|
||||
*/
|
||||
$this->logger->info( 'Token already processed, continuing silently', array( 'token' => $log_token ) );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_token_processing( $token ) ) {
|
||||
/*
|
||||
* Authentication token is currently processed (in another request):
|
||||
* Briefly wait and then retry this request, basically waiting for
|
||||
* the above "was_processed" condition to become true.
|
||||
*/
|
||||
$this->logger->info( 'Token is currently being processed, waiting and retrying', array( 'token' => $log_token ) );
|
||||
sleep( 1 );
|
||||
|
||||
// Get the current URL.
|
||||
$current_url = add_query_arg( null, null );
|
||||
|
||||
// Add time to the query parameter to prevent browser cache issues.
|
||||
$current_url = add_query_arg( 'retry', microtime( true ), $current_url );
|
||||
|
||||
wp_safe_redirect( $current_url );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! $this->url_manager->validate_token_and_delete( $token, $this->user_id ) ) {
|
||||
$this->logger->error( 'Token validation failed', array( 'token' => $log_token ) );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->extract_data();
|
||||
if ( ! $data ) {
|
||||
$this->logger->error( 'Failed to extract merchant data from request' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->info( 'Found OAuth merchant data in request', $data );
|
||||
|
||||
try {
|
||||
$this->authentication_manager->finish_oauth_authentication( $data );
|
||||
$this->mark_token_as_processed( $token );
|
||||
$this->set_token_state( $token, self::TOKEN_STATE_PROCESSING );
|
||||
|
||||
$this->authentication_manager->handle_oauth_authentication( $data );
|
||||
} catch ( \Exception $e ) {
|
||||
$this->logger->error( 'Failed to complete authentication: ' . $e->getMessage() );
|
||||
}
|
||||
|
||||
$this->set_token_state( $token, self::TOKEN_STATE_PROCESSED );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,29 +236,57 @@ class ConnectionListener {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided authentication token is new or has been used before.
|
||||
* Sets the state for a token.
|
||||
*
|
||||
* This check catches an issue where we receive the same authentication token twice,
|
||||
* which does not impact the login flow but creates noise in the logs.
|
||||
*
|
||||
* @param string $token The authentication token to check.
|
||||
* @return bool True if the token was already processed.
|
||||
* @param string $token The token to set state for.
|
||||
* @param string $state The state to set.
|
||||
* @return void
|
||||
*/
|
||||
private function was_token_processed( string $token ) : bool {
|
||||
$prev_token = get_transient( 'ppcp_previous_auth_token' );
|
||||
private function set_token_state( string $token, string $state ) : void {
|
||||
$data = array(
|
||||
'token' => $token,
|
||||
'state' => $state,
|
||||
);
|
||||
|
||||
return $prev_token && $prev_token === $token;
|
||||
// 10 second expiration will block the page for max 10 seconds.
|
||||
set_transient( self::TOKEN_STATE_TRANSIENT, $data, 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the processed authentication token so we can prevent double-processing
|
||||
* of already verified token.
|
||||
* Gets the current state of a token.
|
||||
*
|
||||
* @param string $token The processed authentication token.
|
||||
* @return void
|
||||
* @param string $token The token to check.
|
||||
* @return string The current state of the token, or empty string if the token doesn't match.
|
||||
*/
|
||||
private function mark_token_as_processed( string $token ) : void {
|
||||
set_transient( 'ppcp_previous_auth_token', $token, 60 );
|
||||
private function get_token_state( string $token ) : string {
|
||||
$data = get_transient( self::TOKEN_STATE_TRANSIENT );
|
||||
|
||||
if ( empty( $data ) || ! is_array( $data ) || empty( $data['token'] ) || empty( $data['state'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Only return the state if the token matches.
|
||||
return ( $data['token'] === $token ) ? $data['state'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the token is currently being processed.
|
||||
*
|
||||
* @param string $token The token to check.
|
||||
* @return bool True if the token is currently being processed, false otherwise.
|
||||
*/
|
||||
private function is_token_processing( string $token ) : bool {
|
||||
return $this->get_token_state( $token ) === self::TOKEN_STATE_PROCESSING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the token has been fully processed.
|
||||
*
|
||||
* @param string $token The token to check.
|
||||
* @return bool True if the token has been processed, false otherwise.
|
||||
*/
|
||||
private function was_token_processed( string $token ) : bool {
|
||||
return $this->get_token_state( $token ) === self::TOKEN_STATE_PROCESSED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,11 +9,9 @@ declare( strict_types = 1 );
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service;
|
||||
|
||||
use JsonException;
|
||||
use Throwable;
|
||||
use JsonException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller;
|
||||
|
@ -24,6 +22,7 @@ use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Helper\EnvironmentConfig;
|
||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||
use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
|
||||
use WooCommerce\PayPalCommerce\Settings\DTO\OAuthConnectionDTO;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||
use WooCommerce\PayPalCommerce\Settings\Enum\SellerTypeEnum;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
||||
|
@ -33,6 +32,12 @@ use WooCommerce\PayPalCommerce\Settings\Endpoint\CommonRestEndpoint;
|
|||
* Class that manages the connection to PayPal.
|
||||
*/
|
||||
class AuthenticationManager {
|
||||
|
||||
/**
|
||||
* Option name used to store the OAuth connection details.
|
||||
*/
|
||||
private const OAUTH_OPTION_NAME = 'ppcp_oauth_connection_details';
|
||||
|
||||
/**
|
||||
* Data model that stores the connection details.
|
||||
*
|
||||
|
@ -221,6 +226,65 @@ class AuthenticationManager {
|
|||
$this->update_connection_details( $connection );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the OAuth details in the DB.
|
||||
*
|
||||
* Those details are used in another request to complete the OAuth login process.
|
||||
*
|
||||
* @param string $shared_id The shared onboarding ID.
|
||||
* @param string $auth_code The authorization code.
|
||||
* @param bool $use_sandbox Whether it's a sandbox or production account.
|
||||
* @return void
|
||||
*/
|
||||
public function remember_oauth_connection_details( string $shared_id, string $auth_code, bool $use_sandbox ) : void {
|
||||
$this->logger->info(
|
||||
'Storing one-time OAuth credentials...',
|
||||
array(
|
||||
'shared_id' => $shared_id,
|
||||
'auth_code' => $auth_code,
|
||||
'use_sandbox' => $use_sandbox,
|
||||
)
|
||||
);
|
||||
|
||||
$oauth_connection = new OAuthConnectionDTO(
|
||||
$use_sandbox,
|
||||
$shared_id,
|
||||
$auth_code
|
||||
);
|
||||
|
||||
update_option( self::OAUTH_OPTION_NAME, $oauth_connection, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the temporary oauth credentials from the DB.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function remove_oauth_connection_details() : void {
|
||||
delete_option( self::OAUTH_OPTION_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previously remembered oauth details.
|
||||
*
|
||||
* @return OAuthConnectionDTO The stored oauth credentials.
|
||||
* @throws RuntimeException When no stored credentials are found, or they have expired.
|
||||
*/
|
||||
private function retrieve_oauth_connection_details() : OAuthConnectionDTO {
|
||||
$oauth_data = get_option( self::OAUTH_OPTION_NAME );
|
||||
|
||||
if ( ! $oauth_data instanceof OAuthConnectionDTO ) {
|
||||
throw new RuntimeException( 'No stored OAuth credentials found' );
|
||||
}
|
||||
|
||||
// Check for expiration (credentials expire after 1 hour).
|
||||
if ( time() - $oauth_data->timestamp > 3600 ) {
|
||||
$this->remove_oauth_connection_details();
|
||||
throw new RuntimeException( 'Stored OAuth credentials have expired' );
|
||||
}
|
||||
|
||||
return $oauth_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided ID and auth-code have a valid format.
|
||||
|
@ -236,7 +300,7 @@ class AuthenticationManager {
|
|||
* @return void
|
||||
* @throws RuntimeException When invalid shared ID or auth provided.
|
||||
*/
|
||||
public function validate_id_and_auth_code( string $shared_id, string $auth_code ) : void {
|
||||
private function validate_id_and_auth_code( string $shared_id, string $auth_code ) : void {
|
||||
if ( empty( $shared_id ) ) {
|
||||
throw new RuntimeException( 'No onboarding ID provided.' );
|
||||
}
|
||||
|
@ -255,10 +319,10 @@ class AuthenticationManager {
|
|||
* @param bool $use_sandbox Whether to use the sandbox mode.
|
||||
* @param string $shared_id The OAuth client ID.
|
||||
* @param string $auth_code The OAuth authorization code.
|
||||
* @return void
|
||||
* @return MerchantConnectionDTO A DTO containing the connection details.
|
||||
* @throws RuntimeException When failed to retrieve payee.
|
||||
*/
|
||||
public function authenticate_via_oauth( bool $use_sandbox, string $shared_id, string $auth_code ) : void {
|
||||
private function authenticate_via_oauth( bool $use_sandbox, string $shared_id, string $auth_code ) : MerchantConnectionDTO {
|
||||
$this->logger->info(
|
||||
'Attempting OAuth login to PayPal...',
|
||||
array(
|
||||
|
@ -283,29 +347,44 @@ class AuthenticationManager {
|
|||
$connection->client_secret = $credentials['client_secret'];
|
||||
$connection->merchant_id = $credentials['merchant_id'];
|
||||
|
||||
$this->update_connection_details( $connection );
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the merchant details in the final OAuth redirect and extracts
|
||||
* missing credentials from the URL.
|
||||
* Handles the full OAuth authentication flow.
|
||||
*
|
||||
* 1. Verify that the provided request contains required details.
|
||||
* 2. Retrieve the oauth-connection details and validate them.
|
||||
* 3. Convert the oauth-connection details into a permanent id/secret.
|
||||
* 4. Complete the authentication by storing all details in the DB.
|
||||
*
|
||||
* @param array $request_data Array of request parameters to process.
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException Missing or invalid credentials.
|
||||
*/
|
||||
public function finish_oauth_authentication( array $request_data ) : void {
|
||||
public function handle_oauth_authentication( array $request_data ) : void {
|
||||
$merchant_id = $request_data['merchant_id'] ?? '';
|
||||
$merchant_email = $request_data['merchant_email'] ?? '';
|
||||
$seller_type = $request_data['seller_type'] ?? '';
|
||||
|
||||
// 1. Verify the request details.
|
||||
if ( empty( $merchant_id ) || empty( $merchant_email ) ) {
|
||||
throw new RuntimeException( 'Missing merchant ID or email in request' );
|
||||
}
|
||||
|
||||
$connection = $this->common_settings->get_merchant_data();
|
||||
// 2. Retrieve and validate the oauth connection.
|
||||
$oauth_connection = $this->retrieve_oauth_connection_details();
|
||||
$this->validate_id_and_auth_code( $oauth_connection->shared_id, $oauth_connection->auth_token );
|
||||
|
||||
// 3. Trade oauth connection details to permanent client credentials.
|
||||
$connection = $this->authenticate_via_oauth(
|
||||
$oauth_connection->is_sandbox,
|
||||
$oauth_connection->shared_id,
|
||||
$oauth_connection->auth_token
|
||||
);
|
||||
|
||||
// 4. Complete the authentication checks and persist details.
|
||||
if ( $connection->merchant_id && $connection->merchant_id !== $merchant_id ) {
|
||||
throw new RuntimeException( 'Unexpected merchant ID in request' );
|
||||
}
|
||||
|
@ -317,6 +396,8 @@ class AuthenticationManager {
|
|||
$connection->seller_type = $seller_type;
|
||||
}
|
||||
|
||||
$this->remove_oauth_connection_details();
|
||||
|
||||
$this->update_connection_details( $connection );
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
/**
|
||||
* Provides functionality for settings migration management.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service\Migration
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service\Migration;
|
||||
|
||||
/**
|
||||
* Class MigrationManager
|
||||
*
|
||||
* Manages migration operations for plugin settings.
|
||||
*/
|
||||
class MigrationManager implements SettingsMigrationInterface {
|
||||
|
||||
protected SettingsMigration $general_settings_migration;
|
||||
protected SettingsTabMigration $settings_tab_migration;
|
||||
protected StylingSettingsMigration $styling_settings_migration;
|
||||
protected PaymentSettingsMigration $payment_settings_migration;
|
||||
|
||||
public function __construct(
|
||||
SettingsMigration $general_settings_migration,
|
||||
SettingsTabMigration $settings_tab_migration,
|
||||
StylingSettingsMigration $styling_settings_migration,
|
||||
PaymentSettingsMigration $payment_settings_migration
|
||||
) {
|
||||
$this->general_settings_migration = $general_settings_migration;
|
||||
$this->settings_tab_migration = $settings_tab_migration;
|
||||
$this->styling_settings_migration = $styling_settings_migration;
|
||||
$this->payment_settings_migration = $payment_settings_migration;
|
||||
}
|
||||
|
||||
public function migrate(): void {
|
||||
$this->general_settings_migration->migrate();
|
||||
$this->settings_tab_migration->migrate();
|
||||
$this->styling_settings_migration->migrate();
|
||||
$this->payment_settings_migration->migrate();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles migration of payment settings from legacy format to new structure.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service\Migration
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service\Migration;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\PaymentSettings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class PaymentSettingsMigration
|
||||
*
|
||||
* Handles migration of payment settings.
|
||||
*/
|
||||
class PaymentSettingsMigration implements SettingsMigrationInterface {
|
||||
|
||||
protected Settings $settings;
|
||||
protected PaymentSettings $payment_settings;
|
||||
|
||||
/**
|
||||
* The list of local apm methods.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected array $local_apms;
|
||||
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
PaymentSettings $payment_settings,
|
||||
array $local_apms
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->payment_settings = $payment_settings;
|
||||
$this->local_apms = $local_apms;
|
||||
}
|
||||
|
||||
public function migrate(): void {
|
||||
$allow_local_apm_gateways = $this->settings->has( 'allow_local_apm_gateways' ) && $this->settings->get( 'allow_local_apm_gateways' );
|
||||
|
||||
if ( $this->settings->has( 'disable_funding' ) ) {
|
||||
$disable_funding = (array) $this->settings->get( 'disable_funding' );
|
||||
if ( ! in_array( 'venmo', $disable_funding, true ) ) {
|
||||
$this->payment_settings->toggle_method_state( 'venmo', true );
|
||||
}
|
||||
|
||||
if ( ! $allow_local_apm_gateways ) {
|
||||
foreach ( $this->local_apms as $apm ) {
|
||||
if ( ! in_array( $apm['id'], $disable_funding, true ) ) {
|
||||
$this->payment_settings->toggle_method_state( $apm['id'], true );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->map() as $old_key => $method_name ) {
|
||||
if ( $this->settings->has( $old_key ) && $this->settings->get( $old_key ) ) {
|
||||
$this->payment_settings->toggle_method_state( $method_name, true );
|
||||
}
|
||||
}
|
||||
|
||||
$this->payment_settings->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps old setting keys to new payment method names.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function map(): array {
|
||||
return array(
|
||||
'dcc_enabled' => CreditCardGateway::ID,
|
||||
'axo_enabled' => AxoGateway::ID,
|
||||
'applepay_button_enabled' => ApplePayGateway::ID,
|
||||
'googlepay_button_enabled' => GooglePayGateway::ID,
|
||||
'pay_later_button_enabled' => 'pay-later',
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles migration of general settings from legacy format to new structure.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service\Migration
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service\Migration;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\GeneralSettings;
|
||||
use WooCommerce\PayPalCommerce\Settings\DTO\MerchantConnectionDTO;
|
||||
use WooCommerce\PayPalCommerce\Settings\Enum\SellerTypeEnum;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class GeneralSettingsMigration
|
||||
*
|
||||
* Handles migration of general plugin settings.
|
||||
*/
|
||||
class SettingsMigration implements SettingsMigrationInterface {
|
||||
|
||||
protected Settings $settings;
|
||||
protected GeneralSettings $general_settings;
|
||||
protected PartnersEndpoint $partners_endpoint;
|
||||
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
GeneralSettings $general_settings,
|
||||
PartnersEndpoint $partners_endpoint
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->general_settings = $general_settings;
|
||||
$this->partners_endpoint = $partners_endpoint;
|
||||
}
|
||||
|
||||
public function migrate(): void {
|
||||
if ( ! $this->settings->has( 'client_id' )
|
||||
|| ! $this->settings->has( 'client_secret' )
|
||||
|| ! $this->settings->has( 'merchant_id' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$connection = new MerchantConnectionDTO(
|
||||
$this->settings->has( 'sandbox_on' ) && $this->settings->get( 'sandbox_on' ),
|
||||
$this->settings->get( 'client_id' ),
|
||||
$this->settings->get( 'client_secret' ),
|
||||
$this->settings->get( 'merchant_id' ),
|
||||
$this->settings->has( 'merchant_email' ) ? $this->settings->get( 'merchant_email' ) : '',
|
||||
$this->partners_endpoint->seller_status()->country(),
|
||||
$this->merchant_account_type( $this->partners_endpoint->seller_status() )
|
||||
);
|
||||
|
||||
$this->general_settings->set_merchant_data( $connection );
|
||||
$this->general_settings->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the merchant account type based on seller status capabilities.
|
||||
*
|
||||
* Analyzes PayPal seller capabilities to determine if the account is a business
|
||||
* account or falls back to unknown.
|
||||
*
|
||||
* @param SellerStatus $seller_status
|
||||
* @return 'business' | 'unknown' The merchant account type (SellerTypeEnum constant).
|
||||
*/
|
||||
protected function merchant_account_type( SellerStatus $seller_status ): string {
|
||||
if ( $this->has_capability_active( $seller_status, 'COMMERCIAL_ENTITY' ) ) {
|
||||
return SellerTypeEnum::BUSINESS;
|
||||
}
|
||||
|
||||
$business_capabilities = array(
|
||||
'CUSTOM_CARD_PROCESSING',
|
||||
'CARD_PROCESSING_VIRTUAL_TERMINAL',
|
||||
'FRAUD_TOOL_ACCESS',
|
||||
'PAY_UPON_INVOICE',
|
||||
'SEND_INVOICE',
|
||||
);
|
||||
|
||||
foreach ( $business_capabilities as $capability ) {
|
||||
if ( $this->has_capability_active( $seller_status, $capability ) ) {
|
||||
return SellerTypeEnum::BUSINESS;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $seller_status->products() as $product ) {
|
||||
if ( $product->name() === 'PPCP_CUSTOM' &&
|
||||
$product->vetting_status() === 'SUBSCRIBED' ) {
|
||||
return SellerTypeEnum::BUSINESS;
|
||||
}
|
||||
}
|
||||
|
||||
return SellerTypeEnum::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific capability is active for the seller.
|
||||
*
|
||||
* @param SellerStatus $seller_status
|
||||
* @param string $capability_name
|
||||
* @return bool True if the capability is active, false otherwise.
|
||||
*/
|
||||
private function has_capability_active( SellerStatus $seller_status, string $capability_name ): bool {
|
||||
foreach ( $seller_status->capabilities() as $capability ) {
|
||||
if ( $capability->name() === $capability_name &&
|
||||
$capability->status() === 'ACTIVE' ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* Interface for settings migration classes.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service\Migration
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service\Migration;
|
||||
|
||||
/**
|
||||
* Interface SettingsMigrationInterface
|
||||
*
|
||||
* Defines the contract for all settings migration classes.
|
||||
*/
|
||||
interface SettingsMigrationInterface {
|
||||
/**
|
||||
* Migrates legacy settings to new data structure.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function migrate(): void;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles migration of settings tab settings from legacy format to new structure.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service\Migration
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service\Migration;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
|
||||
use WooCommerce\PayPalCommerce\Compat\Settings\SettingsTabMapHelper;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\SettingsModel;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class SettingsTabMigration
|
||||
*
|
||||
* Handles migration of settings tab settings.
|
||||
*/
|
||||
class SettingsTabMigration implements SettingsMigrationInterface {
|
||||
|
||||
protected Settings $settings;
|
||||
protected SettingsModel $settings_tab;
|
||||
protected SettingsTabMapHelper $settings_tab_map_helper;
|
||||
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
SettingsModel $settings_tab,
|
||||
SettingsTabMapHelper $settings_tab_map_helper
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->settings_tab = $settings_tab;
|
||||
$this->settings_tab_map_helper = $settings_tab_map_helper;
|
||||
}
|
||||
|
||||
public function migrate(): void {
|
||||
$data = array();
|
||||
|
||||
foreach ( $this->settings_tab_map_helper->map() as $old_key => $new_key ) {
|
||||
if ( ! $this->settings->has( $old_key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ( $old_key ) {
|
||||
case 'subtotal_mismatch_behavior':
|
||||
$value = $this->settings->get( $old_key );
|
||||
$data[ $new_key ] = $value === PurchaseUnitSanitizer::MODE_EXTRA_LINE ? 'correction' : 'no_details';
|
||||
break;
|
||||
case 'landing_page':
|
||||
$value = $this->settings->get( $old_key );
|
||||
$data[ $new_key ] = $value === ExperienceContext::LANDING_PAGE_LOGIN
|
||||
? 'login'
|
||||
: ( $value === ExperienceContext::LANDING_PAGE_GUEST_CHECKOUT
|
||||
? 'guest_checkout'
|
||||
: 'any'
|
||||
);
|
||||
break;
|
||||
case 'intent':
|
||||
$value = $this->settings->get( $old_key );
|
||||
$data['authorize_only'] = $value === 'authorize';
|
||||
$data['capture_virtual_orders'] = $value === 'capture';
|
||||
break;
|
||||
case 'blocks_final_review_enabled':
|
||||
$data[ $new_key ] = ! $this->settings->get( $old_key );
|
||||
break;
|
||||
case '3d_secure_contingency':
|
||||
$value = $this->settings->get( $old_key );
|
||||
$old_to_new_3d_secure_map = array_flip( SettingsTabMapHelper::THREE_D_SECURE_VALUES_MAP );
|
||||
$data[ $new_key ] = $old_to_new_3d_secure_map[ $value ] ?? 'NO_3D_SECURE';
|
||||
break;
|
||||
default:
|
||||
$data[ $new_key ] = $this->settings->get( $old_key );
|
||||
}
|
||||
}
|
||||
|
||||
$this->settings_tab->from_array( $data );
|
||||
$this->settings_tab->save();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles migration of styling settings from legacy format to new structure.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Settings\Service\Migration
|
||||
*/
|
||||
|
||||
declare( strict_types = 1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Settings\Service\Migration;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
|
||||
use WooCommerce\PayPalCommerce\Settings\Data\StylingSettings;
|
||||
use WooCommerce\PayPalCommerce\Settings\DTO\LocationStylingDTO;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class StylingSettingsMigration
|
||||
*
|
||||
* Handles migration of styling settings.
|
||||
*/
|
||||
class StylingSettingsMigration implements SettingsMigrationInterface {
|
||||
|
||||
protected Settings $settings;
|
||||
protected StylingSettings $styling_settings;
|
||||
|
||||
public function __construct( Settings $settings, StylingSettings $styling_settings ) {
|
||||
$this->settings = $settings;
|
||||
$this->styling_settings = $styling_settings;
|
||||
}
|
||||
|
||||
public function migrate(): void {
|
||||
$location_styles = array();
|
||||
|
||||
$styling_per_location = $this->settings->has( 'smart_button_enable_styling_per_location' ) && $this->settings->get( 'smart_button_enable_styling_per_location' );
|
||||
|
||||
foreach ( $this->locations_map() as $old_location => $new_location ) {
|
||||
$context = $styling_per_location ? $old_location : 'general';
|
||||
|
||||
$location_styles[ $new_location ] = new LocationStylingDTO(
|
||||
$new_location,
|
||||
$this->is_button_enabled_for_location( $old_location, 'smart' ),
|
||||
$this->enabled_methods( $old_location ),
|
||||
(string) ( $this->style_for_context( 'shape', $context ) ?? 'rect' ),
|
||||
(string) ( $this->style_for_context( 'label', $context ) ?? 'pay' ),
|
||||
(string) ( $this->style_for_context( 'color', $context ) ?? 'gold' ),
|
||||
(string) ( $this->style_for_context( 'layout', $context ) ?? 'vertical' ),
|
||||
(bool) ( $this->style_for_context( 'tagline', $context ) ?? false )
|
||||
);
|
||||
}
|
||||
|
||||
$this->styling_settings->from_array( $location_styles );
|
||||
$this->styling_settings->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which payment methods are enabled for a specific location.
|
||||
*
|
||||
* Checks legacy settings to determine which payment methods (PayPal, Pay Later,
|
||||
* Venmo, Apple Pay, Google Pay) should be enabled for the given location.
|
||||
*
|
||||
* @param string $location The location name ('cart', 'checkout', etc.).
|
||||
* @return string[] The list of enabled payment method IDs.
|
||||
*/
|
||||
protected function enabled_methods( string $location ): array {
|
||||
$methods = array( PayPalGateway::ID );
|
||||
|
||||
if ( $this->is_button_enabled_for_location( $location, 'pay_later' ) ) {
|
||||
$methods[] = 'pay-later';
|
||||
}
|
||||
|
||||
if ( $this->settings->has( 'disable_funding' ) ) {
|
||||
$disable_funding = $this->settings->get( 'disable_funding' );
|
||||
if ( ! in_array( 'venmo', $disable_funding, true ) ) {
|
||||
$methods[] = 'venmo';
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' ) ) {
|
||||
$methods[] = ApplePayGateway::ID;
|
||||
}
|
||||
|
||||
if ( $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' ) ) {
|
||||
$methods[] = GooglePayGateway::ID;
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a specific button type is enabled for a given location.
|
||||
*
|
||||
* @param string $location The location name ('cart', 'checkout', etc.).
|
||||
* @param string $type The button type ('smart', 'pay_later', etc.).
|
||||
* @return bool True if the button is enabled for the location, false otherwise.
|
||||
*/
|
||||
protected function is_button_enabled_for_location( string $location, string $type ): bool {
|
||||
$key = "{$type}_button_locations";
|
||||
if ( ! $this->settings->has( $key ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$enabled_locations = $this->settings->get( $key );
|
||||
|
||||
if ( $location === 'cart' ) {
|
||||
return in_array( $location, $enabled_locations, true ) || in_array( 'cart-block', $enabled_locations, true );
|
||||
}
|
||||
|
||||
return in_array( $location, $enabled_locations, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mapping of old button location names to new settings location names.
|
||||
*
|
||||
* @return string[] The mapping of old location names to new location names.
|
||||
*/
|
||||
protected function locations_map(): array {
|
||||
return array(
|
||||
'product' => 'product',
|
||||
'cart' => 'cart',
|
||||
'checkout' => 'classic_checkout',
|
||||
'mini-cart' => 'mini_cart',
|
||||
'checkout-block-express' => 'express_checkout',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the style value for a given property in a given context.
|
||||
*
|
||||
* Looks up style values with context-specific fallbacks. For cart context,
|
||||
* also checks cart-block variants.
|
||||
*
|
||||
* @param string $style The name of the style property ('shape', 'label', 'color', etc.).
|
||||
* @param string $location The location name ('cart', 'checkout', etc.).
|
||||
* @return string|bool|null The style value or null if not found.
|
||||
*/
|
||||
private function style_for_context( string $style, string $location ) {
|
||||
if ( $location === 'cart' ) {
|
||||
return $this->get_style_value( "button_{$location}_{$style}" )
|
||||
?? $this->get_style_value( "button_cart-block_{$style}" );
|
||||
}
|
||||
|
||||
return $this->get_style_value( "button_{$location}_{$style}" )
|
||||
?? $this->get_style_value( "button_{$style}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a style property value from legacy settings.
|
||||
*
|
||||
* @param string $key The style property key in the settings.
|
||||
* @return string|bool|null The style value or null if not found.
|
||||
*/
|
||||
private function get_style_value( string $key ) {
|
||||
if ( ! $this->settings->has( $key ) ) {
|
||||
return null;
|
||||
}
|
||||
return $this->settings->get( $key );
|
||||
}
|
||||
}
|
|
@ -87,13 +87,6 @@ class OnboardingUrlManager {
|
|||
return true;
|
||||
}
|
||||
|
||||
if ( OnboardingUrl::validate_previous_token( $this->cache, $token, $user_id ) ) {
|
||||
// TODO: Do we need this here? Previous logic was to reload the page without doing anything in this case.
|
||||
$this->logger->info( 'Validated previous token, silently redirecting: ' . $log_token );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->logger->error( 'Failed to validate onboarding ppcpToken: ' . $log_token );
|
||||
|
||||
return false;
|
||||
|
|
|
@ -11,6 +11,8 @@ namespace WooCommerce\PayPalCommerce\Settings;
|
|||
|
||||
use WC_Payment_Gateway;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\PartnerAttribution;
|
||||
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||
|
@ -36,6 +38,7 @@ use WooCommerce\PayPalCommerce\Settings\Handler\ConnectionListener;
|
|||
use WooCommerce\PayPalCommerce\Settings\Service\BrandedExperience\PathRepository;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\GatewayRedirectService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\LoadingScreenService;
|
||||
use WooCommerce\PayPalCommerce\Settings\Service\Migration\MigrationManager;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
|
@ -94,11 +97,35 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
add_filter(
|
||||
'woocommerce_paypal_payments_inside_settings_page_header',
|
||||
static fn() : string => sprintf(
|
||||
'<a href="#" class="button button-settings-switch-ui">%s</a>',
|
||||
esc_html__( 'Switch to new settings UI', 'woocommerce-paypal-payments' )
|
||||
'<button type="button" class="button button-settings-switch-ui" aria-describedby="switch-ui-desc">%s</button><span id="switch-ui-desc" class="screen-reader-text">%s</span>',
|
||||
esc_html__( 'Switch to new settings UI', 'woocommerce-paypal-payments' ),
|
||||
esc_html__( 'This action will permanently switch to the new settings interface and cannot be undone', 'woocommerce-paypal-payments' )
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds new settings discovery notice.
|
||||
*
|
||||
* @param Message[] $notices
|
||||
* @return Message[]
|
||||
*/
|
||||
add_filter(
|
||||
Repository::NOTICES_FILTER,
|
||||
static function ( array $notices ): array {
|
||||
$message = sprintf(
|
||||
// translators: %1$s is the URL for the startup guide.
|
||||
__(
|
||||
'🎉 <strong>Discover the new PayPal Payments settings!</strong> Enjoy a cleaner, faster interface. Check out the <a href="%1$s" target="_blank">Startup Guide</a>, then click <a href="#" class="settings-switch-ui" role="button" aria-describedby="switch-ui-desc"><strong>Switch to New Settings</strong></a> to activate it.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'https://woocommerce.com/document/woocommerce-paypal-payments/paypal-payments-startup-guide/'
|
||||
);
|
||||
|
||||
$notices[] = new Message( $message, 'info', false, 'ppcp-notice-wrapper' );
|
||||
return $notices;
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
static function () use ( $container ) {
|
||||
|
@ -119,8 +146,12 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
'ppcp-switch-settings-ui',
|
||||
'ppcpSwitchSettingsUi',
|
||||
array(
|
||||
'endpoint' => \WC_AJAX::get_endpoint( SwitchSettingsUiEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( SwitchSettingsUiEndpoint::nonce() ),
|
||||
'endpoint' => \WC_AJAX::get_endpoint( SwitchSettingsUiEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( SwitchSettingsUiEndpoint::nonce() ),
|
||||
'confirmMessage' => __(
|
||||
'Are you sure you want to switch to the new settings interface?This action cannot be undone.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -132,15 +163,14 @@ class SettingsModule implements ServiceModule, ExecutableModule {
|
|||
}
|
||||
);
|
||||
|
||||
$endpoint = $container->get( 'settings.ajax.switch_ui' ) ? $container->get( 'settings.ajax.switch_ui' ) : null;
|
||||
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . SwitchSettingsUiEndpoint::ENDPOINT,
|
||||
array(
|
||||
$endpoint,
|
||||
'handle_request',
|
||||
)
|
||||
static function () use ( $container ): void {
|
||||
$endpoint = $container->get( 'settings.ajax.switch_ui' ) ? $container->get( 'settings.ajax.switch_ui' ) : null;
|
||||
assert( $endpoint instanceof SwitchSettingsUiEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -9,18 +9,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\StatusReport;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
|
||||
use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
|
||||
use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookEventStorage;
|
||||
|
||||
/**
|
||||
|
@ -75,8 +75,8 @@ class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableMo
|
|||
$last_webhook_storage = $c->get( 'webhook.last-webhook-storage' );
|
||||
assert( $last_webhook_storage instanceof WebhookEventStorage );
|
||||
|
||||
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
|
||||
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
||||
$reference_transaction_status = $c->get( 'api.reference-transaction-status' );
|
||||
assert( $reference_transaction_status instanceof ReferenceTransactionStatus );
|
||||
|
||||
/* @var Renderer $renderer The renderer. */
|
||||
$renderer = $c->get( 'status-report.renderer' );
|
||||
|
@ -170,7 +170,7 @@ class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableMo
|
|||
'exported_label' => 'Reference Transactions',
|
||||
'description' => esc_html__( 'Whether Reference Transactions are enabled for the connected account', 'woocommerce-paypal-payments' ),
|
||||
'value' => $this->bool_to_html(
|
||||
$this->reference_transaction_enabled( $billing_agreements_endpoint )
|
||||
$reference_transaction_status->reference_transaction_enabled()
|
||||
),
|
||||
),
|
||||
array(
|
||||
|
@ -275,19 +275,6 @@ class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableMo
|
|||
return $field_settings['options'][ $subscriptions_mode ] ?? $subscriptions_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if reference transactions are enabled in account.
|
||||
*
|
||||
* @param BillingAgreementsEndpoint $billing_agreements_endpoint The endpoint.
|
||||
*/
|
||||
private function reference_transaction_enabled( BillingAgreementsEndpoint $billing_agreements_endpoint ): bool {
|
||||
try {
|
||||
return $billing_agreements_endpoint->reference_transaction_enabled();
|
||||
} catch ( RuntimeException $exception ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the bool value to "yes" icon or dash.
|
||||
*
|
||||
|
|
|
@ -11,51 +11,37 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers;
|
||||
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
|
||||
use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ShippingCallbackEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Settings\SettingsModule;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderReauthorizeAction;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\FeesUpdater;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\SendOnlyCountryNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Factory\SimpleRedirectTaskFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Factory\SimpleRedirectTaskFactoryInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrar;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrarInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Tasks\SimpleRedirectTask;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Shipping\ShippingCallbackUrlFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Endpoint\CartEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\CartFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\CartTotalsFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\MoneyFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\ShippingRatesFactory;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderAuthorizeAction;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderReauthorizeAction;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\FraudNetAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Cli\SettingsCommand;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\CaptureCardPayment;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ShippingCallbackEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSourceWebsiteId;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
|
||||
|
@ -69,17 +55,24 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PaymentSourceFac
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\FeesUpdater;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\InstallmentsProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\MerchantDetails;
|
||||
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;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\SendOnlyCountryNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\UnsupportedCurrencyAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
|
@ -89,11 +82,18 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
|
||||
use WooCommerce\PayPalCommerce\Axo\Helper\PropertiesDictionary;
|
||||
use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\ConnectionState;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\MerchantDetails;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Factory\SimpleRedirectTaskFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Factory\SimpleRedirectTaskFactoryInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrar;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrarInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Tasks\SimpleRedirectTask;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Shipping\ShippingCallbackUrlFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Endpoint\CartEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\CartFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\CartTotalsFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\MoneyFactory;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\StoreApi\Factory\ShippingRatesFactory;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
|
||||
return array(
|
||||
'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway {
|
||||
|
@ -547,9 +547,10 @@ return array(
|
|||
$container->get( 'http.redirector' ),
|
||||
$container->get( 'api.partner_merchant_id-production' ),
|
||||
$container->get( 'api.partner_merchant_id-sandbox' ),
|
||||
$container->get( 'api.endpoint.billing-agreements' ),
|
||||
$container->get( 'api.reference-transaction-status' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' ),
|
||||
new Cache( 'ppcp-client-credentials-cache' )
|
||||
new Cache( 'ppcp-client-credentials-cache' ),
|
||||
$container->get( 'api.reference-transaction-status-cache' )
|
||||
);
|
||||
},
|
||||
'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor {
|
||||
|
@ -651,9 +652,10 @@ return array(
|
|||
'wcgateway.settings.fields.subscriptions_mode' => static function ( ContainerInterface $container ): array {
|
||||
$subscription_mode_options = $container->get( 'wcgateway.settings.fields.subscriptions_mode_options' );
|
||||
|
||||
$billing_agreements_endpoint = $container->get( 'api.endpoint.billing-agreements' );
|
||||
$reference_transaction_enabled = $billing_agreements_endpoint->reference_transaction_enabled();
|
||||
if ( $reference_transaction_enabled !== true ) {
|
||||
$reference_transaction_status = $container->get( 'api.reference-transaction-status' );
|
||||
assert( $reference_transaction_status instanceof ReferenceTransactionStatus );
|
||||
|
||||
if ( ! $reference_transaction_status->reference_transaction_enabled() ) {
|
||||
unset( $subscription_mode_options['vaulting_api'] );
|
||||
}
|
||||
|
||||
|
@ -1767,10 +1769,10 @@ return array(
|
|||
$environment = $container->get( 'settings.environment' );
|
||||
assert( $environment instanceof Environment );
|
||||
|
||||
$billing_agreements_endpoint = $container->get( 'api.endpoint.billing-agreements' );
|
||||
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
||||
$reference_transaction_status = $container->get( 'api.reference-transaction-status' );
|
||||
assert( $reference_transaction_status instanceof ReferenceTransactionStatus );
|
||||
|
||||
$enabled = $billing_agreements_endpoint->reference_transaction_enabled();
|
||||
$enabled = $reference_transaction_status->reference_transaction_enabled();
|
||||
|
||||
$enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
|
||||
$disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
|
||||
|
|
|
@ -9,10 +9,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\CurrencyGetter;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\Environment;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
|
||||
/**
|
||||
|
@ -104,12 +104,7 @@ class SettingsPageAssets {
|
|||
*/
|
||||
private $is_acdc_enabled;
|
||||
|
||||
/**
|
||||
* Billing Agreements endpoint.
|
||||
*
|
||||
* @var BillingAgreementsEndpoint
|
||||
*/
|
||||
private $billing_agreements_endpoint;
|
||||
private $reference_transaction_status;
|
||||
|
||||
/**
|
||||
* Whether we're on a settings page for our plugin's payment methods.
|
||||
|
@ -121,20 +116,20 @@ class SettingsPageAssets {
|
|||
/**
|
||||
* Assets constructor.
|
||||
*
|
||||
* @param string $module_url The url of this module.
|
||||
* @param string $version The assets version.
|
||||
* @param SubscriptionHelper $subscription_helper The subscription helper.
|
||||
* @param string $client_id The PayPal SDK client ID.
|
||||
* @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop.
|
||||
* @param string $country 2-letter country code of the shop.
|
||||
* @param Environment $environment The environment object.
|
||||
* @param bool $is_pay_later_button_enabled Whether Pay Later button is enabled either for checkout, cart or product page.
|
||||
* @param array $disabled_sources The list of disabled funding sources.
|
||||
* @param array $all_funding_sources The list of all existing funding sources.
|
||||
* @param bool $is_settings_page Whether it's a settings page of this plugin.
|
||||
* @param bool $is_acdc_enabled Whether the ACDC gateway is enabled.
|
||||
* @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint.
|
||||
* @param bool $is_paypal_payment_method_page Whether we're on a settings page for our plugin's payment methods.
|
||||
* @param string $module_url The url of this module.
|
||||
* @param string $version The assets version.
|
||||
* @param SubscriptionHelper $subscription_helper The subscription helper.
|
||||
* @param string $client_id The PayPal SDK client ID.
|
||||
* @param CurrencyGetter $currency The getter of the 3-letter currency code of the shop.
|
||||
* @param string $country 2-letter country code of the shop.
|
||||
* @param Environment $environment The environment object.
|
||||
* @param bool $is_pay_later_button_enabled Whether Pay Later button is enabled either for checkout, cart or product page.
|
||||
* @param array $disabled_sources The list of disabled funding sources.
|
||||
* @param array $all_funding_sources The list of all existing funding sources.
|
||||
* @param bool $is_settings_page Whether it's a settings page of this plugin.
|
||||
* @param bool $is_acdc_enabled Whether the ACDC gateway is enabled.
|
||||
* @param ReferenceTransactionStatus $reference_transaction_status
|
||||
* @param bool $is_paypal_payment_method_page Whether we're on a settings page for our plugin's payment methods.
|
||||
*/
|
||||
public function __construct(
|
||||
string $module_url,
|
||||
|
@ -149,7 +144,7 @@ class SettingsPageAssets {
|
|||
array $all_funding_sources,
|
||||
bool $is_settings_page,
|
||||
bool $is_acdc_enabled,
|
||||
BillingAgreementsEndpoint $billing_agreements_endpoint,
|
||||
ReferenceTransactionStatus $reference_transaction_status,
|
||||
bool $is_paypal_payment_method_page
|
||||
) {
|
||||
$this->module_url = $module_url;
|
||||
|
@ -164,7 +159,7 @@ class SettingsPageAssets {
|
|||
$this->all_funding_sources = $all_funding_sources;
|
||||
$this->is_settings_page = $is_settings_page;
|
||||
$this->is_acdc_enabled = $is_acdc_enabled;
|
||||
$this->billing_agreements_endpoint = $billing_agreements_endpoint;
|
||||
$this->reference_transaction_status = $reference_transaction_status;
|
||||
$this->is_paypal_payment_method_page = $is_paypal_payment_method_page;
|
||||
}
|
||||
|
||||
|
@ -239,7 +234,7 @@ class SettingsPageAssets {
|
|||
),
|
||||
),
|
||||
),
|
||||
'reference_transaction_enabled' => $this->billing_agreements_endpoint->reference_transaction_enabled(),
|
||||
'reference_transaction_enabled' => $this->reference_transaction_status->reference_transaction_enabled(),
|
||||
'vaulting_must_enable_advanced_wallet_message' => sprintf(
|
||||
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
|
||||
esc_html__( 'Your PayPal account must be eligible to %1$ssave PayPal and Venmo payment methods%2$s to enable PayPal Vaulting.', 'woocommerce-paypal-payments' ),
|
||||
|
|
|
@ -9,10 +9,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
|
||||
|
||||
use DomainException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Exception;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
||||
|
@ -75,20 +79,34 @@ class ReturnUrlEndpoint {
|
|||
* Handles the incoming request.
|
||||
*/
|
||||
public function handle_request(): void {
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! isset( $_GET['token'] ) ) {
|
||||
wc_add_notice( __( 'Payment session expired. Please try placing your order again.', 'woocommerce-paypal-payments' ), 'error' );
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
$token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
try {
|
||||
$order = $this->order_endpoint->order( $token );
|
||||
} catch ( Exception $exception ) {
|
||||
$this->logger->warning( "Return URL endpoint failed to fetch order $token: " . $exception->getMessage() );
|
||||
wc_add_notice( __( 'Could not retrieve payment information. Please try again.', 'woocommerce-paypal-payments' ), 'error' );
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
$token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
$order = $this->order_endpoint->order( $token );
|
||||
|
||||
if ( $order->status()->is( OrderStatus::APPROVED )
|
||||
|| $order->status()->is( OrderStatus::COMPLETED )
|
||||
) {
|
||||
$this->session_handler->replace_order( $order );
|
||||
// Handle 3DS completion if needed.
|
||||
if ( $this->needs_3ds_completion( $order ) ) {
|
||||
try {
|
||||
$order = $this->complete_3ds_verification( $order );
|
||||
} catch ( Exception $e ) {
|
||||
$this->logger->warning( "3DS completion failed for order $token: " . $e->getMessage() );
|
||||
wc_add_notice( $this->get_3ds_error_message( $e ), 'error' );
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
$wc_order_id = (int) $order->purchase_units()[0]->custom_id();
|
||||
|
@ -102,12 +120,17 @@ class ReturnUrlEndpoint {
|
|||
}
|
||||
|
||||
$this->logger->warning( "Return URL endpoint $token: no WC order ID." );
|
||||
wc_add_notice( __( 'Order information is missing. Please try placing your order again.', 'woocommerce-paypal-payments' ), 'error' );
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
$wc_order = wc_get_order( $wc_order_id );
|
||||
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
|
||||
$this->logger->warning( "Return URL endpoint $token: WC order $wc_order_id not found." );
|
||||
|
||||
wc_add_notice( __( 'Order not found. Please try placing your order again.', 'woocommerce-paypal-payments' ), 'error' );
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
|
@ -117,7 +140,15 @@ class ReturnUrlEndpoint {
|
|||
exit();
|
||||
}
|
||||
|
||||
$success = $this->gateway->process_payment( $wc_order_id );
|
||||
$payment_gateway = $this->get_payment_gateway( $wc_order->get_payment_method() );
|
||||
if ( ! $payment_gateway ) {
|
||||
wc_add_notice( __( 'Payment gateway is unavailable. Please try again or contact support.', 'woocommerce-paypal-payments' ), 'error' );
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
$success = $payment_gateway->process_payment( $wc_order_id );
|
||||
|
||||
if ( isset( $success['result'] ) && 'success' === $success['result'] ) {
|
||||
add_filter(
|
||||
'allowed_redirect_hosts',
|
||||
|
@ -130,7 +161,95 @@ class ReturnUrlEndpoint {
|
|||
wp_safe_redirect( $success['redirect'] );
|
||||
exit();
|
||||
}
|
||||
|
||||
wc_add_notice( __( 'Payment processing failed. Please try again or contact support.', 'woocommerce-paypal-payments' ), 'error' );
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if order needs 3DS completion.
|
||||
*
|
||||
* @param Order $order The PayPal order.
|
||||
* @return bool
|
||||
*/
|
||||
private function needs_3ds_completion( Order $order ): bool {
|
||||
// If order is still CREATED after 3DS redirect, it needs to be captured.
|
||||
return $order->status()->is( OrderStatus::CREATED );
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete 3DS verification by capturing the order.
|
||||
*
|
||||
* @param mixed $order The PayPal order.
|
||||
* @return mixed The processed order.
|
||||
* @throws Exception When 3DS completion fails.
|
||||
* @throws RuntimeException When API errors occur that don't match decline patterns.
|
||||
*/
|
||||
private function complete_3ds_verification( $order ) {
|
||||
try {
|
||||
$captured_order = $this->order_endpoint->capture( $order );
|
||||
|
||||
// Check if capture actually succeeded vs. payment declined.
|
||||
if ( $captured_order->status()->is( OrderStatus::COMPLETED ) ) {
|
||||
return $captured_order;
|
||||
} else {
|
||||
// Capture API succeeded but payment was declined.
|
||||
throw new Exception( __( 'Payment was declined by the payment provider. Please try a different payment method.', 'woocommerce-paypal-payments' ) );
|
||||
}
|
||||
} catch ( DomainException $e ) {
|
||||
throw new Exception( __( '3D Secure authentication was unavailable or failed. Please try a different payment method or contact your bank.', 'woocommerce-paypal-payments' ) );
|
||||
} catch ( RuntimeException $e ) {
|
||||
if ( strpos( $e->getMessage(), 'declined' ) !== false ||
|
||||
strpos( $e->getMessage(), 'PAYMENT_DENIED' ) !== false ||
|
||||
strpos( $e->getMessage(), 'INSTRUMENT_DECLINED' ) !== false ||
|
||||
strpos( $e->getMessage(), 'Payment provider declined' ) !== false ) {
|
||||
throw new Exception( __( 'Your payment was declined after 3D Secure verification. Please try a different payment method or contact your bank.', 'woocommerce-paypal-payments' ) );
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user-friendly error message for 3DS failures.
|
||||
*
|
||||
* @param Exception $exception The exception.
|
||||
* @return string
|
||||
*/
|
||||
private function get_3ds_error_message( Exception $exception ): string {
|
||||
$error_message = $exception->getMessage();
|
||||
|
||||
if ( strpos( $error_message, '3D Secure' ) !== false ) {
|
||||
return $error_message;
|
||||
}
|
||||
|
||||
if ( strpos( $error_message, 'declined' ) !== false ) {
|
||||
return __( 'Your payment was declined after 3D Secure verification. Please try a different payment method or contact your bank.', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
|
||||
return __( 'There was an error processing your payment. Please try again or contact support.', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate payment gateway for the given payment method.
|
||||
*
|
||||
* @param string $payment_method The payment method ID.
|
||||
* @return \WC_Payment_Gateway|null
|
||||
*/
|
||||
private function get_payment_gateway( string $payment_method ) {
|
||||
|
||||
// For regular PayPal payments, use the injected gateway.
|
||||
if ( $payment_method === $this->gateway->id ) {
|
||||
return $this->gateway;
|
||||
}
|
||||
|
||||
// For other payment methods (like AXO), get from WooCommerce.
|
||||
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
|
||||
|
||||
if ( isset( $available_gateways[ $payment_method ] ) ) {
|
||||
return $available_gateways[ $payment_method ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ use WC_Order;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\FraudProcessorResponse;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\CardAuthenticationResultFactory;
|
||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +25,6 @@ trait CreditCardOrderInfoHandlingTrait {
|
|||
* Handles the 3DS details.
|
||||
*
|
||||
* Adds the order note with 3DS details.
|
||||
* Adds the order meta with 3DS details.
|
||||
*
|
||||
* @param Order $order The PayPal order.
|
||||
* @param WC_Order $wc_order The WC order.
|
||||
|
@ -35,7 +35,7 @@ trait CreditCardOrderInfoHandlingTrait {
|
|||
): void {
|
||||
|
||||
$payment_source = $order->payment_source();
|
||||
if ( ! $payment_source || $payment_source->name() !== 'card' ) {
|
||||
if ( ! $payment_source || ( $payment_source->name() !== 'card' && $payment_source->name() !== AxoGateway::ID ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,13 +13,12 @@ use Psr\Log\LoggerInterface;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\Http\RedirectorInterface;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
|
||||
|
@ -152,12 +151,7 @@ class SettingsListener {
|
|||
*/
|
||||
private $partner_merchant_id_sandbox;
|
||||
|
||||
/**
|
||||
* Billing Agreements endpoint.
|
||||
*
|
||||
* @var BillingAgreementsEndpoint
|
||||
*/
|
||||
private $billing_agreements_endpoint;
|
||||
private ReferenceTransactionStatus $reference_transaction_status;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
|
@ -173,26 +167,29 @@ class SettingsListener {
|
|||
*/
|
||||
private $client_credentials_cache;
|
||||
|
||||
protected Cache $reference_transaction_status_cache;
|
||||
|
||||
/**
|
||||
* SettingsListener constructor.
|
||||
*
|
||||
* @param Settings $settings The settings.
|
||||
* @param array $setting_fields The setting fields.
|
||||
* @param WebhookRegistrar $webhook_registrar The Webhook Registrar.
|
||||
* @param Cache $cache The Cache.
|
||||
* @param State $state The state.
|
||||
* @param Bearer $bearer The bearer.
|
||||
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
|
||||
* @param Cache $signup_link_cache The signup link cache.
|
||||
* @param array $signup_link_ids Signup link ids.
|
||||
* @param Cache $pui_status_cache The PUI status cache.
|
||||
* @param Cache $dcc_status_cache The DCC status cache.
|
||||
* @param RedirectorInterface $redirector The HTTP redirector.
|
||||
* @param string $partner_merchant_id_production Partner merchant ID production.
|
||||
* @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
|
||||
* @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint.
|
||||
* @param ?LoggerInterface $logger The logger.
|
||||
* @param Cache $client_credentials_cache The client credentials cache.
|
||||
* @param Settings $settings The settings.
|
||||
* @param array $setting_fields The setting fields.
|
||||
* @param WebhookRegistrar $webhook_registrar The Webhook Registrar.
|
||||
* @param Cache $cache The Cache.
|
||||
* @param State $state The state.
|
||||
* @param Bearer $bearer The bearer.
|
||||
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
|
||||
* @param Cache $signup_link_cache The signup link cache.
|
||||
* @param array $signup_link_ids Signup link ids.
|
||||
* @param Cache $pui_status_cache The PUI status cache.
|
||||
* @param Cache $dcc_status_cache The DCC status cache.
|
||||
* @param RedirectorInterface $redirector The HTTP redirector.
|
||||
* @param string $partner_merchant_id_production Partner merchant ID production.
|
||||
* @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
|
||||
* @param ReferenceTransactionStatus $reference_transaction_status
|
||||
* @param ?LoggerInterface $logger The logger.
|
||||
* @param Cache $client_credentials_cache The client credentials cache.
|
||||
* @param Cache $reference_transaction_status_cache The client credentials cache.
|
||||
*/
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
|
@ -209,30 +206,32 @@ class SettingsListener {
|
|||
RedirectorInterface $redirector,
|
||||
string $partner_merchant_id_production,
|
||||
string $partner_merchant_id_sandbox,
|
||||
BillingAgreementsEndpoint $billing_agreements_endpoint,
|
||||
ReferenceTransactionStatus $reference_transaction_status,
|
||||
LoggerInterface $logger = null,
|
||||
Cache $client_credentials_cache
|
||||
Cache $client_credentials_cache,
|
||||
Cache $reference_transaction_status_cache
|
||||
) {
|
||||
|
||||
// This is a legacy settings class, it's correctly relying on the `Status` class.
|
||||
|
||||
$this->settings = $settings;
|
||||
$this->setting_fields = $setting_fields;
|
||||
$this->webhook_registrar = $webhook_registrar;
|
||||
$this->cache = $cache;
|
||||
$this->state = $state;
|
||||
$this->bearer = $bearer;
|
||||
$this->page_id = $page_id;
|
||||
$this->signup_link_cache = $signup_link_cache;
|
||||
$this->signup_link_ids = $signup_link_ids;
|
||||
$this->pui_status_cache = $pui_status_cache;
|
||||
$this->dcc_status_cache = $dcc_status_cache;
|
||||
$this->redirector = $redirector;
|
||||
$this->partner_merchant_id_production = $partner_merchant_id_production;
|
||||
$this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
|
||||
$this->billing_agreements_endpoint = $billing_agreements_endpoint;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
$this->client_credentials_cache = $client_credentials_cache;
|
||||
$this->settings = $settings;
|
||||
$this->setting_fields = $setting_fields;
|
||||
$this->webhook_registrar = $webhook_registrar;
|
||||
$this->cache = $cache;
|
||||
$this->state = $state;
|
||||
$this->bearer = $bearer;
|
||||
$this->page_id = $page_id;
|
||||
$this->signup_link_cache = $signup_link_cache;
|
||||
$this->signup_link_ids = $signup_link_ids;
|
||||
$this->pui_status_cache = $pui_status_cache;
|
||||
$this->dcc_status_cache = $dcc_status_cache;
|
||||
$this->redirector = $redirector;
|
||||
$this->partner_merchant_id_production = $partner_merchant_id_production;
|
||||
$this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
|
||||
$this->reference_transaction_status = $reference_transaction_status;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
$this->client_credentials_cache = $client_credentials_cache;
|
||||
$this->reference_transaction_status_cache = $reference_transaction_status_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -394,9 +393,7 @@ class SettingsListener {
|
|||
$vault_enabled = wc_clean( wp_unslash( $_POST['ppcp']['vault_enabled'] ?? '' ) );
|
||||
$subscription_mode = wc_clean( wp_unslash( $_POST['ppcp']['subscriptions_mode'] ?? '' ) );
|
||||
|
||||
$reference_transaction_enabled = $this->billing_agreements_endpoint->reference_transaction_enabled();
|
||||
|
||||
if ( $reference_transaction_enabled !== true ) {
|
||||
if ( ! $this->reference_transaction_status->reference_transaction_enabled() ) {
|
||||
$this->settings->set( 'vault_enabled', false );
|
||||
|
||||
/**
|
||||
|
@ -413,7 +410,9 @@ class SettingsListener {
|
|||
$this->settings->persist();
|
||||
}
|
||||
|
||||
if ( $subscription_mode === 'vaulting_api' && $vault_enabled !== '1' && $reference_transaction_enabled === true ) {
|
||||
$reference_transaction_enabled = $this->reference_transaction_status->reference_transaction_enabled();
|
||||
|
||||
if ( $subscription_mode === 'vaulting_api' && $vault_enabled !== '1' && $reference_transaction_enabled ) {
|
||||
$this->settings->set( 'vault_enabled', true );
|
||||
$this->settings->persist();
|
||||
}
|
||||
|
@ -529,9 +528,8 @@ class SettingsListener {
|
|||
*/
|
||||
do_action( 'woocommerce_paypal_payments_on_listening_request' );
|
||||
|
||||
$ppcp_reference_transaction_enabled = get_transient( 'ppcp_reference_transaction_enabled' ) ?? '';
|
||||
if ( $ppcp_reference_transaction_enabled ) {
|
||||
delete_transient( 'ppcp_reference_transaction_enabled' );
|
||||
if ( $this->reference_transaction_status_cache->has( ReferenceTransactionStatus::CACHE_KEY ) ) {
|
||||
$this->reference_transaction_status_cache->delete( ReferenceTransactionStatus::CACHE_KEY );
|
||||
}
|
||||
|
||||
$redirect_url = false;
|
||||
|
|
|
@ -12,57 +12,57 @@ namespace WooCommerce\PayPalCommerce\WcGateway;
|
|||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductStatus;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ShippingCallbackEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\InstallmentsProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\SendOnlyCountryNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\CreditCardOrderInfoHandlingTrait;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderAuthorizeAction;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\FraudNetAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\SettingsPageAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Assets\VoidButtonAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ShippingCallbackEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\VoidOrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewayRepository;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\InstallmentsProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\SendOnlyCountryNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\UnsupportedCurrencyAdminNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\CreditCardOrderInfoHandlingTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\HeaderRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\WcTasks\Registrar\TaskRegistrarInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CardPaymentsConfiguration;
|
||||
use WooCommerce\PayPalCommerce\LocalAlternativePaymentMethods\LocalApmProductStatus;
|
||||
|
||||
/**
|
||||
* Class WcGatewayModule
|
||||
|
@ -214,7 +214,7 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
|||
$c->get( 'wcgateway.settings.funding-sources' ),
|
||||
$c->get( 'wcgateway.is-ppcp-settings-page' ),
|
||||
$dcc_configuration->is_enabled(),
|
||||
$c->get( 'api.endpoint.billing-agreements' ),
|
||||
$c->get( 'api.reference-transaction-status' ),
|
||||
$c->get( 'wcgateway.is-ppcp-settings-payment-methods-page' )
|
||||
);
|
||||
$assets->register_assets();
|
||||
|
@ -489,8 +489,12 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
|||
$pui_product_status->clear( $settings );
|
||||
}
|
||||
|
||||
$reference_transaction_status_cache = $c->get( 'api.reference-transaction-status-cache' );
|
||||
assert( $reference_transaction_status_cache instanceof Cache );
|
||||
// Clear Reference Transaction status.
|
||||
delete_transient( 'ppcp_reference_transaction_enabled' );
|
||||
if ( $reference_transaction_status_cache->has( ReferenceTransactionStatus::CACHE_KEY ) ) {
|
||||
$reference_transaction_status_cache->delete( ReferenceTransactionStatus::CACHE_KEY );
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -536,8 +540,8 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
|||
return $features;
|
||||
}
|
||||
|
||||
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
|
||||
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
|
||||
$reference_transaction_status = $c->get( 'api.reference-transaction-status' );
|
||||
assert( $reference_transaction_status instanceof ReferenceTransactionStatus );
|
||||
|
||||
$dcc_product_status = $c->get( 'wcgateway.helper.dcc-product-status' );
|
||||
assert( $dcc_product_status instanceof DCCProductStatus );
|
||||
|
@ -552,7 +556,7 @@ class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModul
|
|||
assert( is_callable( $contact_module_check ) );
|
||||
|
||||
$features['save_paypal_and_venmo'] = array(
|
||||
'enabled' => $billing_agreements_endpoint->reference_transaction_enabled(),
|
||||
'enabled' => $reference_transaction_status->reference_transaction_enabled(),
|
||||
);
|
||||
|
||||
$features['advanced_credit_and_debit_cards'] = array(
|
||||
|
|
|
@ -28,6 +28,7 @@ class OrderFactoryTest extends TestCase
|
|||
$order->expects('create_time')->andReturn($createTime);
|
||||
$order->expects('update_time')->andReturn($updateTime);
|
||||
$order->expects('payment_source')->andReturnNull();
|
||||
$order->expects('links')->andReturnNull();
|
||||
$wcOrder = Mockery::mock(\WC_Order::class);
|
||||
$purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class);
|
||||
$purchaseUnit = Mockery::mock(PurchaseUnit::class);
|
||||
|
@ -89,6 +90,11 @@ class OrderFactoryTest extends TestCase
|
|||
} else {
|
||||
$this->assertEquals($orderData->update_time, $order->update_time()->format(\DateTime::ISO8601));
|
||||
}
|
||||
if ( isset($orderData->links) ) {
|
||||
$this->assertEquals($orderData->links, $order->links());
|
||||
} else {
|
||||
$this->assertNull($order->links());
|
||||
}
|
||||
}
|
||||
|
||||
public function dataForTestFromPayPalResponseTest() : array
|
||||
|
@ -135,6 +141,20 @@ class OrderFactoryTest extends TestCase
|
|||
'update_time' => '2005-09-15T15:52:01+0000',
|
||||
],
|
||||
],
|
||||
'with_links' => [
|
||||
(object) [
|
||||
'id' => 'id',
|
||||
'purchase_units' => [new \stdClass(), new \stdClass()],
|
||||
'status' => OrderStatus::PAYER_ACTION_REQUIRED,
|
||||
'intent' => 'CAPTURE',
|
||||
'create_time' => '2005-08-15T15:52:01+0000',
|
||||
'update_time' => '2005-09-15T15:52:01+0000',
|
||||
'payer' => new \stdClass(),
|
||||
'links' => [
|
||||
(object) ['rel' => 'payer-action', 'href' => 'https://example.com/3ds']
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -181,13 +201,6 @@ class OrderFactoryTest extends TestCase
|
|||
'intent' => '',
|
||||
],
|
||||
],
|
||||
'no_status' => [
|
||||
(object) [
|
||||
'id' => '',
|
||||
'purchase_units' => [],
|
||||
'intent' => '',
|
||||
],
|
||||
],
|
||||
'no_intent' => [
|
||||
(object) [
|
||||
'id' => '',
|
||||
|
|
|
@ -32,6 +32,8 @@ class ModularTestCase extends TestCase
|
|||
when('WC')->justReturn((object) [
|
||||
'session' => null,
|
||||
]);
|
||||
when('is_admin')->justReturn(true);
|
||||
when('sanitize_key')->returnArg();
|
||||
|
||||
global $wpdb;
|
||||
$wpdb = \Mockery::mock(\stdClass::class);
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
|
||||
|
||||
use Mockery;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\Helper\RedirectorStub;
|
||||
use WooCommerce\PayPalCommerce\ModularTestCase;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use Mockery;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||
|
@ -43,10 +43,11 @@ class SettingsListenerTest extends ModularTestCase
|
|||
$signup_link_ids = array();
|
||||
$pui_status_cache = Mockery::mock(Cache::class);
|
||||
$dcc_status_cache = Mockery::mock(Cache::class);
|
||||
$billing_agreement_endpoint = Mockery::mock(BillingAgreementsEndpoint::class);
|
||||
$reference_transaction_status = Mockery::mock(ReferenceTransactionStatus::class);
|
||||
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
|
||||
$logger = Mockery::mock(LoggerInterface::class);
|
||||
$client_credentials_cache = Mockery::mock(Cache::class);
|
||||
$reference_transaction_status_cache = Mockery::mock(Cache::class);
|
||||
|
||||
$testee = new SettingsListener(
|
||||
$settings,
|
||||
|
@ -63,9 +64,10 @@ class SettingsListenerTest extends ModularTestCase
|
|||
new RedirectorStub(),
|
||||
'',
|
||||
'',
|
||||
$billing_agreement_endpoint,
|
||||
$reference_transaction_status,
|
||||
$logger,
|
||||
$client_credentials_cache
|
||||
$client_credentials_cache,
|
||||
$reference_transaction_status_cache
|
||||
);
|
||||
|
||||
$_GET['section'] = PayPalGateway::ID;
|
||||
|
@ -99,6 +101,8 @@ class SettingsListenerTest extends ModularTestCase
|
|||
->andReturn(false);
|
||||
$dcc_status_cache->shouldReceive('has')
|
||||
->andReturn(false);
|
||||
$reference_transaction_status_cache->shouldReceive('has')
|
||||
->andReturn(false);
|
||||
$client_credentials_cache->shouldReceive('has')->andReturn(true);
|
||||
$client_credentials_cache->shouldReceive('delete');
|
||||
|
||||
|
|
|
@ -11,5 +11,6 @@ require_once TESTS_ROOT_DIR . '/stubs/WC_Payment_Gateway_CC.php';
|
|||
require_once TESTS_ROOT_DIR . '/stubs/WC_Ajax.php';
|
||||
require_once TESTS_ROOT_DIR . '/stubs/WC_Checkout.php';
|
||||
require_once TESTS_ROOT_DIR . '/stubs/Task.php';
|
||||
require_once TESTS_ROOT_DIR . '/stubs/DefaultPaymentGateways.php';
|
||||
|
||||
Hamcrest\Util::registerGlobalFunctions();
|
||||
|
|
|
@ -4,15 +4,15 @@ namespace WooCommerce\PayPalCommerce\Tests\Integration;
|
|||
|
||||
use WC_Payment_Token;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Token;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
||||
/**
|
||||
* @group subscriptions
|
||||
|
@ -97,8 +97,8 @@ class VaultingSubscriptionsTest extends IntegrationMockedTestCase
|
|||
add_filter('user_has_cap', $user_has_cap_callback, 10, 3);
|
||||
|
||||
// Convert to Mockery mocks
|
||||
$billing_agreements_endpoint_mock = \Mockery::mock(BillingAgreementsEndpoint::class);
|
||||
$billing_agreements_endpoint_mock->shouldReceive('reference_transaction_enabled')
|
||||
$reference_transaction_status = \Mockery::mock(ReferenceTransactionStatus::class);
|
||||
$reference_transaction_status->shouldReceive('reference_transaction_enabled')
|
||||
->andReturn(true);
|
||||
|
||||
$state_mock = \Mockery::mock(State::class);
|
||||
|
@ -115,8 +115,8 @@ class VaultingSubscriptionsTest extends IntegrationMockedTestCase
|
|||
|
||||
// Create and configure the SettingsListener
|
||||
$c = $this->bootstrapModule([
|
||||
'api.endpoint.billing-agreements' => function () use ($billing_agreements_endpoint_mock) {
|
||||
return $billing_agreements_endpoint_mock;
|
||||
'api.endpoint.billing-agreements' => function () use ($reference_transaction_status) {
|
||||
return $reference_transaction_status;
|
||||
},
|
||||
'onboarding.state' => function () use ($state_mock) {
|
||||
return $state_mock;
|
||||
|
|
17
tests/stubs/DefaultPaymentGateways.php
Normal file
17
tests/stubs/DefaultPaymentGateways.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions;
|
||||
|
||||
class DefaultPaymentGateways
|
||||
{
|
||||
public static function get_all(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function get_wcpay_countries(): array
|
||||
{
|
||||
return ['US', 'CA', 'GB', 'AU', 'DE', 'FR'];
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue