Skip to content

Commit

Permalink
Merge pull request #217 from mewebstudio/204-kuveytturk-partial-refun…
Browse files Browse the repository at this point in the history
…d-support

issue #204 kuveytturk partial refund support
  • Loading branch information
nuryagdym authored May 24, 2024
2 parents 74ccce7 + c6eb253 commit 52f7b43
Show file tree
Hide file tree
Showing 29 changed files with 317 additions and 243 deletions.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
"escapestudios/symfony2-coding-standard": "^3.11",
"monolog/monolog": "^2.8",
"php-http/curl-client": "^2.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan": "^1.11",
"phpstan/phpstan-strict-rules": "^1.4",
"phpunit/phpunit": "^9",
"rector/rector": "^1.0",
"rector/rector": "^1.1",
"slim/psr7": "^1.4",
"squizlabs/php_codesniffer": "^3.5",
"symfony/event-dispatcher": "^5.4",
Expand Down
13 changes: 13 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Changelog
## [1.3.0] - 2024-05-24

### New Features
- **KuveytPos** kısmi iade desteği eklendi. (issue #205)
- **EstPos/Payten** ön otorizasyonu kapatırken daha fazla miktar desteği eklendi. (issue #171)

### Changed
- Dispatched event'lere **gatewayClass**, **order**, **paymenModel** verileri eklendi.

### Fixed
- **PosNet** - 3D Secure ödeme başarısız olduğunda `3d_all` boş olması giderildi.

# Changelog
## [1.2.0] - 2024-05-19

Expand Down
24 changes: 17 additions & 7 deletions docs/REFUND-EXAMPLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,18 @@ try {

require 'config.php';

function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip): array
function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip, ?float $refundAmount = null): array
{
$refundOrder = [
'id' => $lastResponse['order_id'], // MerchantOrderId
'amount' => $lastResponse['amount'],
'currency' => $lastResponse['currency'],
'ref_ret_num' => $lastResponse['ref_ret_num'],
'ip' => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $ip : '127.0.0.1',
'id' => $lastResponse['order_id'], // MerchantOrderId
'amount' => $refundAmount ?? $lastResponse['amount'],

// toplam siparis tutari, kismi iade mi ya da tam iade mi oldugunu anlamak icin kullanilir.
'order_amount' => $lastResponse['amount'],

'currency' => $lastResponse['currency'],
'ref_ret_num' => $lastResponse['ref_ret_num'],
'ip' => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $ip : '127.0.0.1',
];

if (\Mews\Pos\Gateways\KuveytPos::class === $gatewayClass) {
Expand Down Expand Up @@ -89,8 +93,14 @@ function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip

// odemeden aldiginiz cevap: $pos->getResponse();
$lastResponse = $session->get('last_response');

// tam iade:
$refundAmount = $lastResponse['amount'];
// kismi iade:
$refundAmount = $lastResponse['amount'] - 2;

$ip = '127.0.0.1';
$order = createRefundOrder(get_class($pos), $lastResponse, $ip);
$order = createRefundOrder(get_class($pos), $lastResponse, $ip, $refundAmount);

$pos->refund($order);
$response = $pos->getResponse();
Expand Down
27 changes: 20 additions & 7 deletions examples/_common-codes/regular/refund.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@

require '../../_templates/_header.php';

function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip): array
function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip, ?float $refundAmount = null): array
{
$refundOrder = [
'id' => $lastResponse['order_id'], // MerchantOrderId
'amount' => $lastResponse['amount'],
'currency' => $lastResponse['currency'],
'ref_ret_num' => $lastResponse['ref_ret_num'],
'ip' => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $ip : '127.0.0.1',
'id' => $lastResponse['order_id'], // MerchantOrderId
'amount' => $refundAmount ?? $lastResponse['amount'],

// toplam siparis tutari, kismi iade mi ya da tam iade mi oldugunu anlamak icin kullanilir.
'order_amount' => $lastResponse['amount'],

'currency' => $lastResponse['currency'],
'ref_ret_num' => $lastResponse['ref_ret_num'],
'ip' => filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $ip : '127.0.0.1',
];

if (\Mews\Pos\Gateways\KuveytPos::class === $gatewayClass) {
Expand Down Expand Up @@ -53,8 +57,17 @@ function createRefundOrder(string $gatewayClass, array $lastResponse, string $ip
return $refundOrder;
}

$lastResponse = $session->get('last_response');
// kismi iade:
$refundAmount = $lastResponse['amount'] - 2;


$order = createRefundOrder(get_class($pos), $session->get('last_response'), $ip);
$order = createRefundOrder(
get_class($pos),
$lastResponse,
$ip,
$refundAmount
);
dump($order);

$transaction = PosInterface::TX_TYPE_REFUND;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ public function createCancelRequestData(AbstractPosAccount $posAccount, array $o
}

/**
* Eğer kısmi tutarlı iade işlemi yapılmak isteniyorsa, iade işlemi requestinde transaction alanı gönderilmelidir.
* Eğer transaction alanı gönderilmezse, iade işlemi tam tutarlı olarak gerçekleşecektir.
*
* @param AkbankPosAccount $posAccount
*
* {@inheritDoc}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ public function createCancelRequestData(AbstractPosAccount $posAccount, array $o
'RRN' => $order['ref_ret_num'],
'Stan' => $order['transaction_id'],
'ProvisionNumber' => $order['auth_code'],
'TransactionType' => 0,
'VPosMessage' => $this->getRequestAccountData($posAccount) + [
'APIVersion' => self::API_VERSION,
'InstallmentMaturityCommisionFlag' => 0,
Expand Down Expand Up @@ -314,6 +313,9 @@ public function createRefundRequestData(AbstractPosAccount $posAccount, array $o
{
$order = $this->prepareRefundOrder($order);

$isPartialRefund = isset($order['order_amount']) && $order['order_amount'] > $order['amount'];
$txType = $isPartialRefund ? 'PartialDrawback' : $this->mapTxType(PosInterface::TX_TYPE_REFUND);

$result = [
'IsFromExternalNetwork' => true,
'BusinessKey' => 0,
Expand All @@ -328,15 +330,14 @@ public function createRefundRequestData(AbstractPosAccount $posAccount, array $o
'RRN' => $order['ref_ret_num'],
'Stan' => $order['transaction_id'],
'ProvisionNumber' => $order['auth_code'],
'TransactionType' => 0,
'VPosMessage' => $this->getRequestAccountData($posAccount) + [
'APIVersion' => self::API_VERSION,
'InstallmentMaturityCommisionFlag' => 0,
'HashData' => '',
'SubMerchantId' => 0,
'CardType' => $this->cardTypeMapping[CreditCardInterface::CARD_TYPE_VISA], //Default gönderilebilir.
'BatchID' => 0,
'TransactionType' => $this->mapTxType(PosInterface::TX_TYPE_REFUND),
'TransactionType' => $txType,
'InstallmentCount' => 0,
'Amount' => $this->formatAmount($order['amount']),
'DisplayAmount' => 0,
Expand Down Expand Up @@ -465,6 +466,7 @@ protected function prepareRefundOrder(array $order): array
'auth_code' => $order['auth_code'],
'transaction_id' => $order['transaction_id'],
'amount' => $order['amount'],
'order_amount' => $order['order_amount'] ?? null,
'currency' => $order['currency'] ?? PosInterface::CURRENCY_TRY,
]);
}
Expand Down
13 changes: 7 additions & 6 deletions src/Gateways/KuveytPos.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ public function make3DPayment(Request $request, array $order, string $txType, Cr
*
* @return array<string, mixed>
*
* @throws UnsupportedTransactionTypeException
* @throws SoapFault
*/
protected function send($contents, string $txType, string $paymentModel, string $url): array
Expand Down Expand Up @@ -244,7 +243,6 @@ protected function send($contents, string $txType, string $paymentModel, string
*
* @throws SoapFault
* @throws RuntimeException
* @throws UnsupportedTransactionTypeException
*/
private function sendSoapRequest(array $contents, string $txType, string $url): array
{
Expand Down Expand Up @@ -276,13 +274,16 @@ private function sendSoapRequest(array $contents, string $txType, string $url):

$client = new SoapClient($url, $options);
try {
$result = $client->__soapCall($this->requestDataMapper->mapTxType($txType), ['parameters' => ['request' => $contents]]);
} catch (SoapFault $throwable) {
$result = $client->__soapCall(
$contents['VPosMessage']['TransactionType'],
['parameters' => ['request' => $contents]]
);
} catch (SoapFault $soapFault) {
$this->logger->error('soap error response', [
'message' => $throwable->getMessage(),
'message' => $soapFault->getMessage(),
]);

throw $throwable;
throw $soapFault;
}

if (null === $result) {
Expand Down
72 changes: 59 additions & 13 deletions tests/Functional/KuveytPosTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThro
}

/**
* @depends testRefundFail
* @depends testFullRefundFail
*/
public function testCancelSuccess(array $lastResponse): array
{
Expand All @@ -183,7 +183,7 @@ public function testCancelSuccess(array $lastResponse): array
function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void {
$eventIsThrown = true;
$this->assertSame(PosInterface::TX_TYPE_CANCEL, $requestDataPreparedEvent->getTxType());
$this->assertCount(15, $requestDataPreparedEvent->getRequestData());
$this->assertCount(14, $requestDataPreparedEvent->getRequestData());
});

$this->pos->cancel($statusOrder);
Expand Down Expand Up @@ -224,34 +224,80 @@ function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThro
return $lastResponse;
}

public function testNonSecurePaymentSuccessForRefundTest(): array
{
$order = $this->createPaymentOrder(PosInterface::MODEL_NON_SECURE);

$this->pos->payment(
PosInterface::MODEL_NON_SECURE,
$order,
PosInterface::TX_TYPE_PAY_AUTH,
$this->card
);

$this->assertTrue($this->pos->isSuccess());

return $this->pos->getResponse();
}

// /**
// * @depends testNonSecurePaymentSuccessForRefundTest
// */
// public function testFullRefundFail(array $lastResponse): array
// {
// $refundOrder = $this->createRefundOrder(\get_class($this->pos), $lastResponse);
//
// $eventIsThrown = false;
// $this->eventDispatcher->addListener(
// RequestDataPreparedEvent::class,
// function (RequestDataPreparedEvent $requestDataPreparedEvent) use (&$eventIsThrown): void {
// $eventIsThrown = true;
// $this->assertSame(PosInterface::TX_TYPE_REFUND, $requestDataPreparedEvent->getTxType());
// $this->assertCount(14, $requestDataPreparedEvent->getRequestData());
// });
//
// $this->pos->refund($refundOrder);
//
// $this->assertFalse($this->pos->isSuccess());
// $response = $this->pos->getResponse();
// $this->assertIsArray($response);
// $this->assertNotEmpty($response);
// $this->assertTrue($eventIsThrown);
// $this->assertSame(
// 'İade işlemi, satışla aynı gün içerisinde yapılamaz. İptal işlemi yapabilirsiniz.',
// $response['error_message']
// );
//
// return $lastResponse;
// }

/**
* @depends testNonSecurePaymentSuccess
* @depends testNonSecurePaymentSuccessForRefundTest
*/
public function testRefundFail(array $lastResponse): array
public function testPartialRefundSuccess(array $lastResponse): array
{
$refundOrder = $this->createRefundOrder(\get_class($this->pos), $lastResponse);
$refundOrder['amount'] = 1.0;
$refundOrder = $this->createRefundOrder(
\get_class($this->pos),
$lastResponse,
$lastResponse['amount'] - 3,
);

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

$this->pos->refund($refundOrder);

$this->assertFalse($this->pos->isSuccess());
$response = $this->pos->getResponse();

$this->assertTrue($this->pos->isSuccess(), $response['error_message'] ?? 'error');
$this->assertIsArray($response);
$this->assertNotEmpty($response);
$this->assertTrue($eventIsThrown);
$this->assertSame(
'İade işlemi, satışla aynı gün içerisinde yapılamaz. İptal işlemi yapabilirsiniz.',
$response['error_message']
);

return $lastResponse;
}
Expand Down
13 changes: 7 additions & 6 deletions tests/Functional/PaymentTestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,15 @@ private function createHistoryOrder(string $gatewayClass, array $extraData): arr
return [];
}

private function createRefundOrder(string $gatewayClass, array $lastResponse): array
private function createRefundOrder(string $gatewayClass, array $lastResponse, ?float $refundAmount = null): array
{
$refundOrder = [
'id' => $lastResponse['order_id'], // MerchantOrderId
'amount' => $lastResponse['amount'],
'currency' => $lastResponse['currency'],
'ref_ret_num' => $lastResponse['ref_ret_num'],
'ip' => '127.0.0.1',
'id' => $lastResponse['order_id'], // MerchantOrderId
'amount' => $refundAmount ?? $lastResponse['amount'],
'order_amount' => $lastResponse['amount'],
'currency' => $lastResponse['currency'],
'ref_ret_num' => $lastResponse['ref_ret_num'],
'ip' => '127.0.0.1',
];

if (\Mews\Pos\Gateways\KuveytPos::class === $gatewayClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,11 @@ public function testGet3DFormData(array $order, string $txType, string $paymentM

$this->dispatcher->expects(self::once())
->method('dispatch')
->with($this->callback(function ($dispatchedEvent) use ($txType, $paymentModel) {
return $dispatchedEvent instanceof Before3DFormHashCalculatedEvent
&& AkbankPos::class === $dispatchedEvent->getGatewayClass()
&& $txType === $dispatchedEvent->getTxType()
&& $paymentModel === $dispatchedEvent->getPaymentModel()
&& count($dispatchedEvent->getFormInputs()) > 3
;
}));
->with($this->callback(static fn($dispatchedEvent): bool => $dispatchedEvent instanceof Before3DFormHashCalculatedEvent
&& AkbankPos::class === $dispatchedEvent->getGatewayClass()
&& $txType === $dispatchedEvent->getTxType()
&& $paymentModel === $dispatchedEvent->getPaymentModel()
&& count($dispatchedEvent->getFormInputs()) > 3));

$actual = $this->requestDataMapper->create3DFormData(
$this->account,
Expand Down Expand Up @@ -213,14 +210,11 @@ public function testGet3DFormDataSubMerchant(): void

$this->dispatcher->expects(self::once())
->method('dispatch')
->with($this->callback(function ($dispatchedEvent) use ($txType, $paymentModel) {
return $dispatchedEvent instanceof Before3DFormHashCalculatedEvent
&& AkbankPos::class === $dispatchedEvent->getGatewayClass()
&& $txType === $dispatchedEvent->getTxType()
&& $paymentModel === $dispatchedEvent->getPaymentModel()
&& count($dispatchedEvent->getFormInputs()) > 3
;
}));
->with($this->callback(static fn($dispatchedEvent): bool => $dispatchedEvent instanceof Before3DFormHashCalculatedEvent
&& AkbankPos::class === $dispatchedEvent->getGatewayClass()
&& $txType === $dispatchedEvent->getTxType()
&& $paymentModel === $dispatchedEvent->getPaymentModel()
&& count($dispatchedEvent->getFormInputs()) > 3));

$actual = $this->requestDataMapper->create3DFormData(
$this->subMerchantAccount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,11 @@ public function testGet3DFormData(

$this->dispatcher->expects(self::once())
->method('dispatch')
->with($this->callback(function ($dispatchedEvent) use ($txType, $paymentModel) {
return $dispatchedEvent instanceof Before3DFormHashCalculatedEvent
&& EstPos::class === $dispatchedEvent->getGatewayClass()
&& $txType === $dispatchedEvent->getTxType()
&& $paymentModel === $dispatchedEvent->getPaymentModel()
&& count($dispatchedEvent->getFormInputs()) > 3
;
}));
->with($this->callback(static fn($dispatchedEvent): bool => $dispatchedEvent instanceof Before3DFormHashCalculatedEvent
&& EstPos::class === $dispatchedEvent->getGatewayClass()
&& $txType === $dispatchedEvent->getTxType()
&& $paymentModel === $dispatchedEvent->getPaymentModel()
&& count($dispatchedEvent->getFormInputs()) > 3));

$actual = $this->requestDataMapper->create3DFormData(
$this->account,
Expand Down
Loading

0 comments on commit 52f7b43

Please sign in to comment.