Files
Gnuboard7/modules/_bundled/sirsoft-ecommerce/database/factories/OrderPaymentFactory.php
T
HeuJung e896a47d4f feat(ecommerce,pay): 확장 결제수단(간편결제) 1급화
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 인프라
2026-07-15 11:23:51 +09:00

234 lines
7.7 KiB
PHP

<?php
namespace Modules\Sirsoft\Ecommerce\Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Modules\Sirsoft\Ecommerce\Enums\PaymentMethodEnum;
use Modules\Sirsoft\Ecommerce\Enums\PaymentStatusEnum;
use Modules\Sirsoft\Ecommerce\Models\Order;
use Modules\Sirsoft\Ecommerce\Models\OrderPayment;
use Modules\Sirsoft\Ecommerce\Services\CurrencyConversionService;
/**
* 주문 결제 Factory
*/
class OrderPaymentFactory extends Factory
{
protected $model = OrderPayment::class;
/**
* 기본 정의
*/
public function definition(): array
{
$faker = \fake();
$amount = $faker->numberBetween(10000, 500000);
return [
'order_id' => Order::factory(),
'payment_status' => PaymentStatusEnum::PAID,
'pg_provider' => $faker->randomElement(['tosspayments', 'inicis', 'nicepay']),
'embedded_pg_provider' => null,
'transaction_id' => $faker->uuid(),
'merchant_order_id' => 'MO-'.strtoupper($faker->bothify('????-####-????')),
'payment_method' => PaymentMethodEnum::CARD->value,
'payment_device' => $faker->randomElement(['pc', 'mobile']),
'paid_amount_local' => $amount,
'paid_amount_base' => $amount,
'vat_amount' => round($amount / 11, 2),
'currency' => $this->defaultCurrency(),
'currency_snapshot' => $this->defaultRateMap(),
'card_name' => $faker->randomElement(['신한카드', '삼성카드', 'KB국민카드', '현대카드']),
'card_number_masked' => $faker->numerify('####-****-****-####'),
'card_approval_number' => $faker->numerify('########'),
'card_installment_months' => 0,
'is_interest_free' => false,
'vbank_code' => null,
'vbank_name' => null,
'vbank_number' => null,
'vbank_holder' => null,
'vbank_due_at' => null,
'vbank_issued_at' => null,
'dbank_code' => null,
'dbank_name' => null,
'dbank_account' => null,
'dbank_holder' => null,
'depositor_name' => null,
'deposit_due_at' => null,
'is_escrow' => false,
'buyer_name' => $faker->name(),
'buyer_email' => $faker->email(),
'buyer_phone' => '010-'.$faker->numerify('####-####'),
'is_cash_receipt_requested' => false,
'is_cash_receipt_issued' => false,
'cash_receipt_type' => null,
'cash_receipt_identifier' => null,
'cash_receipt_issued_at' => null,
'cancelled_amount' => 0,
'cancelled_vat_amount' => 0,
'cancel_reason' => null,
'cancel_history' => null,
'refund_bank_code' => null,
'refund_bank_name' => null,
'refund_bank_account' => null,
'refund_bank_holder' => null,
'receipt_url' => $faker->optional()->url(),
'payment_name' => $faker->words(3, true),
'user_agent' => null,
'payment_meta' => null,
'payment_started_at' => now()->subMinutes(5),
'paid_at' => now(),
'cancelled_at' => null,
];
}
/**
* 설정의 기본 통화 코드를 반환합니다 (KRW 하드코딩 제거 — base 추종).
*
* @return string 기본 통화 코드
*/
private function defaultCurrency(): string
{
return app(CurrencyConversionService::class)->getDefaultCurrency();
}
/**
* 통화코드 → 환율 맵을 반환합니다 (기본 통화=1.0).
*
* @return array<string, float> 통화코드별 환율
*/
private function defaultRateMap(): array
{
$service = app(CurrencyConversionService::class);
$rates = [];
foreach ($service->getCurrencySettings() as $currency) {
$code = $currency['code'];
$rates[$code] = ($currency['is_default'] ?? false) ? 1.0 : (float) ($currency['exchange_rate'] ?? 0);
}
return $rates;
}
/**
* 특정 주문의 결제
*/
public function forOrder(Order $order): static
{
return $this->state(fn (array $attributes) => [
'order_id' => $order->id,
'paid_amount_local' => $order->total_amount,
'paid_amount_base' => $order->total_amount,
]);
}
/**
* 카드 결제
*/
public function card(): static
{
$faker = \fake();
return $this->state(fn (array $attributes) => [
'payment_method' => PaymentMethodEnum::CARD->value,
'card_name' => $faker->randomElement(['신한카드', '삼성카드', 'KB국민카드', '현대카드']),
'card_number_masked' => $faker->numerify('####-****-****-####'),
]);
}
/**
* 무통장입금 결제
*/
public function directBank(): static
{
$faker = \fake();
return $this->state(fn (array $attributes) => [
'payment_method' => PaymentMethodEnum::DBANK->value,
'payment_status' => PaymentStatusEnum::WAITING_DEPOSIT,
'pg_provider' => '',
'embedded_pg_provider' => null,
'dbank_code' => '088',
'dbank_name' => $faker->randomElement(['신한은행', '국민은행', '우리은행', '하나은행']),
'dbank_account' => $faker->numerify('###-###-######'),
'dbank_holder' => '(주)테스트몰',
'depositor_name' => $faker->name(),
'deposit_due_at' => now()->addDays(3),
'card_name' => null,
'card_number_masked' => null,
'card_approval_number' => null,
'paid_at' => null,
]);
}
/**
* 가상계좌 결제
*/
public function virtualAccount(): static
{
$faker = \fake();
return $this->state(fn (array $attributes) => [
'payment_method' => PaymentMethodEnum::VBANK->value,
'payment_status' => PaymentStatusEnum::WAITING_DEPOSIT,
'vbank_code' => '088',
'vbank_name' => $faker->randomElement(['신한은행', '국민은행', '우리은행']),
'vbank_number' => $faker->numerify('###-###-######'),
'vbank_holder' => $faker->name(),
'vbank_due_at' => now()->addDays(7),
'vbank_issued_at' => now(),
'card_name' => null,
'card_number_masked' => null,
'card_approval_number' => null,
'paid_at' => null,
]);
}
/**
* 결제 대기 상태
*/
public function pending(): static
{
return $this->state(fn (array $attributes) => [
'payment_status' => PaymentStatusEnum::READY,
'paid_at' => null,
]);
}
/**
* 결제 완료 상태
*/
public function completed(): static
{
return $this->state(fn (array $attributes) => [
'payment_status' => PaymentStatusEnum::PAID,
'paid_at' => now(),
]);
}
/**
* 결제 실패 상태
*/
public function failed(): static
{
return $this->state(fn (array $attributes) => [
'payment_status' => PaymentStatusEnum::FAILED,
'paid_at' => null,
]);
}
/**
* 취소 상태
*/
public function cancelled(): static
{
return $this->state(fn (array $attributes) => [
'payment_status' => PaymentStatusEnum::CANCELLED,
'cancelled_at' => now(),
'cancelled_amount' => $attributes['paid_amount_local'],
'cancelled_vat_amount' => $attributes['vat_amount'],
'cancel_reason' => '고객 요청 취소',
]);
}
}