Skip to content

Commit

Permalink
issue #191 akbankpos - added support for history transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
nuryagdym committed Apr 26, 2024
1 parent 707869a commit 7744892
Show file tree
Hide file tree
Showing 19 changed files with 14,251 additions and 29 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sistemlerinin kullanılabilmesidir.
| Gateway | Desktekleyen<br/>bankalar | Desteklenen<br/>Ödeme Tipleri | Desteklenen Sorgular |
|-------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|---------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| Tosla (AKÖde) | ? | NonSecure<br/>3DPay<br/>3DHost | İptal<br/>İade<br/>Durum sorgulama<br/>Sipariş Tarihçesini sorgulama |
| AkbankPos | Akbank | NonSecure<br/>3DSecur<br/>3DPay<br/>3DHost<br/>Tekrarlanan Ödeme | İptal<br/>İade<br/>Sipariş Tarihçesini sorgulama |
| AkbankPos | Akbank | NonSecure<br/>3DSecur<br/>3DPay<br/>3DHost<br/>Tekrarlanan Ödeme | İptal<br/>İade<br/>Sipariş Tarihçesini sorgulama<br/>Geçmiş İşlemleri sorgulama |
| EST POS<br/>(Asseco/Payten)<br/>_deprecated_ | Akbank<br/>TEB<br/>İşbank<br/>Şekerbank<br/>Halkbank<br/>Finansbank<br/>Ziraat | NonSecure<br/>3DSecure<br/>3DPay<br/>3DHost<br/>3DPayHost<br/>Tekrarlanan Ödeme | İptal<br/>İade<br/>Durum sorgulama<br/>Sipariş Tarihçesini sorgulama |
| EST V3 POS<br/><br/>EstPos altyapının<br/>daha güvenli<br/>(sha512) hash<br/>algoritmasıyla<br/>uygulaması. | -----"----- | -----"----- | -----"----- |
| PayFlex MPI VPOS V4 | Ziraat<br/>Vakıfbank<br/>İşbank | NonSecure<br/>3DSecure<br/>Tekrarlanan Ödeme | İptal<br/>İade<br/>Durum sorgulama |
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"ext-json": "*",
"ext-libxml": "*",
"ext-openssl": "*",
"ext-zlib": "*",
"php-http/discovery": "^1.14",
"psr/event-dispatcher-implementation": "*",
"psr/http-client-implementation": "*",
Expand Down
11 changes: 11 additions & 0 deletions docs/HISTORY-EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ function createHistoryOrder(string $gatewayClass, array $extraData): array
'start_date' => $txTime->modify('-1 day'),
'end_date' => $txTime->modify('+1 day'),
];
} elseif (\Mews\Pos\Gateways\AkbankPos::class === $gatewayClass) {
$txTime = new \DateTimeImmutable();
$order = [
// Gün aralığı 1 günden fazla girilemez
'start_date' => $txTime->modify('-23 hour'),
'end_date' => $txTime,
];
// ya da batch number ile (batch number odeme isleminden alinan response'da bulunur):
// $order = [
// 'batch_num' => 24,
// ];
}

return $order;
Expand Down
11 changes: 11 additions & 0 deletions examples/_common-codes/regular/history.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ function createHistoryOrder(string $gatewayClass, array $extraData): array
'start_date' => $txTime->modify('-1 day'),
'end_date' => $txTime->modify('+1 day'),
];
} elseif (\Mews\Pos\Gateways\AkbankPos::class === $gatewayClass) {
$txTime = new \DateTimeImmutable();
$order = [
// Gün aralığı 1 günden fazla girilemez
'start_date' => $txTime->modify('-23 hour'),
'end_date' => $txTime,
];
// ya da batch number ile (batch number odeme isleminden alinan response'da bulunur):
// $order = [
// 'batch_num' => 24,
// ];
}

return $order;
Expand Down
3 changes: 3 additions & 0 deletions examples/akbankpos/regular/history.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

require '../../_common-codes/regular/history.php';
24 changes: 23 additions & 1 deletion src/DataMapper/RequestDataMapper/AkbankPosRequestDataMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,33 @@ public function createOrderHistoryRequestData(AbstractPosAccount $posAccount, ar
}

/**
* İşlem cevabında, sadece 9999 adet işlem sorgulanabilir.
* Tarih aralığında, 9999 adet işlemden daha fazla işlem olması durumunda,
* “VPS-2235” - "Toplam kayıt sayısı aşıldı. Batch No girerek ilerleyiniz.” hatası verilecektir.
*
* @param AkbankPosAccount $posAccount
*
* {@inheritDoc}
*/
public function createHistoryRequestData(AbstractPosAccount $posAccount, array $data = []): array
{
throw new NotImplementedException();
$order = $this->prepareHistoryOrder($data);

$requestData = $this->getRequestAccountData($posAccount) + [
'randomNumber' => $this->crypt->generateRandomString(),
];
if (isset($order['batch_num'])) {
$requestData['report'] = [
'batchNumber' => $order['batch_num'],
];
} elseif (isset($order['start_date']) && isset($order['end_date'])) {
$requestData['report'] = [
'startDateTime' => $this->formatRequestDateTime($order['start_date']),
'endDateTime' => $this->formatRequestDateTime($order['end_date']),
];
}

return $requestData;
}

/**
Expand Down
98 changes: 93 additions & 5 deletions src/DataMapper/ResponseDataMapper/AkbankPosResponseDataMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ class AkbankPosResponseDataMapper extends AbstractResponseDataMapper
* @var array<string, PosInterface::PAYMENT_STATUS_*>
*/
private array $orderStatusMappings = [
'N' => PosInterface::PAYMENT_STATUS_PAYMENT_COMPLETED,
'S' => PosInterface::PAYMENT_STATUS_ERROR,
'V' => PosInterface::PAYMENT_STATUS_CANCELED,
'R' => PosInterface::PAYMENT_STATUS_FULLY_REFUNDED,
'N' => PosInterface::PAYMENT_STATUS_PAYMENT_COMPLETED,
'S' => PosInterface::PAYMENT_STATUS_ERROR,
'V' => PosInterface::PAYMENT_STATUS_CANCELED,
'R' => PosInterface::PAYMENT_STATUS_FULLY_REFUNDED,

// status that are return on history request
'Başarılı' => PosInterface::PAYMENT_STATUS_PAYMENT_COMPLETED,
'Başarısız' => PosInterface::PAYMENT_STATUS_ERROR,
'İptal' => PosInterface::PAYMENT_STATUS_CANCELED,
];

/**
Expand Down Expand Up @@ -324,7 +329,35 @@ public function mapOrderHistoryResponse(array $rawResponseData): array
*/
public function mapHistoryResponse(array $rawResponseData): array
{
throw new NotImplementedException();
$rawResponseData = $this->emptyStringsToNull($rawResponseData);

$mappedTransactions = [];
$procReturnCode = $this->getProcReturnCode($rawResponseData);
$status = self::TX_DECLINED;
if (self::PROCEDURE_SUCCESS_CODE === $procReturnCode) {
$status = self::TX_APPROVED;
foreach ($rawResponseData['data']['txnDetailList'] as $rawTx) {
$mappedTransactions[] = $this->mapSingleHistoryTransaction($rawTx);
}
}

$result = [
'proc_return_code' => $procReturnCode,
'error_code' => null,
'error_message' => null,
'status' => $status,
'status_detail' => null !== $procReturnCode ? $this->getStatusDetail($procReturnCode) : null,
'trans_count' => \count($mappedTransactions),
'transactions' => $mappedTransactions,
'all' => $rawResponseData,
];

if (null !== $procReturnCode && self::PROCEDURE_SUCCESS_CODE !== $procReturnCode) {
$result['error_code'] = $procReturnCode;
$result['error_message'] = $rawResponseData['responseMessage'];
}

return $result;
}

/**
Expand Down Expand Up @@ -501,6 +534,61 @@ private function mapSingleRecurringOrderHistoryTransaction(array $rawTx): array
return $transaction;
}

/**
* @param array<string, string|null> $rawTx
*
* @return array<string, int|string|null|float|bool|\DateTimeImmutable>
*
* @throws \Exception
*/
private function mapSingleHistoryTransaction(array $rawTx): array
{
$rawTx = $this->emptyStringsToNull($rawTx);
$transaction = $this->getDefaultOrderHistoryTxResponse();
$transaction['proc_return_code'] = $this->getProcReturnCode($rawTx);
if (self::PROCEDURE_SUCCESS_CODE === $transaction['proc_return_code']) {
$transaction['status'] = self::TX_APPROVED;
}

$transaction['order_id'] = null;
$transaction['status_detail'] = $this->getStatusDetail($transaction['proc_return_code']);

$transaction['currency'] = $this->mapCurrency($rawTx['currencyCode']);
$transaction['installment_count'] = $this->mapInstallment($rawTx['installmentCount']);
$transaction['transaction_type'] = $this->mapTxType($rawTx['txnCode']);
$transaction['first_amount'] = null === $rawTx['amount'] ? null : $this->formatAmount($rawTx['amount']);
$transaction['transaction_time'] = new \DateTimeImmutable($rawTx['txnDateTime']);

if (self::TX_APPROVED === $transaction['status']) {
$transaction['order_id'] = $rawTx['orderId'];
$transaction['masked_number'] = $rawTx['maskedCardNumber'];
$transaction['ref_ret_num'] = $rawTx['rrn'];
// batchNumber is not provided when payment is canceled
$transaction['batch_num'] = $rawTx['batchNumber'] ?? null;
$transaction['order_status'] = $this->mapOrderStatus($rawTx['txnStatus'], $rawTx['preAuthStatus'] ?? null);
$transaction['auth_code'] = $rawTx['authCode'];
if (PosInterface::PAYMENT_STATUS_PAYMENT_COMPLETED === $transaction['order_status'] && \in_array(
$transaction['transaction_type'],
[
PosInterface::TX_TYPE_PAY_AUTH,
PosInterface::TX_TYPE_PAY_POST_AUTH,
],
true,
)
) {
$transaction['capture_amount'] = null === $rawTx['amount'] ? null : $this->formatAmount($rawTx['amount']);
$transaction['capture'] = $transaction['first_amount'] === $transaction['capture_amount'];
if ($transaction['capture']) {
$transaction['capture_time'] = new \DateTimeImmutable($rawTx['txnDateTime']);
}
}
} else {
$transaction['error_code'] = $transaction['proc_return_code'];
}

return $transaction;
}

/**
* @phpstan-param PosInterface::MODEL_3D_* $paymentModel
*
Expand Down
10 changes: 1 addition & 9 deletions src/Gateways/AkbankPos.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class AkbankPos extends AbstractGateway
PosInterface::TX_TYPE_CANCEL => true,
PosInterface::TX_TYPE_REFUND => true,
PosInterface::TX_TYPE_ORDER_HISTORY => true,
PosInterface::TX_TYPE_HISTORY => false,
PosInterface::TX_TYPE_HISTORY => true,
];

/**
Expand Down Expand Up @@ -170,14 +170,6 @@ public function status(array $order): PosInterface
throw new UnsupportedTransactionTypeException();
}

/**
* @inheritDoc
*/
public function history(array $data): PosInterface
{
throw new UnsupportedTransactionTypeException();
}

/**
* @inheritDoc
*
Expand Down
40 changes: 39 additions & 1 deletion src/Serializer/AkbankPosSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
namespace Mews\Pos\Serializer;

use Mews\Pos\Gateways\AkbankPos;
use Mews\Pos\PosInterface;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Serializer;

Expand Down Expand Up @@ -39,6 +40,43 @@ public function encode(array $data, ?string $txType = null): string
*/
public function decode(string $data, ?string $txType = null): array
{
return $this->serializer->decode($data, JsonEncoder::FORMAT);
if ('' === $data) {
return [];
}
$decodedData = $this->serializer->decode($data, JsonEncoder::FORMAT);

if (PosInterface::TX_TYPE_HISTORY === $txType && isset($decodedData['data'])) {
$decompressedData = $this->decompress($decodedData['data']);
$decodedData['data'] = \json_decode($decompressedData, true);
}

return $decodedData;
}

/**
* @param string $data
*
* @return string json string
*/
private function decompress(string $data): string
{
$decodedData = \base64_decode($data);
$gzipStream = gzopen('data://application/octet-stream;base64,'.base64_encode($decodedData), 'rb');

if (!$gzipStream) {
return '';
}
$decompressedData = '';
$i = 0;
while (!gzeof($gzipStream)) {
$i++;
if ($i > 1000000) {
throw new \RuntimeException('Invalid history data');
}
$decompressedData .= gzread($gzipStream, 1024);
}
gzclose($gzipStream);

return $decompressedData;
}
}
45 changes: 45 additions & 0 deletions tests/Functional/AkbankPosTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,27 @@ function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThro
$this->assertTrue($eventIsThrown);
}

public function testHistorySuccess(): void
{
$historyOrder = $this->createHistoryOrder(\get_class($this->pos), []);

$eventIsThrown = false;
$this->eventDispatcher->addListener(
RequestDataPreparedEvent::class,
function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void {
$eventIsThrown = true;
$this->assertSame(PosInterface::TX_TYPE_HISTORY, $requestDataPreparedEvent->getTxType());
$this->assertCount(3, $requestDataPreparedEvent->getRequestData());
});

$this->pos->history($historyOrder);

$response = $this->pos->getResponse();
$this->assertIsArray($response);
$this->assertTrue($eventIsThrown);
$this->assertNotEmpty($response['transactions']);
}

public function testNonSecurePrePaymentSuccess(): array
{
$order = $this->createPaymentOrder(PosInterface::CURRENCY_TRY, 30.0, 3);
Expand Down Expand Up @@ -356,4 +377,28 @@ function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThro
$this->assertNotEmpty($response);
$this->assertTrue($eventIsThrown);
}

/**
* @depends testCancelRecurringOrder
*/
public function testRecurringHistorySuccess(): void
{
$historyOrder = $this->createHistoryOrder(\get_class($this->pos), []);

$eventIsThrown = false;
$this->eventDispatcher->addListener(
RequestDataPreparedEvent::class,
function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void {
$eventIsThrown = true;
$this->assertSame(PosInterface::TX_TYPE_HISTORY, $requestDataPreparedEvent->getTxType());
$this->assertCount(3, $requestDataPreparedEvent->getRequestData());
});

$this->recurringPos->history($historyOrder);

$response = $this->recurringPos->getResponse();
$this->assertIsArray($response);
$this->assertTrue($eventIsThrown);
$this->assertNotEmpty($response['transactions']);
}
}
14 changes: 14 additions & 0 deletions tests/Functional/PaymentTestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,20 @@ private function createHistoryOrder(string $gatewayClass, array $extraData): arr
];
}

if (\Mews\Pos\Gateways\AkbankPos::class === $gatewayClass) {
$txTime = new \DateTimeImmutable();

return [
// Gün aralığı 1 günden fazla girilemez
'start_date' => $txTime->modify('-23 hour'),
'end_date' => $txTime,
];
// ya da batch number ile (batch number odeme isleminden alinan response'da bulunur):
// $order = [
// 'batch_num' => 24,
// ];
}

return [];
}

Expand Down
Loading

0 comments on commit 7744892

Please sign in to comment.