IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 「实用 StoreKit2 」App Connect API 2.0 实战 -> 正文阅读

[开发测试]「实用 StoreKit2 」App Connect API 2.0 实战

前言:先说IAP的事,原本我们的支付功能需要S2S验证收据的真实性。但是以前的收据[appStoreReceiptURL]在>StoreKit2中无法获取。

查阅了现在收据原来是在JWS中,可是发现这个JWS我们后台验证还没开始实现(#_#)。

没办法,一起找啊找,各种关键词: StoreKit2 verify?AppleDeveloper?Forums...丢人就不念了。

直到遇见了这篇文章?转载一下,希望也可以解决你现在遇到的问题。

注:这篇文章是验证服务端S2S的JWS,并不是验证SDK传过来的JWS(有好的方式请留言)

正文开始

App Store Server API – 是一种新的 REST API,可让您获取有关所有客户应用内购买的信息。与旧的 verifyReceipt 端点的主要区别在于,您不再需要向服务器发送大型 base64 收据。`

检索信息是使用原始事务 ID 完成的,请求和响应都使用 JWT 和从 App Store Connect 生成的 API 密钥进行签名。

生成应用内购买 API 密钥

为应用内购买生成密钥与生成订阅密钥相同——该选项卡已被简单地重命名:【PS: 如果看不到这个选项,说明你的权限不够高】

要为应用内购买生成 API 密钥,请访问:

  1. 用户和访问
  2. 钥匙
  3. 在应用程序内购买

下载密钥并将其保存到安全的地方。请注意,您只能下载一次密钥。

发行人编号

要创建请求,您还需要颁发者 ID,可以在Keys > App Store Connect API选项卡中找到它。如果页面上缺少此字段,您可能需要创建您的第一个 App Store Connect API 密钥,即使您不会使用它。您也可以尝试从所有者帐户签名。

创建 JWT

JSON Web Token (JWT) 使用开放标准RFC 7519,该标准定义了一种安全传输信息的方式。

生成令牌使用 3 个步骤完成:

1.  创建 JWT 标头
1.  创建 JWT 负载
1.  签署 JWT
复制代码

Header 包含三个字段:

{ 
"alg": "ES256", 
"kid": "2X9R4HXF34", 
"typ": "JWT" 
}
复制代码

其中algtyp– 静态值,以及kid– 是您的密钥 ID。

JWT 负载如下所示:

{ 
 ?"iss": "57246542-96fe-1a63e053-0824d011072a", 
 ?"iat": 1623085200, 
 ?"exp": 1623086400, 
 ?"aud": "appstoreconnect-v1", 
 ?"nonce": "nonce6-12b482e82" 0242ac130003" ,
 ?“中标”: “com.apphud” 
}
复制代码

iss?– 是我们从 App Store Connect 获得的 Issuer ID。

iat?– 令牌创建日期,以秒为单位。

exp– 令牌到期日期,以秒为单位。必须在令牌创建日期之后不到 1 小时。

aud?– 静态值“appstoreconnect-v1”。

nonce?– 一个随机的唯一请求标识符,“salt”。

bid?– 应用程序的捆绑 ID。

可以在此处找到有关 JWT 有效负载的更多信息。

获取交易信息

要获取交易列表,您需要订阅的原始交易 ID。默认情况下,API 一次返回 20 个事务,从旧到新排序。如果有超过 20 笔交易,则参数hasMore将为true

网址如下:

https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{original_transaction_id}
复制代码

在沙盒域中是以下内容:

https://api.storekit-sandbox.itunes.apple.com
复制代码

JWT 库非常流行,适用于所有主要语言。我们使用Ruby编写 Demo:【PS: php我最讨厌的语言】

让我们创建StoreKit类:

require 'jwt'
require_relative 'jwt_helper'
require 'httparty'

class StoreKit
  ...
  
  attr_reader :private_key, :issuer_id, :original_transaction_id, :key_id, :bundle_id, :response
  
  ALGORITHM = 'ES256'
 
  def jwt
    JWT.encode(
      payload,
      private_key,
      ALGORITHM,
      headers
    )
  end
?
  def headers
    { kid: key_id, typ: 'JWT' }
  end
?
  def payload
    {
      iss: issuer_id,
      iat: timestamp,
      exp: timestamp(1800),
      aud: 'appstoreconnect-v1',
      nonce: SecureRandom.uuid,
      bid: bundle_id
    }
  end
end

复制代码

这里很简单。我们刚刚定义了我们之前描述的方法。

现在让我们添加 URL 变量并添加一些代码来启动请求:

URL = 'https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/%<original_transaction_id>s'
  
  def request!
 ? ?url = format(URL, original_transaction_id: original_transaction_id)
 ? ?result = HTTP.get(url, headers: { 'Authorization' => "Bearer #{jwt}" })
 ? ?# raise UnauthenticatedError if result.code == 401
 ? ?# raise ForbiddenError if result.code == 403
?
 ? ?result.parsed_response
 ?end
复制代码

要调用此代码,让我们创建一个单独的文件subscription.rb,在其中初始化我们的StoreKit类实例并调用它:

key_id = File.basename(ENV['KEY'], File.extname(ENV['KEY'])).split('_').last
ENV['KEY_ID'] = key_id
?
StoreKit.new(
 ?private_key: File.read("#{Dir.pwd}/keys/#{ENV['KEY']}"),
 ?issuer_id: '69a6de82-48b4-47e3-e053-5b8c7c11a4d1',
 ?original_transaction_id: ENV['OTI'],
 ?key_id: key_id,
 ?bundle_id: 'com.apphud'
).call
复制代码

在响应中,我们得到带有 JWT 签名字段的 JSON。

解码响应

要解码响应,我们需要一个公钥。它可以从我们的私钥中提取。让我们写一个辅助类JWTHelper

require 'jwt'
require 'byebug'
require 'openssl/x509/spki'
?
# JWT class
class JWTHelper
 ?ALGORITHM = 'ES256'
?
 ?def self.decode(token)
 ? ?JWT.decode(token, key, false, algorithm: ALGORITHM).first
 ?end
?
 ?def self.key
 ? ?OpenSSL::PKey.read(File.read(File.join(Dir.pwd, 'keys', ENV['KEY']))).to_spki.to_key
 ?end
end
复制代码

此类使用 OpenSSL 库读取私钥并使用to_spki方法(简单公钥基础结构)提取公钥。然后使用公钥和 ES256 算法从响应中解码 JWT。

让我们解码我们的响应:

def decoded_response
 ? ?response['data'].each do |item|
 ? ? ?item['lastTransactions'].each do |t|
 ? ? ? ?t['signedTransactionInfo'] = JWTHelper.decode(t['signedTransactionInfo'])
 ? ? ? ?t['signedRenewalInfo'] = JWTHelper.decode(t['signedRenewalInfo'])
 ? ? ?end
 ? ?end
?
 ? ?response
 ?end
复制代码

如果一切正常,我们将得到最终的 JSON:

{
 ?"environment": "Sandbox",
 ?"bundleId": "com.apphud",
 ?"data": [
 ?  {
 ? ? ?"subscriptionGroupIdentifier": "20771176",
 ? ? ?"lastTransactions": [
 ? ? ?  {
 ? ? ? ? ?"originalTransactionId": "1000000809414960",
 ? ? ? ? ?"status": 2,
 ? ? ? ? ?"signedTransactionInfo": {
 ? ? ? ? ? ?"transactionId": "1000000811162893",
 ? ? ? ? ? ?"originalTransactionId": "1000000809414960",
 ? ? ? ? ? ?"webOrderLineItemId": "1000000062388288",
 ? ? ? ? ? ?"bundleId": "com.apphud",
 ? ? ? ? ? ?"productId": "com.apphud.monthly",
 ? ? ? ? ? ?"subscriptionGroupIdentifier": "20771176",
 ? ? ? ? ? ?"purchaseDate": 1620741004000,
 ? ? ? ? ? ?"originalPurchaseDate": 1620311199000,
 ? ? ? ? ? ?"expiresDate": 1620741304000,
 ? ? ? ? ? ?"quantity": 1,
 ? ? ? ? ? ?"type": "Auto-Renewable Subscription",
 ? ? ? ? ? ?"inAppOwnershipType": "PURCHASED",
 ? ? ? ? ? ?"signedDate": 1623773050102
 ? ? ? ?  },
 ? ? ? ? ?"signedRenewalInfo": {
 ? ? ? ? ? ?"expirationIntent": 1,
 ? ? ? ? ? ?"originalTransactionId": "1000000809414960",
 ? ? ? ? ? ?"autoRenewProductId": "com.apphud.monthly",
 ? ? ? ? ? ?"productId": "com.apphud.monthly",
 ? ? ? ? ? ?"autoRenewStatus": 0,
 ? ? ? ? ? ?"isInBillingRetryPeriod": false,
 ? ? ? ? ? ?"signedDate": 1623773050102
 ? ? ? ?  }
 ? ? ?  }
 ? ?  ]
 ?  }
  ]
}
复制代码

如您所见,lastTransactions数组包含有关订阅的最后一笔交易以及订阅状态的信息。状态字段的值为2,这意味着expired此处描述所有订阅状态。

还有一个新字段"type": "Auto-Renewable Subscription",它是人类可读字符串中的应用内购买类型。

不幸的是,新 API 中仍然缺少交易价格。【以下是我贴的以前的旧收据,本来想单独写一篇文章的,想想没必要】

//iOS14.0 收据解析结果
{
  "environment" : "Sandbox",
  "receipt" : {
    "adam_id" : 0,
    "app_item_id" : 0,
    "application_version" : "1",
    "bundle_id" : "com.xxx.xxx.ios",
    "download_id" : 0,
    "in_app" : [
      {
        "in_app_ownership_type" : "PURCHASED",
        "is_trial_period" : "false",
        "original_purchase_date" : "2021-11-19 07:57:08 Etc\/GMT",
        "original_purchase_date_ms" : "1637308628000",
        "original_purchase_date_pst" : "2021-11-18 23:57:08 America\/Los_Angeles",
        "original_transaction_id" : "1000000914137632",
        "product_id" : "xx.xx.xx.xx",
        "purchase_date" : "2021-11-19 07:57:08 Etc\/GMT",
        "purchase_date_ms" : "1637308628000",
        "purchase_date_pst" : "2021-11-18 23:57:08 America\/Los_Angeles",
        "quantity" : "1",
        "transaction_id" : "1000000914137632"
      }
    ],
    "original_application_version" : "1.0",
    "original_purchase_date" : "2013-08-01 07:00:00 Etc\/GMT",
    "original_purchase_date_ms" : "1375340400000",
    "original_purchase_date_pst" : "2013-08-01 00:00:00 America\/Los_Angeles",
    "receipt_creation_date" : "2021-11-19 07:57:08 Etc\/GMT",
    "receipt_creation_date_ms" : "1637308628000",
    "receipt_creation_date_pst" : "2021-11-18 23:57:08 America\/Los_Angeles",
    "receipt_type" : "ProductionSandbox",
    "request_date" : "2021-11-19 08:02:28 Etc\/GMT",
    "request_date_ms" : "1637308948244",
    "request_date_pst" : "2021-11-19 00:02:28 America\/Los_Angeles",
    "version_external_identifier" : 0
  },
  "status" : 0
}
//JWS解析后
{
    "transactionId":"10000009269223342",
    "originalTransactionId":"1000003316922942",
    "bundleId":"com.---.ios",
    "productId":"king.test.gold.60",
    "purchaseDate":1637723816809,
    "originalPurchaseDate":1637723816809,
    "quantity":1,
    "type":"Consumable",
    "deviceVerification":"qVh9...+B3fIAoQKL8Kz0CkmVGfUiwpPrfcGdlJyht775ID9ytSQCWItx",
    "deviceVerificationNonce":"a8735bcf-825f-4aeb-b99c-6f866cadc96e",
    "appAccountToken":"3977...-61b8-bfb7-c94f-8ea670fdb7b7",
    "inAppOwnershipType":"PURCHASED",
    "signedDate":1637723816914
}
复制代码

可以在此处找到本文的完整源代码。

结论

由于缺少大型 base64 接收参数,新的 App Store Connect API 为开发人员提供了更多信息并且运行速度更快。

新API的优点:

  • 轻量级快速请求,通过original_transaction_id就够了。
  • 不再需要共享秘密
  • 还有一些额外的字段,如状态、类型。
  • 新的 API 可用,例如从应用程序管理退款。
  • 交易已在 API 中排序。

缺点:

  • 相当复杂的请求授权:您需要生成 API Key 并从 App Store Connect 复制 Issuer ID。
  • 交易价格仍然缺失。然而,Apphud 成功地计算了所有交易的价格,即使在如此困难的情况下,如升级期间按比例退款、价格上涨等。

欢迎大家关注我的掘金:Fat君 的个人主页 - 动态 - 掘金iOS开发 有理想的iOS工程狮https://juejin.cn/user/1345457963926535

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-12-03 13:19:52  更:2021-12-03 13:20:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 4:31:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码