PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.0
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.0
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
fluent-cart / app / Modules / PaymentMethods / StripeGateway / UpdateCustomerPaymentMethod.php

UpdateCustomerPaymentMethod.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.0, at app/Modules/PaymentMethods/StripeGateway/UpdateCustomerPaymentMethod.php

188 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Modules\PaymentMethods\StripeGateway;
4
5 use FluentCart\App\Models\Subscription;
6 use FluentCart\App\Models\SubscriptionMeta;
7 use FluentCart\App\Modules\PaymentMethods\StripeGateway\API\API;
8 use FluentCart\App\Modules\Subscriptions\Services\SystemChargeService;
9 use FluentCart\App\Services\Payments\PaymentHelper;
10 use FluentCart\Framework\Support\Arr;
11
12 class UpdateCustomerPaymentMethod
13 {
14 private SubscriptionsManager $subscriptions;
15
16 public function __construct()
17 {
18 $this->subscriptions = new SubscriptionsManager();
19 }
20
21 /**
22 * @throws \Exception
23 */
24 public function update($data, $subscriptionId)
25 {
26 $vendorSubscriptionId = Arr::get($data, 'vendor_subscription_id');
27 $newPaymentMethod = Arr::get($data, 'newPaymentMethod');
28 $verificationStatus = Arr::get($data, 'verification_status', '');
29
30 if (!$vendorSubscriptionId) {
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 }
38
39 return;
40 }
41
42 // get customer id
43 $subscription = Subscription::query()->where('vendor_subscription_id', $vendorSubscriptionId)->first();
44 $customerId = $subscription->vendor_customer_id;
45
46 $newPaymentMethodId = Arr::get($newPaymentMethod, 'id');
47
48 if ('verify' === $verificationStatus) {
49 // verify the payment method
50 $this->subscriptions::verifyPaymentMethod($newPaymentMethodId, $customerId, true);
51 }
52
53
54 $response = (new API())->createStripeObject('payment_methods/' . $newPaymentMethodId . '/attach', ['customer' => $customerId]);
55
56 if (is_wp_error($response)) {
57 wp_send_json([
58 'status' => 'failed',
59 'message' => $response->get_error_message()
60 ], 423);
61 }
62
63 // now update the subscription's default payment method ,
64 // ensures that the subscription will use the new payment method for the next invoice and all subsequent invoices
65 static::updateSubscriptionDefaultPaymentMethod($vendorSubscriptionId, $newPaymentMethodId, $newPaymentMethod, $customerId, $subscriptionId);
66
67 }
68
69
70 /**
71 * Replace the stored token of a system (auto-charged) subscription. No vendor
72 * subscription exists, so nothing is called on Stripe's subscriptions API: the
73 * new payment method is verified for off-session use (SCA mandate), attached to
74 * the Stripe customer, and written to active_payment_method — which the charge
75 * engine reads at fire time, so the next renewal (or a pending retry) uses it.
76 *
77 * @throws \Exception
78 */
79 private function updateSystemPaymentMethod(Subscription $subscription, $newPaymentMethod, $verificationStatus)
80 {
81 $newPaymentMethodId = Arr::get($newPaymentMethod, 'id');
82 $customerId = $subscription->vendor_customer_id;
83
84 if (!$newPaymentMethodId || !$customerId) {
85 wp_send_json([
86 'status' => 'failed',
87 'message' => __('This subscription has no saved payment profile to update.', 'fluent-cart')
88 ], 423);
89 }
90
91 if ('verify' === $verificationStatus) {
92 // SCA-compliant off-session mandate (handles requires_action + rate limiting)
93 $this->subscriptions::verifyPaymentMethod($newPaymentMethodId, $customerId, true);
94 }
95
96 $response = (new API())->createStripeObject('payment_methods/' . $newPaymentMethodId . '/attach', [
97 'customer' => $customerId
98 ], $subscription->order ? $subscription->order->mode : 'current');
99
100 if (is_wp_error($response)) {
101 wp_send_json([
102 'status' => 'failed',
103 'message' => $response->get_error_message()
104 ], 423);
105 }
106
107 $billingInfo = PaymentHelper::parsePaymentMethodDetails('stripe', $newPaymentMethod);
108
109 SubscriptionMeta::updateOrCreate([
110 'subscription_id' => $subscription->id,
111 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
112 'meta_key' => 'active_payment_method'
113 ], [
114 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
115 'meta_value' => json_encode($billingInfo)
116 ]);
117
118 // The previous decline reason describes the OLD card — the portal and admin
119 // render it verbatim, so drop it. Retry bookkeeping and any in-flight
120 // processing marker survive; the next attempt reads the new token.
121 SystemChargeService::clearFailureState($subscription);
122
123 $subscription->addLog(
124 'Payment method updated',
125 __('The saved payment method for automatic renewal charges was updated by the customer.', 'fluent-cart'),
126 'info'
127 );
128
129 do_action('fluent_cart/subscriptions/system_payment_method_updated', [
130 'subscription' => $subscription,
131 'payment_method' => $billingInfo,
132 ]);
133
134 wp_send_json([
135 'status' => 'success',
136 'message' => __('Payment method updated successfully', 'fluent-cart'),
137 'action' => 'none',
138 'data' => $billingInfo
139 ], 200);
140 }
141
142 public static function updateSubscriptionDefaultPaymentMethod($vendorSubscriptionId, $newPaymentMethodId, $paymentMethod, $customerId, $subscriptionId, $customerDefault = false)
143 {
144 // attach payment method to customer and make it the default payment method for the subscription
145 $subscriptionData = [
146 'default_payment_method' => $newPaymentMethodId
147 ];
148
149 $response = (new API())->createStripeObject('subscriptions/' . $vendorSubscriptionId, $subscriptionData);
150
151 if (is_wp_error($response)) {
152 wp_send_json([
153 'status' => 'failed',
154 'message' => $response->get_error_message()
155 ], 423);
156 }
157
158 if ($customerDefault) {
159 $response = (new API())->createStripeObject('customers/' . $customerId, ['invoice_settings' => ['default_payment_method' => $newPaymentMethodId]]);
160 if (is_wp_error($response)) {
161 wp_send_json([
162 'status' => 'failed',
163 'message' => $response->get_error_message()
164 ], 423);
165 }
166 }
167
168 // update active payment method subscription meta
169 $billingInfo = PaymentHelper::parsePaymentMethodDetails('stripe', $paymentMethod);
170
171 SubscriptionMeta::updateOrCreate([
172 'subscription_id' => $subscriptionId,
173 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
174 'meta_key' => 'active_payment_method'
175 ], [
176 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
177 'meta_value' => json_encode($billingInfo)
178 ]);
179
180 wp_send_json([
181 'status' => 'success',
182 'message' => __('Payment method updated successfully', 'fluent-cart'),
183 'action' => 'none',
184 'data' => $billingInfo
185 ], 200);
186 }
187 }
188