@@ -1015,6 +1015,25 @@ public function update_order( $data ) {
10151015 } elseif ( 'succeeded ' === $ payment_status ) {
10161016 $ member_order = $ this ->members_orders_repository ->get_member_orders ( $ member_id );
10171017
1018+ // The retrieved PaymentIntent must belong to the order being completed. Verification runs
1019+ // against the order that owns the transaction while completion applies to the member's newest
1020+ // order; without this guard a succeeded PaymentIntent from an earlier order can be replayed to
1021+ // complete a newer pending order, renewing/extending a membership with no new payment.
1022+ if ( empty ( $ member_order ) || (int ) $ latest_order ['ID ' ] !== (int ) $ member_order ['ID ' ] ) {
1023+ return $ this ->update_order_error (
1024+ $ response ,
1025+ __ ( 'Payment verification failed. ' , 'user-registration ' ),
1026+ 'Payment confirmation rejected: PaymentIntent does not belong to the order being completed ' ,
1027+ array (
1028+ 'error_code ' => 'PAYMENT_INTENT_ORDER_MISMATCH ' ,
1029+ 'member_id ' => $ member_id ,
1030+ 'payment_intent_id ' => $ pi_id ,
1031+ 'verified_order_id ' => $ latest_order ['ID ' ],
1032+ 'target_order_id ' => $ member_order ['ID ' ] ?? 0 ,
1033+ )
1034+ );
1035+ }
1036+
10181037 if ( 'completed ' === $ member_order ['status ' ] ) {
10191038 $ response ['message ' ] = $ is_upgrading ? __ ( 'Membership upgraded successfully. ' , 'user-registration ' ) : get_option ( 'user_registration_successful_membership_creation_message ' , esc_html__ ( 'New member has been successfully created. ' , 'user-registration ' ) );
10201039 $ response ['status ' ] = true ;
0 commit comments