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 (from StripePaymentAppSeeder):

json
{
  "endpoints": {
    "payment_session": "https://stripe-app.example.com/sessions",
    "refund": "https://stripe-app.example.com/refunds"
  },
  "capabilities": {
    "supports_refund": true,
    "supports_capture": false,
    "supports_void": false,
    "supports_3ds": true
  },
  "supported_currencies": [
    "USD",
    "EUR",
    "GBP",
    "AUD",
    "CAD"
  ],
  "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."
    }
  ],
  "webhooks": [],
  "api_permissions": [
    "payment_sessions:write"
  ]
}

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://app.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://app.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.

Refunds

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

Checklist

  • [ ] Manifest registered with type: payment, endpoints, settings_schema.
  • [ ] 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