diff --git a/src/components/interactive-docs/PgSelect.tsx b/src/components/interactive-docs/PgSelect.tsx
index e896881c7..b417283fc 100644
--- a/src/components/interactive-docs/PgSelect.tsx
+++ b/src/components/interactive-docs/PgSelect.tsx
@@ -10,7 +10,7 @@ import kpnLogo from "~/assets/pg-circle/kpn.png";
import ksnetLogo from "~/assets/pg-circle/ksnet.png";
import naverLogo from "~/assets/pg-circle/naver.png";
import niceLogo from "~/assets/pg-circle/nice.png";
-import smatroLogo from "~/assets/pg-circle/smartro.png";
+import smartro from "~/assets/pg-circle/smartro.png";
import tossLogo from "~/assets/pg-circle/toss.png";
import { type Pg, useInteractiveDocs } from "~/state/interactive-docs";
@@ -21,7 +21,7 @@ export type PgSelectOption = {
const PgOptions = {
nice: { label: "나이스페이먼츠", icon: niceLogo },
- smatro: { label: "스마트로", icon: smatroLogo },
+ smartro: { label: "스마트로", icon: smartro },
toss: { label: "토스페이먼츠", icon: tossLogo },
kpn: { label: "한국결제네트웍스", icon: kpnLogo },
inicis: { label: "이니시스", icon: inicisLogo },
diff --git a/src/routes/(root)/opi/ko/quick-guide/_preview/Preview.tsx b/src/routes/(root)/opi/ko/quick-guide/_preview/Preview.tsx
index cf66c7600..ef19bdc54 100644
--- a/src/routes/(root)/opi/ko/quick-guide/_preview/Preview.tsx
+++ b/src/routes/(root)/opi/ko/quick-guide/_preview/Preview.tsx
@@ -44,15 +44,19 @@ export function Preview() {
const requestPayment = async () => {
if (paymentStatus().status === "PENDING") return undefined;
+ const paymentId = crypto
+ .getRandomValues(new Uint32Array(1))[0]!
+ .toString(16)
+ .padStart(8, "0");
const request = match(untrack(() => params))
.with(
{ pg: { name: "toss", payMethods: "card" } },
() =>
({
storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
- paymentId: crypto.randomUUID(),
- orderName: "테스트 결제",
- totalAmount: 100,
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
currency: "CURRENCY_KRW",
channelKey: "channel-key-ebe7daa6-4fe4-41bd-b17d-3495264399b5",
payMethod: "CARD",
@@ -64,9 +68,9 @@ export function Preview() {
() =>
({
storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
- paymentId: crypto.randomUUID(),
- orderName: "테스트 결제",
- totalAmount: 100,
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
currency: "CURRENCY_KRW",
channelKey: "channel-key-ebe7daa6-4fe4-41bd-b17d-3495264399b5",
payMethod: "VIRTUAL_ACCOUNT",
@@ -82,9 +86,9 @@ export function Preview() {
() =>
({
storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
- paymentId: crypto.randomUUID(),
- orderName: "테스트 결제",
- totalAmount: 100,
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
currency: "CURRENCY_KRW",
channelKey: "channel-key-4ca6a942-3ee0-48fb-93ef-f4294b876d28",
payMethod: "CARD",
@@ -97,9 +101,9 @@ export function Preview() {
() =>
({
storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
- paymentId: crypto.randomUUID(),
- orderName: "테스트 결제",
- totalAmount: 100,
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
currency: "CURRENCY_KRW",
channelKey: "channel-key-e6c31df1-5559-4b4a-9b2c-a35793d14db2",
payMethod: "VIRTUAL_ACCOUNT",
@@ -111,6 +115,198 @@ export function Preview() {
redirectUrl: "https://sdk-playground.portone.io/",
}) satisfies PortOne.PaymentRequest,
)
+ .with(
+ { pg: { name: "smartro", payMethods: "card" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-c4a4b281-a1e5-40c9-8140-f055262bcefd",
+ payMethod: "CARD",
+ card: {},
+ redirectUrl: "https://sdk-playground.portone.io/",
+ customer: {
+ phoneNumber: "01012341234",
+ },
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "smartro", payMethods: "virtualAccount" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-c4a4b281-a1e5-40c9-8140-f055262bcefd",
+ payMethod: "VIRTUAL_ACCOUNT",
+ virtualAccount: {
+ accountExpiry: {
+ validHours: 1,
+ },
+ },
+ customer: {
+ phoneNumber: "01012341234",
+ },
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "inicis", payMethods: "card" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-fc5f33bb-c51e-4ac7-a0df-4dc40330046d",
+ payMethod: "CARD",
+ card: {},
+ customer: {
+ fullName: "포트원",
+ email: "example@portone.io",
+ phoneNumber: "01012341234",
+ },
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "inicis", payMethods: "virtualAccount" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-fc5f33bb-c51e-4ac7-a0df-4dc40330046d",
+ payMethod: "VIRTUAL_ACCOUNT",
+ virtualAccount: {
+ accountExpiry: {
+ validHours: 1,
+ },
+ },
+ customer: {
+ fullName: "포트원",
+ email: "example@portone.io",
+ phoneNumber: "01012341234",
+ },
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "kcp", payMethods: "card" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-a79920e0-a898-49f0-aab7-50aa6834848f",
+ payMethod: "CARD",
+ card: {},
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "kcp", payMethods: "virtualAccount" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-a79920e0-a898-49f0-aab7-50aa6834848f",
+ payMethod: "VIRTUAL_ACCOUNT",
+ virtualAccount: {
+ accountExpiry: {
+ validHours: 1,
+ },
+ },
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "kpn", payMethods: "card" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-bcbb1622-ff80-49d5-adef-49191fda8ede",
+ payMethod: "CARD",
+ card: {},
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "kpn", payMethods: "virtualAccount" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-bcbb1622-ff80-49d5-adef-49191fda8ede",
+ payMethod: "VIRTUAL_ACCOUNT",
+ virtualAccount: {
+ accountExpiry: {
+ validHours: 1,
+ },
+ },
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "ksnet", payMethods: "card" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-4a5daa34-aecb-44af-aaad-e42384acfb6e",
+ payMethod: "CARD",
+ card: {},
+ customer: {
+ fullName: "포트원",
+ },
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
+ .with(
+ { pg: { name: "ksnet", payMethods: "virtualAccount" } },
+ () =>
+ ({
+ storeId: "store-e4038486-8d83-41a5-acf1-844a009e0d94",
+ paymentId,
+ orderName: "나이키 멘즈 조이라이드 플라이니트",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ channelKey: "channel-key-4a5daa34-aecb-44af-aaad-e42384acfb6e",
+ payMethod: "VIRTUAL_ACCOUNT",
+ virtualAccount: {
+ accountExpiry: {
+ validHours: 1,
+ },
+ },
+ customer: {
+ fullName: "포트원",
+ },
+ redirectUrl: "https://sdk-playground.portone.io/",
+ }) satisfies PortOne.PaymentRequest,
+ )
.exhaustive();
setPaymentStatus({ status: "PENDING" });
@@ -123,7 +319,7 @@ export function Preview() {
.with({ code: P.nonNullable }, () => {
setPaymentStatus({ status: "FAILED" });
})
- .with({ code: P.nullish }, () => {
+ .with(P.not({ code: P.nonNullable }), () => {
setPaymentStatus({ status: "PAID" });
})
.exhaustive(),
diff --git a/src/routes/(root)/opi/ko/quick-guide/_preview/backend/node/index.ts b/src/routes/(root)/opi/ko/quick-guide/_preview/backend/express/index.ts
similarity index 100%
rename from src/routes/(root)/opi/ko/quick-guide/_preview/backend/node/index.ts
rename to src/routes/(root)/opi/ko/quick-guide/_preview/backend/express/index.ts
diff --git a/src/routes/(root)/opi/ko/quick-guide/_preview/backend/node/server.js.ts b/src/routes/(root)/opi/ko/quick-guide/_preview/backend/express/server.js.ts
similarity index 77%
rename from src/routes/(root)/opi/ko/quick-guide/_preview/backend/node/server.js.ts
rename to src/routes/(root)/opi/ko/quick-guide/_preview/backend/express/server.js.ts
index 95329e68a..a064dad75 100644
--- a/src/routes/(root)/opi/ko/quick-guide/_preview/backend/node/server.js.ts
+++ b/src/routes/(root)/opi/ko/quick-guide/_preview/backend/express/server.js.ts
@@ -12,8 +12,9 @@ ${({ section }) => section("server:import-portone-sdk")`
const PortOne = require("@portone/server-sdk")
`}
-const portOne = PortOne.PortOneClient(process.env.V2_API_SECRET)
+const portone = PortOne.PortOneClient(process.env.V2_API_SECRET)
+${({ section }) => section("server:complete-payment:verify-payment")`
function verifyPayment(payment) {
if (payment.customData == null) return false
const customData = JSON.parse(payment.customData)
@@ -25,6 +26,7 @@ function verifyPayment(payment) {
payment.currency === item.currency
)
}
+`}
const paymentStore = new Map()
async function syncPayment(paymentId) {
@@ -34,14 +36,16 @@ async function syncPayment(paymentId) {
})
}
const payment = paymentStore.get(paymentId)
+ ${({ section }) => section("server:complete-payment:get-payment")`
let actualPayment
try {
- actualPayment = await portOne.payment.getPayment(paymentId)
+ actualPayment = await portone.payment.getPayment(paymentId)
} catch (e) {
if (e instanceof PortOne.Errors.PortOneError) return false
throw e
}
if (actualPayment == null) return false
+ `}
switch (actualPayment.status) {
case "PAID":
if (!verifyPayment(actualPayment)) return false
@@ -49,9 +53,11 @@ async function syncPayment(paymentId) {
payment.status = "PAID"
console.info("결제 성공", actualPayment)
break
+ ${({ when }) => when(({ pg }) => pg.payMethods === "virtualAccount")`
case "VIRTUAL_ACCOUNT_ISSUED":
payment.status = "VIRTUAL_ACCOUNT_ISSUED"
break
+ `}
default:
return false
}
@@ -60,12 +66,14 @@ async function syncPayment(paymentId) {
const app = express()
+${({ section }) => section("server:webhook:raw-body")`
app.use(
"/api/payment/webhook",
bodyParser.text({
type: "application/json",
}),
)
+`}
app.use(bodyParser.json())
const items = new Map([
@@ -87,6 +95,7 @@ app.get("/api/item", (req, res) => {
})
})
+${({ section }) => section("server:complete-payment")`
app.post("/api/payment/complete", async (req, res, next) => {
try {
const { paymentId } = req.body
@@ -101,11 +110,15 @@ app.post("/api/payment/complete", async (req, res, next) => {
next(e)
}
})
+`}
+${({ section }) => section("server:webhook")`
app.post("/api/payment/webhook", async (req, res, next) => {
try {
+ ${({ section }) => section("server:webhook:verify")`
+ let webhook
try {
- await PortOne.Webhook.verify(
+ webhook = await PortOne.Webhook.verify(
process.env.V2_WEBHOOK_SECRET,
req.body,
req.headers,
@@ -115,16 +128,17 @@ app.post("/api/payment/webhook", async (req, res, next) => {
return res.status(400).end()
throw e
}
- const {
- type,
- data: { paymentId },
- } = JSON.parse(req.body)
- if (type.startsWith("Transaction.")) await syncPayment(paymentId)
+ `}
+ ${({ section }) => section("server:webhook:complete-payment")`
+ if ('data' in webhook && 'paymentId' in webhook.data)
+ await syncPayment(webhook.data.paymentId)
+ `}
res.status(200).end()
} catch (e) {
next(e)
}
})
+`}
const server = app.listen(8080, "localhost", () => {
console.log("server is running on", server.address())
diff --git a/src/routes/(root)/opi/ko/quick-guide/_preview/backend/index.ts b/src/routes/(root)/opi/ko/quick-guide/_preview/backend/index.ts
index e341544e3..2652fccdd 100644
--- a/src/routes/(root)/opi/ko/quick-guide/_preview/backend/index.ts
+++ b/src/routes/(root)/opi/ko/quick-guide/_preview/backend/index.ts
@@ -1 +1 @@
-export { files as node } from "./node";
+export { files as express } from "./express";
diff --git a/src/routes/(root)/opi/ko/quick-guide/_preview/frontend/react/app.jsx.ts b/src/routes/(root)/opi/ko/quick-guide/_preview/frontend/react/app.jsx.ts
index abf890b65..16d89bdc9 100644
--- a/src/routes/(root)/opi/ko/quick-guide/_preview/frontend/react/app.jsx.ts
+++ b/src/routes/(root)/opi/ko/quick-guide/_preview/frontend/react/app.jsx.ts
@@ -1,7 +1,28 @@
import { code } from "~/components/interactive-docs/index.jsx";
+import type { Pg } from "~/state/interactive-docs";
import type { Params, Sections } from "../../type";
+function isCustomerRequired(params: Params) {
+ return (
+ isCustomerPhoneNumberRequired(params) || isCustomerEmailRequired(params)
+ );
+}
+
+function isCustomerNameRequired(params: Params) {
+ return (["ksnet", "inicis"] satisfies Pg[] as Pg[]).includes(params.pg.name);
+}
+
+function isCustomerPhoneNumberRequired(params: Params) {
+ return (["smartro", "inicis"] satisfies Pg[] as Pg[]).includes(
+ params.pg.name,
+ );
+}
+
+function isCustomerEmailRequired(params: Params) {
+ return (["inicis"] satisfies Pg[] as Pg[]).includes(params.pg.name);
+}
+
export default code<{
params: Params;
sections: Sections;
@@ -24,6 +45,7 @@ export function App() {
status: "IDLE",
})
+ ${({ section }) => section("client:fetch-item")`
useEffect(() => {
async function loadItem() {
const response = await fetch("/api/item")
@@ -32,6 +54,7 @@ export function App() {
loadItem().catch((error) => console.error(error))
}, [])
+ `}
if (item == null) {
return (
@@ -45,19 +68,12 @@ export function App() {
e.preventDefault()
setPaymentStatus({ status: "PENDING" })
${({ section }) => section("client:request-payment")`
- ${({ section }) => section("client:payment-id-description")`
+ ${({ section }) => section("client:payment-id")`
const paymentId = randomId()
`}
const payment = await PortOne.requestPayment({
storeId: VITE_STORE_ID,
- ${({ when }) => when(({ smartRouting }) => smartRouting === false)`
channelKey: VITE_CHANNEL_KEY,
- `}
- ${({ when }) => when(({ smartRouting }) => smartRouting === true)`
- ${({ section }) => section("client:smart-routing:channel-group-id")`
- channelGroupId: VITE_CHANNEL_GROUP_ID,
- `}
- `}
paymentId,
orderName: item.name,
totalAmount: item.price,
@@ -68,13 +84,30 @@ export function App() {
${({ when }) => when(({ pg }) => pg.payMethods === "virtualAccount")`
payMethod: "VIRTUAL_ACCOUNT",
`}
+ ${({ when }) => when(isCustomerRequired)`
+ ${({ section }) => section("client:customer-data")`
+ customer: {
+ ${({ when }) => when(isCustomerNameRequired)`
+ fullName: '포트원',
+ `}
+ ${({ when }) => when(isCustomerPhoneNumberRequired)`
+ phoneNumber: '01012341234',
+ `}
+ ${({ when }) => when(isCustomerEmailRequired)`
+ email: 'example@portone.io',
+ `}
+ },
+ `}
+ `}
+ ${({ section }) => section("client:custom-data")`
customData: {
item: item.id,
},
+ `}
})
`}
${({ section }) => section("client:handle-payment-error")`
- if (payment.code != null) {
+ if (payment.code !== undefined) {
setPaymentStatus({
status: "FAILED",
message: payment.message,
@@ -93,15 +126,19 @@ export function App() {
}),
})
if (completeResponse.ok) {
+ ${({ section }) => section("client:handle-payment-status:paid")`
const paymentComplete = await completeResponse.json()
setPaymentStatus({
status: paymentComplete.status,
})
+ `}
} else {
+ ${({ section }) => section("client:handle-payment-status:failed")`
setPaymentStatus({
status: "FAILED",
message: await completeResponse.text(),
})
+ `}
}
`}
}
@@ -141,7 +178,6 @@ export function App() {
- ${({ section }) => section("client:handle-payment-status:failed")`
{paymentStatus.status === "FAILED" && (
)}
- `}
- ${({ section }) => section("client:handle-payment-status:paid")`
- `}
- ${({ section }) => section(
- "client:handle-payment-status:virtual-account-issued",
- )`
+ ${({ when }) => when(({ pg }) => pg.payMethods === "virtualAccount")`
+ ${({ section }) => section(
+ "client:handle-payment-status:virtual-account-issued",
+ )`
+
+ `}
`}
>
)
diff --git a/src/routes/(root)/opi/ko/quick-guide/_preview/index.tsx b/src/routes/(root)/opi/ko/quick-guide/_preview/index.tsx
index 3998b5c3c..47bc54ec0 100644
--- a/src/routes/(root)/opi/ko/quick-guide/_preview/index.tsx
+++ b/src/routes/(root)/opi/ko/quick-guide/_preview/index.tsx
@@ -16,6 +16,6 @@ export const { Section, InteractiveDoc, Condition, Toggle } =
smartRouting: false,
pg: { name: "toss", payMethods: "card" },
},
- initialSelectedExample: ["react", "node"],
+ initialSelectedExample: ["react", "express"],
preview: Preview,
});
diff --git a/src/routes/(root)/opi/ko/quick-guide/_preview/type.ts b/src/routes/(root)/opi/ko/quick-guide/_preview/type.ts
index 03f00133e..b2905ef86 100644
--- a/src/routes/(root)/opi/ko/quick-guide/_preview/type.ts
+++ b/src/routes/(root)/opi/ko/quick-guide/_preview/type.ts
@@ -7,6 +7,21 @@ export const pgOptions = {
nice: {
payMethods: ["card", "virtualAccount"],
},
+ smartro: {
+ payMethods: ["card", "virtualAccount"],
+ },
+ kpn: {
+ payMethods: ["card", "virtualAccount"],
+ },
+ inicis: {
+ payMethods: ["card", "virtualAccount"],
+ },
+ ksnet: {
+ payMethods: ["card", "virtualAccount"],
+ },
+ kcp: {
+ payMethods: ["card", "virtualAccount"],
+ },
} as const satisfies PgOptions;
export type Params = {
@@ -16,12 +31,21 @@ export type Params = {
export type Sections =
| "client:import-portone-sdk"
+ | "client:fetch-item"
| "client:request-payment"
- | "client:payment-id-description"
+ | "client:payment-id"
+ | "client:customer-data"
+ | "client:custom-data"
| "client:handle-payment-error"
| "client:request-server-side-verification"
| "client:handle-payment-status:failed"
| "client:handle-payment-status:paid"
| "client:handle-payment-status:virtual-account-issued"
- | "client:smart-routing:channel-group-id"
- | "server:import-portone-sdk";
+ | "server:import-portone-sdk"
+ | "server:complete-payment"
+ | "server:complete-payment:get-payment"
+ | "server:complete-payment:verify-payment"
+ | "server:webhook"
+ | "server:webhook:raw-body"
+ | "server:webhook:verify"
+ | "server:webhook:complete-payment";
diff --git a/src/routes/(root)/opi/ko/quick-guide/payment.mdx b/src/routes/(root)/opi/ko/quick-guide/payment.mdx
index 208b7da5b..042936edd 100644
--- a/src/routes/(root)/opi/ko/quick-guide/payment.mdx
+++ b/src/routes/(root)/opi/ko/quick-guide/payment.mdx
@@ -7,7 +7,9 @@ versionVariants:
v1: /opi/ko/integration/ready/readme?v=v1
---
-import {Condition, InteractiveDoc, Section, Toggle} from "./_preview";
+import Hint from "~/components/Hint";
+
+import { Condition, InteractiveDoc, Section } from "./_preview";
{/* client:import-portone-sdk
client:request-payment
@@ -20,68 +22,124 @@ client:handle-payment-status:paid */}
- ## PortOne SDK 불러오기
+ ## 포트원 브라우저 SDK 불러오기
+
+ 포트원 브라우저 SDK를 불러옵니다.
+
- PortOne SDK를 불러오기 위한 코드입니다.
+
+ ## 상품 정보 불러오기
+
+ 서버로부터 결제할 상품의 정보를 불러옵니다.
## 결제 요청
- 결제 요청을 위한 코드입니다.
+ 포트원 브라우저 SDK를 사용하여 결제를 요청합니다.
+
+
+
+ ## Payment ID 설정
-
- smartRouting === true}>
-
- ## `channelGroupId` 설정
+ Payment ID는 결제 요청 시 사용하는 고유한 ID입니다. 포트원에서 제공하지 않고 수동으로 생성해야 합니다.
- 스마트라우팅 기능을 사용하기 위해 `channelGroupId`를 설정하는 코드입니다.
-
-
-
+
+ 결제대행사에 따라 Payment ID의 형식에 제한이 있을 수 있습니다.
+
-
- ## Payment ID 설명
+ ['ksnet', 'inicis', 'smartro'].includes(name)}>
+
+ ## 고객 정보 설정
- Payment ID는 결제 요청 시 사용하는 고유한 ID입니다.
+ 결제 시 요구되는 고객 정보를 입력합니다.
+
+
+
+
+ ## Custom Data 설정
+
+ Custom Data에는 임의의 데이터를 저장할 수 있습니다. 서버에서 결제건 조회 시에 확인할 수 있으며, 상품 정보를 전달하여 서버가 인식한 상품 정보와 일치하는지 확인할 수 있습니다.
## 결제 오류 처리
- 결제 오류를 처리하는 코드입니다.
+ 결제 중 오류가 발생하여 결제가 완료되지 않은 경우를 처리합니다.
- pg.name === "toss" && pg.payMethods === "card"}>
-
- ## 토스 카드 결제 조건 테스트
+
+ ## 서버 측으로 결제 완료 요청
- 토스 카드 결제 조건 테스트
-
-
+ 완료된 결제의 Payment ID를 서버로 전송하여 결제 상태를 반영합니다.
+
-
- ## 서버 측 검증 요청
+
+ ## 결제 완료 상태 처리
- 서버 측 검증 요청을 위한 코드입니다.
+ 서버로부터 검증 후 결제가 완료된 경우를 처리합니다.
## 결제 실패 상태 처리
- 결제 실패 상태를 처리하는 코드입니다.
+ 서버로부터 검증 결과를 획득하여, 결제가 최종적으로 실패한 경우를 처리합니다.
-
- ## 결제 완료 상태 처리
+ pg.payMethods === "virtualAccount" }>
+
+ ## 가상계좌 발급 상태 처리
- 결제 완료 상태를 처리하는 코드입니다.
-
+ 서버로부터 검증 후 가상계좌가 발급된 경우를 처리합니다.
+
+
- ## PortOne 서버 SDK 불러오기
+ ## 포트원 서버 SDK 불러오기
+
+ 포트원 서버 SDK를 불러옵니다.
+
+
+
+ ## 결제 완료 요청
+
+ 완료된 결제의 실제 상태를 조회해 시스템에 반영합니다. 브라우저 SDK를 통해 결제하는 경우 모든 결제 과정이 브라우저에서 진행되므로 결제가 조작되는 것을 막기 위해 서버에서 검증이 필요합니다.
+
+
+
+ ## 결제 정보 조회
+
+ 브라우저에서 전송한 Payment ID를 통해 실제 결제 상태를 조회합니다.
+
+
+
+ ## 결제 정보 일치 검증
+
+ 포트원에 전달한 Custom Data로 조회한 상품 정보와 결제 정보가 일치하는지 검증합니다.
+
+
+
+ ## 웹훅 수신
+
+ 결제 상태의 변화를 실시간으로 확인해야 한다면 웹훅을 사용할 수 있습니다.
+
+
+
+ ## HTTP Body 수신 설정
+
+ 웹훅 내용을 검증하기 위해서는 HTTP Body를 문자열 형태로 수신해야 합니다.
+
+
+
+ ## 웹훅 검증
+
+ 수신한 웹훅이 위조되지 않았는지 포트원 서버 SDK를 사용하여 검증합니다.
+
+
+
+ ## 결제 상태 업데이트
- PortOne 서버 SDK를 불러오기 위한 코드입니다.
+ 검증된 웹훅 결과를 바탕으로 결제 상태를 업데이트합니다.
diff --git a/src/state/interactive-docs/index.tsx b/src/state/interactive-docs/index.tsx
index ace9040f6..7ca08dc62 100644
--- a/src/state/interactive-docs/index.tsx
+++ b/src/state/interactive-docs/index.tsx
@@ -34,7 +34,7 @@ export type Tab = {
export type PayMethod = "card" | "virtualAccount";
export type Pg =
| "nice"
- | "smatro"
+ | "smartro"
| "toss"
| "kpn"
| "inicis"
@@ -108,12 +108,12 @@ const [InteractiveDocsProvider, useInteractiveDocs] = createContextProvider(
hybrid: string[];
}>({
frontend: ["react", "html"],
- backend: ["node", "python"],
+ backend: ["express", "fastapi", "flask"],
hybrid: ["nextjs"],
});
const [selectedLanguage, setSelectedLanguage] = createSignal<
[frontend: string, backend: string] | string
- >(["react", "node"]);
+ >(["react", "express"]);
createEffect(() => {
setSelectedLanguage([languages().frontend[0], languages().backend[0]]);
});
@@ -245,11 +245,11 @@ const [InteractiveDocsProvider, useInteractiveDocs] = createContextProvider(
setPgOptions: (_) => {},
languages: () => ({
frontend: ["react", "html"],
- backend: ["node", "python"],
+ backend: ["express", "fastapi", "flask"],
hybrid: ["nextjs"],
}),
setLanguages: (_) => {},
- selectedLanguage: () => ["react", "node"],
+ selectedLanguage: () => ["react", "express"],
setSelectedLanguage: (_) => {},
params: {
pg: {