fix(payment): 나이스페이 간편결제 표시 라벨 보강
나이스페이 간편결제 선택값을 콜백까지 보존하고 주문완료, 마이페이지, 관리자 주문목록/상세에서 실제 간편결제명을 표시한다.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- 나이스페이먼츠 간편결제 주문이 주문완료, 마이페이지 주문상세, 관리자 주문목록/상세에서 실제 간편결제 수단 대신 `신용카드`로만 표시되던 문제를 수정했습니다.
|
||||
- 관리자 주문목록과 주문상세 레이아웃이 리뉴얼되거나 플러그인 업데이트 과정에서 나이스페이먼츠 테스트결제 배지, 테스트 모드 안내, 결제 조회/결제 정보 패널이 누락될 수 있던 문제를 수정했습니다.
|
||||
- 운영 모드에서 live MID가 비어 있으면 `SR` 같은 잘못된 MID로 결제창을 만들지 않고 서명 요청 단계에서 실패하도록 보강했습니다.
|
||||
- 환불 요청 시 현재 설정값이 아니라 결제 당시 MID와 테스트/운영 모드로 나이스페이먼츠 API 서명 컨텍스트를 복원하도록 수정했습니다.
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4
@@ -247,9 +247,13 @@ describe('requestPaymentHandler', () => {
|
||||
|
||||
const form = submitSpy.mock.instances[0] as HTMLFormElement;
|
||||
const reservedInput = form.querySelector('input[name="NicepayReserved"]') as HTMLInputElement | null;
|
||||
const mallReservedInput = form.querySelector('input[name="MallReserved"]') as HTMLInputElement | null;
|
||||
const mallReserved1Input = form.querySelector('input[name="MallReserved1"]') as HTMLInputElement | null;
|
||||
|
||||
expect(getSubmittedPayMethod()).toBe('CARD');
|
||||
expect(reservedInput?.value).toBe('DirectKakao=Y');
|
||||
expect(mallReservedInput?.value).toBe('nicepay_easy_pay_method=nicepay_kakaopay');
|
||||
expect(mallReserved1Input?.value).toBe('nicepay_kakaopay');
|
||||
});
|
||||
|
||||
it('모바일 form 은 acceptCharset=euc-kr', async () => {
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { patchAdminPaymentMethodDisplay } from '../adminOrderPaymentDisplayInjector';
|
||||
|
||||
describe('NicePayments order payment display injectors', () => {
|
||||
it('관리자 주문상세 결제수단 행을 간편결제 표시명으로 교체한다', () => {
|
||||
document.body.innerHTML = `
|
||||
<section id="section_payment_info">
|
||||
<div>
|
||||
<div>결제수단</div>
|
||||
<div>신용카드</div>
|
||||
<div>(일시불)</div>
|
||||
</div>
|
||||
</section>
|
||||
`;
|
||||
|
||||
const root = document.getElementById('section_payment_info');
|
||||
|
||||
const patched = patchAdminPaymentMethodDisplay(root!, {
|
||||
_pay_method_label: '카카오페이 (신용카드)',
|
||||
_base_pay_method_label: '신용카드',
|
||||
_embedded_pg_provider_label: '카카오페이',
|
||||
});
|
||||
|
||||
expect(patched).toBe(true);
|
||||
expect(root?.textContent).toContain('카카오페이 (신용카드, 일시불)');
|
||||
expect(root?.textContent).not.toContain('(일시불)');
|
||||
});
|
||||
|
||||
it('관리자 주문상세 결제수단 배지를 간편결제 라벨로 교체한다', () => {
|
||||
document.body.innerHTML = `
|
||||
<section id="section_payment_info">
|
||||
<span class="rounded-full font-medium">신용카드</span>
|
||||
</section>
|
||||
`;
|
||||
|
||||
const root = document.getElementById('section_payment_info');
|
||||
|
||||
const patched = patchAdminPaymentMethodDisplay(root!, {
|
||||
_pay_method_label: '네이버페이 (신용카드)',
|
||||
_base_pay_method_label: '신용카드',
|
||||
_embedded_pg_provider_label: '네이버페이',
|
||||
});
|
||||
|
||||
expect(patched).toBe(true);
|
||||
expect(root?.querySelector('span')?.textContent).toBe('네이버페이');
|
||||
});
|
||||
});
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
const PLUGIN_ID = 'sirsoft-pay_nicepayments';
|
||||
const FLAG = '__nicepayAdminPaymentDisplayInjectorInstalled';
|
||||
|
||||
const ADMIN_ORDER_SHOW_RE = /^\/admin\/ecommerce\/orders\/([^/]+)$/;
|
||||
|
||||
interface AdminTransactionStatus {
|
||||
_pay_method_label?: string | null;
|
||||
_base_pay_method_label?: string | null;
|
||||
_embedded_pg_provider_label?: string | null;
|
||||
}
|
||||
|
||||
function getToken(): string | null {
|
||||
return localStorage.getItem('auth_token');
|
||||
}
|
||||
|
||||
async function fetchAdminTransactionStatus(orderNumber: string): Promise<AdminTransactionStatus | null> {
|
||||
const token = getToken();
|
||||
if (!token) return null;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/plugins/${PLUGIN_ID}/admin/orders/${orderNumber}/transaction-status`, {
|
||||
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
|
||||
const json = (await res.json()) as { data?: AdminTransactionStatus | null } | AdminTransactionStatus;
|
||||
return ('data' in json ? json.data : json) ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function directElementChildren(el: Element): HTMLElement[] {
|
||||
return Array.from(el.children).filter(
|
||||
(child): child is HTMLElement => child instanceof HTMLElement,
|
||||
);
|
||||
}
|
||||
|
||||
function patchLabeledPaymentMethod(root: Element, displayLabel: string): boolean {
|
||||
let patched = false;
|
||||
|
||||
const rows = Array.from(root.querySelectorAll<HTMLElement>('div'));
|
||||
for (const row of rows) {
|
||||
const children = directElementChildren(row);
|
||||
const labelIndex = children.findIndex(child => child.textContent?.trim() === '결제수단');
|
||||
if (labelIndex < 0) continue;
|
||||
|
||||
const value = children
|
||||
.slice(labelIndex + 1)
|
||||
.find(child => child.textContent?.trim() && child.textContent.trim() !== '결제수단');
|
||||
if (!value) continue;
|
||||
|
||||
const installment = children
|
||||
.slice(labelIndex + 2)
|
||||
.find(child => /^\(.+\)$/.test(child.textContent?.trim() ?? ''));
|
||||
const installmentText = installment?.textContent?.trim().replace(/^\(|\)$/g, '') ?? '';
|
||||
const nextDisplayLabel = installmentText && displayLabel.endsWith(')')
|
||||
? displayLabel.replace(/\)$/, `, ${installmentText})`)
|
||||
: displayLabel;
|
||||
|
||||
if (value.textContent?.trim() !== nextDisplayLabel) {
|
||||
value.textContent = nextDisplayLabel;
|
||||
value.dataset.nicepayPaymentMethodPatched = 'true';
|
||||
}
|
||||
row.dataset.nicepayPaymentMethodRow = 'admin-order';
|
||||
if (installment && installmentText) {
|
||||
installment.textContent = '';
|
||||
installment.style.display = 'none';
|
||||
installment.dataset.nicepayPaymentInstallmentHidden = 'true';
|
||||
}
|
||||
patched = true;
|
||||
}
|
||||
|
||||
return patched;
|
||||
}
|
||||
|
||||
function patchPaymentMethodBadge(root: Element, baseLabel: string, embeddedLabel: string): boolean {
|
||||
const candidates = Array.from(root.querySelectorAll<HTMLElement>('span')).filter(
|
||||
el => el.textContent?.trim() === baseLabel,
|
||||
);
|
||||
|
||||
const badge = candidates.find(el => {
|
||||
const className = typeof el.className === 'string' ? el.className : '';
|
||||
return className.includes('rounded-full') || className.includes('font-medium');
|
||||
});
|
||||
|
||||
if (!badge) return false;
|
||||
|
||||
badge.textContent = embeddedLabel;
|
||||
badge.dataset.nicepayPaymentMethodPatched = 'true';
|
||||
badge.dataset.nicepayPaymentMethodBadge = 'admin-order';
|
||||
return true;
|
||||
}
|
||||
|
||||
export function patchAdminPaymentMethodDisplay(
|
||||
root: Element,
|
||||
status: AdminTransactionStatus | null | undefined,
|
||||
): boolean {
|
||||
const displayLabel = status?._pay_method_label;
|
||||
const embeddedLabel = status?._embedded_pg_provider_label;
|
||||
const baseLabel = status?._base_pay_method_label;
|
||||
|
||||
if (!displayLabel || !embeddedLabel) return false;
|
||||
|
||||
const labeled = patchLabeledPaymentMethod(root, displayLabel);
|
||||
const badge = baseLabel ? patchPaymentMethodBadge(root, baseLabel, embeddedLabel) : false;
|
||||
|
||||
return labeled || badge;
|
||||
}
|
||||
|
||||
async function tryInject(orderNumber: string): Promise<boolean> {
|
||||
const root = document.getElementById('section_payment_info') ?? document.getElementById('payment_info_card');
|
||||
if (!root) return false;
|
||||
|
||||
const status = await fetchAdminTransactionStatus(orderNumber);
|
||||
if (!status) return true;
|
||||
|
||||
const patched = patchAdminPaymentMethodDisplay(root, status);
|
||||
if (patched) {
|
||||
console.info(`[${PLUGIN_ID}] payment method display patched on admin order detail`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function startPolling(orderNumber: string): void {
|
||||
let attempts = 0;
|
||||
const id = setInterval(() => {
|
||||
attempts++;
|
||||
void tryInject(orderNumber).then(done => {
|
||||
if (done || attempts >= 30) clearInterval(id);
|
||||
});
|
||||
}, 400);
|
||||
}
|
||||
|
||||
function onRouteChange(): void {
|
||||
const match = location.pathname.match(ADMIN_ORDER_SHOW_RE);
|
||||
if (match) startPolling(match[1]);
|
||||
}
|
||||
|
||||
export function installAdminOrderPaymentDisplayInjector(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
const w = window as unknown as Record<string, unknown>;
|
||||
if (w[FLAG]) return;
|
||||
w[FLAG] = true;
|
||||
|
||||
console.info(`[${PLUGIN_ID}] admin order payment display injector installed`);
|
||||
|
||||
const schedule = (delay = 1500) => setTimeout(onRouteChange, delay);
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => schedule());
|
||||
} else {
|
||||
schedule();
|
||||
}
|
||||
|
||||
const origPush = history.pushState.bind(history);
|
||||
history.pushState = (...args: Parameters<typeof history.pushState>) => {
|
||||
origPush(...args);
|
||||
schedule(600);
|
||||
};
|
||||
window.addEventListener('popstate', () => schedule(500));
|
||||
}
|
||||
@@ -267,6 +267,8 @@ export async function requestPaymentHandler(action: PaymentAction, _context?: un
|
||||
DirectEasyPay: '',
|
||||
NicepayReserved: '',
|
||||
EasyPayMethod: '',
|
||||
MallReserved: '',
|
||||
MallReserved1: '',
|
||||
};
|
||||
|
||||
// 휴대폰결제: 상품 유형 덮어쓰기 (0:디지털컨텐츠, 1:실물)
|
||||
@@ -281,6 +283,8 @@ export async function requestPaymentHandler(action: PaymentAction, _context?: un
|
||||
// 간편결제 directive 필드 설정 — gnu5 orderform.js switch(settle_method==='간편결제') 동일
|
||||
if (isEasyPay) {
|
||||
formFields.DirectShowOpt = 'CARD';
|
||||
formFields.MallReserved = `nicepay_easy_pay_method=${encodeURIComponent(paymentMethod)}`;
|
||||
formFields.MallReserved1 = paymentMethod;
|
||||
switch (paymentMethod) {
|
||||
case 'nicepay_naverpay':
|
||||
formFields.DirectEasyPay = 'E020';
|
||||
|
||||
@@ -5,6 +5,7 @@ import { installCheckoutEasyPayInjector } from './checkoutEasyPayInjector';
|
||||
import { installOrderCompleteReceiptInjector } from './orderCompleteReceiptInjector';
|
||||
import { installMypageOrderShowInjector } from './mypageOrderShowInjector';
|
||||
import { installAdminPaymentMethodBrandInjector } from './adminPaymentMethodBrandInjector';
|
||||
import { installAdminOrderPaymentDisplayInjector } from './adminOrderPaymentDisplayInjector';
|
||||
|
||||
const PLUGIN_IDENTIFIER = 'sirsoft-pay_nicepayments';
|
||||
|
||||
@@ -53,6 +54,7 @@ function initPlugin(): void {
|
||||
installOrderCompleteReceiptInjector();
|
||||
installMypageOrderShowInjector();
|
||||
installAdminPaymentMethodBrandInjector();
|
||||
installAdminOrderPaymentDisplayInjector();
|
||||
|
||||
const doInit = () => {
|
||||
const count = registerHandlers();
|
||||
|
||||
@@ -23,6 +23,11 @@ interface OrderData {
|
||||
payment?: Payment;
|
||||
}
|
||||
|
||||
interface ReceiptInfo {
|
||||
receipt_url?: string | null;
|
||||
payment_method_display_label?: string | null;
|
||||
}
|
||||
|
||||
function getOrderFromState(orderNumber: string): OrderData | null {
|
||||
try {
|
||||
const g7 = (window as Record<string, unknown>).G7Core as Record<string, unknown> | undefined;
|
||||
@@ -41,7 +46,7 @@ function getToken(): string | null {
|
||||
return localStorage.getItem('auth_token');
|
||||
}
|
||||
|
||||
async function fetchReceiptUrl(orderNumber: string): Promise<string | null> {
|
||||
async function fetchReceiptInfo(orderNumber: string): Promise<ReceiptInfo | null> {
|
||||
const token = getToken();
|
||||
if (!token) return null;
|
||||
try {
|
||||
@@ -49,13 +54,37 @@ async function fetchReceiptUrl(orderNumber: string): Promise<string | null> {
|
||||
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = (await res.json()) as { receipt_url?: string };
|
||||
return data.receipt_url ?? null;
|
||||
return (await res.json()) as ReceiptInfo;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function patchPaymentMethodDisplay(container: Element, displayLabel: string | null | undefined): boolean {
|
||||
if (!displayLabel) return false;
|
||||
|
||||
const rows = Array.from(container.querySelectorAll<HTMLElement>('div'));
|
||||
for (const row of rows) {
|
||||
const spans = Array.from(row.children).filter(
|
||||
(child): child is HTMLElement => child instanceof HTMLElement && child.tagName === 'SPAN',
|
||||
);
|
||||
if (spans.length < 2) continue;
|
||||
|
||||
const label = spans[0].textContent?.trim();
|
||||
if (label !== '결제 방법' && label !== '결제수단' && label !== 'Payment Method') continue;
|
||||
|
||||
const value = spans[spans.length - 1];
|
||||
if (value.textContent?.trim() !== displayLabel) {
|
||||
value.textContent = displayLabel;
|
||||
value.dataset.nicepayPaymentMethodPatched = 'true';
|
||||
}
|
||||
row.dataset.nicepayPaymentMethodRow = 'true';
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function findPaymentContainer(): Element | null {
|
||||
const panel = document.getElementById('order_payment_info_panel');
|
||||
if (panel) {
|
||||
@@ -91,9 +120,10 @@ function buildReceiptRow(orderNumber: string): HTMLElement {
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
btn.textContent = '로딩 중...';
|
||||
const url = await fetchReceiptUrl(orderNumber);
|
||||
const data = await fetchReceiptInfo(orderNumber);
|
||||
btn.disabled = false;
|
||||
btn.textContent = '영수증 조회';
|
||||
const url = data?.receipt_url ?? null;
|
||||
if (url) {
|
||||
window.open(url, 'nicepay_receipt', 'width=800,height=600,scrollbars=yes,resizable=yes');
|
||||
}
|
||||
@@ -188,6 +218,8 @@ async function tryInject(orderNumber: string): Promise<boolean> {
|
||||
|
||||
const container = findPaymentContainer();
|
||||
if (!container) return false;
|
||||
const receiptInfo = await fetchReceiptInfo(orderNumber);
|
||||
patchPaymentMethodDisplay(container, receiptInfo?.payment_method_display_label);
|
||||
|
||||
// 가상계좌 입금 안내 — 결제수단이 vbank 이고 가상계좌 번호 발급된 경우 표시 (paid_at 무관: 입금 전후 모두 표시).
|
||||
if (payment.payment_method === 'vbank' && payment.vbank_number && !document.getElementById(VBANK_BLOCK_ID)) {
|
||||
|
||||
+37
-4
@@ -11,6 +11,11 @@ type Payment = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
interface ReceiptInfo {
|
||||
receipt_url?: string | null;
|
||||
payment_method_display_label?: string | null;
|
||||
}
|
||||
|
||||
function getToken(): string | null {
|
||||
return localStorage.getItem('auth_token');
|
||||
}
|
||||
@@ -31,7 +36,7 @@ async function fetchPayment(orderNumber: string): Promise<Payment | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchReceiptUrl(orderNumber: string): Promise<string | null> {
|
||||
async function fetchReceiptInfo(orderNumber: string): Promise<ReceiptInfo | null> {
|
||||
const token = getToken();
|
||||
if (!token) return null;
|
||||
|
||||
@@ -40,13 +45,37 @@ async function fetchReceiptUrl(orderNumber: string): Promise<string | null> {
|
||||
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = (await res.json()) as { receipt_url?: string };
|
||||
return data.receipt_url ?? null;
|
||||
return (await res.json()) as ReceiptInfo;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function patchPaymentMethodDisplay(displayLabel: string | null | undefined): boolean {
|
||||
if (!displayLabel) return false;
|
||||
|
||||
const rows = Array.from(document.querySelectorAll<HTMLElement>('div'));
|
||||
for (const row of rows) {
|
||||
const spans = Array.from(row.children).filter(
|
||||
(child): child is HTMLElement => child instanceof HTMLElement && child.tagName === 'SPAN',
|
||||
);
|
||||
if (spans.length < 2) continue;
|
||||
|
||||
const label = spans[0].textContent?.trim();
|
||||
if (label !== '결제 방법' && label !== '결제수단' && label !== '결제 방식') continue;
|
||||
|
||||
const value = spans[spans.length - 1];
|
||||
if (value.textContent?.trim() !== displayLabel) {
|
||||
value.textContent = displayLabel;
|
||||
value.dataset.nicepayPaymentMethodPatched = 'true';
|
||||
}
|
||||
row.dataset.nicepayPaymentMethodRow = 'true';
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function injectOnOrderComplete(orderNumber: string): Promise<void> {
|
||||
if (document.getElementById(BTN_ID)) return;
|
||||
|
||||
@@ -59,6 +88,9 @@ async function injectOnOrderComplete(orderNumber: string): Promise<void> {
|
||||
|
||||
if (!blueBtn?.parentElement) return;
|
||||
|
||||
const receiptInfo = await fetchReceiptInfo(orderNumber);
|
||||
patchPaymentMethodDisplay(receiptInfo?.payment_method_display_label);
|
||||
|
||||
const container = blueBtn.parentElement;
|
||||
|
||||
const receiptBtn = document.createElement('button');
|
||||
@@ -72,9 +104,10 @@ async function injectOnOrderComplete(orderNumber: string): Promise<void> {
|
||||
receiptBtn.addEventListener('click', async () => {
|
||||
receiptBtn.disabled = true;
|
||||
receiptBtn.textContent = '로딩 중...';
|
||||
const url = await fetchReceiptUrl(orderNumber);
|
||||
const data = await fetchReceiptInfo(orderNumber);
|
||||
receiptBtn.disabled = false;
|
||||
receiptBtn.textContent = '영수증 조회';
|
||||
const url = data?.receipt_url ?? null;
|
||||
if (url) {
|
||||
window.open(url, 'nicepay_receipt', 'width=800,height=600,scrollbars=yes,resizable=yes');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Plugins\Sirsoft\PayNicepayments\Concerns;
|
||||
|
||||
use Modules\Sirsoft\Ecommerce\Enums\PaymentMethodEnum;
|
||||
|
||||
trait ResolvesEasyPayDisplay
|
||||
{
|
||||
private const EASY_PAY_METHODS = [
|
||||
'nicepay_naverpay' => [
|
||||
'provider' => 'naverpay',
|
||||
'label' => ['ko' => '네이버페이', 'en' => 'NaverPay'],
|
||||
],
|
||||
'nicepay_kakaopay' => [
|
||||
'provider' => 'kakaopay',
|
||||
'label' => ['ko' => '카카오페이', 'en' => 'KakaoPay'],
|
||||
],
|
||||
'nicepay_samsungpay' => [
|
||||
'provider' => 'samsungpay',
|
||||
'label' => ['ko' => '삼성페이', 'en' => 'Samsung Pay'],
|
||||
],
|
||||
'nicepay_applepay' => [
|
||||
'provider' => 'applepay',
|
||||
'label' => ['ko' => '애플페이', 'en' => 'Apple Pay'],
|
||||
],
|
||||
'nicepay_payco' => [
|
||||
'provider' => 'payco',
|
||||
'label' => ['ko' => 'PAYCO (페이코)', 'en' => 'PAYCO'],
|
||||
],
|
||||
'nicepay_skpay' => [
|
||||
'provider' => 'skpay',
|
||||
'label' => ['ko' => 'SK pay', 'en' => 'SK pay'],
|
||||
],
|
||||
'nicepay_ssgpay' => [
|
||||
'provider' => 'ssgpay',
|
||||
'label' => ['ko' => 'SSG PAY', 'en' => 'SSG PAY'],
|
||||
],
|
||||
'nicepay_lpay' => [
|
||||
'provider' => 'lpay',
|
||||
'label' => ['ko' => 'L.pay', 'en' => 'L.pay'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array{method: string, provider: string, label: array{ko: string, en: string}}|array{}
|
||||
*/
|
||||
protected function resolveEasyPayMeta(array $payload): array
|
||||
{
|
||||
$method = $this->resolveEasyPayMethod($payload);
|
||||
if ($method === null || ! isset(self::EASY_PAY_METHODS[$method])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$definition = self::EASY_PAY_METHODS[$method];
|
||||
|
||||
return [
|
||||
'method' => $method,
|
||||
'provider' => $definition['provider'],
|
||||
'label' => $definition['label'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function decodePaymentMeta(mixed $paymentMeta): array
|
||||
{
|
||||
if (is_array($paymentMeta)) {
|
||||
return $paymentMeta;
|
||||
}
|
||||
|
||||
if (! is_string($paymentMeta) || trim($paymentMeta) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($paymentMeta, true);
|
||||
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* payment_method_label: string|null,
|
||||
* payment_method_display_label: string|null,
|
||||
* selected_payment_method: string|null,
|
||||
* embedded_pg_provider: string|null,
|
||||
* embedded_pg_provider_label: string|null
|
||||
* }
|
||||
*/
|
||||
protected function resolvePaymentDisplay(object $payment): array
|
||||
{
|
||||
$paymentMeta = $this->decodePaymentMeta($payment->payment_meta ?? null);
|
||||
$selectedPaymentMethod = $this->stringOrNull($paymentMeta['nicepay_easy_pay_method'] ?? null);
|
||||
$provider = $this->stringOrNull(
|
||||
$payment->embedded_pg_provider
|
||||
?? $paymentMeta['nicepay_easy_pay_provider']
|
||||
?? $paymentMeta['embedded_pg_provider']
|
||||
?? null
|
||||
);
|
||||
|
||||
if ($provider === null && $selectedPaymentMethod !== null) {
|
||||
$provider = $this->stringOrNull(self::EASY_PAY_METHODS[$selectedPaymentMethod]['provider'] ?? null);
|
||||
}
|
||||
|
||||
$baseLabel = $this->paymentMethodLabel($this->stringOrNull($payment->payment_method ?? null));
|
||||
$embeddedLabel = $this->embeddedPgProviderLabel($provider, $paymentMeta);
|
||||
|
||||
return [
|
||||
'payment_method_label' => $baseLabel,
|
||||
'payment_method_display_label' => $this->paymentMethodDisplayLabel($baseLabel, $embeddedLabel),
|
||||
'selected_payment_method' => $selectedPaymentMethod,
|
||||
'embedded_pg_provider' => $provider,
|
||||
'embedded_pg_provider_label' => $embeddedLabel,
|
||||
];
|
||||
}
|
||||
|
||||
protected function paymentMethodLabel(?string $method): ?string
|
||||
{
|
||||
if ($method === null || $method === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return PaymentMethodEnum::tryFrom($method)?->label() ?? $method;
|
||||
}
|
||||
|
||||
protected function paymentMethodDisplayLabel(?string $baseLabel, ?string $embeddedLabel): ?string
|
||||
{
|
||||
if ($baseLabel === null || $baseLabel === '') {
|
||||
return $embeddedLabel;
|
||||
}
|
||||
|
||||
if ($embeddedLabel === null || $embeddedLabel === '') {
|
||||
return $baseLabel;
|
||||
}
|
||||
|
||||
return $embeddedLabel . ' (' . $baseLabel . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $paymentMeta
|
||||
*/
|
||||
protected function embeddedPgProviderLabel(?string $provider, array $paymentMeta = []): ?string
|
||||
{
|
||||
$metaLabel = $this->localizedLabel($paymentMeta['nicepay_easy_pay_label'] ?? null)
|
||||
?? $this->localizedLabel($paymentMeta['embedded_pg_provider_label'] ?? null);
|
||||
|
||||
if ($metaLabel !== null) {
|
||||
return $metaLabel;
|
||||
}
|
||||
|
||||
if ($provider === null || $provider === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (self::EASY_PAY_METHODS as $definition) {
|
||||
if (($definition['provider'] ?? null) === $provider) {
|
||||
return $this->localizedLabel($definition['label'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $payload
|
||||
*/
|
||||
private function resolveEasyPayMethod(array $payload): ?string
|
||||
{
|
||||
foreach (['nicepay_easy_pay_method', 'MallReserved1'] as $key) {
|
||||
$method = $this->stringOrNull($payload[$key] ?? null);
|
||||
if ($method !== null && isset(self::EASY_PAY_METHODS[$method])) {
|
||||
return $method;
|
||||
}
|
||||
}
|
||||
|
||||
$mallReserved = $this->stringOrNull($payload['MallReserved'] ?? null);
|
||||
if ($mallReserved === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($mallReserved, true);
|
||||
if (is_array($decoded)) {
|
||||
$method = $this->stringOrNull($decoded['nicepay_easy_pay_method'] ?? null);
|
||||
if ($method !== null && isset(self::EASY_PAY_METHODS[$method])) {
|
||||
return $method;
|
||||
}
|
||||
}
|
||||
|
||||
parse_str($mallReserved, $parsed);
|
||||
$method = $this->stringOrNull($parsed['nicepay_easy_pay_method'] ?? null);
|
||||
if ($method !== null && isset(self::EASY_PAY_METHODS[$method])) {
|
||||
return $method;
|
||||
}
|
||||
|
||||
return isset(self::EASY_PAY_METHODS[$mallReserved]) ? $mallReserved : null;
|
||||
}
|
||||
|
||||
private function localizedLabel(mixed $label): ?string
|
||||
{
|
||||
if (is_string($label) && trim($label) !== '') {
|
||||
return $label;
|
||||
}
|
||||
|
||||
if (! is_array($label)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$locale = app()->getLocale();
|
||||
|
||||
return $this->stringOrNull($label[$locale] ?? null)
|
||||
?? $this->stringOrNull($label['ko'] ?? null)
|
||||
?? $this->stringOrNull($label['en'] ?? null);
|
||||
}
|
||||
|
||||
private function stringOrNull(mixed $value): ?string
|
||||
{
|
||||
if (! is_string($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim($value);
|
||||
|
||||
return $value === '' ? null : $value;
|
||||
}
|
||||
}
|
||||
+43
@@ -8,9 +8,12 @@ use App\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Api\Base\AdminBaseController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Plugins\Sirsoft\PayNicepayments\Concerns\ResolvesEasyPayDisplay;
|
||||
|
||||
class AdminOrderListController extends AdminBaseController
|
||||
{
|
||||
use ResolvesEasyPayDisplay;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
@@ -45,4 +48,44 @@ class AdminOrderListController extends AdminBaseController
|
||||
|
||||
return ResponseHelper::success('messages.success', $map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 나이스페이먼츠 간편결제 표시 맵 반환
|
||||
*
|
||||
* 관리자 주문목록은 코어 결제 리소스의 기본 결제수단 라벨을 사용하므로,
|
||||
* 플러그인 데이터소스로 실제 간편결제 표시명을 보강한다.
|
||||
*
|
||||
* @return JsonResponse 주문번호별 표시 맵
|
||||
*/
|
||||
public function easyPayDisplayMap(): JsonResponse
|
||||
{
|
||||
$rows = DB::table('ecommerce_orders as o')
|
||||
->join('ecommerce_order_payments as p', 'p.order_id', '=', 'o.id')
|
||||
->whereIn('p.pg_provider', ['nicepayments', 'nicepay'])
|
||||
->where('p.created_at', '>=', now()->subMonths(6))
|
||||
->select([
|
||||
'o.order_number',
|
||||
'p.payment_method',
|
||||
'p.embedded_pg_provider',
|
||||
'p.payment_meta',
|
||||
])
|
||||
->get();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$display = $this->resolvePaymentDisplay($row);
|
||||
if (($display['embedded_pg_provider_label'] ?? null) === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[$row->order_number] = [
|
||||
'payment_method_label' => $display['payment_method_label'],
|
||||
'payment_method_display_label' => $display['payment_method_display_label'],
|
||||
'embedded_pg_provider' => $display['embedded_pg_provider'],
|
||||
'embedded_pg_provider_label' => $display['embedded_pg_provider_label'],
|
||||
];
|
||||
}
|
||||
|
||||
return ResponseHelper::success('messages.success', $map);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -8,10 +8,13 @@ use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Plugins\Sirsoft\PayNicepayments\Concerns\ResolvesEasyPayDisplay;
|
||||
use Plugins\Sirsoft\PayNicepayments\Services\NicePaymentsApiService;
|
||||
|
||||
class AdminTransactionController extends AdminBaseController
|
||||
{
|
||||
use ResolvesEasyPayDisplay;
|
||||
|
||||
public function __construct(
|
||||
private NicePaymentsApiService $apiService
|
||||
) {
|
||||
@@ -72,10 +75,21 @@ class AdminTransactionController extends AdminBaseController
|
||||
|
||||
$localPayment = DB::table('ecommerce_order_payments')
|
||||
->where('transaction_id', $tid)
|
||||
->select(['is_escrow', 'payment_meta'])
|
||||
->select(['is_escrow', 'payment_meta', 'payment_method', 'embedded_pg_provider'])
|
||||
->first();
|
||||
|
||||
$result['_local_is_escrow'] = (bool) ($localPayment?->is_escrow ?? false);
|
||||
$display = $localPayment ? $this->resolvePaymentDisplay($localPayment) : [
|
||||
'payment_method_label' => null,
|
||||
'payment_method_display_label' => null,
|
||||
'embedded_pg_provider' => null,
|
||||
'embedded_pg_provider_label' => null,
|
||||
];
|
||||
$result['_base_pay_method_label'] = $display['payment_method_label'];
|
||||
$result['_embedded_pg_provider'] = $display['embedded_pg_provider'];
|
||||
$result['_embedded_pg_provider_label'] = $display['embedded_pg_provider_label'];
|
||||
$result['_pay_method_label'] = $display['payment_method_display_label'];
|
||||
$result['payment_method_display_label'] = $display['payment_method_display_label'];
|
||||
|
||||
if ($localPayment?->payment_meta) {
|
||||
$meta = json_decode($localPayment->payment_meta, true);
|
||||
|
||||
+16
-3
@@ -23,6 +23,7 @@ use Modules\Sirsoft\Ecommerce\Models\OrderPayment;
|
||||
use Modules\Sirsoft\Ecommerce\Services\OrderProcessingService;
|
||||
use Plugins\Sirsoft\PayNicepayments\Concerns\PreventsReplayCallback;
|
||||
use Plugins\Sirsoft\PayNicepayments\Concerns\RecordsPaymentWindowClosure;
|
||||
use Plugins\Sirsoft\PayNicepayments\Concerns\ResolvesEasyPayDisplay;
|
||||
use Plugins\Sirsoft\PayNicepayments\Concerns\SanitizesPgResponse;
|
||||
use Plugins\Sirsoft\PayNicepayments\Http\Requests\AuthCallbackRequest;
|
||||
use Plugins\Sirsoft\PayNicepayments\Http\Requests\VbankNotifyRequest;
|
||||
@@ -40,6 +41,7 @@ class PaymentCallbackController
|
||||
{
|
||||
use PreventsReplayCallback;
|
||||
use RecordsPaymentWindowClosure;
|
||||
use ResolvesEasyPayDisplay;
|
||||
use SanitizesPgResponse;
|
||||
|
||||
private const PLUGIN_IDENTIFIER = 'sirsoft-pay_nicepayments';
|
||||
@@ -72,6 +74,7 @@ class PaymentCallbackController
|
||||
'RcptTID',
|
||||
'EscrowYN',
|
||||
'MallReserved',
|
||||
'MallReserved1',
|
||||
'Currency',
|
||||
];
|
||||
|
||||
@@ -92,6 +95,7 @@ class PaymentCallbackController
|
||||
'RcptTID',
|
||||
'EscrowYN',
|
||||
'MallReserved',
|
||||
'MallReserved1',
|
||||
];
|
||||
|
||||
private const VBANK_NOTIFY_RESPONSE_KEYS = [
|
||||
@@ -306,7 +310,7 @@ class PaymentCallbackController
|
||||
]);
|
||||
} else {
|
||||
$effectiveTid = (string) ($pgResponse['TID'] ?? $txTid);
|
||||
$completionStatus = $this->completeAuthorizedPaymentWithLock($moid, $pgResponse, $txTid, $amt, $isEscrow, $request);
|
||||
$completionStatus = $this->completeAuthorizedPaymentWithLock($moid, $pgResponse, $txTid, $amt, $isEscrow, $request, $validated);
|
||||
|
||||
if ($completionStatus === 'already_paid') {
|
||||
$this->logReplayDetected($effectiveTid, $moid, 'authCallback (card)');
|
||||
@@ -468,8 +472,9 @@ class PaymentCallbackController
|
||||
int $amt,
|
||||
bool $isEscrow,
|
||||
Request $request,
|
||||
array $callbackPayload,
|
||||
): string {
|
||||
return DB::transaction(function () use ($moid, $pgResponse, $txTid, $amt, $isEscrow, $request): string {
|
||||
return DB::transaction(function () use ($moid, $pgResponse, $txTid, $amt, $isEscrow, $request, $callbackPayload): string {
|
||||
$lockedOrder = Order::query()
|
||||
->where('order_number', $moid)
|
||||
->lockForUpdate()
|
||||
@@ -499,6 +504,7 @@ class PaymentCallbackController
|
||||
}
|
||||
|
||||
$lockedOrder->setRelation('payment', $payment);
|
||||
$easyPayMeta = $this->resolveEasyPayMeta(array_merge($pgResponse, $callbackPayload));
|
||||
|
||||
$this->orderService->completePayment($lockedOrder, [
|
||||
'transaction_id' => $effectiveTid,
|
||||
@@ -507,10 +513,17 @@ class PaymentCallbackController
|
||||
'card_name' => $pgResponse['IssuCardName'] ?? $pgResponse['CardName'] ?? null,
|
||||
'card_installment_months' => (int) ($pgResponse['CardQuota'] ?? 0),
|
||||
'is_interest_free' => false,
|
||||
'embedded_pg_provider' => null,
|
||||
'embedded_pg_provider' => $easyPayMeta['provider'] ?? null,
|
||||
'receipt_url' => 'https://npg.nicepay.co.kr/issue/IssueLoader.do?type=0&TID='.rawurlencode($effectiveTid),
|
||||
'payment_meta' => [
|
||||
'result_code' => $pgResponse['ResultCode'] ?? null,
|
||||
...($easyPayMeta === [] ? [] : [
|
||||
'nicepay_easy_pay_method' => $easyPayMeta['method'],
|
||||
'nicepay_easy_pay_provider' => $easyPayMeta['provider'],
|
||||
'nicepay_easy_pay_label' => $easyPayMeta['label'],
|
||||
'embedded_pg_provider' => $easyPayMeta['provider'],
|
||||
'embedded_pg_provider_label' => $easyPayMeta['label'],
|
||||
]),
|
||||
'pay_method' => $pgResponse['PayMethod'] ?? null,
|
||||
'auth_date' => $pgResponse['AuthDate'] ?? null,
|
||||
'mid' => $this->apiService->getMid(),
|
||||
|
||||
+16
-1
@@ -7,9 +7,12 @@ namespace Plugins\Sirsoft\PayNicepayments\Controllers;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Plugins\Sirsoft\PayNicepayments\Concerns\ResolvesEasyPayDisplay;
|
||||
|
||||
class UserReceiptController
|
||||
{
|
||||
use ResolvesEasyPayDisplay;
|
||||
|
||||
private const RECEIPT_BASE_URL = 'https://npg.nicepay.co.kr/issue/IssueLoader.do';
|
||||
|
||||
/**
|
||||
@@ -31,7 +34,13 @@ class UserReceiptController
|
||||
->join('ecommerce_orders as o', 'o.id', '=', 'p.order_id')
|
||||
->where('o.order_number', $orderNumber)
|
||||
->where('o.user_id', $user->id)
|
||||
->select(['p.transaction_id', 'p.receipt_url', 'p.payment_meta'])
|
||||
->select([
|
||||
'p.transaction_id',
|
||||
'p.receipt_url',
|
||||
'p.payment_meta',
|
||||
'p.payment_method',
|
||||
'p.embedded_pg_provider',
|
||||
])
|
||||
->first();
|
||||
|
||||
if (! $payment) {
|
||||
@@ -45,6 +54,7 @@ class UserReceiptController
|
||||
|
||||
$cashReceiptUrl = null;
|
||||
$isTestMode = false;
|
||||
$display = $this->resolvePaymentDisplay($payment);
|
||||
if ($payment->payment_meta) {
|
||||
$meta = json_decode($payment->payment_meta, true);
|
||||
$rcptTid = $meta['rcpt_tid'] ?? ($meta['pg_raw_response']['RcptTID'] ?? null);
|
||||
@@ -58,6 +68,11 @@ class UserReceiptController
|
||||
'receipt_url' => $receiptUrl,
|
||||
'cash_receipt_url' => $cashReceiptUrl,
|
||||
'is_test_mode' => $isTestMode,
|
||||
'payment_method_label' => $display['payment_method_label'],
|
||||
'payment_method_display_label' => $display['payment_method_display_label'],
|
||||
'selected_payment_method' => $display['selected_payment_method'],
|
||||
'embedded_pg_provider' => $display['embedded_pg_provider'],
|
||||
'embedded_pg_provider_label' => $display['embedded_pg_provider_label'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ class AuthCallbackRequest extends FormRequest
|
||||
'AuthResultMsg' => ['nullable', 'string'],
|
||||
'Moid' => ['nullable', 'string'],
|
||||
'Amt' => ['nullable', 'integer', 'min:1'],
|
||||
'MallReserved' => ['nullable', 'string', 'max:1000'],
|
||||
'MallReserved1' => ['nullable', 'string', 'max:20'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -68,6 +70,8 @@ class AuthCallbackRequest extends FormRequest
|
||||
'Amt' => ['required', 'integer', 'min:1'],
|
||||
'NetCancelURL' => ['required', 'string', 'url'],
|
||||
'Signature' => ['required', 'string'],
|
||||
'MallReserved' => ['nullable', 'string', 'max:1000'],
|
||||
'MallReserved1' => ['nullable', 'string', 'max:20'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+56
-12
@@ -14,8 +14,12 @@ class EnsureAdminOrderListTestBadgeLayoutListener implements HookListenerInterfa
|
||||
|
||||
private const DATA_SOURCE_ID = 'nicepay_test_map';
|
||||
|
||||
private const EASY_PAY_DISPLAY_DATA_SOURCE_ID = 'nicepay_easy_pay_display_map';
|
||||
|
||||
private const BADGE_CONDITION = 'nicepay_test_map.data?.[row.order_number] === true';
|
||||
|
||||
private const EASY_PAY_DISPLAY_EXPRESSION = 'nicepay_easy_pay_display_map.data?.[row.order_number]?.payment_method_display_label ?? row.payment?.payment_method_label';
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
@@ -65,24 +69,42 @@ class EnsureAdminOrderListTestBadgeLayoutListener implements HookListenerInterfa
|
||||
? $layout['data_sources']
|
||||
: [];
|
||||
|
||||
$existingIds = [];
|
||||
foreach ($dataSources as $dataSource) {
|
||||
if (is_array($dataSource) && ($dataSource['id'] ?? null) === self::DATA_SOURCE_ID) {
|
||||
$layout['data_sources'] = $dataSources;
|
||||
$existingIds[self::DATA_SOURCE_ID] = true;
|
||||
}
|
||||
|
||||
return $layout;
|
||||
if (is_array($dataSource) && ($dataSource['id'] ?? null) === self::EASY_PAY_DISPLAY_DATA_SOURCE_ID) {
|
||||
$existingIds[self::EASY_PAY_DISPLAY_DATA_SOURCE_ID] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$dataSources[] = [
|
||||
'id' => self::DATA_SOURCE_ID,
|
||||
'label_key' => '$t:sirsoft-pay_nicepayments.editor.data_source.nicepay_test_map',
|
||||
'type' => 'api',
|
||||
'endpoint' => '/api/plugins/sirsoft-pay_nicepayments/admin/orders/test-mode-map',
|
||||
'method' => 'GET',
|
||||
'auto_fetch' => true,
|
||||
'auth_required' => true,
|
||||
'loading_strategy' => 'progressive',
|
||||
];
|
||||
if (! isset($existingIds[self::DATA_SOURCE_ID])) {
|
||||
$dataSources[] = [
|
||||
'id' => self::DATA_SOURCE_ID,
|
||||
'label_key' => '$t:sirsoft-pay_nicepayments.editor.data_source.nicepay_test_map',
|
||||
'type' => 'api',
|
||||
'endpoint' => '/api/plugins/sirsoft-pay_nicepayments/admin/orders/test-mode-map',
|
||||
'method' => 'GET',
|
||||
'auto_fetch' => true,
|
||||
'auth_required' => true,
|
||||
'loading_strategy' => 'progressive',
|
||||
];
|
||||
}
|
||||
|
||||
if (! isset($existingIds[self::EASY_PAY_DISPLAY_DATA_SOURCE_ID])) {
|
||||
$dataSources[] = [
|
||||
'id' => self::EASY_PAY_DISPLAY_DATA_SOURCE_ID,
|
||||
'label_key' => '$t:sirsoft-pay_nicepayments.editor.data_source.nicepay_easy_pay_display_map',
|
||||
'type' => 'api',
|
||||
'endpoint' => '/api/plugins/sirsoft-pay_nicepayments/admin/orders/easy-pay-display-map',
|
||||
'method' => 'GET',
|
||||
'auto_fetch' => true,
|
||||
'auth_required' => true,
|
||||
'loading_strategy' => 'progressive',
|
||||
];
|
||||
}
|
||||
|
||||
$layout['data_sources'] = $dataSources;
|
||||
|
||||
@@ -131,12 +153,34 @@ class EnsureAdminOrderListTestBadgeLayoutListener implements HookListenerInterfa
|
||||
}
|
||||
|
||||
$this->appendBadgeToColumn($column);
|
||||
$this->ensurePaymentMethodDisplayExpression($column);
|
||||
|
||||
return;
|
||||
}
|
||||
unset($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $node
|
||||
*/
|
||||
private function ensurePaymentMethodDisplayExpression(array &$node): bool
|
||||
{
|
||||
if (($node['text'] ?? null) === '{{row.payment?.payment_method_label}}') {
|
||||
$node['text'] = '{{'.self::EASY_PAY_DISPLAY_EXPRESSION.'}}';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($node as &$value) {
|
||||
if (is_array($value) && $this->ensurePaymentMethodDisplayExpression($value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $column
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,11 @@ Route::prefix('admin')->name('admin.')->middleware(['auth:sanctum', 'admin'])->g
|
||||
->middleware('permission:admin,sirsoft-ecommerce.orders.read')
|
||||
->name('orders.test-mode-map');
|
||||
|
||||
// 주문 목록 간편결제 표시 맵 조회
|
||||
Route::get('/orders/easy-pay-display-map', [AdminOrderListController::class, 'easyPayDisplayMap'])
|
||||
->middleware('permission:admin,sirsoft-ecommerce.orders.read')
|
||||
->name('orders.easy-pay-display-map');
|
||||
|
||||
// 가상계좌 입금 완료 건 환불 (환불 계좌 정보 필요)
|
||||
Route::post('/vbank-refund', [AdminVbankRefundController::class, 'refund'])
|
||||
->middleware('permission:admin,sirsoft-ecommerce.orders.update')
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Plugins\Sirsoft\PayNicepayments\Tests\Feature\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use Modules\Sirsoft\Ecommerce\Database\Factories\OrderFactory;
|
||||
use Modules\Sirsoft\Ecommerce\Database\Factories\OrderPaymentFactory;
|
||||
use Modules\Sirsoft\Ecommerce\Enums\OrderStatusEnum;
|
||||
use Modules\Sirsoft\Ecommerce\Enums\PaymentMethodEnum;
|
||||
use Modules\Sirsoft\Ecommerce\Enums\PaymentStatusEnum;
|
||||
use Plugins\Sirsoft\PayNicepayments\Services\NicePaymentsApiService;
|
||||
use Plugins\Sirsoft\PayNicepayments\Tests\PluginTestCase;
|
||||
|
||||
class EasyPayDisplayControllerTest extends PluginTestCase
|
||||
{
|
||||
public function test_admin_order_list_easy_pay_display_map_returns_display_labels(): void
|
||||
{
|
||||
app()->setLocale('ko');
|
||||
|
||||
$admin = $this->createAdminUser(['sirsoft-ecommerce.orders.read']);
|
||||
$order = $this->createPaidEasyPayOrder('nicepay_payco', 'payco', [
|
||||
'ko' => 'PAYCO (페이코)',
|
||||
'en' => 'PAYCO',
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($admin)
|
||||
->getJson('/api/plugins/sirsoft-pay_nicepayments/admin/orders/easy-pay-display-map');
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
$display = $response->json("data.{$order->order_number}");
|
||||
$this->assertSame('payco', $display['embedded_pg_provider'] ?? null);
|
||||
$this->assertContains($display['embedded_pg_provider_label'] ?? null, ['PAYCO (페이코)', 'PAYCO']);
|
||||
$this->assertSame(
|
||||
($display['embedded_pg_provider_label'] ?? '') . ' (' . ($display['payment_method_label'] ?? '') . ')',
|
||||
$display['payment_method_display_label'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_admin_transaction_status_includes_easy_pay_display_label(): void
|
||||
{
|
||||
app()->setLocale('ko');
|
||||
|
||||
$admin = $this->createAdminUser(['sirsoft-ecommerce.orders.read']);
|
||||
$order = $this->createPaidEasyPayOrder('nicepay_kakaopay', 'kakaopay', [
|
||||
'ko' => '카카오페이',
|
||||
'en' => 'KakaoPay',
|
||||
]);
|
||||
|
||||
$apiServiceMock = $this->createMock(NicePaymentsApiService::class);
|
||||
$apiServiceMock->expects($this->once())
|
||||
->method('queryTransaction')
|
||||
->with('NICE_TID_' . $order->id)
|
||||
->willReturn([
|
||||
'ResultCode' => '3001',
|
||||
'ResultMsg' => '정상처리',
|
||||
'TID' => 'NICE_TID_' . $order->id,
|
||||
'PayMethod' => 'CARD',
|
||||
]);
|
||||
$this->app->instance(NicePaymentsApiService::class, $apiServiceMock);
|
||||
|
||||
$response = $this->actingAs($admin)
|
||||
->getJson("/api/plugins/sirsoft-pay_nicepayments/admin/orders/{$order->order_number}/transaction-status");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('data._embedded_pg_provider', 'kakaopay');
|
||||
|
||||
$data = $response->json('data');
|
||||
$this->assertContains($data['_embedded_pg_provider_label'] ?? null, ['카카오페이', 'KakaoPay']);
|
||||
$this->assertSame(
|
||||
($data['_embedded_pg_provider_label'] ?? '') . ' (' . ($data['_base_pay_method_label'] ?? '') . ')',
|
||||
$data['_pay_method_label'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_user_receipt_response_includes_easy_pay_display_label(): void
|
||||
{
|
||||
app()->setLocale('ko');
|
||||
|
||||
$user = User::factory()->create();
|
||||
$order = $this->createPaidEasyPayOrder('nicepay_naverpay', 'naverpay', [
|
||||
'ko' => '네이버페이',
|
||||
'en' => 'NaverPay',
|
||||
], $user);
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->getJson("/api/plugins/sirsoft-pay_nicepayments/user/orders/{$order->order_number}/receipt");
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('selected_payment_method', 'nicepay_naverpay')
|
||||
->assertJsonPath('embedded_pg_provider', 'naverpay');
|
||||
|
||||
$data = $response->json();
|
||||
$this->assertContains($data['embedded_pg_provider_label'] ?? null, ['네이버페이', 'NaverPay']);
|
||||
$this->assertSame(
|
||||
($data['embedded_pg_provider_label'] ?? '') . ' (' . ($data['payment_method_label'] ?? '') . ')',
|
||||
$data['payment_method_display_label'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{ko: string, en: string} $label
|
||||
*/
|
||||
private function createPaidEasyPayOrder(
|
||||
string $method,
|
||||
string $provider,
|
||||
array $label,
|
||||
?User $user = null,
|
||||
): \Modules\Sirsoft\Ecommerce\Models\Order {
|
||||
$order = OrderFactory::new()->create([
|
||||
'user_id' => ($user ?? User::factory()->create())->id,
|
||||
'order_number' => 'ORD-NICE-EASY-' . random_int(10000, 99999),
|
||||
'order_status' => OrderStatusEnum::PAYMENT_COMPLETE,
|
||||
'subtotal_amount' => 50000,
|
||||
'total_amount' => 50000,
|
||||
'total_due_amount' => 0,
|
||||
'total_paid_amount' => 50000,
|
||||
'currency' => 'KRW',
|
||||
'currency_snapshot' => self::krwCurrencySnapshot(),
|
||||
'paid_at' => now(),
|
||||
]);
|
||||
|
||||
OrderPaymentFactory::new()->create([
|
||||
'order_id' => $order->id,
|
||||
'payment_status' => PaymentStatusEnum::PAID,
|
||||
'payment_method' => PaymentMethodEnum::CARD,
|
||||
'pg_provider' => 'nicepayments',
|
||||
'transaction_id' => 'NICE_TID_' . $order->id,
|
||||
'embedded_pg_provider' => $provider,
|
||||
'paid_amount_local' => 50000,
|
||||
'paid_at' => now(),
|
||||
'payment_meta' => [
|
||||
'nicepay_easy_pay_method' => $method,
|
||||
'nicepay_easy_pay_provider' => $provider,
|
||||
'nicepay_easy_pay_label' => $label,
|
||||
'pg_raw_response' => [
|
||||
'ResultCode' => '3001',
|
||||
'PayMethod' => 'CARD',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
+38
@@ -63,6 +63,8 @@ class PaymentCallbackControllerTest extends PluginTestCase
|
||||
'total_points_used_amount' => 0,
|
||||
'total_deposit_used_amount' => 0,
|
||||
'total_paid_amount' => 0,
|
||||
'currency' => 'KRW',
|
||||
'currency_snapshot' => self::krwCurrencySnapshot(),
|
||||
]);
|
||||
|
||||
OrderPaymentFactory::new()->create([
|
||||
@@ -387,6 +389,42 @@ class PaymentCallbackControllerTest extends PluginTestCase
|
||||
$this->assertEquals('APP12345', $payment->card_approval_number);
|
||||
}
|
||||
|
||||
public function test_auth_callback_stores_easy_pay_display_metadata(): void
|
||||
{
|
||||
app()->setLocale('ko');
|
||||
|
||||
$order = $this->createTestOrder(50000);
|
||||
$this->mockPluginSettings();
|
||||
|
||||
$tid = 'TID_EASY_PAY_' . uniqid();
|
||||
$params = $this->makeCallbackParams($order->order_number, 50000, [
|
||||
'MallReserved' => 'nicepay_easy_pay_method=nicepay_kakaopay',
|
||||
'MallReserved1' => 'nicepay_kakaopay',
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'pay.nicepay.co.kr/v1/authorize' => Http::response(
|
||||
$this->makeAuthorizeResponse($tid, $order->order_number, 50000),
|
||||
200
|
||||
),
|
||||
]);
|
||||
|
||||
$response = $this->post('/plugins/sirsoft-pay_nicepayments/payment/callback', $params);
|
||||
|
||||
$response->assertRedirect("/shop/orders/{$order->order_number}/complete");
|
||||
|
||||
$payment = $order->payment;
|
||||
$payment->refresh();
|
||||
$meta = $payment->payment_meta;
|
||||
|
||||
$this->assertSame('kakaopay', $payment->embedded_pg_provider);
|
||||
$this->assertSame('nicepay_kakaopay', $meta['nicepay_easy_pay_method'] ?? null);
|
||||
$this->assertSame('kakaopay', $meta['nicepay_easy_pay_provider'] ?? null);
|
||||
$this->assertSame('카카오페이', $meta['nicepay_easy_pay_label']['ko'] ?? null);
|
||||
$this->assertSame('KakaoPay', $meta['nicepay_easy_pay_label']['en'] ?? null);
|
||||
$this->assertSame('kakaopay', $meta['embedded_pg_provider'] ?? null);
|
||||
}
|
||||
|
||||
public function test_auth_callback_stores_only_whitelisted_pg_response_fields(): void
|
||||
{
|
||||
$order = $this->createTestOrder(50000);
|
||||
|
||||
+5
@@ -21,6 +21,7 @@ class PaymentCloseReportControllerTest extends PluginTestCase
|
||||
'order_number' => 'ORD-NICE-CLOSE-001',
|
||||
'order_status' => OrderStatusEnum::PENDING_ORDER,
|
||||
'currency' => 'KRW',
|
||||
'currency_snapshot' => self::krwCurrencySnapshot(),
|
||||
'subtotal_amount' => 10000,
|
||||
'total_amount' => 10000,
|
||||
'total_due_amount' => 10000,
|
||||
@@ -55,6 +56,7 @@ class PaymentCloseReportControllerTest extends PluginTestCase
|
||||
'order_number' => 'ORD-NICE-CLOSE-REAL',
|
||||
'order_status' => OrderStatusEnum::PENDING_ORDER,
|
||||
'currency' => 'KRW',
|
||||
'currency_snapshot' => self::krwCurrencySnapshot(),
|
||||
'subtotal_amount' => 10000,
|
||||
'total_amount' => 10000,
|
||||
'total_due_amount' => 10000,
|
||||
@@ -172,6 +174,7 @@ class PaymentCloseReportControllerTest extends PluginTestCase
|
||||
'order_number' => 'ORD-NICE-CLOSE-RACE',
|
||||
'order_status' => OrderStatusEnum::PENDING_ORDER,
|
||||
'currency' => 'KRW',
|
||||
'currency_snapshot' => self::krwCurrencySnapshot(),
|
||||
'subtotal_amount' => 10000,
|
||||
'total_amount' => 10000,
|
||||
'total_due_amount' => 10000,
|
||||
@@ -232,6 +235,7 @@ class PaymentCloseReportControllerTest extends PluginTestCase
|
||||
'order_number' => 'ORD-NICE-CLOSE-PAID',
|
||||
'order_status' => OrderStatusEnum::PENDING_ORDER,
|
||||
'currency' => 'KRW',
|
||||
'currency_snapshot' => self::krwCurrencySnapshot(),
|
||||
'subtotal_amount' => 10000,
|
||||
'total_amount' => 10000,
|
||||
'total_due_amount' => 10000,
|
||||
@@ -270,6 +274,7 @@ class PaymentCloseReportControllerTest extends PluginTestCase
|
||||
$order->order_number = $orderNumber;
|
||||
$order->order_status = OrderStatusEnum::PENDING_ORDER;
|
||||
$order->currency = 'KRW';
|
||||
$order->currency_snapshot = self::krwCurrencySnapshot();
|
||||
$order->total_due_amount = $amount;
|
||||
|
||||
return $order;
|
||||
|
||||
@@ -51,6 +51,24 @@ abstract class PluginTestCase extends TestCase
|
||||
\Illuminate\Support\Facades\Config::set('app.url', env('APP_URL', 'http://localhost'));
|
||||
}
|
||||
|
||||
protected static function krwCurrencySnapshot(): array
|
||||
{
|
||||
return [
|
||||
'base_currency' => 'KRW',
|
||||
'order_currency' => 'KRW',
|
||||
'base_unit' => 1,
|
||||
'exchange_rates' => [
|
||||
'KRW' => [
|
||||
'rate' => 1,
|
||||
'rounding_unit' => '1',
|
||||
'rounding_method' => 'round',
|
||||
'decimal_places' => 0,
|
||||
'base_unit' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function registerModuleAutoload(): void
|
||||
{
|
||||
$moduleBasePath = base_path('modules/sirsoft-ecommerce/src/');
|
||||
|
||||
+5
@@ -18,9 +18,12 @@ class EnsureAdminOrderLayoutExtensionsListenerTest extends TestCase
|
||||
$json = json_encode($result, JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$this->assertSame(1, $this->countDataSources($result, 'nicepay_test_map'));
|
||||
$this->assertSame(1, $this->countDataSources($result, 'nicepay_easy_pay_display_map'));
|
||||
$this->assertSame(1, $this->countString($result, 'nicepay_test_map.data?.[row.order_number] === true'));
|
||||
$this->assertSame(1, $this->countString($result, 'nicepay_easy_pay_display_map.data?.[row.order_number]?.payment_method_display_label ?? row.payment?.payment_method_label'));
|
||||
$this->assertIsString($json);
|
||||
$this->assertStringContainsString('/api/plugins/sirsoft-pay_nicepayments/admin/orders/test-mode-map', $json);
|
||||
$this->assertStringContainsString('/api/plugins/sirsoft-pay_nicepayments/admin/orders/easy-pay-display-map', $json);
|
||||
$this->assertStringContainsString('sirsoft-pay_nicepayments.admin.test_mode_badge', $json);
|
||||
}
|
||||
|
||||
@@ -32,7 +35,9 @@ class EnsureAdminOrderLayoutExtensionsListenerTest extends TestCase
|
||||
$twice = $listener->ensureTestBadgeLayout($once, 1);
|
||||
|
||||
$this->assertSame(1, $this->countDataSources($twice, 'nicepay_test_map'));
|
||||
$this->assertSame(1, $this->countDataSources($twice, 'nicepay_easy_pay_display_map'));
|
||||
$this->assertSame(1, $this->countString($twice, 'nicepay_test_map.data?.[row.order_number] === true'));
|
||||
$this->assertSame(1, $this->countString($twice, 'nicepay_easy_pay_display_map.data?.[row.order_number]?.payment_method_display_label ?? row.payment?.payment_method_label'));
|
||||
}
|
||||
|
||||
public function test_detail_listener_ensures_test_notice_and_payment_panels(): void
|
||||
|
||||
Reference in New Issue
Block a user