KISA 제보 취약점(KVE-2026-1914/1919/2019/2029/2041/2042/2043/2044)과 그 수정 과정에서 드러난 자격증명 전송로 결함을 함께 해소한다. globalHeaders 는 데이터소스와 apiCall 핸들러에만 적용되는데, 코어 ApiClient 를 직접 부르는 경로들이 그 사실을 모른 채 게이트된 엔드포인트를 호출하고 있었다. 서버는 정당한 사용자를 거부하고 화면은 이미 버튼을 내준 뒤라, 예외도 로그도 없이 그 자리만 비는 형태로만 드러났다. 전송로 10축을 전수 열거해 6건을 고치고, 같은 실수가 반복되지 않도록 규정과 coverage 에 등재했다. 아웃바운드 프록시가 사이트 자기 자신으로 가는 내부 요청까지 가로채 저장이 수십 초씩 걸리던 문제도 함께 고쳤다. 실패가 폴백으로 삼켜져 화면에는 지연으로만 나타났다.
18 lines
35 KiB
JavaScript
18 lines
35 KiB
JavaScript
(function(){"use strict";function De(e){return/^https?:\/\//i.test(e)||e.startsWith("/api/")?e:e.startsWith("/plugins/")?`/api${e}`:e.startsWith("plugins/")?`/api/${e}`:e}function je(e){return e.trim().slice(0,160)}function Be(e){if(e)return e.replace(/\/payment\/close-report$/,"/payment/retry")}async function oe(e,t,n=!1){const r=window.G7Core?.api;if(typeof r?.post=="function"){await r.post(e,t);return}const o=await fetch(De(e),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),keepalive:n});if(!o.ok){let a=`NHN KCP payment request failed (${o.status})`;try{const s=await o.json(),i=Array.isArray(s?.errors?.message)?s.errors.message[0]:s?.error??s?.message;typeof i=="string"&&i.trim()!==""&&(a=i)}catch{}throw new Error(a)}}async function He(e){const t=Be(e.closeReportUrl);t&&await oe(t,{oid:e.oid,price:Number(e.price),buyer_email:e.buyer_email??"",buyer_phone:e.buyer_phone??"",payment_method:e.payment_method??""})}const T="g7:sirsoft-pay_nhnkcp:pendingClose";function M(){try{return window.sessionStorage??null}catch{return null}}function Fe(e){const t=M();if(t)try{t.setItem(T,JSON.stringify(e))}catch{}}function ae(){const e=M();if(e)try{e.removeItem(T)}catch{}}function Ge(){const e=M();if(!e)return null;try{const t=e.getItem(T);if(!t)return null;const n=JSON.parse(t);return n&&typeof n.oid=="string"&&n.oid!==""?n:null}catch{return null}}async function Ue(){const e=Ge();if(!e)return;let t;try{t=new URLSearchParams(window.location.search)}catch{return}const n=t.get("orderId")??"";if(n!==""&&n!==e.oid)return;if(/\/(complete|success)(\/|$|\?)/.test(window.location.pathname)){ae();return}const r=t.get("error")??"",o=t.get("message")??"";r===""&&n===""||(ae(),await se(e,o!==""?o:r))}async function se(e,t="kcp-window-closed"){if(!e.closeReportUrl)return;const n={oid:e.oid,price:Number(e.price),buyer_email:e.buyer_email??"",buyer_phone:e.buyer_phone??"",payment_method:e.payment_method??"",reason:je(t)||"kcp-window-closed"};try{await oe(e.closeReportUrl,n,!0)}catch(r){console.warn("[sirsoft-pay_nhnkcp] failed to report payment window close",r)}}const qe="nhnkcp_applepay";function R(e){return e===qe}function x(){if(typeof navigator>"u")return!1;const e=navigator,t=(e.userAgent||"").toLowerCase(),n=((e.userAgentData?.platform??e.platform)||"").toLowerCase(),r=e.maxTouchPoints??0;return/iphone|ipad|ipod/.test(t)||/iphone|ipad|ipod|ios/.test(n)?!0:/macintosh|mac os x/.test(t)&&r>1}function L(){return((typeof document<"u"?document.documentElement.lang:"")||"").toLowerCase().startsWith("ko")?"애플페이는 IOS 기기에 모바일결제만 가능합니다.":"Apple Pay is available only for mobile payments on iOS devices."}function O(e){return(e??"KRW").trim().toUpperCase()||"KRW"}function Ye(e){return O(e)==="KRW"}const We=["testpay.kcp.co.kr","pay.kcp.co.kr"];function ze(e,t){const n=window?.G7Core?.t;if(typeof n!="function")return t;const r=`sirsoft-pay_nhnkcp.${e}`,o=n(r);return typeof o=="string"&&o!==r?o:t}function Je(e){let t=null;try{t=new URL(e)}catch{t=null}if(t===null||t.protocol!=="https:"||!We.includes(t.hostname.toLowerCase()))throw new Error(ze("payment.error.sdk_url_untrusted","결제 모듈 주소가 올바르지 않아 결제를 진행할 수 없습니다."))}function Ve(e){const t=O(e);return((typeof document<"u"?document.documentElement.lang:"")||"").toLowerCase().startsWith("ko")?`NHN KCP는 KRW 결제만 지원합니다. (${t})`:`NHN KCP supports KRW payments only. (${t})`}function Qe(e){const t=Math.max(0,Math.round(Number(e.amount??0))),n=Math.min(Math.max(0,Math.round(Number(e.tax_free_amount??0))),t);if(n<=0)return{};const r=Math.max(0,Math.round(Number(e.tax_amount??0))),o=Math.max(0,Math.round(Number(e.vat_amount??0))),a=Math.max(0,t-n),s=a>0&&r>0&&o>0?Math.min(a,Math.round(a*(o/r))):0,i=a-s;return{tax_flag:"TG03",comm_tax_mny:String(i),comm_vat_mny:String(s),comm_free_mny:String(n)}}class $ extends Error{constructor(t){super(t??"결제가 취소되었습니다."),this.name="KcpCancelledError"}}const Xe={card:"100000000000",bank_transfer:"010000000000",virtual_account:"001000000000",mobile:"000010000000",bank:"010000000000",vbank:"001000000000",phone:"000010000000"},ie={nhnkcp_payco:{payco_direct:"Y"},nhnkcp_naverpay:{naverpay_direct:"Y"},nhnkcp_naverpay_point:{naverpay_direct:"Y",naverpay_point_direct:"Y"},nhnkcp_kakaopay:{kakaopay_direct:"Y"},nhnkcp_applepay:{applepay_direct:"Y"}};function Ze(e,t){const n=e.toLowerCase();return!t||!Object.prototype.hasOwnProperty.call(ie,n)?{}:{param_opt_1:n,nhnkcp_easy_pay_method:n}}function et(e){if(!e.isConnected)return!0;const t=window.getComputedStyle(e);if(t.display==="none"||t.visibility==="hidden")return!0;const n=e.getBoundingClientRect();return n.width<=0||n.height<=0}function tt(e,t){let n=!1,r,o;const a=()=>{n=!0,o&&(o.disconnect(),o=void 0),r!==void 0&&(window.clearInterval(r),r=void 0)},s=()=>{n||!et(e)||(a(),t())},i=document.body??document.documentElement;return i&&typeof MutationObserver<"u"&&(o=new MutationObserver(s),o.observe(i,{attributes:!0,attributeFilter:["class","hidden","style"],childList:!0,subtree:!0})),r=window.setInterval(s,300),a}function nt(){if(typeof navigator>"u")return!1;if(x())return!0;const e=navigator;if(e.userAgentData?.mobile!==void 0)return e.userAgentData.mobile;const t=(navigator.userAgent||"").toLowerCase();return!!(/android|iphone|ipod|windows phone|iemobile|blackberry|opera mini|mobile safari/.test(t)||(navigator.maxTouchPoints??0)>1&&!t.includes("windows")&&!t.includes("macintosh"))}async function rt(e,t){const{pgPaymentData:n,paymentMethod:r}=e.params||{};if(!n){console.error("[sirsoft-pay_nhnkcp] pgPaymentData is required");return}const o=r??n.payment_method??n.pay_method??"card",a=typeof o=="string"&&o.startsWith("nhnkcp_"),s=window.G7Core;let i=null;try{if(!Ye(n.currency)){const u=Ve(n.currency);s?.state?.setLocal?.({paymentErrorMessage:u,isSubmittingOrder:!1,paymentMethod:o}),s?.modal?.open?.("nhnkcp_payment_error_modal");return}if(R(o)&&!x()){const u=L();s?.state?.setLocal?.({paymentErrorMessage:u,isSubmittingOrder:!1,paymentMethod:o}),s?.modal?.open?.("nhnkcp_payment_error_modal");return}const c=await s.api.get("/modules/sirsoft-ecommerce/payments/client-config/nhnkcp");if(!c.data){console.error("[sirsoft-pay_nhnkcp] Failed to fetch client config",c);return}const d=c.data;i={closeReportUrl:d.callback_urls.close_report,oid:n.order_number,price:Number(n.amount),buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??"",payment_method:o};const l=window.location.origin+d.callback_urls.callback;await He(i),Fe(i),nt()?await ot(s,n,o,a,l):await at(d,n,o,a,l)}catch(c){if(c instanceof $){i&&await se(i,c.message||"kcp-window-closed"),s?.state?.setLocal?.({isSubmittingOrder:!1,paymentMethod:o});return}console.error("[sirsoft-pay_nhnkcp] requestPayment error",c);const l=c?.response?.data,u=l?.errors?Object.values(l.errors)[0]?.[0]:void 0,p=l?.error??l?.message??u??(c instanceof Error?c.message:"Unknown error");s?.state?.setLocal?.({paymentErrorMessage:p,isSubmittingOrder:!1,paymentMethod:o}),s?.modal?.open?.("nhnkcp_payment_error_modal")}}async function ot(e,t,n,r,o){const a=await e.api.post("/plugins/sirsoft-pay_nhnkcp/mobile/approval-key",{order_number:t.order_number,amount:t.amount,good_name:t.order_name,pay_method:n,buyr_name:t.customer_name??"",buyr_mail:t.customer_email??"",buyr_tel1:t.customer_phone??"",ret_url:o,currency:O(t.currency)});if(!a.success||!a.data){const d=a.errors,l=d?Object.values(d)[0]?.[0]:void 0;throw new Error(a.error??a.message??l??"KCP 모바일 승인키 획득 실패")}const{pay_url:s,fields:i}=a.data,c=document.createElement("form");c.method="POST",c.action=s,c.acceptCharset="euc-kr",c.style.display="none";for(const[d,l]of Object.entries(i)){const u=document.createElement("input");u.type="hidden",u.name=d,u.value=String(l),c.appendChild(u)}document.body.appendChild(c),c.submit(),await new Promise(()=>{})}async function at(e,t,n,r,o){const a=r?"100000000000":Xe[t.pay_method??"card"]??"100000000000",i={site_cd:n==="nhnkcp_payco"?e.easy_pay_client_id??e.client_id:e.use_escrow&&!r?e.escrow_client_id??e.client_id:e.client_id,ordr_idxx:t.order_number,good_name:t.order_name,good_mny:String(t.amount),buyr_name:t.customer_name??"",buyr_mail:t.customer_email??"",buyr_tel1:t.customer_phone??"",pay_method:a,Ret_URL:o};if(Object.assign(i,Qe(t)),Object.assign(i,Ze(n,r)),a==="001000000000"&&(i.vcnt_expire_term=String(e.vbank_expire_days??3),i.disp_tax_yn="N"),e.use_escrow&&!r&&(i.escw_used="Y",i.pay_mod="O"),r){i.payco_direct="",i.naverpay_direct="A",i.kakaopay_direct="A",i.applepay_direct="A";const d=ie[n];d&&Object.assign(i,d)}const c=Object.entries(i).map(([d,l])=>`<input type="hidden" name="${d}" value="${l.replace(/"/g,""")}">`).join("");Je(e.sdk_url),await new Promise((d,l)=>{document.getElementById("kcp-sdk-iframe")?.remove(),document.getElementById("kcp-dim-overlay")?.remove();const u=document.createElement("div");u.id="kcp-dim-overlay",u.style.cssText="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:99998;",document.body.appendChild(u);const p=document.createElement("iframe");p.id="kcp-sdk-iframe",p.setAttribute("allowtransparency","true"),p.style.cssText="position:fixed;top:0;left:0;width:100%;height:100%;border:0;z-index:99999;background:transparent;",document.body.appendChild(p);const f=p.contentWindow;let ee=!1,y,k;const gn=()=>{k?.(),k=void 0,y!==void 0&&(clearTimeout(y),y=void 0),p.remove(),u.remove()},A=m=>{ee||(ee=!0,gn(),l(m))},wn=()=>{ee=!0,k?.(),k=void 0,y!==void 0&&(clearTimeout(y),y=void 0)};k=tt(p,()=>{A(new $("kcp-payment-frame-closed"))}),f.m_Completepayment=function(m){const S=m.elements.res_cd?.value??"",ne=m.elements.res_msg?.value??"";if(S==="0000"){wn();const h=f.document.forms.namedItem("order_info"),C=h&&h!==m?h:m;if(h&&h!==m)for(let re=0;re<m.elements.length;re++){const I=m.elements[re];if(!I.name)continue;let w=h.elements.namedItem(I.name);w||(w=f.document.createElement("input"),w.type="hidden",w.name=I.name,h.appendChild(w)),w.value=I.value}C.action=o,C.method="POST",C.target="_top",f.document.body.appendChild(C),C.submit()}else{const h=S===""||S==="7777"||ne.includes("취소");A(h?new $(ne):new Error(`KCP 오류 [${S}]: ${ne}`))}},f.__kcpFail=m=>{A(m)};const te=p.contentDocument||f.document;te.open(),te.write(`<!DOCTYPE html><html><head>
|
|
<script src="${e.sdk_url}"><\/script>
|
|
</head><body style="margin:0;padding:0;background:transparent;">
|
|
<form name="order_info">${c}</form>
|
|
<script>
|
|
try {
|
|
if (typeof KCP_Pay_Execute === 'function') {
|
|
KCP_Pay_Execute(document.forms['order_info']);
|
|
window.__kcpReady && window.__kcpReady();
|
|
} else {
|
|
window.__kcpFail && window.__kcpFail(new Error('KCP_Pay_Execute not defined'));
|
|
}
|
|
} catch(e) {
|
|
window.__kcpFail && window.__kcpFail(e);
|
|
}
|
|
<\/script>
|
|
</body></html>`),te.close(),y=window.setTimeout(()=>{A(new Error("KCP SDK load timeout"))},15e3),f.__kcpReady=()=>{y!==void 0&&(clearTimeout(y),y=void 0)}})}const st={nhnkcp_payco:"PAYCO",nhnkcp_naverpay:"네이버페이",nhnkcp_naverpay_point:"네이버페이 포인트",nhnkcp_kakaopay:"카카오페이",nhnkcp_applepay:"Apple Pay"},it="0 0 0 2px #ffffff, 0 0 0 5px rgba(0,0,0,0.55)";function ce(){return document.querySelector(".nhnkcp-easy-pay-btns")??null}function de(){const e=ce();e&&e.querySelectorAll("button").forEach(t=>{t.style.boxShadow="",t.style.outline=""})}function ct(e){const t=ce();if(!t)return;const n=st[e];t.querySelectorAll("button").forEach(r=>{r.textContent?.trim()===n?(r.style.boxShadow=it,r.style.outline="none"):(r.style.boxShadow="",r.style.outline="")})}function dt(e){const t=e.params?.paymentMethod;if(!t)return;const n=window.G7Core;if(R(t)&&!x()){const o=L();n?.state?.setLocal?.({paymentErrorMessage:o,isSubmittingOrder:!1}),n?.modal?.open?.("nhnkcp_payment_error_modal"),de();return}const r=typeof t=="string"&&t.startsWith("nhnkcp_");n?.state?.setLocal?.({paymentMethod:t}),r?ct(t):de()}function lt(e){const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly","readonly"),t.style.position="fixed",t.style.left="-9999px",t.style.top="0",document.body.appendChild(t),t.select();try{document.execCommand("copy")}finally{t.remove()}}async function ut(e){const t=String(e.params?.text??"");if(t!==""){if(navigator.clipboard&&typeof navigator.clipboard.writeText=="function"){await navigator.clipboard.writeText(t);return}lt(t)}}const le={requestPayment:rt,setPaymentMethod:dt,copyToClipboard:ut},ue="/api/modules/sirsoft-ecommerce/user/orders",K="sirsoft-pay_nhnkcp",pe={info:(...e)=>console.info(`[${K}]`,...e),warn:(...e)=>console.warn(`[${K}]`,...e),error:(...e)=>console.error(`[${K}]`,...e)};function pt(e){return typeof e=="string"?e:e instanceof URL?e.toString():typeof Request<"u"&&e instanceof Request?e.url:String(e)}function mt(e,t){return t?.method?t.method.toUpperCase():typeof Request<"u"&&e instanceof Request?e.method.toUpperCase():"GET"}function ft(e,t){if(t!=="POST")return!1;const n=e.split("?")[0].split("#")[0];return n===ue||n.endsWith(ue)}function yt(e){try{return JSON.parse(e).payment_method}catch{return}}function ht(e){return new Response(JSON.stringify({success:!1,message:e,error:e,errors:{payment_method:[e]}}),{status:422,statusText:"Unprocessable Content",headers:{"Content-Type":"application/json"}})}function _t(){if(typeof window>"u"||typeof window.fetch!="function")return;const e="__sirsoftNhnkcpInterceptorInstalled",t=window;if(t[e])return;t[e]=!0;const n=window.fetch.bind(window);window.fetch=async function(o,a){const s=pt(o),i=mt(o,a);if(!ft(s,i))return n(o,a);let c;if(a?.body&&typeof a.body=="string"&&(c=yt(a.body)),R(c)&&!x()){const d=L();return pe.warn(d),ht(d)}return n(o,a)},pe.info("order request interceptor installed (Apple Pay device guard)")}function gt(){return localStorage.getItem("auth_token")}function wt(){try{const t=sessionStorage.getItem("g7_guest_order_token");if(t)return t}catch{}const e=window.G7Core?.state?.get?.("_global")?.guestOrderToken;return typeof e=="string"&&e!==""?e:null}function v(){const e=gt(),t=wt();if(!e&&!t)return null;const n={Accept:"application/json"};return e?n.Authorization=`Bearer ${e}`:t&&(n["X-Guest-Order-Token"]=t),n}const D="sirsoft-pay_nhnkcp",me="__kcpOcReceiptInjectorInstalled",fe="kcp-oc-receipt-btn",bt=/^\/shop\/orders\/([^/]+)\/complete$/;async function kt(e){const t=v();if(!t)return null;try{const n=await fetch(`/api/modules/sirsoft-ecommerce/user/orders/${e}`,{headers:t,credentials:"same-origin"});return n.ok?(await n.json())?.data?.payment??null:null}catch{return null}}async function j(e){const t=v();if(!t)return null;try{const n=await fetch(`/api/plugins/${D}/user/orders/${e}/receipt`,{headers:t,credentials:"same-origin"});return n.ok?await n.json():null}catch{return null}}function ye(e,t,n){const r=document.createElement("button");return r.type="button",r.className=t,r.textContent=e,r.addEventListener("click",()=>n(r)),r}function Ct(e){if(!e)return!1;const t=Array.from(document.querySelectorAll("div"));for(const n of t){const r=Array.from(n.children).filter(s=>s instanceof HTMLElement&&s.tagName==="SPAN");if(r.length<2)continue;const o=r[0].textContent?.trim();if(o!=="결제 방법"&&o!=="결제수단"&&o!=="결제 방식")continue;const a=r[r.length-1];return a.textContent?.trim()===e?(n.dataset.nhnkcpPaymentMethodRow="true",!0):(a.textContent=e,a.dataset.nhnkcpPaymentMethodPatched="true",n.dataset.nhnkcpPaymentMethodRow="true",!0)}return!1}async function xt(e){if(document.getElementById(fe))return;const t=await kt(e);if(!t||t.pg_provider!=="nhnkcp"||!t.transaction_id||!t.paid_at)return;const n=Array.from(document.querySelectorAll('button[type="button"]')).find(c=>c.className.includes("bg-blue-600"));if(!n?.parentElement)return;const r=await j(e);Ct(r?.payment_method_display_label);const o=n.parentElement,a=ye("영수증 조회",n.className.replace(/bg-blue-\d+/g,"bg-green-600").replace(/hover:bg-blue-\d+/g,"hover:bg-green-700"),async c=>{c.disabled=!0,c.textContent="로딩 중...";const d=await j(e);if(c.disabled=!1,c.textContent="영수증 조회",d){const l=window.KcpReceiptPopup;l&&new l({url:d.receipt_url??void 0})}});a.id=fe;const s=o.lastElementChild;o.insertBefore(a,s);const i="kcp-oc-cash-receipt-btn";if(!document.getElementById(i)&&(t.payment_method==="vbank"||t.payment_method==="bank_transfer")){const c=ye("현금영수증 조회",a.className.replace(/bg-green-\d+/g,"bg-teal-600").replace(/hover:bg-green-\d+/g,"hover:bg-teal-700"),async d=>{d.disabled=!0,d.textContent="로딩 중...";const l=await j(e);if(d.disabled=!1,d.textContent="현금영수증 조회",l){const u=window.KcpReceiptPopup;u&&new u({cash_url:l.cash_receipt_url??void 0})}});c.id=i,o.insertBefore(c,s)}console.info(`[${D}] receipt button injected on order complete page`)}function vt(){const t=location.pathname.match(bt);t&&xt(t[1])}function Pt(){if(typeof window>"u")return;const e=window;if(e[me])return;e[me]=!0,console.info(`[${D}] order complete receipt injector installed`);const t=(r=1200)=>setTimeout(vt,r);document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>t()):t();const n=history.pushState.bind(history);history.pushState=(...r)=>{n(...r),t()},window.addEventListener("popstate",()=>t(500))}const _="sirsoft-pay_nhnkcp",he="__kcpMpShowInjectorInstalled",B="kcp-mp-vbank-row",H="kcp-mp-receipt-row",P="kcp-mp-mock-deposit",Et=/^(?:\/mypage\/orders\/([^/]+)|\/shop\/guest\/orders\/([^/]+))$/;function Nt(e){try{const n=window.G7Core?.getState,a=n?.()?.currentDataContext?.order?.data;return!a||a.order_number!==e?null:a}catch{return null}}async function _e(e){const t=v();if(!t)return null;try{const n=await fetch(`/api/plugins/${_}/user/orders/${e}/receipt`,{headers:t,credentials:"same-origin"});return n.ok?await n.json():null}catch{return null}}async function ge(e){const t=v();if(!t)return null;try{const n=await fetch(`/api/plugins/${_}/user/orders/${e}/vbank-mock-deposit-info`,{headers:t,credentials:"same-origin"});return n.ok?await n.json():null}catch{return null}}function we(e){const t=document.createElement("div");t.id=P,t.className="pt-4 mt-2 border-t border-dashed border-orange-300 dark:border-orange-700";const n=document.createElement("div");n.className="flex items-center gap-2 mb-3";const r=document.createElement("span");r.className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300 border border-orange-300 dark:border-orange-700",r.textContent="TEST";const o=document.createElement("p");o.className="text-sm font-semibold text-orange-700 dark:text-orange-300",o.textContent="모의입금처리",n.appendChild(r),n.appendChild(o);const a=document.createElement("form");a.method="POST",a.action=e.mock_url??"",a.target="_blank",a.className="space-y-2";const s=[{label:"KCP 거래번호",name:"e_trade_no",value:e.trade_no??""},{label:"입금계좌",name:"deposit_no",value:e.account_no??""},{label:"입금자명",name:"req_name",value:"NHN KCP",editable:!0},{label:"입금통보 URL",name:"noti_url",value:e.notify_url??""}];for(const d of s){const l=document.createElement("div");l.className="flex items-center gap-2 text-sm";const u=document.createElement("span");u.className="w-28 flex-shrink-0 text-gray-500 dark:text-gray-400 text-xs",u.textContent=d.label;const p=document.createElement("input");p.type="text",p.name=d.name,p.value=d.value,p.readOnly=!d.editable,p.className=d.editable?"flex-1 px-2 py-1 text-xs border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono":"flex-1 px-2 py-1 text-xs border border-gray-200 dark:border-gray-700 rounded bg-gray-50 dark:bg-gray-800 text-gray-700 dark:text-gray-300 font-mono",l.appendChild(u),l.appendChild(p),a.appendChild(l)}const i=document.createElement("div");i.className="pt-1";const c=document.createElement("button");if(c.type="submit",c.className="w-full py-1.5 text-xs font-semibold rounded border border-orange-400 dark:border-orange-600 bg-orange-50 dark:bg-orange-900/30 text-orange-700 dark:text-orange-300 hover:bg-orange-100 dark:hover:bg-orange-900/50 transition-colors",c.textContent="입금통보 테스트",i.appendChild(c),a.appendChild(i),t.appendChild(n),t.appendChild(a),e.is_admin_view){const d=document.createElement("p");d.className="mt-2 text-xs text-center text-orange-500 dark:text-orange-400 font-medium",d.textContent="관리자에게만 출력되는 내용",t.appendChild(d)}return t}function be(){const e=document.getElementById("order_payment_info_panel");if(e)return Array.from(e.children).find(a=>a.className?.includes("space-y"))??e;const t=Array.from(document.querySelectorAll("h3")).find(o=>o.textContent?.includes("결제 정보"));if(!t)return null;const n=t.parentElement?.parentElement;return n?Array.from(n.children).find(o=>o.className?.includes("space-y"))??n:null}function b(e,t,n="font-medium text-gray-900 dark:text-white"){const r=document.createElement("div");r.className="flex items-center justify-between text-sm";const o=document.createElement("span");o.className="text-gray-500 dark:text-gray-400",o.textContent=e;const a=document.createElement("span");return a.className=n,a.textContent=t,r.appendChild(o),r.appendChild(a),r}function At(e,t){if(!t)return!1;const n=Array.from(e.querySelectorAll("div"));for(const r of n){const o=Array.from(r.children).filter(i=>i instanceof HTMLElement&&i.tagName==="SPAN");if(o.length<2)continue;const a=o[0].textContent?.trim();if(a!=="결제 방법"&&a!=="결제수단"&&a!=="Payment Method")continue;const s=o[o.length-1];return s.textContent?.trim()===t?(r.dataset.nhnkcpPaymentMethodRow="true",!0):(s.textContent=t,s.dataset.nhnkcpPaymentMethodPatched="true",r.dataset.nhnkcpPaymentMethodRow="true",!0)}return!1}function St(e,t){const n=document.createElement("div");n.id=B,n.className="pt-4 mt-2 border-t border-gray-200 dark:border-gray-700 space-y-3";const r=document.createElement("p");r.className="text-sm font-semibold text-gray-800 dark:text-gray-200",r.textContent="가상계좌 입금 안내";const o=document.createElement("div");o.className="space-y-1.5",o.appendChild(b("은행",e.vbank_name??"")),o.appendChild(b("계좌번호",e.vbank_number??"","font-medium text-gray-900 dark:text-white font-mono tracking-wide")),o.appendChild(b("예금주",e.vbank_holder??"")),o.appendChild(b("입금 금액",t)),e.due_date_formatted&&o.appendChild(b("입금 기한",e.due_date_formatted));const a=document.createElement("p");return a.className="text-xs text-amber-600 dark:text-amber-400",a.textContent="입금 기한 내에 입금하지 않으시면 주문이 자동 취소됩니다.",n.appendChild(r),n.appendChild(o),n.appendChild(a),n}function It(e){const t=document.createElement("div");t.id=H,t.className="pt-4 mt-2 border-t border-gray-200 dark:border-gray-700";const n=document.createElement("div");n.className="flex items-center justify-between";const r=document.createElement("span");r.className="text-gray-500 dark:text-gray-400 text-sm",r.textContent="영수증";const o=document.createElement("button");return o.type="button",o.className="inline-flex items-center gap-1 text-sm text-blue-600 dark:text-blue-400 hover:underline disabled:opacity-50",o.textContent="영수증 조회",o.addEventListener("click",async()=>{o.disabled=!0,o.textContent="로딩 중...";const a=await _e(e);if(o.disabled=!1,o.textContent="영수증 조회",a){const s=window.KcpReceiptPopup;s&&new s({url:a.receipt_url,cash_url:a.cash_receipt_url})}}),n.appendChild(r),n.appendChild(o),t.appendChild(n),t}async function Tt(e){const t=Nt(e);if(!t){if(!document.getElementById(P)){const p=be();if(!p)return!1;const f=await ge(e);f?.available&&(p.appendChild(we(f)),console.info(`[${_}] mock deposit form injected on mypage order show`))}return!0}const{payment:n}=t;if(!n||n.pg_provider!=="nhnkcp")return!0;const r=n.payment_method==="vbank"&&!!n.vbank_name,o=!!n.transaction_id&&!!n.paid_at,a=n.payment_method==="vbank"&&!!n.vbank_number,s=!r||!!document.getElementById(B),i=!o||!!document.getElementById(H),c=!a||!!document.getElementById(P);if(s&&i&&c)return!0;const d=be();if(!d)return!1;const l=o?await _e(e):null,u=At(d,l?.payment_method_display_label);if(!document.getElementById(B)&&r)return d.appendChild(St(n,t.total_amount_formatted??"")),console.info(`[${_}] vbank info injected on mypage order show`),!1;if(!document.getElementById(H)&&o)return d.appendChild(It(e)),console.info(`[${_}] receipt button injected on mypage order show`),!1;if(u&&console.info(`[${_}] payment method display patched on mypage order show`),!document.getElementById(P)&&a){const p=await ge(e);if(p?.available)d.appendChild(we(p)),console.info(`[${_}] mock deposit form injected on mypage order show`);else return!0}return!0}function Mt(e){let t=0;const n=setInterval(()=>{t++,Tt(e).then(r=>{(r||t>=30)&&clearInterval(n)})},400)}function Rt(){const e=location.pathname.match(Et),t=e?.[1]??e?.[2];t&&Mt(t)}function Lt(){if(typeof window>"u")return;const e=window;if(e[he])return;e[he]=!0,console.info(`[${_}] mypage order show injector installed`);const t=(r=1500)=>setTimeout(Rt,r);document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>t()):t();const n=history.pushState.bind(history);history.pushState=(...r)=>{n(...r),t(600)},window.addEventListener("popstate",()=>t(500))}const Ot="sirsoft-pay_nhnkcp",ke="__kcpAdminApplePayNoticeInjectorInstalled",$t=/^\/admin\/ecommerce\/settings\/?$/,Ce="__kcpAdminApplePayNoticeListenerAttached",Kt="애플페이는 IOS 기기에 모바일결제만 가능합니다.",Dt="Apple Pay is available only for mobile payments on iOS devices.";let xe=null,E=null,F=!1;function jt(){return window}function G(){if(!$t.test(window.location.pathname))return!1;const e=new URLSearchParams(window.location.search).get("tab");return e===null||e==="order_settings"}function ve(){return(document.documentElement.lang||navigator.language||"").toLowerCase().startsWith("ko")?Kt:Dt}function Pe(e){return(e??"").replace(/\s+/g," ").trim()}function Bt(e){const t=Pe(e.textContent);return t==="애플페이"||t==="Apple Pay"}function Ht(){const e=document.createElement("span");return e.dataset.nhnkcpAdminApplePayNotice="true",e.className="text-label-subtle",e.style.display="block",e.style.width="100%",e.style.flexBasis="100%",e.style.marginTop="2px",e.style.fontSize="12px",e.style.lineHeight="1rem",e.textContent=ve(),e}function U(e=document){if(typeof window>"u"||typeof document>"u"||!G()||Pe(document.body?.innerText??document.body?.textContent).includes(ve()))return!1;const n=Array.from(e.querySelectorAll("span, p, div")).find(Bt);return n?(n.insertAdjacentElement("afterend",Ht()),!0):!1}function Ft(){F||(F=!0,window.setTimeout(()=>{F=!1,U()},0))}function Ee(){E!==null&&(window.clearInterval(E),E=null)}function q(){if(!G())return;Ee(),U();let e=0;E=window.setInterval(()=>{e+=1,(U()||e>=50)&&Ee()},200);const t=document.body;t[Ce]||(t[Ce]=!0,xe=new MutationObserver(()=>Ft()),xe.observe(document.body,{childList:!0,subtree:!0,characterData:!0}))}function Ne(){G()&&q()}function Gt(){if(typeof window>"u"||typeof document>"u")return;const e=jt();if(e[ke])return;e[ke]=!0,console.info(`[${Ot}] admin Apple Pay notice injector installed`),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>q()):q();const t=history.pushState.bind(history);history.pushState=(...n)=>{t(...n),window.setTimeout(()=>Ne(),200)},window.addEventListener("popstate",()=>window.setTimeout(()=>Ne(),200))}const Ut="sirsoft-pay_nhnkcp",Ae="__nhnkcpAdminPaymentMethodBrandInjectorInstalled",Se="__nhnkcpAdminPaymentMethodBrandSyncListenerAttached",qt=/^\/admin\/ecommerce\/settings\/?$/,Ie='[data-nhnkcp-admin-payment-brand-mark="true"]',Yt=200,Wt=120,zt=[{id:"nhnkcp_payco",labels:["PAYCO (NHN KCP)","PAYCO로 결제 (NHN KCP)","Pay with PAYCO (NHN KCP)"],shortLabels:["PAYCO"],markLines:["P"],markClassName:"bg-red-500 text-white"},{id:"nhnkcp_naverpay",labels:["네이버페이 (NHN KCP)","네이버페이 신용카드로 결제 (NHN KCP)","Naver Pay (NHN KCP)","Pay by Naver Pay credit card (NHN KCP)"],shortLabels:["네이버페이 (카드)","Naver Pay (Card)"],markLines:["N"],markClassName:"bg-green-500 text-white"},{id:"nhnkcp_naverpay_point",labels:["네이버페이 포인트 (NHN KCP)","네이버페이 머니/포인트로 결제 (NHN KCP)","Naver Pay Point (NHN KCP)","Pay with Naver Pay Money/Points (NHN KCP)"],shortLabels:["네이버페이 (포인트)","Naver Pay (Point)"],markLines:["NP"],markClassName:"bg-green-600 text-white"},{id:"nhnkcp_kakaopay",labels:["카카오페이 (NHN KCP)","카카오페이로 결제 (NHN KCP)","Kakao Pay (NHN KCP)","Pay with Kakao Pay (NHN KCP)"],shortLabels:["카카오페이","Kakao Pay"],markLines:["K"],markClassName:"bg-yellow-400 text-gray-950"},{id:"nhnkcp_applepay",labels:["애플페이 (NHN KCP)","애플페이로 결제 (NHN KCP)","Apple Pay (NHN KCP)","Pay with Apple Pay (NHN KCP)"],shortLabels:["애플페이","Apple Pay"],markLines:["A"],markClassName:"bg-gray-900 text-white"}];let Te=null,N=null,Y=!1;function Jt(){return window}function W(){if(!qt.test(window.location.pathname))return!1;const e=new URLSearchParams(window.location.search).get("tab");return e===null||e===""||e==="order_settings"}function Vt(e){return e instanceof HTMLElement?e.className:""}function Me(e){return(e??"").replace(/\u200B/g,"").replace(/\s+/g," ").trim()}function Qt(e){const t=Vt(e);return t.includes("excel-card")||t.includes("flex-center")&&t.includes("border")&&t.includes("gap-4")}function Re(e){return e.querySelector(".font-medium")}function Xt(e){const t=Me(Re(e)?.textContent),n=Me(e.textContent);return zt.find(r=>r.shortLabels.some(o=>t===o)||r.labels.some(o=>n.includes(o)))??null}function Zt(e){return Array.from(e.querySelectorAll("div")).filter(Qt)}function en(e){return Array.from(e.querySelectorAll("svg, [data-icon], i")).find(n=>!n.closest("[data-drag-handle]")&&!n.closest(Ie)&&!n.closest('.row-stack, .excel-card-body, button, select, [role="switch"]'))??null}function Le(e,t){e.dataset.nhnkcpAdminPaymentBrandMark="true",e.dataset.nhnkcpAdminPaymentMethod=t.id,e.setAttribute("aria-hidden","true"),e.className=`inline-flex items-center justify-center rounded-lg font-bold ${t.markClassName}`,e.style.width="32px",e.style.height="32px",e.style.flex="0 0 32px",e.style.lineHeight="1",e.style.fontSize=t.markLines.join("").length>2?"9px":"12px";const n=t.markLines.join("");e.textContent!==n&&(e.replaceChildren(),t.markLines.length>1?(e.style.flexDirection="column",t.markLines.forEach(r=>{const o=document.createElement("span");o.style.lineHeight="1",o.textContent=r,e.appendChild(o)})):(e.style.removeProperty("flex-direction"),e.textContent=t.markLines[0]??""))}function tn(e){const t=document.createElement("span");return Le(t,e),t}function nn(e,t){const n=e.querySelector(Ie);if(n instanceof HTMLSpanElement)return e.dataset.nhnkcpAdminPaymentMethod=t.id,Le(n,t),!0;const r=tn(t),o=en(e);if(o&&o.parentElement)return e.dataset.nhnkcpAdminPaymentMethod=t.id,o.replaceWith(r),!0;const a=Re(e);return a&&a.parentElement?(e.dataset.nhnkcpAdminPaymentMethod=t.id,a.parentElement.insertBefore(r,a),!0):!1}function z(e=document){if(typeof window>"u"||typeof document>"u"||!W())return!1;let t=!1;return Zt(e).forEach(n=>{const r=Xt(n);r&&nn(n,r)&&(t=!0)}),t}function rn(){Y||(Y=!0,window.setTimeout(()=>{Y=!1,z()},0))}function J(){N!==null&&(window.clearInterval(N),N=null)}function V(){if(!W())return;J(),z();let e=0;N=window.setInterval(()=>{e+=1,z(),e>=Wt&&J()},Yt);const t=document.body;t[Se]||(t[Se]=!0,Te=new MutationObserver(()=>rn()),Te.observe(document.body,{childList:!0,subtree:!0,characterData:!0}))}function Q(){if(W()){V();return}J()}function on(){if(typeof window>"u"||typeof document>"u")return;const e=Jt();if(e[Ae])return;e[Ae]=!0,console.info(`[${Ut}] admin payment method brand sync installed`),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>V()):V();const t=history.pushState.bind(history);history.pushState=(...r)=>{t(...r),window.setTimeout(()=>Q(),200)};const n=history.replaceState.bind(history);history.replaceState=(...r)=>{n(...r),window.setTimeout(()=>Q(),200)},window.addEventListener("popstate",()=>window.setTimeout(()=>Q(),200))}const X="sirsoft-pay_nhnkcp",Oe="__nhnkcpAdminPaymentDisplayInjectorInstalled",an=/^\/admin\/ecommerce\/orders\/([^/]+)$/;function sn(){return localStorage.getItem("auth_token")}async function cn(e){const t=sn();if(!t)return null;try{const n=await fetch(`/api/plugins/${X}/admin/orders/${e}/transaction-status`,{headers:{Authorization:`Bearer ${t}`,Accept:"application/json"}});if(!n.ok)return null;const r=await n.json();return("data"in r?r.data:r)??null}catch{return null}}function dn(e){return Array.from(e.children).filter(t=>t instanceof HTMLElement)}function ln(e,t){let n=!1;const r=Array.from(e.querySelectorAll("div"));for(const o of r){const a=dn(o),s=a.findIndex(u=>u.textContent?.trim()==="결제수단");if(s<0)continue;const i=a.slice(s+1).find(u=>u.textContent?.trim()&&u.textContent.trim()!=="결제수단");if(!i)continue;const c=a.slice(s+2).find(u=>/^\(.+\)$/.test(u.textContent?.trim()??"")),d=c?.textContent?.trim().replace(/^\(|\)$/g,"")??"",l=d&&t.endsWith(")")?t.replace(/\)$/,`, ${d})`):t;i.textContent?.trim()!==l&&(i.textContent=l,i.dataset.nhnkcpPaymentMethodPatched="true"),o.dataset.nhnkcpPaymentMethodRow="admin-order",c&&d&&(c.textContent="",c.style.display="none",c.dataset.nhnkcpPaymentInstallmentHidden="true"),n=!0}return n}function un(e,t,n){const o=Array.from(e.querySelectorAll("span")).filter(a=>a.textContent?.trim()===t).find(a=>{const s=typeof a.className=="string"?a.className:"";return s.includes("rounded-full")||s.includes("font-medium")});return o?(o.textContent=n,o.dataset.nhnkcpPaymentMethodPatched="true",o.dataset.nhnkcpPaymentMethodBadge="admin-order",!0):!1}function pn(e,t){const n=t?._pay_method_label,r=t?._embedded_pg_provider_label,o=t?._base_pay_method_label;if(!n||!r)return!1;const a=ln(e,n),s=o?un(e,o,r):!1;return a||s}async function mn(e){const t=document.getElementById("section_payment_info")??document.getElementById("payment_info_card");if(!t)return!1;const n=await cn(e);return n&&pn(t,n)&&console.info(`[${X}] payment method display patched on admin order detail`),!0}function fn(e){let t=0;const n=setInterval(()=>{t++,mn(e).then(r=>{(r||t>=30)&&clearInterval(n)})},400)}function yn(){const e=location.pathname.match(an);e&&fn(e[1])}function hn(){if(typeof window>"u")return;const e=window;if(e[Oe])return;e[Oe]=!0,console.info(`[${X}] admin order payment display injector installed`);const t=(r=1500)=>setTimeout(yn,r);document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>t()):t();const n=history.pushState.bind(history);history.pushState=(...r)=>{n(...r),t(600)},window.addEventListener("popstate",()=>t(500))}class _n{constructor(t){const n="width=800,height=600,scrollbars=yes,resizable=yes,toolbar=no,menubar=no";t.url&&window.open(t.url,"kcp_receipt",n),t.cash_url&&window.open(t.cash_url,"kcp_cash_receipt",n)}}window.KcpReceiptPopup=_n;const g="sirsoft-pay_nhnkcp",Z={info:(...e)=>console.info(`[${g}]`,...e),warn:(...e)=>console.warn(`[${g}]`,...e),error:(...e)=>console.error(`[${g}]`,...e)};function $e(){const e=window.G7Core;if(!e)return 0;const t=e.getActionDispatcher;if(typeof t!="function")return 0;const n=t();if(!n||typeof n.registerHandler!="function")return 0;let r=0;for(const[o,a]of Object.entries(le)){const s=`${g}.${o}`;n.registerHandler(s,a,{category:"plugin",source:g}),r++}return r}function Ke(){Ue();const e=()=>{const t=$e();if(t>0){Z.info(`${t} handler(s) registered`);return}let n=0;const r=50,o=setInterval(()=>{n++;const a=$e();if(a>0){clearInterval(o),Z.info(`${a} handler(s) registered (after ${n} retries)`);return}n>=r&&(clearInterval(o),Z.warn("ActionDispatcher not available after timeout"))},100)};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e):e()}_t(),Pt(),Lt(),Gt(),on(),hn(),Ke(),window.__SirsoftNhnkcp={identifier:g,handlers:Object.keys(le),initPlugin:Ke}})();
|