DELIVER_ITEM_GRANT
Hand a bought item to a character, exactly once
The capability the whole platform exists for: turning a purchase into an item the player is holding. It is required — a server without it stays out of the player portal entirely, because there would be nothing to sell.
{
"commandId": "77e6a4f0-4f6b-4c85-9e37-1d2a55f0a001",
"orderId": "b1c9c6a2-8d33-4f1e-9b70-6c0d64f0a002",
"grantActionId": "5a3f2d10-91ab-4d4f-8c22-7e9b64f0a003",
"target": { "normalizedAccountName": "robert", "characterId": 268485666 },
"grant": {
"type": "l2.grant_item",
"schemaVersion": 1,
"itemId": 57,
"count": 1000
}
}The grant fields sit under grant and the target under target — read them from there, not from
the payload root. commandId is the replay key the journal is built on, and arrives separately as
the method's first argument. orderId names the purchase behind the command; it is the value the
journal's order_id column stores. grantActionId identifies this action inside a multi-action
order — useful in logs, not needed for idempotency.
enchantLevel is optional on the grant object; absent means +0. A grant path that ignores it
delivers an enchanted purchase at +0, silently — see the reference grantItems for the per-unit
handling a non-stackable item needs.
target carries normalizedAccountName and characterId. Check that the character really
belongs to that account: a command names a character by id, and nothing further down would notice
an id belonging to someone else.
What the answer means
| Outcome | Durable | What the gateway does |
|---|---|---|
APPLIED | Yes | Completes the command |
ALREADY_APPLIED | Yes | Completes a replay without applying it twice |
QUEUED | Yes | Parks the command as awaiting settlement — the L2Off booking; the same command settles later, once the game has decided |
RETRYABLE_FAILURE | No | Retries; CHARACTER_OFFLINE waits for PLAYER_ONLINE_EVENT |
REFUSED | Yes | Cancels the order and releases the player's coins — return it only from a check that ran before any write, the journal included |
PERMANENT_FAILURE | Yes | Stops retrying |
RECONCILIATION_REQUIRED | No | Asks a human |
Never let an exception escape — a thrown error says nothing about whether the item was handed over,
and the only honest answer in that case is RECONCILIATION_REQUIRED.
Delivering exactly once
The gateway replays a command after a reconnect or a crash. Replay protection is what stops that becoming a second free item, and it is the same shape on both families:
- Look up the
commandId. - If it is already settled, return that result.
- If a previous attempt is still
APPLYING, returnRECONCILIATION_REQUIRED— it died mid-grant and no automatic rule can tell whether the player received anything. - Run every check that can decline — unknown item, invalid count, target mismatch. A decline is
REFUSED, and the portal can act on it precisely because the journal was not touched. - Write the
APPLYINGmarker. - Apply the change.
- Settle it as
APPLIED.
Implementing it
GrantResult applyGrant(String commandId, JsonObject payload);One method serves every grant kind. Return REFUSED with UNSUPPORTED_GRANT_TYPE for any payload
type you do not implement, and advertise
DELIVER_SKILL_GRANT only once l2.grant_skill is
handled too.
The bridge runs in-process, so it hands the item to the live player object and the player sees it
immediately. That also means it can only act on someone who is there: both reference
implementations return RETRYABLE_FAILURE / CHARACTER_OFFLINE otherwise, and
PLAYER_ONLINE_EVENT is what resumes it.
The journal is a table the bridge creates on startup:
CREATE TABLE IF NOT EXISTS portal_command (
command_id VARCHAR(36) NOT NULL PRIMARY KEY,
status VARCHAR(24) NOT NULL,
error_code VARCHAR(100) NULL,
order_id VARCHAR(64) NULL,
created_at BIGINT NOT NULL,
applied_at BIGINT NULL
);The game database user needs rights to create and alter it and to read, insert and update its
rows — the reference bridges add columns the journal gained after its first release, through JDBC
metadata, because CREATE TABLE IF NOT EXISTS never migrates an existing table.
Revocation
There is none. A delivered item cannot be taken back through this capability, and neither can a skill — plan refunds around that.
Verification
Connector check grants one Adena to the character you designate. It is a real grant with no economic impact, but it is real: use a test character.