From 92a574f4fae096ab3b4559438d2654735bc29f06 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 29 Nov 2023 17:29:20 +0100 Subject: [PATCH] Add pay with save payment method (WIP) --- .../js/modules/Renderer/CardFieldsRenderer.js | 20 +- .../ppcp-save-payment-methods/services.php | 13 ++ .../src/Endpoint/CaptureCardPayment.php | 195 ++++++++++++++++++ .../src/SavePaymentMethodsModule.php | 83 +++++--- .../src/Gateway/CreditCardGateway.php | 15 +- 5 files changed, 298 insertions(+), 28 deletions(-) create mode 100644 modules/ppcp-save-payment-methods/src/Endpoint/CaptureCardPayment.php diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js index e930e9d89..3fc4e29f9 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js @@ -91,12 +91,30 @@ class CardFieldsRenderer { this.spinner.block(); this.errorHandler.clear(); + const paymentToken = document.querySelector('input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked').value + if(paymentToken !== 'new') { + fetch(this.defaultConfig.ajax.capture_card_payment.endpoint, { + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify({ + nonce: this.defaultConfig.ajax.capture_card_payment.nonce, + payment_token: paymentToken + }) + }).then((res) => { + return res.json(); + }).then((data) => { + document.querySelector('#place_order').click(); + }); + + return; + } + cardField.submit() .catch((error) => { this.spinner.unblock(); console.error(error) this.errorHandler.message(this.defaultConfig.hosted_fields.labels.fields_not_valid); - }) + }); }); } diff --git a/modules/ppcp-save-payment-methods/services.php b/modules/ppcp-save-payment-methods/services.php index ceee1efac..8089fbaf2 100644 --- a/modules/ppcp-save-payment-methods/services.php +++ b/modules/ppcp-save-payment-methods/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavePaymentMethods; +use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CaptureCardPayment; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken; use WooCommerce\PayPalCommerce\SavePaymentMethods\Helper\SavePaymentMethodsApplies; @@ -74,4 +75,16 @@ return array( $container->get( 'save-payment-methods.wc-payment-tokens' ) ); }, + 'save-payment-methods.endpoint.capture-card-payment' => static function( ContainerInterface $container): CaptureCardPayment { + return new CaptureCardPayment( + $container->get( 'button.request-data' ), + $container->get( 'api.host' ), + $container->get( 'api.bearer' ), + $container->get( 'api.factory.order' ), + $container->get('api.factory.purchase-unit'), + $container->get('api.endpoint.order'), + $container->get('session.handler'), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + } ); diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CaptureCardPayment.php b/modules/ppcp-save-payment-methods/src/Endpoint/CaptureCardPayment.php new file mode 100644 index 000000000..63b965bbd --- /dev/null +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CaptureCardPayment.php @@ -0,0 +1,195 @@ +request_data = $request_data; + $this->host = $host; + $this->bearer = $bearer; + $this->order_factory = $order_factory; + $this->purchase_unit_factory = $purchase_unit_factory; + $this->order_endpoint = $order_endpoint; + $this->logger = $logger; + $this->session_handler = $session_handler; + } + + /** + * Returns the nonce. + * + * @return string + */ + public static function nonce(): string { + return self::ENDPOINT; + } + + public function handle_request(): bool { + $data = $this->request_data->read_request( $this->nonce() ); + + $tokens = WC_Payment_Tokens::get_customer_tokens(get_current_user_id()); + foreach ($tokens as $token) { + if($token->get_id() === (int)$data['payment_token']) { + try { + $order = $this->create_order($token->get_token()); + + $id = $order->id ?? ''; + $status = $order->status ?? ''; + $payment_source = isset($order->payment_source->card) ? 'card' : ''; + if($id && $status && $payment_source) { + WC()->session->set('ppcp_saved_payment_card', array( + 'order_id' => $id, + 'status' => $status, + 'payment_source' => $payment_source + )); + + wp_send_json_success(); + return true; + } + } catch (RuntimeException $exception) { + wp_send_json_error(); + return false; + } + } + } + + wp_send_json_error(); + return false; + } + + /** + * Creates PayPal order from the given card vault id. + * + * @param string $vault_id + * @return stdClass + */ + private function create_order(string $vault_id): stdClass { + $items = array( $this->purchase_unit_factory->from_wc_cart()); + + $data = array( + 'intent' => 'CAPTURE', + 'purchase_units' => array_map( + static function ( PurchaseUnit $item ): array { + return $item->to_array( true, false ); + }, + $items + ), + 'payment_source' => array( + 'card' => array( + 'vault_id' => $vault_id + ), + ), + ); + + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v2/checkout/orders'; + $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 ( $response instanceof WP_Error ) { + throw new RuntimeException( $response->get_error_message() ); + } + + return json_decode( $response['body'] ); + } +} + diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index b00d9b1e2..4f3d89cc5 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -18,6 +18,7 @@ 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\SavePaymentMethods\Endpoint\CaptureCardPayment; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; @@ -50,37 +51,21 @@ class SavePaymentMethodsModule implements ModuleInterface { return; } - // Adds `id_token` to localized script data. add_filter( 'woocommerce_paypal_payments_localized_script_data', function( array $localized_script_data ) use ( $c ) { - $api = $c->get( 'api.user-id-token' ); - assert( $api instanceof UserIdToken ); + $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 ); - } + $logger = $c->get('woocommerce.logger.woocommerce'); + assert($logger instanceof LoggerInterface); - $id_token = $api->id_token( $target_customer_id ); - $localized_script_data['save_payment_methods'] = array( - 'id_token' => $id_token, - ); + $localized_script_data = $this->add_id_token_to_script_data($api, $logger, $localized_script_data); - $localized_script_data['data_client_id']['set_attribute'] = false; - - } 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 ); - } + $localized_script_data['ajax']['capture_card_payment'] = array( + 'endpoint' => \WC_AJAX::get_endpoint( CaptureCardPayment::ENDPOINT ), + 'nonce' => wp_create_nonce( CaptureCardPayment::nonce() ), + ); return $localized_script_data; } @@ -329,5 +314,53 @@ class SavePaymentMethodsModule implements ModuleInterface { return $supports; } ); + + add_action( + 'wc_ajax_' . CaptureCardPayment::ENDPOINT, + static function () use ( $c ) { + $endpoint = $c->get( 'save-payment-methods.endpoint.capture-card-payment' ); + assert( $endpoint instanceof CaptureCardPayment ); + + $endpoint->handle_request(); + } + ); + } + + /** + * Adds id token to localized script data. + * + * @param UserIdToken $api User id token api. + * @param LoggerInterface $logger The logger. + * @param array $localized_script_data The localized script data. + * @return array + */ + private function add_id_token_to_script_data( + UserIdToken $api, + LoggerInterface $logger, + array $localized_script_data + ): array { + 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); + $localized_script_data['save_payment_methods'] = array( + 'id_token' => $id_token, + ); + + $localized_script_data['data_client_id']['set_attribute'] = false; + + } catch (RuntimeException $exception) { + $error = $exception->getMessage(); + if (is_a($exception, PayPalApiException::class)) { + $error = $exception->get_details($error); + } + + $logger->error($error); + } + + return $localized_script_data; } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 29fc3064c..04c7664a2 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -11,13 +11,13 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; use Exception; use Psr\Log\LoggerInterface; -use WC_Customer; use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\VaultedCreditCardHandler; @@ -32,7 +32,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; */ class CreditCardGateway extends \WC_Payment_Gateway_CC { - use ProcessPaymentTrait, GatewaySettingsRendererTrait; + use ProcessPaymentTrait, GatewaySettingsRendererTrait, TransactionIdHandlingTrait; const ID = 'ppcp-credit-card-gateway'; @@ -365,6 +365,17 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { ); } + $saved_payment_card = WC()->session->get('ppcp_saved_payment_card'); + if($saved_payment_card) { + if($saved_payment_card['payment_source'] === 'card' && $saved_payment_card['status'] === 'COMPLETED') { + $this->update_transaction_id( $saved_payment_card['order_id'], $wc_order ); + $wc_order->payment_complete(); + WC()->session->set( 'ppcp_saved_payment_card', null ); + + return $this->handle_payment_success( $wc_order ); + } + } + /** * If customer has chosen a saved credit card payment. */