GS2-SerialKey Transaction Actions
Combining actions, and concurrency
The background common to every service is collected in Combining Transaction Actions. Read that first; the rest of this section is what GS2-SerialKey adds to it.
The transaction actions of GS2-SerialKey address one serial key, identified by the combination of namespace and serial code. A key records whether it has been used and by whom, and it is rewritten as a whole on every update.
| Operation | Repeated in one transaction | Across a nested transaction | Boundary that separates targets | Under sequential execution mode |
|---|---|---|---|---|
Using a keyUseByUserId | Rejected when the transaction is issued, even if the requests are identical | Fails | namespace, code | Unchanged for two uses of the same key: the rejection happens when the transaction is issued, before any action runs. Colliding with an undo is not resolved either, whether the undo is written directly or arrives from a nested transaction; see below |
Undoing a useRevertUseByUserId | Rejected when the transaction is issued, even if the requests are identical | Fails | namespace, code | Unchanged for two undos of the same key, for the same reason. Colliding with a use is not resolved either; see below |
Issuing a keyIssueOnce | Each one issues a new key; they never collide | No collision | every issue is its own target | No change; it already never collides |
Verifying a codeVerifyCodeByUserId | Checks that match exactly are gathered into one | No collision, because it only reads | code, verify type, user, campaign | No change; it already succeeds because it only reads |
Using and undoing are separate rows sharing a boundary, so one key cannot be used and undone in the same transaction. Different keys are different targets, so using several of them is fine. Two undos against the same key placed in one transaction are rejected when the transaction is issued as well, because there is no reason to place two of them side by side and because the second one always fails once the first has put the key back to unused. Turning on sequential execution mode (enableSequentialExecution or TransactionSettingV2) does not resolve this. Using is a consume action and does run before undoing, which is an acquire action, so the undo’s own check passes against the key the use left behind — but the two then write the key’s status to opposite values in the same transaction, which cannot be combined, and the transaction fails (400). The two-undos-of-one-key rejection is unaffected, since it happens when the transaction is issued.
A serial key can be used once. That is the reason serial keys exist. A second use of the same key fails, whether from the same transaction or a later one. Two uses of the same key placed in one transaction are rejected when the transaction is issued. The second one cannot be combined into the first, so keep one per transaction. The user is not part of that boundary: a key records who used it, but it is reached by namespace and code alone, so two uses of one code written for two different users are still one target and are rejected in the same way. Buying a display item that uses a serial key with a quantity of 2 or more runs into this as well, because the actions are stacked once per unit. Releasing several skill tree nodes that ask for the same code runs into it too.
A key cannot be issued and used in the same transaction. Using works from the state at the start of the transaction, and at that point the key does not exist yet. Verify actions look at the same starting state, so a code used by the same transaction still reads as unused.
Using a key hands out whatever its campaign grants. Those are issued as a transaction of their own, so the restrictions of whichever services they belong to apply, and so does the section below. Using several keys in one transaction is fine as far as GS2-SerialKey is concerned, but everything they grant gathers into the same transaction: where two campaigns grant the same item or the same currency, read that service’s page.
Take care with nested transactions
The same serial key used from the inside and undone from the outside makes the transaction fail. Sequential execution mode does not resolve this either. Which of the two runs first is decided by the action names — the outer undo and the acquire action that triggers the inner use are both acquire actions — but neither order works: if the use runs first the undo’s check passes and the two then write the key’s status to opposite values, which cannot be combined (400); if the undo runs first it fails because the key is not used yet.
If you want to avoid these restrictions
Using a key is a consume action; undoing a use and issuing a key are acquire actions. acquireActionUseJobQueue separates the grants from each other only, never from the use itself. Turning enableAtomicCommit off clears a collision, but weigh that carefully: a serial key that has been used cannot be used again even if a grant fails afterwards.
Neither two uses of the same key nor two undos of one have such an escape. The check runs before either setting is applied, and a serial code is meant to be used once, so turning them on changes nothing here. The check looks only at the namespace and the code, so two uses of one campaign code, which may be used any number of times, are rejected in the same way. Split it into separate transactions, buy a display item that uses a serial key one at a time, and release skill tree nodes that ask for the same code separately.
Concurrency and retries
Using a key checks that it has not been used already, so using the same code at the same time makes the one confirmed later fail. This is what stops one code being used twice, and once the key is used a retry does not change it.
Different keys are different targets, so using different codes at the same time does not conflict inside GS2-SerialKey itself.
Verify Action
Gs2SerialKey:VerifyCodeByUserId
Verify the validity of the Serial Code by User ID
Verifies a serial code for the specified user without consuming it. Supports verification against a specific campaign model name, and can check whether the code is active (unused) or inactive (already used).
Quantity specification supported: NO
| Type | Condition | Required | Default | Value Limits | Description | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128 chars | Namespace name Unique Namespace name. Specified using alphanumeric characters, hyphens (-), underscores (_), and periods (.). | ||||||||
| userId | string | ✓ | ~ 128 chars | User ID Specify #{userId} to substitute the currently logged-in user’s ID. | ||||||||
| code | string | ✓ | ~ 48 chars | Serial Code The serial code string in the format “XXXXX-XXXX-XXXXX-XXXX-XXXX”. Each code is unique and includes campaign identification information. The code format and data length are fixed and cannot be changed. | ||||||||
| campaignModelName | string | ~ 128 chars | Campaign name The name of the campaign model this serial code belongs to. Campaign information is embedded within the serial code itself, so only the Namespace needs to be specified when using the code. | |||||||||
| verifyType | string (enum) enum { “active”, “inactive” } | ✓ | Verification type
| |||||||||
| timeOffsetToken | string | ~ 1024 chars | Time offset token |
{
"action": "Gs2SerialKey:VerifyCodeByUserId",
"request": {
"namespaceName": "[string]Namespace name",
"userId": "[string]User ID",
"code": "[string]Serial Code",
"campaignModelName": "[string]Campaign name",
"verifyType": "[string]Verification type",
"timeOffsetToken": "[string]Time offset token"
}
}action: Gs2SerialKey:VerifyCodeByUserId
request:
namespaceName: "[string]Namespace name"
userId: "[string]User ID"
code: "[string]Serial Code"
campaignModelName: "[string]Campaign name"
verifyType: "[string]Verification type"
timeOffsetToken: "[string]Time offset token"transaction.service("serialKey").verify.verify_code_by_user_id({
namespaceName="[string]Namespace name",
userId="[string]User ID",
code="[string]Serial Code",
campaignModelName="[string]Campaign name",
verifyType="[string]Verification type",
timeOffsetToken="[string]Time offset token",
})Consume Action
Gs2SerialKey:UseByUserId
Use Serial Code by User ID
Marks a serial code as used by the specified user. Validates that the code exists and has not already been used.
Quantity specification supported: NO
Reversible action: YES
| Type | Condition | Required | Default | Value Limits | Description | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128 chars | Namespace name Unique Namespace name. Specified using alphanumeric characters, hyphens (-), underscores (_), and periods (.). | ||
| userId | string | ✓ | ~ 128 chars | User ID Specify #{userId} to substitute the currently logged-in user’s ID. | ||
| code | string | ✓ | ~ 48 chars | Serial Code The serial code string in the format “XXXXX-XXXX-XXXXX-XXXX-XXXX”. Each code is unique and includes campaign identification information. The code format and data length are fixed and cannot be changed. | ||
| timeOffsetToken | string | ~ 1024 chars | Time offset token |
{
"action": "Gs2SerialKey:UseByUserId",
"request": {
"namespaceName": "[string]Namespace name",
"userId": "[string]User ID",
"code": "[string]Serial Code",
"timeOffsetToken": "[string]Time offset token"
}
}action: Gs2SerialKey:UseByUserId
request:
namespaceName: "[string]Namespace name"
userId: "[string]User ID"
code: "[string]Serial Code"
timeOffsetToken: "[string]Time offset token"transaction.service("serialKey").consume.use_by_user_id({
namespaceName="[string]Namespace name",
userId="[string]User ID",
code="[string]Serial Code",
timeOffsetToken="[string]Time offset token",
})Acquire Action
Gs2SerialKey:RevertUseByUserId
Set Serial Code to Unused by User ID
Reverts a used serial code back to unused status. Validates that the code is currently in used state before reverting.
Quantity specification supported: NO
Reversible action: YES
| Type | Condition | Required | Default | Value Limits | Description | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128 chars | Namespace name Unique Namespace name. Specified using alphanumeric characters, hyphens (-), underscores (_), and periods (.). | ||
| userId | string | ✓ | ~ 128 chars | User ID Specify #{userId} to substitute the currently logged-in user’s ID. | ||
| code | string | ✓ | ~ 48 chars | Serial Code The serial code string in the format “XXXXX-XXXX-XXXXX-XXXX-XXXX”. Each code is unique and includes campaign identification information. The code format and data length are fixed and cannot be changed. | ||
| timeOffsetToken | string | ~ 1024 chars | Time offset token |
{
"action": "Gs2SerialKey:RevertUseByUserId",
"request": {
"namespaceName": "[string]Namespace name",
"userId": "[string]User ID",
"code": "[string]Serial Code",
"timeOffsetToken": "[string]Time offset token"
}
}action: Gs2SerialKey:RevertUseByUserId
request:
namespaceName: "[string]Namespace name"
userId: "[string]User ID"
code: "[string]Serial Code"
timeOffsetToken: "[string]Time offset token"transaction.service("serialKey").acquire.revert_use_by_user_id({
namespaceName="[string]Namespace name",
userId="[string]User ID",
code="[string]Serial Code",
timeOffsetToken="[string]Time offset token",
})Gs2SerialKey:IssueOnce
Issue a serial code
Issues a single serial code immediately. The code is generated using AES encryption and base32 encoding in the format XXXXX-XXXX-XXXXX-XXXX-XXXXX. Unlike batch issuance jobs, this generates and returns a code synchronously.
Quantity specification supported: NO
Reversible action: NO
| Type | Condition | Required | Default | Value Limits | Description | |
|---|---|---|---|---|---|---|
| namespaceName | string | ✓ | ~ 128 chars | Namespace name Unique Namespace name. Specified using alphanumeric characters, hyphens (-), underscores (_), and periods (.). | ||
| campaignModelName | string | ✓ | ~ 128 chars | Campaign Model name | ||
| metadata | string | ~ 2048 chars | Metadata Arbitrary values can be set in the metadata. Since they do not affect GS2’s behavior, they can be used to store information used in the game. |
{
"action": "Gs2SerialKey:IssueOnce",
"request": {
"namespaceName": "[string]Namespace name",
"campaignModelName": "[string]Campaign Model name",
"metadata": "[string]Metadata"
}
}action: Gs2SerialKey:IssueOnce
request:
namespaceName: "[string]Namespace name"
campaignModelName: "[string]Campaign Model name"
metadata: "[string]Metadata"transaction.service("serialKey").acquire.issue_once({
namespaceName="[string]Namespace name",
campaignModelName="[string]Campaign Model name",
metadata="[string]Metadata",
})