PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.19
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.19
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 / PayPalGateway / PayPalHelper.php

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

338 lines 12.1 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\Api\StoreSettings;
6 use FluentCart\App\Helpers\Helper;
7 use FluentCart\App\Helpers\Status;
8 use FluentCart\App\Models\Product;
9 use FluentCart\App\Models\ProductVariation;
10 use FluentCart\App\Modules\PaymentMethods\PayPalGateway\API\API;
11 use FluentCart\App\Services\ProductItemService;
12 use FluentCart\Framework\Support\Arr;
13
14 class PayPalHelper
15 {
16 /**
17 * Get or create a Stripe pricing plan for a product variation.
18 *
19 * @param array $data {
20 * @type string $product_id Product ID.
21 * @type string $variation_id Variation ID.
22 * @type string $trial_days Trial Days.
23 * @type string $billing_interval Billing interval (e.g., 'month', 'year').
24 * @type string $currency Currency code (e.g., 'usd').
25 * @type int $interval_count Number of intervals.
26 * @type int $recurring_amount Recurring total amount (in cents).
27 * @type int $signup_fee Recurring total amount (in cents).
28 * @type int bill_times optional 0 for continuous billing, or a specific number of billing cycles
29 * }
30 *
31 * @return \WP_Error|array
32 */
33 public static function getPayPalPlan($data = [])
34 {
35 $item = ProductItemService::getItem($data);
36 $product = $item->product;
37 $variation = $item->variation;
38
39 if (!$variation || !$product) {
40 return new \WP_Error('invalid_product', esc_html__('Invalid product or variation.', 'fluent-cart'));
41 }
42 $sitePrefix = Helper::getSitePrefix();
43
44 $planId = 'fct_pp_plan_'
45 . $data['currency'] . '_'
46 . $data['variation_id'] . '_'
47 . $data['recurring_amount'] . '_'
48 . $data['billing_interval'] . '_'
49 . $data['interval_count'] . '_'
50 . $data['trial_days'] . '_'
51 . $data['signup_fee'] . '_'
52 . $data['bill_times'];
53
54 $planId = apply_filters('fluent_cart/paypal_plan_id', $planId, [
55 'plan_data' => $data,
56 'variation' => $variation,
57 'product' => $product
58 ]);
59
60 $paypalPlan = null;
61 if ($product && $product instanceof Product) {
62 $paypalPlanId = $product->getProductMeta($planId);
63
64 if ($paypalPlanId) {
65 $paypalPlan = API::getResource('billing/plans/' . $paypalPlanId);
66 if (!is_wp_error($paypalPlan)) {
67 return $paypalPlan;
68 }
69 }
70 }
71
72 $paypalProduct = self::getRemoteProductByData([
73 'id' => 'prod_' . $data['product_id'] . '_' . $sitePrefix,
74 'name' => sanitize_text_field($product->post_title),
75 'description' => sanitize_text_field($product->post_excerpt)
76 ]);
77
78 if (is_wp_error($paypalProduct)) {
79 return $paypalProduct;
80 }
81
82 $planData = wp_parse_args($data, [
83 'plan_name' => $product->post_title . ' - ' . $variation->variation_title,
84 'plan_description' => $product->post_title,
85 'paypal_product_id' => $paypalProduct['id'],
86 ]);
87
88 $paypalPlanArgs = self::getPayPalPlanArgs($planData);
89
90 $paypalPlan = API::createResource('billing/plans', $paypalPlanArgs);
91
92 if (is_wp_error($paypalPlan)) {
93 return $paypalPlan;
94 }
95
96
97 if ($product && $product instanceof Product) {
98 $product->updateProductMeta($planId, $paypalPlan['id']);
99 }
100
101 return $paypalPlan;
102 }
103
104 public static function processRemoteRefund($transaction, $amount, $args)
105 {
106 $chargeId = $transaction->vendor_charge_id;
107
108 if (!$chargeId) {
109 return new \WP_Error('invalid_charge_id', __('Please provide a valid charge Id!', 'fluent-cart'));
110 }
111
112 $refundData = [
113 'custom_id' => $transaction->uuid,
114 'amount' => array(
115 'value' => Helper::toDecimalWithoutComma($amount),
116 'currency_code' => $transaction->currency
117 ),
118
119 ];
120
121
122 $reason = Arr::get($args, 'reason', '');
123 if ($reason) {
124 $refundData['note_to_payer'] = substr($reason, 0, 255);
125 }
126
127 $vendorRefund = (new API())->makeRequest('payments/captures/' . $chargeId . '/refund', 'v2', 'POST', $refundData);
128
129 if (is_wp_error($vendorRefund)) {
130 return $vendorRefund;
131 }
132
133 if (Arr::get($vendorRefund, 'status') !== 'COMPLETED') {
134 return new \WP_Error('refund_not_completed', __('Refund not completed in paypal. Please refund from PayPal Manually.', 'fluent-cart'));
135 }
136
137 return Arr::get($vendorRefund, 'id');
138 }
139
140 private static function getRemoteProductByData($productData = [])
141 {
142
143 if (isset($productData['id']) && strlen($productData['id']) > 50) {
144 $productData['id'] = substr($productData['id'], 0, 48); // PayPal product ID should be less than 50 characters
145 }
146
147 if (isset($productData['id'])) {
148 $existingProduct = API::getResource('catalogs/products/' . $productData['id']);
149 if (!is_wp_error($existingProduct)) {
150 return $existingProduct;
151 }
152 }
153
154
155 $formattedData = [
156 'id' => Arr::get($productData, 'id'),
157 'name' => Arr::get($productData, 'name'),
158 'description' => Arr::get($productData, 'description'),
159 'type' => Arr::get($productData, 'type'),
160 ];
161
162 $validTypes = ['PHYSICAL', 'DIGITAL', 'SERVICE'];
163 if (!in_array($formattedData['type'], $validTypes)) {
164 $formattedData['type'] = 'DIGITAL'; // Default to PHYSICAL if type is not valid
165 }
166
167 // name should be under 127 characters, add '...' if it is more than 127 characters
168 if ($formattedData['name'] && strlen($formattedData['name']) > 127) {
169 $formattedData['name'] = substr($formattedData['name'], 0, 120) . '...';
170 }
171
172 // description should be under 256 characters, add '...' if it is more than 256 characters
173 if ($formattedData['description'] && strlen($formattedData['description']) > 256) {
174 $formattedData['description'] = substr($formattedData['description'], 0, 250) . '...';
175 }
176
177 $formattedData = array_filter($formattedData);
178
179 $createdProduct = API::createResource('catalogs/products', $formattedData);
180
181
182 return $createdProduct;
183 }
184
185 private static function getPayPalPlanArgs($data = [])
186 {
187 // Default values
188 $defaults = [
189 'paypal_product_id' => '',
190 'trial_days' => 0,
191 'billing_interval' => 'monthly',
192 'currency' => 'USD',
193 'interval_count' => 1,
194 'recurring_amount' => 0,
195 'signup_fee' => 0,
196 'bill_times' => 0,
197 'plan_name' => __('Subscription', 'fluent-cart'),
198 'plan_description' => __('Subscription Plan', 'fluent-cart')
199 ];
200
201 // Merge input data with defaults
202 $data = wp_parse_args($data, $defaults);
203
204 $data['product_id'] = $data['paypal_product_id'];
205 $data['recurring_total'] = $data['recurring_amount'];
206
207 // Convert amounts from cents to dollars with 2 decimal places
208 $recurring_amount = number_format($data['recurring_total'] / 100, 2, '.', '');
209 $initial_amount = $data['signup_fee'] > 0 ? number_format($data['signup_fee'] / 100, 2, '.', '') : 0;
210
211 // Map billing interval to PayPal API interval unit
212 $interval_map = [
213 Status::BILLING_MONTHLY => 'MONTH',
214 Status::BILLING_QUARTERLY => 'MONTH',
215 Status::BILLING_HALF_YEARLY => 'MONTH',
216 Status::BILLING_YEARLY => 'YEAR',
217 Status::BILLING_WEEKLY => 'WEEK',
218 Status::BILLING_DAILY => 'DAY'
219 ];
220
221 $interval_unit = isset($interval_map[$data['billing_interval']]) ? $interval_map[$data['billing_interval']] : 'MONTH';
222
223 // Determine interval_count based on billing_interval
224 $intervalCount = 1;
225 if ($data['billing_interval'] === Status::BILLING_QUARTERLY) {
226 $intervalCount = 3;
227 } elseif ($data['billing_interval'] === Status::BILLING_HALF_YEARLY) {
228 $intervalCount = 6;
229 }
230
231 $intervalCount = 1;
232 if ($data['billing_interval'] === Status::BILLING_QUARTERLY) {
233 $intervalCount = 3;
234 } elseif ($data['billing_interval'] === Status::BILLING_HALF_YEARLY) {
235 $intervalCount = 6;
236 }
237
238 $billingPeriod = [
239 'interval_unit' => $interval_unit,
240 'interval_frequency' => $intervalCount,
241 ];
242
243 $billingPeriod = apply_filters('fluent_cart/subscription_billing_period', $billingPeriod, [
244 'subscription_interval' => $data['billing_interval'],
245 'payment_method' => 'paypal',
246 ]);
247
248 // Base plan structure
249 $plan_name = $data['plan_name'];
250 $plan_description = $data['plan_description'];
251
252 $paypal_plan = [
253 'product_id' => $data['product_id'],
254 'name' => $plan_name,
255 'description' => $plan_description,
256 'status' => 'ACTIVE',
257 'payment_preferences' => [
258 'auto_bill_outstanding' => true,
259 'payment_failure_threshold' => 3
260 ]
261 ];
262
263 $normalCycle = [
264 'frequency' => [
265 'interval_unit' => Arr::get($billingPeriod, 'interval_unit'),
266 'interval_count' => Arr::get($billingPeriod, 'interval_frequency'),
267 ],
268 'tenure_type' => 'REGULAR',
269 'sequence' => 1,
270 'total_cycles' => $data['bill_times'],
271 'pricing_scheme' => [
272 'fixed_price' => [
273 'value' => $recurring_amount,
274 'currency_code' => $data['currency']
275 ]
276 ]
277 ];
278
279 $trialCycle = [];
280
281 if ($data['trial_days'] > 0) {
282 $trialCycle = [
283 'tenure_type' => 'TRIAL',
284 'frequency' => [
285 'interval_unit' => 'DAY',
286 'interval_count' => $data['trial_days']
287 ],
288 'sequence' => 1,
289 'pricing_scheme' => [
290 'fixed_price' => [
291 'value' => '0.00',
292 'currency_code' => $data['currency']
293 ]
294 ],
295 'total_cycles' => 1
296 ];
297 $normalCycle['sequence'] = 2; // If there's a trial, the regular cycle sequence should be 2
298 }
299
300 if ($initial_amount) {
301 if ($trialCycle) {
302 $trialCycle['pricing_scheme']['fixed_price']['value'] = $initial_amount;
303 } else {
304 $trialCycle = [
305 'tenure_type' => 'TRIAL',
306 'frequency' => [
307 'interval_unit' => $interval_unit,
308 'interval_count' => 1
309 ],
310 'sequence' => 1,
311 'pricing_scheme' => [
312 'fixed_price' => [
313 'value' => $initial_amount + $recurring_amount,
314 'currency_code' => $data['currency']
315 ]
316 ],
317 'total_cycles' => 1
318 ];
319
320 $normalCycle['total_cycles'] = $data['bill_times'] > 1 ? $data['bill_times'] - 1 : $data['bill_times']; // this trial cycle is without trial days, it's an adjusted paid trial cycle to take signupFee with one transaction
321 $normalCycle['sequence'] = 2; // If there's a trial, the regular cycle sequence should be 2
322 }
323 }
324
325 $cycles = [];
326
327 if ($trialCycle) {
328 $cycles[] = $trialCycle;
329 }
330
331 $cycles[] = $normalCycle;
332
333 $paypal_plan['billing_cycles'] = $cycles;
334
335 return $paypal_plan;
336 }
337 }
338