Merge branch 'trunk' into PCP-2287-subscription-support-on-block-cart-block-express-checkout

This commit is contained in:
Pedro Silva 2024-01-12 15:25:34 +00:00
commit 1fe9b9f1a6
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
24 changed files with 428 additions and 173 deletions

View file

@ -17,8 +17,8 @@ return array(
return array_merge(
$locations,
array(
'checkout-block-express' => _x( 'Block Express Checkout', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
'cart-block' => _x( 'Block Cart', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
'checkout-block-express' => _x( 'Express Checkout', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
'cart-block' => _x( 'Cart', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
)
);
},
@ -45,7 +45,7 @@ return array(
'title' => __( 'Require final confirmation on checkout', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'label' => __(
'Require customers to confirm express payments from the Block Cart and Block Express Checkout on the checkout page.
'Require customers to confirm express payments from the Cart and Express Checkout on the checkout page.
<p class="description">If this setting is not enabled, <a href="https://woocommerce.com/document/woocommerce-paypal-payments/#blocks-faq" target="_blank">payment confirmation on the checkout will be skipped</a>.
Skipping the final confirmation on the checkout page may impact the buyer experience during the PayPal payment process.</p>',
'woocommerce-paypal-payments'

View file

@ -112,6 +112,14 @@ class CardFieldsRenderer {
show(buttonSelector);
if(this.defaultConfig.cart_contains_subscription) {
const saveToAccount = document.querySelector('#wc-ppcp-credit-card-gateway-new-payment-method');
if(saveToAccount) {
saveToAccount.checked = true;
saveToAccount.disabled = true;
}
}
document.querySelector(buttonSelector).addEventListener("click", (event) => {
event.preventDefault();
this.spinner.block();

View file

@ -1056,6 +1056,7 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
),
),
'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(),
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(),
'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(),

View file

@ -212,6 +212,19 @@ trait ContextTrait {
return $page_id && is_page( $page_id ) && isset( $wp->query_vars['add-payment-method'] );
}
/**
* Checks whether this user is changing the payment method for a subscription.
*
* @return bool
*/
private function is_subscription_change_payment_method_page(): bool {
if ( isset( $_GET['change_payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
return wcs_is_subscription( wc_clean( wp_unslash( $_GET['change_payment_method'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
}
return false;
}
/**
* Checks if it is the block editor page.
*/

View file

@ -28,6 +28,14 @@ document.addEventListener(
init()
});
if(ppcp_add_payment_method.is_subscription_change_payment_page) {
const saveToAccount = document.querySelector('#wc-ppcp-credit-card-gateway-new-payment-method');
if(saveToAccount) {
saveToAccount.checked = true;
saveToAccount.disabled = true;
}
}
setTimeout(() => {
loadScript({
clientId: ppcp_add_payment_method.client_id,
@ -38,54 +46,57 @@ document.addEventListener(
.then((paypal) => {
errorHandler.clear();
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 paypalButtonContainer = document.querySelector(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`);
if(paypalButtonContainer) {
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
}
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,
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,
})
})
})
const result = await response.json();
if(result.success === true) {
window.location.href = ppcp_add_payment_method.payment_methods_page;
return;
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);
}
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`);
).render(`#ppc-button-${PaymentMethods.PAYPAL}-save-payment-method`);
}
const cardField = paypal.CardFields({
createVaultSetupToken: async () => {
@ -125,6 +136,33 @@ document.addEventListener(
const result = await response.json();
if(result.success === true) {
if(ppcp_add_payment_method.is_subscription_change_payment_page) {
const subscriptionId = ppcp_add_payment_method.subscription_id_to_change_payment;
if(subscriptionId && result.data) {
const req = await fetch(ppcp_add_payment_method.ajax.subscription_change_payment_method.endpoint, {
method: "POST",
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
nonce: ppcp_add_payment_method.ajax.subscription_change_payment_method.nonce,
subscription_id: subscriptionId,
payment_method: getCurrentPaymentMethod(),
wc_payment_token_id: result.data
})
});
const res = await req.json();
if (res.success === true) {
window.location.href = `${ppcp_add_payment_method.view_subscriptions_page}/${subscriptionId}`;
return;
}
}
return;
}
window.location.href = ppcp_add_payment_method.payment_methods_page;
return;
}
@ -167,7 +205,15 @@ document.addEventListener(
}
}
document.querySelector('#place_order').addEventListener("click", (event) => {
document.querySelector('#place_order')?.addEventListener("click", (event) => {
const cardPaymentToken = document.querySelector('input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked')?.value;
if (
getCurrentPaymentMethod() !== 'ppcp-credit-card-gateway'
|| cardPaymentToken && cardPaymentToken !== 'new'
) {
return;
}
event.preventDefault();
cardField.submit()

View file

@ -80,7 +80,8 @@ class CreatePaymentToken implements EndpointInterface {
*/
public function handle_request(): bool {
try {
$data = $this->request_data->read_request( $this->nonce() );
$data = $this->request_data->read_request( $this->nonce() );
$wc_token_id = 0;
/**
* Suppress ArgumentTypeCoercion
@ -98,7 +99,8 @@ class CreatePaymentToken implements EndpointInterface {
$result = $this->payment_method_tokens_endpoint->payment_tokens( $payment_source );
if ( is_user_logged_in() && isset( $result->customer->id ) ) {
update_user_meta( get_current_user_id(), '_ppcp_target_customer_id', $result->customer->id );
$current_user_id = get_current_user_id();
update_user_meta( $current_user_id, '_ppcp_target_customer_id', $result->customer->id );
if ( isset( $result->payment_source->paypal ) ) {
$email = '';
@ -106,34 +108,19 @@ class CreatePaymentToken implements EndpointInterface {
$email = $result->payment_source->paypal->email_address;
}
$this->wc_payment_tokens->create_payment_token_paypal(
get_current_user_id(),
$wc_token_id = $this->wc_payment_tokens->create_payment_token_paypal(
$current_user_id,
$result->id,
$email
);
}
if ( isset( $result->payment_source->card ) ) {
$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();
$wc_token_id = $this->wc_payment_tokens->create_payment_token_card( $current_user_id, $result );
}
}
wp_send_json_success( $result );
wp_send_json_success( $wc_token_id );
return true;
} catch ( Exception $exception ) {
wp_send_json_error();

View file

@ -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;
use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod;
/**
* Class SavePaymentMethodsModule
@ -176,7 +177,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
add_action(
'wp_enqueue_scripts',
function() use ( $c ) {
if ( ! is_user_logged_in() || ! $this->is_add_payment_method_page() ) {
if ( ! is_user_logged_in() || ! ( $this->is_add_payment_method_page() || $this->is_subscription_change_payment_method_page() ) ) {
return;
}
@ -207,17 +208,22 @@ class SavePaymentMethodsModule implements ModuleInterface {
assert( $settings instanceof Settings );
$verification_method = $settings->has( '3d_secure_contingency' ) ? $settings->get( '3d_secure_contingency' ) : '';
$change_payment_method = wc_clean( wp_unslash( $_GET['change_payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
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' ),
'verification_method' => $verification_method,
'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' ),
'view_subscriptions_page' => wc_get_account_endpoint_url( 'view-subscription' ),
'is_subscription_change_payment_page' => $this->is_subscription_change_payment_method_page(),
'subscription_id_to_change_payment' => $this->is_subscription_change_payment_method_page() ? (int) $change_payment_method : 0,
'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() ),
@ -226,6 +232,10 @@ class SavePaymentMethodsModule implements ModuleInterface {
'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentToken::ENDPOINT ),
'nonce' => wp_create_nonce( CreatePaymentToken::nonce() ),
),
'subscription_change_payment_method' => array(
'endpoint' => \WC_AJAX::get_endpoint( SubscriptionChangePaymentMethod::ENDPOINT ),
'nonce' => wp_create_nonce( SubscriptionChangePaymentMethod::nonce() ),
),
),
)
);

View file

@ -11,10 +11,13 @@ namespace WooCommerce\PayPalCommerce\SavePaymentMethods;
use Exception;
use Psr\Log\LoggerInterface;
use stdClass;
use WC_Payment_Token_CC;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
@ -67,17 +70,17 @@ class WooCommercePaymentTokens {
* @param string $token The PayPal payment token.
* @param string $email The PayPal customer email.
*
* @return void
* @return int
*/
public function create_payment_token_paypal(
int $customer_id,
string $token,
string $email
): void {
): int {
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token ) ) {
return;
return 0;
}
$payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
@ -98,5 +101,48 @@ class WooCommercePaymentTokens {
"Could not create WC payment token PayPal for customer {$customer_id}. " . $exception->getMessage()
);
}
return $payment_token_paypal->get_id();
}
/**
* Creates a WC Payment Token for Credit Card payment.
*
* @param int $customer_id The WC customer ID.
* @param stdClass $payment_token The Credit Card payment token.
*
* @return int
*/
public function create_payment_token_card( int $customer_id, stdClass $payment_token ): int {
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, CreditCardGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $payment_token->id ) ) {
return 0;
}
$token = new WC_Payment_Token_CC();
$token->set_token( $payment_token->id );
$token->set_user_id( get_current_user_id() );
$token->set_gateway_id( CreditCardGateway::ID );
$token->set_last4( $payment_token->payment_source->card->last_digits ?? '' );
$expiry = explode( '-', $payment_token->payment_source->card->expiry ?? '' );
$token->set_expiry_year( $expiry[0] ?? '' );
$token->set_expiry_month( $expiry[1] ?? '' );
$brand = $payment_token->payment_source->card->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
if ( $brand ) {
$token->set_card_type( $brand );
}
try {
$token->save();
} catch ( Exception $exception ) {
$this->logger->error(
"Could not create WC payment token card for customer {$customer_id}. " . $exception->getMessage()
);
}
$token->save();
return $token->get_id();
}
}

View file

@ -143,19 +143,6 @@ class VaultedCreditCardHandler {
string $saved_credit_card,
WC_Order $wc_order
): WC_Order {
if (
// phpcs:ignore WordPress.Security.NonceVerification.Missing
isset( $_POST['woocommerce_change_payment'] )
&& $this->subscription_helper->has_subscription( $wc_order->get_id() )
&& $this->subscription_helper->is_subscription_change_payment()
&& $saved_credit_card
) {
$wc_order->update_meta_data( 'payment_token_id', $saved_credit_card );
$wc_order->save();
return $wc_order;
}
$tokens = $this->payment_token_repository->all_for_user_id( $wc_order->get_customer_id() );
$selected_token = null;
foreach ( $tokens as $token ) {

View file

@ -1400,8 +1400,8 @@ return array(
'wcgateway.button.locations' => static function( ContainerInterface $container ): array {
return array(
'product' => 'Single Product',
'cart' => 'Cart',
'checkout' => 'Checkout',
'cart' => 'Classic Cart',
'checkout' => 'Classic Checkout',
'mini-cart' => 'Mini Cart',
);
},

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
@ -377,11 +378,11 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
}
/**
* If customer has chosen a saved credit card payment.
* If customer has chosen a saved credit card payment from checkout page.
*/
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$saved_credit_card = wc_clean( wp_unslash( $_POST['saved_credit_card'] ?? '' ) );
if ( $saved_credit_card ) {
if ( $saved_credit_card && is_checkout() ) {
try {
$wc_order = $this->vaulted_credit_card_handler->handle_payment(
$saved_credit_card,
@ -395,6 +396,40 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
}
}
/**
* If customer is changing subscription payment.
*/
if (
// phpcs:disable WordPress.Security.NonceVerification.Missing
isset( $_POST['woocommerce_change_payment'] )
&& $this->subscription_helper->has_subscription( $wc_order->get_id() )
&& $this->subscription_helper->is_subscription_change_payment()
) {
$saved_credit_card = wc_clean( wp_unslash( $_POST['wc-ppcp-credit-card-gateway-payment-token'] ?? '' ) );
if ( ! $saved_credit_card ) {
$saved_credit_card = wc_clean( wp_unslash( $_POST['saved_credit_card'] ?? '' ) );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
if ( $saved_credit_card ) {
$payment_token = WC_Payment_Tokens::get( $saved_credit_card );
if ( $payment_token ) {
$wc_order->add_payment_token( $payment_token );
$wc_order->save();
return $this->handle_payment_success( $wc_order );
}
}
wc_add_notice( __( 'Could not change payment.', 'woocommerce-paypal-payments' ), 'error' );
return array(
'result' => 'failure',
'redirect' => wc_get_checkout_url(),
'errorMessage' => __( 'Could not change payment.', 'woocommerce-paypal-payments' ),
);
}
/**
* If the WC_Order is paid through the approved webhook.
*/

View file

@ -12,11 +12,11 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
@ -522,10 +522,21 @@ class PayPalGateway extends \WC_Payment_Gateway {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) );
if ( $saved_paypal_payment ) {
$wc_order->update_meta_data( 'payment_token_id', $saved_paypal_payment );
$wc_order->save();
$payment_token = WC_Payment_Tokens::get( $saved_paypal_payment );
if ( $payment_token ) {
$wc_order->add_payment_token( $payment_token );
$wc_order->save();
return $this->handle_payment_success( $wc_order );
return $this->handle_payment_success( $wc_order );
}
wc_add_notice( __( 'Could not change payment.', 'woocommerce-paypal-payments' ), 'error' );
return array(
'result' => 'failure',
'redirect' => wc_get_checkout_url(),
'errorMessage' => __( 'Could not change payment.', 'woocommerce-paypal-payments' ),
);
}
}

View file

@ -378,7 +378,7 @@ return function ( ContainerInterface $container, array $fields ): array {
// Cart.
'pay_later_cart_messaging_heading' => array(
'heading' => __( 'Pay Later Messaging on Cart', 'woocommerce-paypal-payments' ),
'heading' => __( 'Pay Later Messaging on Classic Cart', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array( State::STATE_ONBOARDED ),
'requirements' => array(),
@ -502,7 +502,7 @@ return function ( ContainerInterface $container, array $fields ): array {
// Checkout.
'pay_later_checkout_messaging_heading' => array(
'heading' => __( 'Pay Later Messaging on Checkout', 'woocommerce-paypal-payments' ),
'heading' => __( 'Pay Later Messaging on Classic Checkout', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array( State::STATE_ONBOARDED ),
'requirements' => array(),

View file

@ -222,11 +222,11 @@ return function ( ContainerInterface $container, array $fields ): array {
// Checkout page.
'button_checkout_heading' => array(
'heading' => __( 'Checkout Buttons', 'woocommerce-paypal-payments' ),
'heading' => __( 'Classic Checkout Buttons', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__(
'Customize the appearance of the PayPal smart buttons on the %1$sCheckout page%2$s.
'Customize the appearance of the PayPal smart buttons on the %1$sClassic Checkout page%2$s.
%3$sCheckout Buttons must be enabled to display the PayPal gateway on the Checkout page.
',
'woocommerce-paypal-payments'
@ -508,11 +508,11 @@ return function ( ContainerInterface $container, array $fields ): array {
// Cart settings.
'button_cart_heading' => array(
'heading' => __( 'Cart Buttons', 'woocommerce-paypal-payments' ),
'heading' => __( 'Classic Cart Buttons', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__(
'Customize the appearance of the PayPal smart buttons on the %1$sCart page%2$s.',
'Customize the appearance of the PayPal smart buttons on the %1$sClassic Cart page%2$s.',
'woocommerce-paypal-payments'
),
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-cart" target="_blank">',
@ -805,11 +805,11 @@ return function ( ContainerInterface $container, array $fields ): array {
// Block express checkout settings.
'button_checkout-block-express_heading' => array(
'heading' => __( 'Block Express Checkout Buttons', 'woocommerce-paypal-payments' ),
'heading' => __( 'Express Checkout Buttons', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__(
'Customize the appearance of the PayPal smart buttons on the %1$sBlock Express Checkout%2$s.',
'Customize the appearance of the PayPal smart buttons on the %1$sExpress Checkout%2$s.',
'woocommerce-paypal-payments'
),
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-block-express-checkout" target="_blank">',
@ -923,11 +923,11 @@ return function ( ContainerInterface $container, array $fields ): array {
// Block cart settings.
'button_cart-block_heading' => array(
'heading' => __( 'Block Cart Buttons', 'woocommerce-paypal-payments' ),
'heading' => __( 'Cart Buttons', 'woocommerce-paypal-payments' ),
'description' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
__(
'Customize the appearance of the PayPal smart buttons on the %1$sBlock Cart%2$s.',
'Customize the appearance of the PayPal smart buttons on the %1$sCart%2$s.',
'woocommerce-paypal-payments'
),
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#button-on-cart-block" target="_blank">',

View file

@ -9,10 +9,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcSubscriptions;
use WooCommerce\PayPalCommerce\PayPalSubscriptions\DeactivatePlanEndpoint;
use WooCommerce\PayPalCommerce\PayPalSubscriptions\SubscriptionsApiHandler;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
return array(
@ -45,4 +44,9 @@ return array(
$endpoint = $container->get( 'api.endpoint.payment-token' );
return new PaymentTokenRepository( $factory, $endpoint );
},
'wc-subscriptions.endpoint.subscription-change-payment-method' => static function( ContainerInterface $container ): SubscriptionChangePaymentMethod {
return new SubscriptionChangePaymentMethod(
$container->get( 'button.request-data' )
);
},
);

View file

@ -0,0 +1,81 @@
<?php
/**
* The Create Payment Token endpoint.
*
* @package WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint;
use Exception;
use WC_Order;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
/**
* Class SubscriptionChangePaymentMethod
*/
class SubscriptionChangePaymentMethod implements EndpointInterface {
const ENDPOINT = 'ppc-subscription-change-payment-method';
/**
* The request data.
*
* @var RequestData
*/
private $request_data;
/**
* SubscriptionChangePaymentMethod constructor.
*
* @param RequestData $request_data $request_data The request data.
*/
public function __construct( RequestData $request_data ) {
$this->request_data = $request_data;
}
/**
* 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() );
$subscription = wcs_get_subscription( $data['subscription_id'] );
if ( $subscription instanceof WC_Order ) {
$subscription->set_payment_method( $data['payment_method'] );
$wc_payment_token = WC_Payment_Tokens::get( $data['wc_payment_token_id'] );
if ( $wc_payment_token ) {
$subscription->add_payment_token( $wc_payment_token );
$subscription->save();
}
wp_send_json_success();
return true;
}
wp_send_json_error();
return false;
} catch ( Exception $exception ) {
wp_send_json_error();
return false;
}
}
}

View file

@ -11,7 +11,8 @@ namespace WooCommerce\PayPalCommerce\WcSubscriptions;
use Psr\Log\LoggerInterface;
use WC_Order;
use WC_Subscription;
use WC_Payment_Token_CC;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
@ -23,6 +24,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/**
@ -135,6 +137,10 @@ class WcSubscriptionsModule implements ModuleInterface {
* @psalm-suppress MissingClosureParamType
*/
function ( $default_fields, $id ) use ( $c ) {
if ( $c->has( 'save-payment-methods.eligible' ) && $c->get( 'save-payment-methods.eligible' ) ) {
return $default_fields;
}
$payment_token_repository = $c->get( 'vaulting.repository.payment-token' );
$settings = $c->get( 'wcgateway.settings' );
$subscription_helper = $c->get( 'wc-subscriptions.helper' );
@ -144,6 +150,40 @@ class WcSubscriptionsModule implements ModuleInterface {
20,
2
);
add_filter(
'woocommerce_available_payment_gateways',
function( array $methods ) use ( $c ) : array {
if (
! is_wc_endpoint_url( 'order-pay' )
|| $c->has( 'save-payment-methods.eligible' ) && $c->get( 'save-payment-methods.eligible' )
) {
return $methods;
}
$paypal_tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), PayPalGateway::ID );
if ( ! $paypal_tokens ) {
unset( $methods[ PayPalGateway::ID ] );
}
$card_tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), CreditCardGateway::ID );
if ( ! $card_tokens ) {
unset( $methods[ CreditCardGateway::ID ] );
}
return $methods;
}
);
add_action(
'wc_ajax_' . SubscriptionChangePaymentMethod::ENDPOINT,
static function () use ( $c ) {
$endpoint = $c->get( 'wc-subscriptions.endpoint.subscription-change-payment-method' );
assert( $endpoint instanceof SubscriptionChangePaymentMethod );
$endpoint->handle_request();
}
);
}
/**
@ -224,30 +264,18 @@ class WcSubscriptionsModule implements ModuleInterface {
&& PayPalGateway::ID === $id
&& $subscription_helper->is_subscription_change_payment()
) {
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
if ( ! $tokens || ! $payment_token_repository->tokens_contains_paypal( $tokens ) ) {
return esc_html__(
'No PayPal payments saved, in order to use a saved payment you first need to create it through a purchase.',
'woocommerce-paypal-payments'
);
}
$tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), PayPalGateway::ID );
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-paypal-payment" name="saved_paypal_payment">',
esc_html__( 'Select a saved PayPal payment', 'woocommerce-paypal-payments' )
);
$output = '<ul class="wc-saved-payment-methods">';
foreach ( $tokens as $token ) {
if ( isset( $token->source()->paypal ) ) {
$output .= sprintf(
'<option value="%1$s">%2$s</option>',
$token->id(),
$token->source()->paypal->payer->email_address
);
}
$output .= '<li>';
$output .= sprintf( '<input name="saved_paypal_payment" type="radio" value="%s" style="width:auto;" checked="checked">', $token->get_id() );
$output .= sprintf( '<label for="saved_paypal_payment">%s</label>', $token->get_meta( 'email' ) ?? '' );
$output .= '</li>';
}
$output .= '</select></p>';
$output .= '</ul>';
return $output;
return $output;
}
return $description;
@ -271,33 +299,23 @@ class WcSubscriptionsModule implements ModuleInterface {
array $default_fields,
SubscriptionHelper $subscription_helper
) {
if ( $settings->has( 'vault_enabled_dcc' )
&& $settings->get( 'vault_enabled_dcc' )
&& $subscription_helper->is_subscription_change_payment()
&& CreditCardGateway::ID === $id
) {
$tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
if ( ! $tokens || ! $payment_token_repository->tokens_contains_card( $tokens ) ) {
$default_fields = array();
$default_fields['saved-credit-card'] = esc_html__(
'No Credit Card saved, in order to use a saved Credit Card you first need to create it through a purchase.',
'woocommerce-paypal-payments'
);
return $default_fields;
}
$tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), CreditCardGateway::ID );
$output = sprintf(
'<p class="form-row form-row-wide"><label>%1$s</label><select id="saved-credit-card" name="saved_credit_card">',
esc_html__( 'Select a saved Credit Card payment', 'woocommerce-paypal-payments' )
);
foreach ( $tokens as $token ) {
if ( isset( $token->source()->card ) ) {
if ( $token instanceof WC_Payment_Token_CC ) {
$output .= sprintf(
'<option value="%1$s">%2$s ...%3$s</option>',
$token->id(),
$token->source()->card->brand,
$token->source()->card->last_digits
$token->get_id(),
$token->get_card_type(),
$token->get_last4()
);
}
}