From a1dfb52cbbe32167e0ced5591fc379921879eaab Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 12 Dec 2023 12:23:47 +0100 Subject: [PATCH 1/8] Add card fields payment method (WIP) --- .../js/modules/Helper/ScriptLoading.js | 8 + .../resources/js/add-payment-method.js | 236 ++++++++++++++---- 2 files changed, 197 insertions(+), 47 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js index 6c7dc1a37..e3df5a27e 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js @@ -87,3 +87,11 @@ export const loadPaypalJsScript = (options, buttons, container) => { paypal.Buttons(buttons).render(container); }); } + +export const loadPaypalJsScriptPromise = (options) => { + return new Promise((resolve, reject) => { + loadScript(options) + .then(resolve) + .catch(reject); + }); +} 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 200c2b87b..c348cbfeb 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 @@ -8,59 +8,201 @@ import { setVisible, setVisibleByClass } 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, - }) +import { + loadPaypalJsScriptPromise +} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; + +const init = () => { + setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden'); + setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL); + + if(getCurrentPaymentMethod() === PaymentMethods.PAYPAL) { + loadPaypalJsScriptPromise({ + clientId: ppcp_add_payment_method.client_id, + merchantId: ppcp_add_payment_method.merchant_id, + dataUserIdToken: ppcp_add_payment_method.id_token + }) + .then((paypal) => { + paypal.Buttons( + { + 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}) => { + 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) + } + }, + ).render(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`); }) + .catch((error) => { + console.error(error); + }); + } + + if(getCurrentPaymentMethod() === PaymentMethods.CARDS) { + loadPaypalJsScriptPromise({ + clientId: ppcp_add_payment_method.client_id, + merchantId: ppcp_add_payment_method.merchant_id, + dataUserIdToken: ppcp_add_payment_method.id_token, + components: 'card-fields' + }, true) + .then((paypal) => { + const cardField = paypal.CardFields({ + 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}) => { + 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() - - if(result.data.id) { - return result.data.id - } - }, - onApprove: async ({ 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) + } + }); + + if (cardField.isEligible()) { + const nameField = document.getElementById('ppcp-credit-card-gateway-card-name'); + if (nameField) { + let styles = cardFieldStyles(nameField); + cardField.NameField({style: {'input': styles}}).render(nameField.parentNode); + nameField.hidden = true; + } + + const numberField = document.getElementById('ppcp-credit-card-gateway-card-number'); + if (numberField) { + let styles = cardFieldStyles(numberField); + cardField.NumberField({style: {'input': styles}}).render(numberField.parentNode); + numberField.hidden = true; + } + + const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry'); + if (expiryField) { + let styles = cardFieldStyles(expiryField); + cardField.ExpiryField({style: {'input': styles}}).render(expiryField.parentNode); + expiryField.hidden = true; + } + + const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc'); + if (cvvField) { + let styles = cardFieldStyles(cvvField); + cardField.CVVField({style: {'input': styles}}).render(cvvField.parentNode); + cvvField.hidden = true; + } + } + }) + .catch((error) => { + console.error(error) }) + } +} + +const cardFieldStyles = (field) => { + const allowedProperties = [ + 'appearance', + 'color', + 'direction', + 'font', + 'font-family', + 'font-size', + 'font-size-adjust', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-variant-alternates', + 'font-variant-caps', + 'font-variant-east-asian', + 'font-variant-ligatures', + 'font-variant-numeric', + 'font-weight', + 'letter-spacing', + 'line-height', + 'opacity', + 'outline', + 'padding', + 'padding-bottom', + 'padding-left', + 'padding-right', + 'padding-top', + 'text-shadow', + 'transition', + '-moz-appearance', + '-moz-osx-font-smoothing', + '-moz-tap-highlight-color', + '-moz-transition', + '-webkit-appearance', + '-webkit-osx-font-smoothing', + '-webkit-tap-highlight-color', + '-webkit-transition', + ]; - const result = await response.json() - console.log(result) - }, - onError: (error) => { - console.error(error) + const stylesRaw = window.getComputedStyle(field); + const styles = {}; + Object.values(stylesRaw).forEach((prop) => { + if (!stylesRaw[prop] || !allowedProperties.includes(prop)) { + return; } - }, - `#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method` -); + styles[prop] = '' + stylesRaw[prop]; + }); -const init = () => { - setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden'); - setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL); + return styles; } document.addEventListener( From 8a4bcc55a1d67c26728b21763cd6fe75f1738415 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 12 Dec 2023 16:15:07 +0100 Subject: [PATCH 2/8] Add click listener for place order button --- .../resources/js/add-payment-method.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 c348cbfeb..6874770b9 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 @@ -75,7 +75,7 @@ const init = () => { clientId: ppcp_add_payment_method.client_id, merchantId: ppcp_add_payment_method.merchant_id, dataUserIdToken: ppcp_add_payment_method.id_token, - components: 'card-fields' + components: 'card-fields', }, true) .then((paypal) => { const cardField = paypal.CardFields({ @@ -92,7 +92,6 @@ const init = () => { }) const result = await response.json() - if (result.data.id) { return result.data.id } @@ -147,6 +146,15 @@ const init = () => { cvvField.hidden = true; } } + + document.querySelector('#place_order').addEventListener("click", (event) => { + event.preventDefault(); + + cardField.submit() + .catch((error) => { + console.error(error) + }); + }); }) .catch((error) => { console.error(error) From 10e79cbcdc436a0e7ae135d52458e5c80100cbb7 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 12 Dec 2023 17:23:47 +0100 Subject: [PATCH 3/8] Create WC payment method from card payment token --- .../resources/js/add-payment-method.js | 2 ++ .../src/Endpoint/CreatePaymentToken.php | 36 ++++++++++++++----- .../src/Endpoint/CreateSetupToken.php | 16 +++++---- 3 files changed, 40 insertions(+), 14 deletions(-) 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 6874770b9..00d6b42a4 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 @@ -88,6 +88,7 @@ const init = () => { }, body: JSON.stringify({ nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce, + payment_method: PaymentMethods.CARDS }) }) @@ -106,6 +107,7 @@ const init = () => { body: JSON.stringify({ nonce: ppcp_add_payment_method.ajax.create_payment_token.nonce, vault_setup_token: vaultSetupToken, + payment_method: PaymentMethods.CARDS }) }) diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php index 151c9741c..8220b6fbd 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php @@ -15,6 +15,8 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; use WooCommerce\PayPalCommerce\SavePaymentMethods\WooCommercePaymentTokens; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; /** * Class CreatePaymentToken @@ -98,16 +100,34 @@ public function handle_request(): bool { if ( is_user_logged_in() && isset( $result->customer->id ) ) { update_user_meta( get_current_user_id(), '_ppcp_target_customer_id', $result->customer->id ); - $email = ''; - if ( isset( $result->payment_source->paypal->email_address ) ) { - $email = $result->payment_source->paypal->email_address; + $payment_method = $data['payment_method'] ?? ''; + if($payment_method === PayPalGateway::ID) { + $email = ''; + if ( isset( $result->payment_source->paypal->email_address ) ) { + $email = $result->payment_source->paypal->email_address; + } + + $this->wc_payment_tokens->create_payment_token_paypal( + get_current_user_id(), + $result->id, + $email + ); } - $this->wc_payment_tokens->create_payment_token_paypal( - get_current_user_id(), - $result->id, - $email - ); + if ($payment_method === CreditCardGateway::ID) { + $token = new \WC_Payment_Token_CC(); + $token->set_token($result->id); + $token->set_user_id(get_current_user_id()); + $token->set_gateway_id(CreditCardGateway::ID); + + $token->set_last4($result->payment_source->card->last_digits ?? ''); + $expiry = explode('-', $result->payment_source->card->expiry ?? ''); + $token->set_expiry_year($expiry[0] ?? ''); + $token->set_expiry_month($expiry[1] ?? ''); + $token->set_card_type($result->payment_source->card->brand ?? ''); + + $token->save(); + } } wp_send_json_success( $result ); diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php index 1490e61b6..8341633f1 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php @@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource; use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; /** * Class CreateSetupToken @@ -67,13 +68,8 @@ public static function nonce(): string { */ public function handle_request(): bool { try { - $this->request_data->read_request( $this->nonce() ); + $data = $this->request_data->read_request( $this->nonce() ); - /** - * Suppress ArgumentTypeCoercion - * - * @psalm-suppress ArgumentTypeCoercion - */ $payment_source = new PaymentSource( 'paypal', (object) array( @@ -85,6 +81,14 @@ public function handle_request(): bool { ) ); + $payment_method = $data['payment_method'] ?? ''; + if($payment_method === CreditCardGateway::ID) { + $payment_source = new PaymentSource( + 'card', + (object) array() + ); + } + $result = $this->payment_method_tokens_endpoint->setup_tokens( $payment_source ); wp_send_json_success( $result ); From 6c3f648e951c3b4d92e2d3b203f48705ab17eb2f Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 13 Dec 2023 12:24:04 +0100 Subject: [PATCH 4/8] Add redirect to payment methods page and handle error message --- .../js/modules/Helper/CardFieldsHelper.js | 50 +++++++++++ .../js/modules/Renderer/CardFieldsRenderer.js | 60 ++----------- .../resources/js/add-payment-method.js | 85 +++++++------------ .../src/SavePaymentMethodsModule.php | 2 + 4 files changed, 86 insertions(+), 111 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js diff --git a/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js b/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js new file mode 100644 index 000000000..5dc02d3d6 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js @@ -0,0 +1,50 @@ +export const cardFieldStyles = (field) => { + const allowedProperties = [ + 'appearance', + 'color', + 'direction', + 'font', + 'font-family', + 'font-size', + 'font-size-adjust', + 'font-stretch', + 'font-style', + 'font-variant', + 'font-variant-alternates', + 'font-variant-caps', + 'font-variant-east-asian', + 'font-variant-ligatures', + 'font-variant-numeric', + 'font-weight', + 'letter-spacing', + 'line-height', + 'opacity', + 'outline', + 'padding', + 'padding-bottom', + 'padding-left', + 'padding-right', + 'padding-top', + 'text-shadow', + 'transition', + '-moz-appearance', + '-moz-osx-font-smoothing', + '-moz-tap-highlight-color', + '-moz-transition', + '-webkit-appearance', + '-webkit-osx-font-smoothing', + '-webkit-tap-highlight-color', + '-webkit-transition', + ]; + + const stylesRaw = window.getComputedStyle(field); + const styles = {}; + Object.values(stylesRaw).forEach((prop) => { + if (!stylesRaw[prop] || !allowedProperties.includes(prop)) { + return; + } + styles[prop] = '' + stylesRaw[prop]; + }); + + return styles; +} diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js index 3fc4e29f9..edad0ec6c 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js @@ -1,4 +1,5 @@ import {show} from "../Helper/Hiding"; +import {cardFieldStyles} from "../Helper/CardFieldsHelper"; class CardFieldsRenderer { @@ -53,28 +54,28 @@ class CardFieldsRenderer { if (cardField.isEligible()) { const nameField = document.getElementById('ppcp-credit-card-gateway-card-name'); if (nameField) { - let styles = this.cardFieldStyles(nameField); + let styles = cardFieldStyles(nameField); cardField.NameField({style: {'input': styles}}).render(nameField.parentNode); nameField.remove(); } const numberField = document.getElementById('ppcp-credit-card-gateway-card-number'); if (numberField) { - let styles = this.cardFieldStyles(numberField); + let styles = cardFieldStyles(numberField); cardField.NumberField({style: {'input': styles}}).render(numberField.parentNode); numberField.remove(); } const expiryField = document.getElementById('ppcp-credit-card-gateway-card-expiry'); if (expiryField) { - let styles = this.cardFieldStyles(expiryField); + let styles = cardFieldStyles(expiryField); cardField.ExpiryField({style: {'input': styles}}).render(expiryField.parentNode); expiryField.remove(); } const cvvField = document.getElementById('ppcp-credit-card-gateway-card-cvc'); if (cvvField) { - let styles = this.cardFieldStyles(cvvField); + let styles = cardFieldStyles(cvvField); cardField.CVVField({style: {'input': styles}}).render(cvvField.parentNode); cvvField.remove(); } @@ -118,57 +119,6 @@ class CardFieldsRenderer { }); } - cardFieldStyles(field) { - const allowedProperties = [ - 'appearance', - 'color', - 'direction', - 'font', - 'font-family', - 'font-size', - 'font-size-adjust', - 'font-stretch', - 'font-style', - 'font-variant', - 'font-variant-alternates', - 'font-variant-caps', - 'font-variant-east-asian', - 'font-variant-ligatures', - 'font-variant-numeric', - 'font-weight', - 'letter-spacing', - 'line-height', - 'opacity', - 'outline', - 'padding', - 'padding-bottom', - 'padding-left', - 'padding-right', - 'padding-top', - 'text-shadow', - 'transition', - '-moz-appearance', - '-moz-osx-font-smoothing', - '-moz-tap-highlight-color', - '-moz-transition', - '-webkit-appearance', - '-webkit-osx-font-smoothing', - '-webkit-tap-highlight-color', - '-webkit-transition', - ]; - - const stylesRaw = window.getComputedStyle(field); - const styles = {}; - Object.values(stylesRaw).forEach((prop) => { - if (!stylesRaw[prop] || !allowedProperties.includes(prop)) { - return; - } - styles[prop] = '' + stylesRaw[prop]; - }); - - return styles; - } - disableFields() {} enableFields() {} } 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 00d6b42a4..be17568b0 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 @@ -11,6 +11,13 @@ import { import { loadPaypalJsScriptPromise } from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; +import ErrorHandler from "../../../ppcp-button/resources/js/modules/ErrorHandler"; +import {cardFieldStyles} from "../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper"; + +const errorHandler = new ErrorHandler( + PayPalCommerceGateway.labels.error.generic, + document.querySelector('.woocommerce-notices-wrapper') +); const init = () => { setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden'); @@ -23,6 +30,8 @@ const init = () => { dataUserIdToken: ppcp_add_payment_method.id_token }) .then((paypal) => { + errorHandler.clear(); + paypal.Buttons( { createVaultSetupToken: async () => { @@ -38,10 +47,11 @@ const init = () => { }) const result = await response.json() - if (result.data.id) { return result.data.id } + + errorHandler.message(ppcp_add_payment_method.error_message); }, onApprove: async ({vaultSetupToken}) => { const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, { @@ -56,11 +66,16 @@ const init = () => { }) }) - const result = await response.json() - console.log(result) + const result = await response.json(); + if(result.success === true) { + window.location.href = ppcp_add_payment_method.payment_methods_page; + } + + errorHandler.message(ppcp_add_payment_method.error_message); }, onError: (error) => { console.error(error) + errorHandler.message(ppcp_add_payment_method.error_message); } }, ).render(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`); @@ -78,6 +93,8 @@ const init = () => { components: 'card-fields', }, true) .then((paypal) => { + errorHandler.clear(); + const cardField = paypal.CardFields({ createVaultSetupToken: async () => { const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, { @@ -96,6 +113,8 @@ const init = () => { if (result.data.id) { return result.data.id } + + errorHandler.message(ppcp_add_payment_method.error_message); }, onApprove: async ({vaultSetupToken}) => { const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, { @@ -111,11 +130,16 @@ const init = () => { }) }) - const result = await response.json() - console.log(result) + const result = await response.json(); + if(result.success === true) { + window.location.href = ppcp_add_payment_method.payment_methods_page; + } + + errorHandler.message(ppcp_add_payment_method.error_message); }, onError: (error) => { console.error(error) + errorHandler.message(ppcp_add_payment_method.error_message); } }); @@ -164,57 +188,6 @@ const init = () => { } } -const cardFieldStyles = (field) => { - const allowedProperties = [ - 'appearance', - 'color', - 'direction', - 'font', - 'font-family', - 'font-size', - 'font-size-adjust', - 'font-stretch', - 'font-style', - 'font-variant', - 'font-variant-alternates', - 'font-variant-caps', - 'font-variant-east-asian', - 'font-variant-ligatures', - 'font-variant-numeric', - 'font-weight', - 'letter-spacing', - 'line-height', - 'opacity', - 'outline', - 'padding', - 'padding-bottom', - 'padding-left', - 'padding-right', - 'padding-top', - 'text-shadow', - 'transition', - '-moz-appearance', - '-moz-osx-font-smoothing', - '-moz-tap-highlight-color', - '-moz-transition', - '-webkit-appearance', - '-webkit-osx-font-smoothing', - '-webkit-tap-highlight-color', - '-webkit-transition', - ]; - - const stylesRaw = window.getComputedStyle(field); - const styles = {}; - Object.values(stylesRaw).forEach((prop) => { - if (!stylesRaw[prop] || !allowedProperties.includes(prop)) { - return; - } - styles[prop] = '' + stylesRaw[prop]; - }); - - return styles; -} - document.addEventListener( 'DOMContentLoaded', () => { diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index 8b7be4f21..e199db8d8 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -225,6 +225,8 @@ function() use ( $c ) { 'client_id' => $c->get( 'button.client_id' ), 'merchant_id' => $c->get( 'api.merchant_id' ), 'id_token' => $id_token, + 'payment_methods_page' => wc_get_account_endpoint_url('payment-methods'), + 'error_message' => __( 'Could not save payment method.', 'woocommerce-paypal-payments' ), 'ajax' => array( 'create_setup_token' => array( 'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ), From c3d0039f8385d561d37cfa2ad8a5d7e4c4ae62df Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 13 Dec 2023 14:17:46 +0100 Subject: [PATCH 5/8] Add 3ds verification method --- .../resources/js/add-payment-method.js | 5 +++- .../src/Endpoint/CreatePaymentToken.php | 26 +++++++++++-------- .../src/Endpoint/CreateSetupToken.php | 18 +++++++++++-- .../src/SavePaymentMethodsModule.php | 18 ++++++++----- 4 files changed, 47 insertions(+), 20 deletions(-) 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 be17568b0..3f5b46204 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 @@ -69,6 +69,7 @@ const init = () => { const result = await response.json(); if(result.success === true) { window.location.href = ppcp_add_payment_method.payment_methods_page; + return; } errorHandler.message(ppcp_add_payment_method.error_message); @@ -105,7 +106,8 @@ const init = () => { }, body: JSON.stringify({ nonce: ppcp_add_payment_method.ajax.create_setup_token.nonce, - payment_method: PaymentMethods.CARDS + payment_method: PaymentMethods.CARDS, + verification_method: ppcp_add_payment_method.verification_method }) }) @@ -133,6 +135,7 @@ const init = () => { const result = await response.json(); if(result.success === true) { window.location.href = ppcp_add_payment_method.payment_methods_page; + return; } errorHandler.message(ppcp_add_payment_method.error_message); diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php index 8220b6fbd..7ed30b8eb 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php @@ -101,7 +101,7 @@ public function handle_request(): bool { update_user_meta( get_current_user_id(), '_ppcp_target_customer_id', $result->customer->id ); $payment_method = $data['payment_method'] ?? ''; - if($payment_method === PayPalGateway::ID) { + if ( $payment_method === PayPalGateway::ID ) { $email = ''; if ( isset( $result->payment_source->paypal->email_address ) ) { $email = $result->payment_source->paypal->email_address; @@ -114,17 +114,21 @@ public function handle_request(): bool { ); } - if ($payment_method === CreditCardGateway::ID) { + if ( $payment_method === CreditCardGateway::ID ) { $token = new \WC_Payment_Token_CC(); - $token->set_token($result->id); - $token->set_user_id(get_current_user_id()); - $token->set_gateway_id(CreditCardGateway::ID); - - $token->set_last4($result->payment_source->card->last_digits ?? ''); - $expiry = explode('-', $result->payment_source->card->expiry ?? ''); - $token->set_expiry_year($expiry[0] ?? ''); - $token->set_expiry_month($expiry[1] ?? ''); - $token->set_card_type($result->payment_source->card->brand ?? ''); + $token->set_token( $result->id ); + $token->set_user_id( get_current_user_id() ); + $token->set_gateway_id( CreditCardGateway::ID ); + + $token->set_last4( $result->payment_source->card->last_digits ?? '' ); + $expiry = explode( '-', $result->payment_source->card->expiry ?? '' ); + $token->set_expiry_year( $expiry[0] ?? '' ); + $token->set_expiry_month( $expiry[1] ?? '' ); + + $brand = $result->payment_source->card->brand ?? __( 'N/A', 'woocommerce-paypal-payments' ); + if ( $brand ) { + $token->set_card_type( $brand ); + } $token->save(); } diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php index 8341633f1..7eb3a5ace 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreateSetupToken.php @@ -82,10 +82,24 @@ public function handle_request(): bool { ); $payment_method = $data['payment_method'] ?? ''; - if($payment_method === CreditCardGateway::ID) { + if ( $payment_method === CreditCardGateway::ID ) { + $properties = (object) array(); + + $verification_method = $data['verification_method'] ?? ''; + if ( $verification_method === 'SCA_WHEN_REQUIRED' || $verification_method === 'SCA_ALWAYS' ) { + $properties = (object) array( + 'verification_method' => $verification_method, + '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' ) ), + ), + ); + } + $payment_source = new PaymentSource( 'card', - (object) array() + $properties ); } diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index e199db8d8..9dcb02359 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class SavePaymentMethodsModule @@ -218,16 +219,21 @@ function() use ( $c ) { $id_token = $api->id_token( $target_customer_id ); + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + $verification_method = $settings->has( '3d_secure_contingency' ) ? $settings->get( '3d_secure_contingency' ) : ''; + 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, - 'payment_methods_page' => wc_get_account_endpoint_url('payment-methods'), - 'error_message' => __( 'Could not save payment method.', 'woocommerce-paypal-payments' ), - 'ajax' => array( + 'client_id' => $c->get( 'button.client_id' ), + 'merchant_id' => $c->get( 'api.merchant_id' ), + 'id_token' => $id_token, + 'payment_methods_page' => wc_get_account_endpoint_url( 'payment-methods' ), + 'error_message' => __( 'Could not save payment method.', 'woocommerce-paypal-payments' ), + 'verification_method' => $verification_method, + 'ajax' => array( 'create_setup_token' => array( 'endpoint' => \WC_AJAX::get_endpoint( CreateSetupToken::ENDPOINT ), 'nonce' => wp_create_nonce( CreateSetupToken::nonce() ), From 4b536491f2db8c8e9b70449bf720dc2616f75edb Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 14 Dec 2023 15:40:28 +0100 Subject: [PATCH 6/8] Clean button wrapper content on each init --- .../resources/js/add-payment-method.js | 5 +++++ 1 file changed, 5 insertions(+) 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 3f5b46204..f9fe06001 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 @@ -23,6 +23,11 @@ const init = () => { setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden'); setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL); + const buttonWrapper = document.querySelector(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`); + while (buttonWrapper.firstChild) { + buttonWrapper.removeChild(buttonWrapper.firstChild); + } + if(getCurrentPaymentMethod() === PaymentMethods.PAYPAL) { loadPaypalJsScriptPromise({ clientId: ppcp_add_payment_method.client_id, From bc8f09deccbc74fa33379e571dbd7dfe058d0770 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 14 Dec 2023 16:48:41 +0100 Subject: [PATCH 7/8] Load script once --- .../ppcp-save-payment-methods/package.json | 3 +- .../resources/js/add-payment-method.js | 51 +++++-------------- modules/ppcp-save-payment-methods/yarn.lock | 12 +++++ 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/modules/ppcp-save-payment-methods/package.json b/modules/ppcp-save-payment-methods/package.json index a1e23ce17..9556097d6 100644 --- a/modules/ppcp-save-payment-methods/package.json +++ b/modules/ppcp-save-payment-methods/package.json @@ -10,7 +10,8 @@ "Edge >= 14" ], "dependencies": { - "core-js": "^3.25.0" + "core-js": "^3.25.0", + "@paypal/paypal-js": "^6.0.0" }, "devDependencies": { "@babel/core": "^7.19", 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 f9fe06001..829877c3f 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 @@ -3,14 +3,11 @@ import { ORDER_BUTTON_SELECTOR, PaymentMethods } from "../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState"; - +import {loadScript} from "@paypal/paypal-js"; import { setVisible, setVisibleByClass } from "../../../ppcp-button/resources/js/modules/Helper/Hiding"; -import { - loadPaypalJsScriptPromise -} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading"; import ErrorHandler from "../../../ppcp-button/resources/js/modules/ErrorHandler"; import {cardFieldStyles} from "../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper"; @@ -22,17 +19,18 @@ const errorHandler = new ErrorHandler( const init = () => { setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden'); setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL); +} - const buttonWrapper = document.querySelector(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`); - while (buttonWrapper.firstChild) { - buttonWrapper.removeChild(buttonWrapper.firstChild); - } +document.addEventListener( + 'DOMContentLoaded', + () => { + init(); - if(getCurrentPaymentMethod() === PaymentMethods.PAYPAL) { - loadPaypalJsScriptPromise({ + loadScript({ clientId: ppcp_add_payment_method.client_id, merchantId: ppcp_add_payment_method.merchant_id, - dataUserIdToken: ppcp_add_payment_method.id_token + dataUserIdToken: ppcp_add_payment_method.id_token, + components: 'buttons,card-fields', }) .then((paypal) => { errorHandler.clear(); @@ -85,21 +83,6 @@ const init = () => { } }, ).render(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`); - }) - .catch((error) => { - console.error(error); - }); - } - - if(getCurrentPaymentMethod() === PaymentMethods.CARDS) { - loadPaypalJsScriptPromise({ - clientId: ppcp_add_payment_method.client_id, - merchantId: ppcp_add_payment_method.merchant_id, - dataUserIdToken: ppcp_add_payment_method.id_token, - components: 'card-fields', - }, true) - .then((paypal) => { - errorHandler.clear(); const cardField = paypal.CardFields({ createVaultSetupToken: async () => { @@ -189,19 +172,11 @@ const init = () => { console.error(error) }); }); - }) - .catch((error) => { - console.error(error) - }) - } -} -document.addEventListener( - 'DOMContentLoaded', - () => { - jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () { - init() - }); + jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () { + init() + }); + }) } ); diff --git a/modules/ppcp-save-payment-methods/yarn.lock b/modules/ppcp-save-payment-methods/yarn.lock index 0b4549a0b..2171c2b89 100644 --- a/modules/ppcp-save-payment-methods/yarn.lock +++ b/modules/ppcp-save-payment-methods/yarn.lock @@ -1031,6 +1031,13 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@paypal/paypal-js@^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-6.0.1.tgz#5d68d5863a5176383fee9424bc944231668fcffd" + integrity sha512-bvYetmkg2GEC6onsUJQx1E9hdAJWff2bS3IPeiZ9Sh9U7h26/fIgMKm240cq/908sbSoDjHys75XXd8at9OpQA== + dependencies: + promise-polyfill "^8.3.0" + "@types/eslint-scope@^3.7.3": version "3.7.5" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.5.tgz#e28b09dbb1d9d35fdfa8a884225f00440dfc5a3e" @@ -1868,6 +1875,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +promise-polyfill@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63" + integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== + punycode@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" From e93c81d57b28b297ca3ff5c6584aa37abfd83fc9 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 14 Dec 2023 17:17:43 +0100 Subject: [PATCH 8/8] Move add payment method handler outside `loadScript` --- .../resources/js/add-payment-method.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 829877c3f..963fe0dc6 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 @@ -24,7 +24,9 @@ const init = () => { document.addEventListener( 'DOMContentLoaded', () => { - init(); + jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () { + init() + }); loadScript({ clientId: ppcp_add_payment_method.client_id, @@ -172,10 +174,6 @@ document.addEventListener( console.error(error) }); }); - - jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () { - init() - }); }) } );