Java接入苹果支付 – IAP支付 – IOS应用内支付- 完整版

时间:2025-03-27 17:34:11
  • import .Base64;
  • import ;
  • import ;
  • import ;
  • import ;
  • import ;
  • import .slf4j.Slf4j;
  • import .;
  • import ;
  • import ;
  • import ;
  • import ;
  • import ;
  • import ;
  • import ;
  • import ;
  • import .X509TrustManager;
  • import ;
  • import ;
  • import ;
  • import ;
  • import ;
  • import .X509Certificate;
  • /**
  • * @author : zanglikun
  • * @date : 2021/11/18 9:40
  • * @Version: 1.0
  • * @Desc : 苹果支付 参考地址:/shoshana-kong/p/
  • * sendHttpsCoon 方法里面包含了增加的业务逻辑
  • * 本类引入多种的Json序列化工具,选择了Hutool
  • */
  • @Slf4j
  • @Controller
  • @RequestMapping("applePay")
  • public class ApplePayController {
  • //购买凭证验证地址
  • private static final String certificateUrl = "/verifyReceipt";
  • //测试的购买凭证验证地址
  • private static final String certificateUrlTest = "/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) {
  • // ("IOS端发送的购买凭证。数据有 userId = {},receipt = {},chooseEnv = {}",userId,receipt,chooseEnv);
  • if ((userId) || (receipt)) {
  • return "后台发送道具失败,用户ID 或者 receipt为空";
  • }
  • String url = null;
  • url = chooseEnv == true ? certificateUrl : certificateUrlTest;
  • final String certificateCode = receipt;
  • if ((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 (() || () || ()) {
  • return "调用传参错误!";
  • }
  • String line = null;
  • // todo 理论应该锁UserId,因为后台只能允许一个线程执行给玩家发送道具!
  • try {
  • // 设置SSLContext
  • SSLContext ssl = ("SSL");
  • (null, new TrustManager[]{myX509TrustManager}, null);
  • // 打开连接
  • HttpsURLConnection conn = (HttpsURLConnection) new URL(url).openConnection();
  • // 设置套接工厂
  • (());
  • // 加入数据
  • ("POST");
  • (true);
  • ("Content-type", "application/json");
  • // JSONObject obj = new JSONObject();
  • JSONObject obj = new JSONObject();
  • ("receipt-data", receipt);
  • // 发送请求
  • BufferedOutputStream buffOutStr = new BufferedOutputStream(());
  • (().getBytes());
  • ();
  • ();
  • // 获取接收响应的对象reader
  • BufferedReader reader = new BufferedReader(new InputStreamReader(()));
  • // 转换苹果响应的所有结果!
  • StringBuffer sb = new StringBuffer();
  • while ((line = ()) != null) {
  • (line);
  • }
  • // 处理苹果响应的结果
  • AppleResponse appleResponse = ((), );
  • ("用户Id{},回调获取到了{}", userId, ());
  • // 苹果说了,只有订单状态是0才算成功,其他均是错误,可查:/documentation/appstorereceipts/status
  • if (((), "0")) {
  • // 苹果给的订单号
  • AppleOrder[] in_app = ().getIn_app();
  • for (AppleOrder appleOrder : in_app) {
  • // 从订单对象获取订单号
  • String transaction_id = appleOrder.getTransaction_id();
  • String original_purchase_date_ms = appleOrder.getOriginal_purchase_date_ms();
  • (original_purchase_date_ms);
  • ("用户下单时间:{}",new DateTime((original_purchase_date_ms)+16*60*60*1000));
  • // todo 校验订单号没有发放过道具
  • if (true) {
  • String quantity = ();
  • // 获取用户购买的产品Id,
  • String product_id = appleOrder.getProduct_id();
  • switch (product_id) {
  • case ".zhenliao6yuan": {
  • break;
  • }
  • case ".zhenliao999yuan": {
  • break;
  • }
  • }
  • ("{}发送奖励成功喽!",userId);
  • }
  • }
  • return "支付成功";
  • } else {
  • ("用户ID:{},取消支付,响应码是:{}", userId, ());
  • }
  • } catch (Exception e) {
  • ("苹果回调过程中出现异常:{}", ());
  • return "有异常,停止交易!";
  • }
  • return "苹果回调处理失败!";
  • }
  • /**
  • * 注意:下面代码跟苹果支付业务无关。
  • * 这里的code 是前端请求苹果获取到的参数
  • * 苹果给前端的一个密钥(如果我们通过base64解密后,可获得signature、purchase-info、environment、pod、signing-status)
  • * 这个密钥用于告诉我们自己Java服务器 想苹果服务器校验订单是否成功的参数
  • */
  • @Test
  • public void decode() {
  • String code = "ewoJInNpZ25hdHVyZSIgPSAiQXhmVWRiYUx5T2I5bllOM3hINmQzMnBaOHI2THdmV3ZmZ1NKN1o2QTM4dEY2SjNyUTZoRVZqQ3Rra01wMnhmM1pwWnFQRmw3ZlRIdDVxNVpKZUF6UWh4NWQ1djJrR01uM3NKb3ZBWXNuWENxY3VqclBWU3A5WTFYUTZjeTlvbVNORWNYVWt0L1dkQXhsRmN6WDRZMTJzcktsMDc3WHJIdk5JMDd0VTZXajgzbVdDNE1HZmF0c2E2UEo1RG5sT2lEOG96RlJ6a0NIQ3Y3bncvRm80dnFCaFpZRmlQSDZzeW1uN2lUQlhTcXlTdlJOTGJXLytUWktKZngxR1dRV3BWdmJ5M0RtV3l4OTRaYkxGRllNODE0aTB2a1lnWDdPdVUwQWprTVFKOEJhNnJGc1hER0hYY2FCdnVhZ1NGak9iMWdJclZ2MDdmbTlBV3ltNE5KT0dVSHR0Z0FBQVdBTUlJRmZEQ0NCR1NnQXdJQkFnSUlEdXRYaCtlZUNZMHdEUVlKS29aSWh2Y05BUUVGQlFBd2daWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Td3dLZ1lEVlFRTERDTkJjSEJzWlNCWGIzSnNaSGRwWkdVZ1JHVjJaV3h2Y0dWeUlGSmxiR0YwYVc5dWN6RkVNRUlHQTFVRUF3dzdRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTWdRMlZ5ZEdsbWFXTmhkR2x2YmlCQmRYUm9iM0pwZEhrd0hoY05NVFV4TVRFek1ESXhOVEE1V2hjTk1qTXdNakEzTWpFME9EUTNXakNCaVRFM01EVUdBMVVFQXd3dVRXRmpJRUZ3Y0NCVGRHOXlaU0JoYm1RZ2FWUjFibVZ6SUZOMGIzSmxJRkpsWTJWcGNIUWdVMmxuYm1sdVp6RXNNQ29HQTFVRUN3d2pRWEJ3YkdVZ1YyOXliR1IzYVdSbElFUmxkbVZzYjNCbGNpQlNaV3hoZEdsdmJuTXhFekFSQmdOVkJBb01Da0Z3Y0d4bElFbHVZeTR4Q3pBSkJnTlZCQVlUQWxWVE1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBcGMrQi9TV2lnVnZXaCswajJqTWNqdUlqd0tYRUpzczl4cC9zU2cxVmh2K2tBdGVYeWpsVWJYMS9zbFFZbmNRc1VuR09aSHVDem9tNlNkWUk1YlNJY2M4L1cwWXV4c1FkdUFPcFdLSUVQaUY0MWR1MzBJNFNqWU5NV3lwb041UEM4cjBleE5LaERFcFlVcXNTNCszZEg1Z1ZrRFV0d3N3U3lvMUlnZmRZZUZScjZJd3hOaDlLQmd4SFZQTTNrTGl5a29sOVg2U0ZTdUhBbk9DNnBMdUNsMlAwSzVQQi9UNXZ5c0gxUEttUFVockFKUXAyRHQ3K21mNy93bXYxVzE2c2MxRkpDRmFKekVPUXpJNkJBdENnbDdaY3NhRnBhWWVRRUdnbUpqbTRIUkJ6c0FwZHhYUFEzM1k3MkMzWmlCN2o3QWZQNG83UTAvb21WWUh2NGdOSkl3SURBUUFCbzRJQjF6Q0NBZE13UHdZSUt3WUJCUVVIQVFFRU16QXhNQzhHQ0NzR0FRVUZCekFCaGlOb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxYZDNaSEl3TkRBZEJnTlZIUTRFRmdRVWthU2MvTVIydDUrZ2l2Uk45WTgyWGUwckJJVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCU0lKeGNKcWJZWVlJdnM2N3IyUjFuRlVsU2p0ekNDQVI0R0ExVWRJQVNDQVJVd2dnRVJNSUlCRFFZS0tvWklodmRqWkFVR0FUQ0IvakNCd3dZSUt3WUJCUVVIQWdJd2diWU1nYk5TWld4cFlXNWpaU0J2YmlCMGFHbHpJR05sY25ScFptbGpZWFJsSUdKNUlHRnVlU0J3WVhKMGVTQmhjM04xYldWeklHRmpZMlZ3ZEdGdVkyVWdiMllnZEdobElIUm9aVzRnWVhCd2JHbGpZV0pzWlNCemRHRnVaR0Z5WkNCMFpYSnRjeUJoYm1RZ1kyOXVaR2wwYVc5dWN5QnZaaUIxYzJVc0lHTmxjblJwWm1sallYUmxJSEJ2YkdsamVTQmhibVFnWTJWeWRHbG1hV05oZEdsdmJpQndjbUZqZEdsalpTQnpkR0YwWlcxbGJuUnpMakEyQmdnckJnRUZCUWNDQVJZcWFIUjBjRG92TDNkM2R5NWhjSEJzWlM1amIyMHZZMlZ5ZEdsbWFXTmhkR1ZoZFhSb2IzSnBkSGt2TUE0R0ExVWREd0VCL3dRRUF3SUhnREFRQmdvcWhraUc5Mk5rQmdzQkJBSUZBREFOQmdrcWhraUc5dzBCQVFVRkFBT0NBUUVBRGFZYjB5NDk0MXNyQjI1Q2xtelQ2SXhETUlKZjRGelJqYjY5RDcwYS9DV1MyNHlGdzRCWjMrUGkxeTRGRkt3TjI3YTQvdncxTG56THJSZHJqbjhmNUhlNXNXZVZ0Qk5lcGhtR2R2aGFJSlhuWTR3UGMvem83Y1lmcnBuNFpVaGNvT0FvT3NBUU55MjVvQVE1SDNPNXlBWDk4dDUvR2lvcWJpc0IvS0FnWE5ucmZTZW1NL2oxbU9DK1JOdXhUR2Y4YmdwUHllSUdxTktYODZlT2ExR2lXb1IxWmRFV0JHTGp3Vi8xQ0tuUGFObVNBTW5CakxQNGpRQmt1bGhnd0h5dmozWEthYmxiS3RZZGFHNllRdlZNcHpjWm04dzdISG9aUS9PamJiOUlZQVlNTnBJcjdONFl0UkhhTFNQUWp2eWdhWndYRzU2QWV6bEhSVEJoTDhjVHFBPT0iOwoJInB1cmNoYXNlLWluZm8iID0gImV3b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGNITjBJaUE5SUNJeU1ESXhMVEV4TFRJMklESXlPalU1T2pJd0lFRnRaWEpwWTJFdlRHOXpYMEZ1WjJWc1pYTWlPd29KSW5WdWFYRjFaUzFwWkdWdWRHbG1hV1Z5SWlBOUlDSTROelU1WmpNMVlUTTNNMk0wTVRabU5qazRPVFJrWkRRd05XRTFOemhoTURoalpqSTVOMlkwSWpzS0NTSnZjbWxuYVc1aGJDMTBjbUZ1YzJGamRHbHZiaTFwWkNJZ1BTQWlNVEF3TURBd01Ea3hPVE13TWpFeU5DSTdDZ2tpWW5aeWN5SWdQU0FpTVRBd0lqc0tDU0owY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTVRBd01EQXdNRGt4T1RNd01qRXlOQ0k3Q2draWNYVmhiblJwZEhraUlEMGdJakVpT3dvSkltbHVMV0Z3Y0MxdmQyNWxjbk5vYVhBdGRIbHdaU0lnUFNBaVVGVlNRMGhCVTBWRUlqc0tDU0p2Y21sbmFXNWhiQzF3ZFhKamFHRnpaUzFrWVhSbExXMXpJaUE5SUNJeE5qTTNPVGsyTXpZd01qYzNJanNLQ1NKMWJtbHhkV1V0ZG1WdVpHOXlMV2xrWlc1MGFXWnBaWElpSUQwZ0lqaEJSRVJHUlRjMUxURkVRVVl0TkRVek1TMDRNakV3TFVKRE9UZzNSa1l3TlRrNU9DSTdDZ2tpY0hKdlpIVmpkQzFwWkNJZ1BTQWlZMjl0TG1obFptVnBlSFZ1YkdsaGJ5NTZhR1Z1YkdsaGJ6TXdlWFZoYmlJN0Nna2lhWFJsYlMxcFpDSWdQU0FpTVRVNU5qWXpNRGcyT0NJN0Nna2lZbWxrSWlBOUlDSmpiMjB1YUdWbVpXbDRkVzVzYVdGdkxucG9aVzVzYVdGdklqc0tDU0pwY3kxcGJpMXBiblJ5YnkxdlptWmxjaTF3WlhKcGIyUWlJRDBnSW1aaGJITmxJanNLQ1NKd2RYSmphR0Z6WlMxa1lYUmxMVzF6SWlBOUlDSXhOak0zT1RrMk16WXdNamMzSWpzS0NTSndkWEpqYUdGelpTMWtZWFJsSWlBOUlDSXlNREl4TFRFeExUSTNJREEyT2pVNU9qSXdJRVYwWXk5SFRWUWlPd29KSW1sekxYUnlhV0ZzTFhCbGNtbHZaQ0lnUFNBaVptRnNjMlVpT3dvSkluQjFjbU5vWVhObExXUmhkR1V0Y0hOMElpQTlJQ0l5TURJeExURXhMVEkySURJeU9qVTVPakl3SUVGdFpYSnBZMkV2VEc5elgwRnVaMlZzWlhNaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TWpFdE1URXRNamNnTURZNk5UazZNakFnUlhSakwwZE5WQ0k3Q24wPSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0=";
  • byte[] decode = (());
  • (new String(decode));
  • }
  • }