前言:先说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, :responseALGORITHM = 'ES256'def jwtJWT.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_responseend
复制代码

要调用此代码,让我们创建一个单独的文件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 JWTHelperALGORITHM = 'ES256'
​def self.decode(token)JWT.decode(token, key, false, algorithm: ALGORITHM).firstend
​def self.keyOpenSSL::PKey.read(File.read(File.join(Dir.pwd, 'keys', ENV['KEY']))).to_spki.to_keyend
end
复制代码

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

让我们解码我们的响应:

def decoded_responseresponse['data'].each do |item|item['lastTransactions'].each do |t|t['signedTransactionInfo'] = JWTHelper.decode(t['signedTransactionInfo'])t['signedRenewalInfo'] = JWTHelper.decode(t['signedRenewalInfo'])endend
​responseend
复制代码

如果一切正常,我们将得到最终的 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

「实用 StoreKit2 」App Connect API 2.0 实战相关推荐

  1. Vue3 Typescript + Axios 全栈开发教程:手把手教你写「待办清单」APP

    本文完整版:<Vue3 Typescript + Axios 全栈开发教程:手把手教你写「待办清单」APP> Vue3 Typescript + Axios 全栈开发教程 前端 Vue3 ...

  2. php获取股票接口数据接口,「股票数据」 使用Sina API获取新浪财经的证券股票数据接口(时价 K线等)...

    在互联网上找到的新浪API的财务API表示Mark,以供将来参考 ++ e.sina.com.cn/downxls.php?date=2011-07-08&symbol=sh600900 收购 ...

  3. 技术社招!揭秘「剪映」技术团队如何从0到1打造爆款APP?

    8年前,字节跳动诞生在北京锦秋家园的居民楼,如今,我们的产品和服务覆盖150个国家和地区.本着"人才在哪里,就把公司开到哪里"的理念,我们已在全球设立了15个研发中心. 字节跳动深 ...

  4. await原理 js_「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能

    本周,Nodejs v14.3.0 发布.这个版本包括添加顶级 Await.REPL 增强等功能. REPL 增强 通过自动补全改进对 REPL 的预览支持,例如,下图中当输入 process.ver ...

  5. 支付宝、微信回应3D面具破解人脸识别;ofo 否认「发币」;Kafka 2.4.0 发布 | 极客头条...

    整理 | 屠敏 快来收听极客头条音频版吧,智能播报由标贝科技提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注 ...

  6. 12月18日科技资讯|支付宝、微信回应3D面具破解人脸识别;ofo 否认「发币」;Kafka 2.4.0 发布

    「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧.扫描上方二维码进入 CSDN App 可以收听御姐萌妹 Sty ...

  7. 国行 iPhone 12/Pro/Max双卡模式支持5G网络;支付宝推出「晚点付」功能;MySQL 8.0.22 GA|极客头条

    整理 | 郑丽媛 头图 | CSDN 下载自东方 IC 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧. 国内 ...

  8. PRD实用案例|「赶公交」App产品需求文档

    赶公交产品需求文档V1.0 1.产品概述 1.1 背景说明 公交车坐位大众出行必不可少的交通工具,已经成为每个人日常生活的一部分.然而公交系统的运营资源是掌握在公交公司手中,人们无法获得公交车实时运行 ...

  9. catch句子_「实用英文」知道 catch on 是什么意思吗?catch 精选短语合集

    相信大家都知道 catch (抓),那你知道 catch on 是甚么意思吗?catch 实用短语大集合,赶快来看看! 精选 Catch 短语! catch off guard 措手不及 (因突发事件 ...

最新文章

  1. 【集合论】Stirling 子集数 ( 斯特林子集数概念 | 放球模型 | Stirling 子集数递推公式 | 划分的二元关系 加细关系 )
  2. Appium 命令行安装教程
  3. JS根据日期比较显示时分或者月日
  4. 51CTO微博认证说明
  5. MySQL json-table-functions
  6. Intellij如何把JAR包加入到项目运行环境中
  7. Recommended System
  8. 使用SESSION实现PHP会话的步骤
  9. Gtest之TEST宏的用法
  10. CPM-Nets: Cross Partial Multi-View Networks
  11. SVG格式转json文件
  12. 简单了解ACL与NAT!
  13. Word不会排版!看这里,3分钟教你学会30个排版技巧
  14. opendolphin_认床的你也许拥有海豚般的睡眠
  15. 获取android 用到的所有开发包文件
  16. ACL and SCO
  17. Java SE 005 运算符续
  18. 机器学习实战——K均值
  19. 日语自学资料-N5(免费下载)
  20. 罗斯蒙特248温度变送器248HANANONS

热门文章

  1. 判断浏览器是否支持wap
  2. 利用数据可视化技术来学习钻石鉴别
  3. pdf转ppt简单方法
  4. 面试题 05.07. 配对交换
  5. CAS-KG——知识图谱概述
  6. 依赖注入神器:Dagger2详解系列
  7. 在于运动app技术支持
  8. Charles证书安装(IOS)
  9. ws550 第三方固件_华为WS550路由器固件升级教程
  10. golang 错误: cannot use XXX literal (type XXX) as type XXX in assignment:XXX does not implement XXX