PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
← All changes | app/Modules/PaymentMethods/StripeGateway/UpdateCustomerPaymentMethod.php +83 -1 1.3.22 → 1.6.5 View file →
@@ -4,8 +4,9 @@
4 4
5 5 use FluentCart\App\Models\Subscription;
6 6 use FluentCart\App\Models\SubscriptionMeta;
7 7 use FluentCart\App\Modules\PaymentMethods\StripeGateway\API\API;
8 +use FluentCart\App\Modules\Subscriptions\Services\SystemChargeService;
8 9 use FluentCart\App\Services\Payments\PaymentHelper;
9 10 use FluentCart\Framework\Support\Arr;
10 11
11 12 class UpdateCustomerPaymentMethod
@@ -26,9 +27,18 @@
26 27 $newPaymentMethod = Arr::get($data, 'newPaymentMethod');
27 28 $verificationStatus = Arr::get($data, 'verification_status', '');
28 29
29 30 if (!$vendorSubscriptionId) {
30 - return;
31 + // System (auto-charged) subscriptions have no vendor subscription — the
32 + // stored token IS the subscription's payment method. Update it locally.
33 + $localSubscription = Subscription::query()->find($subscriptionId);
34 +
35 + if ($localSubscription instanceof Subscription && $localSubscription->isSystem()) {
36 + $this->updateSystemPaymentMethod($localSubscription, $newPaymentMethod, $verificationStatus);
37 + return;
38 + }
39 +
40 + throw new \Exception(esc_html__('This subscription has no vendor subscription to update.', 'fluent-cart'), 423);
31 41 }
32 42
33 43 // get customer id
34 44 $subscription = Subscription::query()->where('vendor_subscription_id', $vendorSubscriptionId)->first();
@@ -56,8 +66,80 @@
56 66 static::updateSubscriptionDefaultPaymentMethod($vendorSubscriptionId, $newPaymentMethodId, $newPaymentMethod, $customerId, $subscriptionId);
57 67
58 68 }
59 69
70 +
71 + /**
72 + * Replace the stored token of a system (auto-charged) subscription. No vendor
73 + * subscription exists, so nothing is called on Stripe's subscriptions API: the
74 + * new payment method is verified for off-session use (SCA mandate), attached to
75 + * the Stripe customer, and written to active_payment_method — which the charge
76 + * engine reads at fire time, so the next renewal (or a pending retry) uses it.
77 + *
78 + * @throws \Exception
79 + */
80 + private function updateSystemPaymentMethod(Subscription $subscription, $newPaymentMethod, $verificationStatus)
81 + {
82 + $newPaymentMethodId = Arr::get($newPaymentMethod, 'id');
83 + $customerId = $subscription->vendor_customer_id;
84 +
85 + if (!$newPaymentMethodId || !$customerId) {
86 + wp_send_json([
87 + 'status' => 'failed',
88 + 'message' => __('This subscription has no saved payment profile to update.', 'fluent-cart')
89 + ], 423);
90 + }
91 +
92 + if ('verify' === $verificationStatus) {
93 + // SCA-compliant off-session mandate (handles requires_action + rate limiting)
94 + $this->subscriptions::verifyPaymentMethod($newPaymentMethodId, $customerId, true);
95 + }
96 +
97 + $response = (new API())->createStripeObject('payment_methods/' . $newPaymentMethodId . '/attach', [
98 + 'customer' => $customerId
99 + ], $subscription->order ? $subscription->order->mode : 'current');
100 +
101 + if (is_wp_error($response)) {
102 + wp_send_json([
103 + 'status' => 'failed',
104 + 'message' => $response->get_error_message()
105 + ], 423);
106 + }
107 +
108 + $billingInfo = PaymentHelper::parsePaymentMethodDetails('stripe', $newPaymentMethod);
109 +
110 + SubscriptionMeta::updateOrCreate([
111 + 'subscription_id' => $subscription->id,
112 + //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
113 + 'meta_key' => 'active_payment_method'
114 + ], [
115 + //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
116 + 'meta_value' => json_encode($billingInfo)
117 + ]);
118 +
119 + // The previous decline reason describes the OLD card — the portal and admin
120 + // render it verbatim, so drop it. Retry bookkeeping and any in-flight
121 + // processing marker survive; the next attempt reads the new token.
122 + SystemChargeService::clearFailureState($subscription);
123 +
124 + $subscription->addLog(
125 + 'Payment method updated',
126 + __('The saved payment method for automatic renewal charges was updated by the customer.', 'fluent-cart'),
127 + 'info'
128 + );
129 +
130 + do_action('fluent_cart/subscriptions/system_payment_method_updated', [
131 + 'subscription' => $subscription,
132 + 'payment_method' => $billingInfo,
133 + ]);
134 +
135 + wp_send_json([
136 + 'status' => 'success',
137 + 'message' => __('Payment method updated successfully', 'fluent-cart'),
138 + 'action' => 'none',
139 + 'data' => $billingInfo
140 + ], 200);
141 + }
60 142
61 143 public static function updateSubscriptionDefaultPaymentMethod($vendorSubscriptionId, $newPaymentMethodId, $paymentMethod, $customerId, $subscriptionId, $customerDefault = false)
62 144 {
63 145 // attach payment method to customer and make it the default payment method for the subscription