Case studiesIndonesian theme park and resort group

Annual passes and daily sales through one integration hub

Two flows for a theme park and resort group: buying an annual pass, with an undo for every step, and posting daily sales to NetSuite with settlement matching and a nightly exception report. One flow document, three environments, and stand-ins for every system that has no sandbox yet.

What the proof of concept covers

The group sells tickets and annual passes through several systems and books the money in NetSuite. The proof of concept builds two flows that move that data, and the controls around them.

  • F1, annual pass purchase: a guest asks for a pass in an app, pays through the payment gateway, and the hub creates the member, books the pass in the ticketing platform and posts the deposit to NetSuite. If a later step fails, the earlier ones are undone.
  • F2, daily sales and settlement: payment and booking notifications, the nightly sales summary from the point-of-sale system and the gateway’s settlement file are posted to NetSuite and checked against each other.

It is one flow document with four tabs: F1, F2 and two shared sub-flows, one for the payment callback and one for posting to NetSuite. A finance IT team can read and change it on the same canvas after handover.

F1: annual pass purchase

Nodes in the F1 payment-confirmed lane, in order

  1. signed webhook
  2. payment intake
  3. amount = price?
  4. create member
  5. reserve and confirm pass
  6. post deposit to NetSuite
  7. passes ledger

The first lane takes the guest’s request from the app form. The guest is signed in through the platform identity, the hub looks up the price and creates the payment, and the guest gets a payment link.

The second lane starts when the gateway confirms payment with a signed notification. The shared intake sub-flow checks the signature, drops repeats and records the payment. The flow then checks the amount against the price, creates the member, reserves and confirms the pass ticket in the ticketing platform, posts the deposit to NetSuite, writes the passes ledger and emits a member.renewed event for the dashboard.

Three of those steps carry an Undo node: the member, the ticket booking and the NetSuite deposit. If a later step fails for good, the hub runs the undo nodes in reverse order. The member is deactivated, the booking is cancelled and the deposit is deleted.

The demo includes a payment notification dated in an earlier accounting period. NetSuite refused the deposit because the period was closed. The hub cancelled the booking and deactivated the member, and the record waited in Dead letters for an operator to correct and replay.

F2: daily sales and settlement

Nodes in the F2 nightly sales lane, in order

  1. schedule 00:30
  2. http: sales summary
  3. one record per branch
  4. validate
  5. ledger
  6. approve above IDR 50 M
  7. post journal to NetSuite

F2 has one lane per source. Ticket payments from the gateway are deposited to NetSuite through the shared intake and posting sub-flows. Ticketing platform bookings arrive as signed notifications, are deduplicated on the request id, and the flow reads the booking back from the platform before it posts a journal. The nightly job at 00:30 Asia/Jakarta pulls the sales-payment summary from the point-of-sale system and makes one record per branch. The gateway’s settlement rows are tallied into a ledger of their own.

A branch whose daily total is above IDR 50 million waits in an approval inbox for Finance. A branch that NetSuite refuses, for example one with an unknown department, is parked for an approver with the error text. The other branches are posted anyway, so one bad record does not hold up the night.

Shared parts

Two sub-flows are called from both F1 and F2, so each rule lives in one place.

  • Payment callback intake: drops repeats on the gateway’s request id, builds a payment.confirmed event, validates it and writes it to the payments ledger.
  • NetSuite posting: takes a deposit or a journal and writes it as an upsert keyed by an external id, so a retry cannot book twice. It holds the posting mode, and the undo for each post is a delete.

Three event types were added to the business-event catalogue for the proof of concept: pass.payment_requested, settlement.row_received and reconciliation.completed. The flows also use payment.confirmed, ticket.sold, sales.daily_summary and member.renewed.

Controls

  • Signatures: the payment gateway notification is checked with a shared secret and a 300 second time tolerance. The ticketing platform signs only the event type and timestamp, not the data, so the flow re-reads the booking from the platform before posting and dedupes on the request id. A forged payment notification is refused before it enters any flow.
  • Once only: repeats are dropped on the notification’s id, and NetSuite writes are upserts.
  • Validation: invalid payments and bookings are quarantined with the reason.
  • Approvals: the nightly sales above IDR 50 million, and any posting that NetSuite refuses, wait for a person.
  • Go-live gate: posting to NetSuite starts read-only on uat and prod. An editor files a request to switch it to live, and a second approver with the finance go-live role approves it. The decision is audited and shown on the posting nodes.
  • Failed records: kept in Dead letters with the original payload, editable and replayable from the editor.

Nightly reconciliation

Every night at 01:30 Asia/Jakarta three reconciliations compare the ledgers: gateway payments against the settlement file, gateway payments against NetSuite, and point-of-sale sales against NetSuite. Each exception becomes a case that Finance assigns, investigates and resolves.

After the demo run there were six open cases. The settlement check found three: one invoice settled IDR 15,000 lower than the payment, one payment missing in the file and one settlement row for an invoice the hub never saw. The sales check found two, the branch waiting for approval and the branch NetSuite refused. The payments check found one on the earlier date, the payment that was rolled back as described above.

Environments and mocks

The flow never names a host. Every base URL and the NetSuite posting mode are variables, and each environment supplies its own values. Secrets are environment variables and stay out of the flow.

The three environments
Itemdevuatprod
TargetsMock services served by the hub itselfVendor sandboxesProduction hosts
NetSuite posting at startLive, because the target is a mockRead-onlyRead-only
ProtectedNo, deploy directlyYes, promotion only, no mocksYes

The customer has not given sandbox access yet, so every external system is a mock until it does. The mocks follow each vendor’s public documentation, with real field names and illustrative values.

What is not confirmed

These are open points, marked as unconfirmed in the mocks and the flow.

  • Mocks, not sandboxes. The annual pass membership system has no public API and its vendor is unknown, so that mock’s shape is invented.
  • The ticketing platform has no create-pass call. A pass ticket is a booking: reserve, then confirm. The amount field in its booking details and the text of NetSuite’s closed-period error are placeholders.
  • Settlement is a back-office or SFTP file, not an API. The flow stands in for the drop, and an SFTP source can replace it with the same nodes downstream. The file layout is unconfirmed.
  • The point-of-sale system has no public bill-level API, so F2 pulls a summary. Tax and discount are not in it, so net equals gross. Near-real-time push needs the vendor to confirm.
  • The gateway’s customer echo in the notification, used for the member name and email, is unconfirmed. Production needs an order store between payment request and notification.
  • NetSuite authentication uses token-based auth. OAuth 2.0 machine-to-machine is built but needs the customer’s certificate. The subsidiary, account, department and customer ids are placeholders.
  • One nightly run handles up to 27 branches. More needs one schedule per park, filtered by branch code.
  • Sign-in is the platform identity. Single sign-on across the app, the website and the membership system is a separate flow, not in this proof of concept.

Related

From download to first record in minutes.

Run one binary, connect a PLC, a CNC or a webhook, and watch the records arrive in the editor.

Builds for Linux (x86_64 and ARM64), Windows and macOS.

  • One binary
  • Raspberry Pi to rack server
  • Signed delivery with retries

Sizing questions or an air-gapped install? Email marketing@opexcg.com or read the quick start.