Files
Gnuboard7/plugins/_bundled/sirsoft-pay_nhnkcp/tests/Feature/Controllers/PaymentRetryControllerTest.php
T

235 lines
8.2 KiB
PHP

<?php
namespace Plugins\Sirsoft\PayNhnkcp\Tests\Feature\Controllers;
use App\Models\User;
use Modules\Sirsoft\Ecommerce\Database\Factories\OrderAddressFactory;
use Modules\Sirsoft\Ecommerce\Database\Factories\OrderFactory;
use Modules\Sirsoft\Ecommerce\Database\Factories\OrderPaymentFactory;
use Modules\Sirsoft\Ecommerce\Enums\OrderStatusEnum;
use Modules\Sirsoft\Ecommerce\Enums\PaymentMethodEnum;
use Modules\Sirsoft\Ecommerce\Enums\PaymentStatusEnum;
use Modules\Sirsoft\Ecommerce\Models\Order;
use Plugins\Sirsoft\PayNhnkcp\Tests\PluginTestCase;
class PaymentRetryControllerTest extends PluginTestCase
{
private const ENDPOINT = '/api/plugins/sirsoft-pay_nhnkcp/payment/retry';
private function createOrder(
int $amount = 10000,
OrderStatusEnum $orderStatus = OrderStatusEnum::PENDING_ORDER,
PaymentStatusEnum $paymentStatus = PaymentStatusEnum::READY,
array $orderMeta = [],
array $paymentMeta = [],
): Order {
$user = User::factory()->create();
$order = OrderFactory::new()->create([
'user_id' => $user->id,
'order_number' => 'ORD-KCP-RETRY-' . random_int(10000, 99999),
'order_status' => $orderStatus,
'subtotal_amount' => $amount,
'total_amount' => $amount,
'total_due_amount' => $amount,
'total_paid_amount' => 0,
'order_meta' => $orderMeta,
'currency' => 'KRW',
'currency_snapshot' => self::krwCurrencySnapshot(),
]);
OrderPaymentFactory::new()->create([
'order_id' => $order->id,
'payment_status' => $paymentStatus,
'payment_method' => PaymentMethodEnum::CARD,
'pg_provider' => 'nhnkcp',
'paid_amount_local' => 0,
'paid_at' => null,
'cancelled_at' => $paymentStatus === PaymentStatusEnum::CANCELLED ? now() : null,
'transaction_id' => null,
'card_approval_number' => null,
'payment_meta' => $paymentMeta,
]);
return $order;
}
private static function krwCurrencySnapshot(): array
{
return [
'base_currency' => 'KRW',
'order_currency' => 'KRW',
'base_unit' => 1,
'exchange_rates' => [
'KRW' => [
'rate' => 1,
'rounding_unit' => '1',
'rounding_method' => 'round',
'decimal_places' => 0,
'base_unit' => 1,
],
],
];
}
private static function invalidUsdCurrencySnapshot(): array
{
return [
'base_currency' => 'KRW',
'order_currency' => 'USD',
'base_unit' => 1000,
'exchange_rates' => [
'KRW' => [
'rate' => 1,
'rounding_unit' => '1',
'rounding_method' => 'round',
'decimal_places' => 0,
'base_unit' => 1000,
],
'USD' => [
'rate' => 0,
'rounding_unit' => '0.01',
'rounding_method' => 'round',
'decimal_places' => 2,
'base_unit' => 1,
],
],
];
}
public function test_retry_returns_ready_for_pending_order(): void
{
$order = $this->createOrder(10000);
$response = $this->postJson(self::ENDPOINT, [
'oid' => $order->order_number,
'price' => 10000,
]);
$response->assertOk()
->assertJsonPath('data.status', 'ready');
$order->refresh();
$this->assertEquals(OrderStatusEnum::PENDING_ORDER, $order->order_status);
$this->assertEquals(PaymentStatusEnum::READY, $order->payment->payment_status);
}
public function test_retry_rejects_invalid_payment_currency_without_server_error(): void
{
$order = $this->createOrder(10000);
$order->update([
'currency' => 'USD',
'currency_snapshot' => self::invalidUsdCurrencySnapshot(),
]);
$response = $this->postJson(self::ENDPOINT, [
'oid' => $order->order_number,
'price' => 10000,
]);
$response->assertStatus(422)
->assertJsonPath('errors.message.0', 'Payment currency is not chargeable.');
$order->refresh();
$this->assertEquals(OrderStatusEnum::PENDING_ORDER, $order->order_status);
$this->assertEquals(PaymentStatusEnum::READY, $order->payment->payment_status);
}
public function test_retry_restores_cancelled_kcp_failure_order(): void
{
$order = $this->createOrder(
20000,
OrderStatusEnum::CANCELLED,
PaymentStatusEnum::CANCELLED,
[
'payment_failure_code' => 'USER_CANCEL',
'payment_failure_message' => '사용자가 NHN KCP 결제창을 닫았습니다.',
'payment_failed_at' => now()->subMinute()->toIso8601String(),
],
[
'failure_source' => 'nhnkcp',
'failure_code' => 'USER_CANCEL',
'failure_message' => 'kcp-window-closed',
'failure_stage' => 'window_closed',
'failed_at' => now()->subMinute()->toIso8601String(),
],
);
$response = $this->postJson(self::ENDPOINT, [
'oid' => $order->order_number,
'price' => 20000,
]);
$response->assertOk()
->assertJsonPath('data.status', 'restored');
$order->refresh();
$payment = $order->payment;
$payment->refresh();
$this->assertEquals(OrderStatusEnum::PENDING_ORDER, $order->order_status);
$this->assertArrayNotHasKey('payment_failure_code', $order->order_meta);
$this->assertSame(1, $order->order_meta['nhnkcp_retry_count'] ?? null);
$this->assertNotEmpty($order->order_meta['nhnkcp_retry_failures'] ?? []);
$this->assertEquals(PaymentStatusEnum::READY, $payment->payment_status);
$this->assertNull($payment->cancelled_at);
$this->assertArrayNotHasKey('failure_source', $payment->payment_meta);
$this->assertSame(1, $payment->payment_meta['retry_count'] ?? null);
}
public function test_retry_rejects_amount_mismatch_failure_order(): void
{
$order = $this->createOrder(
20000,
OrderStatusEnum::CANCELLED,
PaymentStatusEnum::FAILED,
[
'payment_failure_code' => 'AMOUNT_MISMATCH',
'payment_failure_message' => '금액 불일치',
'payment_failed_at' => now()->subMinute()->toIso8601String(),
],
[
'failure_source' => 'nhnkcp',
'failure_code' => 'AMOUNT_MISMATCH',
'failure_message' => '금액 불일치',
'failure_stage' => 'amount_mismatch',
'failed_at' => now()->subMinute()->toIso8601String(),
],
);
$response = $this->postJson(self::ENDPOINT, [
'oid' => $order->order_number,
'price' => 20000,
]);
$response->assertStatus(409)
->assertJsonPath('errors.message.0', 'Order is not retryable for NHN KCP payment.');
$order->refresh();
$this->assertEquals(OrderStatusEnum::CANCELLED, $order->order_status);
$this->assertEquals(PaymentStatusEnum::FAILED, $order->payment->payment_status);
}
public function test_retry_rejects_buyer_mismatch(): void
{
$order = $this->createOrder(20000);
OrderAddressFactory::new()->create([
'order_id' => $order->id,
'address_type' => 'shipping',
'orderer_email' => 'buyer@example.com',
'orderer_phone' => '010-1234-5678',
]);
$response = $this->postJson(self::ENDPOINT, [
'oid' => $order->order_number,
'price' => 20000,
'buyer_email' => 'attacker@example.com',
'buyer_phone' => '01012345678',
]);
$response->assertStatus(403)
->assertJsonPath('errors.message.0', 'Order buyer verification failed.');
}
}