diff --git a/luoo_user/src/main/java/com/luoo/user/controller/ApplePayController.java b/luoo_user/src/main/java/com/luoo/user/controller/ApplePayController.java new file mode 100644 index 0000000..a22d91d --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/controller/ApplePayController.java @@ -0,0 +1,186 @@ +package com.luoo.user.controller; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.date.DateTime; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.luoo.user.pojo.AppleOrder; +import com.luoo.user.pojo.AppleResponse; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +/** + + * sendHttpsCoon 方法里面包含了增加的业务逻辑 + * 本类引入多种的Json序列化工具,选择了Hutool + */ +@Slf4j +@Controller +@RequestMapping("applePay") +public class ApplePayController { + //购买凭证验证地址 + private static final String certificateUrl = "https://buy.itunes.apple.com/verifyReceipt"; + //测试的购买凭证验证地址 + private static final String certificateUrlTest = "https://sandbox.itunes.apple.com/verifyReceipt"; + /** + * 重写X509TrustManager + */ + private static TrustManager myX509TrustManager = new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + }; + /** + * 接收自己APP客户端发过来的购买凭证 + * + * @param userId 用户ID + * @param receipt 前端支付成功后:苹果给前端的一长串字符串 + * @param chooseEnv false代表苹果沙箱环境,true代表真实支付环境 + */ + @PostMapping("/setIapCertificate") + @ApiImplicitParams({@ApiImplicitParam(name = "userId", value = "用户Id"), @ApiImplicitParam(name = "receipt", value = "苹果传递前端支付成功的值"), @ApiImplicitParam(name = "chooseEnv", value = "是否是真实环境,布尔值")}) + @ResponseBody + public String setIapCertificate(String userId, String receipt, boolean chooseEnv) { + // log.info("IOS端发送的购买凭证。数据有 userId = {},receipt = {},chooseEnv = {}",userId,receipt,chooseEnv); + if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(receipt)) { + return "后台发送道具失败,用户ID 或者 receipt为空"; + } + String url = null; + url = chooseEnv == true ? certificateUrl : certificateUrlTest; + final String certificateCode = receipt; + if (StringUtils.isNotEmpty(certificateCode)) { + String s = sendHttpsCoon(url, certificateCode, userId); + if ("支付成功".equals(s)) { + return "后台发送道具成功了,返回值按需自定义即可"; + } else { + return "后台发送道具失败了!"; + } + } else { + return "后台发送道具失败!传参receipt 为空!"; + } + } + /** + * 苹果沙箱账号密码:Xunliaoceshi002 + * 发送请求 向苹果校验支付请求是否有效:本方法由认证方法进行调用 + * + * @param url 支付的环境校验,不同Url代表 真实支付或苹果沙箱环境 + * @param receipt 接口传递的 receipt + * @return 苹果响应的请求结果 + */ + private String sendHttpsCoon(String url, String receipt, String userId) { + if (url.isEmpty() || receipt.isEmpty() || userId.isEmpty()) { + return "调用传参错误!"; + } + String line = null; + // todo 理论应该锁UserId,因为后台只能允许一个线程执行给玩家发送道具! + try { + // 设置SSLContext + SSLContext ssl = SSLContext.getInstance("SSL"); + ssl.init(null, new TrustManager[]{myX509TrustManager}, null); + // 打开连接 + HttpsURLConnection conn = (HttpsURLConnection) new URL(url).openConnection(); + // 设置套接工厂 + conn.setSSLSocketFactory(ssl.getSocketFactory()); + // 加入数据 + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setRequestProperty("Content-type", "application/json"); + // JSONObject obj = new JSONObject(); + JSONObject obj = new JSONObject(); + obj.set("receipt-data", receipt); + // 发送请求 + BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream()); + buffOutStr.write(obj.toString().getBytes()); + buffOutStr.flush(); + buffOutStr.close(); + // 获取接收响应的对象reader + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + // 转换苹果响应的所有结果! + StringBuffer sb = new StringBuffer(); + while ((line = reader.readLine()) != null) { + sb.append(line); + } + // 处理苹果响应的结果 + AppleResponse appleResponse = JSONUtil.toBean(sb.toString(), AppleResponse.class); + log.info("用户Id{},回调获取到了{}", userId, appleResponse.toString()); + // 苹果说了,只有订单状态是0才算成功,其他均是错误,可查:https://developer.apple.com/documentation/appstorereceipts/status + if (StringUtils.equalsAny(appleResponse.getStatus(), "0")) { + // 苹果给的订单号 + AppleOrder[] in_app = appleResponse.getReceipt().getIn_app(); + for (AppleOrder appleOrder : in_app) { + // 从订单对象获取订单号 + String transaction_id = appleOrder.getTransaction_id(); + String original_purchase_date_ms = appleOrder.getOriginal_purchase_date_ms(); + System.out.println(original_purchase_date_ms); + log.info("用户下单时间:{}",new DateTime(Long.valueOf(original_purchase_date_ms)+16*60*60*1000)); + // todo 校验订单号没有发放过道具 + if (true) { + String quantity = appleOrder.getQuantity(); + // 获取用户购买的产品Id, + String product_id = appleOrder.getProduct_id(); + switch (product_id) { + case "cn.luoo.IndieMusic.annual": { + // 年度会员 逻辑从这开始 + System.out.println("appleIAP 年度会员 逻辑从这开始,userid___" +userId); + + //这里拿到userId,在我们自己的订单逻辑里插一条订单记录,给用户开通相应的会员 + userId=""; + break; + } + case "cn.luoo.IndieMusic.monthly": { + // 月度会员逻辑从这开始 + System.out.println("appleIAP 月度会员 逻辑从这开始,userid___"+userId);; + break; + } + case "cn.luoo.IndieMusic.lifetime": { + // 终身会员逻辑从这开始 + System.out.println("appleIAP 终身会员 逻辑从这开始,userid___"+userId); + break; + } + } + log.info("{}发送奖励成功喽!",userId); + } + } + return "支付成功"; + } else { + log.info("用户ID:{},取消支付,响应码是:{}", userId, appleResponse.getStatus()); + } + } catch (Exception e) { + log.error("苹果回调过程中出现异常:{}", e.getMessage()); + return "有异常,停止交易!"; + } + return "苹果回调处理失败!"; + } + /** + * 注意:下面代码跟苹果支付业务无关。 + * 这里的code 是前端请求苹果获取到的参数 + * 苹果给前端的一个密钥(如果我们通过base64解密后,可获得signature、purchase-info、environment、pod、signing-status) + * 这个密钥用于告诉我们自己Java服务器 想苹果服务器校验订单是否成功的参数 + */ + public void decode() { + String code = "ewoJInNpZ25hdHVyZSIgPSAiQXhmVWRiYUx5T2I5bllOM3hINmQzMnBaOHI2THdmV3ZmZ1NKN1o2QTM4dEY2SjNyUTZoRVZqQ3Rra01wMnhmM1pwWnFQRmw3ZlRIdDVxNVpKZUF6UWh4NWQ1djJrR01uM3NKb3ZBWXNuWENxY3VqclBWU3A5WTFYUTZjeTlvbVNORWNYVWt0L1dkQXhsRmN6WDRZMTJzcktsMDc3WHJIdk5JMDd0VTZXajgzbVdDNE1HZmF0c2E2UEo1RG5sT2lEOG96RlJ6a0NIQ3Y3bncvRm80dnFCaFpZRmlQSDZzeW1uN2lUQlhTcXlTdlJOTGJXLytUWktKZngxR1dRV3BWdmJ5M0RtV3l4OTRaYkxGRllNODE0aTB2a1lnWDdPdVUwQWprTVFKOEJhNnJGc1hER0hYY2FCdnVhZ1NGak9iMWdJclZ2MDdmbTlBV3ltNE5KT0dVSHR0Z0FBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ESXhMVEV4TFRJMklESXlPalU1T2pJd0lFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5WdWFYRjFaUzFwWkdWdWRHbG1hV1Z5SWlBOUlDSTROelU1WmpNMVlUTTNNMk0wTVRabU5qazRPVFJrWkRRd05XRTFOemhoTURoalpqSTVOMlkwSWpzS0NTSnZjbWxuYVc1aGJDMTBjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01Ea3hPVE13TWpFeU5DSTdDZ2tpWW5aeWN5SWdQU0FpTVRBd0lqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTVRBd01EQXdNRGt4T1RNd01qRXlOQ0k3Q2draWNYVmhiblJwZEhraUlEMGdJakVpT3dvSkltbHVMV0Z3Y0MxdmQyNWxjbk5vYVhBdGRIbHdaU0lnUFNBaVVGVlNRMGhCVTBWRUlqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNJeE5qTTNPVGsyTXpZd01qYzNJanNLQ1NKMWJtbHhkV1V0ZG1WdVpHOXlMV2xrWlc1MGFXWnBaWElpSUQwZ0lqaEJSRVJHUlRjMUxURkVRVVl0TkRVek1TMDRNakV3TFVKRE9UZzNSa1l3TlRrNU9DSTdDZ2tpY0hKdlpIVmpkQzFwWkNJZ1BTQWlZMjl0TG1obFptVnBlSFZ1YkdsaGJ5NTZhR1Z1YkdsaGJ6TXdlWFZoYmlJN0Nna2lhWFJsYlMxcFpDSWdQU0FpTVRVNU5qWXpNRGcyT0NJN0Nna2lZbWxrSWlBOUlDSmpiMjB1YUdWbVpXbDRkVzVzYVdGdkxucG9aVzVzYVdGdklqc0tDU0pwY3kxcGJpMXBiblJ5YnkxdlptWmxjaTF3WlhKcGIyUWlJRDBnSW1aaGJITmxJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxMVzF6SWlBOUlDSXhOak0zT1RrMk16WXdNamMzSWpzS0NTSndkWEpqYUdGelpTMWtZWFJsSWlBOUlDSXlNREl4TFRFeExUSTNJREEyT2pVNU9qSXdJRVYwWXk5SFRWUWlPd29KSW1sekxYUnlhV0ZzTFhCbGNtbHZaQ0lnUFNBaVptRnNjMlVpT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURJeExURXhMVEkySURJeU9qVTVPakl3SUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TWpFdE1URXRNamNnTURZNk5UazZNakFnUlhSakwwZE5WQ0k3Q24wPSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0="; + byte[] decode = Base64.decode(code.getBytes()); + System.out.println(new String(decode)); + } +} \ No newline at end of file diff --git a/luoo_user/src/main/java/com/luoo/user/pojo/AppleOrder.java b/luoo_user/src/main/java/com/luoo/user/pojo/AppleOrder.java new file mode 100644 index 0000000..00ed1d5 --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/pojo/AppleOrder.java @@ -0,0 +1,40 @@ +package com.luoo.user.pojo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +/** + * @Author: zanglikun + * @Date: 2022/5/7 14:43 + * @Description: 苹果回调的订单对象,因为苹果回调字段 in_app给了很多 订单信息,所以我们以AppleOrder数组接,方便Java处理 + * Desc From Apple Web:https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt/in_app + */ +@ApiModel(value = "苹果返回的所有订单信息 In_App") +@Data +public class AppleOrder implements Serializable { + @ApiModelProperty(value = "购买的消耗品数量。此值对应于SKPayment存储在交易的支付属性中的对象的数量属性。“1”除非使用可变付款进行修改,否则该值通常是不变的。最大值为 10。") + private String quantity; + @ApiModelProperty(value = "购买的产品的唯一标识符。您在 App Store Connect 中创建产品时提供此值,它对应于存储在交易的支付属性中的对象的属性。productIdentifierSKPayment") + private String product_id; + @ApiModelProperty(value = "交易的唯一标识符,例如购买、恢复或续订。有关更多信息,请参阅") + private String transaction_id; + @ApiModelProperty(value = "原始购买的交易标识符。有关更多信息,请参阅。original_transaction_id") + private String original_transaction_id; + @ApiModelProperty(value = "App Store 向用户帐户收取购买或恢复产品费用的时间,或 App Store 向用户帐户收取订阅购买或续订费用的时间,采用类似于 ISO 8601 的日期时间格式。") + private String purchase_date; + @ApiModelProperty(value = "对于消耗性、非消耗性和非续订订阅产品,App Store 向用户帐户收取购买或恢复产品的时间,采用 UNIX 纪元时间格式,以毫秒为单位。对于自动续订订阅,App Store 向用户帐户收取订阅购买或过期后续订的时间,采用 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期。") + private String purchase_date_ms; + @ApiModelProperty(value = "在太平洋时区,App Store 向用户帐户收取购买或恢复产品费用的时间,或 App Store 向用户帐户收取订阅购买或续订费用的时间。") + private String purchase_date_pst; + @ApiModelProperty(value = "原始应用内购买的时间,采用类似于 ISO 8601 的日期时间格式。") + private String original_purchase_date; + @ApiModelProperty(value = "原始应用内购买的时间,采用 UNIX 纪元时间格式,以毫秒为单位。对于自动续订订阅,此值表示订阅的初始购买日期。原始购买日期适用于所有产品类型,并且在相同产品 ID 的所有交易中保持相同。此值对应于 StoreKit 中原始交易的属性。使用此时间格式处理日期。transactionDate") + private String original_purchase_date_ms; + @ApiModelProperty(value = "原始应用内购买的时间,在太平洋时区。") + private String original_purchase_date_pst; + @ApiModelProperty(value = "指示订阅是否处于免费试用期。有关更多信息,请参阅。is_trial_period") + private String is_trial_period; + @ApiModelProperty(value = "狗日的苹果没说:沙箱环境是:PURCHASED") + private String in_app_ownership_type; +} \ No newline at end of file diff --git a/luoo_user/src/main/java/com/luoo/user/pojo/AppleReceipt.java b/luoo_user/src/main/java/com/luoo/user/pojo/AppleReceipt.java new file mode 100644 index 0000000..e173dac --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/pojo/AppleReceipt.java @@ -0,0 +1,52 @@ +package com.luoo.user.pojo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +/** + * @Author: zanglikun + * @Date: 2022/5/7 15:25 + * @Description: Desc from Apple Web:https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt + */ +@ApiModel(value = "苹果响应的核心数据,反应了一些重要的数据") +@Data +public class AppleReceipt implements Serializable { + @ApiModelProperty(value = "生成的收据类型。该值对应于进行应用程序或 VPP 购买的环境。\n" + + "可能的值:Production, ProductionVPP, ProductionSandbox, ProductionVPPSandbox") + private String receipt_type; + @ApiModelProperty(value = "见。app_item_id") + private Long adam_id; + @ApiModelProperty(value = "由 App Store Connect 生成并由 App Store 用于唯一标识所购买的应用程序。仅在生产中为应用分配此标识符。将此值视为 64 位长整数。") + private Long app_item_id; + @ApiModelProperty(value = "收据所属应用的捆绑包标识符。您在 App Store Connect 上提供此字符串。这对应于应用程序文件中的值。CFBundleIdentifierInfo.plist") + private String bundle_id; + @ApiModelProperty(value = "应用程序的版本号。应用程序的版本号对应于. 在生产中,此值是设备上基于. 在沙盒中,该值始终为。CFBundleVersionCFBundleShortVersionStringInfo.plistreceipt_creation_date_ms\"1.0\"") + private String application_version; + @ApiModelProperty(value = "应用下载交易的唯一标识符。") + private Long download_id; + @ApiModelProperty(value = "一个任意数字,用于标识您的应用程序的修订版。在沙箱中,此键的值为“0”。") + private Long version_external_identifier; + @ApiModelProperty(value = "App Store 生成收据的时间,采用类似于 ISO 8601 的日期时间格式。") + private String receipt_creation_date; + @ApiModelProperty(value = "App Store 生成收据的时间,采用 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期。这个值不会改变。") + private String receipt_creation_date_ms; + @ApiModelProperty(value = "App Store 生成收据的时间,在太平洋时区。") + private String receipt_creation_date_pst; + @ApiModelProperty(value = "处理对端点的请求并生成响应的时间,采用类似于 ISO 8601 的日期时间格式。verifyReceipt") + private String request_date; + @ApiModelProperty(value = "处理对端点的请求并生成响应的时间,采用 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期。verifyReceipt") + private String request_date_ms; + @ApiModelProperty(value = "在太平洋时区处理对端点的请求并生成响应的时间。verifyReceipt") + private String request_date_pst; + @ApiModelProperty(value = "原始应用购买的时间,采用类似于 ISO 8601 的日期时间格式。") + private String original_purchase_date; + @ApiModelProperty(value = "原始应用购买的时间,采用 UNIX 纪元时间格式,以毫秒为单位。使用此时间格式处理日期。") + private String original_purchase_date_ms; + @ApiModelProperty(value = "原始应用购买时间,太平洋时区。") + private String original_purchase_date_pst; + @ApiModelProperty(value = "用户最初购买的应用版本。该值不变,对应原始购买文件中(iOS 中)或String(macOS 中)的值。在沙盒环境中,该值始终为.CFBundleVersionCFBundleShortVersionInfo.plist\"1.0\"") + private String original_application_version; + @ApiModelProperty(value = "包含所有应用内购买交易的应用内购买收据字段的数组。") + private AppleOrder[] in_app; +} \ No newline at end of file diff --git a/luoo_user/src/main/java/com/luoo/user/pojo/AppleResponse.java b/luoo_user/src/main/java/com/luoo/user/pojo/AppleResponse.java new file mode 100644 index 0000000..c16a513 --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/pojo/AppleResponse.java @@ -0,0 +1,22 @@ +package com.luoo.user.pojo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import java.io.Serializable; +/** + * @Author: zanglikun + * @Date: 2022/5/7 15:23 + * @Description: 拿着前端给的长字符串 去向Apple校验,Apple服务器响应的对象 + * Desc from Apple Web:https://developer.apple.com/documentation/appstorereceipts/responsebody + */ +@ApiModel("向Apple校验的所有返回结果") +@Data +public class AppleResponse implements Serializable { + @ApiModelProperty("交易环境:可能的值:Sandbox, Production") + private String environment; + @ApiModelProperty("交易环境:可能的值:Sandbox, Production") + private AppleReceipt receipt; + @ApiModelProperty("如果收据有效,或者0如果有错误,则返回状态代码。状态码反映了整个应用收据的状态。") + private String status; +} \ No newline at end of file