{"openapi":"3.0.3","info":{"title":"JhaPay API","version":"1.0.0","description":"Payments platform API. Server integrations authenticate with a secret API key (`jsk_live_...`); collecting devices use a short-lived payment session client secret. Money amounts are always integer **minor units** (cents): `$25.00` → `2500`.\n\nOfficial server SDK: [`@jhax/jhapay`](https://www.npmjs.com/package/@jhax/jhapay).","contact":{"name":"JhaPay","url":"https://jhax.us"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.jhax.us","description":"Production"},{"url":"http://localhost:8080","description":"Local development"}],"tags":[{"name":"Health","description":"Service liveness."},{"name":"Auth","description":"User (dashboard) identity."},{"name":"Merchants","description":"Merchant accounts & API keys."},{"name":"Payments","description":"Server-to-server payment sessions, refunds (secret API key)."},{"name":"Checkout","description":"Device-facing collection, authenticated by the session client secret."}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","description":"Merchant secret API key: `Authorization: Bearer jsk_live_...`"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Dashboard user access token (JWT)."},"ClientSecret":{"type":"apiKey","in":"header","name":"X-Client-Secret","description":"Short-lived payment-session client secret (device side)."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string","example":"Invalid request"},"details":{}},"required":["code","message"]}}},"Tokens":{"type":"object","properties":{"accessToken":{"type":"string","description":"Short-lived JWT."},"refreshToken":{"type":"string","description":"Opaque rotating refresh token."},"expiresAt":{"type":"string","format":"date-time"}}},"Merchant":{"type":"object","properties":{"id":{"type":"string","example":"cmr..."},"businessName":{"type":"string","example":"Acme Coffee"},"email":{"type":"string","format":"email"},"status":{"type":"string","enum":["DRAFT","DETAILS_REQUIRED","SUBMITTED","UNDER_REVIEW","ACTIVE","RESTRICTED","REJECTED","CLOSED"]},"createdAt":{"type":"string","format":"date-time"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","example":"prod-server"},"keyPrefix":{"type":"string","example":"jsk_test_7a4bf20e"},"environment":{"type":"string","enum":["live","test"]},"scopes":{"type":"array","items":{"type":"string"}},"secret":{"type":"string","description":"The full secret key — returned ONCE at creation, never again.","example":"jsk_test_7a4bf20e....d-ahUyi7Ave8Jsu1b9F0"},"createdAt":{"type":"string","format":"date-time"}}},"PaymentSession":{"type":"object","properties":{"paymentSessionId":{"type":"string"},"paymentIntentId":{"type":"string"},"amount":{"type":"integer","example":2500,"description":"Minor units."},"currency":{"type":"string","example":"USD"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"clientSecret":{"type":"string","nullable":true,"description":"Hand to the collecting device. Null on an idempotent replay."},"expiresAt":{"type":"string","format":"date-time"},"reused":{"type":"boolean"}}},"PaymentStatus":{"type":"string","enum":["REQUIRES_PAYMENT_METHOD","PROCESSING","AUTHORIZED","CAPTURED","FAILED","CANCELED","REFUNDED"]},"DeviceSession":{"type":"object","properties":{"paymentSessionId":{"type":"string"},"paymentIntentId":{"type":"string"},"amount":{"type":"integer"},"currency":{"type":"string"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"consumed":{"type":"boolean"},"expired":{"type":"boolean"},"expiresAt":{"type":"string","format":"date-time"}}},"ConfirmResult":{"type":"object","properties":{"paymentSessionId":{"type":"string"},"paymentIntentId":{"type":"string"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"processorTransactionId":{"type":"string","description":"Netevia GTRC."},"note":{"type":"string"}}},"Refund":{"type":"object","properties":{"id":{"type":"string"},"paymentIntentId":{"type":"string"},"amount":{"type":"integer"},"status":{"type":"string","example":"SUCCEEDED"},"processorRefundId":{"type":"string","nullable":true},"reason":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Payment":{"type":"object","properties":{"id":{"type":"string"},"merchantId":{"type":"string"},"amount":{"type":"integer"},"currency":{"type":"string"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"createdAt":{"type":"string","format":"date-time"}}},"WhoAmI":{"type":"object","properties":{"merchant":{"$ref":"#/components/schemas/Merchant"},"apiKey":{"type":"object","properties":{"id":{"type":"string"},"environment":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}}}}}}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":8,"maxLength":255},"description":"Unique key for this money-moving request. Replays return the original result."}},"responses":{"Unauthorized":{"description":"Missing or invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/health":{"get":{"tags":["Health"],"summary":"Liveness check","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"env":{"type":"string"}}}}}}}}},"/v1/auth/signup":{"post":{"tags":["Auth"],"summary":"Create a user account","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tokens"}}}},"409":{"description":"Email already registered"}}}},"/v1/auth/login/password":{"post":{"tags":["Auth"],"summary":"Log in with email + password","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tokens"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/merchants":{"post":{"tags":["Merchants"],"summary":"Create a merchant account","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["businessName","email"],"properties":{"businessName":{"type":"string"},"email":{"type":"string","format":"email"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Merchant"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"get":{"tags":["Merchants"],"summary":"List merchants for the current user","security":[{"BearerAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Merchant"}}}}}}}}}},"/v1/merchants/{merchantId}/api-keys":{"post":{"tags":["Merchants"],"summary":"Create an API key","description":"Owner-only. `live` keys require an ACTIVE merchant; `test` keys work at any stage. The full secret is returned once.","security":[{"BearerAuth":[]}],"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"environment":{"type":"string","enum":["live","test"],"default":"test"},"scopes":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Created (secret shown once)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"409":{"description":"Live key requested for a non-ACTIVE merchant"}}}},"/v1/whoami":{"get":{"tags":["Payments"],"summary":"Introspect the API key","description":"Returns the merchant + scopes the key resolves to.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoAmI"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/payment-sessions":{"post":{"tags":["Payments"],"summary":"Create a payment session","description":"Returns a short-lived `clientSecret` to hand to the collecting device. Requires the `payments:create` scope.","security":[{"ApiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"integer","minimum":1,"example":2500},"currency":{"type":"string","default":"USD"},"metadata":{"type":"object","additionalProperties":true},"deviceId":{"type":"string"},"allowedBundleId":{"type":"string"}}}}}},"responses":{"200":{"description":"Idempotent replay (existing session reused)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentSession"}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentSession"}}}},"400":{"description":"Missing Idempotency-Key or invalid amount"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/payment-sessions/{id}":{"get":{"tags":["Payments"],"summary":"Retrieve a payment session","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentSession"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/payment-sessions/{id}/cancel":{"post":{"tags":["Payments"],"summary":"Cancel a payment session","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Canceled"},"409":{"description":"Session already consumed"}}}},"/v1/refunds":{"post":{"tags":["Payments"],"summary":"Refund a captured payment","description":"Omit `amount` to refund the full remaining balance. Requires the `refunds:create` scope.","security":[{"ApiKeyAuth":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentId"],"properties":{"paymentId":{"type":"string"},"amount":{"type":"integer","minimum":1},"reason":{"type":"string"}}}}}},"responses":{"201":{"description":"Refund created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"}}}},"400":{"description":"Refund declined by the processor / invalid amount"},"409":{"description":"Payment not refundable"}}}},"/v1/refunds/{id}":{"get":{"tags":["Payments"],"summary":"Retrieve a refund","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/payments/{id}":{"get":{"tags":["Payments"],"summary":"Retrieve a payment","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/checkout/sessions/{id}":{"get":{"tags":["Checkout"],"summary":"Poll a session (device side)","description":"Authenticated by the session client secret, not an API key.","security":[{"ClientSecret":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceSession"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/checkout/sessions/{id}/confirm":{"post":{"tags":["Checkout"],"summary":"Confirm / collect a payment (device side)","description":"Send an opaque, processor-issued `paymentToken` (never raw card data). Enforces device/bundle binding, single-use and expiry, then charges via Netevia. Result is provisional until the webhook + reconciliation confirm.","security":[{"ClientSecret":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"paymentToken":{"type":"string","description":"Opaque Netevia CardToken."},"deviceId":{"type":"string"},"bundleId":{"type":"string"}}}}}},"responses":{"200":{"description":"Charged (provisional)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Session bound to a different device/app"},"409":{"description":"Session already used or expired"}}}}}}