Guides

Why a donation panel breaks when the player is online

The idempotency problem every L2J donation panel eventually hits, and the grant flow that avoids it

Most homegrown L2J donation panels get item delivery half right: they find the character in the database, add the item, done. It works in testing. It breaks in production, and almost always the same way — a reconnect, a timeout, or a retry hands the same purchase to the same player twice, or the panel can't tell whether a delivery actually happened and just guesses.

The problem is idempotency, not database access

On L2J, your delivery code runs inside the game server process, so it can reach the live Player object directly — that part is easy. The hard part is that "grant this item" is not a safe operation to run twice, and network calls get retried. A panel that adds the item and then marks the order paid has a window where a retry, a double-click, or a webhook firing twice grants the item again before the first grant is recorded.

A grant flow that survives replays

The fix is to persist intent before touching game state, so a replay can recognize itself:

new command → APPLYING → APPLIED
  1. A command arrives with a stable id. Before granting anything, write a row: APPLYING.
  2. If a row for that id already reads APPLIED, stop — return "already applied" and grant nothing. This is what makes a retry safe.
  3. Grant the item to the live player object, then flip the row to APPLIED.
  4. If the character isn't online, don't guess — fail with a retryable result and wait for the character's next login event instead of polling for it.
  5. Input that can never succeed (unknown item id, a name already taken, a skill already known) should be rejected before anything is written, so the reservation on the player's coins can be released immediately instead of leaving the purchase stuck.
  6. A crash between "wrote APPLYING" and "flipped to APPLIED" is the one case that needs a human — flag it for reconciliation rather than silently retrying, since you genuinely don't know if the item landed.

That's the whole shape of it. The two failure modes a lot of panels never handle are the offline character (most just retry-poll, which is wasted work and delays the grant) and the crash mid-grant (most either double-grant on retry or lose the purchase silently).

What this costs you

An afternoon, if you're starting from a working database layer — the logic is a handful of states, not a rewrite. Forgeport's L2J connector core handles the retry, reconnect, and command delivery; the grant flow above is what your GameBridge implementation needs to get right. Two complete, working examples — aCis and Mobius CT2.6 High Five — show this exact flow end to end, including the journal table it's built on: see Architecture and the GameBridge examples.

On this page