Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Cash App Pay token reuse issues (with subscriptions) #3263

Merged
merged 35 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
72a6be7
Fix Cash App Pay reuse of tokens
wjrosa Jul 10, 2024
9d77a48
Fix cash app confirm intent call
wjrosa Jul 16, 2024
89b42b6
Fix parsing of wallet URL partials
wjrosa Jul 16, 2024
e3e20b6
Removing additional log
wjrosa Jul 16, 2024
5864360
Enable selecting Cash App when changing subscription payment methods
wjrosa Jul 16, 2024
4757307
Possible redirect fix commented out
wjrosa Jul 18, 2024
7815744
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Jul 18, 2024
2667452
Changelog and readme updates
wjrosa Jul 26, 2024
c62c313
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Jul 26, 2024
e01db5f
Uncommenting out wallet confirmation function updates
wjrosa Jul 26, 2024
4370018
Fix tests
wjrosa Jul 26, 2024
584e716
Updating docs related to the wallet redirect hash
wjrosa Jul 26, 2024
f08b0be
Update includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
wjrosa Aug 5, 2024
0160e3c
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Aug 5, 2024
beb754b
Fix Cash App Pay token inclusion
wjrosa Aug 6, 2024
64b20d5
Turning customer country available when changing a subscription payme…
wjrosa Aug 6, 2024
58f84b0
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Aug 7, 2024
dec4fcf
Fix redirect when using a new token
wjrosa Aug 7, 2024
668d75b
Removing unnecessary check when saving method to subscription
wjrosa Aug 7, 2024
75b8259
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Aug 9, 2024
08b9d4b
Revert unnecessary change
wjrosa Aug 9, 2024
2bd36b1
Adding specific unit tests
wjrosa Aug 9, 2024
98c216b
Re-including tests removed by mistake
wjrosa Aug 9, 2024
3d1a1af
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Aug 13, 2024
b8bd1af
Fix missing token information when subscribing using a saved token
wjrosa Aug 13, 2024
e759d89
Adding more doc blocks
wjrosa Aug 13, 2024
bf0fe7e
Improving code readability
wjrosa Aug 14, 2024
be8bf69
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Aug 19, 2024
edae1e5
Fix lint issues
wjrosa Aug 26, 2024
dd46f12
Merge branch 'develop' into fix/cashapp-token-reuse2
wjrosa Sep 11, 2024
0ce917e
Changelog fix
wjrosa Sep 11, 2024
af1cc6c
Minor doc updates
wjrosa Sep 11, 2024
b48260b
Minor variable name change
wjrosa Sep 12, 2024
17cd7aa
Merge branch 'develop' into fix/cashapp-token-reuse2
james-allan Sep 16, 2024
e243ebc
Remove changelog entry that slipped into this branch
james-allan Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*** Changelog ***

= 8.6.0 - xxxx-xx-xx =
* Fix - Fix multiple issues related to the reuse of Cash App Pay tokens (as a saved payment method) when subscribing.
Tweak - Minor text updates to webhook-related configuration labels and buttons.
** Add - Indicate the activation status of each payment method individually, instead of using a general notice.
* Fix - JS error when billing country field does not exist on the payment method page.
Expand Down
6 changes: 4 additions & 2 deletions client/classic/upe/deferred-intent.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ jQuery( function ( $ ) {
function maybeConfirmVoucherOrWalletPayment() {
if (
getStripeServerData()?.isOrderPay ||
getStripeServerData()?.isCheckout
getStripeServerData()?.isCheckout ||
getStripeServerData()?.isChangingPayment
) {
if ( window.location.hash.startsWith( '#wc-stripe-voucher-' ) ) {
confirmVoucherPayment(
Expand All @@ -125,7 +126,8 @@ jQuery( function ( $ ) {
) {
confirmWalletPayment(
api,
getStripeServerData()?.isOrderPay
getStripeServerData()?.isOrderPay ||
getStripeServerData()?.isChangingPayment
? $( '#order_review' )
: $( 'form.checkout' )
);
Expand Down
33 changes: 23 additions & 10 deletions client/classic/upe/payment-processing.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ export const confirmVoucherPayment = async ( api, jQueryForm ) => {
*
* When processing a payment for a wallet payment method on the checkout or order pay page,
* the process_payment_with_deferred_intent() function redirects the customer to a URL
* formatted with: #wc-stripe-wallet-<order_id>:<payment_method_type>:<client_secret>:<redirect_url>.
* formatted with: #wc-stripe-wallet-<order_id>:<payment_method_type>:<payment_intent_type>:<client_secret>:<redirect_url>.
*
* This function, which is hooked onto the hashchanged event, checks if the URL contains the data we need to process the wallet payment.
*
Expand All @@ -407,7 +407,7 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
}

const partials = window.location.href.match(
/#wc-stripe-wallet-(.+):(.+):(.+):(.+)$/
/#wc-stripe-wallet-(.+):(.+):(.+):(.+):(.+)$/
);

if ( ! partials ) {
Expand All @@ -423,7 +423,7 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
);

const orderId = partials[ 1 ];
const clientSecret = partials[ 3 ];
const clientSecret = partials[ 4 ];

// Verify the request using the data added to the URL.
if (
Expand All @@ -435,7 +435,8 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
}

const paymentMethodType = partials[ 2 ];
const returnURL = decodeURIComponent( partials[ 4 ] );
const intentType = partials[ 3 ];
const returnURL = decodeURIComponent( partials[ 5 ] );

try {
// Confirm the payment to tell Stripe to display the modal to the customer.
Expand All @@ -453,11 +454,19 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {
} );
break;
case 'cashapp':
confirmPayment = await api
.getStripe()
.confirmCashappPayment( clientSecret, {
return_url: returnURL,
} );
if ( intentType === 'setup_intent' ) {
confirmPayment = await api
.getStripe()
.confirmCashappSetup( clientSecret, {
return_url: returnURL,
} );
} else {
confirmPayment = await api
.getStripe()
.confirmCashappPayment( clientSecret, {
return_url: returnURL,
} );
}
break;
default:
// eslint-disable-next-line no-console
Expand All @@ -477,7 +486,11 @@ export const confirmWalletPayment = async ( api, jQueryForm ) => {

// Do not redirect to the order received page if the modal is closed without payment.
// Otherwise redirect to the order received page.
if ( confirmPayment.paymentIntent.status !== 'requires_action' ) {
const status =
intentType === 'setup_intent'
? confirmPayment.setupIntent.status
: confirmPayment.paymentIntent.status;
if ( status !== 'requires_action' ) {
window.location.href = returnURL;
}
} catch ( error ) {
Expand Down
13 changes: 11 additions & 2 deletions includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,14 @@ private function process_payment_with_deferred_intent( int $order_id ) {

// Create a payment intent, or update an existing one associated with the order.
$payment_intent = $this->process_payment_intent_for_order( $order, $payment_information );
} elseif ( $payment_information['is_using_saved_payment_method'] && 'cashapp' === $selected_payment_type ) {
// If the payment method is Cash App Pay and the order has no cost, mark the order as paid.
$order->payment_complete();

return [
'result' => 'success',
'redirect' => '',
wjrosa marked this conversation as resolved.
Show resolved Hide resolved
];
} else {
$payment_intent = $this->process_setup_intent_for_order( $order, $payment_information );
}
Expand Down Expand Up @@ -884,11 +892,12 @@ private function process_payment_with_deferred_intent( int $order_id ) {
rawurlencode( $redirect )
);
} elseif ( isset( $payment_intent->payment_method_types ) && count( array_intersect( [ 'wechat_pay', 'cashapp' ], $payment_intent->payment_method_types ) ) !== 0 ) {
// For Wallet payment method types (CashApp/WeChat Pay), redirect the customer to a URL hash formatted #wc-stripe-wallet-{order_id}:{payment_method_type}:{client_secret}:{redirect_url} to confirm the intent which also displays the modal.
// For Wallet payment method types (CashApp/WeChat Pay), redirect the customer to a URL hash formatted #wc-stripe-wallet-{order_id}:{payment_method_type}:{payment_intent_type}:{client_secret}:{redirect_url} to confirm the intent which also displays the modal.
$redirect = sprintf(
'#wc-stripe-wallet-%s:%s:%s:%s',
'#wc-stripe-wallet-%s:%s:%s:%s:%s',
$order_id,
$payment_information['selected_payment_type'],
$payment_intent->object,
$payment_intent->client_secret,
rawurlencode( $redirect )
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,6 @@ public function __construct() {
// Cash App Pay supports subscriptions. Init subscriptions so it can process subscription payments.
$this->maybe_init_subscriptions();

/**
* Cash App Pay is incapable of processing zero amount payments with saved payment methods.
*
* This is because setup intents with a saved payment method (token) fail. While we wait for a solution to this issue, we
* disable customer's changing the payment method to Cash App Pay as that would result in a $0 set up intent.
*/
$this->supports = array_diff( $this->supports, [ 'subscription_payment_method_change_customer' ] );

add_filter( 'woocommerce_thankyou_order_received_text', [ $this, 'order_received_text_for_wallet_failure' ], 10, 2 );
}

Expand All @@ -71,28 +63,6 @@ public function get_retrievable_type() {
return $this->get_id();
}

/**
* Determines whether Cash App Pay is enabled at checkout.
*
* @param int $order_id The order ID.
* @param string $account_domestic_currency The account's default currency.
*
* @return bool Whether Cash App Pay is enabled at checkout.
*/
public function is_enabled_at_checkout( $order_id = null, $account_domestic_currency = null ) {
/**
* Cash App Pay is incapable of processing zero amount payments with saved payment methods.
*
* This is because setup intents with a saved payment method (token) fail. While we wait for a solution to this issue, we
* disable Cash App Pay for zero amount orders.
*/
if ( ! is_add_payment_method_page() && $this->get_current_order_amount() <= 0 ) {
return false;
}

return parent::is_enabled_at_checkout( $order_id, $account_domestic_currency );
}

/**
* Creates a Cash App Pay payment token for the customer.
*
Expand Down Expand Up @@ -130,10 +100,10 @@ public function order_received_text_for_wallet_failure( $text, $order ) {
$redirect_status = wc_clean( wp_unslash( $_GET['redirect_status'] ) );
}
if ( $order && $this->id === $order->get_payment_method() && 'failed' === $redirect_status ) {
$text = '<p class="woocommerce-error">';
$text = '<p class="woocommerce-error">';
$text .= esc_html( 'Unfortunately your order cannot be processed as the payment method has declined your transaction. Please attempt your purchase again.' );
$text .= '</p>';
$text .= '<p class="woocommerce-notice woocommerce-notice--error woocommerce-thankyou-order-failed-actions">';
$text .= '</p>';
$text .= '<p class="woocommerce-notice woocommerce-notice--error woocommerce-thankyou-order-failed-actions">';
$text .= '<a href="' . esc_url( $order->get_checkout_payment_url() ) . '" class="button pay">' . esc_html( 'Pay' ) . '</a>';
if ( is_user_logged_in() ) {
$text .= '<a href="' . esc_url( wc_get_page_permalink( 'myaccount' ) ) . '" class="button pay">' . esc_html( 'My account' ) . '</a>';
Expand Down
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ If you get stuck, you can ask for help in the Plugin Forum.
== Changelog ==

= 8.6.0 - xxxx-xx-xx =
* Fix - Fix multiple issues related to the reuse of Cash App Pay tokens (as a saved payment method) when subscribing.
* Tweak - Minor text updates to webhook-related configuration labels and buttons.
* Add - Indicate the activation status of each payment method individually, instead of using a general notice.
* Fix - JS error when billing country field does not exist on the payment method page.
Expand Down
3 changes: 2 additions & 1 deletion tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ public function test_process_payment_deferred_intent_with_required_action_for_wa
$mock_intent = (object) wp_parse_args(
[
'status' => 'requires_action',
'object' => 'payment_intent',
'data' => [
(object) [
'id' => $order_id,
Expand Down Expand Up @@ -530,7 +531,7 @@ public function test_process_payment_deferred_intent_with_required_action_for_wa
$return_url = self::MOCK_RETURN_URL;

$this->assertEquals( 'success', $response['result'] );
$this->assertMatchesRegularExpression( "/#wc-stripe-wallet-{$order_id}:wechat_pay:{$mock_intent->client_secret}:{$return_url}/", $response['redirect'] );
$this->assertMatchesRegularExpression( "/#wc-stripe-wallet-{$order_id}:wechat_pay:{$mock_intent->object}:{$mock_intent->client_secret}:{$return_url}/", $response['redirect'] );
}

/**
Expand Down
Loading