400 lines
16 KiB
PHP
400 lines
16 KiB
PHP
<?php
|
|
|
|
use Adyen\Webhook\Exception\AuthenticationException;
|
|
use Adyen\Webhook\Receiver\HmacSignature;
|
|
use Adyen\Webhook\Receiver\NotificationReceiver;
|
|
use KupShop\AdminBundle\Util\ActivityLog;
|
|
use KupShop\BankAutoPaymentBundle\BankAutoPaymentBundle;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\OrderingBundle\Exception\PaymentException;
|
|
use KupShop\OrderingBundle\Util\Order\PaymentOrderSubMethodTrait;
|
|
|
|
/**
|
|
* "adyen/php-api-library": "^15.2.0",
|
|
* "adyen/php-webhook-module": "^0.8.0".
|
|
*/
|
|
class Adyen extends Payment
|
|
{
|
|
use PaymentOrderSubMethodTrait;
|
|
|
|
public static $name = 'Adyen platební brána';
|
|
|
|
public static bool $canAutoReturn = true;
|
|
|
|
protected $templateCart = 'payment.Adyen.cart.tpl';
|
|
|
|
protected $templateOrderView = 'payment.Adyen.orderView.tpl';
|
|
|
|
public $class = 'Adyen';
|
|
|
|
protected $pay_method = Payment::METHOD_ONLINE;
|
|
|
|
protected $method;
|
|
|
|
public function createAdyenSession()
|
|
{
|
|
$session = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService('session');
|
|
$adyenSession = $session->get('adyen_'.$this->orderId);
|
|
// Aby když mě to přesměruje na detail a nestihl přijit webhook, naukázal se mi znova platební widget
|
|
if ($adyenSession['paymentResult'] == \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED) {
|
|
return false;
|
|
}
|
|
|
|
// Když chci změnit metodu, tak tohle přeskočit
|
|
if (!getVal('rpm')) {
|
|
// Pokud mám sessionId a není starší pěti minut, použiji ho.
|
|
if ($adyenSession['sessionId'] && $adyenSession['expired'] > time() - (5 * 60)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$service = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());
|
|
|
|
$createCheckoutSessionRequest = new \Adyen\Model\Checkout\CreateCheckoutSessionRequest();
|
|
$createCheckoutSessionRequest->setCountryCode(Contexts::get(\KupShop\KupShopBundle\Context\CountryContext::class)->getActiveId())
|
|
->setMerchantAccount($this->config['merchantAccount'])
|
|
->setReturnUrl($this->getGenericPaymentUrl(5))
|
|
->setReference($this->order->order_no.'_'.time())
|
|
->setShopperEmail($this->order->getUserEmail())
|
|
->setShopperLocale($this->getLocale());
|
|
|
|
$amount = new \Adyen\Model\Checkout\Amount();
|
|
$amount->setValue($this->order->getRemainingPayment(true)->mul(DecimalConstants::hundred())->asInteger())
|
|
->setCurrency($this->order->getCurrency());
|
|
|
|
$createCheckoutSessionRequest->setAmount($amount);
|
|
|
|
$this->kibanaLogger->notice('[Adyen] Create session', [
|
|
'order' => $this->order->order_no,
|
|
'amount' => $amount->getValue(),
|
|
]);
|
|
|
|
if ($this->method && !getVal('rpm')) {
|
|
$adyenMethods = [$this->method];
|
|
} else {
|
|
$methods = $this->getAvailableMethods($amount);
|
|
$adyenMethods = array_values(array_map(fn ($val) => $val['type'], $methods));
|
|
}
|
|
$createCheckoutSessionRequest->setAllowedPaymentMethods($adyenMethods);
|
|
|
|
$items = [];
|
|
foreach ($this->order->fetchItems() as $item) {
|
|
$items[] = (new \Adyen\Model\Checkout\LineItem())
|
|
->setQuantity($item['pieces'])
|
|
->setTaxPercentage($item['vat'])
|
|
->setDescription($item['descr'])
|
|
->setAmountIncludingTax($item['total_price']['value_with_vat']->div(toDecimal($item['pieces']))->mul(DecimalConstants::hundred())->asInteger());
|
|
}
|
|
$createCheckoutSessionRequest->setLineItems($items);
|
|
|
|
$adyenSessionRequestResult = $service->sessions($createCheckoutSessionRequest);
|
|
$session->set('adyen_'.$this->orderId, [
|
|
'sessionId' => $adyenSessionRequestResult->getId(),
|
|
'sessionData' => $adyenSessionRequestResult->getSessionData(),
|
|
'expired' => time(),
|
|
'orderId' => $this->orderId,
|
|
]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Return from gateway */
|
|
public function processStep_5()
|
|
{
|
|
$sessionId = $this->request->get('sessionId');
|
|
$sessionResult = $this->request->get('sessionResult');
|
|
|
|
if ($sessionResult) {
|
|
$result = $this->getSessionStatus($sessionId, $sessionResult);
|
|
}
|
|
|
|
// Z informace redirectResult vubec nic nedostanu. Takze proste zakaznikovi zakazu znova zaplatit a bude se cekat az vysledek dorazi pres webhook.
|
|
if (($result ?? false) == \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED || $this->request->get('redirectResult')) {
|
|
$adyenSession = $this->request->getSession()->get('adyen_'.$this->orderId) ?: [];
|
|
$this->request->getSession()->set('adyen_'.$this->orderId, $adyenSession + ['paymentResult' => \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED]);
|
|
}
|
|
|
|
$this->success(translate('paymentSuccess', 'payment'));
|
|
}
|
|
|
|
public function getSessionStatus($sessionId, $sessionResult)
|
|
{
|
|
$paymentsApi = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());
|
|
$sessionResultResponse = $paymentsApi->getResultOfPaymentSession($sessionId, ['queryParams' => ['sessionResult' => $sessionResult]]);
|
|
|
|
return $sessionResultResponse->getStatus();
|
|
}
|
|
|
|
public function startPayment()
|
|
{
|
|
$pathData = ['id' => $this->order->id];
|
|
if (!\User::getCurrentUser()) {
|
|
$pathData['cf'] = $this->order->getSecurityCode();
|
|
}
|
|
redirection(path('payment-redirect', $pathData));
|
|
}
|
|
|
|
public function processStep_1()
|
|
{
|
|
redirection($this->order->getDetailUrl());
|
|
}
|
|
|
|
/** Return from gateway */
|
|
public function processStep_10()
|
|
{
|
|
$request = json_decode($this->request->getContent() ?? '', true);
|
|
|
|
if (empty($request['notificationItems'])) {
|
|
$this->error('Not found !!');
|
|
}
|
|
|
|
// Setup NotificationReceiver with dependency injection or create an instance as follows
|
|
$notificationReceiver = new NotificationReceiver(new HmacSignature());
|
|
|
|
$_SERVER['PHP_AUTH_USER'] = $this->request->headers->get('php-auth-user');
|
|
$_SERVER['PHP_AUTH_PW'] = $this->request->headers->get('php-auth-pw');
|
|
|
|
if (!$notificationReceiver->isAuthenticated(
|
|
$request['notificationItems'][0]['NotificationRequestItem'],
|
|
$this->config['merchantAccount'],
|
|
$this->config['basic_name'],
|
|
$this->config['basic_pass']
|
|
)) {
|
|
throw new AuthenticationException('Incoming webhook wasn\'t authenticated!');
|
|
}
|
|
|
|
foreach ($request['notificationItems'] as $notificationItem) {
|
|
$notifItem = $notificationItem['NotificationRequestItem'];
|
|
|
|
if ($notificationReceiver->validateHmac($notifItem, $this->config['hmac']) && in_array($notifItem['eventCode'], ['AUTHORISATION', 'REFUND'])) {
|
|
$isRefund = ($notifItem['eventCode'] == 'REFUND');
|
|
$message = ($isRefund ? '[Adyen] Refund payment' : '[Adyen] Incoming payment');
|
|
$this->kibanaLogger->notice($message, [
|
|
'notificationItem' => $notifItem,
|
|
]);
|
|
|
|
$merchantReference = $notifItem['merchantReference'];
|
|
$ref = explode('_', $merchantReference);
|
|
$id_order = ($ref[0] ?? false);
|
|
|
|
if ($notifItem['success'] == 'true') {
|
|
if ($id_order) {
|
|
try {
|
|
$order = Order::createFromDbOrderNo($id_order);
|
|
$payment_data = [
|
|
'paymentClass' => self::class,
|
|
'session' => $notifItem['additionalData']['checkoutSessionId'] ?? null,
|
|
'pspReference' => $notifItem['pspReference'] ?? null,
|
|
];
|
|
$price = $notifItem['amount']['value'] / 100;
|
|
$note = "Platba modulu {$this->class}";
|
|
if ($isRefund) {
|
|
$price *= -1;
|
|
$note = "Vrácení platby: objednávka {$order->order_no}, ID platby: {$notifItem['originalReference']}";
|
|
$payment_data['originalReference'] = $notifItem['originalReference'];
|
|
}
|
|
$order?->insertPayment($price, $note, method: self::METHOD_ONLINE, payment_data: json_encode($payment_data));
|
|
|
|
if ($order && !empty($notifItem['paymentMethod'])) {
|
|
$this->setPaymentSubMethod($notifItem['paymentMethod'], $order);
|
|
}
|
|
} catch (InvalidArgumentException $exception) {
|
|
getRaven()->captureException($exception);
|
|
}
|
|
}
|
|
} else {
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION,
|
|
"Objednávka {$id_order}: {$message} error: {$notifItem['reason']}", $notifItem, [BankAutoPaymentBundle::LOG_TAG_ADYEN]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->sendNotificationResponse(200, '[accepted]');
|
|
}
|
|
|
|
public function doReturnPayment(array $payment, float $amount)
|
|
{
|
|
if ($paymentPspReference = $payment['payment_data']['pspReference'] ?? null) {
|
|
$class = new \Adyen\Service\Checkout\ModificationsApi($this->getApiClient());
|
|
$paymentRefundRequest = new \Adyen\Model\Checkout\PaymentRefundRequest();
|
|
$paymentRefundRequest->setMerchantAccount($this->config['merchantAccount']);
|
|
$paymentRefundRequest->setReference($this->order->order_no.'_'.time());
|
|
$amountCents = (int) floor($amount * -100); // musí být integer v centech
|
|
$amount = new \Adyen\Model\Checkout\Amount();
|
|
$amount->setValue($amountCents)->setCurrency($this->order->getCurrency());
|
|
$paymentRefundRequest->setAmount($amount);
|
|
$paymentRefundResponse = $class->refundCapturedPayment($paymentPspReference, $paymentRefundRequest);
|
|
if ($paymentRefundResponse->getStatus() == 'received') {
|
|
// the refund request was successfully received by Adyen - no result yet,
|
|
// because you receive the outcome of the refund request asynchronously, in a REFUND webhook.
|
|
|
|
// Vrácení platby bylo odesláno na platební bránu.
|
|
throw new PaymentException(translate('returnSucceed', 'orderPayment'), 'Adyen');
|
|
}
|
|
}
|
|
|
|
// Vrácení platby se nezdařilo, vyřešte, prosím, na platební bráně.);
|
|
throw new PaymentException(translate('returnFailed', 'orderPayment'));
|
|
}
|
|
|
|
public function getApiClient(): Adyen\Client
|
|
{
|
|
if (!isset($this->apiClient)) {
|
|
$config = new \Adyen\Config();
|
|
$testEnv = (isDevelopment() || ($this->config['test'] ?? '0') == '1');
|
|
|
|
if (!$testEnv) {
|
|
$config->set('prefix', $this->config['api_urls_prefix']);
|
|
}
|
|
|
|
$client = new \Adyen\Client($config);
|
|
|
|
$client->setXApiKey($this->config['apikey']);
|
|
$client->setEnvironment($testEnv ? \Adyen\Environment::TEST : \Adyen\Environment::LIVE);
|
|
$client->setTimeout(5);
|
|
$this->apiClient = $client;
|
|
}
|
|
|
|
return $this->apiClient;
|
|
}
|
|
|
|
/**
|
|
* @throws \Adyen\AdyenException
|
|
*/
|
|
public function getAvailableMethods(Adyen\Model\Checkout\Amount|Decimal|null $amount = null)
|
|
{
|
|
$countryContext = Contexts::get(\KupShop\KupShopBundle\Context\CountryContext::class);
|
|
$currencyContext = Contexts::get(\KupShop\KupShopBundle\Context\CurrencyContext::class);
|
|
$currency = $currencyContext->getActiveId();
|
|
$country = $countryContext->getActiveId();
|
|
$cacheKey = "adyen-methods-{$currency}-{$country}-{$this->getLocale()}";
|
|
if (!$amount) {
|
|
if ($paymentMethods = getCache($cacheKey)) {
|
|
return $paymentMethods;
|
|
}
|
|
}
|
|
|
|
try {
|
|
$class = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());
|
|
|
|
$paymentMethodsRequest = new \Adyen\Model\Checkout\PaymentMethodsRequest();
|
|
$paymentMethodsRequest->setMerchantAccount($this->config['merchantAccount']);
|
|
$paymentMethodsRequest->setCountryCode($countryContext->getActiveId());
|
|
$paymentMethodsRequest->setShopperLocale($this->getLocale());
|
|
|
|
if ($amount instanceof Decimal) {
|
|
$amount = (new \Adyen\Model\Checkout\Amount())->setValue($amount->mul(DecimalConstants::hundred())->asInteger())->setCurrency($currencyContext->getActiveId());
|
|
}
|
|
|
|
if ($amount) {
|
|
$paymentMethodsRequest->setAmount($amount);
|
|
}
|
|
|
|
$paymentMethodsResponse = $class->paymentMethods($paymentMethodsRequest);
|
|
|
|
$paymentMethods = [];
|
|
foreach ($paymentMethodsResponse->getPaymentMethods() as $method) {
|
|
if ($method->getType() == 'scheme') {
|
|
$images = array_map(function ($brand) {
|
|
return "https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/{$brand}.svg";
|
|
}, $method->getBrands());
|
|
} else {
|
|
$images = ["https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/{$method->getType()}.svg"];
|
|
}
|
|
|
|
$paymentMethods[$method->getType()] = [
|
|
'name' => $method->getName(),
|
|
'type' => $method->getType(),
|
|
'images' => $images,
|
|
];
|
|
}
|
|
} catch (Exception $e) {
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION,
|
|
'Chyba Adyen při načítání platebních metod: '.$e->getMessage(),
|
|
['key' => $cacheKey, 'config' => $this->config], [BankAutoPaymentBundle::LOG_TAG_ADYEN]);
|
|
|
|
return [];
|
|
}
|
|
|
|
if (!$amount) {
|
|
setCache($cacheKey, $paymentMethods);
|
|
}
|
|
|
|
return $paymentMethods;
|
|
}
|
|
|
|
public function hasOnlinePayment()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public static function getSettingsConfiguration(): array
|
|
{
|
|
return ['fields' => [
|
|
'apikey' => [
|
|
'title' => 'API klíč',
|
|
'type' => 'text',
|
|
],
|
|
'merchantAccount' => [
|
|
'title' => 'Merchant Account',
|
|
'type' => 'text',
|
|
],
|
|
'clientKey' => [
|
|
'title' => 'Client Key',
|
|
'type' => 'text',
|
|
],
|
|
'hmac' => [
|
|
'title' => 'HMAC key',
|
|
'type' => 'text',
|
|
],
|
|
'api_urls_prefix' => [
|
|
'title' => 'API URLs prefix',
|
|
'type' => 'text',
|
|
],
|
|
'basic_name' => [
|
|
'title' => 'Basic auth name',
|
|
'type' => 'text',
|
|
],
|
|
'basic_pass' => [
|
|
'title' => 'Basic auth pass',
|
|
'type' => 'text',
|
|
],
|
|
'test' => [
|
|
'title' => 'Testovací režim',
|
|
'type' => 'toggle',
|
|
],
|
|
]];
|
|
}
|
|
|
|
public function getLocale()
|
|
{
|
|
$languageContext = Contexts::get(\KupShop\KupShopBundle\Context\LanguageContext::class);
|
|
|
|
return $languageContext->getActive()->getLocale();
|
|
}
|
|
|
|
// private function checkWebhooks()
|
|
// {
|
|
// Maybe later alligator
|
|
//
|
|
// $merchantAccount = $this->config['merchantAccount'];
|
|
// $webhooksApi = new \Adyen\Service\Management\WebhooksMerchantLevelApi($this->getApiClient());
|
|
// $allWebhooks = $webhooksApi->listAllWebhooks($merchantAccount);
|
|
//
|
|
// $webhooks = $allWebhooks->getData();
|
|
//
|
|
// if ($webhooks) {
|
|
// return true;
|
|
// }
|
|
//
|
|
// $createMerchantWebhookRequest = new \Adyen\Model\Management\CreateMerchantWebhookRequest();
|
|
// $createMerchantWebhookRequest->setUrl(path('kupshop_ordering_payment_legacypayment', ['class' => 'Adyen', 'step' => 10], \Symfony\Component\Routing\Router::ABSOLUTE_URL))
|
|
// ->setDescription('API - wpjshop')
|
|
// ->setType('standard')
|
|
// ->setCommunicationFormat('json');
|
|
//
|
|
// $webhooksApi->setUpWebhook($merchantAccount, $createMerchantWebhookRequest);
|
|
// }
|
|
}
|