diff --git a/docs/config/swagger-merger-ingress-config.json b/docs/config/swagger-merger-ingress-config.json index da2837d..53476a6 100644 --- a/docs/config/swagger-merger-ingress-config.json +++ b/docs/config/swagger-merger-ingress-config.json @@ -1,8 +1,8 @@ { "swagger": "2.0", "info": { - "title": "Stash Managed Catalog API", - "description": "Read-only API for querying the Stash managed product catalog. Designed for server-to-server communication between partner backends and Stash services.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| **Test** | `https://test-api.stash.gg` |\n| **Production** | `https://api.stash.gg` |\n\nAll endpoint paths below are relative to the base URL. For example, to list products in the test environment:\n\n```\nGET https://test-api.stash.gg/sdk/studio/{shop_id}/products\n```\n\n## Authentication\n\nAll requests must include an HMAC-SHA256 signature in the `stash-hmac-signature` header.\n\n### Setup\n\n1. Go to **Stash Studio > Project Settings > API Secrets**\n2. Click **Generate Secret** to create a new key\n3. Keys created through this portal are Ingress keys by default and work with this API (this is the same key used for `/sdk/` API authentication)\n4. Store the secret securely — you will need it to sign every request\n\n### Signing a Request\n\n1. For GET requests (no body), sign an **empty string** using HMAC-SHA256 with your API secret\n2. Base64-encode the resulting signature\n3. Send it in the `stash-hmac-signature` HTTP header\n\n### Example (curl)\n\n```bash\nSIGNATURE=$(echo -n \"\" | openssl dgst -sha256 -hmac \"YOUR_API_SECRET\" -binary | base64)\ncurl -H \"stash-hmac-signature: $SIGNATURE\" \\\n \"https://test-api.stash.gg/sdk/studio/{shop_id}/products\"\n```", + "title": "Stash Studio Read API", + "description": "Read-only APIs for querying a shop's managed product catalog and loyalty program. Designed for server-to-server communication between partner backends and Stash services.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| **Test** | `https://test-api.stash.gg` |\n| **Production** | `https://api.stash.gg` |\n\nAll endpoint paths below are relative to the base URL. For example, to list products in the test environment:\n\n```\nGET https://test-api.stash.gg/sdk/studio/{shop_id}/products\n```\n\n## Authentication\n\nAll requests must include an HMAC-SHA256 signature in the `stash-hmac-signature` header.\n\n### Setup\n\n1. Go to **Stash Studio > Project Settings > API Secrets**\n2. Click **Generate Secret** to create a new key\n3. Keys created through this portal are Ingress keys by default and work with this API (this is the same key used for `/sdk/` API authentication)\n4. Store the secret securely; you will need it to sign every request\n\n### Signing a Request\n\n1. For GET requests (no body), sign an **empty string** using HMAC-SHA256 with your API secret\n2. Base64-encode the resulting signature\n3. Send it in the `stash-hmac-signature` HTTP header\n\n### Example (curl)\n\n```bash\nSIGNATURE=$(echo -n \"\" | openssl dgst -sha256 -hmac \"YOUR_API_SECRET\" -binary | base64)\ncurl -H \"stash-hmac-signature: $SIGNATURE\" \\\n \"https://test-api.stash.gg/sdk/studio/{shop_id}/products\"\n```", "version": "1.0.0", "contact": { "name": "API Support", @@ -17,13 +17,19 @@ { "name": "Managed Catalog", "description": "Read-only endpoints for querying the managed product catalog" + }, + { + "name": "Loyalty", + "description": "Read-only endpoints for querying a shop's loyalty program" } ], "paths": { - "$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#paths" + "$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#paths", + "$ref": "../../gen/openapiv2/server/ingress/loyalty/v1/service.swagger.json#paths" }, "definitions": { - "$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#definitions" + "$ref": "../../gen/openapiv2/server/ingress/studio/v1/service.swagger.json#definitions", + "$ref": "../../gen/openapiv2/server/ingress/loyalty/v1/service.swagger.json#definitions" }, "securityDefinitions": { "$ref": "../../gen/openapiv2/server/ingress/server.swagger.json#securityDefinitions" diff --git a/docs/gen/swagger.ingress.v1.json b/docs/gen/swagger.ingress.v1.json index 8f5a1cd..a984858 100644 --- a/docs/gen/swagger.ingress.v1.json +++ b/docs/gen/swagger.ingress.v1.json @@ -1,8 +1,8 @@ { "swagger": "2.0", "info": { - "title": "Stash Managed Catalog API", - "description": "Read-only API for querying the Stash managed product catalog. Designed for server-to-server communication between partner backends and Stash services.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| **Test** | `https://test-api.stash.gg` |\n| **Production** | `https://api.stash.gg` |\n\nAll endpoint paths below are relative to the base URL. For example, to list products in the test environment:\n\n```\nGET https://test-api.stash.gg/sdk/studio/{shop_id}/products\n```\n\n## Authentication\n\nAll requests must include an HMAC-SHA256 signature in the `stash-hmac-signature` header.\n\n### Setup\n\n1. Go to **Stash Studio > Project Settings > API Secrets**\n2. Click **Generate Secret** to create a new key\n3. Keys created through this portal are Ingress keys by default and work with this API (this is the same key used for `/sdk/` API authentication)\n4. Store the secret securely — you will need it to sign every request\n\n### Signing a Request\n\n1. For GET requests (no body), sign an **empty string** using HMAC-SHA256 with your API secret\n2. Base64-encode the resulting signature\n3. Send it in the `stash-hmac-signature` HTTP header\n\n### Example (curl)\n\n```bash\nSIGNATURE=$(echo -n \"\" | openssl dgst -sha256 -hmac \"YOUR_API_SECRET\" -binary | base64)\ncurl -H \"stash-hmac-signature: $SIGNATURE\" \\\n \"https://test-api.stash.gg/sdk/studio/{shop_id}/products\"\n```", + "title": "Stash Studio Read API", + "description": "Read-only APIs for querying a shop's managed product catalog and loyalty program. Designed for server-to-server communication between partner backends and Stash services.\n\n## Base URLs\n\n| Environment | Base URL |\n|---|---|\n| **Test** | `https://test-api.stash.gg` |\n| **Production** | `https://api.stash.gg` |\n\nAll endpoint paths below are relative to the base URL. For example, to list products in the test environment:\n\n```\nGET https://test-api.stash.gg/sdk/studio/{shop_id}/products\n```\n\n## Authentication\n\nAll requests must include an HMAC-SHA256 signature in the `stash-hmac-signature` header.\n\n### Setup\n\n1. Go to **Stash Studio > Project Settings > API Secrets**\n2. Click **Generate Secret** to create a new key\n3. Keys created through this portal are Ingress keys by default and work with this API (this is the same key used for `/sdk/` API authentication)\n4. Store the secret securely; you will need it to sign every request\n\n### Signing a Request\n\n1. For GET requests (no body), sign an **empty string** using HMAC-SHA256 with your API secret\n2. Base64-encode the resulting signature\n3. Send it in the `stash-hmac-signature` HTTP header\n\n### Example (curl)\n\n```bash\nSIGNATURE=$(echo -n \"\" | openssl dgst -sha256 -hmac \"YOUR_API_SECRET\" -binary | base64)\ncurl -H \"stash-hmac-signature: $SIGNATURE\" \\\n \"https://test-api.stash.gg/sdk/studio/{shop_id}/products\"\n```", "version": "1.0.0", "contact": { "name": "API Support", @@ -23,6 +23,10 @@ { "name": "Managed Catalog", "description": "Read-only endpoints for querying the managed product catalog" + }, + { + "name": "Loyalty", + "description": "Read-only endpoints for querying a shop's loyalty program" } ], "paths": { @@ -720,6 +724,346 @@ } ] } + }, + "/sdk/studio/{shopId}/loyalty": { + "get": { + "summary": "Get loyalty program configuration", + "description": "Retrieves the shop's active loyalty campaign configuration: its tiers (ordered by starting points) and milestones (ordered by points needed). Returns NOT_FOUND when the shop has no active loyalty campaign.", + "operationId": "LoyaltyService_GetLoyalty", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "activeCampaign": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the loyalty campaign (season)." + }, + "tiers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tierId": { + "type": "string", + "description": "Partner-facing tier identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the tier." + }, + "startPoints": { + "type": "integer", + "format": "int64", + "description": "The points balance at which this tier begins. Tiers are returned ordered by this value ascending." + }, + "pointsPerDollar": { + "type": "integer", + "format": "int64", + "description": "Loyalty points earned per US dollar of spend while in this tier." + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "This tier's configured points-earning rate relative to the base (lowest) tier, expressed in permille; divide by 1000 (base tier = 1000). The ratio of this tier's points-per-dollar to the base tier's points-per-dollar." + }, + "iconUrl": { + "type": "string", + "description": "URL to the tier icon image. Omitted when no icon is configured." + }, + "milestones": { + "type": "array", + "items": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Milestone identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the milestone." + }, + "pointsNeeded": { + "type": "integer", + "format": "int64", + "description": "The points balance required to reach this milestone. Milestones are returned ordered by this value ascending." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Loyalty Milestone Reward Item" + }, + "description": "All rewards granted at this milestone. Empty when no reward is configured." + } + }, + "description": "A milestone belonging to a tier in a shop's active loyalty campaign.", + "title": "Loyalty Milestone", + "required": [ + "milestoneId" + ] + }, + "description": "The milestones that belong to this tier, ordered by points needed ascending. May be empty." + } + }, + "description": "A tier in a shop's active loyalty campaign.", + "title": "Loyalty Tier", + "required": [ + "tierId" + ] + }, + "description": "The campaign's tiers, ordered by starting points ascending; each tier lists its own milestones. May be empty." + } + }, + "description": "The shop's active loyalty campaign (season): its tiers, each with the milestones that belong to it.", + "title": "Loyalty Campaign", + "required": [ + "campaignId" + ] + } + }, + "description": "A shop's active loyalty campaign configuration", + "title": "Get Loyalty Response", + "required": [ + "activeCampaign" + ] + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + } + } + } + } + } + }, + "parameters": [ + { + "name": "shopId", + "description": "Shop identifier from Stash Studio", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "Loyalty" + ], + "security": [ + { + "hmac": [] + } + ] + } + }, + "/sdk/studio/{shopId}/loyalty/players/{playerId}": { + "get": { + "summary": "Get a player's loyalty standing", + "description": "Retrieves a single player's loyalty standing (points balance, current tier, distance to the next tier and milestone, and effective points multiplier) for the shop's active loyalty campaign. Returns NOT_FOUND when the shop has no active loyalty campaign.", + "operationId": "LoyaltyService_GetPlayerLoyalty", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "type": "object", + "properties": { + "loyalty": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event; negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Player Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Player Next Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "The player's loyalty standing for the shop's active campaign.", + "title": "Player Loyalty State" + } + }, + "description": "A single player's loyalty standing", + "title": "Get Player Loyalty Response", + "required": [ + "loyalty" + ] + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + } + } + } + } + } + }, + "parameters": [ + { + "name": "shopId", + "description": "Shop identifier from Stash Studio", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "playerId", + "description": "The player whose loyalty standing to retrieve", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "Loyalty" + ], + "security": [ + { + "hmac": [] + } + ] + } } }, "definitions": { @@ -1754,6 +2098,690 @@ ], "default": "PRODUCT_STATUS_UNSPECIFIED", "description": "Product publication status. This API only returns published products." + }, + "CampaignLoyaltyTier": { + "type": "object", + "properties": { + "tierId": { + "type": "string", + "description": "Partner-facing tier identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the tier." + }, + "startPoints": { + "type": "integer", + "format": "int64", + "description": "The points balance at which this tier begins. Tiers are returned ordered by this value ascending." + }, + "pointsPerDollar": { + "type": "integer", + "format": "int64", + "description": "Loyalty points earned per US dollar of spend while in this tier." + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "This tier's configured points-earning rate relative to the base (lowest) tier, expressed in permille; divide by 1000 (base tier = 1000). The ratio of this tier's points-per-dollar to the base tier's points-per-dollar." + }, + "iconUrl": { + "type": "string", + "description": "URL to the tier icon image. Omitted when no icon is configured." + }, + "milestones": { + "type": "array", + "items": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Milestone identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the milestone." + }, + "pointsNeeded": { + "type": "integer", + "format": "int64", + "description": "The points balance required to reach this milestone. Milestones are returned ordered by this value ascending." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Loyalty Milestone Reward Item" + }, + "description": "All rewards granted at this milestone. Empty when no reward is configured." + } + }, + "description": "A milestone belonging to a tier in a shop's active loyalty campaign.", + "title": "Loyalty Milestone", + "required": [ + "milestoneId" + ] + }, + "description": "The milestones that belong to this tier, ordered by points needed ascending. May be empty." + } + }, + "description": "A tier in a shop's active loyalty campaign.", + "title": "Loyalty Tier", + "required": [ + "tierId" + ] + }, + "GetLoyaltyResponseCampaign": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the loyalty campaign (season)." + }, + "tiers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tierId": { + "type": "string", + "description": "Partner-facing tier identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the tier." + }, + "startPoints": { + "type": "integer", + "format": "int64", + "description": "The points balance at which this tier begins. Tiers are returned ordered by this value ascending." + }, + "pointsPerDollar": { + "type": "integer", + "format": "int64", + "description": "Loyalty points earned per US dollar of spend while in this tier." + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "This tier's configured points-earning rate relative to the base (lowest) tier, expressed in permille; divide by 1000 (base tier = 1000). The ratio of this tier's points-per-dollar to the base tier's points-per-dollar." + }, + "iconUrl": { + "type": "string", + "description": "URL to the tier icon image. Omitted when no icon is configured." + }, + "milestones": { + "type": "array", + "items": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Milestone identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the milestone." + }, + "pointsNeeded": { + "type": "integer", + "format": "int64", + "description": "The points balance required to reach this milestone. Milestones are returned ordered by this value ascending." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Loyalty Milestone Reward Item" + }, + "description": "All rewards granted at this milestone. Empty when no reward is configured." + } + }, + "description": "A milestone belonging to a tier in a shop's active loyalty campaign.", + "title": "Loyalty Milestone", + "required": [ + "milestoneId" + ] + }, + "description": "The milestones that belong to this tier, ordered by points needed ascending. May be empty." + } + }, + "description": "A tier in a shop's active loyalty campaign.", + "title": "Loyalty Tier", + "required": [ + "tierId" + ] + }, + "description": "The campaign's tiers, ordered by starting points ascending; each tier lists its own milestones. May be empty." + } + }, + "description": "A loyalty campaign (season) with its tiers and milestones.", + "title": "Loyalty Campaign", + "required": [ + "campaignId" + ] + }, + "GetPlayerLoyaltyResponsePlayerLoyaltyState": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event; negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Player Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Player Next Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "A player's standing in a shop's active loyalty campaign at a point in time.", + "title": "Player Loyalty State" + }, + "GetPlayerLoyaltyResponsePlayerLoyaltyStateMilestoneRewardItem": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Player Milestone Reward Item" + }, + "LoyaltyTierLoyaltyMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Milestone identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the milestone." + }, + "pointsNeeded": { + "type": "integer", + "format": "int64", + "description": "The points balance required to reach this milestone. Milestones are returned ordered by this value ascending." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Loyalty Milestone Reward Item" + }, + "description": "All rewards granted at this milestone. Empty when no reward is configured." + } + }, + "description": "A milestone belonging to a tier in a shop's active loyalty campaign.", + "title": "Loyalty Milestone", + "required": [ + "milestoneId" + ] + }, + "LoyaltyTierLoyaltyMilestoneMilestoneRewardItem": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Loyalty Milestone Reward Item" + }, + "PlayerLoyaltyStateMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Player Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next loyalty milestone a player is progressing toward and the rewards it grants.", + "title": "Player Next Milestone" + }, + "v1GetLoyaltyResponse": { + "type": "object", + "properties": { + "activeCampaign": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the loyalty campaign (season)." + }, + "tiers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tierId": { + "type": "string", + "description": "Partner-facing tier identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the tier." + }, + "startPoints": { + "type": "integer", + "format": "int64", + "description": "The points balance at which this tier begins. Tiers are returned ordered by this value ascending." + }, + "pointsPerDollar": { + "type": "integer", + "format": "int64", + "description": "Loyalty points earned per US dollar of spend while in this tier." + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "This tier's configured points-earning rate relative to the base (lowest) tier, expressed in permille; divide by 1000 (base tier = 1000). The ratio of this tier's points-per-dollar to the base tier's points-per-dollar." + }, + "iconUrl": { + "type": "string", + "description": "URL to the tier icon image. Omitted when no icon is configured." + }, + "milestones": { + "type": "array", + "items": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Milestone identifier. Unique within the campaign." + }, + "name": { + "type": "string", + "description": "Display name of the milestone." + }, + "pointsNeeded": { + "type": "integer", + "format": "int64", + "description": "The points balance required to reach this milestone. Milestones are returned ordered by this value ascending." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Loyalty Milestone Reward Item" + }, + "description": "All rewards granted at this milestone. Empty when no reward is configured." + } + }, + "description": "A milestone belonging to a tier in a shop's active loyalty campaign.", + "title": "Loyalty Milestone", + "required": [ + "milestoneId" + ] + }, + "description": "The milestones that belong to this tier, ordered by points needed ascending. May be empty." + } + }, + "description": "A tier in a shop's active loyalty campaign.", + "title": "Loyalty Tier", + "required": [ + "tierId" + ] + }, + "description": "The campaign's tiers, ordered by starting points ascending; each tier lists its own milestones. May be empty." + } + }, + "description": "The shop's active loyalty campaign (season): its tiers, each with the milestones that belong to it.", + "title": "Loyalty Campaign", + "required": [ + "campaignId" + ] + } + }, + "description": "A shop's active loyalty campaign configuration", + "title": "Get Loyalty Response", + "required": [ + "activeCampaign" + ] + }, + "v1GetPlayerLoyaltyResponse": { + "type": "object", + "properties": { + "loyalty": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event; negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Player Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Player Next Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "The player's loyalty standing for the shop's active campaign.", + "title": "Player Loyalty State" + } + }, + "description": "A single player's loyalty standing", + "title": "Get Player Loyalty Response", + "required": [ + "loyalty" + ] + }, + "v1LoyaltyRewardType": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted at a loyalty milestone." } }, "securityDefinitions": { diff --git a/docs/gen/swagger.v1.json b/docs/gen/swagger.v1.json index d31619a..436be8d 100644 --- a/docs/gen/swagger.v1.json +++ b/docs/gen/swagger.v1.json @@ -4225,6 +4225,93 @@ ] }, "description": "Optional list of bonus items to be granted with the purchase" + }, + "loyalty": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event (for example, a purchase); negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign.", + "title": "Player Loyalty State" } }, "description": "Request to confirm and complete a pending purchase that was previously registered.", @@ -4465,6 +4552,93 @@ "region": { "type": "string", "description": "Region/country code using ISO-3166-1 alpha-2 code (e.g., 'US', 'CA', 'GB')" + }, + "loyalty": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event (for example, a purchase); negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign.", + "title": "Player Loyalty State" } }, "description": "Request to register a purchase intent with the game backend, at which point the game backend is expected to reserve the inventory for the purchase.", @@ -20795,6 +20969,77 @@ "description": "Represents the result of a confirmed purchase", "title": "Confirm Payment Result" }, + "PlayerLoyaltyStateMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next loyalty milestone a player is progressing toward and the rewards it grants.", + "title": "Milestone" + }, + "PlayerLoyaltyStateMilestoneRewardItem": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Milestone Reward Item" + }, "RegisterPaymentRequestPurchaseRegistration": { "type": "object", "properties": { @@ -21165,6 +21410,93 @@ ] }, "description": "Optional list of bonus items to be granted with the purchase" + }, + "loyalty": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event (for example, a purchase); negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign.", + "title": "Player Loyalty State" } }, "description": "Request to confirm and complete a pending purchase that was previously registered.", @@ -21209,6 +21541,18 @@ "results" ] }, + "v1LoyaltyRewardType": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted at a loyalty milestone." + }, "v1PaymentMethodType": { "type": "string", "enum": [ @@ -21223,6 +21567,93 @@ "description": "Represents the payment method used for the purchase", "title": "- PAYMENT_METHOD_TYPE_UNSPECIFIED: Default unknown payment method\n - PAYMENT_METHOD_TYPE_CARD: Standard credit/debit card payment\n - PAYMENT_METHOD_TYPE_APPLE_PAY: Apple Pay wallet payment\n - PAYMENT_METHOD_TYPE_GOOGLE_PAY: Google Pay wallet payment\n - PAYMENT_METHOD_TYPE_PAYPAL: PayPal payment\n - PAYMENT_METHOD_TYPE_KLARNA: Klarna buy-now-pay-later payment" }, + "v1PlayerLoyaltyState": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event (for example, a purchase); negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "A player's standing in a shop's active loyalty campaign at a point in time.", + "title": "Player Loyalty State" + }, "v1PurchaseCancellationStatus": { "type": "string", "enum": [ @@ -21394,6 +21825,93 @@ "region": { "type": "string", "description": "Region/country code using ISO-3166-1 alpha-2 code (e.g., 'US', 'CA', 'GB')" + }, + "loyalty": { + "type": "object", + "properties": { + "campaignId": { + "type": "string", + "description": "Identifier of the live loyalty campaign (season) this snapshot is scoped to." + }, + "totalPoints": { + "type": "integer", + "format": "int64", + "description": "The player's loyalty points balance for this campaign." + }, + "pointsDelta": { + "type": "integer", + "format": "int32", + "description": "Signed change in the loyalty points balance caused by the associated event (for example, a purchase); negative on a refund reversal, zero when the balance did not change." + }, + "currentTierId": { + "type": "string", + "description": "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance." + }, + "previousTierId": { + "type": "string", + "description": "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change." + }, + "pointsToNextTier": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier." + }, + "pointsToNextMilestone": { + "type": "integer", + "format": "int64", + "description": "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone." + }, + "nextMilestone": { + "type": "object", + "properties": { + "milestoneId": { + "type": "string", + "description": "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API." + }, + "rewards": { + "type": "array", + "items": { + "type": "object", + "properties": { + "itemId": { + "type": "string", + "description": "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards." + }, + "quantity": { + "type": "integer", + "format": "int64", + "description": "Amount of the reward granted." + }, + "type": { + "type": "string", + "enum": [ + "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY", + "LOYALTY_REWARD_TYPE_LOYALTY_POINTS", + "LOYALTY_REWARD_TYPE_SKU_ITEM" + ], + "default": "LOYALTY_REWARD_TYPE_UNSPECIFIED", + "description": "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)." + } + }, + "description": "A single reward granted when a loyalty milestone is reached.", + "title": "Milestone Reward Item" + }, + "description": "All rewards granted when this milestone is reached." + } + }, + "description": "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone.", + "title": "Milestone" + }, + "pointsMultiplierPermille": { + "type": "integer", + "format": "int64", + "description": "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration." + } + }, + "description": "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign.", + "title": "Player Loyalty State" } }, "description": "Request to register a purchase intent with the game backend, at which point the game backend is expected to reserve the inventory for the purchase.", diff --git a/server/egress/shop/purchase/v1/service.proto b/server/egress/shop/purchase/v1/service.proto index 94e8862..f99af93 100644 --- a/server/egress/shop/purchase/v1/service.proto +++ b/server/egress/shop/purchase/v1/service.proto @@ -7,6 +7,7 @@ import "google/api/field_behavior.proto"; import "protoc-gen-openapiv2/options/annotations.proto"; import "server/egress/shop/purchase/v1/enums.proto"; import "server/egress/shop/v1/enums.proto"; +import "server/egress/shop/v1/loyalty.proto"; import "validate/validate.proto"; option go_package = "github.com/stashgg/public-api/gen/proto/server/egress/shop/purchase/v1"; @@ -162,6 +163,9 @@ message RegisterPaymentRequest { }, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Region/country code using ISO-3166-1 alpha-2 code (e.g., 'US', 'CA', 'GB')"} ]; + // Additive, optional. Present only for shops with an active loyalty campaign; + // absent otherwise, so the request is byte-identical to before for non-loyalty shops. + server.egress.shop.v1.PlayerLoyaltyState loyalty = 15 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign."}]; } message RegisterPaymentResponse { @@ -443,6 +447,10 @@ message ConfirmPaymentRequest { ]; } repeated BonusItem bonus_items = 19 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Optional list of bonus items to be granted with the purchase"}]; + + // Additive, optional. Present only for shops with an active loyalty campaign; + // absent otherwise, so the request is byte-identical to before for non-loyalty shops. + server.egress.shop.v1.PlayerLoyaltyState loyalty = 25 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty program standing that applied to this purchase. Present only for shops with an active loyalty campaign."}]; } message ConfirmPaymentResponse { diff --git a/server/egress/shop/v1/enums.proto b/server/egress/shop/v1/enums.proto index 2aa44a0..8dfd801 100644 --- a/server/egress/shop/v1/enums.proto +++ b/server/egress/shop/v1/enums.proto @@ -12,3 +12,12 @@ enum PaymentEnvironment { PAYMENT_ENVIRONMENT_TEST = 1; PAYMENT_ENVIRONMENT_PRODUCTION = 2; } + +enum LoyaltyRewardType { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {description: "The kind of reward granted at a loyalty milestone."}; + LOYALTY_REWARD_TYPE_UNSPECIFIED = 0; + LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY = 1; + LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY = 2; + LOYALTY_REWARD_TYPE_LOYALTY_POINTS = 3; + LOYALTY_REWARD_TYPE_SKU_ITEM = 4; +} diff --git a/server/egress/shop/v1/loyalty.proto b/server/egress/shop/v1/loyalty.proto new file mode 100644 index 0000000..cc6665f --- /dev/null +++ b/server/egress/shop/v1/loyalty.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package server.egress.shop.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; +import "server/egress/shop/v1/enums.proto"; + +option go_package = "github.com/stashgg/public-api/gen/proto/server/egress/shop/v1"; + +// PlayerLoyaltyState is the loyalty program sub-payload describing a player's +// standing in a shop's active loyalty campaign at a point in time, embedded on +// purchase requests to report the standing that applied to a purchase. It is a +// snapshot: every value is scoped to the live campaign identified by campaign_id. +// Its Milestone and MilestoneRewardItem messages are nested so this egress +// payload is self-contained and shares nothing with other APIs. +message PlayerLoyaltyState { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Player Loyalty State" + description: "A player's standing in a shop's active loyalty campaign at a point in time." + } + }; + string campaign_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the live loyalty campaign (season) this snapshot is scoped to."}]; + uint32 total_points = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty points balance for this campaign."}]; + int32 points_delta = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Signed change in the loyalty points balance caused by the associated event (for example, a purchase); negative on a refund reversal, zero when the balance did not change."}]; + optional string current_tier_id = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance."}]; + optional string previous_tier_id = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change."}]; + optional uint32 points_to_next_tier = 6 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier."}]; + optional uint32 points_to_next_milestone = 7 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone."}]; + Milestone next_milestone = 8 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone."}]; + // points_multiplier_permille is a fixed-point ratio expressed in permille + // (thousandths) so partners can compute in-game bonuses with exact, + // deterministic integer arithmetic across platforms. Divide by 1000 to get the + // multiplier: 1000 = 1.0x, 1500 = 1.5x, 3000 = 3.0x. + uint32 points_multiplier_permille = 9 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration."}]; + + // MilestoneRewardItem describes a single reward granted at a loyalty milestone. + message MilestoneRewardItem { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Milestone Reward Item" + description: "A single reward granted when a loyalty milestone is reached." + } + }; + string item_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards."}]; + uint32 quantity = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Amount of the reward granted."}]; + LoyaltyRewardType type = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)."}]; + } + + // Milestone identifies the next loyalty milestone a player is progressing toward + // and lists every reward it grants. Kept intentionally minimal (an identifier + // plus the reward list) and extensible; full milestone configuration is served + // by the loyalty read API. + message Milestone { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Milestone" + description: "The next loyalty milestone a player is progressing toward and the rewards it grants." + } + }; + string milestone_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API."}]; + repeated MilestoneRewardItem rewards = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "All rewards granted when this milestone is reached."}]; + } +} diff --git a/server/ingress/loyalty/v1/enums.proto b/server/ingress/loyalty/v1/enums.proto new file mode 100644 index 0000000..5176373 --- /dev/null +++ b/server/ingress/loyalty/v1/enums.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package server.ingress.loyalty.v1; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "github.com/stashgg/public-api/gen/proto/server/ingress/loyalty/v1"; + +enum LoyaltyRewardType { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_enum) = {description: "The kind of reward granted at a loyalty milestone."}; + LOYALTY_REWARD_TYPE_UNSPECIFIED = 0; + LOYALTY_REWARD_TYPE_IN_GAME_CURRENCY = 1; + LOYALTY_REWARD_TYPE_LOYALTY_CURRENCY = 2; + LOYALTY_REWARD_TYPE_LOYALTY_POINTS = 3; + LOYALTY_REWARD_TYPE_SKU_ITEM = 4; +} diff --git a/server/ingress/loyalty/v1/service.proto b/server/ingress/loyalty/v1/service.proto new file mode 100644 index 0000000..6f49e0c --- /dev/null +++ b/server/ingress/loyalty/v1/service.proto @@ -0,0 +1,237 @@ +syntax = "proto3"; + +package server.ingress.loyalty.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; +import "server/ingress/loyalty/v1/enums.proto"; +import "validate/validate.proto"; + +option go_package = "github.com/stashgg/public-api/gen/proto/server/ingress/loyalty/v1"; + +// Partner-facing Loyalty read API, authenticated with HMAC over the SDK surface +// (/sdk/studio/{shop_id}/...). It exposes a shop's active loyalty campaign +// configuration (tiers and milestones) and a single player's loyalty standing. +service LoyaltyService { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_tag) = { + name: "Loyalty" + description: "Read-only endpoints for querying a shop's loyalty program" + }; + rpc GetLoyalty(GetLoyaltyRequest) returns (GetLoyaltyResponse) { + option (google.api.http) = {get: "/sdk/studio/{shop_id}/loyalty"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Get loyalty program configuration" + description: "Retrieves the shop's active loyalty campaign configuration: its tiers (ordered by starting points) and milestones (ordered by points needed). Returns NOT_FOUND when the shop has no active loyalty campaign." + security: [ + { + security_requirement: {key: "hmac"} + } + ] + }; + } + rpc GetPlayerLoyalty(GetPlayerLoyaltyRequest) returns (GetPlayerLoyaltyResponse) { + option (google.api.http) = {get: "/sdk/studio/{shop_id}/loyalty/players/{player_id}"}; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Get a player's loyalty standing" + description: "Retrieves a single player's loyalty standing (points balance, current tier, distance to the next tier and milestone, and effective points multiplier) for the shop's active loyalty campaign. Returns NOT_FOUND when the shop has no active loyalty campaign." + security: [ + { + security_requirement: {key: "hmac"} + } + ] + }; + } +} + +message GetLoyaltyRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Get Loyalty Request" + description: "Request to get a shop's loyalty program configuration" + required: ["shop_id"] + } + }; + string shop_id = 1 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).string.uuid = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Shop identifier from Stash Studio"} + ]; +} + +message GetLoyaltyResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Get Loyalty Response" + description: "A shop's active loyalty campaign configuration" + required: ["activeCampaign"] + } + }; + Campaign active_campaign = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The shop's active loyalty campaign (season): its tiers, each with the milestones that belong to it."}]; + + // Campaign is a loyalty campaign (season) and everything it configures: its + // tiers, and the milestones nested under each tier. A future response could add + // a next_campaign of the same shape plus a current-to-next tier mapping. + message Campaign { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Loyalty Campaign" + description: "A loyalty campaign (season) with its tiers and milestones." + required: ["campaignId"] + } + }; + string campaign_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the loyalty campaign (season)."}]; + repeated LoyaltyTier tiers = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The campaign's tiers, ordered by starting points ascending; each tier lists its own milestones. May be empty."}]; + + // LoyaltyTier is one tier in a shop's active loyalty campaign, describing the + // points threshold at which the tier begins, the points-per-dollar rate it + // earns, and the milestones that belong to it. + message LoyaltyTier { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Loyalty Tier" + description: "A tier in a shop's active loyalty campaign." + required: ["tierId"] + } + }; + string tier_id = 1 [ + (validate.rules).string = {min_len: 1}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Partner-facing tier identifier. Unique within the campaign."} + ]; + string name = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Display name of the tier."}]; + uint32 start_points = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The points balance at which this tier begins. Tiers are returned ordered by this value ascending."}]; + uint32 points_per_dollar = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Loyalty points earned per US dollar of spend while in this tier."}]; + uint32 points_multiplier_permille = 7 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "This tier's configured points-earning rate relative to the base (lowest) tier, expressed in permille; divide by 1000 (base tier = 1000). The ratio of this tier's points-per-dollar to the base tier's points-per-dollar."}]; + optional string icon_url = 5 [ + (validate.rules).string = { + uri: true + ignore_empty: true + }, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "URL to the tier icon image. Omitted when no icon is configured."} + ]; + repeated LoyaltyMilestone milestones = 6 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The milestones that belong to this tier, ordered by points needed ascending. May be empty."}]; + + // LoyaltyMilestone is one milestone belonging to this tier: a points threshold + // that grants rewards when reached. + message LoyaltyMilestone { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Loyalty Milestone" + description: "A milestone belonging to a tier in a shop's active loyalty campaign." + required: ["milestoneId"] + } + }; + string milestone_id = 1 [ + (validate.rules).string = {min_len: 1}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Milestone identifier. Unique within the campaign."} + ]; + string name = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Display name of the milestone."}]; + uint32 points_needed = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The points balance required to reach this milestone. Milestones are returned ordered by this value ascending."}]; + repeated MilestoneRewardItem rewards = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "All rewards granted at this milestone. Empty when no reward is configured."}]; + + // MilestoneRewardItem describes a single reward granted at this milestone. + message MilestoneRewardItem { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Loyalty Milestone Reward Item" + description: "A single reward granted when a loyalty milestone is reached." + } + }; + string item_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards."}]; + uint32 quantity = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Amount of the reward granted."}]; + LoyaltyRewardType type = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)."}]; + } + } + } + } +} + +message GetPlayerLoyaltyRequest { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Get Player Loyalty Request" + description: "Request to get a single player's loyalty standing" + required: [ + "shop_id", + "player_id" + ] + } + }; + string shop_id = 1 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).string.uuid = true, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Shop identifier from Stash Studio"} + ]; + string player_id = 2 [ + (google.api.field_behavior) = REQUIRED, + (validate.rules).string = {min_len: 1}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player whose loyalty standing to retrieve"} + ]; +} + +message GetPlayerLoyaltyResponse { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Get Player Loyalty Response" + description: "A single player's loyalty standing" + required: ["loyalty"] + } + }; + PlayerLoyaltyState loyalty = 1 [ + (google.api.field_behavior) = REQUIRED, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty standing for the shop's active campaign."} + ]; + + // PlayerLoyaltyState is a player's standing in the shop's active loyalty + // campaign at a point in time. Its Milestone and MilestoneRewardItem messages + // are nested so this snapshot is self-contained and shares nothing with the + // configuration messages or other APIs. + message PlayerLoyaltyState { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Player Loyalty State" + description: "A player's standing in a shop's active loyalty campaign at a point in time." + } + }; + string campaign_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the live loyalty campaign (season) this snapshot is scoped to."}]; + uint32 total_points = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's loyalty points balance for this campaign."}]; + int32 points_delta = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Signed change in the loyalty points balance caused by the associated event; negative on a refund reversal, zero when the balance did not change."}]; + optional string current_tier_id = 4 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's current tier, as the partner-facing tier identifier. Omitted when no tier matches the balance."}]; + optional string previous_tier_id = 5 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The player's prior tier, set only when the associated event changed the tier. Omitted when the tier did not change."}]; + optional uint32 points_to_next_tier = 6 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Loyalty points still needed to reach the next tier. Omitted when the player is already at the top tier."}]; + optional uint32 points_to_next_milestone = 7 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Loyalty points still needed to reach the next unreached milestone. Omitted when there is no further milestone."}]; + Milestone next_milestone = 8 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The next milestone the player is progressing toward, including every reward it grants. Omitted when there is no further milestone."}]; + // points_multiplier_permille is a fixed-point ratio expressed in permille + // (thousandths) so partners can compute in-game bonuses with exact, + // deterministic integer arithmetic across platforms. Divide by 1000 to get the + // multiplier: 1000 = 1.0x, 1500 = 1.5x, 3000 = 3.0x. + uint32 points_multiplier_permille = 9 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The effective (applied) points-earning multiplier for the player's current loyalty tier, relative to the base (lowest) tier, expressed in permille; divide by 1000 (1500 = 1.5x; base tier = 1000). Exact per-tier rates are available in the tier configuration."}]; + + // MilestoneRewardItem describes a single reward granted at a loyalty milestone. + message MilestoneRewardItem { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Player Milestone Reward Item" + description: "A single reward granted when a loyalty milestone is reached." + } + }; + string item_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the reward: a shop bonus identifier for currency/points rewards, or the product's external identifier (SKU) for product rewards."}]; + uint32 quantity = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Amount of the reward granted."}]; + LoyaltyRewardType type = 3 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The kind of reward granted (in-game currency, loyalty currency, loyalty points, or a SKU item)."}]; + } + + // Milestone identifies the next loyalty milestone a player is progressing + // toward and lists every reward it grants. Kept intentionally minimal (an + // identifier plus the reward list) and extensible. + message Milestone { + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { + json_schema: { + title: "Player Next Milestone" + description: "The next loyalty milestone a player is progressing toward and the rewards it grants." + } + }; + string milestone_id = 1 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Identifier of the milestone; the same identifier reported as milestoneId on the loyalty milestone claimed event and by the loyalty read API."}]; + repeated MilestoneRewardItem rewards = 2 [(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "All rewards granted when this milestone is reached."}]; + } + } +}