Bootstrap

苹果认证Apple Store Connenct api的使用

苹果认证工具类Apple Store Connenct api

苹果应用商店支持下载财务报告和销售报告,官方有提供两种下载方式
方式一:Reporter.jar
方式二:Api(Http)

先定个结论,推荐使用Api的方式去调用。下文会介绍原因

Reporter.jar调用

官方链接:https://help.apple.com/itc/contentreporterguide/#/apda86f89da5

1. 下载reporter工具

如Reporter2.2,你将会得到两个文件:

  • Reporter.jar
  • Reporter.properties

2. 生成访问令牌

参考地址:https://help.apple.com/itc/contentreporterguide/#/apd2f1f1cfa3

  • 语法
$ java -jar Reporter.jar p=[properties file name] [application name].generateToken

[application name]——请替换为“Sales”或“Finance”
注:您只需生成一个访问令牌即可访问销售和财务数据。无须为二者生成单独的访问令牌。

  • 示例
$ java -jar Reporter.jar p=Reporter.properties Sales.generateToken
Please type in your username: ********
Please type in your password: ********
Once you generate an access token, you won't be able to log in to Reporter with your username and password. Do you still want to continue? (y/n):
如果输入“y”:
Your access token has been generated.
AccessToken:12abc345-de6f-7ghi-89jk123lmno
Expiration Date:2017-06-07
(过期日期格式:YYYY-MM-DD)

如果用户已有令牌:
$ java -jar Reporter.jar p=Reporter.properties Sales.generateToken
Please type in your username: ********
Please type in your password: ********
If you generate a new access token, your existing token will be deleted. Do you still want to continue? (y/n):
  • 令牌回填到属性文件的AccessToken中
    Reporter.properties
AccessToken=xxxxxxxx-xxxxx-xxxx-xxxx-xxxxxxxxxxxx

Mode=Normal

SalesUrl=https://reportingitc-reporter.apple.com/reportservice/sales/v1
FinanceUrl=https://reportingitc-reporter.apple.com/reportservice/finance/v1

3. 下载财务/销售报告

参考地址:https://help.apple.com/itc/contentreporterguide/#/itc21263284f

获取供应商编码和地区编码

java -jar Reporter.jar p=Reporter.properties Finance.getVendorsAndRegions

return:
The following reports are available for vendor 00000000:
AE:Financial
AU:Financial
BG:Financial
BR:Financial
CA:Financial
CH:Financial
CL:Financial
CN:Financial
Z1:FinancialDetail

PS:特别备注,Z1表示多个国家的报告,且只有详情报告FinancialDetail。 其他国家代码均为汇总报告Financial

获取财务汇总报告

【语法】

java -jar Reporter.jar p=Reporter.properties m=Robot.XML Finance.getReport [供应商编码],[地区代码], [报告类型], [苹果日历-年], [苹果日历-月]

【示例】

java -jar Reporter.jar p=Reporter.properties m=Robot.XML Finance.getReport 00000000, US, Financial, 2022, 9

得到国家US的汇总报告,返回一个.gz压缩文件

获取财务详情报告

java -jar Reporter.jar p=Reporter.properties m=Robot.XML Finance.getReport 00000000, Z1, FinanceDetail, 2022, 9

得到多个国家详情报告,返回一个.gz压缩文件

Api调用

简述调用逻辑:
1、苹果应用商店下载密钥私钥
2、生成JWT令牌
3、携带JWT访问API接口(如访问财务报告)

这一看清晰多了,通用逻辑就两个,一个是密钥、私钥,另一个是令牌。

1.密钥和私钥

参考地址:https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api

每个开发者账号都会对应一个密钥和私钥,请各自保管好

示例:

密钥kid私钥private key
12345678-----BEGIN PRIVATE KEY-----12345678省略一大堆-----END PRIVATE KEY-----

2.生成JWT令牌

参考地址:https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests

按照JWT生成规则三要素:
1、标头header
2、负载payload
3、私钥

  1. 组装header
Header FieldValue
alg - Encryption AlgorithmES256 All JWTs for App Store Connect API must be signed with ES256 encryption.
kid - Key IdentifierYour private key ID from App Store Connect; for example 2X9R4HXF34.
typ - Token TypeJWT

简化一下:

{
    "alg" : "ES256", //固定
    "kid" : {密钥},
    "typ" : "JWT" // 固定
}
  1. 组装payload
Payload FieldValue
iss - Issuer IDYour issuer ID from the API Keys page in App Store Connect; for example, 57246542-96fe-1a63-e053-0824d011072a.
iat - Issued At TimeThe token’s creation time, in UNIX epoch time; for example, 1528407600.
exp - Expiration TimeThe token’s expiration time in Unix epoch time. Tokens that expire more than 20 minutes into the future are not valid except for resources listed in Determine the Appropriate Token Lifetime.
aud - Audienceappstoreconnect-v1
scope - Token ScopeA list of operations you want App Store Connect to allow for this token; for example, GET /v1/apps/123. (Optional)

简化下:

{
    "iss" : {用户ID},
    "iat" : 1528407600, // 令牌起始时间,单位秒
    "exp" : 1528408800, // 令牌失效时间,注意最长不超过20分钟
    "aud" : "appstoreconnect-v1" // 固定
    "scope" : [ // [可选值]用来限制权限
        "GET /v1/apps?filter[platform]=IOS"
    ]
}
  1. 签名私钥认证,得到jwt

参考下面工具类,或者全文检索一下createToken关键字,读一下你就懂了

3.工具类(已实现,可以拿来即用)

package com.whale.data.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.whale.common.core.constant.TokenConstants;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.codec.binary.Base64;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yanyq
 * @desc apple store connect 苹果认证工具类
 * @date 2022/10/18
 */
public class AppleKitUtils {

    /**
     * 创建令牌
     *
     * @param userId     用户ID
     * @param kid        密钥 如:2X9R4HXF34
     * @param privateKey 私钥 如:djhdkajshdky12dy1hd12hd98h129d8h192d8h198dh1982dh198dh918hd92d总之很长就对了
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static String createToken(String userId, String kid, String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // 1.设置标头
        Map<String, Object> header = new HashMap<>();
        header.put("alg", "ES256");
        header.put("kid", kid);
        header.put("typ", "JWT");

        // 2.设置负载
        Map<String, Object> claims = new HashMap<>();
        // 发行人ID
        claims.put("iss", userId);
        // 按时发行(单位秒)
        long currentTimeSeconds = DateUtil.currentSeconds();
        claims.put("iat", currentTimeSeconds);
        // 到期时间
        claims.put("exp", currentTimeSeconds + 60 * 20);
        // 观众(固定值)
        claims.put("aud", "appstoreconnect-v1");

        // 3.创建私钥对象
        // 私钥多余数据过滤
        String privateKeyPEM = privateKey.replaceAll("\\-*BEGIN.*KEY\\-*", "")
                .replaceAll("\\-*END.*KEY\\-*", "")
                .replaceAll("\r", "")
                .replaceAll("\n", "");
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyPEM));
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        PrivateKey pk = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

        // 4.生成token
        String token = Jwts.builder()
                .setHeader(header)
                .setClaims(claims)
                .signWith(SignatureAlgorithm.ES256, pk)
                .compact();
        return token;
    }

    /**
     * 下载苹果财务详细报告
     *
     * @param token        令牌
     * @param vendorNumber 供应商编码
     * @param reportDate   报告日期,参考苹果日历
     * @param downloadPath 文件下载路径
     */
    private static void downloadAppleFinanceReportDetail(String token, String vendorNumber, String reportDate, String downloadPath) throws IOException {
        String url = "https://api.appstoreconnect.apple.com/v1/financeReports";
        String reportType = "FINANCE_DETAIL";
        String regionCode = "Z1";
        // 创建客户端
        HttpRequest post = HttpUtil.createGet(url);
        // 创建请求头
        post.header(TokenConstants.AUTHENTICATION, TokenConstants.PREFIX + token);
        post.header("Accept", "application/a-gzip");
        post.form("filter[regionCode]", regionCode);
        post.form("filter[reportDate]", reportDate);
        post.form("filter[reportType]", reportType);
        post.form("filter[vendorNumber]", vendorNumber);
        // 执行
        HttpResponse execute = post.execute();
        if (execute.isOk()) {
            // 请求成功
            byte[] retBytes = execute.bodyBytes();
            File file = FileUtil.file(downloadPath);
            if (file.exists()) {
                file.delete();
            }
            file.createNewFile();
            FileOutputStream fos = new FileOutputStream(file);
            // 数据写到本地
            IoUtil.write(fos, true, retBytes);
        } else {
            // 请求失败
            String retBody = execute.body();
            System.out.println(retBody);
        }
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        // 账号信息
        String kid = "xxxxxxxx";
        String userId = "xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx";
        String privateKey = "xxxxxxxxxx/xxxxxxxx/xxxxxxxxx/xxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxx";
        String token = createToken(userId, kid, privateKey);

        // 请求财务报告明细数据
        // 请求信息
        String vendorNumber = "80000000";
        String reportDate = "2022-09";
        String downloadPath = "/Users/admin/Downloads/bb.gz";
        downloadAppleFinanceReportDetail(token, vendorNumber, reportDate, downloadPath);

    }
}


;