Files
clinicpro/tests/Payment/PaymentCallbackAmountTest.php

105 lines
3.4 KiB
PHP

<?php
namespace App\Tests\Payment;
use App\Config\Entity\SiteConfig;
use App\Payment\Entity\Payment;
use App\Tests\ApiTestCase;
/**
* The payment callback must confirm an order only when the gateway-reported
* settled amount matches what we charged. Guards against underpayment / a
* replayed RefNum from another (cheaper) order marking an expensive order paid.
*/
class PaymentCallbackAmountTest extends ApiTestCase
{
private function enableTestMode(): void
{
$cfg = $this->em->getRepository(SiteConfig::class)->findOneBy(['configKey' => 'payment_test_mode']);
if ($cfg === null) {
$cfg = new SiteConfig('payment_test_mode', '1');
$this->em->persist($cfg);
} else {
$cfg->setValue('1');
}
$this->em->flush();
}
private function makePayment(int $amountRials): Payment
{
$user = $this->createUser();
$payment = $this->stampTenant(new Payment($user, $amountRials, 'mock', Payment::TYPE_SMS_WALLET));
$this->em->persist($payment);
$this->em->flush();
return $payment;
}
private function fireCallback(Payment $payment, int $reportedAmount): void
{
$this->client->request('POST', '/api/v1/payment/callback?' . http_build_query([
'gateway' => 'mock',
'order_id' => $payment->getOrderId(),
'mock' => '1',
'ResCode' => '0',
'mock_amount' => (string) $reportedAmount,
]));
}
private function reload(Payment $payment): Payment
{
$this->em->clear();
return $this->em->getRepository(Payment::class)->find($payment->getId());
}
public function testUnderpaymentIsRejected(): void
{
$this->enableTestMode();
$payment = $this->makePayment(50000);
$this->fireCallback($payment, 10000);
$this->assertSame(Payment::STATUS_FAILED, $this->reload($payment)->getStatus());
}
public function testMatchingAmountSucceeds(): void
{
$this->enableTestMode();
$payment = $this->makePayment(50000);
$this->fireCallback($payment, 50000);
$this->assertSame(Payment::STATUS_SUCCESS, $this->reload($payment)->getStatus());
}
public function testReplayedGatewayReferenceIsRejected(): void
{
$this->enableTestMode();
// Unique per run — db_test is shared and not reset between runs.
$ref = 'REF-' . bin2hex(random_bytes(8));
$first = $this->makePayment(50000);
$this->fireCallbackWithRef($first, $ref, 50000);
$this->assertSame(Payment::STATUS_SUCCESS, $this->reload($first)->getStatus());
// Same gateway reference replayed onto a different (same-amount) order.
$second = $this->makePayment(50000);
$this->fireCallbackWithRef($second, $ref, 50000);
$this->assertSame(Payment::STATUS_FAILED, $this->reload($second)->getStatus());
}
private function fireCallbackWithRef(Payment $payment, string $refId, int $reportedAmount): void
{
$this->client->request('POST', '/api/v1/payment/callback?' . http_build_query([
'gateway' => 'mock',
'order_id' => $payment->getOrderId(),
'mock' => '1',
'ResCode' => '0',
'RefId' => $refId,
'mock_amount' => (string) $reportedAmount,
]));
}
}