Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
lucionescu committed Sep 4, 2023
0 parents commit 0d92168
Show file tree
Hide file tree
Showing 75 changed files with 7,401 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Shopware 6 plugin for Lunar
34 changes: 34 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "lunar/plugin-shopware-6",
"description": "Secure payment with credit card via Lunar",
"version": "1.0.0",
"type": "shopware-platform-plugin",
"license": "MIT",
"authors": [
{
"name": "Lunar",
"email": "[email protected]"
}
],
"require": {
"shopware/core": "6.4.*",
"shopware/administration": "6.4.*",
"shopware/storefront": "6.4.*"
},
"extra": {
"shopware-plugin-class": "Lunar\\Payment\\LunarPayment",
"label": {
"de-DE": "Cards - Lunar",
"en-GB": "Cards - Lunar"
},
"description": {
"de-DE": "Secure payment with credit card via Lunar",
"en-GB": "Secure payment with credit card via Lunar"
}
},
"autoload": {
"psr-4": {
"Lunar\\Payment\\": "src/"
}
}
}
341 changes: 341 additions & 0 deletions src/Controller/OrderTransactionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
<?php declare(strict_types=1);

namespace Lunar\Payment\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Core\System\StateMachine\StateMachineRegistry;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler;

use Lunar\Payment\lib\ApiClient;
use Lunar\Payment\Helpers\OrderHelper;
use Lunar\Payment\Helpers\PluginHelper;
use Lunar\Payment\Helpers\CurrencyHelper;
use Lunar\Payment\Helpers\LogHelper as Logger;

/**
* Responsible for handling order payment transactions
*
* @RouteScope(scopes={"api"})
*/
class OrderTransactionController extends AbstractController
{
private const CONFIG_PATH = PluginHelper::PLUGIN_CONFIG_PATH;

/** @var EntityRepository */
private $stateMachineHistory;

/** @var StateMachineRegistry */
private $stateMachineRegistry;

/** @var OrderTransactionStateHandler */
private $transactionStateHandler;

/** @var EntityRepository */
private $lunarTransactionRepository;

/** @var OrderHelper */
private $orderHelper;

/** @var SystemConfigService */
private $systemConfigService;

/** @var Logger */
private $logger;


/**
* Constructor
*/
public function __construct(
EntityRepository $stateMachineHistory,
StateMachineRegistry $stateMachineRegistry,
OrderTransactionStateHandler $transactionStateHandler,
EntityRepository $lunarTransactionRepository,
OrderHelper $orderHelper,
SystemConfigService $systemConfigService,
Logger $logger
)
{
$this->stateMachineHistory = $stateMachineHistory;
$this->stateMachineRegistry = $stateMachineRegistry;
$this->transactionStateHandler = $transactionStateHandler;
$this->lunarTransactionRepository = $lunarTransactionRepository;
$this->orderHelper = $orderHelper;
$this->systemConfigService = $systemConfigService;
$this->logger = $logger;
}

/**
* CAPTURE
*
* @Route("/api/lunar/capture", name="api.action.lunar.capture", methods={"POST"})
*/
public function capture(Request $request, Context $context): JsonResponse
{
return $this->processPaymentAction($request, $context, 'capture');
}

/**
* REFUND
*
* @Route("/api/lunar/refund", name="api.action.lunar.refund", methods={"POST"})
*/
public function refund(Request $request, Context $context): JsonResponse
{
return $this->processPaymentAction($request, $context, 'refund');
}

/**
* VOID / CANCEL
*
* @Route("/api/lunar/void", name="api.action.lunar.void", methods={"POST"})
*/
public function void(Request $request, Context $context): JsonResponse
{
return $this->processPaymentAction($request, $context, 'void');
}


/**
* @TODO unify code with that from \Subscriber\OrderTransactionStateChangeSubscriber.php
*
*/
private function processPaymentAction(
Request $request,
Context $context,
string $actionType
): JsonResponse
{

switch ($actionType) {
case OrderHelper::CAPTURE_STATUS:
$lunarTransactionState = OrderHelper::AUTHORIZE_STATUS;
$orderTransactionStateToCheck = OrderHelper::TRANSACTION_AUTHORIZED;
$orderTransactionAction = OrderHelper::TRANSACTION_PAID;
$amountToCheck = 'pendingAmount';
break;
case OrderHelper::REFUND_STATUS:
$lunarTransactionState = OrderHelper::CAPTURE_STATUS;
$orderTransactionStateToCheck = OrderHelper::TRANSACTION_PAID;
$orderTransactionAction = OrderHelper::TRANSACTION_REFUND;
$amountToCheck = 'capturedAmount';
break;
case OrderHelper::VOID_STATUS:
$lunarTransactionState = OrderHelper::AUTHORIZE_STATUS;
$orderTransactionStateToCheck = OrderHelper::TRANSACTION_AUTHORIZED;
$orderTransactionAction = OrderHelper::TRANSACTION_VOID;
$amountToCheck = 'pendingAmount';
break;
}

$actionType = ucfirst($actionType);
$actionTypeAllCaps = strtoupper($actionType);

$params = $request->request->get('params');
$orderId = $params['orderId'];
$lunarTransactionId = $params['lunarTransactionId'];

try {
$order = $this->orderHelper->getOrderById($orderId, $context);

$lastOrderTransaction = $order->transactions->last();

$transactionStateName = $lastOrderTransaction->getStateMachineState()->technicalName;

/**
* Get lunar transaction
*/
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('orderId', $orderId));
$criteria->addFilter(new EqualsFilter('transactionType', $lunarTransactionState));

$lunarTransaction = $this->lunarTransactionRepository->search($criteria, $context)->first();

if (!$lunarTransaction || $orderTransactionStateToCheck != $transactionStateName) {
return new JsonResponse([
'status' => false,
'message' => 'Error',
'code' => 0,
'errors'=> [$actionType . ' failed. Not lunar transaction or payment not ' . $orderTransactionStateToCheck],
], 400);
}

/**
* Instantiate Api Client
* Fetch transaction
* Check amount & currency
* Proceed with payment capture
*/
$privateApiKey = $this->getApiKey($order);
$apiClient = new ApiClient($privateApiKey);
$fetchedTransaction = $apiClient->transactions()->fetch($lunarTransactionId);

if (!$fetchedTransaction) {
return new JsonResponse([
'status' => false,
'message' => 'Error',
'code' => 0,
'errors'=> ['Fetch transaction failed'],
], 400);
}

$totalPrice = $lastOrderTransaction->amount->getTotalPrice();
$currencyCode = $order->getCurrency()->isoCode;
$amountInMinor = (int) CurrencyHelper::getAmountInMinor($currencyCode, $totalPrice);

if ($fetchedTransaction['amount'] !== $amountInMinor) {
$errors[] = 'Fetch transaction failed: amount mismatch';
}
if ($fetchedTransaction['currency'] !== $currencyCode) {
$errors[] = 'Fetch transaction failed: currency mismatch';
}

if ($fetchedTransaction[$amountToCheck] !== $amountInMinor) {
$errors[] = 'Fetch transaction failed: ' . $amountToCheck . ' mismatch';
}

$transactionData = [
'amount' => $amountInMinor,
'currency' => $currencyCode,
];

$result['successful'] = false;

/**
* API Transaction call: capture/refund/void
*/
$result = $apiClient->transactions()->{$actionType}($lunarTransactionId, $transactionData);

$this->logger->writeLog([$actionTypeAllCaps . ' request data: ', $transactionData]);

if (true !== $result['successful']) {
$this->logger->writeLog([$actionTypeAllCaps . ' error (admin): ', $result]);
$errors[] = $actionType . ' transaction api action failed';
}


$lastLunarOperation = end($result['trail']);
$transactionAmount = $lastLunarOperation['amount'];
$transactionAmount = CurrencyHelper::getAmountInMajor($currencyCode, $transactionAmount);

$transactionData = [
[
'orderId' => $orderId,
'transactionId' => $lunarTransactionId,
'transactionType' => strtolower($actionType),
'transactionCurrency' => $currencyCode,
'orderAmount' => $totalPrice,
'transactionAmount' => $transactionAmount,
'amountInMinor' => $amountInMinor,
'createdAt' => date(Defaults::STORAGE_DATE_TIME_FORMAT),
],
];


/** Change order transaction state. */
$this->transactionStateHandler->{$orderTransactionAction}($lastOrderTransaction->id, $context);

/** Change order state. */
OrderHelper::changeOrderState($orderId, strtolower($actionType), $context, $this->stateMachineRegistry);


/** Insert new data to database and log it. */
$this->lunarTransactionRepository->create($transactionData, $context);

$this->logger->writeLog(['Succes: ', $transactionData[0]]);

} catch (\Exception $e) {
$errors[] = 'An exception occured. Please try again. If this persist please contact plugin developer.';
$this->logger->writeLog(['EXCEPTION ' . $actionType . ' (admin): ', $e->getMessage()]);

/** Fail order transaction. */
$this->transactionStateHandler->fail($lastOrderTransaction->id, $context);
}

if (!empty($errors)) {
return new JsonResponse([
'status' => empty($errors),
'message' => 'Error',
'code' => 0,
'errors'=> $errors ?? [],
], 400);
}

return new JsonResponse([
'status' => empty($errors),
'message' => 'Success',
'code' => 0,
'errors' => $errors ?? [],
], 200);
}

/**
*
*/
private function getApiKey($order)
{
$salesChannelId = $order->getSalesChannelId();

$transactionMode = $this->systemConfigService->get(self::CONFIG_PATH . 'transactionMode', $salesChannelId);

if ($transactionMode == 'test') {
return $this->systemConfigService->get(self::CONFIG_PATH . 'testModeAppKey', $salesChannelId);
}

return $this->systemConfigService->get(self::CONFIG_PATH . 'liveModeAppKey', $salesChannelId);
}

/**
* FETCH TRANSACTIONS
*
* @Route("/api/lunar/fetch-transactions", name="api.lunar.fetch-transactions", methods={"POST"})
*/
public function fetchTransactions(Request $request, Context $context): JsonResponse
{
$errors = [];
$orderId = $request->request->get('params')['orderId'];

try {
/**
* Check transaction registered in custom table
*/
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('orderId', $orderId));
$criteria->addSorting(new FieldSorting('createdAt', FieldSorting::ASCENDING));

$lunarTransactions = $this->lunarTransactionRepository->search($criteria, $context);

} catch (\Exception $e) {
$errors[] = $e->getMessage();
}

if (!empty($errors)) {
return new JsonResponse([
'status' => empty($errors),
'message' => 'Error',
'code' => 0,
'errors' => $errors,
], 404);
}

return new JsonResponse([
'status' => empty($errors),
'message' => 'Success',
'code' => 0,
'errors' => $errors,
'transactions' => $lunarTransactions->getElements(),
], 200);
}
}
Loading

0 comments on commit 0d92168

Please sign in to comment.