Skip to content

Storefront book-and-pay (customer-present)

POST
/storefront/purchases
curl --request POST \
--url https://quikstik-core.fly.dev/storefront/purchases \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'x-tenant-id: 2489E9AD-2EE2-8E00-8EC9-32D5F69181C0' \
--data '{ "obligation_ref": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "settlement": "full", "offering_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "offering_variant_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "demand_class": "example", "effective_year": 1, "effective_month": 1, "effective_day": 1, "provider": "stripe", "instrument_token": "example", "booking_intent_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "units": 1, "rental_days": 1 }'

Anonymous (publishable-key) one-shot book-and-pay. The caller submits the booking coordinates plus a customer-present tokenized instrument; the token is vaulted and charged through the same reserve-then-pay engine as recordCaptureViaRail — the seats are committed against the capacity pool before the rail is touched (oversell rejected 409 with nothing charged), and net + tax + gross are resolved server-side from the governed catalog (the client never supplies a price). Idempotent on the session idempotency key. Fail-closed: an unconfigured processor (402) or a pool that cannot hold the seats (409) charges nothing.

x-tenant-id
required
string format: uuid

Tenant identifier (UUID).

Authn/authz semantics are intentionally TBD for this contract slice; this header exists to keep the contract explicitly multi-tenant from day 0.

Media typeapplication/json
object
obligation_ref
required

The booking obligation this charge settles.

string format: uuid
settlement

Which settlement leg to charge. full charges the whole gross (default, backward compatible). deposit charges the policy-derived deposit and leaves a balance owed; the deposit policy is resolved server-side (an unconfigured merchant is refused 422). The merchant-initiated balance leg is not storefront-callable.

string
default: full
Allowed values: full deposit
offering_id
required
string format: uuid
offering_variant_id
required
string format: uuid
demand_class
required

Demand lane the price resolves in. The net is derived server-side from the governed catalog; the client cannot supply a price.

string
effective_year
required
integer format: int32
effective_month
required
integer
>= 1 <= 12
effective_day
required
integer
>= 1 <= 31
provider
required

Processor the instrument token belongs to.

string
Allowed values: stripe finix
instrument_token
required

Customer-present tokenized instrument (e.g. a Stripe PaymentMethod id). Never a PAN.

string
booking_intent_id
required

The hold this purchase settles. Its bound departure (resolved at intent creation) is the capacity leaf the seats commit against — the storefront supplies no capacity key, so it cannot mis-key a non-leaf parent. Unknown hold -> 404; a hold with no departure -> 422.

string format: uuid
units
required

Seats to commit before any charge. Zero is rejected 400 before the rail is touched.

integer
>= 1
rental_days

Rental-day count for a multi-day product (e.g. a multi-day equipment rental). The charge scales by units × rental_days at the server-governed per-day rate; the capacity commit stays at units (seats). A day COUNT, never an amount — the price remains server-derived. Out-of-range values are rejected 400 (invalid_rental_days) before the rail is touched. Omitted = 1 (single-day, byte-identical behavior).

integer
default: 1 >= 1 <= 31

Charged — the seat was committed and the customer’s card settled

Media typeapplication/json
object
outbox_id
required

Transactional-outbox record id for the recorded capture.

string format: uuid
processor_ref
required

The processor’s reference for the charge (Stripe / Finix).

string
gross_minor
required

Total charged in integer minor units (net + tax), server-derived.

integer format: int64
net_minor
required

Tax-exclusive net in integer minor units, server-derived from the governed catalog.

integer format: int64
tax_minor
required

Tax in integer minor units, server-derived from the tax configuration.

integer format: int64

Example generated

{
"outbox_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"processor_ref": "example",
"gross_minor": 1,
"net_minor": 1,
"tax_minor": 1
}

Malformed request (unsupported provider, empty instrument token, or zero seats)

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}

Missing or invalid session bearer token

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}

The operator has not connected a processor for this tenant (fail-closed, nothing charged)

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}

The capacity pool cannot hold the requested seats (oversell guard; nothing charged)

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}

The processor rail refused the charge

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}

The operator’s stored credential could not be opened into a usable processor

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}

Capacity backend or funds-capture unavailable (nothing charged)

Media typeapplication/json
object
reason
required

Stable, machine-readable reason code (e.g. “sold_out”, “processor_not_configured”, “rail_refused”).

string

Example

{
"reason": "sold_out"
}