284 lines
11 KiB
PHP
284 lines
11 KiB
PHP
<?php
|
|
|
|
use girosolution\GiroCheckout_SDK\GiroCheckout_SDK_Notify;
|
|
use girosolution\GiroCheckout_SDK\GiroCheckout_SDK_Request;
|
|
use KupShop\KupShopBundle\Config;
|
|
|
|
/**
|
|
* composer require girosolution/girocheckout-sdk.
|
|
*
|
|
* https://github.com/girosolution/girocheckout_sdk
|
|
* http://api.girocheckout.de/en:girocheckout:giropay:start
|
|
*/
|
|
class GiroCheckoutGiropay extends Payment
|
|
{
|
|
public static $name = 'GiroCheckout Giropay';
|
|
|
|
public $template = 'payment.OmnipayNestpay.tpl';
|
|
// protected $templateCart = 'payment.GiroCheckout.cart.tpl';
|
|
|
|
public $class = 'GiroCheckoutGiropay';
|
|
|
|
protected $methodCode = 'giropayTransaction';
|
|
|
|
public $method;
|
|
|
|
protected $pay_method = Payment::METHOD_ONLINE;
|
|
|
|
// public function storePaymentInfo()
|
|
// {
|
|
// $data = parent::storePaymentInfo();
|
|
// $data['method'] = explode('-', getVal('payment_id'))[1];
|
|
//
|
|
// return $data;
|
|
// }
|
|
|
|
public function getPaymentUrl()
|
|
{
|
|
return createScriptURL([
|
|
's' => 'payment',
|
|
'IDo' => $this->order->id,
|
|
'cf' => $this->order->getSecurityCode(),
|
|
'step' => 1,
|
|
'class' => $this->class,
|
|
]);
|
|
}
|
|
|
|
/* Payment steps */
|
|
public function processStep_1()
|
|
{
|
|
$returnUrl = createScriptURL([
|
|
's' => 'payment',
|
|
'IDo' => $this->order->id,
|
|
'cf' => $this->order->getSecurityCode(),
|
|
'step' => 5,
|
|
'class' => $this->class,
|
|
'absolute' => true,
|
|
]);
|
|
$webhookUrl = createScriptURL([
|
|
's' => 'payment',
|
|
'IDo' => $this->order->id,
|
|
'cf' => $this->order->getSecurityCode(),
|
|
'step' => 10,
|
|
'class' => $this->class,
|
|
'absolute' => true,
|
|
]);
|
|
|
|
$method = $this->determinePaymentMethod();
|
|
$methodSpecificConfig = $this->getMethodSpecificConfig($method);
|
|
|
|
$amount = roundPrice($this->order->getRemainingPayment())->asFloat();
|
|
$amountInCents = roundPrice($this->order->getRemainingPayment())->mul(DecimalConstants::hundred())->asInteger();
|
|
$paymentIDSuffix = uniqid('', true);
|
|
$this->createPayment(
|
|
null,
|
|
$amount,
|
|
['paymentClass' => static::class, 'method' => $method, 'IDSuffix' => $paymentIDSuffix]
|
|
);
|
|
|
|
// save session (use our identifier that we send as merchantTxId)
|
|
$session = $this->paymentId.'-'.$paymentIDSuffix;
|
|
$this->updateCustomPaymentData('session', $session);
|
|
|
|
// create request of selected type
|
|
$request = new GiroCheckout_SDK_Request($method);
|
|
$request->setSecret($methodSpecificConfig['projectPassphrase']); // String
|
|
$request->addParam('merchantId', $methodSpecificConfig['merchantID']) // Integer merchant ID of a giropay project
|
|
->addParam('projectId', $methodSpecificConfig['projectID']) // integer project ID of a giropay project
|
|
->addParam('merchantTxId', $session) // String(255) unique transaction id of the merchant
|
|
->addParam('amount', $amountInCents) // Integer if a decimal currency is used, the amount has to be in the smallest unit of value, eg. Cent, Penny
|
|
->addParam('currency', 'EUR') // String(3)
|
|
->addParam('purpose', 'Bestellung '.$this->orderId) // String(27)
|
|
// bic - This parameter must not be used anymore. All giropay transactions now use an external bank selection form!
|
|
->addParam('urlRedirect', $returnUrl) // URL, where the buyer has to be sent after payment
|
|
->addParam('urlNotify', $webhookUrl); // URL, where the notification has to be sent after payment
|
|
|
|
$request = $this->addRequestParams($request);
|
|
|
|
// the hash field is auto generated by the SDK
|
|
$request->submit();
|
|
|
|
if ($request->requestHasSucceeded()) {
|
|
// save gcReference (giropay request param "reference")
|
|
$this->updateCustomPaymentData(
|
|
'gcReference',
|
|
$request->getResponseParam('reference')
|
|
);
|
|
|
|
$request->redirectCustomerToPaymentProvider();
|
|
} else {
|
|
// if the transaction did not succeed, update your local system, get the responsecode and notify the customer
|
|
throw new Exception($request->getResponseMessage($request->getResponseParam('rc'), 'DE'));
|
|
}
|
|
}
|
|
|
|
protected function addRequestParams(GiroCheckout_SDK_Request $request): GiroCheckout_SDK_Request
|
|
{
|
|
return $request;
|
|
}
|
|
|
|
/**
|
|
* @param string $paymentIdentifier kupshopPaymentID-uniqueSuffix
|
|
*/
|
|
private function getPaymentByUniqueIdentifier(string $paymentIdentifier, bool $ignoreStatus = false): ?array
|
|
{
|
|
foreach ($this->order->getPaymentsArray() as $payment) {
|
|
$paymentData = json_decode($payment['payment_data']);
|
|
if (($payment['status'] == static::STATUS_CREATED || $payment['status'] == static::STATUS_PENDING || $ignoreStatus)
|
|
&& isset($paymentData->paymentClass)
|
|
&& $paymentData->paymentClass === static::class
|
|
&& $payment['id'] == explode('-', $paymentIdentifier)[0]
|
|
&& $paymentData->IDSuffix == (explode('-', $paymentIdentifier)[1] ?? null)
|
|
) {
|
|
$payment['decoded_data'] = $paymentData;
|
|
|
|
return $payment;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected function updateCustomPaymentData($keyName, $value)
|
|
{
|
|
$paymentRow = sqlQueryBuilder()->select('*')->from('order_payments')
|
|
->where(\Query\Operator::equals(['id' => $this->paymentId]))->execute()->fetch();
|
|
$data = json_decode($paymentRow['payment_data'] ?? '{}');
|
|
$data->$keyName = $value;
|
|
sqlQueryBuilder()->update('order_payments')
|
|
->directValues(['payment_data' => json_encode($data)])
|
|
->where(\Query\Operator::equals(['id' => $this->paymentId]))->execute();
|
|
}
|
|
|
|
public function processStep_5()
|
|
{
|
|
$method = $this->determinePaymentMethod();
|
|
$methodSpecificConfig = $this->getMethodSpecificConfig($method);
|
|
|
|
$notify = new GiroCheckout_SDK_Notify($method);
|
|
$notify->setSecret($methodSpecificConfig['projectPassphrase']);
|
|
$notify->parseNotification($_GET);
|
|
|
|
$payment = $this->getPaymentByUniqueIdentifier($notify->getResponseParam('gcMerchantTxId'), true);
|
|
if (is_null($payment) || $payment['status'] == static::STATUS_STORNO) {
|
|
$this->step(-3, 'storno');
|
|
} elseif ($payment['status'] == static::STATUS_FINISHED) {
|
|
$this->success(translate('paymentSuccess', 'payment'));
|
|
}
|
|
|
|
$this->paymentId = $payment['id'];
|
|
$this->saveTransactionData($notify, $payment);
|
|
|
|
if ($notify->paymentSuccessful()) {
|
|
// change payment status to finished
|
|
if (!$this->setStatus(Payment::STATUS_FINISHED, $payment['decoded_data']->session)) {
|
|
throw new Exception('GiroCheckout::setStatus failed!');
|
|
}
|
|
|
|
$this->success(translate('paymentSuccess', 'payment'));
|
|
} else {
|
|
if (!$this->setStatus(Payment::STATUS_STORNO, $payment['decoded_data']->session)) {
|
|
throw new Exception('GiroCheckout::setStatus failed!');
|
|
}
|
|
|
|
$this->step(-3, 'storno');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GiroPurchase webhook handler - check for approved payment.
|
|
*/
|
|
public function processStep_10()
|
|
{
|
|
$this->setIsNotification(true);
|
|
$method = $this->determinePaymentMethod();
|
|
$methodSpecificConfig = $this->getMethodSpecificConfig($method);
|
|
|
|
$notify = new GiroCheckout_SDK_Notify($method);
|
|
$notify->setSecret($methodSpecificConfig['projectPassphrase']);
|
|
$notify->parseNotification($_GET);
|
|
|
|
$payment = $this->getPaymentByUniqueIdentifier($notify->getResponseParam('gcMerchantTxId'));
|
|
if (is_null($payment)) {
|
|
// send #400: The merchant did not process the notification and does not wish to be notified again.
|
|
$notify->sendBadRequestStatus();
|
|
exit;
|
|
}
|
|
|
|
$this->paymentId = $payment['id'];
|
|
$this->saveTransactionData($notify, $payment);
|
|
|
|
if ($notify->paymentSuccessful()) {
|
|
// change payment status to finished
|
|
if (!$this->setStatus(Payment::STATUS_FINISHED, $payment['decoded_data']->session)) {
|
|
throw new Exception('GiroCheckout::setStatus failed!');
|
|
}
|
|
|
|
// send #200: The notification was processed correctly.
|
|
$notify->sendOkStatus();
|
|
exit;
|
|
} else {
|
|
if (!$this->setStatus(Payment::STATUS_STORNO, $payment['decoded_data']->session)) {
|
|
throw new Exception('GiroCheckout::setStatus failed!');
|
|
}
|
|
|
|
// send #200: The notification was processed correctly.
|
|
$notify->sendOkStatus();
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// public function getAvailableMethods(): array
|
|
// {
|
|
// $availableMethods = [];
|
|
// foreach ($this->config['methods'] as $requestType => $methodConfig) {
|
|
// $availableMethods[$requestType] = ['name' => $methodConfig['name'] ?? $requestType];
|
|
// }
|
|
//
|
|
// return $availableMethods;
|
|
// }
|
|
|
|
/**
|
|
* determine payment method (request type) with fallback to first available method.
|
|
*/
|
|
private function determinePaymentMethod(): string
|
|
{
|
|
// $method = $this->order->getDataAll()['payment_data']['method'] ?? null;
|
|
// $methods = $this->getAvailableMethods();
|
|
// reset($methods);
|
|
//
|
|
// return isset($methods[$method]) ? $method : key($methods);
|
|
return $this->methodCode;
|
|
}
|
|
|
|
private function getMethodSpecificConfig(?string $method = null): array
|
|
{
|
|
// return $this->config['methods'][$method ?? $this->determinePaymentMethod()];
|
|
return $this->config;
|
|
}
|
|
|
|
private function saveTransactionData(GiroCheckout_SDK_Notify $notify, array $payment): void
|
|
{
|
|
// save transaction reference ID and result code
|
|
$json = $payment['decoded_data'];
|
|
$json->resultCode = $notify->getResponseParam('gcResultPayment'); // http://api.girocheckout.de/en:girocheckout:resultcodes
|
|
$this->updateSQL('order_payments', ['payment_data' => json_encode($json)], ['id' => $payment['id']]);
|
|
}
|
|
|
|
public function hasOnlinePayment()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public static function isEnabled($className)
|
|
{
|
|
$cfg = Config::get();
|
|
|
|
if (empty($cfg['Modules']['payments'][$className])) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|