PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.23
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.23
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 / SquareGateway / Square.php

Square.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.23, at app/Modules/PaymentMethods/SquareGateway/Square.php

401 lines 13.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\SquareGateway;
4
5 use FluentCart\App\App;
6 use FluentCart\App\Modules\PaymentMethods\Core\AbstractPaymentGateway;
7 use FluentCart\App\Services\Payments\PaymentInstance;
8 use FluentCart\App\Vite;
9
10 class Square extends AbstractPaymentGateway
11 {
12 public array $supportedFeatures = [
13 'payment',
14 'refund',
15 'webhook'
16 ];
17
18 public function __construct()
19 {
20 parent::__construct(new SquareSettingsBase());
21 }
22
23 public function meta(): array
24 {
25 return [
26 'title' => __('Square', 'fluent-cart'),
27 'route' => 'square',
28 'slug' => 'square',
29 'description' => __('Pay securely with Square - Credit and Debit Cards', 'fluent-cart'),
30 'logo' => Vite::getAssetUrl("images/payment-methods/square-logo.svg"),
31 'icon' => Vite::getAssetUrl("images/payment-methods/square-logo.svg"),
32 'brand_color' => '#3e4348',
33 'status' => $this->settings->get('is_active') === 'yes',
34 'upcoming' => true,
35 ];
36 }
37
38 public function boot()
39 {
40 // init ipn related actions/class can be initiated here
41
42 add_filter('fluent_cart/payment_methods/square_settings', [$this, 'getSettings'], 10, 2);
43 }
44
45 public function makePaymentFromPaymentInstance(PaymentInstance $paymentInstance)
46 {
47 // todo: implement in future
48 die();
49 }
50
51 public function refund($refundInfo, $order, $transaction)
52 {
53 // todo: implement in future
54 die();
55 }
56
57 public function handleIPN()
58 {
59 $payload = json_decode(file_get_contents('php://input'), true); // will get from request after verification
60 $eventType = $payload['type'] ?? '';
61
62 switch ($eventType) {
63 case 'payment.updated':
64 $this->handlePaymentUpdated($payload);
65 break;
66 case 'refund.updated':
67 $this->handleRefundUpdated($payload);
68 break;
69 }
70
71 http_response_code(200);
72 exit('OK');
73 }
74
75 public function getOrderInfo(array $data)
76 {
77 $items = $this->getCheckoutItems();
78
79 $subTotal = 0;
80 foreach ($items as $item) {
81 $subTotal += intval($item['quantity'] * $item['unit_price']);
82 }
83
84 $paymentArgs = [
85 'application_id' => $this->settings->getApplicationId(),
86 'location_id' => $this->settings->getLocationId(),
87 'amount' => $subTotal,
88 'currency' => $this->storeSettings->get('currency'),
89 ];
90
91 wp_send_json([
92 'status' => 'success',
93 'payment_args' => $paymentArgs,
94 'has_subscription' => false
95 ], 200);
96 }
97
98 public function fields(): array
99 {
100 $webhook_url = site_url() . '?fct_payment_listener=1&method=square';
101 $webhook_instructions = sprintf(
102 '<div>
103 <p><b>%1$s</b><code class="copyable-content">%2$s</code></p>
104 <p>%3$s</p>
105 <br>
106 <h4>%4$s</h4>
107 <br>
108 <p>%5$s</p>
109 <p>%6$s <a href="https://developer.squareup.com/apps" target="_blank">%7$s</a></p>
110 <p>%8$s <code class="copyable-content">%2$s</code></p>
111 <p>%9$s</p>
112 </div>',
113 __('Webhook URL: ', 'fluent-cart'), // %1$s
114 $webhook_url, // %2$s (reused)
115 __('You should configure your Square webhooks to get all updates of your payments remotely.', 'fluent-cart'), // %3$s
116 __('How to configure?', 'fluent-cart'), // %4$s
117 __('In your Square Dashboard:', 'fluent-cart'), // %5$s
118 __('Go to Developer > Webhooks >', 'fluent-cart'), // %6$s
119 __('Add webhook', 'fluent-cart'), // %7$s
120 __('Enter The Webhook URL: ', 'fluent-cart'), // %8$s
121 __('Select payment events', 'fluent-cart') // %9$s
122 );
123
124 return array(
125 // 'notice' => [
126 // 'value' => $this->getStoreModeNotice(),
127 // 'label' => __('Store Mode notice', 'fluent-cart'),
128 // 'type' => 'notice'
129 // ],
130 'upcoming' => [
131 'value' => $this->isUpcoming(),
132 'label' => __('Payment method is upcoming!', 'fluent-cart'),
133 'type' => 'upcoming'
134 ],
135 'payment_mode' => [
136 'type' => 'tabs',
137 'schema' => [
138 [
139 'type' => 'tab',
140 'label' => __('Test credentials', 'fluent-cart'),
141 'value' => 'test',
142 'schema' => [
143 'test_access_token' => array(
144 'value' => '',
145 'label' => __('Test Access Token', 'fluent-cart'),
146 'type' => 'password',
147 'placeholder' => __('EAAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'fluent-cart'),
148 'dependency' => [
149 'depends_on' => 'payment_mode',
150 'operator' => '=',
151 'value' => 'test'
152 ]
153 ),
154 'test_application_id' => array(
155 'value' => '',
156 'label' => __('Test Application ID', 'fluent-cart'),
157 'type' => 'text',
158 'placeholder' => __('sandbox-sq0idb-xxxxxxxxxxxxxxxxxxxxxxxx', 'fluent-cart'),
159 'dependency' => [
160 'depends_on' => 'payment_mode',
161 'operator' => '=',
162 'value' => 'test'
163 ]
164 ),
165 ],
166 ],
167 [
168 'type' => 'tab',
169 'label' => __('Live credentials', 'fluent-cart'),
170 'value' => 'live',
171 'schema' => [
172 'live_access_token' => array(
173 'value' => '',
174 'label' => __('Live Access Token', 'fluent-cart'),
175 'type' => 'password',
176 'placeholder' => __('EAAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'fluent-cart'),
177 'dependency' => [
178 'depends_on' => 'payment_mode',
179 'operator' => '=',
180 'value' => 'live'
181 ]
182 ),
183 'live_application_id' => array(
184 'value' => '',
185 'label' => __('Live Application ID', 'fluent-cart'),
186 'type' => 'text',
187 'placeholder' => __('sq0idb-xxxxxxxxxxxxxxxxxxxxxxxx', 'fluent-cart'),
188 'dependency' => [
189 'depends_on' => 'payment_mode',
190 'operator' => '=',
191 'value' => 'live'
192 ]
193 ),
194 ]
195 ]
196 ]
197 ],
198 'webhook_desc' => array(
199 'value' => $webhook_instructions,
200 'label' => __('Webhook URL', 'fluent-cart'),
201 'type' => 'html_attr'
202 ),
203 );
204 }
205
206 public static function validateSettings($data): array
207 {
208 $applicationId = $data['application_id'] ?? '';
209 $accessToken = $data['access_token'] ?? '';
210 $locationId = $data['location_id'] ?? '';
211
212 if (empty($applicationId) || empty($accessToken) || empty($locationId)) {
213 return [
214 'status' => 'failed',
215 'message' => __('Application ID, Access Token, and Location ID are required', 'fluent-cart')
216 ];
217 }
218
219 try {
220 $testResponse = static::testApiConnection($accessToken, $locationId, $data['payment_mode'] ?? 'sandbox');
221
222 return [
223 'status' => 'success',
224 'message' => __('Square settings validated successfully', 'fluent-cart')
225 ];
226 } catch (\Exception $e) {
227 return [
228 'status' => 'failed',
229 'message' => $e->getMessage()
230 ];
231 }
232 }
233
234 public function getEnqueueScriptSrc($hasSubscription = 'no'): array
235 {
236 $mode = $this->settings->getMode();
237 $squareJsUrl = $mode === 'production'
238 ? 'https://js.squareup.com/v2/paymentform'
239 : 'https://js.squareupsandbox.com/v2/paymentform';
240
241 return [
242 [
243 'handle' => 'square-payment-form',
244 'src' => $squareJsUrl,
245 ],
246 [
247 'handle' => 'fluent-cart-square-checkout',
248 'src' => Vite::getEnqueuePath('public/payment-methods/square-checkout.js'),
249 'deps' => ['square-payment-form']
250 ]
251 ];
252 }
253
254 public function getEnqueueStyleSrc(): array
255 {
256 return [
257 [
258 'handle' => 'fluent-cart-square-styles',
259 'src' => Vite::getEnqueuePath('public/payment-methods/square.css'),
260 ]
261 ];
262 }
263
264 private function createSquarePayment($data)
265 {
266 $accessToken = $this->settings->getAccessToken();
267 $baseUrl = $this->getApiBaseUrl();
268
269 $response = wp_remote_post("{$baseUrl}/v2/payments", [
270 'headers' => [
271 'Authorization' => 'Bearer ' . $accessToken,
272 'Content-Type' => 'application/json',
273 'Square-Version' => '2023-10-18'
274 ],
275 'body' => json_encode($data),
276 'timeout' => 30
277 ]);
278
279 if (is_wp_error($response)) {
280 throw new \Exception(esc_html($response->get_error_message()));
281 }
282
283 $body = wp_remote_retrieve_body($response);
284 return json_decode($body, true);
285 }
286
287 private function processSquareRefund($refundData)
288 {
289 $accessToken = $this->settings->getAccessToken();
290 $baseUrl = $this->getApiBaseUrl();
291
292 $response = wp_remote_post("{$baseUrl}/v2/refunds", [
293 'headers' => [
294 'Authorization' => 'Bearer ' . $accessToken,
295 'Content-Type' => 'application/json',
296 'Square-Version' => '2023-10-18'
297 ],
298 'body' => json_encode($refundData),
299 'timeout' => 30
300 ]);
301
302 if (is_wp_error($response)) {
303 throw new \Exception(esc_html($response->get_error_message()));
304 }
305
306 $body = wp_remote_retrieve_body($response);
307 return json_decode($body, true);
308 }
309
310 private static function testApiConnection($accessToken, $locationId, $mode)
311 {
312 $baseUrl = $mode === 'production'
313 ? 'https://connect.squareup.com'
314 : 'https://connect.squareupsandbox.com';
315
316 $response = wp_remote_get("{$baseUrl}/v2/locations/{$locationId}", [
317 'headers' => [
318 'Authorization' => 'Bearer ' . $accessToken,
319 'Square-Version' => '2023-10-18'
320 ],
321 'timeout' => 30
322 ]);
323
324 if (is_wp_error($response)) {
325 throw new \Exception(esc_html($response->get_error_message()));
326 }
327
328 $body = wp_remote_retrieve_body($response);
329 $data = json_decode($body, true);
330
331 if (wp_remote_retrieve_response_code($response) !== 200 || isset($data['errors'])) {
332 throw new \Exception(esc_html__('Invalid Square credentials or location ID', 'fluent-cart'));
333 }
334
335 return true;
336 }
337
338 private function getApiBaseUrl()
339 {
340 return $this->settings->getMode() === 'production'
341 ? 'https://connect.squareup.com'
342 : 'https://connect.squareupsandbox.com';
343 }
344
345 private function getWebhookUrl()
346 {
347 return site_url('?fluent_cart_payment_api_notify=square');
348 }
349
350 public function webHookPaymentMethodName()
351 {
352 return $this->getMeta('route');
353 }
354
355 private function generateIdempotencyKey($order, $type = 'payment')
356 {
357 return md5($order->id . '_' . $type . '_' . time());
358 }
359
360 private function getSourceToken()
361 {
362 // This would be provided by the frontend Square payment form
363 $sourceId = App::request()->get('source_id') ?? '';
364 return sanitize_text_field($sourceId);
365 }
366
367 private function handlePaymentUpdated($payload)
368 {
369 // Handle payment status updates
370 $payment = $payload['data']['object']['payment'] ?? [];
371 $orderId = $payment['order_id'] ?? '';
372
373 if ($orderId) {
374 $order = fluent_cart_get_order_by_uuid($orderId);
375 if ($order && $payment['status'] === 'COMPLETED') {
376 $this->handlePaymentSuccess($payment, $order);
377 }
378 }
379 }
380
381 private function handleRefundUpdated($payload)
382 {
383 // Handle refund status updates
384 $refund = $payload['data']['object']['refund'] ?? [];
385 // Process refund update logic here
386 }
387
388 private function handlePaymentSuccess($payment, $order)
389 {
390 $order->payment_status = 'paid';
391 $order->status = 'processing';
392 $order->vendor_charge_id = $payment['id'];
393 $order->save();
394
395 do_action('fluent_cart/payment_success', [
396 'order' => $order,
397 'payment_intent' => $payment
398 ]);
399 }
400 }
401