PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
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 / StripeHelper.php

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

282 lines 9.9 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\Helpers\CurrenciesHelper;
6 use FluentCart\App\Helpers\Status;
7 use FluentCart\App\Models\Customer;
8 use FluentCart\App\Models\OrderTransaction;
9 use FluentCart\App\Models\Order;
10 use FluentCart\App\Modules\PaymentMethods\StripeGateway\API\API;
11 use FluentCart\App\Services\Payments\PaymentHelper;
12 use FluentCart\Api\StoreSettings;
13 use FluentCart\App\App;
14 use FluentCart\Framework\Support\Arr;
15
16 class StripeHelper
17 {
18 public static function createOrGetStripeCustomer(Customer $customer)
19 {
20 // check if we already have a stripe_customer_id for this person
21 $existingStripeCustomerId = $customer->getMeta('stripe_customer_id', false);
22 if ($existingStripeCustomerId) {
23 $existingStripeCustomer = (new API())->getStripeObject('customers/' . $existingStripeCustomerId);
24
25 if (!is_wp_error($existingStripeCustomer) && is_array($existingStripeCustomer) && !empty($existingStripeCustomer['id']) && isset($existingStripeCustomer['email']) && $existingStripeCustomer['email'] === $customer->email) {
26 return $existingStripeCustomer;
27 }
28 }
29
30 $customerInfo = array_filter([
31 'name' => $customer->full_name,
32 'email' => $customer->email,
33 'phone' => $customer->phone ?? '',
34 'address' => array_filter([
35 'city' => $customer->city ?? '',
36 'country' => $customer->country ?? '',
37 'postal_code' => $customer->postcode ?? '',
38 'state' => $customer->state ?? '',
39 ])
40 ]);
41
42 $newStripeCustomer = (new API())->createStripeObject('customers', $customerInfo);
43
44 if (is_wp_error($newStripeCustomer)) {
45 return $newStripeCustomer;
46 }
47
48 $id = Arr::get($newStripeCustomer, 'id', false);
49
50 if ($id) {
51 $customer->updateMeta('stripe_customer_id', $id);
52 }
53
54 return $newStripeCustomer;
55 }
56
57 public static function transformSubscriptionStatus($stripeSubscription, $subscriptionModel = null)
58 {
59 $status = strtolower($stripeSubscription['status']);
60
61 if ($status === 'active') {
62 $status = Status::SUBSCRIPTION_ACTIVE;
63 } else if ($status === 'incomplete' || $status === 'incomplete_expired') {
64 $status = Status::SUBSCRIPTION_INTENDED;
65 } else if ($status === 'trialing') {
66 $status = Status::SUBSCRIPTION_TRIALING;
67 } else if ($status === 'canceled') {
68 $status = Status::SUBSCRIPTION_CANCELED;
69 if (Arr::get($stripeSubscription, 'cancellation_details.reason', '') === 'payment_failed') {
70 $status = Status::SUBSCRIPTION_EXPIRED;
71 }
72 } else if ($status === 'unpaid') {
73 $status = Status::SUBSCRIPTION_EXPIRED;
74 } else if ($status === 'paused') {
75 $status = Status::SUBSCRIPTION_PAUSED;
76 } else if ($status === 'past_due') {
77 $status = Status::SUBSCRIPTION_EXPIRING;
78 if ($subscriptionModel && $subscriptionModel->status === 'expired') {
79 $status = Status::SUBSCRIPTION_EXPIRED;
80 }
81 }
82
83 return $status;
84 }
85
86 public static function getSubscriptionUpdateData($stripeSubscription, $subscriptionModel = null)
87 {
88 $stripeStatus = strtolower(Arr::get($stripeSubscription, 'status', ''));
89
90 $status = self::transformSubscriptionStatus($stripeSubscription, $subscriptionModel);
91
92 $amount = Arr::get($stripeSubscription, 'plan.amount', 0);
93 $currency = Arr::get($stripeSubscription, 'plan.currency', null);
94
95 if ($currency && CurrenciesHelper::isZeroDecimal($currency)) {
96 $amount = $amount * 100;
97 }
98
99 $subscriptionUpdateData = array_filter([
100 'current_payment_method' => 'stripe',
101 'status' => $status,
102 'recurring_total' => $amount,
103 ]);
104
105 if ($stripeStatus == Status::SUBSCRIPTION_CANCELED) {
106 $cancelledAt = (int)Arr::get($stripeSubscription, 'canceled_at');
107 if ($cancelledAt) {
108 $subscriptionUpdateData['canceled_at'] = gmdate('Y-m-d H:i:s', $cancelledAt);
109 }
110 }
111
112 $currentPeriodEnds = (int)Arr::get($stripeSubscription, 'current_period_end');
113 // we have to check if the last invoice is paid or not!
114 // If not paid, then we have to use the $stripeSubscription['current_period_start']
115 $latestInvoice = Arr::get($stripeSubscription, 'latest_invoice', null);
116 if ($latestInvoice && !empty($latestInvoice['id'])) {
117 // we have the latest invoice
118 if (Arr::get($latestInvoice, 'status') !== 'paid') {
119 $currentPeriodEnds = (int)Arr::get($stripeSubscription, 'current_period_start');
120 }
121 }
122
123 if ($currentPeriodEnds) {
124 $subscriptionUpdateData['next_billing_date'] = gmdate('Y-m-d H:i:s', $currentPeriodEnds);
125 }
126
127 return $subscriptionUpdateData;
128 }
129
130
131 /**
132 * Convert a Stripe wire amount (invoice/charge/payment_intent) to FluentCart's
133 * internal ×100 unit. Zero-decimal currencies (JPY, KRW, ...) come off the wire
134 * unscaled, so they need the ×100 that non-zero-decimal currencies already have.
135 *
136 * @param int|float $amount
137 * @param string $currency
138 * @return int
139 */
140 public static function toInternalAmount($amount, $currency)
141 {
142 $amount = (int)$amount;
143
144 if ($currency && CurrenciesHelper::isZeroDecimal($currency)) {
145 $amount *= 100;
146 }
147
148 return $amount;
149 }
150
151 public static function processRemoteRefund($transaction, $amount, $args)
152 {
153 $intentId = $transaction->vendor_charge_id;
154 if (!$intentId) {
155 return new \WP_Error('invalid_refund', __('Invalid transaction ID for refund.', 'fluent-cart'));
156 }
157
158 $refundAmount = (int)$amount;
159 $refundCurrency = $transaction->currency;
160
161 if ($refundCurrency && CurrenciesHelper::isZeroDecimal($refundCurrency)) {
162 $refundAmount = (int)($refundAmount / 100);
163 }
164
165 $refundData = [
166 'payment_intent' => $intentId,
167 'amount' => $refundAmount,
168 ];
169
170 $reason = Arr::get($args, 'reason', '');
171
172 if ($reason && in_array($reason, ['duplicate', 'fraudulent', 'requested_by_customer'])) {
173 $refundData['reason'] = $reason;
174 }
175
176 $refunded = (new API())->createStripeObject('refunds', $refundData, $transaction->payment_mode);
177
178 if (is_wp_error($refunded)) {
179 return $refunded;
180 }
181
182 $status = Arr::get($refunded, 'status');
183 $acceptedStatus = ['succeeded', 'pending'];
184 if (!in_array($status, $acceptedStatus)) {
185 return new \WP_Error('refund_failed', __('Refund could not be processed in stripe. Please check on your stripe account', 'fluent-cart'));
186 }
187
188 return Arr::get($refunded, 'id');
189 }
190
191 public static function createOrUpdateIpnRefund($refundData, $parentTransaction)
192 {
193 $allRefunds = OrderTransaction::query()
194 ->where('order_id', $refundData['order_id'])
195 ->where('transaction_type', Status::TRANSACTION_TYPE_REFUND)
196 ->orderBy('id', 'DESC')
197 ->get();
198
199 if ($allRefunds->isEmpty()) {
200 // this is the first refund for this order
201 return OrderTransaction::query()->create($refundData);
202 }
203
204 $currentRefundTransactionId = Arr::get($refundData, 'meta.parent_id', '');
205
206 $existingLocalRefund = null;
207 foreach ($allRefunds as $refund) {
208 if ($refund->vendor_charge_id == $refundData['vendor_charge_id']) {
209 if ($refund->total != $refundData['total']) {
210 $refund->fill($refundData);
211 $refund->save();
212 }
213 // this refund already exists
214 return $refund;
215 }
216
217 if (!$refund->vendor_charge_id) { // this is a local redfund without vendor charge id
218 $refundTransactionId = Arr::get($refund->meta, 'parent_id', '');
219 $isTransactionMatched = $refundTransactionId == $currentRefundTransactionId;
220
221 // this is a local refund without vendor charge id, we will update it
222 if ($refund->total == $refundData['total'] && $isTransactionMatched) {
223 // this refund already exists
224 $existingLocalRefund = $refund;
225 }
226 }
227 }
228
229 if ($existingLocalRefund) {
230 $existingLocalRefund->fill($refundData);
231 $existingLocalRefund->save();
232 return $existingLocalRefund;
233 }
234
235 $createdRefund = OrderTransaction::query()->create($refundData);
236
237 PaymentHelper::updateTransactionRefundedTotal($parentTransaction, $createdRefund->total);
238 return $createdRefund;
239 }
240
241 /*
242 * To validate by session, id
243 *
244 */
245 public static function validateBySession($id)
246 {
247 $apiKey = (new StripeSettingsBase())->getApiKey();
248
249 $session = (new API())->makeRequest('checkout/sessions/' . $id, [], $apiKey, 'GET');
250
251 if (!$session || is_wp_error($session)) {
252 return null;
253 }
254
255
256 $order = Order::query()
257 ->where('uuid', Arr::get($session, 'client_reference_id'))
258 ->first();
259
260 if (!$order) {
261 return null;
262 }
263
264 return $order;
265
266 }
267
268 public static function getCancelUrl(): string
269 {
270 $checkoutPage = (new StoreSettings())->getCheckoutPage();
271 // get cart hash from url
272 $cartHash = App::request()->get('fct_cart_hash', '');
273 if ($cartHash) {
274 return add_query_arg([
275 'fct_cart_hash' => $cartHash
276 ], $checkoutPage);
277 }
278 return $checkoutPage;
279 }
280
281 }
282