Skip to content

Commit

Permalink
Use the customer ID saved in the subscription to process renewal paym…
Browse files Browse the repository at this point in the history
…ents (#9065)

Co-authored-by: Brett Shumaker <[email protected]>
  • Loading branch information
danielmx-dev and brettshumaker authored Jul 12, 2024
1 parent 540480e commit 93eaed1
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 4 deletions.
4 changes: 4 additions & 0 deletions changelog/fix-8892-use-correct-customer-id-in-renewals
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: fix

Use the customer id saved in the subscription to process renewal payments.
21 changes: 20 additions & 1 deletion includes/class-payment-information.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ class Payment_Information {
*/
private $payment_method_stripe_id;

/**
* The WCPay Customer ID that owns the payment token.
*
* @var string
*/
private $customer_id;

/**
* Payment information constructor.
*
Expand All @@ -117,6 +124,7 @@ class Payment_Information {
* @param string $cvc_confirmation The CVC confirmation for this payment method.
* @param string $fingerprint The attached fingerprint.
* @param string $payment_method_stripe_id The Stripe ID of the payment method used for this payment.
* @param string $customer_id The WCPay Customer ID that owns the payment token.
*
* @throws Invalid_Payment_Method_Exception When no payment method is found in the provided request.
*/
Expand All @@ -129,7 +137,8 @@ public function __construct(
Payment_Capture_Type $manual_capture = null,
string $cvc_confirmation = null,
string $fingerprint = '',
string $payment_method_stripe_id = null
string $payment_method_stripe_id = null,
string $customer_id = null
) {
if ( empty( $payment_method ) && empty( $token ) && ! \WC_Payments::is_network_saved_cards_enabled() ) {
// If network-wide cards are enabled, a payment method or token may not be specified and the platform default one will be used.
Expand All @@ -147,6 +156,7 @@ public function __construct(
$this->cvc_confirmation = $cvc_confirmation;
$this->fingerprint = $fingerprint;
$this->payment_method_stripe_id = $payment_method_stripe_id;
$this->customer_id = $customer_id;
}

/**
Expand Down Expand Up @@ -436,4 +446,13 @@ public function get_fingerprint() {
public function get_payment_method_stripe_id() {
return $this->payment_method_stripe_id;
}

/**
* Returns the WCPay Customer ID that owns the payment token.
*
* @return string The WCPay Customer ID.
*/
public function get_customer_id() {
return $this->customer_id;
}
}
10 changes: 8 additions & 2 deletions includes/class-wc-payment-gateway-wcpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -1475,10 +1475,16 @@ public function process_payment_for_order( $cart, $payment_information, $schedul
$amount = $order->get_total();
$metadata = $this->get_metadata_from_order( $order, $payment_information->get_payment_type() );

$customer_details_options = [
$customer_details_options = [
'is_woopay' => filter_var( $metadata['paid_on_woopay'] ?? false, FILTER_VALIDATE_BOOLEAN ),
];
list( $user, $customer_id ) = $this->manage_customer_details_for_order( $order, $customer_details_options );

if ( $payment_information->get_customer_id() ) {
$user = $order->get_user();
$customer_id = $payment_information->get_customer_id();
} else {
list( $user, $customer_id ) = $this->manage_customer_details_for_order( $order, $customer_details_options );
}

$intent_failed = false;
$payment_needed = $amount > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,10 @@ public function scheduled_subscription_payment( $amount, $renewal_order ) {
return;
}

$customer_id = $this->order_service->get_customer_id_for_order( $renewal_order );

try {
$payment_information = new Payment_Information( '', $renewal_order, Payment_Type::RECURRING(), $token, Payment_Initiated_By::MERCHANT(), null, null, '', $this->get_payment_method_to_use_for_intent() );
$payment_information = new Payment_Information( '', $renewal_order, Payment_Type::RECURRING(), $token, Payment_Initiated_By::MERCHANT(), null, null, '', $this->get_payment_method_to_use_for_intent(), $customer_id );
$this->process_payment_for_order( null, $payment_information, true );
} catch ( API_Exception $e ) {
Logger::error( 'Error processing subscription renewal: ' . $e->getMessage() );
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test-class-payment-information.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ public function test_set_token_updates_token() {
$this->assertTrue( $payment_information->is_using_saved_payment_method() );
}

public function test_get_customer_id() {
$expected_customer_id = 'old_customer_id';
$payment_information = new Payment_Information(
self::PAYMENT_METHOD,
null,
Payment_Type::SINGLE(),
$this->card_token,
null,
null,
null,
'',
null,
$expected_customer_id
);
$this->assertEquals( $expected_customer_id, $payment_information->get_customer_id() );
}

public function test_get_payment_method_from_request() {
$payment_method = Payment_Information::get_payment_method_from_request(
[ self::PAYMENT_METHOD_REQUEST_KEY => self::PAYMENT_METHOD ]
Expand Down
70 changes: 70 additions & 0 deletions tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,76 @@ public function test_scheduled_subscription_payment() {
$this->assertEquals( 'processing', $renewal_order->get_status() );
}

public function test_scheduled_subscription_payment_with_saved_customer_id() {
$saved_customer_id = self::CUSTOMER_ID . '_old';

$renewal_order = WC_Helper_Order::create_order( self::USER_ID );

$token = WC_Helper_Token::create_token( self::PAYMENT_METHOD_ID, self::USER_ID );
$renewal_order->add_payment_token( $token );

$this->order_service->set_customer_id_for_order( $renewal_order, $saved_customer_id );

$mock_subscription = new WC_Subscription();

$this->mock_wcs_get_subscriptions_for_renewal_order( [ '1' => $mock_subscription ] );

$this->mock_customer_service
->expects( $this->never() )
->method( 'get_customer_id_by_user_id' );

$request = $this->mock_wcpay_request( Create_And_Confirm_Intention::class );

$request->expects( $this->once() )
->method( 'set_customer' )
->with( $saved_customer_id );

$request->expects( $this->once() )
->method( 'set_payment_method' )
->with( self::PAYMENT_METHOD_ID );

$request->expects( $this->once() )
->method( 'set_cvc_confirmation' )
->with( null );

$request->expects( $this->once() )
->method( 'set_amount' )
->with( 5000 )
->willReturn( $request );

$request->expects( $this->once() )
->method( 'set_currency_code' )
->with( 'usd' )
->willReturn( $request );

$request->expects( $this->never() )
->method( 'setup_future_usage' );

$request->expects( $this->once() )
->method( 'set_capture_method' )
->with( false );

$request->expects( $this->once() )
->method( 'set_off_session' )
->with( true );

$request->expects( $this->once() )
->method( 'set_capture_method' )
->with( false );

$request->expects( $this->once() )
->method( 'format_response' )
->willReturn( WC_Helper_Intention::create_intention() );

$this->mock_customer_service
->expects( $this->never() )
->method( 'update_customer_for_user' );

$this->wcpay_gateway->scheduled_subscription_payment( $renewal_order->get_total(), $renewal_order );

$this->assertEquals( 'processing', $renewal_order->get_status() );
}

public function test_scheduled_subscription_payment_fails_when_token_is_missing() {
$renewal_order = WC_Helper_Order::create_order( self::USER_ID );

Expand Down

0 comments on commit 93eaed1

Please sign in to comment.