Skip to content

Payment apps

A payment app lets hosts charge buyers at checkout through your provider. It implements the Stayblox payment-session protocol: Stayblox opens a session against your server, the buyer pays on your hosted page, and you report the outcome back over GraphQL.

This guide takes you from manifest to a resolved session. For a complete runnable server, see the reference app.

1. Declare your app (manifest)

A payment app is registered as a marketplace App with type: "payment" and a manifest. Here is the reference Stripe app's manifest:

json
{
  "scopes": [],
  "webhooks": [],
  "api_permissions": [
    "payment_sessions:write"
  ],
  "supported_currencies": [
    "USD",
    "EUR",
    "GBP",
    "AUD",
    "CAD"
  ],
  "endpoints": {
    "payment_session": "https://stripe-app.example.com/sessions",
    "refund": "https://stripe-app.example.com/refunds"
  },
  "capabilities": {
    "refunds": true
  },
  "settings_schema": [
    {
      "key": "stripe_account",
      "type": "text",
      "label": "Stripe account ID",
      "placeholder": "acct_XXXXXXXX",
      "required": true,
      "help": "Your connected Stripe account, used by the provider to route charges. No secret keys are stored here."
    },
    {
      "key": "statement_descriptor",
      "type": "text",
      "label": "Statement descriptor",
      "required": false,
      "help": "Shown on the buyer's card statement."
    }
  ]
}

settings_schema fields

These are the fields the host fills in when configuring the install. Values are passed to your server in the session payload's settings.

KeyTypeLabelRequiredHelp
stripe_accounttextStripe account IDyesYour connected Stripe account, used by the provider to route charges. No secret keys are stored here.
statement_descriptortextStatement descriptornoShown on the buyer's card statement.

Supported currencies

USD, EUR, GBP, AUD, CAD

An empty list means the provider accepts any currency.

Endpoints

KeyPurpose
payment_sessionStayblox POSTs here to open a session; returns { redirect_url }.
refundStayblox POSTs here to reverse a resolved session.

2. Open a session (platform → app)

When a buyer chooses your app at checkout, Stayblox creates a pending session and POSTs it to your manifest's payment_session endpoint. The request is HMAC-signed: verify it before acting (see Signing & security).

Stayblox POSTs this signed JSON to your app's payment_session endpoint (keys from RemotePaymentClient::sessionPayload()):

json
{
  "session_id": "9b2c1f0e-4d6a-4f3b-8c21-7a0b5e9d1234",
  "amount": 100.0,
  "currency": "USD",
  "payable_type": "App\\Models\\Booking",
  "payable_id": 42,
  "return_url": "https://shop.example/pay/return/9b2c1f0e",
  "cancel_url": "https://shop.example/pay/cancel/9b2c1f0e",
  "api_base_url": "https://api.stayblox.com/developer/api/2026-01/graphql",
  "version": "…",
  "settings": { "stripe_account": "acct_123" }
}
FieldDescription
session_idThe Stayblox payment-session id. Echo it back when you resolve/reject the session.
amountAmount to charge, in major currency units.
currencyISO 4217 currency code (uppercase).
payable_typeThe morph type of what is being paid for (e.g. a booking).
payable_idThe id of the payable record.
return_urlSend the buyer here after a successful payment.
cancel_urlSend the buyer here if they abandon checkout.
api_base_urlThe GraphQL endpoint to call back to settle the session.
version
settingsNon-secret host config from the install (the fields you declared in settings_schema). Secrets stay on your server.

Respond 200 with { "redirect_url": "https://…" } — the hosted payment page Stayblox redirects the buyer to.

3. Report the outcome (app → platform)

After the buyer pays (or fails, or you're still waiting on an async result), call the Developer API with your bearer token to move the session to its final state.

The session lifecycle on the Stayblox side:

PENDING → PROCESSING → ┬─ resolve ─▶ RESOLVED  (paid)
                       ├─ reject  ─▶ REJECTED  (declined)
                       └─ pending ─▶ PENDING_EXTERNAL (awaiting async outcome)

resolve, reject, and cancel/expire are terminal. A session may only be acted on by the app that owns it (resolved from your token).

Resolve a paid session

graphql
mutation Resolve($id: ID!, $ref: String) {
  paymentSessionResolve(id: $id, providerReference: $ref) {
    paymentSession { id status amount currency providerReference }
    userErrors { field message }
  }
}
bash
curl -s https://api.stayblox.com/developer/api/2026-01/graphql \
  -H "Authorization: Bearer $STAYBLOX_APP_TOKEN" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "query": "mutation($id: ID!, $ref: String){ paymentSessionResolve(id:$id, providerReference:$ref){ paymentSession{ id status } userErrors{ message } } }",
    "variables": { "id": "9b2c1f0e-...", "ref": "pi_3Q…" }
  }'

Resolving is idempotent: replaying the same resolve does not record a second payment, so it's safe to retry on provider webhook redelivery.

Reject a failed session

graphql
mutation Reject($id: ID!, $reason: String) {
  paymentSessionReject(id: $id, reason: $reason) {
    paymentSession { id status }
    userErrors { field message }
  }
}

Mark a session pending (async outcomes)

For payment methods that settle asynchronously, acknowledge receipt and resolve later when the provider confirms:

graphql
mutation Pending($id: ID!, $ref: String) {
  paymentSessionPending(id: $id, providerReference: $ref) {
    paymentSession { id status }
    userErrors { field message }
  }
}

4. Handle errors

Every mutation returns a userErrors array. On success it's empty; on a bad request it describes the problem (e.g. an unknown session id) without throwing a GraphQL error. Always check it before treating a call as successful.

Capabilities

Declare what your provider can do under the capabilities key in your manifest. All flags default to false; omit any you don't support.

FlagMeaning
saved_payment_methodsYour server can store a buyer's payment method and reuse it for future sessions without re-prompting.
off_session_chargesYour server can initiate a charge without the buyer being present (requires saved_payment_methods).
refundsYour server handles refund requests POSTed to your refund endpoint.
manual_captureYour server uses an authorize-then-capture flow; the platform may ask you to capture or void separately.
offlineYour server collects payment offline (e.g. cash on arrival). The platform keeps the booking pending until the host records receipt rather than waiting for a session resolve.

Degrade gracefully. Schedules never hard-depend on any flag; capabilities only change which collection strategy the platform uses. An offline-capable provider, for example, keeps the booking in a pending state until the owner confirms receipt; no session needs to be resolved programmatically.

The reference Stripe app declares refunds: true.

Refunds

If your manifest declares refunds: true in capabilities, Stayblox calls your refund endpoint (platform → app) when a host refunds a resolved session. Reverse the charge with your provider and return 200.

Payment schedules

Bookings may carry owner-configured installment schedules. When they do, a payment session is linked to a specific installment rather than the full booking total, so amount in the session payload can be less than the booking's total value. This was always technically possible, but with schedules it is the norm rather than the exception.

The wire protocol is unchanged: same signed session POST payload, same GraphQL mutations (paymentSessionResolve, paymentSessionReject, paymentSessionPending). Treat amount as the authoritative charge amount for that session and don't try to reconcile it against the booking total yourself.

Checklist

  • [ ] Manifest registered with type: payment, endpoints, settings_schema.
  • [ ] capabilities block declares the flags your server actually supports.
  • [ ] payment_session endpoint verifies the signature and returns { redirect_url }.
  • [ ] Provider callback resolves/rejects the session over GraphQL.
  • [ ] userErrors checked on every mutation.
  • [ ] Resolve handled idempotently.

© Stayblox — Developer Platform