fix(nicepayments): 부분취소 후 잔액 취소 판정 보정
이미 부분취소된 거래의 남은 잔액을 취소할 때도 나이스페이에 부분취소 코드가 전달되도록 보정합니다. 회귀 테스트와 나이스페이 플러그인 CHANGELOG를 함께 추가합니다.
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- 이미 부분취소된 나이스페이먼츠 거래의 남은 잔액을 다시 취소할 때 PG 취소 요청이 전액취소로 잘못 전달되어 실패하던 문제를 수정했습니다.
|
||||
- 비회원 나이스페이먼츠 주문완료/주문상세에서 매출전표 조회가 인증 사용자 전용 라우트에 막히지 않도록 게스트 주문 토큰과 결제 성공 후 임시 영수증 쿠키 접근을 지원했습니다.
|
||||
- 나이스페이먼츠 인증 단계 실패(`AuthResultCode != 0000`)는 실제 PG 승인 전이므로 주문/결제/주문옵션을 취소 상태로 바꾸지 않고 체크아웃 재시도가 가능하도록 수정했습니다.
|
||||
- 다통화 주문의 환율 스냅샷이 누락되거나 0인 경우 SignData, 결제 승인 콜백, 결제창 닫힘 보고에서 예외가 일반 승인 오류로 뭉개지지 않도록 결제 통화 설정 오류로 명시 처리했습니다.
|
||||
|
||||
@@ -14,6 +14,7 @@ use Plugins\Sirsoft\PayNicepayments\Services\NicePaymentsApiService;
|
||||
class PaymentRefundListener implements HookListenerInterface
|
||||
{
|
||||
private const PG_PROVIDER_ID = 'nicepayments';
|
||||
private const MONEY_EPSILON = 0.0001;
|
||||
|
||||
/**
|
||||
* 구독할 훅 매핑 반환
|
||||
@@ -104,9 +105,7 @@ class PaymentRefundListener implements HookListenerInterface
|
||||
|
||||
$moid = (string) $order->order_number;
|
||||
|
||||
// 코어가 cancelled_amount를 훅 호출 전에 먼저 업데이트하므로
|
||||
// getCancellableAmount() > 0이면 부분취소, 0이면 전액취소
|
||||
$isPartial = $payment->getCancellableAmount() > 0;
|
||||
$isPartial = $this->shouldUsePartialCancelCode($payment, $cancelAmt);
|
||||
$response = $apiService->cancelPayment($tid, $moid, $cancelAmt, $cancelMsg, $isPartial ? 1 : 0);
|
||||
|
||||
Log::info('NicePayments: refund success', [
|
||||
@@ -154,4 +153,17 @@ class PaymentRefundListener implements HookListenerInterface
|
||||
|
||||
$apiService->useStoredCredentials((bool) $meta['is_test_mode'], $mid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 코어는 PG 환불 훅 호출 전에 누적 취소액을 먼저 반영한다.
|
||||
* 따라서 남은 잔액 취소 시 getCancellableAmount() 만 보면 전액취소처럼 보일 수 있으므로,
|
||||
* 이번 환불 이전에 이미 취소된 결제 통화 금액이 있었는지도 함께 확인한다.
|
||||
*/
|
||||
private function shouldUsePartialCancelCode(OrderPayment $payment, int $cancelAmt): bool
|
||||
{
|
||||
$cancelledBeforeThisRefund = max(0.0, $payment->cancelledLocalAmount() - $cancelAmt);
|
||||
|
||||
return $cancelledBeforeThisRefund > self::MONEY_EPSILON
|
||||
|| $payment->getCancellableAmount() > self::MONEY_EPSILON;
|
||||
}
|
||||
}
|
||||
|
||||
+46
@@ -506,6 +506,52 @@ class ShippingAndCouponCancellationTest extends PluginTestCase
|
||||
$this->assertEquals(23000, (float) $refund->refund_amount, '상품(20,000) + 배송비 차액(3,000) = 23,000원 환불');
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미 부분취소된 거래의 남은 잔액을 취소할 때도 NicePayments 에는 부분취소 코드가 전달되어야 한다.
|
||||
*/
|
||||
public function test_remaining_cancel_after_prior_partial_cancel_keeps_partial_flag(): void
|
||||
{
|
||||
$calls = [];
|
||||
$mock = $this->createMock(NicePaymentsApiService::class);
|
||||
$mock->expects($this->exactly(2))
|
||||
->method('cancelPayment')
|
||||
->willReturnCallback(function (
|
||||
string $tid,
|
||||
string $moid,
|
||||
int $cancelAmt,
|
||||
string $cancelMsg,
|
||||
int $partialCancelCode,
|
||||
) use (&$calls) {
|
||||
$calls[] = compact('tid', 'moid', 'cancelAmt', 'cancelMsg', 'partialCancelCode');
|
||||
|
||||
return self::CANCEL_SUCCESS;
|
||||
});
|
||||
$this->app->instance(NicePaymentsApiService::class, $mock);
|
||||
|
||||
$data = $this->createNicepayOrderWithShipping(optionCount: 2, unitPrice: 1000, shippingFee: 0);
|
||||
$order = $data['order'];
|
||||
$options = $data['options'];
|
||||
|
||||
$this->actingAs($this->adminUser)
|
||||
->postJson("/api/modules/sirsoft-ecommerce/admin/orders/{$order->order_number}/cancel", [
|
||||
'type' => 'partial',
|
||||
'items' => [['order_option_id' => $options[0]->id, 'cancel_quantity' => 1]],
|
||||
'reason' => 'changed_mind',
|
||||
'cancel_pg' => true,
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$this->actingAs($this->adminUser)
|
||||
->postJson("/api/modules/sirsoft-ecommerce/admin/orders/{$order->order_number}/cancel", [
|
||||
'type' => 'partial',
|
||||
'items' => [['order_option_id' => $options[1]->id, 'cancel_quantity' => 1]],
|
||||
'reason' => 'changed_mind',
|
||||
'cancel_pg' => true,
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$this->assertSame(1, $calls[0]['partialCancelCode']);
|
||||
$this->assertSame(1, $calls[1]['partialCancelCode']);
|
||||
}
|
||||
|
||||
/**
|
||||
* cancel_pg=false 이면 NicePayments API 를 호출하지 않아야 한다.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user