Integrate and Use Provider Entitlements #

To use Pragma Engine’s Provider Entitlement feature, there are several steps that need to be taken:

  1. Set up a third-party provider of your choice.
  2. Link accounts between the third-party provider and Pragma Engine.
  3. Map offerings in Pragma Engine’s Content Data system.
  4. Configure the Entitlement Provider Plugin in Pragma Engine.
  5. Sync entitlements.
  6. (optional) Access provider entitlement history.

Set up a third-party provider #

Before integrating with Pragma Engine, an account with a third-party provider of your choice is needed. We’ll be using the third-party provider to manage store catalogs. This includes setting up item descriptions, regional pricing, and creating and completing purchases. In most cases, this will also involve the use of a third-party provider’s SDK to render an in-game store.

Once a purchase has been completed, the player receives an entitlement on their account in the third-party provider’s system. Pragma Engine then handles the process of granting the entitlement to the player.

For more information on each third-party provider’s system, refer to their respective documentation:

See Epic Games Store’s Ecom Interface Overview documentation.
See Twitch’s Drops Guide documentation for details on how to grant in-game rewards to Twitch stream viewers.
The Provider Entitlement feature allows for integration with multiple third party providers.

To allow Pragma Engine to fetch entitlements, set up account linking between the chosen third-party provider and Pragma Engine. See Identity Providers for details on how to configure each provider with Pragma Engine.

Set up configuration for fetching linked accounts #

During the SyncEntitlements stage, the service makes a request to the Accounts service to fetch all linked provider identities for players. To allow the Game node to make this request to the Social node, a social bearer token needs to be configured.

To set up this configuration, add a SocialBackendPartnerClientConfig with a bearerToken that has the encrypted Social Partner token.

game:
  serviceConfigs:
    SocialBackendPartnerClientConfig:
      bearerToken: <encrypted social partner token>
Check out the Generate Partner tokens guide if you need help generating Partner tokens.

Map offerings in Pragma Engine #

In this section, we’ll go over defining entitlement content in Pragma Engine’s Content Data system.

Collect the IDs #

To grant players the items from their entitlements, first collect the third-party provider’s generated IDs. These IDs are used by Pragma Engine to know what corresponding content to grant a player when an entitlement is consumed.

In some cases, third-party providers may have different locations for their generated IDs. As shown in the tabs below, Steam has different locations for their Steam Inventory and Steam App IDs.
  1. Log into the Epic Games Developer Portal.
  2. Navigate to your product’s Epic Games Store dashboard.
  3. Go to Offers and click on an offer.
  4. Under the right panel Offer Details, collect the Audience Item IDs.
  • Note the offer type because that will be used when creating provider entitlement content in Pragma Engine. Pragma Engine currently supports the offer types: CONSUMABLE, ADDON, EDITION, DEMO, DIGITALEXTRA, SEASONPASS, and GAME.

To find the generated IDs for items such as cosmetics in Steam’s Inventory Service:

  1. Log into Steamworks.
  2. Navigate to the Steamworks Admin page for your game.
  3. Hover over the Community tab, click Inventory Service.
  4. Under the Item Definitions section, click Edit Raw Definition and collect the itemdefid.

To find Steam App Ids:

  1. Log into Steamworks.
  2. Navigate to the Steamworks Admin page for your game.
  3. Go to Apps and Packages.
  4. Click on View All Associated Items.
  5. Under the All DLC section, grab the values under the # column. These are the AppIds.
  1. Log into the Twitch Developer Portal.
  2. Navigate to Drops Campaigns.
  3. Go to Reward Manager and collect the Reward IDs.

Author Player Data Operation #

Player Data operations can modify a player’s live data (entities and components) as a result of an entitlement purchase. These Operations must be allowed on the SERVICE SessionType in order to be called from an Entitlement, and are assigned to entitlements under the playerDataOperation property.

See the Player Data Operations docs for more information.

grantSkin Player Data Operation
package playerdata.demo.playerdata

import pragma.playerdata.Component
import pragma.playerdata.Context
import pragma.playerdata.PlayerDataContentLibrary
import pragma.playerdata.PlayerDataOperation
import pragma.playerdata.PlayerDataRequest
import pragma.playerdata.PlayerDataResponse
import pragma.playerdata.PlayerDataSubService
import pragma.rpcs.SessionType


data class GrantSkin(
    val id: String,
): PlayerDataRequest
class GrantSkinResponse: PlayerDataResponse

data class Skin(val id: String): Component

class EntitlementModule(
    contentLibrary: PlayerDataContentLibrary
): PlayerDataSubService(contentLibrary) {

    @PlayerDataOperation(sessionTypes = [SessionType.SERVICE])
    fun grantSkin(grantSkin: GrantSkin, context: Context): GrantSkinResponse {
        // can validate skin id against custom content for skins

        // all skins are stored in one unique entity
        val entity = context.snapshot.getOrCreateUniqueEntity("skins")

        // check if player already has this skin
        if (entity.getComponentsByType<Skin>().any { it.id == grantSkin.id }) {
            /* can throw an ApplicationErrorException and the entitlement record status will stay pending - customer support can help resolve */
        }

        // add the new skin and return success response
        entity.addComponent(Skin(grantSkin.id))
        return GrantSkinResponse()
    }
}
ProviderEntitlement.json content using a Player Data Operation named grantSkin
 {
    "id": "player-data-demo",
    "playerDataOperation": {
     // `PlayerDataRequestProto`for the `grantSkin` PlayerDataOperation
      "grantSkin": {
        "id": "example_skin"
      }
    },
    "providerDetails": [
      {
        "steamEntitlement": {
          "inventoryItem": {
            "itemDefId": 510
          }
        }
      },
      {
        "epicEntitlement": {
          "audienceItemId": "272f58c3e1a44fb7b486e1832ed6b3d6",
          "offerType": "CONSUMABLE"
        }
      }
    ]
  }

Create provider entitlement content #

Provider entitlement content is defined within content/src/ProviderEntitlements.json. The structure of provider details is specific to each provider. To genereate the Provider Entitlement content files, run make init-provider-entitlement-content.

While mapping entitlements in Pragma Engine, keep in mind that a third-party provider’s generated ID can only be mapped to a single data type in Pragma Engine.

Creating provider entitlement content for each supported third-party provider:

  1. Create a content entry in ProviderEntitlements.json.
    • id must be a unique string for each entry.
  2. Assign a Player Data Operation grant type.
  3. Add Epic as a provider under providerDetails.
    • Create an epicEntitlement object.
    • Add the collected audienceItemId from Epic Games Store.
    • Define the Epic offer type. Pragma Engine supports the types: CONSUMABLE, ADDON, EDITION, DEMO, DIGITALEXTRA, SEASONPASS, and GAME.
// example ProviderEntitlement.json content object for Epic
{
  "id": "60-elemental-orbs",
  "playerDataOperation": {
    "grantOrbs": {
      "amount": 60
    }
  },
  "providerDetails": [
    {
      "epicEntitlement": {
        "audienceItemId": "<audienceItemId>",
        "offerType": "<offer type>"
      }
    }
  ]  
}
  1. Create a content entry in ProviderEntitlements.json.
    • id must be a unique string for each entry.
  2. Assign a Player Data Operation grant type.
  3. Add Steam as a provider under providerDetails.
    • Create a steamEntitlement object.
    • Add the collected itemDefId and/or appId.
// example ProviderEntitlement content object for Steam
{
  "id": "60-elemental-orbs",
  "playerDataOperation": {
    "grantOrbs": {
      "amount": 60
    }
  },
  "providerDetails": [
    {
      "steamEntitlement": {
        "inventoryItem": {
          "itemDefId": "<itemDefId>"
        }
      }
    },
    {
      "steamEntitlement": {
        "app": {
          "appId": "<appId>" 
        }
      }
    }
  ]
}
  1. Create a content entry in ProviderEntitlements.json.
    • id must be a unique string for each entry.
  2. Assign a Player Data Operation grant type.
  3. Add Twitch as a provider under providerDetails.
    • Create a twitchDrop object.
    • Add the collected rewardId.
 // example ProviderEntitlement content object for Twitch
{
  "id": "60-elemental-orbs",
  "playerDataOperation": {
    "grantOrbs": {
      "amount": 60
    }
  },
  "providerDetails": [
    { 
      "twitchDrop": { 
        "rewardId": "<rewardId>" 
      } 
    }
  ]  
}
ProviderEntitlements.json Example
// ProviderEntitlements.json with multiple providers
[
  {
    "id": "60-elemental-orbs",
    "playerDataOperation": {
      "grantOrbs": {
        "amount": 60
      }
    },
    "providerDetails": [
      {
        "steamEntitlement": {
          "inventoryItem": {
            "itemDefId": 10010
          }
        }
      },
      {
        "epicEntitlement": {
          "audienceItemId": "294f58c3e1a44fb7b456j1832er7h3d8",
          "offerType": "CONSUMABLE"
        }
      },
      { 
        "twitchDrop": { 
           "rewardId": "df1e2846-be27-4188-8da9-665aa0a2204d" } 
      }
    ]
  },
  {
    "id": "founders-edition-bundle",
    "playerDataOperation": {
      "specialEditionGrants": {
        "id": "founders-edition"
      }
    },
    "providerDetails": [
      {
        "steamEntitlement": {
          "app": {
            "appId": 2215760
          }
        }
      },
      {
        "epicEntitlement": {
          "audienceItemId": "7fa45ecd47e243e6b55dd0dec34f12cb",
          "offerType": "EDITION"
        }
      }
    ]
  }
]

Apply content data #

In order to register the content you just defined with Pragma Engine, you must apply your content data changes. You may apply content data either using the command line with make or via an IntelliJ run configuration.

Applying content data using Make

In a terminal with platform as the working directory, run:

make ext-contentdata-apply
Applying content data using IntelliJ
From the IntelliJ toolbar in the upper right, ensure contentdata apply is selected, then click the play button.
If the JSON is not valid, Pragma Engine returns an error message and the content will not be applied.

Configure the Entitlement Provider Plugin #

The following are supported preimplemented plugins for third-party providers:

ProviderPragma Engine Plugin
Epic Games Storepragma.inventory.EpicEntitlementProviderPlugin
Steampragma.inventory.SteamEntitlementProviderPlugin
Twitchpragma.inventory.TwitchEntitlementProviderPlugin
For third-party providers not listed, studios can implement a custom Entitlement Provider Plugin. See the Create a Custom Entitlement Provider Plugin section for more information.

To enable integration with your chosen third-party providers, configure the appropriate plugin in your Pragma Engine config:

  1. In Pragma Engine, open local-dev.yml.
  2. Add Epic as an Entitlement Provider under InventoryService.entitlementProviderPlugins.
    # local-dev.yml 
    
    game:
      pluginConfigs:
        InventoryService.entitlementProviderPlugins:
          plugins:
            Epic:
              class: "pragma.inventory.EpicEntitlementProviderPlugin"
              config:
                sandboxId: "<sandbox>"
                deploymentId: "<deploymentId>"
                clientId: "<clientId>"
                clientSecret: "<encryptedClientSecret>"
    
  3. Update sandbox, deploymentId, clientId, and clientSecret in local-dev.yml. To find these values, log into the Epic Games Developer Portal:
    • Select the desired product.
    • Click Product Settings. If the SDK Downloads & Credentials tab is not active, click it.
  1. In Pragma Engine, open local-dev.yml.
  2. Add Steam as an Entitlement Provider under InventoryService.entitlementProviderPlugins.
    # local-dev.yml 
    
    game:
      pluginConfigs:
        InventoryService.entitlementProviderPlugins:
          plugins:
            Steam:
              class: "pragma.inventory.SteamEntitlementProviderPlugin"
              config:
                appConfig:
                  appId: "<appId>"
                  steamWebAPIKey: "<steamWebAPIKey>"
    
  3. Update appId and steamWebAPIKey in local-dev.yml. To find these values, log into Steamworks:
    • Navigate to Users & Permissions and click Manage Groups.
    • Select the group for the application you want.
    • Get the Web API key.
  1. In Pragma Engine, open local-dev.yml.
  2. Add Twitch as an Entitlement Provider under InventoryService.entitlementProviderPlugins.
    # local-dev.yml 
    
    game:
      pluginConfigs:
        InventoryService.entitlementProviderPlugins:
          plugins:
            Twitch:
              class: "pragma.inventory.TwitchEntitlementProviderPlugin"
              config:
                clientId: "<clientId>"
                clientSecret: "<encryptedClientSecret>"
                gameId: "<gameId>"
    
  3. Update clientId, clientSecret, and gameId in local-dev.yml. To find these values, log into the Twitch Developer Portal:
    • clientId:
      • Navigate to Drops Campaigns.
      • Go to Applications and click Manage.
      • Under the section ClientID get the client ID.
    • clientSecret
      • Use the Client Secret key associated to your application.
      • Navigate to Drops Campaigns.
      • Go to Applications and click Manage.
      • Click on New Secret.
    • gameId
      • Navigate to Drops Campaigns.
      • Click on Edit Details.
      • Hover over Game ID to get the game ID.

Pragma Engine allows for simultaneous integration with multiple third-party providers.

Create a custom Entitlement Provider Plugin #

Pragma Engine offers the flexibility to create custom Entitlement Provider Plugins to integrate with any third-party provider.

Below is a plugin interface to implement a custom Entitlement Provider Plugin. Refer to the inline comments for detailed instruction:

data class Entitlement(val id: String, val thirdPartyItemId: String)

/**
* Plugin interface used to manage entitlement fulfillment with a third party provider.
*/
interface EntitlementProviderPlugin: PragmaServicePlugin {
  /**
  * A unique ID for the provider this plugin is implemented for.
  * Used when mapping entitlements returned from [fetchPlayerEntitlements] to
  * content configured in `ThirdPartyEntitlementMappings.json`
  *
  * @returns a string representing an ID of the provider, ex: "EPIC", "TWITCH", etc.
  */
  fun entitlementProviderId(): String


  /**
  * Used by the Inventory Service to obtain a full view of the player's entitlements in
  * the third party provider. These results will be used to determine if any
  * Pragma content should be granted to the player's inventory.
  *
  * Note: Unconsumed entitlements that are returned from this method will be passed back
  * to [markEntitlementsConsumed] once they have been marked for fulfillment internally  
  * in Pragma.
  *
  * @returns all active basic entitlements and all unconsumed entitlements.
  */
  suspend fun fetchPlayerEntitlements(
    playerId: PlayerId,
    idProviderAccounts: List<IdProviderAccount>
  ): List<Entitlement>
                    

  /**
  * This method will be invoked with a list of entitlements previously returned by
  * [fetchPlayerEntitlements] that had been successfully marked for fulfillment  
  * internally in Pragma.
  */
  suspend fun markEntitlementsConsumed(
    playerId: PlayerId,
    idProviderAccounts: List<IdProviderAccount>, entitlements: List<Entitlement>
  )

  /**
  * if the [ProviderEntitlement] has [ProviderDetails] for this plugin's provider, 
  * return the thirdPartyItemIds from those details
  */
  fun getThirdPartyItemIdsForProvider(providerEntitlement: ProviderEntitlement):       
    List<String> = listOf()
  }

Sync entitlements #

Use Pragma Engine’s InventoryRpc.SyncEntitlementsV1 endpoint to give players the corresponding content listed in their entitlements. This call can be made any time we want to check when a player’s account has been given an entitlement, such as on login or purchase completion.

The following is the information returned on entitlements:

  • FulfilledEntitlements are entitlements that have been granted to a player and are now marked as fulfilled by Pragma Engine.
  • UnfulfilledEntitlements are entitlements that failed to be granted to a player. This can be due to a number of issues; there are available server logs for more details.
// On InventoryService
void SyncEntitlements(FOnCompleteSyncEntitlementsDelegate Delegate);
DECLARE_DELEGATE_OneParam(
  FOnCompleteSyncEntitlementsDelegate,
  TPragmaResult<FPragmaSyncEntitlementsResults>
);

struct FPragmaSyncEntitlementsResults
{
  TArray<FPragma_Inventory_ProviderEntitlementV1> FulfilledEntitlements;
  TArray<FPragma_Inventory_ProviderEntitlementV1> UnfulfilledEntitlements;
};
// On InventoryService
public Future<SyncEntitlementsResults> SyncEntitlements()
public void SyncEntitlements(SyncEntitlementsDelegate completeDelegate)

public delegate void SyncEntitlementsDelegate(Result<SyncEntitlementsResults> result);

public class SyncEntitlementsResults
{
   public IReadOnlyList<ProviderEntitlementV1> FulfilledEntitlements { get; }
   public IReadOnlyList<ProviderEntitlementV1> UnfulfilledEntitlements { get; }
}
message SyncEntitlementsV1Request {
  option (pragma_session_type) = PLAYER;
  option (pragma_message_type) = REQUEST;
}

/*
* Returns lists of fulfilled and unfulfilled entitlements by their provider and provider
* item ID. Details of the items granted can be looked up in the  
* ProviderEntitlementMappings content file. Subsequent calls will attempt to fulfill
* any unfulfilled entitlements.
*/
message SyncEntitlementsV1Response {
 option (pragma_session_type) = PLAYER;
 option (pragma_message_type) = RESPONSE;

 // List of Provider and Item ID for all entitlements that were fulfilled by this request.
 repeated ProviderEntitlementV1 fulfilled = 1;

 // List of Provider and Item ID for all entitlements that were available
 // but unable to be fulfilled
 repeated ProviderEntitlementV1 unfulfilled = 2;
}

/*
* Represents an entitlement from a specific provider.
*/
message ProviderEntitlementV1 {
 // The identifier of the provider granting the entitlement.
 string provider_id = 1;


 // The provider's identifier of the entitlement,
 // mapped to Pragma Inventory content in ProviderEntitlementMappings.
 string provider_item_id = 2;
}

Access provider entitlement history #

Pragma Engine tracks the processing of player entitlements for player support. There are Operator and Partner endpoints that can be used to access this data.

The GetProviderEntitlementHistory endpoint returns a ProviderEntitlementRecord for each entitlement Pragma Engine has recieved from a configured Provider Entitlement Plugin.

This record includes the following information:

parameterdescription
player_idplayer ID from request
fulfillment_idused in the database layer to make sure entitlements are only granted once
provider_idprovider ID assigned in the Provider Entitlement Plugin which identifies the provider for this entitlement
provider_entitlement_idcontent entry ID which identifies the item grants for this entitlements
provider_item_idprovider ID used by the third-party provider to identify the purchased entity
provider_entitlement_record_statusprocessing status–either pending or completed
fulfilled_timestamp_millistime in milliseconds it took to fulfill the item grant

The GetProviderEntitlementHistory endpoint returns a ProviderEntitlementRecord for each entitlement Pragma Engine has recieved from a configured Provider Entitlement Plugin. This record includes information like the status (pending or completed) and the time in milliseconds it took to fulfill the entitlement.

// in PragmaInventoryPartnerServiceRaw

virtual void GetProviderEntitlementHistoryPartnerV1(
  const FPragma_Inventory_GetProviderEntitlementHistoryPartnerV1Request& Request,
  FGetProviderEntitlementHistoryPartnerV1Delegate Delegate
) const;

virtual void GetProviderEntitlementHistoryPartnerV1(
  const FPragma_Inventory_GetProviderEntitlementHistoryPartnerV1Request& Request,
  TUniqueFunction<
    void(TPragmaResult<FPragma_Inventory_GetProviderEntitlementHistoryPartnerV1Response>,
      const FPragmaMessageMetadata&)> Callback
) const;
};
// In InventoryServiceRaw.cs

public virtual void GetProviderEntitlementHistoryPartnerV1(
    GetProviderEntitlementHistoryPartnerV1Request request,
    Protocol.OnComplete<GetProviderEntitlementHistoryPartnerV1Response> callback)
{
    Connection.Game.SendMessage(request, callback);
}

public RpcFuture<GetProviderEntitlementHistoryPartnerV1Response>
    GetProviderEntitlementHistoryPartnerV1(
        GetProviderEntitlementHistoryPartnerV1Request request)
{
    return Connection.Game
        .SendMessage<GetProviderEntitlementHistoryPartnerV1Request,
            GetProviderEntitlementHistoryPartnerV1Response>(request);
}

Partner:

Request

message GetProviderEntitlementHistoryPartnerV1Request {
  option (pragma_session_type) = PARTNER;
  option (pragma_message_type) = REQUEST;

  // The playerId to return records for
  Fixed128 player_id = 1;
  // The index of the page to view for pagination.
  int32 page_index = 2;
  // The size of the page to view for pagination, equivalent to the entry count of historical purchases.
  int32 page_size = 3;
}

Response

message GetProviderEntitlementHistoryPartnerV1Response {
  option (pragma_session_type) = PARTNER;
  option (pragma_message_type) = RESPONSE;

  // list of player's entitlement history records
  repeated ProviderEntitlementRecord provider_entitlement_records = 1;
}

Operator:

Request

message GetProviderEntitlementHistoryOperatorV1Request {
  option (pragma_session_type) = OPERATOR;
  option (pragma_message_type) = REQUEST;

  // The playerId to return records for
  Fixed128 player_id = 1;

  // The index of the page to view for pagination.
  int32 page_index = 2;

  // The size of the page to view for pagination, equivalent to the entry count of historical purchases.
  int32 page_size = 3;
}

Response

message GetProviderEntitlementHistoryOperatorV1Response {
  option (pragma_session_type) = OPERATOR;
  option (pragma_message_type) = RESPONSE;

  // list of player's entitlement history records
  repeated ProviderEntitlementRecord provider_entitlement_records = 1;
}