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 인프라
234 lines
7.7 KiB
PHP
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' => '고객 요청 취소',
|
|
]);
|
|
}
|
|
}
|