์ด์ API: payment-backend-tan.vercel.app
์น ๋๋ฉ ํ์ด์ง ์์ด API ๊ฒฝ๋ก๋ก ์๋ตํฉ๋๋ค. ์ํ ํ์ธ์ ํ์ฉ๋ Origin์ ํฌํจํด ์์ฒญํฉ๋๋ค.
curl -H "Origin: https://ai-ing.org" https://payment-backend-tan.vercel.app/api/v1/healthHono + Vercel Serverless + Turso DB ํ๊ฒฝ ์์ ๊ตฌ์ถ๋ ๋ํ๋ฏผ๊ตญ PG์ฌ(ํ ์คํ์ด๋จผ์ธ , ์นด์นด์คํ์ด, ์นด๋ ๋ฑ) ์ฐ๋์ ์ํ PortOne V2 ์ด๋ํฐ ๋ฐ ํด์ธ ๊ฒฐ์ ๋ฅผ ์ํ PayPal Checkout ํตํฉ ๊ฒฐ์ API ์ฝ์ด ๋ฐฑ์๋์ ๋๋ค.
๊ฒฐ์ ์ ์ ๋ขฐ์ฑ ํ๋ณด ๋ฐ ์ ๋ฐ ์ ์ฐ/๊ฐ์ฌ๋ฅผ ์ํด ์๋์ ๊ฐ์ ํ์ค ํ๊ณ์์ฅ(Ledger) ๋ฐ ์ค๋ณต์์ฒญ๋ฐฉ์ง(Idempotency) ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๋๋ค.
flowchart LR
Order["Order\n์ฃผ๋ฌธ/๊ฒฐ์ ์๋ ์๋ขฐ"]
Payment["PaymentAttempt\nPG์ฌ/๊ฒฐ์ ์๋จ ์๋ ์ด๋ ฅ"]
ProviderEvent["ProviderEvent\nPG/PayPal ์ธ๋ถ ์ํ ๋ก๊ทธ"]
Settlement["SettlementRecord\n์์๋ฃ/์์ก/์ง๊ธ ์ ์ฐ"]
Ledger["LedgerEntry\nํ๊ณ ์์ฅ ๋ณต์ ๋ถ๊ธฐ ๊ธฐ์
"]
Audit["AuditLog\n์ํ ์ ์ด ๋ฐ ํ์ ์ด๋ ฅ"]
Idem["IdempotencyRecord\n์ค๋ณต ํธ๋์ญ์
๋ฐฉ์ง"]
Order --> Payment
Payment --> ProviderEvent
ProviderEvent --> Settlement
Settlement --> Ledger
Order --> Audit
Payment --> Audit
Idem -. protects .-> Order
Idem -. protects .-> Payment
orders: ์ฃผ๋ฌธ ํน์ ๊ธฐ๋ถ ์์ฒญ ์๋. ์ฃผ๋ฌธ๊ธ์ก, ํตํ(KRW, USD ๋ฑ), ํ์ฌ ์ํ ์ ์ฅ.payment_attempts: ๊ฒฐ์ ์ฌ(PortOne ๋๋ PayPal) ๊ฑฐ๋ ๊ณ ์ ์๋ณ์์ ์ฐ๊ฒฐ๋ ์ค์ ๊ฒฐ์ ์น์ธ/์๋ ๊ธฐ๋ก.provider_events: PG/PayPal๋ก๋ถํฐ ์์ ๋ Webhook ์๋ณธ ๋ฐ API ์๋ต ์์ธ ํ์ด๋ก๋ ๋ณด๊ด.settlement_records: ๊ฒฐ์ ๋ํ ์์๋ฃ(Fee), ๋งค์ถ ์์ก(Net) ๋ฐ ์ ์ฐ(Payout) ๋๊ธฐ ๊ด๋ฆฌ.ledger_entries: ๊ฑฐ๋๋ณ ์ฐจ๋ณ(Debit)/๋๋ณ(Credit) ํ๊ณ ์์ฅ ๊ธฐ๋ก.idempotency_records: ์ค๋ณต ๊ฒฐ์ ์๋ ๋ฐ ๋คํธ์ํฌ ์ฌ์์ฒญ ์ค๋ฅ ๋ฐฉ์ง์ฉ ํค ๊ฒ์ฆ ํ ์ด๋ธ.
๊ตญ๋ด ์ ์ฉ์นด๋, ์นด์นด์คํ์ด, ํ ์คํ์ด ๋ฑ์ PortOne SDK๋ฅผ ํตํด ํ๋ก ํธ์๋์์ ๊ฒฐ์ ์ฐฝ์ ํธ์ถํ ๋ค ๋ฐฑ์๋์์ ๊ฒ์ฆ ๋ฐ ์ ์ฐ์ ์งํํฉ๋๋ค.
sequenceDiagram
participant User as ์ฌ์ฉ์
participant Front as ํ๋ก ํธ์๋ (React)
participant Back as ๋ฐฑ์๋ (Hono)
participant DB as ๋ฐ์ดํฐ๋ฒ ์ด์ค (Turso)
participant PG as PortOne / PG์ฌ
User->>Front: ๊ฒฐ์ /๊ธฐ๋ถ ๊ธ์ก ์
๋ ฅ
Front->>Back: POST /api/v1/orders (์ฃผ๋ฌธ ์์ฑ)
Back->>DB: orders + idempotency ์์ฑ
Back-->>Front: orderId ๋ฐํ
Front->>PG: PortOne.requestPayment() ํธ์ถ (PG์ฐฝ ํ์ฑํ)
User->>PG: ์ธ์ฆ ๋ฐ ๊ฒฐ์ ์น์ธ ์๋ฃ
PG-->>Front: ๊ฒฐ์ ์ฑ๊ณต ์๋ต (paymentId, txId ๋ฐํ)
Front->>Back: POST /api/v1/orders/{orderId}/payment-attempts/portone (์น์ธ ์ฌ์ค ์ ๋ฌ)
Back->>DB: payment_attempts ๊ธฐ๋ก ๋ฐ orders ์ํ 'PAID'๋ก ์
๋ฐ์ดํธ
Back->>DB: ์ ์ฐ ๊ธฐ๋ก(settlement_records) & ํ๊ณ ์์ฅ(ledger_entries) ๊ธฐ์
Back-->>Front: ์ฒ๋ฆฌ ์๋ฃ ์๋ต
Front-->>User: ๊ฒฐ์ ์๋ฃ ํ์ด์ง ํ์
ํด์ธ ์นด๋๋ PayPal Orders/Capture API ๋ฐ Webhook ๋ฐฑ์คํฑ ๊ฒ์ฆ์ ๋ฐํ์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
sequenceDiagram
participant User as ์ฌ์ฉ์
participant Front as ํ๋ก ํธ์๋ (React)
participant Back as ๋ฐฑ์๋ (Hono)
participant DB as ๋ฐ์ดํฐ๋ฒ ์ด์ค (Turso)
participant PP as PayPal API
User->>Front: ๊ฒฐ์ /๊ธฐ๋ถ ์ ๋ณด ์
๋ ฅ
Front->>Back: POST /api/v1/orders
Back-->>Front: orderId
Front->>Back: POST /api/v1/orders/{orderId}/payment-attempts/paypal
Back->>PP: /v2/checkout/orders (์ฃผ๋ฌธ ์์ฑ ์์ฒญ)
PP-->>Back: PayPal Order ID + approveUrl ๋ฐํ
Back-->>Front: redirectUrl (approveUrl) ์ ๋ฌ
Front->>User: PayPal ๋ก๊ทธ์ธ/์ฒดํฌ์์ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
User->>PP: ๊ฒฐ์ ์น์ธ
PP-->>Back: ๋ฆฌํด URL ํธ์ถ (GET /paypal/return?attemptId=...)
Back->>PP: /orders/{id}/capture (๊ฒฐ์ ์บก์ฒ ํ์ )
Back->>DB: ์ํ ์
๋ฐ์ดํธ ๋ฐ ์ ์ฐ/์์ฅ ์ผ๊ด ๊ธฐ์
Back-->>Front: ๊ฒฐ์ ์ฑ๊ณต ํ์ด์ง ์ด๋
POST /api/v1/orders: ์ ๊ท ๊ฒฐ์ ์ฃผ๋ฌธ ์์ฑ (๋ฉฑ๋ฑ์ฑ ํค ์ ๊ณต ๊ฐ๋ฅ)POST /api/v1/orders/:orderId/payment-attempts/portone: PortOne PG์ฌ ๊ฒฐ์ ์น์ธ ๋ด์ญ ์ฐ๋ ๋ฐ ์์ฅ ๊ธฐ์POST /api/v1/orders/:orderId/payment-attempts/paypal: PayPal ๊ฒฐ์ ์น์ธ ์์ฒญ ๋ฐ์กGET /paypal/return: PayPal ์น์ธ ํ ์บก์ฒ ์ฝ๋ฐฑ ๊ฒฝ๋กPOST /api/v1/webhooks/paypal: PayPal ์ ์ฐ ๋ฐ์ดํฐ ์ฑํฌ ๋ฐ ๋ฐฑ์คํฑ ์นํGET /api/v1/orders/:orderId: ํน์ ์ฃผ๋ฌธ ์์ธ ์กฐํ
GET /api/v1/admin/dashboard: ์ ์ฒด ๊ฑฐ๋์ก, ์์๋ฃ, ์๋จ๋ณ ์ ์ ์จ ํต๊ณ ๋์๋ณด๋GET /api/v1/admin/tables: ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ ๋ชฉ๋ก ๋ฐํGET /api/v1/admin/tables/:tableName/rows: ํน์ ํ ์ด๋ธ ๋ฐ์ดํฐ ์กฐํ (ํ์ด์ง ์ง์)PATCH /api/v1/admin/tables/:tableName/rows/:rowId: ์๋ ์ ์ฐ ์ํ ๋ฐ ๋น์ ์ ๊ฑฐ๋ ์ ์ ์กฐ์น
์๋ ๋๋ฉ์ธ๊ณผ ์๊ฒฉ์ฆ๋ช ์ ์ค์ ์์์ด๋ฉฐ, ํ์ฌ ์ด์ API ์ฃผ์๋ ์ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
FRONTEND_BASE_URL=https://pay.ai-ing.org
PUBLIC_BASE_URL=https://api.pay.ai-ing.org
CORS_ALLOWED_ORIGINS=https://pay.ai-ing.org,https://www.ai-ing.org
# Admin
ADMIN_PASSWORD=your_secure_password
# Database (Turso SQL DB)
TURSO_DATABASE_URL=libsql://your-db-name.turso.io
TURSO_AUTH_TOKEN=your_turso_auth_token
# PayPal
PAYPAL_ENV=sandbox # sandbox or live
PAYPAL_CLIENT_ID=your_paypal_client_id
PAYPAL_CLIENT_SECRET=your_paypal_client_secret
PAYPAL_WEBHOOK_ID=your_paypal_webhook_id๋ณธ ๊ฒฐ์ ์ฝ์ด ๋ฐฑ์๋ ํ๋ก์ ํธ๋ **MIT License**์ ์๊ฑฐํ์ฌ ๋ฐฐํฌ๋ฉ๋๋ค.