first commit

This commit is contained in:
2025-08-02 16:30:27 +02:00
commit 23646bfcee
14851 changed files with 1750626 additions and 0 deletions

View File

@@ -0,0 +1,283 @@
<?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;
}
}