| 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 |
public static function has_subscription_trial( Order $order ): bool { |
| 302 |
if ( ! self::has_subscription( $order ) ) { |
| 303 |
return false; |
| 304 |
} |
| 305 |
|
| 306 |
if ( $order->get_meta( '_subscription_renewal' ) ) { |
| 307 |
return false; // Renewal order not count for trial. |
| 308 |
} |
| 309 |
|
| 310 |
foreach ( $order->get_items() as $item ) { |
| 311 |
/** @var OrderItemProduct $order_item */ |
| 312 |
if ( 'subscription' === $item->get_price_type() ) { |
| 313 |
return $item->is_trial(); |
| 314 |
} |
| 315 |
} |
| 316 |
|
| 317 |
return false; |
| 318 |
} |
| 319 |
|
| 320 |
public static function is_changing_payment_method_for_subscription(): bool { |
| 321 |
if ( ! Helper::get_addon_active_status( 'subscription' ) ) { |
| 322 |
return false; |
| 323 |
} |
| 324 |
|
| 325 |
return ! empty( $_GET['change_payment_method'] ) && 'subscription' === Helper::get_order_type( absint( wp_unslash( $_GET['change_payment_method'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification |
| 326 |
} |
| 327 |
|
| 328 |
public static function maybe_display_my_payment_method( string $payment_method_to_display, Order $order ): string { |
| 329 |
if ( has_filter( 'storeengine/subscription/my_payment_method' ) ) { |
| 330 |
$payment_method_to_display = apply_filters_deprecated( 'storeengine/subscription/my_payment_method', [ |
| 331 |
$payment_method_to_display, |
| 332 |
$order |
| 333 |
], '1.8.0', 'storeengine/payment_method/display_my_payment_method' ); |
| 334 |
} |
| 335 |
|
| 336 |
return (string) apply_filters( 'storeengine/payment_method/display_my_payment_method', $payment_method_to_display, $order ); |
| 337 |
} |
| 338 |
} |
| 339 |
|