From 0200f1d043e5fc12ce6eccce099a5f9e1f307c07 Mon Sep 17 00:00:00 2001 From: thisgun Date: Thu, 9 Jul 2026 12:53:16 +0900 Subject: [PATCH] =?UTF-8?q?fix(nicepayments):=20=EA=B0=80=EC=83=81?= =?UTF-8?q?=EA=B3=84=EC=A2=8C=20close-report=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=EB=B3=B4=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 가상계좌 발급 후 입금대기 상태인 주문에 PC 결제창 닫힘 보고가 뒤늦게 도착해도 주문/결제 상태를 실패로 덮지 않도록 READY 상태만 실패 처리 대상으로 제한한다. --- .../sirsoft-pay_nicepayments/CHANGELOG.md | 1 + .../PaymentCloseReportController.php | 11 +++-- .../PaymentCloseReportControllerTest.php | 47 +++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md index 75d8d77c..3df77c45 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-pay_nicepayments/CHANGELOG.md @@ -18,6 +18,7 @@ ### Fixed +- 나이스페이먼츠 가상계좌 발급 후 뒤늦게 PC 결제창 닫힘 보고가 들어와도 입금대기 주문이 결제실패/취소로 덮이지 않도록 보강했습니다. - 이미 부분취소된 나이스페이먼츠 거래의 남은 잔액을 다시 취소할 때 PG 취소 요청이 전액취소로 잘못 전달되어 실패하던 문제를 수정했습니다. - 비회원 나이스페이먼츠 주문완료/주문상세에서 매출전표 조회가 인증 사용자 전용 라우트에 막히지 않도록 게스트 주문 토큰과 결제 성공 후 임시 영수증 쿠키 접근을 지원했습니다. - 나이스페이먼츠 인증 단계 실패(`AuthResultCode != 0000`)는 실제 PG 승인 전이므로 주문/결제/주문옵션을 취소 상태로 바꾸지 않고 체크아웃 재시도가 가능하도록 수정했습니다. diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/src/Controllers/PaymentCloseReportController.php b/plugins/_bundled/sirsoft-pay_nicepayments/src/Controllers/PaymentCloseReportController.php index 66899473..b63757b3 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/src/Controllers/PaymentCloseReportController.php +++ b/plugins/_bundled/sirsoft-pay_nicepayments/src/Controllers/PaymentCloseReportController.php @@ -8,6 +8,7 @@ use App\Helpers\ResponseHelper; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\RateLimiter; +use Modules\Sirsoft\Ecommerce\Enums\PaymentStatusEnum; use Modules\Sirsoft\Ecommerce\Models\Order; use Modules\Sirsoft\Ecommerce\Services\OrderProcessingService; use Plugins\Sirsoft\PayNicepayments\Concerns\RecordsPaymentWindowClosure; @@ -129,13 +130,15 @@ class PaymentCloseReportController $payment = $lockedOrder->payment()->lockForUpdate()->first(); $lockedOrder->setRelation('payment', $payment); - // 결제 성공 콜백과 결제창 닫힘 보고가 경쟁할 때, 카드 주문은 승인 직전까지 - // order_status=PENDING_ORDER(=isBeforePayment) 라 위 가드를 통과한다. payment_status 가 - // 이미 PAID 면 결제가 성공한 것이므로 실패 처리하지 않는다(markPaymentWindowClosed 가 - // 옵션을 취소로 덮어 주문/옵션 상태가 어긋나는 race 차단). 행 락 획득 직후 재확인한다. + // close-report 는 결제창이 승인/발급 단계로 넘어가기 전 READY 상태만 실패 처리한다. + // 카드 승인 콜백과 경쟁해 PAID 가 먼저 저장됐거나, 가상계좌 발급으로 WAITING_DEPOSIT 이 + // 된 주문은 닫힘 보고가 뒤늦게 도착해도 주문/결제 상태를 덮으면 안 된다. if ($payment?->isPaid()) { return ['status' => 'ignored', 'reason' => 'payment_already_paid']; } + if (! $payment || $payment->payment_status !== PaymentStatusEnum::READY) { + return ['status' => 'ignored', 'reason' => 'payment_not_ready']; + } $expectedPrice = $this->resolveExpectedPaymentPriceOrNull($lockedOrder, 'close_report_locked', [ 'oid' => $oid, diff --git a/plugins/_bundled/sirsoft-pay_nicepayments/tests/Feature/Controllers/PaymentCloseReportControllerTest.php b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Feature/Controllers/PaymentCloseReportControllerTest.php index ea323b78..579a1029 100644 --- a/plugins/_bundled/sirsoft-pay_nicepayments/tests/Feature/Controllers/PaymentCloseReportControllerTest.php +++ b/plugins/_bundled/sirsoft-pay_nicepayments/tests/Feature/Controllers/PaymentCloseReportControllerTest.php @@ -293,6 +293,53 @@ class PaymentCloseReportControllerTest extends PluginTestCase $this->assertNotEquals(OrderStatusEnum::CANCELLED, $order->order_status); } + public function test_close_report_ignores_vbank_order_waiting_for_deposit(): void + { + $order = OrderFactory::new()->create([ + 'order_number' => 'ORD-NICE-CLOSE-VBANK-WAIT', + 'order_status' => OrderStatusEnum::PENDING_ORDER, + 'currency' => 'KRW', + 'currency_snapshot' => self::krwCurrencySnapshot(), + 'subtotal_amount' => 10000, + 'total_amount' => 10000, + 'total_due_amount' => 10000, + 'total_paid_amount' => 0, + ]); + OrderPaymentFactory::new()->create([ + 'order_id' => $order->id, + 'payment_status' => PaymentStatusEnum::WAITING_DEPOSIT, + 'payment_method' => PaymentMethodEnum::VBANK, + 'pg_provider' => 'nicepayments', + 'paid_amount_local' => 0, + 'transaction_id' => 'TID_VBANK_WAITING', + 'vbank_name' => '국민은행', + 'vbank_number' => '1234567890', + 'vbank_issued_at' => now(), + 'payment_meta' => [ + 'vbank_tid' => 'TID_VBANK_WAITING', + 'vbank_num' => '1234567890', + 'vbank_name' => '국민은행', + ], + ]); + + $response = $this->postJson('/api/plugins/sirsoft-pay_nicepayments/payment/close-report', [ + 'oid' => 'ORD-NICE-CLOSE-VBANK-WAIT', + 'price' => 10000, + 'payment_method' => 'vbank', + 'reason' => 'nicepay-window-closed', + ]); + + $response->assertOk() + ->assertJsonPath('data.status', 'ignored') + ->assertJsonPath('data.reason', 'payment_not_ready'); + + $order->refresh(); + $this->assertEquals(OrderStatusEnum::PENDING_ORDER, $order->order_status); + $this->assertEquals(PaymentStatusEnum::WAITING_DEPOSIT, $order->payment->payment_status); + $this->assertSame('TID_VBANK_WAITING', $order->payment->transaction_id); + $this->assertNull($order->order_meta['payment_failure_code'] ?? null); + } + private function makeOrder(string $orderNumber, int $amount): Order { $order = new Order;