diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 0edf1a8ae..0fbbb59dc 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; @@ -249,6 +250,13 @@ return array( $container->get( 'woocommerce.logger.woocommerce' ) ); }, + 'api.endpoint.payment-method-tokens' => static function( ContainerInterface $container ): PaymentMethodTokensEndpoint { + return new PaymentMethodTokensEndpoint( + $container->get( 'api.host' ), + $container->get( 'api.bearer' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, 'api.repository.application-context' => static function( ContainerInterface $container ) : ApplicationContextRepository { $settings = $container->get( 'wcgateway.settings' ); diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php new file mode 100644 index 000000000..c8e945954 --- /dev/null +++ b/modules/ppcp-api-client/src/Endpoint/PaymentMethodTokensEndpoint.php @@ -0,0 +1,109 @@ +host = $host; + $this->bearer = $bearer; + $this->logger = $logger; + } + + public function setup_tokens(PaymentSource $payment_source): stdClass { + $data = array( + 'payment_source' => array( + $payment_source->name() => $payment_source->properties() + ), + ); + + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v3/vault/setup-tokens'; + + $args = array( + 'method' => 'POST', + 'headers' => array( + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + 'PayPal-Request-Id' => uniqid( 'ppcp-', true ), + ), + '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 setup token.' ); + } + + $json = json_decode( $response['body'] ); + $status_code = (int) wp_remote_retrieve_response_code( $response ); + if ( ! in_array( $status_code, array( 200, 201 ), true ) ) { + throw new PayPalApiException( + $json, + $status_code + ); + } + + return $json; + } + + public function payment_tokens(PaymentSource $payment_source) { + $data = array( + 'payment_source' => array( + $payment_source->name() => $payment_source->properties() + ), + ); + + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v3/vault/payment-tokens'; + + $args = array( + 'method' => 'POST', + 'headers' => array( + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + 'PayPal-Request-Id' => uniqid( 'ppcp-', true ), + ), + '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 setup token.' ); + } + + $json = json_decode( $response['body'] ); + $status_code = (int) wp_remote_retrieve_response_code( $response ); + if ( ! in_array( $status_code, array( 200, 201 ), true ) ) { + throw new PayPalApiException( + $json, + $status_code + ); + } + + return $json; + } +} diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 439d1c04d..844315b67 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -25,7 +25,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; -use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; use WooCommerce\PayPalCommerce\Button\Exception\ValidationException; use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; diff --git a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js index a99cf0356..66913a859 100644 --- a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js +++ b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js @@ -7,6 +7,58 @@ import { import {setVisible} from "../../../ppcp-button/resources/js/modules/Helper/Hiding"; import {loadPaypalJsScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; +loadPaypalJsScript( + { + clientId: ppcp_add_payment_method.client_id, + merchantId: ppcp_add_payment_method.merchant_id, + dataUserIdToken: ppcp_add_payment_method.id_token, + }, + { + createVaultSetupToken: async () => { + const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, { + method: "POST", + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce, + }) + }) + + const result = await response.json() + + if(result.data.id) { + return result.data.id + } + }, + onApprove: async ({ vaultSetupToken }) => { + console.log(vaultSetupToken) + + const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, { + method: "POST", + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + }) + }) + + const result = await response.json() + console.log(result) + }, + onError: (error) => { + console.error(error) + } + }, + `#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method` +); + + + const init = () => { setVisible(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL); setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL); @@ -18,12 +70,6 @@ document.addEventListener( jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () { init() }); - - loadPaypalJsScript( - {}, - {}, - `#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method` - ); } ); diff --git a/modules/ppcp-save-payment-methods/services.php b/modules/ppcp-save-payment-methods/services.php index e4a984262..051bbfc95 100644 --- a/modules/ppcp-save-payment-methods/services.php +++ b/modules/ppcp-save-payment-methods/services.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavePaymentMethods; +use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken; +use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken; use WooCommerce\PayPalCommerce\SavePaymentMethods\Helper\SavePaymentMethodsApplies; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; @@ -52,4 +54,16 @@ return array( dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php' ); }, + 'save-payment-methods.endpoint.create-setup-token' => static function (ContainerInterface $container): CreateSetupToken { + return new CreateSetupToken( + $container->get( 'button.request-data' ), + $container->get('api.endpoint.payment-method-tokens') + ); + }, + 'save-payment-methods.endpoint.create-payment-token' => static function (ContainerInterface $container): CreatePaymentToken { + return new CreatePaymentToken( + $container->get('button.request-data'), + $container->get('api.endpoint.payment-method-tokens') + ); +} ); diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php new file mode 100644 index 000000000..459355f5b --- /dev/null +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php @@ -0,0 +1,61 @@ +request_data = $request_data; + $this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint; + } + + /** + * Returns the nonce. + * + * @return string + */ + public static function nonce(): string { + return self::ENDPOINT; + } + + /** + * Handles the request. + * + * @return bool + * @throws Exception On Error. + */ + public function handle_request(): bool { + try { + $data = $this->request_data->read_request( $this->nonce() ); + + $payment_source = new PaymentSource( + 'token', + (object) array( + 'id' => $data['vault_setup_token'], + 'type' => 'SETUP-TOKEN', + ) + ); + + $result = $this->payment_method_tokens_endpoint->payment_tokens( $payment_source ); + wp_send_json_success( $result ); + return true; + } catch ( Exception $exception ) { + wp_send_json_error(); + return false; + } + } +} diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php new file mode 100644 index 000000000..6541d9d4b --- /dev/null +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php @@ -0,0 +1,79 @@ +request_data = $request_data; + $this->payment_method_tokens_endpoint = $payment_method_tokens_endpoint; + } + + /** + * Returns the nonce. + * + * @return string + */ + public static function nonce(): string + { + return self::ENDPOINT; + } + + /** + * Handles the request. + * + * @return bool + * @throws Exception On Error. + */ + public function handle_request(): bool + { + try { + $this->request_data->read_request( $this->nonce() ); + + $payment_source = new PaymentSource( + 'paypal', + (object) array( + 'usage_type' => 'MERCHANT', + 'experience_context' => (object) array( + 'return_url' => esc_url( wc_get_account_endpoint_url( 'payment-methods' ) ), + 'cancel_url' => esc_url( wc_get_account_endpoint_url( 'add-payment-method' ) ), + ) + ) + ); + + $result = $this->payment_method_tokens_endpoint->setup_tokens($payment_source); + + wp_send_json_success($result); + return true; + } catch (Exception $exception) { + wp_send_json_error(); + return false; + } + } +} diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index 1a583e16b..a93b4921f 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -17,6 +17,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken; +use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal; @@ -58,7 +60,7 @@ class SavePaymentMethodsModule implements ModuleInterface { try { $target_customer_id = ''; - if ( get_current_user_id() !== 0 ) { + if ( is_user_logged_in() ) { $target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true ); } @@ -167,6 +169,48 @@ class SavePaymentMethodsModule implements ModuleInterface { $c->get( 'ppcp.asset-version' ), true ); + + $api = $c->get('api.user-id-token'); + assert($api instanceof UserIdToken); + + try { + $target_customer_id = ''; + if (is_user_logged_in()) { + $target_customer_id = get_user_meta(get_current_user_id(), '_ppcp_target_customer_id', true); + } + + $id_token = $api->id_token($target_customer_id); + + wp_localize_script( + 'ppcp-add-payment-method', + 'ppcp_add_payment_method', + array( + 'client_id' => $c->get( 'button.client_id' ), + 'merchant_id' => $c->get( 'api.merchant_id' ), + 'id_token' => $id_token, + 'ajax' => array( + 'create_setup_token' => array( + 'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ), + 'nonce' => wp_create_nonce( CreateSetupToken::nonce() ), + ), + 'create_payment_token' => array( + 'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentToken::ENDPOINT ), + 'nonce' => wp_create_nonce( CreatePaymentToken::nonce() ), + ), + ), + ) + ); + } catch (RuntimeException $exception) { + $logger = $c->get('woocommerce.logger.woocommerce'); + assert($logger instanceof LoggerInterface); + + $error = $exception->getMessage(); + if (is_a($exception, PayPalApiException::class)) { + $error = $exception->get_details($error); + } + + $logger->error($error); + } } ); @@ -180,5 +224,25 @@ class SavePaymentMethodsModule implements ModuleInterface { echo '
'; } ); + + add_action( + 'wc_ajax_' . CreateSetupToken::ENDPOINT, + static function () use ( $c ) { + $endpoint = $c->get( 'save-payment-methods.endpoint.create-setup-token' ); + assert($endpoint instanceof CreateSetupToken); + + $endpoint->handle_request(); + } + ); + + add_action( + 'wc_ajax_' . CreatePaymentToken::ENDPOINT, + static function () use ( $c ) { + $endpoint = $c->get( 'save-payment-methods.endpoint.create-payment-token' ); + assert($endpoint instanceof CreatePaymentToken); + + $endpoint->handle_request(); + } + ); } }