KISA 제보 취약점(KVE-2026-1914/1919/2019/2029/2041/2042/2043/2044)과 그 수정 과정에서 드러난 자격증명 전송로 결함을 함께 해소한다. globalHeaders 는 데이터소스와 apiCall 핸들러에만 적용되는데, 코어 ApiClient 를 직접 부르는 경로들이 그 사실을 모른 채 게이트된 엔드포인트를 호출하고 있었다. 서버는 정당한 사용자를 거부하고 화면은 이미 버튼을 내준 뒤라, 예외도 로그도 없이 그 자리만 비는 형태로만 드러났다. 전송로 10축을 전수 열거해 6건을 고치고, 같은 실수가 반복되지 않도록 규정과 coverage 에 등재했다. 아웃바운드 프록시가 사이트 자기 자신으로 가는 내부 요청까지 가로채 저장이 수십 초씩 걸리던 문제도 함께 고쳤다. 실패가 폴백으로 삼켜져 화면에는 지연으로만 나타났다.
2 lines
11 KiB
JavaScript
2 lines
11 KiB
JavaScript
(function(){"use strict";const F="sirsoft-tosspayments",f="g7:sirsoft-tosspayments:pendingClose",v="/api/plugins/sirsoft-tosspayments/payment/close-report";function p(){try{return window.sessionStorage??null}catch{return null}}function q(e){const t=p();if(t)try{t.setItem(f,JSON.stringify(e))}catch{}}function I(){const e=p();if(e)try{e.removeItem(f)}catch{}}function j(){const e=p();if(!e)return null;try{const t=e.getItem(f);if(!t)return null;const s=JSON.parse(t);return s&&typeof s.orderId=="string"&&s.orderId!==""?s:null}catch{return null}}async function B(e,t,s){const n={orderId:e.orderId,amount:Number(e.amount),buyer_email:e.buyer_email??"",buyer_phone:e.buyer_phone??"",code:t.slice(0,60),reason:s.trim().slice(0,160)},o=window.G7Core?.api;if(typeof o?.post=="function"){await o.post(v,n);return}await fetch(v,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(n),keepalive:!0})}async function H(){const e=j();if(!e)return;let t;try{t=new URLSearchParams(window.location.search)}catch{return}const s=t.get("orderId")??"";if(s!==""&&s!==e.orderId)return;if(/\/(complete|success)(\/|$|\?)/.test(window.location.pathname)){I();return}const n=t.get("error")??"",o=t.get("message")??"";if(!(n===""&&s==="")){I();try{await B(e,n,o!==""?o:n)}catch(r){console.warn(`[${F}] failed to report payment failure`,r)}}}const Y=["js.tosspayments.com"];function W(e,t){const s=window?.G7Core?.t;if(typeof s!="function")return t;const n=`sirsoft-tosspayments.${e}`,o=s(n);return typeof o=="string"&&o!==n?o:t}function J(e){let t=null;try{t=new URL(e)}catch{t=null}if(t===null||t.protocol!=="https:"||!Y.includes(t.hostname.toLowerCase()))throw new Error(W("payment.error.sdk_url_untrusted","결제 모듈 주소가 올바르지 않아 결제를 진행할 수 없습니다."))}async function V(e){if(J(e),window.TossPayments)return;document.querySelectorAll(`script[src="${CSS.escape(e)}"]`).forEach(s=>s.remove());const t=window?.G7Core?.asset?.loadScript;if(typeof t=="function"){await t(e,{},{label:"tosspayments SDK"});return}await new Promise((s,n)=>{const o=document.createElement("script");o.src=e,o.async=!0,o.onload=()=>s(),o.onerror=()=>n(new Error(`Failed to load script: ${e}`)),document.head.appendChild(o)})}function z(e,t){if(!e.order_sheet_mode||!t)return{method:"CARD",easyPay:null};const s=(e.enabled_methods??[]).find(n=>n.id===t);return s?{method:s.method,easyPay:s.easy_pay_provider}:{method:"CARD",easyPay:null}}function A(e){if(e==="on")return!0;if(e==="off")return!1}function Q(e,t){const s={validHours:e.vbank?.valid_hours??24},n=e.vbank?.cash_receipt_type??"";n&&(s.cashReceipt={type:n});const o=A(e.use_escrow);return o!==void 0&&(s.useEscrow=o),s}function L(e,t,s){if(t.use_escrow==="off")return;const n=s.escrow_products??[];n.length>0&&(e.escrowProducts=n)}async function X(e,t){const s=e.params||{},{pgPaymentData:n}=s;if(!n){console.error("[sirsoft-tosspayments] pgPaymentData is required");return}const o=window.G7Core,r=s.paymentMethod??o?.state?.getLocal?.()?.paymentMethod;try{const a=await o.api.get("/modules/sirsoft-ecommerce/payments/client-config/tosspayments");if(!a.data){console.error("[sirsoft-tosspayments] Failed to fetch client config",a);return}const i=a.data;if(window.TossPayments||await V(i.sdk_url),window.TossPayments||await new Promise(y=>setTimeout(y,100)),!window.TossPayments){console.error("[sirsoft-tosspayments] TossPayments SDK not available");return}const S=window.TossPayments(i.client_key).payment({customerKey:n.customer_key??window.TossPayments.ANONYMOUS}),C=n.currency??"KRW",{method:d,easyPay:E}=z(i,r);if(C!=="KRW"&&(d!=="CARD"||E!==null)){console.warn("[sirsoft-tosspayments] non-KRW currency supports card only",{currency:C,method:d}),o?.state?.setLocal?.({paymentErrorMessage:o?.t?.("sirsoft-tosspayments.errors.non_krw_method")??"This payment method is available for KRW only.",isSubmittingOrder:!1}),o?.modal?.open?.("tosspayments_payment_error_modal");return}const K=window.location.origin,c={method:d,amount:{currency:C,value:n.amount},orderId:n.order_number,orderName:n.order_name,successUrl:K+i.callback_urls.success,failUrl:K+i.callback_urls.fail,customerEmail:n.customer_email??void 0,customerName:n.customer_name??void 0,customerMobilePhone:n.customer_phone??void 0};if(d==="CARD")c.card={flowMode:"DEFAULT",useCardPoint:!1,useAppCardOnly:!1},E&&(c.card.flowMode="DIRECT",c.card.easyPay=E);else if(d==="VIRTUAL_ACCOUNT")c.virtualAccount=Q(i,n),L(c,i,n);else if(d==="TRANSFER"){const y=A(i.use_escrow);y!==void 0&&(c.transfer={useEscrow:y}),L(c,i,n)}q({orderId:n.order_number,amount:n.amount,buyer_email:n.customer_email??"",buyer_phone:n.customer_phone??""}),await S.requestPayment(c)}catch(a){if(console.error("[sirsoft-tosspayments] requestPayment error",a),a?.code==="USER_CANCEL"){console.info("[sirsoft-tosspayments] Payment cancelled by user");try{const u=o?.state?.get?.("_global")?.guestOrderToken,S=u?{headers:{"X-Guest-Order-Token":u}}:void 0;await o.api.post(`/modules/sirsoft-ecommerce/orders/${n.order_number}/cancel-payment`,{cancel_code:a.code,cancel_message:a.message},S)}catch(u){console.warn("[sirsoft-tosspayments] Failed to record cancellation",u)}o?.state?.setLocal?.({isSubmittingOrder:!1}),o?.modal?.open?.("tosspayments_payment_cancel_modal");return}const i=a?.message??"Unknown error";o?.state?.setLocal?.({paymentErrorMessage:i,isSubmittingOrder:!1}),o?.modal?.open?.("tosspayments_payment_error_modal")}}const N={requestPayment:X},Z="sirsoft-tosspayments",R="__tossAdminPaymentMethodBrandInjectorInstalled",M="__tossAdminPaymentMethodBrandSyncListenerAttached",ee=/^\/admin\/ecommerce\/settings\/?$/,k='[data-toss-admin-payment-brand-mark="true"]',te=200,ne=120,se=[{id:"toss_tosspay",labels:["토스페이 (토스페이먼츠)","토스페이 간편결제 — 토스페이먼츠를 통해 처리","TossPay (TossPayments)","TossPay easy payment — processed via TossPayments"],shortLabels:[],markLines:["T"],markClassName:"bg-blue-500 text-white"},{id:"toss_kakaopay",labels:["카카오페이 (토스페이먼츠)","카카오페이 간편결제 — 토스페이먼츠를 통해 처리","KakaoPay (TossPayments)","KakaoPay easy payment — processed via TossPayments"],shortLabels:[],markLines:["K"],markClassName:"bg-yellow-400 text-gray-950"},{id:"toss_naverpay",labels:["네이버페이 (토스페이먼츠)","네이버페이 간편결제 — 토스페이먼츠를 통해 처리","NaverPay (TossPayments)","NaverPay easy payment — processed via TossPayments"],shortLabels:[],markLines:["N"],markClassName:"bg-green-500 text-white"},{id:"toss_payco",labels:["페이코 (토스페이먼츠)","페이코 간편결제 — 토스페이먼츠를 통해 처리","PAYCO (TossPayments)","PAYCO easy payment — processed via TossPayments"],shortLabels:[],markLines:["P"],markClassName:"bg-red-500 text-white"},{id:"toss_samsungpay",labels:["삼성페이 (토스페이먼츠)","삼성페이 간편결제 — 토스페이먼츠를 통해 처리","Samsung Pay (TossPayments)","Samsung Pay easy payment — processed via TossPayments"],shortLabels:[],markLines:["S"],markClassName:"bg-blue-600 text-white"}];let O=null,m=null,w=!1;function oe(){return window}function h(){if(!ee.test(window.location.pathname))return!1;const e=new URLSearchParams(window.location.search).get("tab");return e===null||e===""||e==="order_settings"}function re(e){return e instanceof HTMLElement?e.className:""}function D(e){return(e??"").replace(//g,"").replace(/\s+/g," ").trim()}function ae(e){const t=re(e);return t.includes("excel-card")||t.includes("flex-center")&&t.includes("border")&&t.includes("gap-4")}function x(e){return e.querySelector(".font-medium")}function ie(e){const t=D(x(e)?.textContent),s=D(e.textContent);return se.find(n=>n.shortLabels.some(o=>t===o)||n.labels.some(o=>s.includes(o)))??null}function ce(e){return Array.from(e.querySelectorAll("div")).filter(ae)}function le(e){return Array.from(e.querySelectorAll("svg, [data-icon], i")).find(s=>!s.closest("[data-drag-handle]")&&!s.closest(k)&&!s.closest('.row-stack, .excel-card-body, button, select, [role="switch"]'))??null}function $(e,t){e.dataset.tossAdminPaymentBrandMark="true",e.dataset.tossAdminPaymentMethod=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 s=t.markLines.join("");e.textContent!==s&&(e.textContent=t.markLines[0]??"")}function de(e){const t=document.createElement("span");return $(t,e),t}function ue(e,t){const s=e.querySelector(k);if(s instanceof HTMLSpanElement)return e.dataset.tossAdminPaymentMethod=t.id,$(s,t),!0;const n=de(t),o=le(e);if(o&&o.parentElement)return e.dataset.tossAdminPaymentMethod=t.id,o.replaceWith(n),!0;const r=x(e);return r&&r.parentElement?(e.dataset.tossAdminPaymentMethod=t.id,r.parentElement.insertBefore(n,r),!0):!1}function g(e=document){if(typeof window>"u"||typeof document>"u"||!h())return!1;let t=!1;return ce(e).forEach(s=>{const n=ie(s);n&&ue(s,n)&&(t=!0)}),t}function me(){w||(w=!0,window.setTimeout(()=>{w=!1,g()},0))}function _(){m!==null&&(window.clearInterval(m),m=null)}function P(){if(!h())return;_(),g();let e=0;m=window.setInterval(()=>{e+=1,g(),e>=ne&&_()},te);const t=document.body;t[M]||(t[M]=!0,O=new MutationObserver(()=>me()),O.observe(document.body,{childList:!0,subtree:!0,characterData:!0}))}function b(){if(h()){P();return}_()}function ye(){if(typeof window>"u"||typeof document>"u")return;const e=oe();if(e[R])return;e[R]=!0,console.info(`[${Z}] admin payment method brand sync installed`),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>P()):P();const t=history.pushState.bind(history);history.pushState=(...n)=>{t(...n),window.setTimeout(()=>b(),200)};const s=history.replaceState.bind(history);history.replaceState=(...n)=>{s(...n),window.setTimeout(()=>b(),200)},window.addEventListener("popstate",()=>window.setTimeout(()=>b(),200))}const l="sirsoft-tosspayments",T={info:(...e)=>console.info(`[${l}]`,...e),warn:(...e)=>console.warn(`[${l}]`,...e),error:(...e)=>console.error(`[${l}]`,...e)};function U(){const e=window.G7Core;if(!e)return 0;const t=e.getActionDispatcher;if(typeof t!="function")return 0;const s=t();if(!s||typeof s.registerHandler!="function")return 0;let n=0;for(const[o,r]of Object.entries(N)){const a=`${l}.${o}`;s.registerHandler(a,r,{category:"plugin",source:l}),n++}return n}function G(){ye(),H();const e=()=>{const t=U();if(t>0){T.info(`${t} handler(s) registered`);return}let s=0;const n=50,o=setInterval(()=>{s++;const r=U();if(r>0){clearInterval(o),T.info(`${r} handler(s) registered (after ${s} retries)`);return}s>=n&&(clearInterval(o),T.warn("ActionDispatcher not available after timeout"))},100)};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e):e()}G(),window.__SirsoftTosspayments={identifier:l,handlers:Object.keys(N),initPlugin:G}})();
|