PluginProbe
PayPlug for WooCommerce (Official) / trunk
PayPlug for WooCommerce (Official) vtrunk
3.0.0 2.18.0 1.0.17 1.0.18 1.0.19 1.0.20 1.0.21 1.0.22 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1.0 1.10.0 1.10.1 1.2.1 1.2.10 1.2.11 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 All 101 releases
payplug / src / Service / Mcp.php

Mcp.php in PayPlug for WooCommerce (Official) trunk, at src/Service/Mcp.php

393 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Payplug\PayplugWoocommerce\Service;
4
5 use libphonenumber\NumberParseException;
6 use libphonenumber\PhoneNumberFormat;
7 use libphonenumber\PhoneNumberUtil;
8 use Payplug\PayplugWoocommerce\Gateway\PayplugGateway;
9 use Payplug\PayplugWoocommerce\PayplugWoocommerceHelper;
10 use Payplug\PayplugWoocommerce\Traits\ServiceGetter;
11 use PayPlugPluginMcp\Models\Entities\PaymentInputDTO;
12
13 if (!defined('ABSPATH')) {
14 exit;
15 }
16
17 class Mcp
18 {
19 use ServiceGetter;
20
21 /**
22 * @description Create a PaymentInputDTO from given parameters
23 *
24 * @param array $params
25 *
26 * @return array
27 */
28 protected function createPaymentInputDto(array $params)
29 {
30 if (!$params || !is_array($params)) {
31 return [
32 'result' => false,
33 'code' => null,
34 'message' => 'Wrong $params given',
35 'dto' => null,
36 ];
37 }
38
39 try {
40 $attributes = $this->formatMCPAttributes($params);
41 $dto = PaymentInputDTO::create($attributes);
42
43 return [
44 'result' => true,
45 'code' => 200,
46 'message' => 'DTO created',
47 'dto' => $dto,
48 ];
49 } catch (\Throwable $e) {
50 return [
51 'result' => false,
52 'code' => (int) $e->getCode(),
53 'message' => $e->getMessage(),
54 'dto' => null,
55 ];
56 }
57 }
58
59 /**
60 * @param array $attributes
61 *
62 * @return array
63 */
64 protected function formatMCPAttributes(array $attributes)
65 {
66 $attributes['payment_method'] = 'email_link';
67
68 // Get API key using ServiceGetter trait
69 $api_key = $this->get_api()->get_bearer_token();
70 $attributes['api_bearer'] = $api_key;
71
72 // Build URLs for WooCommerce
73 $attributes['urls'] = [
74 'return' => esc_url_raw(add_query_arg('utm_nooverride', '1', wc_get_checkout_url())),
75 'cancel' => esc_url_raw(wc_get_checkout_url()),
76 'notification' => esc_url_raw(WC()->api_request_url('PayplugGateway')),
77 ];
78
79 // Set metadata
80 $attributes['metadata'] = array_merge(
81 isset($attributes['metadata']) ? $attributes['metadata'] : [],
82 [
83 'order_id' => isset($attributes['order_id']) ? $attributes['order_id'] : '',
84 'customer_id' => isset($attributes['customer']['identifier']) ? $attributes['customer']['identifier'] : 'guest',
85 'source' => 'MCP Payplug WooCommerce',
86 'domain' => esc_url_raw(home_url()),
87 ]
88 );
89 $attributes['context'] = [];
90
91 return $attributes;
92 }
93
94 /**
95 * Validates customer and cart data that don't require a WooCommerce order to exist.
96 *
97 * @param array $customer Customer information
98 * @param array $cart Cart with products
99 *
100 * @return array|null Error response array if invalid, null if valid.
101 */
102 protected function validateCreateByLinkParams(array $customer, array $cart)
103 {
104 if (!empty($customer['customer_address_email']) && !is_email($customer['customer_address_email'])) {
105 return $this->invalidParamError(400, "Invalid email address: '{$customer['customer_address_email']}'.");
106 }
107
108 foreach (!empty($cart['products']) ? $cart['products'] : [] as $product) {
109 $product_id = (int) $product['product_id'];
110 $wc_product = wc_get_product($product_id);
111
112 if (!$wc_product) {
113 return $this->invalidParamError(404, "Product with ID $product_id not found.");
114 }
115
116 $variation_id = isset($product['variation_id']) ? (int) $product['variation_id'] : 0;
117 if ($wc_product->is_type('variable') && empty($variation_id)) {
118 return $this->invalidParamError(
119 400,
120 "Product '$product_id' is a variable product but no variation_id was provided. Please select a variation."
121 );
122 }
123 }
124
125 return null;
126 }
127
128 /**
129 * Normalizes a phone number to E.164 format, using the same libphonenumber-based
130 * logic as the classic checkout flow (see PayplugAddressData::prepare_address_data()).
131 *
132 * @param mixed $phone_number
133 * @param mixed $country ISO country code (e.g. "FR"), used to interpret local formats.
134 *
135 * @return string|null The normalized E.164 phone number, or null if it can't be validated.
136 */
137 protected function normalizePhoneNumber($phone_number, $country)
138 {
139 if (!is_string($phone_number) || !is_string($country) || '' === $country) {
140 return null;
141 }
142
143 try {
144 $phone_number_util = PhoneNumberUtil::getInstance();
145 $parsed_number = $phone_number_util->parse($phone_number, $country);
146
147 if (!$phone_number_util->isValidNumber($parsed_number)) {
148 return null;
149 }
150
151 return $phone_number_util->format($parsed_number, PhoneNumberFormat::E164);
152 } catch (NumberParseException $e) {
153 return null;
154 }
155 }
156
157 /**
158 * @param int $code
159 * @param string $message
160 *
161 * @return array
162 */
163 protected function invalidParamError($code, $message)
164 {
165 return [
166 'result' => false,
167 'code' => $code,
168 'message' => $message,
169 'order_id' => null,
170 'resource_id' => null,
171 'payment_url' => null,
172 ];
173 }
174
175 /**
176 * Creates a payment link for a customer.
177 *
178 * @param array $customer Customer information
179 * @param array $cart Cart with products
180 *
181 * @return array Result with payment URL or error
182 */
183 public function createByLink(array $customer, array $cart)
184 {
185 if (!empty($customer['customer_address_mobile_phone_number'])) {
186 $normalized_phone = $this->normalizePhoneNumber(
187 $customer['customer_address_mobile_phone_number'],
188 isset($customer['customer_address_country']) ? $customer['customer_address_country'] : ''
189 );
190
191 if (null === $normalized_phone) {
192 return $this->invalidParamError(
193 400,
194 "Invalid phone number '{$customer['customer_address_mobile_phone_number']}' for country '{$customer['customer_address_country']}'."
195 );
196 }
197
198 $customer['customer_address_mobile_phone_number'] = $normalized_phone;
199 }
200
201 $validation_error = $this->validateCreateByLinkParams($customer, $cart);
202 if (null !== $validation_error) {
203 return $validation_error;
204 }
205
206 // Create a new WooCommerce order
207 $order = wc_create_order([
208 'customer_id' => isset($customer['customer_id']) ? (int) $customer['customer_id'] : 0,
209 'status' => 'pending',
210 ]);
211
212 if (is_wp_error($order)) {
213 return [
214 'result' => false,
215 'code' => 500,
216 'message' => 'Failed to create order: ' . $order->get_error_message(),
217 'order_id' => null,
218 'resource_id' => null,
219 'payment_url' => null,
220 ];
221 }
222
223 // Add products to the order (already validated in validateCreateByLinkParams)
224 if (!empty($cart['products'])) {
225 foreach ($cart['products'] as $product) {
226 $qty = (int) $product['qty'];
227 $variation_id = isset($product['variation_id']) ? (int) $product['variation_id'] : 0;
228 $variation = isset($product['variation']) ? $product['variation'] : [];
229
230 $order->add_product(wc_get_product((int) $product['product_id']), $qty, [
231 'variation_id' => $variation_id,
232 'variation' => $variation,
233 ]);
234 }
235 }
236
237 // Set billing address
238 $order->set_billing_first_name(isset($customer['customer_address_first_name']) ? wc_clean($customer['customer_address_first_name']) : '');
239 $order->set_billing_last_name(isset($customer['customer_address_last_name']) ? wc_clean($customer['customer_address_last_name']) : '');
240 $order->set_billing_email(isset($customer['customer_address_email']) ? sanitize_email($customer['customer_address_email']) : '');
241 $order->set_billing_phone(isset($customer['customer_address_mobile_phone_number']) ? wc_clean($customer['customer_address_mobile_phone_number']) : '');
242 $order->set_billing_address_1(isset($customer['customer_address_address1']) ? wc_clean($customer['customer_address_address1']) : '');
243 $order->set_billing_address_2(isset($customer['customer_address_address2']) ? wc_clean($customer['customer_address_address2']) : '');
244 $order->set_billing_city(isset($customer['customer_address_city']) ? wc_clean($customer['customer_address_city']) : '');
245 $order->set_billing_postcode(isset($customer['customer_address_postcode']) ? wc_clean($customer['customer_address_postcode']) : '');
246 $order->set_billing_country(isset($customer['customer_address_country']) ? wc_clean($customer['customer_address_country']) : '');
247
248 // Set shipping address (same as billing)
249 $order->set_shipping_first_name(isset($customer['customer_address_first_name']) ? wc_clean($customer['customer_address_first_name']) : '');
250 $order->set_shipping_last_name(isset($customer['customer_address_last_name']) ? wc_clean($customer['customer_address_last_name']) : '');
251 $order->set_shipping_address_1(isset($customer['customer_address_address1']) ? wc_clean($customer['customer_address_address1']) : '');
252 $order->set_shipping_address_2(isset($customer['customer_address_address2']) ? wc_clean($customer['customer_address_address2']) : '');
253 $order->set_shipping_city(isset($customer['customer_address_city']) ? wc_clean($customer['customer_address_city']) : '');
254 $order->set_shipping_postcode(isset($customer['customer_address_postcode']) ? wc_clean($customer['customer_address_postcode']) : '');
255 $order->set_shipping_country(isset($customer['customer_address_country']) ? wc_clean($customer['customer_address_country']) : '');
256
257 // Set payment method to PayPlug so IPN works correctly
258 $order->set_payment_method('payplug');
259 $order->set_payment_method_title(__('PayPlug', 'payplug'));
260
261 // Calculate totals
262 $order->calculate_totals();
263 $order->save();
264
265 $order_total = $order->get_total();
266 $currency = $order->get_currency();
267
268 // Prepare DTO parameters
269 $dto_params = [
270 'order_id' => $order->get_id(),
271 'amount' => PayplugWoocommerceHelper::get_payplug_amount($order_total),
272 'currency_iso_code' => $currency,
273 'customer' => [
274 'identifier' => $customer['customer_id'],
275 'billing' => [
276 'title' => isset($customer['customer_address_title']) ? $customer['customer_address_title'] : '',
277 'first_name' => $customer['customer_address_first_name'],
278 'last_name' => $customer['customer_address_last_name'],
279 'mobile_phone_number' => isset($customer['customer_address_mobile_phone_number']) ? $customer['customer_address_mobile_phone_number'] : '',
280 'email' => $customer['customer_address_email'],
281 'address1' => $customer['customer_address_address1'],
282 'address2' => isset($customer['customer_address_address2']) ? $customer['customer_address_address2'] : '',
283 'postcode' => $customer['customer_address_postcode'],
284 'city' => $customer['customer_address_city'],
285 'country' => $customer['customer_address_country'],
286 'language' => $customer['customer_address_language'],
287 ],
288 'shipping' => [
289 'title' => isset($customer['customer_address_title']) ? $customer['customer_address_title'] : '',
290 'first_name' => $customer['customer_address_first_name'],
291 'last_name' => $customer['customer_address_last_name'],
292 'mobile_phone_number' => isset($customer['customer_address_mobile_phone_number']) ? $customer['customer_address_mobile_phone_number'] : '',
293 'email' => $customer['customer_address_email'],
294 'address1' => $customer['customer_address_address1'],
295 'address2' => isset($customer['customer_address_address2']) ? $customer['customer_address_address2'] : '',
296 'postcode' => $customer['customer_address_postcode'],
297 'city' => $customer['customer_address_city'],
298 'country' => $customer['customer_address_country'],
299 'language' => $customer['customer_address_language'],
300 ],
301 ],
302 ];
303
304 $dtoResult = $this->createPaymentInputDto($dto_params);
305
306 if (!$dtoResult['result'] || !$dtoResult['dto']) {
307 return [
308 'result' => false,
309 'code' => $dtoResult['code'],
310 'message' => $dtoResult['message'],
311 'order_id' => null,
312 'resource_id' => null,
313 'payment_url' => null,
314 ];
315 }
316
317 /** @var PaymentInputDTO $dto */
318 $dto = $dtoResult['dto'];
319
320 // Check if API key is configured
321 if (empty($dto->getApiBearer())) {
322 return [
323 'result' => false,
324 'code' => 401,
325 'message' => 'PayPlug API key is not configured. Please configure the PayPlug plugin settings in WooCommerce.',
326 'order_id' => null,
327 'resource_id' => null,
328 'payment_url' => null,
329 ];
330 }
331
332 try {
333 $payment_action = new \PayPlugPluginMcp\Actions\PaymentAction();
334 $payment_object = $payment_action->createAction($dto);
335
336 $resource = $payment_object->getResource();
337
338 if (!$payment_object->getResult() || !$resource || empty($resource->id)) {
339 $order->update_status('failed', __('Payplug payment link creation failed.', 'payplug'));
340
341 return [
342 'result' => false,
343 'code' => $payment_object->getCode() ? (int) $payment_object->getCode() : 500,
344 'message' => $payment_object->getMessage() ?: __('Payment processing failed. Please retry.', 'payplug'),
345 'order_id' => $order->get_id(),
346 'resource_id' => null,
347 'payment_url' => null,
348 ];
349 }
350
351 // Save transaction id for the order
352 $order->set_transaction_id($resource->id);
353 $order->update_meta_data('_payplug_payment_id', $resource->id);
354
355 $metadata = \Payplug\PayplugWoocommerce\PayplugWoocommerceHelper::extract_transaction_metadata($resource);
356 \Payplug\PayplugWoocommerce\PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
357
358 $order->add_order_note(sprintf(
359 __('Payplug payment link created. Payment ID: %s', 'payplug'),
360 $resource->id
361 ));
362 $order->save();
363
364 \do_action('payplug_gateway_payment_created', $order->get_id(), $resource);
365
366 return [
367 'result' => true,
368 'code' => 200,
369 'message' => 'Order and payment created successfully.',
370 'order_id' => $order->get_id(),
371 'resource_id' => $resource->id,
372 'payment_url' => !empty($resource->hosted_payment->payment_url) ? $resource->hosted_payment->payment_url : null,
373 ];
374 } catch (\Throwable $e) {
375 PayplugGateway::log(
376 sprintf('MCP error while processing order #%s : %s', $order->get_id(), $e->getMessage()),
377 'error'
378 );
379
380 $order->update_status('failed', __('Payplug payment link creation failed.', 'payplug'));
381
382 return [
383 'result' => false,
384 'code' => 500,
385 'message' => __('Payment processing failed. Please retry.', 'payplug'),
386 'order_id' => $order->get_id(),
387 'resource_id' => null,
388 'payment_url' => null,
389 ];
390 }
391 }
392 }
393