AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Test the payment paths you can't trigger in staging: a declined card, a duplicate webhook, a timeout. A self-hosted sandbox for the 2C2P and Omise APIs.
Developer toolsJavaScript · HTMLGitHub @surakiartyskOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Sandbox that emulates the 2C2P and Omise payment APIs, so an integration can be tested against declines, timeouts and duplicate callbacks on demand. Unofficial.
Taken from the repository README
Loading the full README.
Read the full README ↗A self-hosted sandbox that emulates the 2C2P and Omise payment APIs, so you can test an integration against every response a gateway can return — including the ones you cannot trigger on demand in a real staging environment.
Point your backend at it instead of the provider, then drive the transaction: settle it, decline it with a specific issuer code, make an inquiry time out, or deliver the same webhook twice out of order. A built-in callback inspector captures what your webhook would have received, so the whole request → callback loop stays in one place.
Try it: paygate-sandbox.vercel.app — sign in with demo for
sample payments of your own.
Unofficial, and not affiliated with 2C2P or Omise. Request and response shapes follow each provider's public API documentation. This is a testing tool: it processes no real money, holds no real credentials, and must never be pointed at a production system.
Integrating a payment gateway is mostly about the unhappy paths. A card that works is the easy part. The rest is what happens when the issuer declines, when the callback arrives before your database commit, when the same webhook is delivered twice, or when an inquiry hangs for thirty seconds.
Those cases are hard to produce against a provider's own sandbox: you cannot ask it to decline with code 4051 on demand, replay a webhook, or introduce latency. So they get tested by hand once and then never again. This sandbox makes each of them a single API call, which means they can live in an automated test suite.
npm install
npm run dev
The landing page is at http://localhost:3000, the dashboard at /dashboard (default password
mockpay, or demo for a visitor's view with sample payments of its own). No build step and no database — storage falls back to JSON files under data/ when no
KV store is configured.
Create a payment the way your backend would, and watch the callback arrive:
# Get a session on the built-in callback inspector
SESSION=$(curl -s -X POST http://localhost:3000/api/inspect | sed 's/.*"sessionId":"\([^"]*\)".*/\1/')
# 1. Request a payment token
curl -X POST http://localhost:3000/api/2c2p/token \
-H 'Content-Type: application/json' \
-d "{
\"invoiceNo\": \"INV-0001\",
\"amount\": 1500,
\"currencyCode\": \"THB\",
\"backendReturnUrl\": \"http://localhost:3000/api/inspect/${SESSION}\"
}"
# 2. Settle it. This changes the payment's status and delivers nothing
curl -X POST http://localhost:3000/api/admin/payments/INV-0001/status \
-H 'Content-Type: application/json' \
-H 'X-Admin-Password: mockpay' \
-d '{"status":"success","respCode":"0000"}'
# 3. Deliver the callback. This sends a webhook and does not change the status
curl -X POST http://localhost:3000/api/admin/payments/INV-0001/callback \
-H 'Content-Type: application/json' \
-H 'X-Admin-Password: mockpay' \
-d '{"sequence":[{"status":"success","respCode":"0000","delayAfter":0}]}'
# 4. Read back what your webhook would have received
curl http://localhost:3000/api/inspect/${SESSION}
The two are separate on purpose: a callback your endpoint never receives, or one that disagrees with the stored status, is a case worth testing (scenarios). The same flow runs from the landing page with one click, if you would rather watch it than type it.
Speaks the real API shapes. Token requests, inquiries, charges and webhooks follow the providers' published schemas down to the response codes, so your integration needs no sandbox-specific branches around the transaction itself. The exception is payload encryption, which this sandbox does not implement: see What it does not do.
Drives the failure cases. Decline with a specific issuer code, make an inquiry time out or return an error, add latency globally or per payment, or fail a configurable percentage of requests.
Callback sequences. Deliver an ordered series of callbacks with delays between them — pending, then success, then a duplicate — which is how you prove a webhook handler is actually idempotent.
Built-in callback inspector. A callback endpoint inside the sandbox captures what was delivered, headers included. No external request-bin, no tunnel to your laptop.
Custom callback fields. Merchant integrations usually carry extra correlation data through a transaction. Attach arbitrary key/value pairs to a callback and save the combinations you use often as presets.
Payload encryption. 2C2P's production API wraps requests and responses in JWT/JWE with a merchant key pair; this sandbox speaks plain JSON only. That is a deliberate limit, not an oversight: reproducing the crypto would mean shipping a key exchange and would make the sandbox harder to point at than the thing it replaces, while testing almost nothing about your own integration.
The consequence is worth stating plainly. If your integration builds a signed payload before sending, that signing step is not exercised here, and pointing it at this sandbox needs the encryption layer disabled in test configuration. Two ways round it:
Either way, the crypto layer needs its own test against the provider's real sandbox at least once. This tool is for the state machine around it: the declines, the duplicates, the ordering and the timeouts.
| Document | Contents |
|---|---|
| docs/api.md | Every endpoint, with request and response examples |
| docs/scenarios.md | Recipes for the cases worth testing, as runnable curl |
| docs/architecture.md | How it is put together, and why |
| docs/decisions.md | Why it is shaped this way, and what each choice cost |
| docs/deployment.md | Deploying to Vercel, and hardening a public instance |
| openapi.yaml | Machine-readable spec — import straight into Postman or Insomnia |
Everything is optional; the defaults give a working local instance.
| Variable | Default | Purpose |
|---|---|---|
ADMIN_PASSWORD | mockpay locally | Guards the dashboard and /api/admin/*. On a deployment, unset means the admin routes are refused |
MOCK_SERVER_URL | inferred | Public origin, used to build redirect and inspector URLs |
DEFAULT_CALLBACK_URL | — | Fallback callback target when a request omits one |
URL_REWRITE_RULES | — | Rewrite callback hosts, e.g. api.example.com=>staging-api.example.com |
ALLOW_PRIVATE_CALLBACKS | true locally, false once deployed | Whether callbacks may target private and loopback addresses. An explicit value wins either way |
RATE_LIMIT_MAX | 60 | Requests per IP per minute; 0 disables |
TRUST_PROXY | unset | Set true behind a proxy, so the limit counts the client rather than the proxy's address (why) |
MOCK_MAX_DELAY_MS | 30000 | Ceiling on a delay a caller requests by header or per-payment setting |
DEMO_TTL_SECONDS | 86400 | How long a demo visitor and their sample payments last |
DEMO_MAX_LIVE_PAYMENTS | 2000 | Live visitor payments before demo sign-in answers 503 |
PAYMENT_TTL_SECONDS | 604800 | How long a payment is kept after it is first saved (a week) |
KV_REST_API_URL / KV_REST_API_TOKEN | — | Use Vercel KV instead of local JSON files |
DATA_DIR | ./data | Where the local JSON files live (ignored when KV is configured) |
See .env.example for the annotated version.
npm test
Node's built-in test runner — no test framework dependency. The suite boots a real server against a throwaway data directory and drives it over HTTP, covering both providers' flows, the callback inspector, the admin API, and the SSRF guard.
api/ Serverless handlers (Vercel file-based routing)
2c2p/ 2C2P provider API
omise/ Omise provider API
admin/ Dashboard API — payments, config, logs
inspect/ Built-in callback receiver
demo/ Seed data and the guided scenario
lib/ Domain logic shared by the handlers
public/ Landing page, dashboard and inspector (vanilla JS, no build step)
test/ The suite — drives a real server over HTTP
MIT — see LICENSE.
PORT3000 |
Port of the local dev server (npm run dev) |
HOST | 127.0.0.1 | Address the local dev server listens on. Set 0.0.0.0 to reach it from another machine |