PluginProbe
PayPlug for WooCommerce (Official) / 3.1.0
PayPlug for WooCommerce (Official) v3.1.0
3.1.0 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 All 102 releases
payplug / src / Gateway / PPRO / Scalapay.php

Scalapay.php in PayPlug for WooCommerce (Official) 3.1.0, at src/Gateway/PPRO/Scalapay.php

293 lines 13.1 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\Gateway\PPRO;
4
5 use libphonenumber\PhoneNumberType;
6 use libphonenumber\PhoneNumberUtil;
7 use Payplug\PayplugWoocommerce\Controller\PayplugGenericGateway;
8 use Payplug\PayplugWoocommerce\Gateway\PayplugAddressData;
9 use Payplug\PayplugWoocommerce\Gateway\PayplugGateway;
10 use Payplug\PayplugWoocommerce\PayplugWoocommerceHelper;
11
12 class Scalapay extends PayplugGenericGateway
13 {
14 protected $allowed_country_codes = [];
15 protected $enable_refund = true;
16 public const ENABLE_ON_TEST_MODE = false;
17
18 /**
19 * Last-resort fallback bounds, in cents. Used only when neither the account's live API
20 * amounts nor the stored scalapay.default_amounts are available - e.g. an options array
21 * that predates that key, since Configuration loads the raw stored options without
22 * merging the schema defaults. Mirrors Configuration::$expected_fields'
23 * payment_methods.configuration.scalapay.default_amounts.
24 */
25 public const DEFAULT_MIN_AMOUNT = 500;
26 public const DEFAULT_MAX_AMOUNT = 400000;
27
28 public function __construct()
29 {
30 parent::__construct();
31
32 //since we're calling the parent construct we need to redefine the payment properties
33 //once we detach the cc from default payment method, this will be no longer needed
34 $this->id = 'scalapay';
35 $this->method_title = __('pay_with_scalapay', 'payplug');
36 $this->title = __('pay_with_scalapay', 'payplug');
37 $this->method_description = '';
38 $this->description = '';
39 $this->image = 'scalapay.svg';
40
41 //WOOCO FIELDS
42 $this->has_fields = false;
43 $this->enabled = 'yes';
44
45 if (!$this->checkGateway()) {
46 $this->enabled = 'no';
47 }
48
49 add_action('woocommerce_order_item_add_action_buttons', [$this, 'refund_not_available']);
50 add_action('woocommerce_after_checkout_validation', [$this, 'validate_checkout'], 10);
51 }
52
53 /**
54 * Server-side guard mirroring check_gateway(): a stale/bypassed client could still submit
55 * an order outside the merchant's configured (or account-authorized) amount range.
56 *
57 * @throws \Exception
58 */
59 public function validate_checkout(): void
60 {
61 $posted_data = $this->get_post_data();
62
63 if (($posted_data['payment_method'] ?? '') !== $this->id) {
64 return;
65 }
66
67 [$min_cents, $max_cents] = self::effective_bounds(
68 $this->settings['payment_methods']['configuration']['scalapay'] ?? [],
69 $this->settings['payment_methods']['permissions']['scalapay']['amounts'] ?? '{}'
70 );
71
72 // Compared in euros rather than multiplying the total by 100: a float cents value
73 // such as 19.99 * 100 => 1998.9999999999998 can fall the wrong side of an exact
74 // boundary, whereas dividing the (integer) bound by 100 yields the same double as
75 // the parsed total. Same direction as PayplugGatewayOney3x's amount checks.
76 $order_total = (float) $this->get_order_total();
77 if ($order_total < $min_cents / 100 || $order_total > $max_cents / 100) {
78 throw new \Exception(sprintf(__('The total amount of your order should be between %s€ and %s€ to pay with Scalapay.', 'payplug'), $min_cents / 100, $max_cents / 100));
79 }
80 }
81
82 /**
83 * Remove Scalapay from the available gateways if the order/cart amount is outside the
84 * merchant's configured bounds (or the account's authorized range, if unconfigured).
85 *
86 * @param array $gateways
87 *
88 * @return array
89 */
90 public function check_gateway($gateways)
91 {
92 // WC()->cart is only populated on frontend requests (WooCommerce::is_request('frontend')
93 // excludes wp-admin and REST requests) but this filter also fires outside that context -
94 // e.g. the WC Settings > Payments screen calling get_available_payment_gateways() via
95 // PaymentsController::store_has_enabled_gateways(). get_order_total() dereferences
96 // WC()->cart->total unconditionally, which would throw a PHP warning on a null cart.
97 if (isset($gateways[$this->id]) && $gateways[$this->id]->id == $this->id && null !== WC()->cart) {
98 [$min_cents, $max_cents] = self::effective_bounds(
99 $this->settings['payment_methods']['configuration']['scalapay'] ?? [],
100 $this->settings['payment_methods']['permissions']['scalapay']['amounts'] ?? '{}'
101 );
102
103 // Euro comparison, for the float-precision reason spelled out in validate_checkout().
104 $order_total = (float) $this->get_order_total();
105 if ($order_total < $min_cents / 100 || $order_total > $max_cents / 100) {
106 unset($gateways[$this->id]);
107 }
108 }
109
110 return parent::check_gateway($gateways);
111 }
112
113 /**
114 * The account's PayPlug-authorized range, in cents: the live API amounts when available,
115 * otherwise the stored default_amounts fallback. This is the ceiling the merchant may
116 * only narrow, never widen - shared with the BO display (PaymentMethods.php) and the
117 * save-time validator's bounds (Ajax.php) so the three can't silently drift apart.
118 *
119 * @param array $config payment_methods.configuration.scalapay (default_amounts)
120 * @param string $api_amounts payment_methods.permissions.scalapay.amounts, JSON-encoded
121 * {"min":{"EUR":n},"max":{"EUR":n}}
122 *
123 * @return array{0: int, 1: int} [min_cents, max_cents]
124 */
125 public static function authorized_bounds($config, $api_amounts)
126 {
127 $authorized = json_decode($api_amounts ?? '{}', true);
128 $min = is_array($authorized) ? ($authorized['min']['EUR'] ?? null) : null;
129 $max = is_array($authorized) ? ($authorized['max']['EUR'] ?? null) : null;
130
131 if (null === $min || null === $max) {
132 $fallback = json_decode($config['default_amounts'] ?? '', true);
133 $fallback = is_array($fallback) ? $fallback : [];
134 $min = $min ?? ($fallback['min'] ?? self::DEFAULT_MIN_AMOUNT);
135 $max = $max ?? ($fallback['max'] ?? self::DEFAULT_MAX_AMOUNT);
136 }
137
138 return [(int) $min, (int) $max];
139 }
140
141 /**
142 * The merchant's configured min/max (when set) narrowed against the account's live
143 * PayPlug-authorized range - shared with the BO settings display (PaymentMethods.php)
144 * so both stay in sync by construction.
145 *
146 * @param array $config payment_methods.configuration.scalapay (custom_amounts/default_amounts)
147 * @param string $api_amounts payment_methods.permissions.scalapay.amounts, JSON-encoded
148 * {"min":{"EUR":n},"max":{"EUR":n}}
149 *
150 * @return array{0: int, 1: int} [min_cents, max_cents]
151 */
152 public static function effective_bounds($config, $api_amounts)
153 {
154 $custom_amounts = json_decode($config['custom_amounts'] ?? '{}', true);
155 $custom_amounts = is_array($custom_amounts) ? $custom_amounts : [];
156
157 [$authorized_min, $authorized_max] = self::authorized_bounds($config, $api_amounts);
158
159 // !empty() rather than isset(): a stored 0 is not a meaningful Scalapay threshold,
160 // so it falls back to the account bound instead of being read as an override.
161 $min = !empty($custom_amounts['min']) ? (int) $custom_amounts['min'] : $authorized_min;
162 $max = !empty($custom_amounts['max']) ? (int) $custom_amounts['max'] : $authorized_max;
163
164 return [$min, $max];
165 }
166
167 /**
168 * @param \WC_Order $order
169 * @param int $amount
170 * @param int $customer_id
171 *
172 * @throws \Exception
173 *
174 * @return array
175 */
176 public function process_standard_payment($order, $amount, $customer_id)
177 {
178 $order_id = PayplugWoocommerceHelper::is_pre_30() ? $order->id : $order->get_id();
179 try {
180 $country = PayplugWoocommerceHelper::is_pre_30() ? $order->billing_country : $order->get_billing_country();
181 $phone = PayplugWoocommerceHelper::is_pre_30() ? $order->billing_phone : $order->get_billing_phone();
182 $billing_email = PayplugWoocommerceHelper::is_pre_30() ? $order->billing_email : $order->get_billing_email();
183 $phone_number_util = PhoneNumberUtil::getInstance();
184 $phone_number = $phone_number_util->parse($phone, $country);
185 if (PhoneNumberType::MOBILE !== $phone_number_util->getNumberType($phone_number)) {
186 throw new \Exception(__('Mobile phone number fullfilled is invalid. Please retry.', 'payplug'));
187 }
188
189 if (!filter_var($billing_email, FILTER_VALIDATE_EMAIL) || strpos($billing_email, '+') !== false) {
190 throw new \Exception(__('Your email address is too long and the + character is not valid, please change it to another address (max 100 characters).', 'payplug'));
191 }
192
193 $address_data = PayplugAddressData::from_order($order);
194
195 $return_url = esc_url_raw($order->get_checkout_order_received_url());
196
197 if (!(substr($return_url, 0, 4) === 'http')) {
198 $return_url = get_site_url() . $return_url;
199 }
200
201 $cart_items = [];
202 $items = $order->get_items();
203 foreach ($items as $item) {
204 $data = $item->get_data();
205 $total = floatval(round($data['total'], 2)) * 100;
206 $cart_items[] = [
207 'delivery_label' => 'storepickup',
208 'delivery_type' => 'storepickup',
209 'brand' => 'Woocommerce',
210 'merchant_item_id' => 'cart-' . $data['id'] . '-' . $data['product_id'],
211 'name' => $data['name'],
212 'expected_delivery_date' => date('Y-m-d', strtotime('+1 week')),
213 'total_amount' => (int) $total,
214 'price' => round($total / $data['quantity']),
215 'quantity' => $data['quantity'],
216 ];
217 }
218
219 $payment_data = [
220 'amount' => $amount,
221 'currency' => get_woocommerce_currency(),
222 'payment_method' => $this->id,
223 'billing' => $address_data->get_billing(),
224 'shipping' => $address_data->get_shipping(),
225 'payment_context' => [
226 'cart' => $cart_items,
227 ],
228 'notification_url' => esc_url_raw(WC()->api_request_url('PayplugGateway')),
229 'hosted_payment' => [
230 'return_url' => $return_url,
231 'cancel_url' => esc_url_raw($order->get_cancel_order_url_raw()),
232 ],
233 'metadata' => [
234 'order_id' => $order_id,
235 'customer_id' => ((int) $customer_id > 0) ? $customer_id : 'guest',
236 'domain' => $this->limit_length(esc_url_raw(home_url()), 500),
237 ],
238 ];
239
240 if (PayplugWoocommerceHelper::is_checkout_block() && is_checkout()) {
241 $payment_data['metadata']['woocommerce_block'] = 'CHECKOUT';
242 } elseif (PayplugWoocommerceHelper::is_cart_block() && is_cart()) {
243 $payment_data['metadata']['woocommerce_block'] = 'CART';
244 }
245
246 /**
247 * Filter the payment data before it's used
248 *
249 * @param array $payment_data
250 * @param int $order_id
251 * @param array $customer_details
252 * @param PayplugAddressData $address_data
253 */
254 $payment_data = apply_filters('payplug_gateway_payment_data', $payment_data, $order_id, [], $address_data);
255 $payment = $this->payplug_api->payment_create($payment_data);
256
257 // Save transaction id for the order
258 PayplugWoocommerceHelper::is_pre_30()
259 ? update_post_meta($order_id, '_transaction_id', $payment->id)
260 : $order->set_transaction_id($payment->id);
261
262 if (is_callable([$order, 'save'])) {
263 $order->save();
264 }
265
266 /**
267 * Fires once a payment has been created.
268 *
269 * @param int $order_id Order ID
270 * @param PaymentResource $payment Payment resource
271 */
272 \do_action('payplug_gateway_payment_created', $order_id, $payment);
273
274 $metadata = PayplugWoocommerceHelper::extract_transaction_metadata($payment);
275 PayplugWoocommerceHelper::save_transaction_metadata($order, $metadata);
276
277 PayplugGateway::log(sprintf('Payment creation complete for order #%s', $order_id));
278
279 return [
280 'result' => 'success',
281 'redirect' => $payment->hosted_payment->payment_url,
282 'cancel' => $payment->hosted_payment->cancel_url,
283 ];
284 } catch (\HttpException $e) {
285 PayplugGateway::log(sprintf('Error while processing order #%s : %s', $order_id, wc_print_r($e->getErrorObject(), true)), 'error');
286 throw new \Exception(__($e->getMessage(), 'payplug'));
287 } catch (\Exception $e) {
288 PayplugGateway::log(sprintf('Error while processing order #%s : %s', $order_id, $e->getMessage()), 'error');
289 throw new \Exception(__($e->getMessage(), 'payplug'));
290 }
291 }
292 }
293