PG 플러그인이 등록하는 간편결제(네이버페이 등)를 코어가 정식 결제수단으로 인식하도록 결제수단 카탈로그를 SSoT 로 전환했다. 확장 결제수단이 pg_provider=null 로 등록돼 서버가 "PG 결제가 아닌 주문"으로 오인, 결제 실패 주문에 관리자 신규주문 알림이 발송되고 TempOrder 가 즉시 삭제돼 재결제가 막히던 두 결함을 하나의 원인(능력 해석을 enum 이 답하지 못함)으로 근본 해결. - PaymentMethodResolver 신규: 능력(PG 필요/라벨/환불수단/PG 고정) 해석을 카탈로그 SSoT 로 통일, enum case 없는 확장 ID 도 동일 판정 - 프론트 결제수단 위장(card) 제거, provider-agnostic 결제 진입(pg_payment_handler) - 관리자 주문설정 결제수단 목록 PG 고정 3분기 UI (pg_locked/needs_pg) - 저장된 pg_provider=null 자가치유(read-time merge) + 백필 업그레이드 스텝 - 사용자 주문완료 화면 즉시 결제완료 헤더를 입금대기 여집합으로 판정(basic 1.0.3) - 전 계층 회귀 테스트: 훅 발화 스파이(관리자 알림 미발화/PG 환불 훅 진입), 주문 응답 계약(requires_pg_payment/pg_payment_handler)+TempOrder 보존, 관리자 3분기 렌더, 확장 E2E 인프라
175 lines
5.7 KiB
PHP
175 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Plugins\Sirsoft\PayNicepayments\Tests\Unit\Upgrades;
|
|
|
|
use App\Extension\UpgradeContext;
|
|
use App\Upgrades\Data\Ext\Plugins\SirsoftPayNicepayments\V1_0_0_beta_4\Migrations\BackfillEasyPayPgProvider;
|
|
use Illuminate\Support\Facades\File;
|
|
use Tests\TestCase;
|
|
|
|
require_once dirname(__DIR__, 3).'/upgrades/data/1.0.0-beta.4/migrations/BackfillEasyPayPgProvider.php';
|
|
|
|
/**
|
|
* 저장된 주문설정의 나이스페이먼츠 간편결제 PG 고정 백필 테스트 — 이슈 #475
|
|
*
|
|
* 기존 설치 환경의 order_settings.json 에는 간편결제가 `pg_provider: null` 로 영속화되어
|
|
* 있다. 서버가 이 값을 보고 간편결제 주문을 "PG 결제가 아닌 주문" 으로 오인해 결제 실패
|
|
* 시 관리자 알림 오발송 + 임시주문 삭제(재결제 불가) 를 일으켰다.
|
|
*/
|
|
class BackfillEasyPayPgProviderTest extends TestCase
|
|
{
|
|
private string $settingsPath;
|
|
|
|
private bool $hadOriginalSettings = false;
|
|
|
|
private ?string $originalSettings = null;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->settingsPath = storage_path('app/modules/sirsoft-ecommerce/settings/order_settings.json');
|
|
$this->hadOriginalSettings = File::exists($this->settingsPath);
|
|
$this->originalSettings = $this->hadOriginalSettings ? File::get($this->settingsPath) : null;
|
|
|
|
File::ensureDirectoryExists(dirname($this->settingsPath));
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
if ($this->hadOriginalSettings && $this->originalSettings !== null) {
|
|
File::put($this->settingsPath, $this->originalSettings);
|
|
} else {
|
|
File::delete($this->settingsPath);
|
|
}
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* @scenario method_kind=extension, capability_declared=declared, capability=pg_locked
|
|
*
|
|
* @effects upgrade_step_backfills_settings_file, saved_null_pg_provider_self_healed
|
|
*/
|
|
public function test_backfills_pg_declaration_for_nicepay_easy_pay_methods(): void
|
|
{
|
|
$this->writeSettings([
|
|
['id' => 'card', 'pg_provider' => 'nicepayments'],
|
|
['id' => 'nicepay_naverpay', 'pg_provider' => null, 'is_active' => true],
|
|
['id' => 'nicepay_kakaopay', 'pg_provider' => null, 'is_active' => true],
|
|
]);
|
|
|
|
$this->runMigration();
|
|
|
|
$methods = $this->readMethods();
|
|
|
|
foreach (['nicepay_naverpay', 'nicepay_kakaopay'] as $id) {
|
|
$method = $methods[$id];
|
|
$this->assertSame('nicepayments', $method['pg_provider'], "{$id} 의 PG 가 고정되어야 한다");
|
|
$this->assertTrue($method['pg_locked']);
|
|
$this->assertTrue($method['needs_pg']);
|
|
$this->assertSame('pg', $method['refund_method']);
|
|
$this->assertTrue($method['is_active']);
|
|
}
|
|
}
|
|
|
|
public function test_does_not_touch_other_plugins_or_builtin_methods(): void
|
|
{
|
|
$this->writeSettings([
|
|
['id' => 'card', 'pg_provider' => 'nicepayments'],
|
|
['id' => 'dbank', 'pg_provider' => ''],
|
|
['id' => 'nhnkcp_naverpay', 'pg_provider' => null],
|
|
['id' => 'nicepay_naverpay', 'pg_provider' => null],
|
|
]);
|
|
|
|
$this->runMigration();
|
|
|
|
$methods = $this->readMethods();
|
|
|
|
$this->assertSame('nicepayments', $methods['card']['pg_provider']);
|
|
$this->assertArrayNotHasKey('pg_locked', $methods['card']);
|
|
|
|
$this->assertSame('', $methods['dbank']['pg_provider']);
|
|
$this->assertArrayNotHasKey('pg_locked', $methods['dbank']);
|
|
|
|
$this->assertNull($methods['nhnkcp_naverpay']['pg_provider']);
|
|
$this->assertArrayNotHasKey('pg_locked', $methods['nhnkcp_naverpay']);
|
|
|
|
$this->assertSame('nicepayments', $methods['nicepay_naverpay']['pg_provider']);
|
|
}
|
|
|
|
/**
|
|
* @scenario method_kind=extension, capability_declared=declared, capability=pg_locked
|
|
*
|
|
* @effects upgrade_step_backfills_settings_file
|
|
*/
|
|
public function test_is_idempotent(): void
|
|
{
|
|
$this->writeSettings([
|
|
['id' => 'nicepay_naverpay', 'pg_provider' => null],
|
|
]);
|
|
|
|
$this->runMigration();
|
|
$first = File::get($this->settingsPath);
|
|
|
|
$this->runMigration();
|
|
$second = File::get($this->settingsPath);
|
|
|
|
$this->assertSame($first, $second, '재실행해도 결과가 달라지지 않아야 한다');
|
|
}
|
|
|
|
public function test_skips_when_settings_file_is_missing(): void
|
|
{
|
|
File::delete($this->settingsPath);
|
|
|
|
$this->runMigration();
|
|
|
|
$this->assertFalse(File::exists($this->settingsPath));
|
|
}
|
|
|
|
public function test_skips_when_settings_json_is_malformed(): void
|
|
{
|
|
File::put($this->settingsPath, '{ not valid json');
|
|
|
|
$this->runMigration();
|
|
|
|
$this->assertSame('{ not valid json', File::get($this->settingsPath));
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $paymentMethods
|
|
*/
|
|
private function writeSettings(array $paymentMethods): void
|
|
{
|
|
File::put($this->settingsPath, json_encode([
|
|
'payment_methods' => $paymentMethods,
|
|
], JSON_THROW_ON_ERROR));
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string, mixed>> 결제수단 ID => 설정
|
|
*/
|
|
private function readMethods(): array
|
|
{
|
|
$settings = json_decode(File::get($this->settingsPath), true, flags: JSON_THROW_ON_ERROR);
|
|
|
|
$keyed = [];
|
|
foreach ($settings['payment_methods'] as $method) {
|
|
$keyed[$method['id']] = $method;
|
|
}
|
|
|
|
return $keyed;
|
|
}
|
|
|
|
private function runMigration(): void
|
|
{
|
|
(new BackfillEasyPayPgProvider)->run(new UpgradeContext(
|
|
fromVersion: '1.0.0-beta.3',
|
|
toVersion: '1.0.0-beta.4',
|
|
currentStep: '1.0.0-beta.4',
|
|
));
|
|
}
|
|
}
|