<?php
/**
* Copyright (c) 2011-present Qualiteam software Ltd. All rights reserved.
* See https://www.x-cart.com/license-agreement.html for license details.
*/
namespace XC\Stripe\EventListener;
use Stripe\Checkout\Session;
use Stripe\Exception\ApiErrorException;
use XC\Stripe\Main;
use XC\Stripe\Model\Payment\Method;
use XC\Stripe\Model\Payment\Stripe;
use XCart\Event\Payment\PaymentActionEvent;
use XCart\Event\Payment\TransactionData;
use XCart\Payment\RequestProcessor;
use XCart\Payment\URLGenerator\BackendURLGenerator;
use XCart\Payment\URLGenerator\URLGeneratorInterface;
use XLite\Core\Config;
final class PaymentProcessor
{
public function __construct(
private BackendURLGenerator $backendURLGenerator
) {
}
public function onPaymentInitAction(PaymentActionEvent $event): void
{
/** @var Session $session */
$session = $this->getSession($event->getMethod(), $event->getTransaction(), $event->getMetaData());
$event->addOutputTransactionData('stripe_id', $session->payment_intent, 'Stripe payment intent');
/** @var Method $method */
$method = $event->getMethod();
$prefix = $method->getProcessor()->isTestMode($method) ? 'Test' : '';
$publishKey = $method->getSetting('publishKey' . $prefix);
$event->setOutputData([
'publishKey' => $publishKey
]);
$event->setOutputMetaData([
'url' => $session->url,
'method' => 'get'
]);
$event->setOutputCode(RequestProcessor::OUTPUT_CODE_SILENT);
}
public function onPaymentPayAction(PaymentActionEvent $event): void
{
$status = RequestProcessor::OUTPUT_CODE_FAILED;
/** @var TransactionData[] $transactionData */
$transactionData = $event->getTransaction()->getData();
$stripeId = '';
foreach ($transactionData as $data) {
if ($data->getName() === 'stripe_id') {
$stripeId = $data->getValue();
break;
}
}
try {
$intent = $this->getIntent($event->getMethod(), $stripeId);
} catch (\Throwable $exception) {
$event->setOutputNote($exception->getMessage());
$event->setOutputCode($status);
return;
}
if (in_array($intent->status, ['succeeded', 'requires_capture', 'processing'])) {
if ($intent->status === 'processing') {
$status = RequestProcessor::OUTPUT_CODE_PENDING;
} else {
$status = RequestProcessor::OUTPUT_CODE_COMPLETED;
}
}
$event->addOutputBackendTransaction(
$status,
$event->getValue(),
$event->getTransaction()->getType()
);
$event->setOutputNote('');
$event->setOutputCode($status);
}
private function initStripe(\XLite\Model\Payment\Method $method): void
{
/** @var Stripe $processor */
$processor = $method->getProcessor();
$clientKey = $processor->getActualClientSecret($method);
\Stripe\Stripe::setApiKey($clientKey);
\Stripe\Stripe::setApiVersion(Stripe::API_VERSION);
\Stripe\Stripe::setAppInfo(
Stripe::APP_NAME,
Main::getVersion(),
'https://market.x-cart.com/addons/stripe-payment-module.html',
Stripe::APP_PARTNER_ID
);
}
private function getSession(
\XLite\Model\Payment\Method $method,
\XLite\Model\Payment\Transaction $transaction,
array $metaData = []
): ?\Stripe\Checkout\Session {
$this->initStripe($method);
return \Stripe\Checkout\Session::create($this->getCheckoutSessionParams($transaction, $method->getProcessor(), $metaData));
}
private function getIntent(
\XLite\Model\Payment\Method $method,
string $sessionId
) {
$this->initStripe($method);
return \Stripe\PaymentIntent::retrieve($sessionId);
}
/**
* @throws ApiErrorException
*/
private function getCheckoutSessionParams(
\XLite\Model\Payment\Transaction $transaction,
Stripe $processor,
array $metaData = []
): array {
$currency = $transaction->getCurrency();
$order = $transaction->getOrder();
$lineItems = [
[
'price_data' => [
'currency' => strtolower($currency->getCode()),
'product_data' => [
'name' => Config::getInstance()->Company->company_name,
],
'unit_amount' => $currency->roundValueAsInteger($order->getTotal()),
],
'quantity' => 1,
],
];
$paymentMethods = $processor->getEnabledPaymentMethods($order);
$paymentIntentData = $this->getPaymentIntentData($transaction, $paymentMethods);
if (
in_array('afterpay_clearpay', $paymentMethods, true)
&& $shippingInfo = $processor->getShippingInfoForAfterpayClearpay($order)
) {
$paymentIntentData['shipping'] = $shippingInfo;
}
$params = [
'success_url' => $metaData['success_url'] ?? $this->backendURLGenerator->generateReturnURL($transaction, URLGeneratorInterface::RETURN_TXN_ID, true),
'cancel_url' => $metaData['cancel_url'] ?? $this->backendURLGenerator->generateCancelURL($transaction, URLGeneratorInterface::RETURN_TXN_ID, true),
'mode' => 'payment',
'payment_method_types' => $paymentMethods,
'payment_method_options' => $this->getPaymentMethodOptions($paymentMethods),
'client_reference_id' => $order->getOrderId(),
'customer_email' => $order->getProfile()->getLogin(),
'line_items' => $lineItems,
'payment_intent_data' => $paymentIntentData,
];
$shippingAddressCollection = $this->prepareShippingAddressCollection(
$paymentMethods,
$params,
$processor->getCountriesAvailableForShipping($order)
);
if ($shippingAddressCollection) {
$params['shipping_address_collection'] = $shippingAddressCollection;
}
$origProfile = $order->getOrigProfile();
$stripeCustomerId = null;
if ($origProfile && !$origProfile->getAnonymous()) {
$stripeCustomerId = $origProfile->getStripeCustomerId();
}
$stripeCustomer = $processor->updateStripeCustomer($order->getProfile(), $stripeCustomerId);
if ($stripeCustomer && $stripeCustomer->id) {
$params['customer'] = $stripeCustomer->id;
unset($params['customer_email']);
}
return $params;
}
private function getPaymentMethodOptions(array $paymentMethods): array
{
$result = [];
if (in_array('wechat_pay', $paymentMethods, true)) {
$result['wechat_pay'] = ['client' => 'web'];
}
return $result;
}
private function prepareShippingAddressCollection(array $paymentMethods, array $params, array $countriesAvailableForShipping): ?array
{
$result = null;
if (
!empty($countriesAvailableForShipping)
&& empty($sessionParams['payment_intent_data']['shipping'])
&& in_array('afterpay_clearpay', $paymentMethods, true)
) {
$result = [
'allowed_countries' => $countriesAvailableForShipping
];
}
return $result;
}
private function getPaymentIntentData(\XLite\Model\Payment\Transaction $transaction, array $paymentMethods): array
{
return [
'capture_method' => $transaction->getPaymentMethod()->getSetting('type') === \XLite\Model\Payment\BackendTransaction::TRAN_TYPE_SALE
? 'automatic'
: 'manual',
'description' => 'Payment transaction ID: ' . $transaction->getPublicId(),
'metadata' => [
'txnId' => $transaction->getPublicTxnId(),
],
];
}
}