mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 10:55:00 +08:00
Merge pull request #1903 from woocommerce/PCP-1393-acdc-save-payment-for-purchase-later
Save cards for purchase later (1393)
This commit is contained in:
commit
2c4767a55b
9 changed files with 299 additions and 122 deletions
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -3,19 +3,41 @@ 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 {loadPaypalJsScript} 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";
|
||||
|
||||
loadPaypalJsScript(
|
||||
{
|
||||
const errorHandler = new ErrorHandler(
|
||||
PayPalCommerceGateway.labels.error.generic,
|
||||
document.querySelector('.woocommerce-notices-wrapper')
|
||||
);
|
||||
|
||||
const init = () => {
|
||||
setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden');
|
||||
setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.PAYPAL);
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () {
|
||||
init()
|
||||
});
|
||||
|
||||
loadScript({
|
||||
clientId: ppcp_add_payment_method.client_id,
|
||||
merchantId: ppcp_add_payment_method.merchant_id,
|
||||
dataUserIdToken: ppcp_add_payment_method.id_token,
|
||||
},
|
||||
components: 'buttons,card-fields',
|
||||
})
|
||||
.then((paypal) => {
|
||||
errorHandler.clear();
|
||||
|
||||
paypal.Buttons(
|
||||
{
|
||||
createVaultSetupToken: async () => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_setup_token.endpoint, {
|
||||
|
@ -30,12 +52,13 @@ loadPaypalJsScript(
|
|||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if(result.data.id) {
|
||||
if (result.data.id) {
|
||||
return result.data.id
|
||||
}
|
||||
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
},
|
||||
onApprove: async ({ vaultSetupToken }) => {
|
||||
onApprove: async ({vaultSetupToken}) => {
|
||||
const response = await fetch(ppcp_add_payment_method.ajax.create_payment_token.endpoint, {
|
||||
method: "POST",
|
||||
credentials: 'same-origin',
|
||||
|
@ -48,27 +71,110 @@ loadPaypalJsScript(
|
|||
})
|
||||
})
|
||||
|
||||
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;
|
||||
return;
|
||||
}
|
||||
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error)
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
}
|
||||
},
|
||||
`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`
|
||||
);
|
||||
).render(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`);
|
||||
|
||||
const init = () => {
|
||||
setVisibleByClass(ORDER_BUTTON_SELECTOR, getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, 'ppcp-hidden');
|
||||
setVisible(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`, getCurrentPaymentMethod() === PaymentMethods.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,
|
||||
payment_method: PaymentMethods.CARDS,
|
||||
verification_method: ppcp_add_payment_method.verification_method
|
||||
})
|
||||
})
|
||||
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
jQuery(document.body).on('click init_add_payment_method', '.payment_methods input.input-radio', function () {
|
||||
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, {
|
||||
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,
|
||||
payment_method: PaymentMethods.CARDS
|
||||
})
|
||||
})
|
||||
|
||||
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);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error)
|
||||
errorHandler.message(ppcp_add_payment_method.error_message);
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector('#place_order').addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
cardField.submit()
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
|
|||
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,6 +100,8 @@ class CreatePaymentToken implements EndpointInterface {
|
|||
if ( is_user_logged_in() && isset( $result->customer->id ) ) {
|
||||
update_user_meta( get_current_user_id(), '_ppcp_target_customer_id', $result->customer->id );
|
||||
|
||||
$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;
|
||||
|
@ -110,6 +114,26 @@ class CreatePaymentToken implements EndpointInterface {
|
|||
);
|
||||
}
|
||||
|
||||
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] ?? '' );
|
||||
|
||||
$brand = $result->payment_source->card->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
|
||||
if ( $brand ) {
|
||||
$token->set_card_type( $brand );
|
||||
}
|
||||
|
||||
$token->save();
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success( $result );
|
||||
return true;
|
||||
} catch ( Exception $exception ) {
|
||||
|
|
|
@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint;
|
|||
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 @@ class CreateSetupToken implements EndpointInterface {
|
|||
*/
|
||||
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,28 @@ class CreateSetupToken implements EndpointInterface {
|
|||
)
|
||||
);
|
||||
|
||||
$payment_method = $data['payment_method'] ?? '';
|
||||
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',
|
||||
$properties
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->payment_method_tokens_endpoint->setup_tokens( $payment_source );
|
||||
|
||||
wp_send_json_success( $result );
|
||||
|
|
|
@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface
|
|||
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,6 +219,10 @@ class SavePaymentMethodsModule implements ModuleInterface {
|
|||
|
||||
$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',
|
||||
|
@ -225,6 +230,9 @@ class SavePaymentMethodsModule implements ModuleInterface {
|
|||
'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 ),
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue