PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.1.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.1.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / utils / payment-util.php

payment-util.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.1.0, at includes/utils/payment-util.php

361 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * A class of utilities for dealing with payment.
4 */
5
6 namespace StoreEngine\Utils;
7
8 use StoreEngine\Addons\Subscription\Classes\SubscriptionCollection;
9 use StoreEngine\Classes\Order;
10 use StoreEngine\Classes\Order\OrderItemProduct;
11 use StoreEngine\Classes\PaymentTokens\PaymentToken;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 final class PaymentUtil {
18 protected static array $cc_types = [];
19
20 public static function credit_card_type_labels(): array {
21 if ( null === self::$cc_types ) {
22 self::$cc_types = apply_filters( 'storeengine/credit_card_type_labels', [
23 'mastercard' => _x( 'MasterCard', 'Name of credit card', 'storeengine' ),
24 'visa' => _x( 'Visa', 'Name of credit card', 'storeengine' ),
25 'discover' => _x( 'Discover', 'Name of credit card', 'storeengine' ),
26 'american express' => _x( 'American Express', 'Name of credit card', 'storeengine' ),
27 'cartes bancaires' => _x( 'Cartes Bancaires', 'Name of credit card', 'storeengine' ),
28 'diners' => _x( 'Diners', 'Name of credit card', 'storeengine' ),
29 'jcb' => _x( 'JCB', 'Name of credit card', 'storeengine' ),
30 ] );
31 }
32
33 return self::$cc_types;
34 }
35
36 public static function get_card_brand_abbreviations(): array {
37 $brand_abbreviations = [
38 'american_express' => _x( 'AMEX', 'Card brand abbreviation', 'storeengine' ),
39 'cartes_bancaires' => _x( 'CB', 'Card brand abbreviation', 'storeengine' ),
40 'diners_club' => _x( 'DC', 'Card brand abbreviation', 'storeengine' ),
41 'discover' => _x( 'DS', 'Card brand abbreviation', 'storeengine' ),
42 'eftpos_australia' => _x( 'EFTPOS', 'Card brand abbreviation', 'storeengine' ),
43 'interac' => _x( 'IDP', 'Card brand abbreviation', 'storeengine' ),
44 'jcb' => _x( 'JCB', 'Card brand abbreviation', 'storeengine' ),
45 'mastercard' => _x( 'MC', 'Card brand abbreviation', 'storeengine' ),
46 'union_pay' => _x( 'CUP', 'Card brand abbreviation', 'storeengine' ),
47 'visa' => _x( 'VISA', 'Card brand abbreviation', 'storeengine' ),
48 'link' => _x( 'LINK', 'Card brand abbreviation', 'storeengine' ),
49 'other' => _x( 'OTHER', 'Card brand abbreviation', 'storeengine' ),
50 'unknown' => _x( 'UNKNOWN', 'Card brand abbreviation', 'storeengine' ),
51 ];
52
53 $brand_abbreviations['amex'] = &$brand_abbreviations['american_express'];
54 $brand_abbreviations['diners'] = &$brand_abbreviations['diners_club'];
55 $brand_abbreviations['eftpos_au'] = &$brand_abbreviations['eftpos_australia'];
56 $brand_abbreviations['china_unionpay'] = &$brand_abbreviations['union_pay'];
57 $brand_abbreviations['unionpay'] = &$brand_abbreviations['union_pay'];
58
59 return apply_filters( 'storeengine/payment_method/card_brand_abbreviations', $brand_abbreviations );
60 }
61
62 public static function get_card_brand_icons(): array {
63 $card_brand_icon = [
64 'american_express' => Helper::get_assets_url( 'images/cards/amex.svg' ),
65 'cartes_bancaires' => Helper::get_assets_url( 'images/cards/cartes_bancaires.svg' ),
66 'diners_club' => Helper::get_assets_url( 'images/cards/diners.svg' ),
67 'discover' => Helper::get_assets_url( 'images/cards/discover.svg' ),
68 'eftpos_australia' => Helper::get_assets_url( 'images/cards/eftpos_au.svg' ),
69 'interac' => Helper::get_assets_url( 'images/cards/interac.svg' ),
70 'jcb' => Helper::get_assets_url( 'images/cards/jcb.svg' ),
71 'mastercard' => Helper::get_assets_url( 'images/cards/mastercard.svg' ),
72 'union_pay' => Helper::get_assets_url( 'images/cards/union_pay.svg' ),
73 'visa' => Helper::get_assets_url( 'images/cards/visa.svg' ),
74 'link' => Helper::get_assets_url( 'images/cards/link.svg' ),
75 'other' => Helper::get_assets_url( 'images/cards/credit-card.svg'),
76 ];
77
78 $card_brand_icon['amex'] = &$card_brand_icon['american_express'];
79 $card_brand_icon['diners'] = &$card_brand_icon['diners_club'];
80 $card_brand_icon['eftpos_au'] = &$card_brand_icon['eftpos_australia'];
81 $card_brand_icon['china_unionpay'] = &$card_brand_icon['union_pay'];
82 $card_brand_icon['unionpay'] = &$card_brand_icon['union_pay'];
83 $card_brand_icon['unknown'] = &$card_brand_icon['other'];
84
85 return apply_filters( 'storeengine/payment_method/card_brand_icons', $card_brand_icon );
86 }
87
88 /**
89 * Get a nice name for credit card providers.
90 *
91 * @param string $type Provider Slug/Type.
92 *
93 * @return string
94 */
95 public static function get_credit_card_type_label( string $type ): string {
96 self::credit_card_type_labels();
97 // Normalize.
98 $type = strtolower( $type );
99 $type = str_replace( '-', ' ', $type );
100 $type = str_replace( '_', ' ', $type );
101
102
103 /**
104 * Fallback to title case, uppercasing the first letter of each word.
105 */
106 return apply_filters( 'storeengine/get_credit_card_type_label', ( array_key_exists( $type, self::$cc_types ) ? self::$cc_types[ $type ] : ucwords( $type ) ) );
107 }
108
109 /**
110 * Get My Account > Payment methods columns.
111 *
112 * @return array
113 * @since 2.6.0
114 */
115 public static function get_account_payment_methods_columns(): array {
116 return apply_filters(
117 'storeengine/account_payment_methods_columns',
118 [
119 'id' => __( 'ID', 'storeengine' ),
120 'method' => __( 'Method', 'storeengine' ),
121 'gateway' => __( 'Gateway', 'storeengine' ),
122 'expires' => __( 'Expires', 'storeengine' ),
123 'actions' => __( 'Actions', 'storeengine' ),
124 ]
125 );
126 }
127
128 /**
129 * Get My Account > Payment methods types
130 *
131 * @return array
132 * @since 2.6.0
133 */
134 public static function get_account_payment_methods_types(): array {
135 return apply_filters(
136 'storeengine/payment_methods_types',
137 [
138 'cc' => __( 'Credit card', 'storeengine' ),
139 'echeck' => __( 'eCheck', 'storeengine' ),
140 ]
141 );
142 }
143
144 /**
145 * Get customer saved payment methods list.
146 *
147 * @param int $customer_id Customer ID.
148 *
149 * @return array
150 */
151 public static function get_customer_saved_methods_list( int $customer_id ): array {
152 return apply_filters( 'storeengine/saved_payment_methods_list', [], $customer_id );
153 }
154
155 /**
156 * Callback for storeengine/payment_methods_list_item filter to add token id
157 * to the generated list.
158 *
159 * @param array $list_item The current list item for the saved payment method.
160 * @param PaymentToken $token The token for the current list item.
161 *
162 * @return array The list item with the token id added.
163 */
164 public static function include_token_id_with_payment_methods( array $list_item, PaymentToken $token ): array {
165 $list_item['tokenId'] = $token->get_id();
166 // Check if brand in token data.
167 $brand = ! empty( $list_item['method']['brand'] ) ? strtolower( $list_item['method']['brand'] ) : '';
168
169 if ( ! empty( $brand ) && esc_html__( 'Credit card', 'storeengine' ) !== $brand ) {
170 $list_item['method']['brand'] = self::get_credit_card_type_label( $brand );
171 }
172
173 return $list_item;
174 }
175
176 /**
177 * Get enabled payment gateways.
178 *
179 * @return array
180 */
181 public static function get_enabled_payment_gateways(): array {
182 return array_filter(
183 Helper::get_payment_gateways()->payment_gateways(),
184 fn( $payment_gateway ) => $payment_gateway->is_enabled()
185 );
186 }
187
188 /**
189 * Returns enabled saved payment methods for a customer and the default method if there are multiple.
190 *
191 * @return array
192 */
193 public static function get_saved_payment_methods(): array {
194 if ( ! is_user_logged_in() ) {
195 return [];
196 }
197
198 add_filter( 'storeengine/payment_methods_list_item', [
199 self::class,
200 'include_token_id_with_payment_methods'
201 ], 10, 2 );
202
203 $enabled_payment_gateways = self::get_enabled_payment_gateways();
204 $_saved_payment_methods = self::get_customer_saved_methods_list( get_current_user_id() );
205 $payment_methods = [
206 'enabled' => [],
207 'default' => null,
208 ];
209
210 // Filter out payment methods that are not enabled.
211 foreach ( $_saved_payment_methods as $payment_method_group => $saved_payment_methods ) {
212 $payment_methods['enabled'][ $payment_method_group ] = array_values(
213 array_filter(
214 $saved_payment_methods,
215 function ( $saved_payment_method ) use ( $enabled_payment_gateways, &$payment_methods ) {
216 if ( true === $saved_payment_method['is_default'] && null === $payment_methods['default'] ) {
217 $payment_methods['default'] = $saved_payment_method;
218 }
219
220 return in_array( $saved_payment_method['method']['gateway'], array_keys( $enabled_payment_gateways ), true );
221 }
222 )
223 );
224 }
225
226 remove_filter( 'storeengine/payment_methods_list_item', [
227 self::class,
228 'include_token_id_with_payment_methods'
229 ], 10, 2 );
230
231 return $payment_methods;
232 }
233
234 /**
235 * Returns the default payment method for a customer.
236 *
237 * @return string
238 */
239 public static function get_default_payment_method(): string {
240 $saved_payment_methods = self::get_saved_payment_methods();
241 // A saved payment method exists, set as default.
242 if ( $saved_payment_methods && ! empty( $saved_payment_methods['default'] ) ) {
243 return $saved_payment_methods['default']['method']['gateway'] ?? '';
244 }
245
246 $order = Helper::get_recent_draft_order( 0, null, false );
247 // If payment method is already stored in session, use it.
248 if ( $order && $order->get_payment_method() ) {
249 return $order->get_payment_method();
250 }
251
252 // If no saved payment method exists, use the first enabled payment method.
253 $enabled_payment_gateways = self::get_enabled_payment_gateways();
254 $first_key = array_key_first( $enabled_payment_gateways );
255 $first_payment_method = $enabled_payment_gateways[ $first_key ];
256
257 return $first_payment_method->id ?? '';
258 }
259
260 public static function is_valid_order_pay_page(): bool {
261 if ( ! Formatting::string_to_bool( get_query_var( 'order_pay' ) ) || ! get_query_var( 'order_id' ) ) {
262 return false;
263 }
264
265 $order = Helper::get_order( absint( get_query_var( 'order_id' ) ) );
266
267 // Invalid order id / not found — page can't be valid.
268 if ( is_wp_error( $order ) || ! method_exists( $order, 'get_order_key' ) ) {
269 return false;
270 }
271
272 // Order must actually be awaiting payment.
273 if ( ! method_exists( $order, 'needs_payment' ) || ! $order->needs_payment() ) {
274 return false;
275 }
276
277 // Logged-out / cross-user access requires a matching order key in the URL
278 // (the link emailed to guests / linked from the dashboard).
279 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
280 $provided_key = isset( $_GET['key'] ) ? Formatting::clean( wp_unslash( $_GET['key'] ) ) : '';
281 if ( $provided_key && hash_equals( (string) $order->get_order_key(), (string) $provided_key ) ) {
282 return true;
283 }
284
285 // Otherwise the current user must have explicit permission.
286 return current_user_can( 'pay_for_order', $order->get_id() );
287 }
288
289 public static function has_subscription( Order $order ): bool {
290 if ( ! Helper::get_addon_active_status( 'subscription' ) ) {
291 return false;
292 }
293
294 if ( $order->get_meta( '_subscription_renewal' ) ) {
295 return true;
296 }
297
298 return SubscriptionCollection::order_has_subscription( $order->get_id() );
299 }
300
301 /**
302 * Whether a gateway is allowed to settle a specific order, given the
303 * order's own contents rather than the (possibly absent) cart.
304 *
305 * `PaymentGateway::is_available()` only sees a subscription via the cart's
306 * `has_subscription` meta, so it never blocks non-recurring gateways
307 * (COD, BACS, Check) on requests that bypass the cart — namely the
308 * `/checkout/pay-order` and `/checkout/payment-intent` REST endpoints used
309 * by the frontend-dashboard "Pay Order" / early-renewal flows. Call this
310 * as a server-side guard wherever a gateway is charged directly against an
311 * existing order id, so a stale or forged `payment_method` can't settle a
312 * subscription renewal through a gateway that can't actually process one.
313 *
314 * @param \StoreEngine\Payment\Gateways\PaymentGateway $gateway
315 * @param Order $order
316 *
317 * @return bool
318 */
319 public static function gateway_can_pay_order( \StoreEngine\Payment\Gateways\PaymentGateway $gateway, Order $order ): bool {
320 return ! ( self::has_subscription( $order ) && ! $gateway->supports( 'subscriptions' ) );
321 }
322
323 public static function has_subscription_trial( Order $order ): bool {
324 if ( ! self::has_subscription( $order ) ) {
325 return false;
326 }
327
328 if ( $order->get_meta( '_subscription_renewal' ) ) {
329 return false; // Renewal order not count for trial.
330 }
331
332 foreach ( $order->get_items() as $item ) {
333 /** @var OrderItemProduct $order_item */
334 if ( 'subscription' === $item->get_price_type() ) {
335 return $item->is_trial();
336 }
337 }
338
339 return false;
340 }
341
342 public static function is_changing_payment_method_for_subscription(): bool {
343 if ( ! Helper::get_addon_active_status( 'subscription' ) ) {
344 return false;
345 }
346
347 return ! empty( $_GET['change_payment_method'] ) && 'subscription' === Helper::get_order_type( absint( wp_unslash( $_GET['change_payment_method'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
348 }
349
350 public static function maybe_display_my_payment_method( string $payment_method_to_display, Order $order ): string {
351 if ( has_filter( 'storeengine/subscription/my_payment_method' ) ) {
352 $payment_method_to_display = apply_filters_deprecated( 'storeengine/subscription/my_payment_method', [
353 $payment_method_to_display,
354 $order
355 ], '1.8.0', 'storeengine/payment_method/display_my_payment_method' );
356 }
357
358 return (string) apply_filters( 'storeengine/payment_method/display_my_payment_method', $payment_method_to_display, $order );
359 }
360 }
361