<?php
/**
* Copyright (c) 2011-present Qualiteam software Ltd. All rights reserved.
* See https://www.x-cart.com/license-agreement.html for license details.
*/
namespace XPay\XPaymentsCloud\EventListener;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use XCart\Event\Payment\PaymentActionEvent;
use XCart\Payment\RequestProcessor;
use XCart\Payment\URLGenerator\BackendURLGenerator;
use XLite\Core\Config;
use XLite\Core\ConfigCell;
use XLite\Core\Session;
use XLite\Core\Translation;
use XLite\Model\Address;
use XLite\Model\AddressField;
use XLite\Model\Cart;
use XLite\Model\Currency;
use XLite\Model\Order;
use XLite\Model\Payment\BackendTransaction;
use XLite\Model\Payment\Method;
use XLite\Model\Payment\Transaction;
use XLite\Model\Profile;
use XLite\Model\Repo\AddressField as AddressFieldRepo;
use XPay\XPaymentsCloud\Core\Database;
use XPay\XPaymentsCloud\Main as XPaymentsHelper;
use XPay\XPaymentsCloud\Model\Payment\XpaymentsFraudCheckData as FraudCheckData;
use XPay\XPaymentsCloud\Model\Subscription\Subscription;
use XPay\XPaymentsCloud\View\FormField\Select\CardNumberDisplayFormat as Format;
use XPaymentsCloud\ApiException;
use XPaymentsCloud\Client;
use XPaymentsCloud\Model\Payment as XpPayment;
final class PaymentProcessor
{
private Session $XCSession;
private ConfigCell $config;
private AddressFieldRepo $addressFieldRepo;
public function __construct(
private BackendURLGenerator $backendURLGenerator,
private Translation $translation,
private SessionInterface $session,
private EntityManagerInterface $entityManager,
) {
$this->XCSession = Session::getInstance();
$this->config = Config::getInstance();
$this->addressFieldRepo = Database::getRepo(AddressField::class);
}
public function onPaymentGenerateWidgetDataAction(PaymentActionEvent $event): void
{
/** @var Method $method */
$method = $event->getMethod();
$transaction = $event->getTransaction();
/** @var Cart $cart */
$cart = $transaction->getOrder();
$profile = $cart->getProfile();
$event->setOutputData([
'widgetConfig' => json_encode([
'account' => $method->getSetting('account'),
'widgetKey' => $method->getSetting('widget_key'),
'showSaveCard' => !$this->isHideSaveCardCheckbox($cart),
'customerId' => $profile ? $profile->getXpaymentsCustomerId() : '',
'order' => [
'total' => $this->getXpaymentsCartTotal($cart),
'currency' => $transaction->getCurrency()->getCode(),
'tokenizeCard' => XPaymentsHelper::isDelayedPaymentEnabled(),
],
'company' => [
'name' => $this->config->Company->company_name,
'countryCode' => $this->config->Company->location_country,
],
'language' => $this->XCSession->getLanguage()->getCode(),
], JSON_THROW_ON_ERROR),
]);
}
public function onPaymentInitAction(PaymentActionEvent $event): void
{
/** @var Client $client */
$client = XPaymentsHelper::getClient();
$data = $event->getData();
$metaData = $event->getMetaData();
$transaction = $event->getTransaction();
$order = $transaction->getOrder();
$profile = $order->getProfile();
$currency = $transaction->getCurrency();
$orderHasSubscriptions = $order->hasXpaymentsSubscriptions();
$outputData = [];
try {
if ($this->isInitialPaymentTokenizeOnly($order)) {
$response = $client->doTokenizeCard(
$data['token'],
$this->translation->translate('Card Setup'),
$profile ? $profile->getXpaymentsCustomerId() : '',
$this->prepareCardSetupCart(
$profile,
$profile->getBillingAddress(),
$currency
),
$metaData['success_url'] ?? $this->backendURLGenerator->generateReturnURL($transaction, '', true),
$this->backendURLGenerator->generateCallbackURL($transaction, '', true),
);
} else {
$response = $client->doPay(
$data['token'],
$transaction->getPublicId(),
$profile ? $profile->getXpaymentsCustomerId() : '',
$this->prepareCart($transaction),
$metaData['success_url'] ?? $this->backendURLGenerator->generateReturnURL($transaction, '', true),
$this->backendURLGenerator->generateCallbackURL($transaction, '', true),
$orderHasSubscriptions ? true : null
);
}
/** @var XpPayment $payment */
$payment = $response->getPayment();
$note = $response->message ?: $payment->message;
if (!is_null($response->redirectUrl)) {
// Should redirect to continue payment
$transaction->setXpaymentsId($payment->xpid);
$url = $response->redirectUrl;
if (!\XLite\Core\Converter::isURL($url)) {
throw new ApiException('Invalid 3-D Secure URL');
}
$status = RequestProcessor::OUTPUT_CODE_SEPARATE;
$event->setOutputMetaData([
'url' => $url,
'method' => 'get',
'xpaymentsBuyWithWallet' => $data['xpaymentsBuyWithWallet'] ?? '',
'xpaymentsWalletId' => $data['xpaymentsWalletId'] ?? '',
]);
} else {
$status = $this->processPaymentFinish($transaction, $payment, $event, false);
if ($status !== RequestProcessor::OUTPUT_CODE_FAILED) {
$this->processSubscriptions($transaction, $payment);
}
}
} catch (ApiException $exception) {
$status = RequestProcessor::OUTPUT_CODE_FAILED;
XPaymentsHelper::log('Error: ' . $exception->getMessage());
$event->addOutputTransactionData('xpaymentsMessage', $exception->getMessage(), 'Message');
$note = $exception->getPublicMessage() ?: 'Failed to process the payment!';
}
$event->setOutputData($outputData);
$event->setOutputNote(substr($note, 0, 255));
$event->setOutputCode($status);
}
public function onPaymentPayAction(PaymentActionEvent $event): void
{
$transaction = $event->getTransaction();
if ($transaction->isXpayments()) {
try {
/** @var Client $client */
$client = XPaymentsHelper::getClient();
$response = $client->doContinue(
$transaction->getXpaymentsId()
);
$payment = $response->getPayment();
$note = $response->message ?: $payment->message;
$result = $this->processPaymentFinish($transaction, $payment, $event);
if ($result !== RequestProcessor::OUTPUT_CODE_FAILED) {
$this->processSubscriptions($transaction, $payment);
}
} catch (\XPaymentsCloud\ApiException $exception) {
$result = RequestProcessor::OUTPUT_CODE_FAILED;
$note = $exception->getMessage();
XPaymentsHelper::log('Error: ' . $note);
$event->addOutputTransactionData('xpaymentsMessage', $note, 'Message');
}
$event->setOutputNote($note);
$event->setOutputCode($result);
} else {
// Invalid non-XP transaction
TopMessage::addError('Transaction was lost!');
}
}
private function isHideSaveCardCheckbox(Cart $cart)
{
$anonymous = (!$cart->getProfile() || $cart->getProfile()->getAnonymous())
&& (
!$this->session->get('order_create_profile')
|| !$this->session->get('createProfilePassword')
);
$cartHasSubscriptions = $cart->hasXpaymentsSubscriptions();
return $anonymous || $cartHasSubscriptions || XPaymentsHelper::isDelayedPaymentEnabled();
}
private function getXpaymentsCartTotal(Cart $cart)
{
return XPaymentsHelper::isDelayedPaymentEnabled()
? $this->getCardSetupAmount()
: $cart->getTotal();
}
private function getCardSetupAmount()
{
$result = null;
try {
$response = XPaymentsHelper::getClient()->doGetTokenizationSettings();
$result = $response->tokenizeCardAmount;
} catch (ApiException $e) {
XPaymentsHelper::log($e->getMessage());
}
return $result;
}
private function isInitialPaymentTokenizeOnly(Order $order): bool
{
$result = XPaymentsHelper::isDelayedPaymentEnabled();
if (!$result) {
$result = $order->hasXpaymentsSubscriptionsAndTotalIsZero() || ($order->hasOnlyTrialSubscriptionItems() && $order->getOpenTotal() <= Order::ORDER_ZERO);
}
return $result;
}
private function prepareCardSetupCart(Profile $profile, Address $address, Currency $currency)
{
return [
'login' => $this->getLoginForCart($profile),
'currency' => $currency->getCode(),
'billingAddress' => $this->prepareAddress($address, $profile->getLogin()),
'merchantEmail' => $this->getMerchantEmail(),
];
}
private function getLoginForCart(Profile $profile): string
{
return $profile->getLogin() . ' (User ID #' . $profile->getProfileId() . ')';
}
protected function prepareAddress(Address $address, string $email): array
{
$result = [];
$addressFields = [
'firstname' => 'N/A',
'lastname' => '',
'address' => 'N/A',
'city' => 'N/A',
'state' => 'N/A',
'country' => 'XX', // WA fix for MySQL 5.7 with strict mode
'zipcode' => 'N/A',
'phone' => '',
'fax' => '',
'company' => '',
];
foreach ($addressFields as $field => $defValue) {
$method = ($field === 'address') ? 'street' : $field;
if (
$address
&& ($this->addressFieldRepo->findOneBy(['serviceName' => $method]) || method_exists($address, 'get' . $method))
&& $address->$method
) {
$result[$field] = $address->$method;
if (is_object($result[$field])) {
$result[$field] = $result[$field]->getCode();
}
}
if (empty($result[$field])) {
$result[$field] = $defValue;
}
}
$result['email'] = $email;
return $result;
}
private function getMerchantEmail(): string
{
$email = $this->config->Company->orders_department;
// Try modern serialized emails or fallback to plain string
$emails = @unserialize($email);
return (is_array($emails) && !empty($emails))
? reset($emails)
: $email;
}
private function prepareCart(Transaction $transaction): array
{
$order = $transaction->getOrder();
$profile = $order->getProfile();
$currency = $transaction->getCurrency();
if ($order->getOrderNumber()) {
$description = 'Order #' . $order->getOrderNumber();
} else {
$description = 'Payment transaction: ' . $transaction->getPublicId();
}
$result = [
'login' => $this->getLoginForCart($profile),
'items' => [],
'currency' => $currency->getCode(),
'shippingCost' => 0.00,
'taxCost' => 0.00,
'discount' => 0.00,
'totalCost' => 0.00,
'description' => $description,
'merchantEmail' => $this->getMerchantEmail(),
];
$billing = $profile->getBillingAddress();
$shipping = $profile->getShippingAddress();
$email = $profile->getLogin();
if ($billing && $shipping) {
$result['billingAddress'] = $this->prepareAddress($billing, $email);
$result['shippingAddress'] = $this->prepareAddress($shipping, $email);
} elseif ($billing) {
$result['billingAddress'] = $result['shippingAddress'] = $this->prepareAddress($billing, $email);
} else {
$result['billingAddress'] = $result['shippingAddress'] = $this->prepareAddress($shipping, $email);
}
// Set items
if ($order->getItems()) {
foreach ($order->getItems() as $item) {
$itemElement = [
'sku' => (string) ($item->getSku() ?: $item->getName()),
'name' => (string) ($item->getName() ?: $item->getSku()),
'price' => $this->roundCurrency($item->getPrice()),
'quantity' => $item->getAmount(),
];
if (!$itemElement['sku']) {
$itemElement['sku'] = 'N/A';
}
if (!$itemElement['name']) {
$itemElement['name'] = 'N/A';
}
$result['items'][] = $itemElement;
}
}
// Set costs
$result['shippingCost'] = $this->roundCurrency(
$order->getSurchargesSubtotal(\XLite\Model\Base\Surcharge::TYPE_SHIPPING, false)
);
$result['taxCost'] = $this->roundCurrency(
$order->getSurchargesSubtotal(\XLite\Model\Base\Surcharge::TYPE_TAX, false)
);
$result['totalCost'] = $this->roundCurrency($order->getTotal());
$result['discount'] = $this->roundCurrency(
abs($order->getSurchargesSubtotal(\XLite\Model\Base\Surcharge::TYPE_DISCOUNT, false))
);
return $result;
}
private function roundCurrency($data): string
{
return sprintf('%01.2f', round($data, 2));
}
protected function processPaymentFinish(Transaction $transaction, XpPayment $payment, PaymentActionEvent $event, $isRebill = false)
{
$this->setTransactionDataCells($transaction, $payment, $event);
$this->setFraudCheckData($transaction, $payment);
if ($payment->initialTransactionId) {
$transaction->setPublicId($payment->initialTransactionId . ' (' . $transaction->getPublicId() . ')');
}
if ($payment->customerId) {
$transaction->getOrigProfile()->setXpaymentsCustomerId($payment->customerId);
}
$status = $payment->status;
$order = $transaction->getOrder();
if (
!$isRebill
&& $this->isInitialPaymentTokenizeOnly($order)
) {
if (
!empty($payment->card->savedCardId)
|| !empty($payment->parentXpid)
) {
if ($order->hasOnlyTrialSubscriptionItems()) {
$result = RequestProcessor::OUTPUT_CODE_COMPLETED;
} else {
if (empty($payment->card->savedCardId)) {
// TODO: rework delayed payment so it will match used cards by xpid, not by cardId
// (to avoid unnecessary api calls)
$savedCardId = $this->getSavedCardIdByXpid($payment->parentXpid, $payment->customerId);
} else {
$savedCardId = $payment->card->savedCardId;
}
$order->setDelayedPaymentSavedCardId($savedCardId);
$transaction->setValue(0.00);
$transaction->setType(\XLite\Model\Payment\BackendTransaction::TRAN_TYPE_AUTH);
$result = RequestProcessor::OUTPUT_CODE_PENDING;
}
} else {
$result = RequestProcessor::OUTPUT_CODE_FAILED;
}
} else {
if (
$status === XpPayment::AUTH
|| $status === XpPayment::CHARGED
) {
$result = RequestProcessor::OUTPUT_CODE_COMPLETED;
$this->setTransactionTypeByStatus($transaction, $status);
$this->registerBackendTransaction($transaction, $payment);
} elseif (
$status === XpPayment::DECLINED
) {
$result = RequestProcessor::OUTPUT_CODE_FAILED;
} else {
$result = RequestProcessor::OUTPUT_CODE_PENDING;
}
}
return $result;
}
protected function setTransactionDataCells(Transaction $transaction, XpPayment $payment, PaymentActionEvent $event)
{
$transaction->setXpaymentsId($payment->xpid);
$event->addOutputTransactionData('xpaymentsMessage', $payment->lastTransaction->message, 'Message');
if (is_object($payment->details)) {
// Set payment details i.e. something that returned from the gateway
$details = get_object_vars($payment->details);
foreach ($details as $title => $value) {
if (!empty($value) && !preg_match('/(\[Kount\]|\[NoFraud\]|\[Signifyd\])/i', $title)) {
$name = $this->getTransactionDataCellName($title, 'xpaymentsDetails.');
//$transaction->setDataCell($name, $value, $title);
$event->addOutputTransactionData($name, $value, $title);
}
}
}
if (is_object($payment->verification)) {
// Set verification (AVS and CVV)
if (!empty($payment->verification->avsRaw)) {
$event->addOutputTransactionData('xpaymentsAvsResult', $payment->verification->avsRaw, 'AVS Check Result');
}
if (!empty($payment->verification->cvvRaw)) {
$event->addOutputTransactionData('xpaymentsCvvResult', $payment->verification->cvvRaw, 'CVV Check Result');
}
}
if (
is_object($payment->card)
&& !empty($payment->card->last4)
) {
// Set masked card details
if (
empty($payment->card->first6)
|| Format::FORMAT_MASKED === $this->config->XPay->XPaymentsCloud->card_number_display_format
) {
$first6 = '******';
} else {
$first6 = $payment->card->first6;
}
$event->addOutputTransactionData(
'xpaymentsCardNumber',
sprintf('%s******%s', $first6, $payment->card->last4),
'Card number',
'C'
);
if (
!empty($payment->card->expireMonth)
&& !empty($payment->card->expireYear)
) {
if (Format::FORMAT_MASKED === $this->config->XPay->XPaymentsCloud->card_number_display_format) {
$payment->card->expireMonth = '**';
$payment->card->expireYear = '****';
}
$event->addOutputTransactionData(
'xpaymentsCardExpirationDate',
sprintf('%s/%s', $payment->card->expireMonth, $payment->card->expireYear),
'Expiration date',
'C'
);
}
if (!empty($payment->card->type)) {
$event->addOutputTransactionData(
'xpaymentsCardType',
$payment->card->type,
'Card type',
'C'
);
}
if (!empty($payment->card->cardholderName)) {
$event->addOutputTransactionData(
'xpaymentsCardholder',
$payment->card->cardholderName,
'Cardholder name',
'C'
);
}
}
}
protected function setFraudCheckData(Transaction $transaction, XpPayment $payment): void
{
if (!$payment->fraudCheck) {
return;
}
if ($oldFraudCheckData = $transaction->getXpaymentsFraudCheckData()) {
foreach ($oldFraudCheckData as $fraudCheckData) {
$this->entityManager->remove($fraudCheckData);
}
}
// Maximum fraud result within several services (if there are more than one)
$maxFraudResult = FraudCheckData::RESULT_UNKNOWN;
// Code of the service which got "most fraud" result
$maxFraudResultCode = '';
// Flag to check if any errors which prevented fraud check occurred
$errorsFound = false;
foreach ($payment->fraudCheck as $service) {
// Ignore "noname" services. This must be filled in on the X-Payments Cloud side
if (!$service['code'] || !$service['service']) {
continue;
}
if (!$maxFraudResultCode) {
// Use first the code, so that something is specified
$maxFraudResultCode = $service['code'];
}
$fraudCheckData = new FraudCheckData();
$fraudCheckData->setTransaction($transaction);
$transaction->addXpaymentsFraudCheckData($fraudCheckData);
$fraudCheckData->setCode($service['code']);
$fraudCheckData->setService($service['service']);
$module = $service['module'] ?? '';
$fraudCheckData->setModule($module);
if (!empty($service['result'])) {
$fraudCheckData->setResult($service['result']);
if ((int) $service['result'] > $maxFraudResult) {
$maxFraudResult = (int) $service['result'];
$maxFraudResultCode = $service['code'];
}
}
if (!empty($service['status'])) {
$fraudCheckData->setStatus($service['status']);
}
if (!empty($service['score'])) {
$fraudCheckData->setScore($service['score']);
}
if (!empty($service['transactionId'])) {
$fraudCheckData->setServiceTransactionId($service['transactionId']);
}
if (!empty($service['url'])) {
$fraudCheckData->setUrl($service['url']);
}
if (!empty($service['message'])) {
$fraudCheckData->setMessage($service['message']);
if ($service['result'] === FraudCheckData::RESULT_UNKNOWN) {
// Unknown result with message should be shown as error
$errorsFound = true;
}
}
if (!empty($service['errors'])) {
$errors = implode("\n", $service['errors']);
$fraudCheckData->setErrors($errors);
$errorsFound = true;
}
if (!empty($service['rules'])) {
$rules = implode("\n", $service['rules']);
$fraudCheckData->setRules($rules);
}
if (!empty($service['warnings'])) {
$warnings = implode("\n", $service['warnings']);
$fraudCheckData->setWarnings($warnings);
}
}
// Convert maximum fraud result to the order's fraud status
$status = Order::FRAUD_STATUS_UNKNOWN;
switch ($maxFraudResult) {
case FraudCheckData::RESULT_UNKNOWN:
if ($errorsFound) {
$status = Order::FRAUD_STATUS_ERROR;
} else {
$status = Order::FRAUD_STATUS_UNKNOWN;
}
break;
case FraudCheckData::RESULT_ACCEPTED:
$status = Order::FRAUD_STATUS_CLEAN;
break;
case FraudCheckData::RESULT_MANUAL:
case FraudCheckData::RESULT_PENDING:
$status = Order::FRAUD_STATUS_REVIEW;
break;
case FraudCheckData::RESULT_FAIL:
$status = Order::FRAUD_STATUS_FRAUD;
break;
}
$transaction->getOrder()
->setXpaymentsFraudStatus($status)
->setXpaymentsFraudType($maxFraudResultCode)
->setXpaymentsFraudCheckTransactionId($transaction->getTransactionId());
}
/**
* Request savedCardId for xpid
* We do not use getPaymentInfo here because it's a heavy call
* Instead, we request cards saved for particular customer and filter them
*/
protected function getSavedCardIdByXpid(string $xpid, string $customerId): string
{
$client = XPaymentsHelper::getClient();
try {
$response = $client->doGetCustomerCards(
$customerId
);
if (is_array($response->cards)) {
foreach ($response->cards as $card) {
if ($card['xpid'] === $xpid) {
return $card['cardId'];
}
}
}
} catch (ApiException $e) {
XPaymentsHelper::log($e->getMessage());
}
return '';
}
private function getTransactionDataCellName(string $title, string $prefix = ''): string
{
return $prefix . \Includes\Utils\Converter::convertToLowerCamelCase(
preg_replace('/[^a-z0-9_-]+/i', '_', $title)
);
}
private function setTransactionTypeByStatus(Transaction $transaction, $responseStatus)
{
// Initial transaction type is not known currently before payment, try to guess it from X-P transaction status
if ($responseStatus === XpPayment::AUTH) {
$transaction->setType(\XLite\Model\Payment\BackendTransaction::TRAN_TYPE_AUTH);
} elseif ($responseStatus === XpPayment::CHARGED) {
$transaction->setType(\XLite\Model\Payment\BackendTransaction::TRAN_TYPE_SALE);
}
}
protected function registerBackendTransaction(Transaction $transaction, XpPayment $payment)
{
$type = null;
$value = null;
switch ($payment->status) {
case XpPayment::INITIALIZED:
$type = BackendTransaction::TRAN_TYPE_SALE;
break;
case XpPayment::AUTH:
$type = BackendTransaction::TRAN_TYPE_AUTH;
break;
case XpPayment::DECLINED:
if ($payment->authorized->amount === 0 && $payment->charged->amount === 0) {
$type = BackendTransaction::TRAN_TYPE_DECLINE;
} else {
$type = BackendTransaction::TRAN_TYPE_VOID;
}
break;
case XpPayment::CHARGED:
if ($payment->amount === $payment->charged->amount) {
$type = BackendTransaction::TRAN_TYPE_CAPTURE;
$value = $this->getActualAmount('captured', $transaction, $payment->amount);
} else {
$type = BackendTransaction::TRAN_TYPE_CAPTURE_PART;
$value = $this->getActualAmount('captured', $transaction, $payment->amount);
}
break;
case XpPayment::REFUNDED:
$type = BackendTransaction::TRAN_TYPE_REFUND;
$value = $this->getActualAmount('refunded', $transaction, $payment->amount);
break;
case XpPayment::PART_REFUNDED:
$type = BackendTransaction::TRAN_TYPE_REFUND_PART;
$value = $this->getActualAmount('refunded', $transaction, $payment->amount);
break;
default:
}
if ($type) {
$backendTransaction = $transaction->createBackendTransaction($type);
if ($payment->status !== XpPayment::INITIALIZED) {
$backendTransaction->setStatus(BackendTransaction::STATUS_SUCCESS);
}
if ($value >= 0.01) {
$backendTransaction->setValue($value);
}
$backendTransaction->setDataCell('xpaymentsMessage', $payment->lastTransaction->message, 'Message');
$backendTransaction->registerTransactionInOrderHistory('callback');
}
}
private function getActualAmount($action, Transaction $transaction, $baseAmount)
{
$amount = 0;
$btTypes = [];
if ($action === 'refunded') {
$btTypes = [
BackendTransaction::TRAN_TYPE_REFUND,
BackendTransaction::TRAN_TYPE_REFUND_PART,
BackendTransaction::TRAN_TYPE_REFUND_MULTI,
];
} elseif ($action === 'captured') {
$btTypes = [
BackendTransaction::TRAN_TYPE_CAPTURE,
BackendTransaction::TRAN_TYPE_CAPTURE_PART,
BackendTransaction::TRAN_TYPE_CAPTURE_MULTI,
];
}
foreach ($transaction->getBackendTransactions() as $bt) {
if ($bt->isCompleted() && in_array($bt->getType(), $btTypes)) {
$amount += $bt->getValue();
}
}
$amount = $baseAmount - $amount;
return max(0, $amount);
}
private function processSubscriptions(Transaction $transaction, XpPayment $payment)
{
$order = $transaction->getOrder();
if (
$order->hasXpaymentsSubscriptions()
&& (
!empty($payment->card->saved)
|| !empty($payment->parentXpid)
)
) {
/** @var Client $client */
$client = XPaymentsHelper::getClient();
try {
$response = $client->doCreateSubscriptions(
$this->prepareSubscriptionPlans(),
$payment->customerId,
$payment->parentXpid ?: $payment->xpid
);
if ($response->getSubscriptions()) {
$this->createSubscriptions($order, $response->getSubscriptions());
}
} catch (ApiException $e) {
XPaymentsHelper::log($e->getMessage());
}
}
}
protected function prepareSubscriptionPlans(Transaction $transaction): array
{
$result = [];
$cart = $transaction->getOrder();
if ($cart->getItems()) {
foreach ($cart->getItems() as $item) {
if ($item->isXpaymentsSubscription()) {
$uniqOrderItemId = $item->getXpaymentsUniqueId();
if (!$uniqOrderItemId) {
$uniqOrderItemId = \XLite\Core\Converter::generateRandomToken();
$item->setXpaymentsUniqueId($uniqOrderItemId);
}
$plan = $item->getProduct()->getXpaymentsSubscriptionPlan();
$subscriptionPlan = [
'subscriptionSchedule' => [
'type' => $plan->getType(),
'number' => $plan->getNumber(),
'period' => $plan->getPeriod(),
'reverse' => $plan->getReverse(),
'periods' => $plan->getPeriods(),
],
'callbackUrl' => Subscription::getCallbackUrl(),
'recurringAmount' => $item->getAmount() * $item->getXpaymentsDisplayFeePrice(),
'description' => $item->getDescription(),
'uniqueOrderItemId' => $item->getXpaymentsUniqueId(),
];
if ($item->hasTrialPeriod()) {
$subscriptionPlan['trialDuration'] = $plan->getTrialDuration();
$subscriptionPlan['trialDurationUnit'] = $plan->getTrialDurationUnit();
}
$result[] = $subscriptionPlan;
}
}
}
return $result;
}
protected function createSubscriptions(Order $order, array $xpaymentsSubscriptions)
{
$shippingAddress = $order->getProfile()->getShippingAddress();
$shippingId = $order->getShippingId();
foreach ($xpaymentsSubscriptions as $xpaymentsSubscription) {
/** @var Subscription $subscription */
$subscription = Database::getRepo('XPay\XPaymentsCloud\Model\Subscription\Subscription')
->findOneBy(['xpaymentsSubscriptionPublicId' => $xpaymentsSubscription->getPublicId()]);
if (!$subscription) {
$item = null;
if ($xpaymentsSubscription->getUniqueOrderItemId()) {
/** @var \XLite\Model\OrderItem $item */
$item = Database::getRepo('XLite\Model\OrderItem')
->findOneBy([
'xpaymentsUniqueId' => $xpaymentsSubscription->getUniqueOrderItemId(),
]);
}
if ($item) {
$subscription = new Subscription();
$item->setXpaymentsSubscription($subscription);
$subscription
->setInitialOrderItem($item)
->setShippingId($shippingId)
->setShippingAddress($shippingAddress)
->setCalculateShipping($item->getProduct()->getXpaymentsSubscriptionPlan()->getCalculateShipping())
->setDataFromApi($xpaymentsSubscription);
$this->entityManager->persist($subscription);
}
}
}
}
}