From 27e535095b1618bf1164a41961cd51b6b988af0a Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 28 Jun 2023 16:34:14 +0300 Subject: [PATCH 1/9] Remove not working payment_method in root --- .../src/Endpoint/OrderEndpoint.php | 7 -- .../src/Entity/ApplicationContext.php | 22 ----- .../src/Entity/PaymentMethod.php | 81 ------------------- .../src/Endpoint/CreateOrderEndpoint.php | 24 +----- 4 files changed, 1 insertion(+), 133 deletions(-) delete mode 100644 modules/ppcp-api-client/src/Entity/PaymentMethod.php diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index cf22e8106..10c81eb4a 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -18,7 +18,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; -use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; @@ -27,7 +26,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\ErrorResponse; use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository; -use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet; @@ -180,7 +178,6 @@ public function with_bn_code( string $bn_code ): OrderEndpoint { * @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values. * @param Payer|null $payer The payer off the order. * @param PaymentToken|null $payment_token The payment token. - * @param PaymentMethod|null $payment_method The payment method. * @param string $paypal_request_id The paypal request id. * @param string $user_action The user action. * @@ -192,7 +189,6 @@ public function create( string $shipping_preference, Payer $payer = null, PaymentToken $payment_token = null, - PaymentMethod $payment_method = null, string $paypal_request_id = '', string $user_action = ApplicationContext::USER_ACTION_CONTINUE ): Order { @@ -221,9 +217,6 @@ static function ( PurchaseUnit $item ) use ( $shipping_preference ): array { if ( $payment_token ) { $data['payment_source']['token'] = $payment_token->to_array(); } - if ( $payment_method ) { - $data['payment_method'] = $payment_method->to_array(); - } /** * The filter can be used to modify the order creation request body data. diff --git a/modules/ppcp-api-client/src/Entity/ApplicationContext.php b/modules/ppcp-api-client/src/Entity/ApplicationContext.php index 51a3ad005..2efe5b888 100644 --- a/modules/ppcp-api-client/src/Entity/ApplicationContext.php +++ b/modules/ppcp-api-client/src/Entity/ApplicationContext.php @@ -90,13 +90,6 @@ class ApplicationContext { */ private $cancel_url; - /** - * The payment method. - * - * @var null - */ - private $payment_method; - /** * ApplicationContext constructor. * @@ -136,9 +129,6 @@ public function __construct( $this->landing_page = $landing_page; $this->shipping_preference = $shipping_preference; $this->user_action = $user_action; - - // Currently we have not implemented the payment method. - $this->payment_method = null; } /** @@ -204,15 +194,6 @@ public function cancel_url(): string { return $this->cancel_url; } - /** - * Returns the payment method. - * - * @return PaymentMethod|null - */ - public function payment_method() { - return $this->payment_method; - } - /** * Returns the object as array. * @@ -223,9 +204,6 @@ public function to_array(): array { if ( $this->user_action() ) { $data['user_action'] = $this->user_action(); } - if ( $this->payment_method() ) { - $data['payment_method'] = $this->payment_method(); - } if ( $this->shipping_preference() ) { $data['shipping_preference'] = $this->shipping_preference(); } diff --git a/modules/ppcp-api-client/src/Entity/PaymentMethod.php b/modules/ppcp-api-client/src/Entity/PaymentMethod.php deleted file mode 100644 index 0362e0093..000000000 --- a/modules/ppcp-api-client/src/Entity/PaymentMethod.php +++ /dev/null @@ -1,81 +0,0 @@ -preferred = $preferred; - $this->selected = $selected; - } - - /** - * Returns the payer preferred value. - * - * @return string - */ - public function payee_preferred(): string { - return $this->preferred; - } - - /** - * Returns the payer selected value. - * - * @return string - */ - public function payer_selected(): string { - return $this->selected; - } - - /** - * Returns the object as array. - * - * @return array - */ - public function to_array(): array { - return array( - 'payee_preferred' => $this->payee_preferred(), - 'payer_selected' => $this->payer_selected(), - ); - } -} diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 686b87dd8..76e9e7f1a 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -19,7 +19,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; -use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; @@ -32,7 +31,6 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcGateway\CardBillingMode; -use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -448,7 +446,6 @@ private function create_paypal_order( \WC_Order $wc_order = null ): Order { $shipping_preference, $payer, null, - $this->payment_method(), '', $action ); @@ -471,8 +468,7 @@ function ( stdClass $detail ): bool { array( $this->purchase_unit ), $shipping_preference, $payer, - null, - $this->payment_method() + null ); } @@ -536,24 +532,6 @@ private function set_bn_code( array $data ) { $this->api_endpoint->with_bn_code( $bn_code ); } - /** - * Returns the PaymentMethod object for the order. - * - * @return PaymentMethod - */ - private function payment_method() : PaymentMethod { - try { - $payee_preferred = $this->settings->has( 'payee_preferred' ) && $this->settings->get( 'payee_preferred' ) ? - PaymentMethod::PAYEE_PREFERRED_IMMEDIATE_PAYMENT_REQUIRED - : PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED; - } catch ( NotFoundException $exception ) { - $payee_preferred = PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED; - } - - $payment_method = new PaymentMethod( $payee_preferred ); - return $payment_method; - } - /** * Checks whether the form fields are valid. * From 6c6e551c93bebc6d7af4f07995af0fa31c59a1e0 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 28 Jun 2023 16:35:05 +0300 Subject: [PATCH 2/9] Add PAYER_ACTION_REQUIRED status --- .../ppcp-api-client/src/Entity/OrderStatus.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-api-client/src/Entity/OrderStatus.php b/modules/ppcp-api-client/src/Entity/OrderStatus.php index e2dec053b..387830dd3 100644 --- a/modules/ppcp-api-client/src/Entity/OrderStatus.php +++ b/modules/ppcp-api-client/src/Entity/OrderStatus.php @@ -15,14 +15,15 @@ * Class OrderStatus */ class OrderStatus { - const INTERNAL = 'INTERNAL'; - const CREATED = 'CREATED'; - const SAVED = 'SAVED'; - const APPROVED = 'APPROVED'; - const VOIDED = 'VOIDED'; - const COMPLETED = 'COMPLETED'; - const PENDING_APPROVAL = 'PENDING_APPROVAL'; - const VALID_STATUS = array( + const INTERNAL = 'INTERNAL'; + const CREATED = 'CREATED'; + const SAVED = 'SAVED'; + const APPROVED = 'APPROVED'; + const VOIDED = 'VOIDED'; + const COMPLETED = 'COMPLETED'; + const PENDING_APPROVAL = 'PENDING_APPROVAL'; + const PAYER_ACTION_REQUIRED = 'PAYER_ACTION_REQUIRED'; + const VALID_STATUS = array( self::INTERNAL, self::CREATED, self::SAVED, @@ -30,6 +31,7 @@ class OrderStatus { self::VOIDED, self::COMPLETED, self::PENDING_APPROVAL, + self::PAYER_ACTION_REQUIRED, ); /** From aa573c9ec1a55be69f99bad9679601bafea40f4d Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 28 Jun 2023 16:36:37 +0300 Subject: [PATCH 3/9] Fix log --- .../src/Endpoint/OrderEndpoint.php | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index 10c81eb4a..db33690f3 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -247,16 +247,9 @@ static function ( PurchaseUnit $item ) use ( $shipping_preference ): array { $response = $this->request( $url, $args ); if ( is_wp_error( $response ) ) { - $error = new RuntimeException( - __( 'Could not create order.', 'woocommerce-paypal-payments' ) - ); - $this->logger->log( - 'warning', - $error->getMessage(), - array( - 'args' => $args, - 'response' => $response, - ) + $error = new RuntimeException( 'Could not create order.' ); + $this->logger->warning( + $error->getMessage() ); throw $error; } @@ -267,15 +260,10 @@ static function ( PurchaseUnit $item ) use ( $shipping_preference ): array { $json, $status_code ); - $this->logger->log( - 'warning', + $this->logger->warning( sprintf( 'Failed to create order. PayPal API response: %1$s', $error->getMessage() - ), - array( - 'args' => $args, - 'response' => $response, ) ); throw $error; From 5f696ac05d6a1f313fe3b2488d3e5df764d792f9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 3 Jul 2023 17:08:31 +0300 Subject: [PATCH 4/9] Remove unused/unreachable --- modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php index 815f8d73e..2ce2b244a 100644 --- a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php +++ b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php @@ -57,9 +57,6 @@ private function get_payment_source( Order $order ): ?string { if ( $source->card() ) { return 'card'; } - if ( $source->wallet() ) { - return 'wallet'; - } } return null; From b8cb024207545cd4a5343903b396f46d0f0b01b9 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 3 Jul 2023 21:04:25 +0300 Subject: [PATCH 5/9] Allow http 200 order create status code --- modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index db33690f3..ca747b9ea 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -255,7 +255,7 @@ static function ( PurchaseUnit $item ) use ( $shipping_preference ): array { } $json = json_decode( $response['body'] ); $status_code = (int) wp_remote_retrieve_response_code( $response ); - if ( 201 !== $status_code ) { + if ( ! in_array( $status_code, array( 200, 201 ), true ) ) { $error = new PayPalApiException( $json, $status_code From 3450999a40e10f0b3f165a8256d14757f73339f2 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 4 Jul 2023 12:58:36 +0300 Subject: [PATCH 6/9] Use payment_source + experience_context --- modules/ppcp-api-client/services.php | 10 +- .../src/Endpoint/OrderEndpoint.php | 21 +- .../src/Entity/ExperienceContext.php | 212 ++++++++++++++++++ .../ppcp-api-client/src/Entity/PayerName.php | 9 + .../src/Entity/PaymentSource.php | 59 +++-- .../src/Entity/PaymentSource/Bancontact.php | 99 ++++++++ .../src/Entity/PaymentSource/Blik.php | 119 ++++++++++ .../Card.php} | 45 ++-- .../src/Entity/PaymentSource/Eps.php | 99 ++++++++ .../src/Entity/PaymentSource/Giropay.php | 99 ++++++++ .../src/Entity/PaymentSource/Ideal.php | 119 ++++++++++ .../src/Entity/PaymentSource/MyBank.php | 99 ++++++++ .../src/Entity/PaymentSource/P24.php | 117 ++++++++++ .../src/Entity/PaymentSource/PayPal.php | 60 +++++ .../PaymentSource/PaymentSourceInterface.php | 26 +++ .../src/Entity/PaymentSource/Sofort.php | 99 ++++++++ .../src/Entity/PaymentSource/Trustly.php | 99 ++++++++ .../src/Entity/PaymentSourceWallet.php | 25 --- .../src/Entity/PaymentToken.php | 15 +- .../src/Factory/ExperienceContextFactory.php | 112 +++++++++ .../src/Factory/PaymentSourceFactory.php | 212 +++++++++++++++++- modules/ppcp-button/services.php | 1 + .../src/Endpoint/CreateOrderEndpoint.php | 24 +- .../ppcp-subscription/src/RenewalHandler.php | 3 +- .../src/VaultedCreditCardHandler.php | 3 +- .../ApiClient/Endpoint/OrderEndpointTest.php | 4 +- .../Endpoint/CreateOrderEndpointTest.php | 7 +- .../Subscription/RenewalHandlerTest.php | 5 +- .../Vaulting/VaultedCreditCardHandlerTest.php | 7 +- 29 files changed, 1702 insertions(+), 107 deletions(-) create mode 100644 modules/ppcp-api-client/src/Entity/ExperienceContext.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/Bancontact.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/Blik.php rename modules/ppcp-api-client/src/Entity/{PaymentSourceCard.php => PaymentSource/Card.php} (68%) create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/Eps.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/Giropay.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/MyBank.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/P24.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/PayPal.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/PaymentSourceInterface.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/Sofort.php create mode 100644 modules/ppcp-api-client/src/Entity/PaymentSource/Trustly.php delete mode 100644 modules/ppcp-api-client/src/Entity/PaymentSourceWallet.php create mode 100644 modules/ppcp-api-client/src/Factory/ExperienceContextFactory.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index ce363dabc..7091ac5d8 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -13,6 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans; use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory; +use WooCommerce\PayPalCommerce\ApiClient\Factory\ExperienceContextFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PlanFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory; @@ -268,6 +269,11 @@ 'api.factory.application-context' => static function ( ContainerInterface $container ) : ApplicationContextFactory { return new ApplicationContextFactory(); }, + 'api.factory.experience-context' => static function ( ContainerInterface $container ) : ExperienceContextFactory { + return new ExperienceContextFactory( + $container->get( 'wcgateway.settings' ) + ); + }, 'api.factory.payment-token' => static function ( ContainerInterface $container ) : PaymentTokenFactory { return new PaymentTokenFactory(); }, @@ -353,7 +359,9 @@ return new AddressFactory(); }, 'api.factory.payment-source' => static function ( ContainerInterface $container ): PaymentSourceFactory { - return new PaymentSourceFactory(); + return new PaymentSourceFactory( + $container->get( 'api.factory.experience-context' ) + ); }, 'api.factory.order' => static function ( ContainerInterface $container ): OrderFactory { $purchase_unit_factory = $container->get( 'api.factory.purchase-unit' ); diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php index ca747b9ea..d4f7f7a57 100644 --- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php @@ -18,6 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; @@ -175,9 +176,9 @@ public function with_bn_code( string $bn_code ): OrderEndpoint { * Creates an order. * * @param PurchaseUnit[] $items The purchase unit items for the order. - * @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values. + * @param string $shipping_preference One of ExperienceContext::SHIPPING_PREFERENCE_ values. * @param Payer|null $payer The payer off the order. - * @param PaymentToken|null $payment_token The payment token. + * @param PaymentSource|null $payment_source The payment source. * @param string $paypal_request_id The paypal request id. * @param string $user_action The user action. * @@ -188,14 +189,14 @@ public function create( array $items, string $shipping_preference, Payer $payer = null, - PaymentToken $payment_token = null, + ?PaymentSource $payment_source = null, string $paypal_request_id = '', string $user_action = ApplicationContext::USER_ACTION_CONTINUE ): Order { $bearer = $this->bearer->bearer(); $data = array( - 'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) ? 'AUTHORIZE' : $this->intent, - 'purchase_units' => array_map( + 'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) ? 'AUTHORIZE' : $this->intent, + 'purchase_units' => array_map( static function ( PurchaseUnit $item ) use ( $shipping_preference ): array { $data = $item->to_array(); @@ -208,15 +209,15 @@ static function ( PurchaseUnit $item ) use ( $shipping_preference ): array { }, $items ), - 'application_context' => $this->application_context_repository - ->current_context( $shipping_preference, $user_action )->to_array(), ); + if ( $payment_source ) { + $data['payment_source'] = $payment_source->to_array(); + } else { + $data['application_context'] = $this->application_context_repository->current_context( $shipping_preference, $user_action )->to_array(); + } if ( $payer && ! empty( $payer->email_address() ) ) { $data['payer'] = $payer->to_array(); } - if ( $payment_token ) { - $data['payment_source']['token'] = $payment_token->to_array(); - } /** * The filter can be used to modify the order creation request body data. diff --git a/modules/ppcp-api-client/src/Entity/ExperienceContext.php b/modules/ppcp-api-client/src/Entity/ExperienceContext.php new file mode 100644 index 000000000..1a9f9c451 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/ExperienceContext.php @@ -0,0 +1,212 @@ +return_url = $return_url; + $this->cancel_url = $cancel_url; + $this->brand_name = $brand_name; + $this->locale = $locale; + $this->landing_page = $landing_page; + $this->shipping_preference = $shipping_preference; + $this->user_action = $user_action; + $this->payment_method_preference = $payment_method_preference; + } + + /** + * Returns the return URL. + */ + public function return_url(): string { + return $this->return_url; + } + + /** + * Returns the cancel URL. + */ + public function cancel_url(): string { + return $this->cancel_url; + } + + /** + * Returns the brand name. + * + * @return string + */ + public function brand_name(): string { + return $this->brand_name; + } + + /** + * Returns the locale. + */ + public function locale(): string { + return $this->locale; + } + + /** + * Returns the landing page. + */ + public function landing_page(): string { + return $this->landing_page; + } + + /** + * Returns the shipping preference. + */ + public function shipping_preference(): string { + return $this->shipping_preference; + } + + /** + * Returns the user action. + */ + public function user_action(): string { + return $this->user_action; + } + + /** + * Returns the payment method preference. + */ + public function payment_method_preference(): string { + return $this->payment_method_preference; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array(); + if ( $this->user_action ) { + $data['user_action'] = $this->user_action; + } + if ( $this->shipping_preference ) { + $data['shipping_preference'] = $this->shipping_preference; + } + if ( $this->landing_page ) { + $data['landing_page'] = $this->landing_page; + } + if ( $this->payment_method_preference ) { + $data['payment_method_preference'] = $this->payment_method_preference; + } + if ( $this->locale ) { + $data['locale'] = $this->locale; + } + if ( $this->brand_name ) { + $data['brand_name'] = $this->brand_name; + } + if ( $this->return_url ) { + $data['return_url'] = $this->return_url; + } + if ( $this->cancel_url ) { + $data['cancel_url'] = $this->cancel_url; + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PayerName.php b/modules/ppcp-api-client/src/Entity/PayerName.php index 6cee5f0de..17fd60ce5 100644 --- a/modules/ppcp-api-client/src/Entity/PayerName.php +++ b/modules/ppcp-api-client/src/Entity/PayerName.php @@ -61,6 +61,15 @@ public function surname(): string { return $this->surname; } + /** + * Returns given name + surname. + * + * @return string + */ + public function full_name(): string { + return implode( ' ', array( $this->given_name, $this->surname ) ); + } + /** * Returns the object as array. * diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource.php b/modules/ppcp-api-client/src/Entity/PaymentSource.php index dcac61108..7fe9e1f97 100644 --- a/modules/ppcp-api-client/src/Entity/PaymentSource.php +++ b/modules/ppcp-api-client/src/Entity/PaymentSource.php @@ -9,58 +9,53 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Entity; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Card; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\PaymentSourceInterface; + /** * Class PaymentSource */ class PaymentSource { - /** - * The card. + * The map of source ID (paypal, card, ...) -> source object. * - * @var PaymentSourceCard|null + * @var array */ - private $card; - - /** - * The wallet. - * - * @var PaymentSourceWallet|null - */ - private $wallet; + private $sources = array(); /** * PaymentSource constructor. * - * @param PaymentSourceCard|null $card The card. - * @param PaymentSourceWallet|null $wallet The wallet. + * @param PaymentSourceInterface ...$sources The payment source objects. */ public function __construct( - PaymentSourceCard $card = null, - PaymentSourceWallet $wallet = null + PaymentSourceInterface ...$sources ) { - - $this->card = $card; - $this->wallet = $wallet; + foreach ( $sources as $source ) { + $this->sources[ $source->payment_source_id() ] = $source; + } } /** - * Returns the card. + * Returns the payment source objects. * - * @return PaymentSourceCard|null + * @return PaymentSourceInterface[] */ - public function card() { - - return $this->card; + public function sources(): array { + return $this->sources; } /** - * Returns the wallet. + * Returns the card. * - * @return PaymentSourceWallet|null + * @return Card|null */ - public function wallet() { - - return $this->wallet; + public function card() { + $card = $this->sources['card'] ?? null; + if ( $card instanceof Card ) { + return $card; + } + return null; } /** @@ -69,13 +64,9 @@ public function wallet() { * @return array */ public function to_array(): array { - $data = array(); - if ( $this->card() ) { - $data['card'] = $this->card()->to_array(); - } - if ( $this->wallet() ) { - $data['wallet'] = $this->wallet()->to_array(); + foreach ( $this->sources as $source ) { + $data[ $source->payment_source_id() ] = $source->to_array(); } return $data; } diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/Bancontact.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Bancontact.php new file mode 100644 index 000000000..7a08b4e47 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Bancontact.php @@ -0,0 +1,99 @@ +name = $name; + $this->country_code = $country_code; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'bancontact'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/Blik.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Blik.php new file mode 100644 index 000000000..e169c222c --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Blik.php @@ -0,0 +1,119 @@ +name = $name; + $this->country_code = $country_code; + $this->email = $email; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the email of the payer. + */ + public function email(): string { + return $this->email; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'blik'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->email ) { + $data['email'] = $this->email; + } + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSourceCard.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Card.php similarity index 68% rename from modules/ppcp-api-client/src/Entity/PaymentSourceCard.php rename to modules/ppcp-api-client/src/Entity/PaymentSource/Card.php index 1edc7eb37..b9789082b 100644 --- a/modules/ppcp-api-client/src/Entity/PaymentSourceCard.php +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Card.php @@ -1,18 +1,20 @@ authentication_result = $authentication_result; } + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'card'; + } + /** * Returns the last digits. * @@ -109,14 +118,22 @@ public function authentication_result() { * @return array */ public function to_array(): array { - - $data = array( - 'last_digits' => $this->last_digits(), - 'brand' => $this->brand(), - 'type' => $this->type(), - ); - if ( $this->authentication_result() ) { - $data['authentication_result'] = $this->authentication_result()->to_array(); + $data = array(); + if ( $this->last_digits ) { + $data['last_digits'] = $this->last_digits; + } + if ( $this->brand ) { + $data['brand'] = $this->brand; + } + if ( $this->type ) { + $data['type'] = $this->type; + } + if ( $this->authentication_result ) { + $data['authentication_result'] = $this->authentication_result->to_array(); + } + // Empty arrays become [] instead of {} in JSON. Difficult to fix properly now. + if ( ! $data ) { + $data['empty'] = 'obj'; } return $data; } diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/Eps.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Eps.php new file mode 100644 index 000000000..3eddffa34 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Eps.php @@ -0,0 +1,99 @@ +name = $name; + $this->country_code = $country_code; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'eps'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/Giropay.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Giropay.php new file mode 100644 index 000000000..3a9f716e5 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Giropay.php @@ -0,0 +1,99 @@ +name = $name; + $this->country_code = $country_code; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'giropay'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php new file mode 100644 index 000000000..753595ae6 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php @@ -0,0 +1,119 @@ +name = $name; + $this->country_code = $country_code; + $this->bic = $bic; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the bank identification code. + */ + public function bic(): string { + return $this->bic; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'ideal'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->bic ) { + $data['bic'] = $this->bic; + } + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/MyBank.php b/modules/ppcp-api-client/src/Entity/PaymentSource/MyBank.php new file mode 100644 index 000000000..9e746118f --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/MyBank.php @@ -0,0 +1,99 @@ +name = $name; + $this->country_code = $country_code; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'mybank'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/P24.php b/modules/ppcp-api-client/src/Entity/PaymentSource/P24.php new file mode 100644 index 000000000..0b6f0ae2e --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/P24.php @@ -0,0 +1,117 @@ +name = $name; + $this->country_code = $country_code; + $this->email = $email; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the email of the payer. + */ + public function email(): string { + return $this->email; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'p24'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + 'email' => $this->email, + ); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/PayPal.php b/modules/ppcp-api-client/src/Entity/PaymentSource/PayPal.php new file mode 100644 index 000000000..6d964acb3 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/PayPal.php @@ -0,0 +1,60 @@ +experience_context = $experience_context; + } + + /** + * The ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'paypal'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array(); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/PaymentSourceInterface.php b/modules/ppcp-api-client/src/Entity/PaymentSource/PaymentSourceInterface.php new file mode 100644 index 000000000..6efda43c2 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/PaymentSourceInterface.php @@ -0,0 +1,26 @@ +name = $name; + $this->country_code = $country_code; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'sofort'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/Trustly.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Trustly.php new file mode 100644 index 000000000..43b46d32c --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Trustly.php @@ -0,0 +1,99 @@ +name = $name; + $this->country_code = $country_code; + $this->experience_context = $experience_context; + } + + /** + * Returns the payer's full name. + */ + public function name(): string { + return $this->name; + } + + /** + * Returns the 2-letter country code of the payer. + */ + public function country_code(): string { + return $this->country_code; + } + + /** + * Returns the ExperienceContext. + * + * @return ExperienceContext|null + */ + public function experience_context(): ?ExperienceContext { + return $this->experience_context; + } + + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'trustly'; + } + + /** + * Returns the object as array. + */ + public function to_array(): array { + $data = array( + 'name' => $this->name, + 'country_code' => $this->country_code, + ); + if ( $this->experience_context ) { + $data['experience_context'] = $this->experience_context->to_array(); + } + return $data; + } +} diff --git a/modules/ppcp-api-client/src/Entity/PaymentSourceWallet.php b/modules/ppcp-api-client/src/Entity/PaymentSourceWallet.php deleted file mode 100644 index 18a5a7742..000000000 --- a/modules/ppcp-api-client/src/Entity/PaymentSourceWallet.php +++ /dev/null @@ -1,25 +0,0 @@ -id = $id; $this->type = $type; @@ -86,6 +85,13 @@ public function source(): \stdClass { return $this->source; } + /** + * Returns the payment source ID. + */ + public function payment_source_id(): string { + return 'token'; + } + /** * Returns the object as array. * @@ -116,5 +122,4 @@ public static function get_valid_types() { ) ); } - } diff --git a/modules/ppcp-api-client/src/Factory/ExperienceContextFactory.php b/modules/ppcp-api-client/src/Factory/ExperienceContextFactory.php new file mode 100644 index 000000000..be1e89d14 --- /dev/null +++ b/modules/ppcp-api-client/src/Factory/ExperienceContextFactory.php @@ -0,0 +1,112 @@ +settings = $settings; + } + + /** + * Returns an Experience Context based off a PayPal Response. + * + * @param stdClass $data The JSON object. + * + * @return ExperienceContext + */ + public function from_paypal_response( stdClass $data ): ExperienceContext { + return new ExperienceContext( + $data->return_url ?? '', + $data->cancel_url ?? '', + $data->brand_name ?? '', + $data->locale ?? '', + $data->landing_page ?? ExperienceContext::LANDING_PAGE_NO_PREFERENCE, + $data->shipping_preference ?? ExperienceContext::SHIPPING_PREFERENCE_GET_FROM_FILE, + $data->user_action ?? ExperienceContext::USER_ACTION_CONTINUE, + $data->payment_method_preference ?? ExperienceContext::PAYMENT_METHOD_UNRESTRICTED + ); + } + + /** + * Returns the current experience context, overriding some properties. + * + * @param string $shipping_preferences The shipping preferences. + * @param string $user_action The user action. + * + * @return ExperienceContext + */ + public function current_context( + string $shipping_preferences = ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING, + string $user_action = ExperienceContext::USER_ACTION_CONTINUE + ): ExperienceContext { + + $brand_name = $this->settings->has( 'brand_name' ) ? (string) $this->settings->get( 'brand_name' ) : ''; + $locale = $this->locale_to_bcp47( get_user_locale() ); + $landing_page = $this->settings->has( 'landing_page' ) ? + (string) $this->settings->get( 'landing_page' ) : ExperienceContext::LANDING_PAGE_NO_PREFERENCE; + $payment_preference = $this->settings->has( 'payee_preferred' ) && $this->settings->get( 'payee_preferred' ) ? + ExperienceContext::PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED : ExperienceContext::PAYMENT_METHOD_UNRESTRICTED; + return new ExperienceContext( + network_home_url( WC_AJAX::get_endpoint( ReturnUrlEndpoint::ENDPOINT ) ), + wc_get_checkout_url(), + $brand_name, + $locale, + $landing_page, + $shipping_preferences, + $user_action, + $payment_preference + ); + } + + /** + * Returns BCP-47 code supported by PayPal, for example de-DE-formal becomes de-DE. + * + * @param string $locale The locale, e.g. from get_user_locale. + */ + protected function locale_to_bcp47( string $locale ): string { + $locale = str_replace( '_', '-', $locale ); + + if ( preg_match( '/^[a-z]{2}(?:-[A-Z][a-z]{3})?(?:-(?:[A-Z]{2}))?$/', $locale ) ) { + return $locale; + } + + $parts = explode( '-', $locale ); + if ( count( $parts ) === 3 ) { + $ret = substr( $locale, 0, (int) strrpos( $locale, '-' ) ); + if ( false !== $ret ) { + return $ret; + } + } + + return 'en'; + } +} diff --git a/modules/ppcp-api-client/src/Factory/PaymentSourceFactory.php b/modules/ppcp-api-client/src/Factory/PaymentSourceFactory.php index b4efffb0e..700195caf 100644 --- a/modules/ppcp-api-client/src/Factory/PaymentSourceFactory.php +++ b/modules/ppcp-api-client/src/Factory/PaymentSourceFactory.php @@ -9,26 +9,58 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory; +use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Entity\CardAuthenticationResult; +use WooCommerce\PayPalCommerce\ApiClient\Entity\ExperienceContext; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; -use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSourceCard; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Bancontact; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Blik; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Card; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Eps; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Giropay; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Ideal; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\MyBank; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\P24; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\PaymentSourceInterface; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\PayPal; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Sofort; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Trustly; +use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; /** * Class PaymentSourceFactory */ class PaymentSourceFactory { + /** + * The experience context factory. + * + * @var ExperienceContextFactory + */ + private $experience_context_factory; + + /** + * PaymentSourceFactory constructor. + * + * @param ExperienceContextFactory $experience_context_factory The experience context factory. + */ + public function __construct( ExperienceContextFactory $experience_context_factory ) { + $this->experience_context_factory = $experience_context_factory; + } /** * Returns a PaymentSource for a PayPal Response. * - * @param \stdClass $data The JSON object. + * @param stdClass $data The JSON object. * * @return PaymentSource */ - public function from_paypal_response( \stdClass $data ): PaymentSource { + public function from_paypal_response( stdClass $data ): PaymentSource { + + $sources = array(); - $card = null; - $wallet = null; if ( isset( $data->card ) ) { $authentication_result = null; if ( isset( $data->card->authentication_result ) ) { @@ -41,13 +73,179 @@ public function from_paypal_response( \stdClass $data ): PaymentSource { (string) $data->card->authentication_result->three_d_secure->authentication_status : '' ); } - $card = new PaymentSourceCard( + $sources[] = new Card( isset( $data->card->last_digits ) ? (string) $data->card->last_digits : '', isset( $data->card->brand ) ? (string) $data->card->brand : '', isset( $data->card->type ) ? (string) $data->card->type : '', $authentication_result ); } - return new PaymentSource( $card, $wallet ); + // For now not handling other payment source objects here since we don't need them. + + return new PaymentSource( ...$sources ); + } + + /** + * Creates payment source for the current checkout. + * + * @param string $payment_method The payment gateway ID. + * @param string $funding_source The funding source ID. + * @param Payer|null $payer The payer. + * @param string $shipping_preferences One of ExperienceContext::SHIPPING_PREFERENCE_ values. + * @param string $user_action The user action. + * + * @throws RuntimeException When cannot create. + */ + public function from_checkout( + string $payment_method, + string $funding_source, + ?Payer $payer = null, + string $shipping_preferences = ExperienceContext::SHIPPING_PREFERENCE_NO_SHIPPING, + string $user_action = ExperienceContext::USER_ACTION_CONTINUE + ): PaymentSource { + $context = $this->experience_context_factory->current_context( $shipping_preferences, $user_action ); + + $source = null; + switch ( $payment_method ) { + case PayPalGateway::ID: + $source = $this->create_apm_object( $funding_source, $context, $payer ); + if ( ! $source ) { + $source = $this->create_paypal_object( $context ); + } + break; + case CreditCardGateway::ID: + // TODO: currently unclear how to handle ACDC properly here. + $source = new Card( '', '', '' ); + break; + } + + if ( ! $source ) { + throw new RuntimeException( "Cannot create payment source for $payment_method $funding_source" ); + } + + return new PaymentSource( $source ); + } + + /** + * Creates the PayPal payment source object. + * + * @param ExperienceContext $experience_context The experience context. + */ + private function create_paypal_object( + ExperienceContext $experience_context + ): PaymentSourceInterface { + return new PayPal( + $experience_context + ); + } + + /** + * Creates payment source object for the APM corresponding to the funding source ID. + * + * @param string $funding_source The funding source ID. + * @param ExperienceContext $experience_context The experience context. + * @param Payer|null $payer The payer. + */ + private function create_apm_object( + string $funding_source, + ExperienceContext $experience_context, + ?Payer $payer = null + ): ?PaymentSourceInterface { + if ( ! $payer ) { + return null; + } + + $address = $payer->address(); + $name = $payer->name(); + $full_name = $name ? $name->full_name() : ''; + + switch ( $funding_source ) { + case 'bancontact': + if ( $address && $address->country_code() && $full_name ) { + return new Bancontact( + $full_name, + $address->country_code(), + $experience_context + ); + } + break; + case 'blik': + if ( $address && $address->country_code() && $full_name ) { + return new Blik( + $full_name, + $address->country_code(), + $payer->email_address(), + $experience_context + ); + } + break; + case 'eps': + if ( $address && $address->country_code() && $full_name ) { + return new Eps( + $full_name, + $address->country_code(), + $experience_context + ); + } + break; + case 'giropay': + if ( $address && $address->country_code() && $full_name ) { + return new Giropay( + $full_name, + $address->country_code(), + $experience_context + ); + } + break; + case 'ideal': + if ( $address && $address->country_code() && $full_name ) { + return new Ideal( + $full_name, + $address->country_code(), + '', + $experience_context + ); + } + break; + case 'mybank': + if ( $address && $address->country_code() && $full_name ) { + return new MyBank( + $full_name, + $address->country_code(), + $experience_context + ); + } + break; + case 'p24': + if ( $address && $address->country_code() && $full_name && $payer->email_address() ) { + return new P24( + $full_name, + $address->country_code(), + $payer->email_address(), + $experience_context + ); + } + break; + case 'sofort': + if ( $address && $address->country_code() && $full_name ) { + return new Sofort( + $full_name, + $address->country_code(), + $experience_context + ); + } + break; + case 'trustly': + if ( $address && $address->country_code() && $full_name ) { + return new Trustly( + $full_name, + $address->country_code(), + $experience_context + ); + } + break; + } + + return null; } } diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index 56381fcc2..5d7b5b839 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -150,6 +150,7 @@ $request_data, $purchase_unit_factory, $container->get( 'api.factory.shipping-preference' ), + $container->get( 'api.factory.payment-source' ), $order_endpoint, $payer_factory, $session_handler, diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 76e9e7f1a..3be9e06a2 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -23,6 +23,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; +use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentSourceFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Button\Exception\ValidationException; @@ -66,6 +67,13 @@ class CreateOrderEndpoint implements EndpointInterface { */ private $shipping_preference_factory; + /** + * The PaymentSource factory. + * + * @var PaymentSourceFactory + */ + private $payment_source_factory; + /** * The order endpoint. * @@ -170,6 +178,7 @@ class CreateOrderEndpoint implements EndpointInterface { * @param RequestData $request_data The RequestData object. * @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory. * @param ShippingPreferenceFactory $shipping_preference_factory The shipping_preference factory. + * @param PaymentSourceFactory $payment_source_factory The PaymentSource factory. * @param OrderEndpoint $order_endpoint The OrderEndpoint object. * @param PayerFactory $payer_factory The PayerFactory object. * @param SessionHandler $session_handler The SessionHandler object. @@ -187,6 +196,7 @@ public function __construct( RequestData $request_data, PurchaseUnitFactory $purchase_unit_factory, ShippingPreferenceFactory $shipping_preference_factory, + PaymentSourceFactory $payment_source_factory, OrderEndpoint $order_endpoint, PayerFactory $payer_factory, SessionHandler $session_handler, @@ -204,6 +214,7 @@ public function __construct( $this->request_data = $request_data; $this->purchase_unit_factory = $purchase_unit_factory; $this->shipping_preference_factory = $shipping_preference_factory; + $this->payment_source_factory = $payment_source_factory; $this->api_endpoint = $order_endpoint; $this->payer_factory = $payer_factory; $this->session_handler = $session_handler; @@ -408,6 +419,7 @@ public function after_checkout_validation( array $data, \WP_Error $errors ): arr private function create_paypal_order( \WC_Order $wc_order = null ): Order { assert( $this->purchase_unit instanceof PurchaseUnit ); + $payment_method = $this->parsed_request_data['payment_method'] ?? ''; $funding_source = $this->parsed_request_data['funding_source'] ?? ''; $payer = $this->payer( $this->parsed_request_data, $wc_order ); @@ -440,12 +452,20 @@ private function create_paypal_order( \WC_Order $wc_order = null ): Order { } } + $payment_source = $this->payment_source_factory->from_checkout( + $payment_method, + $funding_source, + $payer, + $shipping_preference, + $action + ); + try { return $this->api_endpoint->create( array( $this->purchase_unit ), $shipping_preference, $payer, - null, + $payment_source, '', $action ); @@ -468,7 +488,7 @@ function ( stdClass $detail ): bool { array( $this->purchase_unit ), $shipping_preference, $payer, - null + $payment_source ); } diff --git a/modules/ppcp-subscription/src/RenewalHandler.php b/modules/ppcp-subscription/src/RenewalHandler.php index 3253c2f54..91cb2c03f 100644 --- a/modules/ppcp-subscription/src/RenewalHandler.php +++ b/modules/ppcp-subscription/src/RenewalHandler.php @@ -11,6 +11,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; @@ -191,7 +192,7 @@ private function process_order( \WC_Order $wc_order ): void { array( $purchase_unit ), $shipping_preference, $payer, - $token + new PaymentSource( $token ) ); $this->add_paypal_meta( $wc_order, $order, $this->environment ); diff --git a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php index 04b1c8d5d..816c588cf 100644 --- a/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php +++ b/modules/ppcp-vaulting/src/VaultedCreditCardHandler.php @@ -9,6 +9,7 @@ namespace WooCommerce\PayPalCommerce\Vaulting; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WC_Customer; use WC_Order; @@ -180,7 +181,7 @@ public function handle_payment( array( $purchase_unit ), $shipping_preference, $payer, - $selected_token + new PaymentSource( $selected_token ) ); $this->add_paypal_meta( $wc_order, $order, $this->environment ); diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php index 5041bf16a..de1a64a5e 100644 --- a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php +++ b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php @@ -1056,7 +1056,7 @@ public function testCreateForPurchaseUnitsIsWpError() $intent = 'CAPTURE'; $logger = Mockery::mock(LoggerInterface::class); - $logger->shouldReceive('log'); + $logger->shouldReceive('warning'); $logger->shouldReceive('debug'); $applicationContext = Mockery::mock(ApplicationContext::class); $applicationContext @@ -1147,7 +1147,7 @@ public function testCreateForPurchaseUnitsIsNot201() $intent = 'CAPTURE'; $logger = Mockery::mock(LoggerInterface::class); - $logger->shouldReceive('log'); + $logger->shouldReceive('warning'); $logger->shouldReceive('debug'); $applicationContext = Mockery::mock(ApplicationContext::class); $applicationContext diff --git a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php index 79f80de3f..c62f6f6a9 100644 --- a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php +++ b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php @@ -9,6 +9,7 @@ use ReflectionClass; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; +use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentSourceFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; @@ -146,8 +147,9 @@ public function dataForTestPhoneNumber() : array { protected function mockTestee() { $request_data = Mockery::mock(RequestData::class); - $shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class); + $shipping_preference_factory = Mockery::mock(ShippingPreferenceFactory::class); $purchase_unit_factory = Mockery::mock(PurchaseUnitFactory::class); + $payment_source_factory = Mockery::mock(PaymentSourceFactory::class); $order_endpoint = Mockery::mock(OrderEndpoint::class); $payer_factory = Mockery::mock(PayerFactory::class); $session_handler = Mockery::mock(SessionHandler::class); @@ -158,7 +160,8 @@ protected function mockTestee() $testee = new CreateOrderEndpoint( $request_data, $purchase_unit_factory, - $shippingPreferenceFactory, + $shipping_preference_factory, + $payment_source_factory, $order_endpoint, $payer_factory, $session_handler, diff --git a/tests/PHPUnit/Subscription/RenewalHandlerTest.php b/tests/PHPUnit/Subscription/RenewalHandlerTest.php index d079012c3..759dc447a 100644 --- a/tests/PHPUnit/Subscription/RenewalHandlerTest.php +++ b/tests/PHPUnit/Subscription/RenewalHandlerTest.php @@ -3,6 +3,7 @@ namespace WooCommerce\PayPalCommerce\Subscription; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Dictionary; use Exception; use Psr\Log\LoggerInterface; @@ -135,6 +136,8 @@ public function testRenewProcessOrder() $wcOrder ->expects('set_transaction_id'); + $token->shouldReceive('payment_source_id')->andReturn('token'); + $this->repository->shouldReceive('all_for_user_id') ->andReturn([$token]); @@ -151,7 +154,7 @@ public function testRenewProcessOrder() ->andReturn('no_shipping'); $this->orderEndpoint->shouldReceive('create') - ->with([$purchaseUnit], 'no_shipping', $payer, $token) + ->with([$purchaseUnit], 'no_shipping', $payer, Mockery::type(PaymentSource::class)) ->andReturn($order); $wcOrder->shouldReceive('update_status'); diff --git a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php index 8a7f9de51..2a62f4264 100644 --- a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php +++ b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php @@ -3,6 +3,7 @@ namespace PHPUnit\Vaulting; use Mockery; +use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource\Card; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WC_Customer; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; @@ -100,6 +101,7 @@ public function testHandlePayment() $token = Mockery::mock(PaymentToken::class); $tokenId = 'abc123'; $token->shouldReceive('id')->andReturn($tokenId); + $token->shouldReceive('payment_source_id')->andReturn('token'); $this->paymentTokenRepository->shouldReceive('all_for_user_id') ->andReturn([$token]); @@ -119,7 +121,8 @@ public function testHandlePayment() $order->shouldReceive('id')->andReturn('1'); $order->shouldReceive('intent')->andReturn('CAPTURE'); $paymentSource = Mockery::mock(PaymentSource::class); - $paymentSourceCard = Mockery::mock(PaymentSourceCard::class); + $paymentSourceCard = Mockery::mock(Card::class); + $paymentSourceCard->shouldReceive('payment_source_id')->andReturn('card'); $paymentSource->shouldReceive('card')->andReturn($paymentSourceCard); $order->shouldReceive('payment_source')->andReturn($paymentSource); $orderStatus = Mockery::mock(OrderStatus::class); @@ -138,7 +141,7 @@ public function testHandlePayment() $purchaseUnit->shouldReceive('payments')->andReturn($payments); $this->orderEndpoint->shouldReceive('create') - ->with([$purchaseUnit], 'some_preference', $payer, $token) + ->with([$purchaseUnit], 'some_preference', $payer, Mockery::type(PaymentSource::class)) ->andReturn($order); $this->environment->shouldReceive('current_environment_is')->andReturn(true); From 1bf4fb12aeeb0ca77802e61e177ee1ea9789940c Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 5 Jul 2023 16:39:03 +0300 Subject: [PATCH 7/9] Do not use new experience_context for existing installs by default --- .ddev/config.yaml | 1 + .../src/Endpoint/CreateOrderEndpoint.php | 18 +++++++++++------- modules/ppcp-uninstall/services.php | 1 + woocommerce-paypal-payments.php | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/.ddev/config.yaml b/.ddev/config.yaml index b56e10cf2..17adee5fb 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -28,6 +28,7 @@ web_environment: - ADMIN_EMAIL=admin@example.com - WC_VERSION=7.7.2 - PCP_BLOCKS_ENABLED=1 + - PPCP_FLAG_OLD_APPLICATION_CONTEXT=0 # Key features of ddev's config.yaml: diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 3be9e06a2..214fab17f 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -452,13 +452,17 @@ private function create_paypal_order( \WC_Order $wc_order = null ): Order { } } - $payment_source = $this->payment_source_factory->from_checkout( - $payment_method, - $funding_source, - $payer, - $shipping_preference, - $action - ); + if ( defined( 'PPCP_FLAG_OLD_APPLICATION_CONTEXT' ) && PPCP_FLAG_OLD_APPLICATION_CONTEXT ) { + $payment_source = null; + } else { + $payment_source = $this->payment_source_factory->from_checkout( + $payment_method, + $funding_source, + $payer, + $shipping_preference, + $action + ); + } try { return $this->api_endpoint->create( diff --git a/modules/ppcp-uninstall/services.php b/modules/ppcp-uninstall/services.php index 054a0d9dc..ab7f4a0d0 100644 --- a/modules/ppcp-uninstall/services.php +++ b/modules/ppcp-uninstall/services.php @@ -33,6 +33,7 @@ 'woocommerce-ppcp-version', WebhookSimulation::OPTION_ID, WebhookRegistrar::KEY, + 'woocommerce-ppcp-old-application-context', ); }, diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 4fad31bcd..e23d06dd3 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -44,6 +44,20 @@ function init(): void { define( 'PPCP_FLAG_SUBSCRIPTIONS_API', apply_filters( 'ppcp_flag_subscriptions_api', getenv( 'PPCP_FLAG_SUBSCRIPTIONS_API' ) === '1' ) ); + if ( get_option( 'woocommerce-ppcp-old-application-context', null ) === null ) { + // Use old format by default for existing installations. + update_option( 'woocommerce-ppcp-old-application-context', get_option( 'woocommerce-ppcp-version' ) !== false ); + } + $env_flag_old_application_context = getenv( 'PPCP_FLAG_OLD_APPLICATION_CONTEXT' ); + $env_flag_old_application_context = is_string( $env_flag_old_application_context ) ? (bool) $env_flag_old_application_context : null; + define( + 'PPCP_FLAG_OLD_APPLICATION_CONTEXT', + apply_filters( + 'ppcp_flag_old_application_context', + $env_flag_old_application_context !== null ? $env_flag_old_application_context : get_option( 'woocommerce-ppcp-old-application-context' ) + ) + ); + $root_dir = __DIR__; if ( ! is_woocommerce_activated() ) { From 406f78c57540bfecc8364430a9b6b9a0bb088390 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 5 Jul 2023 17:10:57 +0300 Subject: [PATCH 8/9] Do not use payment_source for acdc --- modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 214fab17f..e017be4d3 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -452,7 +452,10 @@ private function create_paypal_order( \WC_Order $wc_order = null ): Order { } } - if ( defined( 'PPCP_FLAG_OLD_APPLICATION_CONTEXT' ) && PPCP_FLAG_OLD_APPLICATION_CONTEXT ) { + if ( + ( defined( 'PPCP_FLAG_OLD_APPLICATION_CONTEXT' ) && PPCP_FLAG_OLD_APPLICATION_CONTEXT ) + || $payment_method === CreditCardGateway::ID + ) { $payment_source = null; } else { $payment_source = $this->payment_source_factory->from_checkout( From 7bb4112d80cf1dff0d3bb7c28b1027069699c850 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 5 Jul 2023 17:16:35 +0300 Subject: [PATCH 9/9] Fix phpdoc --- modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php b/modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php index 753595ae6..8c76aee99 100644 --- a/modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php +++ b/modules/ppcp-api-client/src/Entity/PaymentSource/Ideal.php @@ -44,7 +44,7 @@ class Ideal implements PaymentSourceInterface { private $experience_context; /** - * Sofort constructor. + * Ideal constructor. * * @param string $name The payer's full name. * @param string $country_code The 2-letter country code of the payer.