开发者可以借助 Epic在线服务 (EOS)的 电子商务接口,支持通过 Epic Games商店 (EGS)进行的游戏内购。利用此接口,你可以管理主游戏、下载内容(DLC)、虚拟产品及游戏内货币等内容。具体操作包括提供商品、完成购买交易、验证所有权,以及兑换已购买项目。
请查阅使用Web API进行所有权验证,了解如何使用Web API处理Ecom接口。
通过平台接口函数 EOS_Platform_GetEcomInterface
获取 EOS_HEcom
句柄,即可访问电子商务接口。所有电子商务接口函数均需将此句柄作为首个参数。必须确保 EOS_HPlatform
句柄可以更新(tick),以便在请求完成时触发回调。
要使用电子商务接口,你的产品必须激活 Epic账户服务 (EAS),并且必须获取用户同意以便访问 基本描述(Basic Profile) 数据。你可以在开发者门户上激活EAS,或通过Epic文档了解详情。如果没有EAS和用户的同意,你仍然可以初始化EOS的SDK和电子商务接口,但所有电子商务接口函数调用后端服务时都会失败。
设立游戏内商店
EOS下的游戏内商店执行两个主要功能:将商店的 目录商品 (有时称为"商品")对用户展示,以便购买;以及让用户能够执行购买。EOS SDK拥有从商店目录中获取商品列表的功能,并按照游戏开发者所希望的方式展示商品。商品数据包含用户将获得的一个或多个项目,以及主要图片和细节,为用户展示商品的信息。
用户选择执行购买后,结账进程将把数据推送至 EGS覆层,其将结束购买流程。EOS将通知游戏的交易结果,提供交易柄,以便游戏执行交易成功完成后的相关操作。
如果设置了EOS_PF_DISABLE_OVERLAY
标志,则Ecom接口中相关的购买功能会被禁用。
目录商品数据
目录商品(类型 EOS_Ecom_CatalogOffer
)包含本地化价格信息和描述文本(这些内容基于浏览商店的用户地区)。本地价格会包含货币代码(例如代表美元的USD),并以此货币的最小单位显示。举例而言:价格为2.99美元的商品对应的值则是299。
展示商店的目录
调用 EOS_Ecom_QueryOffers
函数即可获取开发者门户中定义的目录商品列表。此函数接受一个 EOS_Ecom_QueryOffersOptions
数据结构并将在完成时调用你的委托(类型为 EOS_Ecom_QueryOffersCallbackInfo
)。将 EOS_Ecom_QueryOffersOptions
结构进行如下初始化:
属性 |
值 |
---|---|
|
|
|
请求查询的本地用户的 |
|
可选产品命名空间(如非初始化中指定) |
如果委托接收到 EOS_Ecom_QueryOffersCallbackInfo
中的 ResultCode
代表成功,则你请求的商品数据将在临时缓存中可用。使用 EOS_Ecom_GetOfferCount
来确定缓存中保存的商品数量,并使用 EOS_Ecom_CopyOfferByIndex
从其中获得单个商品(类型为 EOS_Ecom_CatalogOffer
)的副本。商品中每个项目将包含此项目相关的图像和发布信息的数据,你可以单独提取这些数据。你不再需要商品的副本时,可使用 EOS_Ecom_CatalogOffer_Release
将其释放。
显示个体项目数据
在从缓存获取商品之后,你可以调用 EOS_Ecom_GetOfferItemCount
来确定商品包含的项目数量,然后使用 EOS_Ecom_CopyOfferItemByIndex
来获取单独 EOS_Ecom_CatalogItem
的副本。此结构包含额外本地化文本,以及购买时提供的授权ID。要释放分配给数据副本的内存,请调用 EOS_Ecom_CatalogItem_Release
。
与项目相关的图像也在缓存中可用;你可以调用 EOS_Ecom_GetItemImageInfoCount
来确定与给定项目相关的图像数量,然后使用 EOS_Ecom_CopyItemImageInfoByIndex
来获取图像URL的 EOS_Ecom_KeyImageInfo
、尺寸和预期用途。不再需要此数据的副本时,使用 EOS_Ecom_KeyImageInfo_Release
来释放其使用的内存。
要从缓存获取项目的释放细节,先使用 EOS_Ecom_GetItemReleaseCount
来发现数据条目的数量。然后即可使用 EOS_Ecom_CopyItemReleaseByIndex
来获取数据(类型为 EOS_Ecom_CatalogRelease
)单独的块。不再需要此数据时,可调用 EOS_Ecom_CatalogRelease_Release
将其释放。
从商店进行购买
用户从商店进行购买时,此用户将接受目录商品。购买完成后,用户将拥有商品中包含的项目,而每个项目都会为购买者的用户帐户添加一个或多个 授权。
从商店进行购买涉及到3个行为:进行购买、验证所有权,以及兑换授权(这是可选项)。
用户决定进行购买后,可使用一个 EOS_Ecom_CheckoutOptions
结构调用 EOS_Ecom_Checkout
。将结构进行以下初始化:
属性 |
值 |
---|---|
|
|
|
进行购买的本地用户的 |
|
如未提供,当前的 |
|
在 |
|
一个 |
进行此调用后,EOS将使用此信息来生成购买令牌。EOS将使用此购买令牌来打开其自身的覆层,使用户可以查看购买、选择支付选项,并确认或取消交易。覆层关闭时(无论是因成功、失败、或被用户取消),你的回调(类型为 EOS_Ecom_OnCheckoutCallback
)运行时将带有一项 EOS_Ecom_CheckoutCallbackInfo
参数,此参数将讲述交易详情。在回调信息中,如交易成功则 TransactionId
为非空,如失败或被取消则为空。
如果对 EOS_Ecom_Checkout
的任何额外调用在通过回调的首次返回之前执行,则会出现一个 EOS_AlreadyPending
错误。
访问已完成的购买数据
如用户成功进行购买之后获得有效交易ID,可以将其传至 EOS_Ecom_CopyTransactionById
函数来获得一个 EOS_Ecom_HTransaction
。EOS_Ecom_Transaction_GetEntitlementsCount
将返回与交易相关的授权数量,EOS_Ecom_Transaction_CopyEntitlementByIndex
将获取一个单独授权。不再需要此数据时,调用 EOS_Ecom_Entitlement_Release
进行释放。
与交易ID相关的授权包含的数据与通过 EOS_Ecom_QueryEntitlement
函数获取的数据相同,但缓存策略有所不同。由于存在此差异,进行购买之后使用交易ID获取的授权将保持为交易专属的缓存,直到显式调用 EOS_Ecom_Transaction_Release
。
完成购买
进行购买后,用户的帐户将获得授权,但用户可能还不能在游戏中看到购买生效。在一些情况下,完成购买比较简单,就是检查用户拥有特定授权,并对结果应用一些游戏逻辑。在这些情况下,通过EOS验证所有权已足够。在其他情况下(例如涉及消耗品或游戏货币的购买),则可能需要在游戏中、或兑换授权通过第三方服务来完成订单。
枚举授权
要获取用户帐户的授权,用 EOS_Ecom_QueryEntitlementsOptions
结构调用 EOS_Ecom_QueryEntitlements
函数。此结构的初始化如下:
属性 |
值 |
---|---|
|
|
|
要获取其授权的本地用户的 |
|
要检查的授权名的数组。 |
|
|
|
如为true,已兑换的授权将包含在结果中。 |
操作完成时,EOS将缓存结果信息并用一个 EOS_Ecom_QueryEntitlementsCallbackInfo
参数运行你的回调函数(类型为 EOS_Ecom_OnQueryEntitlementsCallback
)。如此参数的 ResultCode
为 EOS_Success
,缓存将包含你请求的数据。你可以调用 EOS_Ecom_GetEntitlementsCount
来确定缓存中授权的数量;调用 EOS_Ecom_CopyEntitlementByIndex
来获取个体元素(类型为 EOS_Ecom_Entitlement
)的副本,其中包括提供授权的目录项目ID、该授权的专属ID,以及其他相关数据。
兑换授权
完成授权、或通过第三方服务管理完成之后,以 EOS_Ecom_RedeemEntitlementsOptions
结构调用 EOS_Ecom_RedeemEntitlements
函数。结构初始化如下:
属性 |
值 |
---|---|
|
|
|
兑换授权的用户帐户的ID |
|
|
|
要兑换的授权(类型为 |
完成之后,类型为 EOS_Ecom_OnRedeemEntitlementsCallback
的回调将接收一个 EOS_Ecom_RedeemEntitlementsCallbackInfo
结构。
兑换授权后,其将不再显示在 EOS_Ecom_QueryEntitlements
调用的结果中,除非 EOS_Ecom_QueryEntitlementsOptions
参数的 bIncludeRedeemed
设为 true
。
验证所有权
不建议在游戏客户端中验证所有权,因为终端用户可能会使用软件作弊来绕过该方案。 如有可能,请在一个受信任的游戏服务器或API上验证所有权。 请联系你的技术客户经理,获得防盗版相关的最新建议。
协助防止盗版的最新建议
为了实现防盗版,我们建议开发商对其游戏和所有DLC实施所有权验证。 强烈建议在可信服务器/API上进行在线所有权验证。 一般认为这是防盗版的唯一有效方法。 客户端验证可以用来缓解低复杂度类型的攻击, 例如重新分配未修改的游戏文件。
对于多人游戏或在线游戏:
在受信任的游戏服务器或者后端API使用 电子商务接口 或者 Web API进行所有权验证。
这些验证通常在授权或者进入 关卡/匹配时进行。
如果用户没有通过所有权认证,则拒绝访问。 如果正确实施,玩家应该无法通过更改API响应或者修改游戏客户端的手段来 越过这些验证。
监视异常高活动等级的账号并且 在多用户共用账号的情况下进行查封。
建议将每个账号并行会话的数量限制为1。
对于所有游戏:
虽然无法通过控制客户端来完全杜绝盗版,但仍可以通过以下方式来阻止简易的攻击/分享:
在启动所有权验证API之前检查 游戏可执行文件以及所有可执行代码 (.exe/.dll/.so/等等) 的数字签名和SHA1校验和。 包括验证EOS SDK以及其它所有第三方库。 若发现异常行为便拒绝访问。
要求线上激活并返回加密的验证令牌, 只有在玩家通过线上验证之后才可以离线运行。
该令牌可以与特定的硬件/玩家属性绑定 并且在指定的时间后失效。
必须要保护游戏二进制文件中嵌入的公共密钥不被修改, 这种方式才能有效。
如果发现该账号高频率或者在很多不同设备上进行激活, 拒绝激活。
使用商用的加密/防破解解决方案来使得 逆向破解和修改验证更加复杂。
不要将DLC作为主体游戏文件的一部分。 通过Epic Games商店来发行, 或者要求进行有服务器端所有权认证的下载。
电子商务接口提供两种所有权验证方法,即"在线"和"离线"方法。在线方法与Epic权限服务直接集成,而离线方法提供用户可验证或传递到第三方服务的签名令牌。在线方法适用于受信任的游戏服务器或在客户端系统上进行安全性较低的检查来简单验证。离线方法提供令牌,其中包含游戏客户端、用户和授权的相关信息,以及游戏客户端或外部服务可验证的签名。离线检查可避免外部服务获权访问用户数据,因此与执行所有权验证的第三方服务集成时,建议使用离线检查。
如果希望产品和服务通过RESTful端点访问所有权,请参阅Ecom网络API。
在线所有权验证
要确定用户是否拥有特定的目录项目,调用 EOS_Ecom_QueryOwnership
并传入一个 EOS_Ecom_QueryOwnershipOptions
结构。这将从服务器获取所有权,并将其传至你提供的 EOS_Ecom_OnQueryOwnershipCallback
回调函数。回调函数也将接收一个你指定的空指针,其能够包含你产品的任何信息,以便理解请求的情境。将以下信息填入 EOS_Ecom_QueryOwnershipOptions
结构即可开始:
属性 |
值 |
---|---|
|
|
|
要查询其所有权的本地用户的 |
|
要检查所有权的目录项目ID的列表 |
|
|
|
可选产品命名空间(如非初始化中指定) |
完成时,EOS将用你请求的数据(以及你的空指针)来调用保存在 EOS_Ecom_OnQueryOwnershipCallback
结构中的回调函数。此结构包含一个 EOS_Ecom_EntitlementOwnership
成员的数组,每个成员描述你请求的一个项目,并表明用户是否拥有它。服务器不能识别的项目将返回为未拥有。
离线所有权验证
要检查所有权并将结果缓存数分钟,则使用 EOS_Ecom_QueryOwnershipToken
。此函数接受一个 EOS_Ecom_QueryOwnershipTokenOptions
结构,其初始化如下:
属性 |
值 |
---|---|
|
|
|
要查询授权的用户的 |
|
由最多32个( |
|
|
|
可选产品命名空间(如非初始化中指定) |
操作完成时,你的回调函数(类型为 EOS_Ecom_OnQueryOwnershipTokenCallback
)将接收一个 EOS_Ecom_QueryOwnershipTokenCallbackInfo
结构,其中包含过期时间为5分钟的JSON Web令牌(JWT)。可使用公共密钥验证该JWT,并解包提取密钥ID。也可将其发送给第三方服务,后者可验证授权信息来自EGS。通过其他Web调用而获得或与组织共享的公共密钥将采用JSON Web密钥(JWK)的形式,可用于验证JWT中的签名。将GET发送至 https://ecommerceintegration-public-service-ecomprod02.ol.epicgames.com/ecommerceintegration/api/public/publickeys/{kid}
即可执行HTTP请求。范例请求如下:
GET
/ecommerceintegration/api/public/publickeys/pbvnNIE97vErdePGIRoG41h8hnP_2wIxG8xbwZCIj3g HTTP/1.1
Host: ecommerceintegration-public-service-ecomprod02.ol.epicgames.com
{
"kty": "RSA",
"e": "AQAB",
"kid": "pbvnNIE97vErdePGIRoG41h8hnP_2wIxG8xbwZCIj3g",
"n": "gcStqtD8XD9c9ifNuxXT9Xd_EEZLLCw34yxINRQPt0MxEWkoOFsuisRWGktSFtGrnUuQnp8GQY0k4Pyl_yDItWAcRtO7JUjrhQnxx3xXp_0P8xJMH1ny-RcxHF3bEJWhDzNW5PBpBjQTQZis-83499z-4OlNA7oUnDKEJkqNfzh4mMDFluPxvW_Hwpaw71nhzJI7-N-BdsPsLdqUANajLsFKq9fr06Lek_tm-6-RUxNPE3yS0x0UIsGyapA4Apcczz0xTzRDfwOkq_TyKGZiZc7vtgjkWnqdsCyXZC7dzKJvg0ggO3mKXhqZNNC_2pz24o1X_xCbG8rXtuvX8-ux-Q"
}
所有权验证令牌细节
所有权验证令牌是使用RS512签名的JWT(使用SHA-512的RSA PKCS#1签名,RSA密钥大小为2048),创建5分钟后过期。该令牌包含下列声明:
声明 |
描述 |
---|---|
jti |
此令牌的专属辨识符 |
sub |
用于请求令牌的帐户ID |
clid |
用于请求令牌的客户端ID |
ent |
此令牌验证的授权数组;如此值为空,则帐户无权获得给定sandboxId的请求授权。 |
iat |
表示令牌发放时间的Unix时间戳 |
exp |
表示令牌过期时间的Unix时间戳 |
电子商务接口术语表
本节是电子商务接口中常用的术语及定义。
术语 |
定义 |
---|---|
目录商品(Catalog Offers) |
目录商品是一个或多个目录项目及相关价格(可为0)的组合。购买商品后,将提供每个项目的授权。 |
目录项目(Catalog Item) |
目录项目可以是整个游戏、某种下载内容、或是游戏内货币/武器皮肤之类的虚拟物品。这种项目可以用来定义将授权提供给帐户的方式。项目也包含授权名称,此名称可以用来对授权进行逻辑分组。 |
授权(Entitlement) |
授权是用户拥有的凭证,在不同产品中意义有所不同。授权可以是动态的,提供对一个或多个目录项目的访问;但其本质也可为动态,支持在产品的生命周期中添加或移除项目的访问。 |
消耗性授权(Consumable Entitlement) |
消耗性授权持续性较短。每次在游戏中使用项目时,"使用次数"都将减少。这通常用于游戏内货币、经验值提升、以及会耗尽并再次通过购买进行补充的其他项目。一些情况下会使用外部设备进行授权。外部服务接收到授权相关的信息后,其便会兑换,使用次数变为0,将其从用户帐户移除。在此之后,外部服务将负责处理项目在游戏中的效果。 |
持久授权(Durable Entitlement) |
部分购买拥有持久性,例如下载内容,甚至整个游戏。如果第三方服务需要,责任转移之后其可以将授权标为"不激活"。 |
完成(Fulfillment) |
授权进入用户账户之后,还需要完成。其可为隐式(由SDK API检查);或者第三方服务负责,通过后端API调用来完成。 |
下表包含电子商务接口中使用的不同ID类型,以及他们的来源和描述内容。
ID类型 |
描述 |
---|---|
目录商品ID |
目录商品ID是商店中商品的独特辨识符。这些ID在一个产品中独一无二。结账流程需要目录商品ID。 |
目录项目ID |
目录项目ID在一个产品中独一无二,其识别单个目录项目。需要此ID才能检查特定用户是否拥有相应的目录项目。 |
授权名称 |
每个项目可拥有一个与其相关的授权名称。这些授权名称可以用于分组。不过,授权名称(Entitlement Name)通常就是目录商品ID。基于目录商品中和目录项目相关的授权名称,利用电子商务接口可查询该目录项目授予其的权力。 |
授权ID |
完成的目录商品及其目录项目在用户帐户中是为此用户保存的特殊授权。每个授权都拥有其专属的辨识符。兑换授权时,此ID由电子商务接口使用。 |