Skip to content

Paid bookings earn points

When a member pays for a booking, tell core once. Core credits points in the same request and keeps no other booking data.

Each booking is credited once per bookingId, however often you retry. How many points a booking earns is configured in core. As an example only, at 1 point per IDR 10,000 paid (rounded down), a IDR 3,155,000 booking earns 315 points. Don’t hard-code a rate: read the points from core’s answer.

sequenceDiagram
  autonumber
  actor G as Guest
  box rgb(246,241,233) Booking engine
    participant BE as Booking site
    participant PAY as Your payment provider
  end
  box rgb(227,244,243) FINNS platform
    participant CORE as Core
    participant APP as FINNS app
  end

  G->>BE: Pay (after a fresh sign-in)
  BE->>PAY: Charge
  PAY-->>BE: Paid
  BE->>CORE: POST /api/v1/booking/bookings<br/>{sub, bookingId, totalIdr}
  Note over CORE: nowrap:points = floor(totalIdr / 10,000)<br/>idempotency key booking:<bookingId><br/>one transaction: ledger + feed
  CORE-->>BE: 204
  BE-->>G: Booking confirmed
  APP->>CORE: GET /api/v1/app/points (app refreshes)
  CORE-->>APP: New balance
POST /api/v1/booking/bookings HTTP/1.1
Host: test-api.finnsbali.com
Authorization: Bearer <booking API token>
Content-Type: application/json
{ "sub": "6f1c2a…", "bookingId": "BK-2026-10-13-0042", "totalIdr": 3155000 }
Field Rule
sub The member’s Keycloak subject, from the handoff redeem or your ID token. Printable ASCII, 1–128 characters.
bookingId Your stable id for the booking. [A-Za-z0-9_-]{1,64}.
totalIdr Amount actually paid, in whole rupiah. A JSON integer ≥ 0; fractions and strings are refused.

Answer: 204 on success (including a repeat), 422 invalid with fields, or 400 malformed.

flowchart TB
  classDef ok fill:#E3F4F3,stroke:#13686C,color:#12191B
  classDef bad fill:#FFF4E8,stroke:#E9724C,color:#12191B
  classDef step fill:#ffffff,stroke:#D9CFBE,color:#12191B

  a["POST /bookings {sub, bookingId, totalIdr}"]:::step --> b{"bookingId seen before?"}
  b -- "no" --> c{"totalIdr ≥ 10,000?"}
  c -- "yes" --> d["Credit floor(total / 10,000)<br/>204"]:::ok
  c -- "no" --> e["Nothing to credit<br/>204"]:::ok
  b -- "yes, same total" --> f["Replay: nothing changes<br/>204"]:::ok
  b -- "yes, different total" --> g["422 idempotency_mismatch"]:::bad
  • Retry until you get an answer. On a timeout, network error, 5xx or 429, send the same request again with backoff. Core makes that safe; never generate a new bookingId for a retry.
  • Report from your backend, after payment is confirmed, not from the browser.
  • Persist the report (outbox) if core is unreachable for long, and drain it later. The points are not time-critical; losing them is.

Internal and confidential. Share partner sections only as partner builds (pnpm build:partner).