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 / PayPalGateway / PayPalSubscriptions.php

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

242 lines 9.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\PayPalGateway;
4
5 use FluentCart\App\Helpers\Helper;
6 use FluentCart\App\Helpers\Status;
7 use FluentCart\App\Models\OrderTransaction;
8 use FluentCart\App\Models\Subscription;
9 use FluentCart\App\Modules\PaymentMethods\Core\AbstractSubscriptionModule;
10 use FluentCart\App\Modules\PaymentMethods\PayPalGateway\API\API;
11 use FluentCart\App\Modules\Subscriptions\Services\SubscriptionService;
12 use FluentCart\App\Services\DateTime\DateTime;
13 use FluentCart\Framework\Support\Arr;
14
15 class PayPalSubscriptions extends AbstractSubscriptionModule
16 {
17 public function reSyncSubscriptionFromRemote(Subscription $subscriptionModel)
18 {
19 $order = $subscriptionModel->order;
20
21 $paypalSubscription = (new API())->verifySubscription($subscriptionModel->vendor_subscription_id, $order->mode);
22
23 if (is_wp_error($paypalSubscription)) {
24 return $paypalSubscription;
25 }
26
27 $newPayment = false;
28
29 $subscriptionStatus = (new SubscriptionManager)->getCorrectSubscriptionStatus(Arr::get($paypalSubscription, 'status'));
30 $nextBillingDate = Arr::get($paypalSubscription, 'billing_info.next_billing_time') ?? null;
31
32 $payer = Arr::get($paypalSubscription, 'subscriber', []);
33
34 if ($nextBillingDate) {
35 $nextBillingDate = gmdate('Y-m-d H:i:s', strtotime($nextBillingDate));
36 }
37
38 $subscriptionUpdateData = array_filter([
39 'current_payment_method' => 'paypal',
40 'status' => $subscriptionStatus
41 ]);
42
43 if ($nextBillingDate) {
44 $subscriptionUpdateData['next_billing_date'] = $nextBillingDate;
45 }
46
47 if (Arr::get($paypalSubscription, 'status') === 'CANCELLED') {
48 $statusUpdateTime = Arr::get($paypalSubscription, 'status_update_time');
49 if ($statusUpdateTime) {
50 $subscriptionUpdateData['canceled_at'] = gmdate('Y-m-d H:i:s', strtotime($statusUpdateTime));
51 }
52 }
53
54 $startTime = Arr::get($paypalSubscription, 'start_time');
55 $endTime = DateTime::gmtNow()->format('Y-m-d\TH:i:s.v\Z');
56
57 $response = (new API())->getResource('billing/subscriptions/' . $subscriptionModel->vendor_subscription_id . '/transactions', [
58 'start_time' => $startTime,
59 'end_time' => $endTime
60 ], $order->mode);
61
62 if (is_wp_error($response)) {
63 return $response;
64 }
65
66 // reverse the array to get the latest transaction last
67 $paypalTransactions = array_reverse(Arr::get($response, 'transactions', []));
68
69
70 if (!empty($paypalTransactions)) {
71 foreach ($paypalTransactions as $paypalTransaction) {
72
73 $amount = Helper::toCent(Arr::get($paypalTransaction, 'amount_with_breakdown.gross_amount.value', 0));
74 $chargeId = Arr::get($paypalTransaction, 'id');
75
76 $status = strtolower(Arr::get($paypalTransaction, 'status'));
77
78 if ($status == 'completed') {
79 $transaction = OrderTransaction::query()
80 ->select(['id', 'order_id'])
81 ->where('vendor_charge_id', $chargeId)
82 ->first();
83
84 if (!$transaction) {
85 // check if any transaction related to this subscription exists without vendor_charge_id, mainly for first cycle payment
86 $transaction = OrderTransaction::query()
87 ->select(['id', 'order_id'])
88 ->where('subscription_id', $subscriptionModel->id)
89 ->where('vendor_charge_id', '')
90 ->where('total', $amount)
91 ->where('transaction_type', Status::TRANSACTION_TYPE_CHARGE)
92 ->first();
93
94 if ($transaction) {
95 $transaction->update([
96 'vendor_charge_id' => $chargeId,
97 'status' => Status::TRANSACTION_SUCCEEDED,
98 'payment_method_type' => 'PayPal',
99 'meta' => array_merge($transaction->meta, [
100 'payer' => $payer
101 ])
102 ]);
103 continue;
104 }
105
106 $transactionData = [
107 'subscription_id' => $subscriptionModel->id,
108 'payment_method' => 'paypal',
109 'vendor_charge_id' => $chargeId,
110 'payment_method_type' => 'PayPal',
111 'total' => $amount,
112 'meta' => [
113 'payer' => $payer
114 ],
115 'created_at' => DateTime::anyTimeToGmt(Arr::get($paypalTransaction, 'time'))->format('Y-m-d H:i:s'),
116 ];
117
118 $newPayment = true;
119 SubscriptionService::recordRenewalPayment($transactionData, $subscriptionModel, $subscriptionUpdateData);
120 } else if ($transaction->status !== Status::TRANSACTION_SUCCEEDED) {
121 $transaction->update([
122 'vendor_charge_id' => $chargeId,
123 'status' => Status::TRANSACTION_SUCCEEDED,
124 'meta' => array_merge($transaction->meta, [
125 'payer' => $payer
126 ])
127 ]);
128 }
129 }
130 }
131 }
132
133 if (!$newPayment) {
134 $subscriptionModel = SubscriptionService::syncSubscriptionStates($subscriptionModel, $subscriptionUpdateData);
135 } else {
136 $subscriptionModel = Subscription::query()->find($subscriptionModel->id);
137 }
138
139 return $subscriptionModel;
140 }
141
142 public function cancel($vendorSubscriptionId, $args = [])
143 {
144 if (!$vendorSubscriptionId) {
145 return new \WP_Error('invalid_subscription', __('Invalid vendor subscription ID.', 'fluent-cart'));
146 }
147
148 // first check , before canceling the subscription
149 $paypalSubscription = (new API())->verifySubscription($vendorSubscriptionId, Arr::get($args, 'mode', ''));
150
151 if (is_wp_error($paypalSubscription)) {
152 return $paypalSubscription;
153 }
154
155 $subscriptionStatus = (new SubscriptionManager)->getCorrectSubscriptionStatus(Arr::get($paypalSubscription, 'status'));
156
157 // CANCELLED and EXPIRED are terminal at PayPal — the cancel API rejects them with SUBSCRIPTION_STATUS_INVALID
158 if (in_array($subscriptionStatus, [Status::SUBSCRIPTION_CANCELED, Status::SUBSCRIPTION_EXPIRED])) {
159 $result = [
160 'status' => $subscriptionStatus
161 ];
162
163 if ($subscriptionStatus === Status::SUBSCRIPTION_CANCELED) {
164 $statusUpdateTime = Arr::get($paypalSubscription, 'status_update_time');
165 $result['canceled_at'] = $statusUpdateTime ? gmdate('Y-m-d H:i:s', strtotime($statusUpdateTime)) : NULL;
166 }
167
168 return $result;
169 }
170
171 $response = API::createResource('billing/subscriptions/' . $vendorSubscriptionId . '/cancel', [
172 'reason' => Arr::get($args, 'reason', __('Subscription canceled.', 'fluent-cart')),
173 ], Arr::get($args, 'mode', ''));
174
175 if (is_wp_error($response)) {
176 return $response;
177 }
178
179 return [
180 'status' => Status::SUBSCRIPTION_CANCELED
181 ];
182 }
183
184 /**
185 * Resume (activate) a suspended PayPal subscription.
186 *
187 * Called by SubscriptionService::resumeSubscription for automatic subscriptions.
188 * Remote first: activates the subscription at PayPal, and only on success flips
189 * the local status and fires the SubscriptionResumed event.
190 *
191 * @param Subscription $subscription
192 * @param string $reason
193 * @return true|\WP_Error
194 */
195 public function resume(Subscription $subscription, $reason = '')
196 {
197 $vendorSubscriptionId = $subscription->vendor_subscription_id;
198
199 if (!$vendorSubscriptionId) {
200 return new \WP_Error('invalid_subscription', __('Invalid vendor subscription ID.', 'fluent-cart'));
201 }
202
203 $order = $subscription->order;
204
205 $response = API::createResource('billing/subscriptions/' . $vendorSubscriptionId . '/activate', [
206 'reason' => $reason ?: __('Subscription resumed.', 'fluent-cart'),
207 ], $order ? $order->mode : '');
208
209 if (is_wp_error($response)) {
210 return $response;
211 }
212
213 $oldStatus = $subscription->status;
214 $subscription->status = Status::SUBSCRIPTION_ACTIVE;
215 $subscription->save();
216
217 $subscription->addLog(
218 'Subscription resumed',
219 $reason ?: __('Subscription resumed via PayPal', 'fluent-cart'),
220 'info'
221 );
222
223 SubscriptionService::dispatchStatusEvent($subscription, 'resumed', [
224 'old_status' => $oldStatus,
225 'reason' => $reason,
226 ]);
227
228 return true;
229 }
230
231 public function getOrCreateNewPlan($subscriptionId, $reason)
232 {
233 (new SubscriptionManager())->getOrCreateNewPlan($subscriptionId, $reason);
234 }
235
236 public function confirmSubscriptionSwitch($data, $subscriptionId)
237 {
238 (new SubscriptionManager())->confirmSubscriptionSwitch($data, $subscriptionId);
239 }
240
241 }
242