背景是需要调用appStore的历史订单查询接口进行数据统计对比,之前没有做过相关的对接,这次写出来记录一下。
有一说一,苹果的官方文档确实是非常详尽,特别是教你如果操作生成秘钥那边,可以说是保姆级教学了,不过很多的文档都是英文的,相对来说看起来比较容易遗漏重点,今天就用查询历史订单这边的逻辑给给大家说下对接的流程以及各种注意事项。
首先我们先找到我们需要调用的接口的苹果官方文档,例如我这次需要调用的就是查询历史订单接口,参考文档为: 苹果官方文档 在这个文档中记录着这个接口的url、请求参数、返回码,注意下响应体是不在这边记录的,需要在最下面返回体的部分点击跳转到另一个页面查看。例如Get Transaction History这个接口的url是https://api.storekit.itunes.apple.com/inApps/v2/history/{transactionId},其中的transactionId是用户的任何订单号(注意下这边如果传入了无效的transactionId,会直接报404),其他的参数是通过Query Parameters传的,这里有个注意点,有一些传参,苹果文档中会加一个中括号,这表示可以多传,多传的方式时在Query Parameters里传多次。例如https://api.storekit.itunes.apple.com/inApps/v2/history/{transactionId}?productType=AUTO_RENEWABLE&productType=NON_RENEWABLE。传参中的sort适用于时间排序,不传表示正序,即从远到近 调用时不仅需要传过滤参数和transactionId
,header里还需要放Authorization
,Authorization
的规则是Bearer [signed token]
,传参方式可以参考curl -v -H 'Authorization: Bearer [signed token]' "https://api.storekit.itunes.apple.com/inApps/v2/history/{transactionId}"
,这里最终要的就是signed token 首先我们需要按照指引生成一个私钥,这里需要登录苹果开发者账户的管理账号,具体的生成规则是傻瓜式的,可以参考文档 生成私钥 生成完毕以后需要保存三个值,第一个是在页面上面的ISSUER_ID(有些文档上也会携程TEAM_ID),第二个是生成秘钥的ID(一般在秘钥文件上也会有),第三个是秘钥文件(一个.p8的文件) 下面是用java实现的生成jwt的流程`
private static final String ISSUER_ID = "57246542-96fe-1a63e053-0824d011072a" ;
private static final String KEY_ID = "D123456ASDF" ;
private static final String PRIVATE_KEY_PATH = "/SubscriptionKey_D123456ASDF.p8" ;
private static String generateJWT ( String issuerId, String keyId, String privateKeyFilePath) throws Exception {
PrivateKey privateKey = parsePEMPrivateKey ( privateKeyFilePath) ;
long nowMillis = System . currentTimeMillis ( ) ;
Date now = new Date ( nowMillis) ;
Date expiration = new Date ( nowMillis + 1000 * 60 * 50 ) ;
Map < String , Object > payLoad = new HashMap < > ( ) ;
payLoad. put ( "iss" , issuerId) ;
payLoad. put ( "iat" , now. getTime ( ) / 1000 ) ;
payLoad. put ( "exp" , expiration. getTime ( ) / 1000 ) ;
payLoad. put ( "aud" , "appstoreconnect-v1" ) ;
payLoad. put ( "bid" , "cloud.example" ) ;
Map < String , Object > header = new HashMap < > ( ) ;
header. put ( "alg" , "ES256" ) ;
header. put ( "kid" , keyId) ;
header. put ( "type" , "JWT" ) ;
JwtBuilder builder = Jwts . builder ( )
. setClaims ( payLoad)
. setHeader ( header)
. signWith ( privateKey, SignatureAlgorithm . ES256 ) ;
return builder. compact ( ) ;
} `
public static PrivateKey parsePEMPrivateKey ( String privateKeyFilePath) throws Exception {
FileReader fileReader = new FileReader ( privateKeyFilePath) ;
PEMParser pemParser = new PEMParser ( fileReader) ;
Object pemObject = pemParser. readObject ( ) ;
if ( pemObject instanceof PEMKeyPair ) {
PEMKeyPair pemKeyPair = ( PEMKeyPair ) pemObject;
byte [ ] privateKeyInfoBytes = pemKeyPair. getPrivateKeyInfo ( ) . getEncoded ( ) ;
KeyFactory keyFactory = KeyFactory . getInstance ( "EC" ) ;
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec ( privateKeyInfoBytes) ;
return keyFactory. generatePrivate ( keySpec) ;
} else if ( pemObject instanceof PrivateKeyInfo ) {
PrivateKeyInfo privateKeyInfo = ( PrivateKeyInfo ) pemObject;
byte [ ] privateKeyInfoBytes = privateKeyInfo. getEncoded ( ) ;
KeyFactory keyFactory = KeyFactory . getInstance ( "EC" ) ;
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec ( privateKeyInfoBytes) ;
return keyFactory. generatePrivate ( keySpec) ;
}
throw new IllegalArgumentException ( "Unsupported PEM object: " + pemObject. getClass ( ) . getName ( ) ) ;
}
需要引入以下pom依赖
< dependency>
< groupId> io. jsonwebtoken< / groupId>
< artifactId> jjwt- api< / artifactId>
< version> 0.11 .2 < / version>
< / dependency>
< dependency>
< groupId> io. jsonwebtoken< / groupId>
< artifactId> jjwt- impl< / artifactId>
< version> 0.11 .2 < / version>
< scope> runtime< / scope>
< / dependency>
< dependency>
< groupId> io. jsonwebtoken< / groupId>
< artifactId> jjwt- jackson< / artifactId> < ! -- or jjwt- gson if Gson is preferred -- >
< version> 0.11 .2 < / version>
< scope> runtime< / scope>
< / dependency>
到这里就结束了?nonono,还有最重要的解析苹果的响应,这里不同接口会有不同的返回参数,类似我这边调用的接口里,返回值signedTransactions是最重要的,这里面存放了订单列表,但并非明文传输,而是以JSON Web Signature (JWS)的格式传递,这种格式的参数是有规律的,是一个以.
链接的三段字符串,第一段为header,第二段为payload,第三段为签名,与传参的JWT一致,我们所需要的一般是payload部分,因此只需要把payload解析出来然后用base64解码即可。
String payloadCode = StrUtil . split ( jws, '.' ) . get ( 1 ) ;
String decodedString = new String ( Base64 . getDecoder ( ) . decode ( payloadCode) ) ;
写到这里就算结束啦,苹果还提供了SDK的接入方式,不过最低版本为java11,大家看各自的需要吧。