Skip to content

Commit

Permalink
Merge pull request #202 from mewebstudio/191-akbank-yeni-api-destegi
Browse files Browse the repository at this point in the history
191 akbankpos history request destegi
  • Loading branch information
nuryagdym authored Apr 26, 2024
2 parents c8b470c + 5e42b54 commit c340a1c
Show file tree
Hide file tree
Showing 29 changed files with 14,312 additions and 74 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';
4 changes: 2 additions & 2 deletions src/Crypt/AbstractCrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public function generateRandomString(int $length = 24): string
$charactersLength = \strlen($characters);
$randomString = '';

for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[\rand(0, $charactersLength - 1)];
for ($i = 0; $i < $length; ++$i) {
$randomString .= $characters[random_int(0, $charactersLength - 1)];
}

return $randomString;
Expand Down
1 change: 1 addition & 0 deletions src/Crypt/AkbankPosCrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public function hashString(string $str, ?string $encryptionKey = null): string
if (null === $encryptionKey) {
throw new \LogicException('Encryption key zorunlu!');
}

$str = \hash_hmac(static::HASH_ALGORITHM, $str, $encryptionKey, true);

return \base64_encode($str);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public function isSupportedTxType(string $txType, ?string $paymentModel = null):
if (!isset($this->txTypeMappings[$txType])) {
return false;
}

if (\is_array($this->txTypeMappings[$txType])) {
if (null === $paymentModel) {
return false;
Expand Down
27 changes: 25 additions & 2 deletions 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 Expand Up @@ -355,6 +377,7 @@ public function create3DFormData(AbstractPosAccount $posAccount, array $order, s
if (null !== $posAccount->getSubMerchantId()) {
$inputs['subMerchantId'] = $posAccount->getSubMerchantId();
}

if ($creditCard instanceof CreditCardInterface) {
$inputs['creditCard'] = $creditCard->getNumber();
$inputs['expiredDate'] = $creditCard->getExpirationDate(self::CREDIT_CARD_EXP_DATE_FORMAT);
Expand Down Expand Up @@ -482,7 +505,7 @@ protected function mapCurrency(string $currency): int
*
* @param array<string, mixed> $data
*
* @return array{batch_num: int}|array{start_date: DateTimeInterface, end_date: DateTimeInterface}
* @return array{batch_num?: int, start_date?: DateTimeInterface, end_date?: DateTimeInterface}
*/
protected function prepareHistoryOrder(array $data): array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ public function create3DFormData(AbstractPosAccount $posAccount, array $order, s
'Amount' => (string) $this->formatAmount($order['amount']),
'CurrencyCode' => (string) $this->mapCurrency($order['currency']),
'MerchantReturnURL' => (string) $order['success_url'],
'InstallmentCount' => (string) $this->mapInstallment($order['installment']),
'InstallmentCount' => $this->mapInstallment($order['installment']),
'Language' => $this->getLang($posAccount, $order),
'TxnState' => 'INITIAL',
'OpenNewWindow' => '0',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function mapTxType($txType): ?string
if (\is_array($mapping) && \in_array($txType, $mapping, true)) {
return $mappedTxType;
}

if ($mapping === $txType) {
return $mappedTxType;
}
Expand Down
148 changes: 125 additions & 23 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 @@ -284,21 +289,26 @@ public function mapOrderHistoryResponse(array $rawResponseData): array
} else {
$transactions[] = $this->mapSingleOrderHistoryTransaction($rawTx);
}
$orderId = $orderId ?? $rawTx['orgOrderId'] ?? $rawTx['orderId'] ?? null;

$orderId ??= $rawTx['orgOrderId'] ?? $rawTx['orderId'] ?? null;
}
}

if (!$isRecurringOrder) {
\usort($transactions, function (array $tx1, array $tx2) {
\usort($transactions, static function (array $tx1, array $tx2) {
if (null !== $tx1['transaction_time'] && null === $tx2['transaction_time']) {
return 1;
}

if (null === $tx1['transaction_time'] && null !== $tx2['transaction_time']) {
return -1;
}

if ($tx1['transaction_time'] == $tx2['transaction_time']) {
return 0;
} elseif ($tx1['transaction_time'] < $tx2['transaction_time']) {
}

if ($tx1['transaction_time'] < $tx2['transaction_time']) {
return -1;
}

Expand All @@ -324,7 +334,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 @@ -413,32 +451,40 @@ private function mapSingleOrderHistoryTransaction(array $rawTx): array
}

$transaction['status_detail'] = $this->getStatusDetail($transaction['proc_return_code']);
$transaction['ref_ret_num'] = $rawTx['rrn'];
$transaction['masked_number'] = $rawTx['maskedCardNumber'];
$transaction['currency'] = $this->mapCurrency($rawTx['currencyCode']);
$transaction['installment_count'] = $this->mapInstallment($rawTx['installCount']);
$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['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']);
if (PosInterface::PAYMENT_STATUS_PAYMENT_COMPLETED === $transaction['order_status']) {
if (\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']);
}
} elseif (PosInterface::TX_TYPE_PAY_PRE_AUTH === $transaction['transaction_type']) {
$transaction['capture_amount'] = null === $rawTx['preAuthCloseAmount'] ? null : $this->formatAmount($rawTx['preAuthCloseAmount']);
$transaction['capture'] = $transaction['first_amount'] === $transaction['capture_amount'];
if ($transaction['capture']) {
$transaction['capture_time'] = new \DateTimeImmutable($rawTx['preAuthCloseDate']);
}
}
}
} else {
Expand Down Expand Up @@ -479,6 +525,7 @@ private function mapSingleRecurringOrderHistoryTransaction(array $rawTx): array
if (PosInterface::PAYMENT_STATUS_PAYMENT_PENDING !== $transaction['order_status']) {
$transaction['transaction_time'] = new \DateTimeImmutable($rawTx['txnDateTime']);
}

if (PosInterface::PAYMENT_STATUS_PAYMENT_COMPLETED === $transaction['order_status']) {
$transaction['batch_num'] = $rawTx['batchNumber'];
$transaction['ref_ret_num'] = $rawTx['rrn'];
Expand All @@ -493,6 +540,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
Loading

0 comments on commit c340a1c

Please sign in to comment.