현금영수증 발급 프로바이더를 PG 와 독립적으로 선택할 수 있도록 훅 축을 신설한다. KG이니시스로 결제받으면서 현금영수증만 토스페이먼츠로 발급하는 구성이 가능하다. 발급/취소 원장(ecommerce_order_cash_receipts)이 국세청 신고 근거를 보관한다. 금액이 바뀌면 syncFromOrder 가 주문의 현재 상태에서 발급액을 재계산해 전체취소 → 전액 재발급한다. 부분취소 API 를 쓰지 않으므로 전액취소만 지원하는 벤더도 동일 인터페이스로 수용되고, 증가/감소 방향을 구분하지 않아 향후 반품·교환 배송비 청구가 도입되어도 수정 없이 동작한다. 배송비는 그동안 과세/면세 분류에서 아예 빠져 있었다. 배송비는 단계 3 에서 계산되고 할인은 단계 4 에서 적용되므로 상품 분류 시점(단계 2-b)에는 존재하지 않는다. 따라서 할인 후 배송비가 확정된 Summary 집계 직전에 3가지 정책 중 하나로 분류해 합산하고, 옵션별 과세액에는 배송비를 섞지 않는다. 재발급용 식별번호는 APP_KEY 기반으로 암호화 보관하고 구매확정 시 폐기한다. 이력에는 마스킹 값만 남기며 프로바이더 원응답의 민감 키도 가린다. 주민등록번호는 수집하지 않는다. cash_receipt_type 은 Enum 캐스트하지 않는다 — 레거시 값(income_deduction)이 남아 있는 동안 Laravel 의 ::from 이 ValueError 를 던져 정규화해야 할 업그레이드 스텝 자신이 그 행을 읽지 못하게 되기 때문이다.
105 lines
3.8 KiB
PHP
105 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace Modules\Sirsoft\Ecommerce\Tests\Unit\Services;
|
|
|
|
use Modules\Sirsoft\Ecommerce\Enums\PaymentMethodEnum;
|
|
use Modules\Sirsoft\Ecommerce\Services\OrderProcessingService;
|
|
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use ReflectionMethod;
|
|
|
|
/**
|
|
* 현금성 금액 산정 테스트 (A-3)
|
|
*
|
|
* 현금성 금액 = 현금영수증 발급 대상이 되는 실제 현금 입금액.
|
|
* 무통장만 실입금액(마일리지 차감 후) 전액이고 나머지는 0 이다.
|
|
*/
|
|
class CashEquivalentAmountTest extends ModuleTestCase
|
|
{
|
|
/**
|
|
* resolveCashEquivalentAmount 를 호출합니다.
|
|
*/
|
|
private function resolve(?string $paymentMethod, int $finalAmount): int
|
|
{
|
|
$service = $this->getMockBuilder(OrderProcessingService::class)
|
|
->disableOriginalConstructor()
|
|
->onlyMethods([])
|
|
->getMock();
|
|
|
|
$method = new ReflectionMethod(OrderProcessingService::class, 'resolveCashEquivalentAmount');
|
|
$method->setAccessible(true);
|
|
|
|
return $method->invoke($service, $paymentMethod, $finalAmount);
|
|
}
|
|
|
|
#[Test]
|
|
#[DataProvider('paymentMethodProvider')]
|
|
public function 결제수단별_현금성_금액(?string $method, int $finalAmount, int $expected): void
|
|
{
|
|
$this->assertSame($expected, $this->resolve($method, $finalAmount));
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array{?string, int, int}>
|
|
*/
|
|
public static function paymentMethodProvider(): array
|
|
{
|
|
return [
|
|
'무통장 = 실입금액 전액' => [PaymentMethodEnum::DBANK->value, 11000, 11000],
|
|
'가상계좌 = 0 (PG 자동발급)' => [PaymentMethodEnum::VBANK->value, 11000, 0],
|
|
'카드 = 0' => [PaymentMethodEnum::CARD->value, 11000, 0],
|
|
'계좌이체 = 0' => [PaymentMethodEnum::BANK->value, 11000, 0],
|
|
'휴대폰 = 0' => [PaymentMethodEnum::PHONE->value, 11000, 0],
|
|
'결제수단 미지정 = 0' => [null, 11000, 0],
|
|
];
|
|
}
|
|
|
|
#[Test]
|
|
public function 무통장에서_마일리지를_사용하면_차감된_실입금액이_현금성이다(): void
|
|
{
|
|
// 주문 20000, 마일리지 8000 사용 → finalAmount 12000 이 실제 계좌 입금액
|
|
$this->assertSame(12000, $this->resolve(PaymentMethodEnum::DBANK->value, 12000));
|
|
}
|
|
|
|
#[Test]
|
|
public function 전액_마일리지_결제면_현금성이_0이다(): void
|
|
{
|
|
$this->assertSame(0, $this->resolve(PaymentMethodEnum::DBANK->value, 0));
|
|
}
|
|
|
|
#[Test]
|
|
public function 음수_실입금액은_0으로_보정된다(): void
|
|
{
|
|
$this->assertSame(0, $this->resolve(PaymentMethodEnum::DBANK->value, -500));
|
|
}
|
|
|
|
#[Test]
|
|
public function 옵션별_현금성_안분합이_주문_현금성과_일치한다(): void
|
|
{
|
|
// 옵션 finalAmount 합 = 주문 finalAmount 이므로 안분 잔차가 발생하지 않는다.
|
|
$optionFinalAmounts = [3333, 3333, 3334];
|
|
$orderFinalAmount = array_sum($optionFinalAmounts);
|
|
|
|
$optionCashSum = 0;
|
|
foreach ($optionFinalAmounts as $amount) {
|
|
$optionCashSum += $this->resolve(PaymentMethodEnum::DBANK->value, $amount);
|
|
}
|
|
|
|
$this->assertSame(
|
|
$this->resolve(PaymentMethodEnum::DBANK->value, $orderFinalAmount),
|
|
$optionCashSum,
|
|
'옵션별 현금성 안분 합 = 주문 현금성 금액',
|
|
);
|
|
}
|
|
|
|
#[Test]
|
|
public function 무통장이_아니면_옵션별_현금성도_모두_0이다(): void
|
|
{
|
|
foreach ([3333, 3333, 3334] as $amount) {
|
|
$this->assertSame(0, $this->resolve(PaymentMethodEnum::VBANK->value, $amount));
|
|
$this->assertSame(0, $this->resolve(PaymentMethodEnum::CARD->value, $amount));
|
|
}
|
|
}
|
|
}
|