Skip to content

Commit 1dfb9b5

Browse files
annemirasolwjrosa
andauthored
Prevent duplicate failed order emails (#3418)
* Prevent duplicate failed order emails When an order changes status from pending/on-hold to failed, a failed-order email is triggered by core. In our webhooks code, we also manually trigger these emails when certain Stripe processes fail. This causes duplicate emails for some scenarios. * Add changelog and readme entries --------- Co-authored-by: Wesley Rosa <[email protected]>
1 parent 18d9396 commit 1dfb9b5

File tree

4 files changed

+33
-6
lines changed

4 files changed

+33
-6
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*** Changelog ***
22

33
= 8.7.0 - xxxx-xx-xx =
4+
* Fix - Prevent duplicate failed-order emails from being sent.
45
* Fix - Support custom name and description for Afterpay.
56
* Fix - Link APM charge IDs in Order Details page to their Stripe dashboard payments page.
67
* Fix - Fix Indian subscription processing by forcing the recreation of mandates during switches (upgrading/downgrading).

includes/abstracts/abstract-wc-stripe-payment-gateway.php

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -622,13 +622,32 @@ public function process_response( $response, $order ) {
622622
* @since 3.1.0
623623
* @version 4.0.0
624624
* @param int $order_id
625-
* @return null
625+
* @param array $status_update Optional. Use fields 'to' and 'from' to provide context
626+
* when a status change has occurred for the order.
627+
* @return void
626628
*/
627-
public function send_failed_order_email( $order_id ) {
629+
public function send_failed_order_email( $order_id, $status_update = [] ) {
628630
$emails = WC()->mailer()->get_emails();
629-
if ( ! empty( $emails ) && ! empty( $order_id ) && isset( $emails['WC_Email_Failed_Order'] ) ) {
630-
$emails['WC_Email_Failed_Order']->trigger( $order_id );
631+
632+
if ( empty( $emails ) || empty( $order_id ) || ! isset( $emails['WC_Email_Failed_Order'] ) ) {
633+
return;
634+
}
635+
636+
// To prevent duplicate failed order emails, check if a status update
637+
// will trigger its own failed order email.
638+
if (
639+
isset( $status_update['to'], $status_update['from'] ) &&
640+
'failed' === $status_update['to'] &&
641+
in_array( $status_update['from'], [ 'on-hold', 'pending' ], true )
642+
) {
643+
$callback = [ $emails['WC_Email_Failed_Order'], 'trigger' ];
644+
$hook_name = "woocommerce_order_status_{$status_update['from']}_to_failed_notification";
645+
if ( has_action( $hook_name, $callback ) ) {
646+
return;
647+
}
631648
}
649+
650+
$emails['WC_Email_Failed_Order']->trigger( $order_id );
632651
}
633652

634653
/**

includes/class-wc-stripe-webhook-handler.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -966,15 +966,18 @@ public function process_payment_intent_success( $notification ) {
966966
/* translators: 1) The error message that was received from Stripe. */
967967
$message = sprintf( __( 'Stripe SCA authentication failed. Reason: %s', 'woocommerce-gateway-stripe' ), $error_message );
968968

969+
$status_update = [];
969970
if ( ! $order->get_meta( '_stripe_status_final', false ) ) {
971+
$status_update['from'] = $order->get_status();
972+
$status_update['to'] = 'failed';
970973
$order->update_status( 'failed', $message );
971974
} else {
972975
$order->add_order_note( $message );
973976
}
974977

975978
do_action( 'wc_gateway_stripe_process_webhook_payment_error', $order, $notification );
976979

977-
$this->send_failed_order_email( $order_id );
980+
$this->send_failed_order_email( $order_id, $status_update );
978981
break;
979982
}
980983

@@ -1017,13 +1020,16 @@ public function process_setup_intent( $notification ) {
10171020
/* translators: 1) The error message that was received from Stripe. */
10181021
$message = sprintf( __( 'Stripe SCA authentication failed. Reason: %s', 'woocommerce-gateway-stripe' ), $error_message );
10191022

1023+
$status_update = [];
10201024
if ( ! $order->get_meta( '_stripe_status_final', false ) ) {
1025+
$status_update['from'] = $order->get_status();
1026+
$status_update['to'] = 'failed';
10211027
$order->update_status( 'failed', $message );
10221028
} else {
10231029
$order->add_order_note( $message );
10241030
}
10251031

1026-
$this->send_failed_order_email( $order_id );
1032+
$this->send_failed_order_email( $order_id, $status_update );
10271033
}
10281034

10291035
$this->unlock_order_payment( $order );

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ If you get stuck, you can ask for help in the Plugin Forum.
129129
== Changelog ==
130130

131131
= 8.7.0 - xxxx-xx-xx =
132+
* Fix - Prevent duplicate failed-order emails from being sent.
132133
* Fix - Support custom name and description for Afterpay.
133134
* Fix - Link APM charge IDs in Order Details page to their Stripe dashboard payments page.
134135
* Fix - Fix Indian subscription processing by forcing the recreation of mandates during switches (upgrading/downgrading).

0 commit comments

Comments
 (0)