| 1 |
<?php declare(strict_types=1); |
| 2 |
|
| 3 |
namespace MultiSafepay\WooCommerce\Services\Qr; |
| 4 |
|
| 5 |
use MultiSafepay\Api\Transactions\Gateways as GatewaysSdk; |
| 6 |
use MultiSafepay\Api\Transactions\OrderRequest; |
| 7 |
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\PaymentOptions; |
| 8 |
use MultiSafepay\Api\Transactions\OrderRequest\Arguments\SecondChance; |
| 9 |
use MultiSafepay\Exception\ApiException; |
| 10 |
use MultiSafepay\Exception\InvalidArgumentException; |
| 11 |
use MultiSafepay\WooCommerce\PaymentMethods\Base\BasePaymentMethod; |
| 12 |
use MultiSafepay\WooCommerce\Services\OrderService; |
| 13 |
use MultiSafepay\WooCommerce\Services\SdkService; |
| 14 |
use MultiSafepay\WooCommerce\Utils\Logger; |
| 15 |
use MultiSafepay\WooCommerce\Utils\MoneyUtil; |
| 16 |
use Psr\Http\Client\ClientExceptionInterface; |
| 17 |
|
| 18 |
/** |
| 19 |
* Class QrOrderService |
| 20 |
* |
| 21 |
* @package MultiSafepay\WooCommerce\Services\Qr |
| 22 |
*/ |
| 23 |
class QrOrderService extends OrderService { |
| 24 |
|
| 25 |
/** |
| 26 |
* Expiration time for payment methods API request |
| 27 |
*/ |
| 28 |
public const EXPIRATION_TIME_CHECKOUT_QR_DATA = 86400; |
| 29 |
|
| 30 |
/** |
| 31 |
* @var QrCustomerService |
| 32 |
*/ |
| 33 |
public $qr_customer_service; |
| 34 |
|
| 35 |
/** |
| 36 |
* @var QrShoppingCartService |
| 37 |
*/ |
| 38 |
public $qr_shopping_cart_service; |
| 39 |
|
| 40 |
/** |
| 41 |
* @var Logger |
| 42 |
*/ |
| 43 |
private $logger; |
| 44 |
|
| 45 |
/** |
| 46 |
* ShoppingCartOrderService constructor. |
| 47 |
*/ |
| 48 |
public function __construct() { |
| 49 |
parent::__construct(); |
| 50 |
$this->qr_customer_service = new QrCustomerService(); |
| 51 |
$this->qr_shopping_cart_service = new QrShoppingCartService(); |
| 52 |
$this->logger = new Logger(); |
| 53 |
} |
| 54 |
|
| 55 |
/** |
| 56 |
* @param string $order_id |
| 57 |
* @param string $gateway_code |
| 58 |
* @param string $payload |
| 59 |
* @param array $checkout_fields |
| 60 |
* @return OrderRequest |
| 61 |
* @throws InvalidArgumentException |
| 62 |
*/ |
| 63 |
public function get_order_request( |
| 64 |
string $order_id, |
| 65 |
string $gateway_code, |
| 66 |
string $payload, |
| 67 |
array $checkout_fields |
| 68 |
): OrderRequest { |
| 69 |
$cart = WC()->cart; |
| 70 |
$order_request = new OrderRequest(); |
| 71 |
$order_request |
| 72 |
->addOrderId( $order_id ) |
| 73 |
->addMoney( MoneyUtil::create_money( (float) $cart->get_total( 'edit' ), get_woocommerce_currency() ) ) |
| 74 |
->addGatewayCode( $gateway_code ) |
| 75 |
->addType( OrderRequest::DIRECT_TYPE ) |
| 76 |
->addPluginDetails( $this->create_plugin_details() ) |
| 77 |
->addDescriptionText( $this->get_order_description_text( $order_id ) ) |
| 78 |
->addCustomer( $this->qr_customer_service->create_customer_details_from_cart( $checkout_fields['customer'] ) ) |
| 79 |
->addPaymentOptions( $this->create_payment_options( $this->generate_token( $order_id ) ) ) |
| 80 |
->addSecondsActive( $this->get_seconds_active() ) |
| 81 |
->addSecondChance( ( new SecondChance() )->addSendEmail( (bool) get_option( 'multisafepay_second_chance', false ) ) ) |
| 82 |
->addData( array( 'var2' => $order_id ) ); |
| 83 |
|
| 84 |
if ( $this->is_filled_shipping_address( $checkout_fields ) && WC()->cart->needs_shipping() ) { |
| 85 |
$order_request->addDelivery( $this->qr_customer_service->create_customer_details_from_cart( $checkout_fields['customer'], 'shipping' ) ); |
| 86 |
} |
| 87 |
|
| 88 |
if ( ! get_option( 'multisafepay_disable_shopping_cart', false ) || in_array( $gateway_code, GatewaysSdk::SHOPPING_CART_REQUIRED_GATEWAYS, true ) ) { |
| 89 |
$order_request->addShoppingCart( $this->qr_shopping_cart_service->create_shopping_cart( $cart, get_woocommerce_currency() ) ); |
| 90 |
} |
| 91 |
|
| 92 |
if ( ! empty( $payload ) ) { |
| 93 |
$order_request->addData( |
| 94 |
array( |
| 95 |
'payment_data' => array( |
| 96 |
'gateway' => $gateway_code, |
| 97 |
'payload' => $payload, |
| 98 |
), |
| 99 |
) |
| 100 |
); |
| 101 |
} |
| 102 |
|
| 103 |
$order_request = $this->add_none_tax_rate( $order_request ); |
| 104 |
|
| 105 |
return apply_filters( 'multisafepay_order_request', $order_request ); |
| 106 |
} |
| 107 |
|
| 108 |
/** |
| 109 |
* Check if the shipping address is filled |
| 110 |
* |
| 111 |
* @param array $checkout_fields |
| 112 |
* @return bool |
| 113 |
*/ |
| 114 |
public function is_filled_shipping_address( array $checkout_fields ): bool { |
| 115 |
$filled_fields = $checkout_fields['customer']['shipping'] ?? array(); |
| 116 |
return ! empty( $filled_fields['address_1'] ) || ! empty( $filled_fields['address_2'] ); |
| 117 |
} |
| 118 |
|
| 119 |
/** |
| 120 |
* @param string $token |
| 121 |
* @return PaymentOptions |
| 122 |
*/ |
| 123 |
public function create_payment_options( string $token ): PaymentOptions { |
| 124 |
$redirect_cancel_url = add_query_arg( 'token', $token, get_rest_url( get_current_blog_id(), 'multisafepay/v1/qr-balancer' ) ); |
| 125 |
$payment_options = new PaymentOptions(); |
| 126 |
$payment_options->addNotificationUrl( get_rest_url( get_current_blog_id(), 'multisafepay/v1/qr-notification' ) ); |
| 127 |
$payment_options->addCancelUrl( $redirect_cancel_url ); |
| 128 |
$payment_options->addRedirectUrl( $redirect_cancel_url ); |
| 129 |
$payment_options->addSettings( array( 'qr' => array( 'enabled' => true ) ) ); |
| 130 |
|
| 131 |
return $payment_options; |
| 132 |
} |
| 133 |
|
| 134 |
/** |
| 135 |
* Generate a token that will be used on validation of QR related endpoints |
| 136 |
* |
| 137 |
* @param string $order_id |
| 138 |
* @return string |
| 139 |
*/ |
| 140 |
public function generate_token( string $order_id ) { |
| 141 |
$token = wp_generate_password( 32, false ); |
| 142 |
set_transient( 'multisafepay_token_' . $order_id, $token, 86400 ); |
| 143 |
return $token; |
| 144 |
} |
| 145 |
|
| 146 |
/** |
| 147 |
* Check if the QR data is valid |
| 148 |
* |
| 149 |
* @param array $qr_data |
| 150 |
* @param string $order_id |
| 151 |
* @return bool |
| 152 |
*/ |
| 153 |
public function is_valid_qr_data( array $qr_data, string $order_id ): bool { |
| 154 |
$required_fields = array( |
| 155 |
'image' => $qr_data['qr']['image'] ?? null, |
| 156 |
'token' => $qr_data['qr']['params']['token'] ?? null, |
| 157 |
'order_id' => $qr_data['order_id'] ?? null, |
| 158 |
); |
| 159 |
|
| 160 |
foreach ( $required_fields as $field => $value ) { |
| 161 |
if ( empty( $value ) || ( ( 'order_id' === $field ) && ( (string) $value !== $order_id ) ) ) { |
| 162 |
return false; |
| 163 |
} |
| 164 |
} |
| 165 |
|
| 166 |
return true; |
| 167 |
} |
| 168 |
|
| 169 |
/** |
| 170 |
* Generate a unique order ID for cart-based transactions |
| 171 |
* |
| 172 |
* @return string |
| 173 |
*/ |
| 174 |
public function generate_unique_order_id(): string { |
| 175 |
return 'QR-' . uniqid( '', true ); |
| 176 |
} |
| 177 |
|
| 178 |
/** |
| 179 |
* Activate the QR code for the order |
| 180 |
* |
| 181 |
* @param BasePaymentMethod $payment_gateway |
| 182 |
* @param string $payload |
| 183 |
* @param array $checkout_fields |
| 184 |
* @return array |
| 185 |
* @throws InvalidArgumentException |
| 186 |
*/ |
| 187 |
public function place_order( BasePaymentMethod $payment_gateway, string $payload, array $checkout_fields ): array { |
| 188 |
$order_id = $this->generate_unique_order_id(); |
| 189 |
|
| 190 |
// Create a transient using order ID and the checkout fields |
| 191 |
set_transient( 'multisafepay_qr_order_' . $order_id, $checkout_fields, self::EXPIRATION_TIME_CHECKOUT_QR_DATA ); |
| 192 |
|
| 193 |
$order_request = $this->get_order_request( |
| 194 |
$order_id, |
| 195 |
$payment_gateway->get_payment_method_gateway_code(), |
| 196 |
$payload, |
| 197 |
$checkout_fields |
| 198 |
); |
| 199 |
|
| 200 |
$sdk = new SdkService(); |
| 201 |
$transaction_manager = $sdk->get_transaction_manager(); |
| 202 |
|
| 203 |
try { |
| 204 |
$transaction = $transaction_manager->create( $order_request ); |
| 205 |
} catch ( ApiException |ClientExceptionInterface $exception ) { |
| 206 |
$this->logger->log_error( $exception->getMessage() ); |
| 207 |
wc_add_notice( __( 'There was a problem processing your payment using a QR code. Please try again later or contact with us.', 'multisafepay' ), 'error' ); |
| 208 |
return array(); |
| 209 |
} |
| 210 |
|
| 211 |
$payment_data = $transaction->getData(); |
| 212 |
|
| 213 |
$this->logger->log_info( 'Start MultiSafepay transaction for the order ID ' . $order_id . ' on ' . date( 'd/m/Y H:i:s' ) . ' with payment data ' . wp_json_encode( $payment_data ) ); |
| 214 |
|
| 215 |
if ( $this->is_valid_qr_data( $payment_data, $order_id ) ) { |
| 216 |
return $payment_data; |
| 217 |
} |
| 218 |
|
| 219 |
$this->logger->log_error( 'QR code was not correct' ); |
| 220 |
|
| 221 |
return array(); |
| 222 |
} |
| 223 |
} |
| 224 |
|