IOS内购数据拉取
目标:拉取app store connect 内购数据拉取,自己做数据报表。
1:api秘钥
接口需要token,token生成需要秘钥。参考官方文档:https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api 。
vendorNumber在这个界面查找:
2:生成token
根据上面保存的东西生成token。官方文档: https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests
这里用 java+vertx框架实现的:
private JWTAuth iniJwt() { JsonObject header = new JsonObject() .put("alg", "ES256") .put("kid", keyID) //key_id .put("typ", "JWT"); JWTOptions options = new JWTOptions() .setAlgorithm("ES256") .setHeader(header); Vertx vertx = ConfigHelp.vertx; //加载下载的 p8文件 Buffer iosPayKeyBuffer = vertx.fileSystem().readFileBlocking("resource/AuthKey_A2BRZ78255.p8"); Buffer keyBuffer = iosPayKeyBuffer; JWTAuthOptions config = new JWTAuthOptions() .addPubSecKey(new PubSecKeyOptions() .setAlgorithm("ES256") .setBuffer(keyBuffer)) .setJWTOptions(options); return JWTAuth.create(vertx, config); }
private String getToken(String parameter) {
long sT = (int) (System.currentTimeMillis() / 1000);
long eT = sT + 60 * 15; //过期时间
String params = "GET " + parameter;
List<String> scope = new ArrayList<>(1);
scope.add(params);
JsonObject body = new JsonObject()
.put("iss", issuer) //issuer
.put("iat", sT)
.put("exp", eT)
.put("aud", "appstoreconnect-v1")
.put("scope", scope);
JWTAuth provider = iniJwt();
return provider.generateToken(body);
}
3:下载销售报告
财务报告是每月生成,销售报告是每天生成。参考文档:https://developer.apple.com/documentation/appstoreconnectapi/download_sales_and_trends_reports
这里请求数据post使用的是 okhttp3,所以需要添加 pom.xml:
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.10.0</version> </dependency>
请求代码
//参数
String curDate = "2023-04-20"; //需要下载报表的日期
String vendorNumber = "21432532456";//通过上面消息得到的 vendor_number
String params = "/v1/salesReports?filter[frequency]=DAILY&filter[reportSubType]=SUMMARY" +
"&filter[reportType]=SALES" +
"&filter[vendorNumber]=" + vendorNumber + "&filter[reportDate]=" + curDate;
//请求数据 String token = getToken(params); String url = "https://api.appstoreconnect.apple.com" + params; Request request = new Request.Builder() .url(url) .method("GET", null) .addHeader("Authorization", "Bearer " + token) .addHeader("Accept", "application/a-gzip") .build(); try { OkHttpClient client = new OkHttpClient().newBuilder() .addInterceptor(new UnzippingInterceptor()) .build(); Response response = client.newCall(request).execute(); String body = Objects.requireNonNull(response.body()).string(); System.out.println(body); } catch (IOException e) { System.out.println(e); }
注意:请求数据返回的是zip文件,所以这里需要解压。addInterceptor(new UnzippingInterceptor())就是用来解压zip的。
package com.gcms.postdata.pay; import okhttp3.Headers; import okhttp3.Interceptor; import okhttp3.Response; import okhttp3.internal.http.RealResponseBody; import okio.GzipSource; import okio.Okio; import org.jetbrains.annotations.NotNull; import java.io.IOException; public class UnzippingInterceptor implements Interceptor { @NotNull @Override public Response intercept(@NotNull Chain chain) throws IOException { Response response = chain.proceed(chain.request()); return unzip(response); } // copied from okhttp3.internal.http.HttpEngine (because is private) private Response unzip(final Response response) throws IOException { if (response.body() == null) { return response; } //check if we have gzip response String contentEncoding = response.headers().get("Content-Encoding"); if(contentEncoding == null) return response; //this is used to decompress gzipped responses if (contentEncoding.equals("gzip") || contentEncoding.equals("agzip")) { Long contentLength = response.body().contentLength(); GzipSource responseBody = new GzipSource(response.body().source()); Headers strippedHeaders = response.headers().newBuilder().build(); return response.newBuilder().headers(strippedHeaders) .body(new RealResponseBody(response.body().contentType().toString(), contentLength, Okio.buffer(responseBody))) .build(); } else { return response; } } }
最后得到的body就是解析后的数据。
4:解析数据
得到的数据是按照excel形式在txt文件中展示的,需要解析出自己需要的数据
String[] separated = body.split("\n"); //使用 \n 拆分行, int len = separated.length; for (int i = 1; i < len; i++) { //遍历每一行数据 String[] row = separated[i].split("\t"); //使用 \t 拆分列,得到每一列数据 String packId = row[17];//包名 float proceeds = Float.parseFloat(row[8]); //收益 if (proceeds == 0) continue; float price = Float.parseFloat(row[15]); //定价 //其他字段 }
具体的报告字段参考:https://developer.apple.com/help/app-store-connect/reference/summary-sales-report
5:完成。具体的数据自己储存到数据库,自己使用。
注意:得到的数据各种货币都有,需要自己转为一种类型的货币。自己查找汇率。
举个例子:在计算或者展示时,统一转为美金计算。units * proceeds * 汇率 = 内购
//转为美金的汇率 switch (city) { case "GBP": return 1.20469f; case "AUD": return 0.66903f; case "USD": return 1; case "CHF": return 1.07279f; case "CAD": return 0.73338f; }