# Integrating point3 payments — guide for coding agents

You are helping a merchant developer add point3 payments to their project. This guide gives you the context, the rules, and a suggested path. Adapt the path to the project in front of you and use your judgement. Ask the user only when a decision affects payment correctness, security, or the scope of the work.

Use the point3 docs for technical details (endpoints, fields, messages). If two sources disagree, tell the user which passages conflict, continue with the parts that aren't affected, and leave the disputed behavior open.

- Docs index: https://docs.point3.io/llms.txt
- Any docs page as Markdown: append `.md` to its URL (e.g. https://docs.point3.io/payment-capture.md)
- Server API schema: https://docs.point3.io/openapi.yaml
- Everything in one file, if targeted pages aren't enough: https://docs.point3.io/llms-full.txt

The docs are written in Korean. Talk to the user in the language they use.

## How point3 payments work

1. **Merchant server** creates a payment session with the order amount and gets a `sessionId`.
2. **Merchant client** opens the point3 payment window with that `sessionId` using the point3 JavaScript SDK. The SDK is the only supported client integration; native apps run it inside a WebView (see below). The buyer verifies their identity and confirms the payment.
3. The payment window hands control back by navigating to the merchant's `successUrl` (or `failUrl`). In a native app, the app intercepts that navigation in the WebView. **A `successUrl` navigation is not a successful payment yet.**
4. **Merchant server** calls the capture API. The capture response, or the payment session lookup, is the only source of the final result.

There is no sandbox and no webhooks. Integration testing happens in production with small amounts that are refunded afterwards (see the go-live checklist).

## Rules

These protect money and credentials. Do not trade them off against convenience.

- The API key (`s2scred-...`) lives on the merchant server only. Never put it in browser or app code, public environment variables, or committed files. The merchant ID (`client-...`) is a public client-side identifier, not the API key.
- Do not log API keys, PINs, account details, or verification codes, including through request/response dumps.
- Create the payment session from the order amount stored on the server, never from an amount sent by the client. Store the session's `id` as the order's `sessionId` before opening the payment window.
- Never treat a `successUrl` navigation as payment success. Fulfil the order only after the capture response or session lookup returns `status: captured`, and only once per order.
- Before capture, check that the caller owns the order and that the `sessionId` from the client (SDK: `orderId`) matches the one stored on the order. Do not trust the `amount` in the redirect URL.
- If the capture result is unknown (timeout, network error, `5xx`, `202`/`processing`), look up the session with the same `sessionId`, or retry capture with it. Capture is idempotent per `sessionId`. Do not guess the outcome or start a new payment.
- A `failUrl` navigation must never trigger capture. Handle it by its `code`, not its `message`.
- Do not invent point3 endpoints, fields, parameters, or behavior. Ordinary project details (file layout, routes, framework choices) are yours to decide following the project's conventions.

## Things worth knowing

- **Credentials.** The user needs a point3 dashboard account with the payment module enabled, an API key, and their merchant ID. None of these are needed to write the integration. If the user doesn't have them yet, read them from environment variables (for example `POINT3_API_KEY`, `POINT3_CLIENT_ID`), carry on, and tell them where to get the values: https://dashboard.point3.io/app/home and the "준비하기" section of https://docs.point3.io/introduction.md. Don't ask the user to paste secrets into the chat.
- **Choosing the client integration.** Decide by what the checkout screen is built with, not by whether the product is "an app". See https://docs.point3.io/integration-methods.md.
  - **The checkout screen is a web page** — a website, or a hybrid app whose checkout runs in a WebView. Integrate the SDK into that web page exactly as for the web. Don't add native payment code; the payment window opens inside the same WebView and results arrive at the web page's `successUrl`/`failUrl`. In a hybrid app, also keep the WebView within the safe area and block system back while the payment window is open.
  - **The checkout screen is native** (React Native, Flutter, iOS, Android). Keep it native. Build a single HTML page that opens the payment window with the JavaScript SDK (the same code as a web integration), ship it as a static asset of the app, and show it in a full-screen WebView when the buyer pays. Load it with an HTTPS base URL on a domain the merchant owns: a `file://` page or one without a base URL has no origin, and the payment window won't open. Point `successUrl`/`failUrl` at that origin and intercept them in the app. Lay the WebView out within the safe area, don't put native buttons over it (the payment window has its own back button), and block system back while it's open. See https://docs.point3.io/integration-methods.md
  - If you can't tell which case applies, look at how existing screens are built before asking.
- **Capture deadline.** By policy, capture must happen before midnight (KST) on the day the buyer confirmed. In practice the daily maintenance window starts at 23:30 KST, so capture right after confirmation and before 23:30. Past the deadline the capture API returns `422 paymentCaptureExpired` and the buyer has to start over with a new session.
- **Session creation is safe to repeat.** A session costs nothing until the buyer confirms and the server captures it. If the create response is lost (timeout, network error, `5xx`), create a new session for the same order and store the new `sessionId`; don't block checkout waiting for manual reconciliation. Unused sessions are never captured.
- **Maintenance window.** Every day 23:30–00:30 KST, session creation returns `503` with `error.eob`, and buyers can't complete payments. Show the buyer the reopening time from `error.eob.endsAt`.
- **Buyer ID (`payerId`).** Store it on the merchant server, in the logged-in member's record, and never on the device (browser storage, UserDefaults, SharedPreferences, secure storage): a device-stored value can be sent for a different member who later pays on the same device. For payments, fetch the session on the server after capture and store the `payerId` from that response; the capture response doesn't include it, and a value relayed by the client is only a hint. The registration-only widget has no session to look up, so store its client-reported `payerId` only when the member has none yet; never overwrite a value confirmed through a session lookup with it. On the next payment, the server passes the member's stored `payerId` to the SDK as `customerKey`, which lets the buyer skip some verification. If the project has no member accounts, don't invent one: build guest checkout without `payerId` reuse and tell the user that saving it needs a member record. See https://docs.point3.io/payer-id.md
- **Existing Toss Payments code.** The `@tosspayments/tosspayments-sdk` package can load the point3 SDK, but only as a loader: use the version and the point3 `src` given in https://docs.point3.io/sdk-environment.md and don't assume other Toss APIs work.

## Suggested path

Skip or reorder steps when the project calls for it.

1. **Understand the project.** Find where checkout happens (web page, web page inside a WebView, or native screen), the server stack, and where orders are stored. Check for existing payment code.
2. **Choose where the SDK runs:** the checkout web page, or an HTML page shown in a WebView from the native checkout screen.
3. **Server: create sessions.** https://docs.point3.io/payment-creation.md and https://docs.point3.io/server-integration.md. If any products are tax-free or zero-rated, see https://docs.point3.io/tax-processing.md
4. **Client: open the payment window.**
   - Always: https://docs.point3.io/sdk-environment.md and https://docs.point3.io/sdk-payment-window.md
   - Native checkout screen: also https://docs.point3.io/integration-methods.md
5. **Server: capture and confirm the result.** https://docs.point3.io/payment-capture.md and https://docs.point3.io/payment-flow.md
6. **Only if the user asks:** refunds (https://docs.point3.io/payment-cancellation.md; `Idempotency-Key` reuse returns `409` rather than replaying the result, so query the status before retrying) or the registration-only widget (https://docs.point3.io/registration-integration.md), or delegated authentication for logged-in members (https://docs.point3.io/delegated-authentication.md, https://docs.point3.io/delegated-creation.md and https://docs.point3.io/sdk-delegated.md; the `payer_id` must come from the server-side member record, never from the client).
7. **Review** the result against https://docs.point3.io/go-live-checklist.md

## When you're done

Tell the user:

- which integration method you used and why
- which files you added or changed
- which values they still need to fill in, and where to get them
- what you verified and what you couldn't run (for example, live calls without credentials)
- how to test with a small real payment and refund it before going live
- anything you assumed because the docs didn't cover it
