Files
clinicpro/tests/Payment/UnifiedPaymentCallbackTest.php

126 lines
4.3 KiB
PHP

<?php
namespace App\Tests\Payment;
use App\Config\Entity\SiteConfig;
use App\Payment\Entity\Payment;
use App\Payment\Service\PaymentManager;
use App\Tests\ApiTestCase;
/**
* Every gateway and every payment type shares one callback path. The gateway
* and the order travel as query params so the URL registered with the bank
* never has to change.
*/
class UnifiedPaymentCallbackTest 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(string $type, string $gateway = 'mock'): Payment
{
$payment = $this->stampTenant(new Payment($this->createUser(), 50000, $gateway, $type));
$this->em->persist($payment);
$this->em->flush();
return $payment;
}
private function reload(Payment $payment): Payment
{
$this->em->clear();
return $this->em->getRepository(Payment::class)->find($payment->getId());
}
/** @param array<string, string> $extra */
private function fireCallback(Payment $payment, array $extra = []): void
{
$this->client->request('POST', PaymentManager::CALLBACK_PATH . '?' . http_build_query(array_merge([
'order_id' => $payment->getOrderId(),
'mock' => '1',
'ResCode' => '0',
'mock_amount' => '50000',
], $extra)));
}
/** @return array<string, array{string}> */
public static function paymentTypeProvider(): array
{
return [
'appointment' => [Payment::TYPE_APPOINTMENT],
'subscription' => [Payment::TYPE_SUBSCRIPTION],
'sms wallet' => [Payment::TYPE_SMS_WALLET],
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('paymentTypeProvider')]
public function testEveryPaymentTypeUsesTheSameCallbackPath(string $type): void
{
$this->enableTestMode();
$payment = $this->makePayment($type);
$url = static::getContainer()->get(PaymentManager::class)->callbackUrl($payment);
$this->assertStringContainsString(PaymentManager::CALLBACK_PATH . '?', $url);
$this->assertStringNotContainsString('/subscription-payment/', $url);
$this->assertStringContainsString('gateway=mock', $url);
$this->assertStringContainsString('order_id=' . $payment->getOrderId(), $url);
}
public function testCallbackAcceptsGatewayAsQueryParam(): void
{
$this->enableTestMode();
$payment = $this->makePayment(Payment::TYPE_SMS_WALLET);
$this->fireCallback($payment, ['gateway' => 'mock']);
$this->assertSame(Payment::STATUS_SUCCESS, $this->reload($payment)->getStatus());
}
public function testCallbackFallsBackToTheGatewayStoredOnThePayment(): void
{
$this->enableTestMode();
$payment = $this->makePayment(Payment::TYPE_SMS_WALLET);
$this->fireCallback($payment);
$this->assertSame(Payment::STATUS_SUCCESS, $this->reload($payment)->getStatus());
}
public function testUnknownOrderWithoutGatewayIsNotFound(): void
{
$this->enableTestMode();
$this->client->request('POST', PaymentManager::CALLBACK_PATH . '?' . http_build_query([
'order_id' => 'ORD-DOES-NOT-EXIST',
'mock' => '1',
'ResCode' => '0',
]));
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
$this->assertStringContainsString('یافت نشد', (string) $this->client->getResponse()->getContent());
}
public function testRemovedLegacyCallbackRoutesReturn404(): void
{
$this->enableTestMode();
$payment = $this->makePayment(Payment::TYPE_SUBSCRIPTION);
$query = '?' . http_build_query(['order_id' => $payment->getOrderId()]);
foreach (['/api/v1/payment/callback/mock', '/api/v1/subscription-payment/callback/mock'] as $legacy) {
$this->client->request('POST', $legacy . $query);
$this->assertSame(404, $this->client->getResponse()->getStatusCode(), $legacy);
}
}
}