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

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

189 lines 7.5 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 return;
38 }
39
40 throw new \Exception(esc_html__('This subscription has no vendor subscription to update.', 'fluent-cart'), 423);
41 }
42
43 // get customer id
44 $subscription = Subscription::query()->where('vendor_subscription_id', $vendorSubscriptionId)->first();
45 $customerId = $subscription->vendor_customer_id;
46
47 $newPaymentMethodId = Arr::get($newPaymentMethod, 'id');
48
49 if ('verify' === $verificationStatus) {
50 // verify the payment method
51 $this->subscriptions::verifyPaymentMethod($newPaymentMethodId, $customerId, true);
52 }
53
54
55 $response = (new API())->createStripeObject('payment_methods/' . $newPaymentMethodId . '/attach', ['customer' => $customerId]);
56
57 if (is_wp_error($response)) {
58 wp_send_json([
59 'status' => 'failed',
60 'message' => $response->get_error_message()
61 ], 423);
62 }
63
64 // now update the subscription's default payment method ,
65 // ensures that the subscription will use the new payment method for the next invoice and all subsequent invoices
66 static::updateSubscriptionDefaultPaymentMethod($vendorSubscriptionId, $newPaymentMethodId, $newPaymentMethod, $customerId, $subscriptionId);
67
68 }
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 }
142
143 public static function updateSubscriptionDefaultPaymentMethod($vendorSubscriptionId, $newPaymentMethodId, $paymentMethod, $customerId, $subscriptionId, $customerDefault = false)
144 {
145 // attach payment method to customer and make it the default payment method for the subscription
146 $subscriptionData = [
147 'default_payment_method' => $newPaymentMethodId
148 ];
149
150 $response = (new API())->createStripeObject('subscriptions/' . $vendorSubscriptionId, $subscriptionData);
151
152 if (is_wp_error($response)) {
153 wp_send_json([
154 'status' => 'failed',
155 'message' => $response->get_error_message()
156 ], 423);
157 }
158
159 if ($customerDefault) {
160 $response = (new API())->createStripeObject('customers/' . $customerId, ['invoice_settings' => ['default_payment_method' => $newPaymentMethodId]]);
161 if (is_wp_error($response)) {
162 wp_send_json([
163 'status' => 'failed',
164 'message' => $response->get_error_message()
165 ], 423);
166 }
167 }
168
169 // update active payment method subscription meta
170 $billingInfo = PaymentHelper::parsePaymentMethodDetails('stripe', $paymentMethod);
171
172 SubscriptionMeta::updateOrCreate([
173 'subscription_id' => $subscriptionId,
174 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
175 'meta_key' => 'active_payment_method'
176 ], [
177 //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
178 'meta_value' => json_encode($billingInfo)
179 ]);
180
181 wp_send_json([
182 'status' => 'success',
183 'message' => __('Payment method updated successfully', 'fluent-cart'),
184 'action' => 'none',
185 'data' => $billingInfo
186 ], 200);
187 }
188 }
189