博仔 發表於 2023-1-3 14:26:00

Java开发APP对接ios内购支付功能,可直接伸手党,简单修改即可使用

<h3><span style="font-size: 15px">苹果支付流程介绍</span></h3>
<p><span style="font-size: 15px">①户在app端购买产品下发支付请求指令app;</span><br><span style="font-size: 15px">②app获取用户购买的信息及指令请求苹果系统服务后台进行支付扣款;</span><br><span style="font-size: 15px">③苹果系统服务器扣款成功后返回receipt_data加密数据和支付订单号order_id给app端;</span><br><span style="font-size: 15px">④app端直接将返回的数据及订单号,请求java后台的验证接口;</span><br><span style="font-size: 15px">⑤java后端直接通过HttpsURLConnection将app端携带来的参数以及验证地址url请求苹果系统服务器验证用户在app端支付的结果;(注:receipt_data加密数据不需要在java后台解密,直接传给苹果服务器)</span><br><span style="font-size: 15px">⑥苹果系统服务器将验证的结果及订单产品信息返回给java后台服务器,java后台服务器根据返回的结果处理自己的业务;</span><br><span style="font-size: 15px">⑦java后端处理后自己的业务后,将验证结果以及自己所要返回的内容返回给app端;</span><br><span style="font-size: 15px">②app端在请求苹果系统服务器检查java服务端服务是否已经验证;</span><br><span style="font-size: 15px">③苹果服务告知app端,java服务是否验证成功;</span><br><span style="font-size: 15px">⑧app端根据苹果服务器返回的验证结果通知提示用户订单是否结束;</span><br><span style="font-size: 15px">注:苹果支付内购,价格是以6的倍数定价,且产品订单是唯一的;</span></p>
<p>&nbsp;</p>
<p><span style="font-size: 15px"><strong>java代码</strong></span></p>
<p><span style="font-size: 15px">ApplePayUtil工具类,用于验证苹果返回的凭证</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 15px"><span style="color: rgba(0, 0, 255, 1)">import</span> javax.net.ssl.*<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.BufferedOutputStream;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.BufferedReader;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.InputStream;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.io.InputStreamReader;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.net.URL;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.security.cert.CertificateException;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.security.cert.X509Certificate;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> java.util.Locale;


</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ApplePayUtil {

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span> TrustAnyTrustManager <span style="color: rgba(0, 0, 255, 1)">implements</span><span style="color: rgba(0, 0, 0, 1)"> X509TrustManager {

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> checkClientTrusted(X509Certificate[] chain, String authType) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> CertificateException {
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> checkServerTrusted(X509Certificate[] chain, String authType) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> CertificateException {
      }

      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> X509Certificate[] getAcceptedIssuers() {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> X509Certificate[]{};
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span> TrustAnyHostnameVerifier <span style="color: rgba(0, 0, 255, 1)">implements</span><span style="color: rgba(0, 0, 0, 1)"> HostnameVerifier {
      @Override
      </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)"> verify(String hostname, SSLSession session) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"<span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">final</span> String url_verify = "https://buy.itunes.apple.com/verifyReceipt"<span style="color: rgba(0, 0, 0, 1)">;

    </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)">
   * 苹果服务器验证
   *
   * </span><span style="color: rgba(128, 128, 128, 1)">@param</span><span style="color: rgba(0, 128, 0, 1)"> receipt 账单
   * </span><span style="color: rgba(128, 128, 128, 1)">@return</span><span style="color: rgba(0, 128, 0, 1)"> null 或返回结果 沙盒 </span><span style="color: rgba(0, 128, 0, 1); text-decoration: underline">https://sandbox.itunes.apple.com/verifyReceipt</span><span style="color: rgba(0, 128, 0, 1)">
   * @url 要验证的地址
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> String buyAppVerify(String receipt, <span style="color: rgba(0, 0, 255, 1)">int</span> type) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">环境判断 线上/开发环境用不同的请求链接</span>
      String url = ""<span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (type == 0<span style="color: rgba(0, 0, 0, 1)">) {
            url </span>= url_sandbox; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">沙盒测试</span>
      } <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            url </span>= url_verify; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">线上测试</span>
<span style="color: rgba(0, 0, 0, 1)">      }
      SSLContext sc </span>= SSLContext.getInstance("SSL"<span style="color: rgba(0, 0, 0, 1)">);
      sc.init(</span><span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">new</span> TrustManager[]{<span style="color: rgba(0, 0, 255, 1)">new</span> TrustAnyTrustManager()}, <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> java.security.SecureRandom());
      URL console </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> URL(url);
      HttpsURLConnection conn </span>=<span style="color: rgba(0, 0, 0, 1)"> (HttpsURLConnection) console.openConnection();
      conn.setSSLSocketFactory(sc.getSocketFactory());
      conn.setHostnameVerifier(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TrustAnyHostnameVerifier());
      conn.setRequestMethod(</span>"POST"<span style="color: rgba(0, 0, 0, 1)">);
      conn.setRequestProperty(</span>"content-type", "text/json"<span style="color: rgba(0, 0, 0, 1)">);
      conn.setRequestProperty(</span>"Proxy-Connection", "Keep-Alive"<span style="color: rgba(0, 0, 0, 1)">);
      conn.setDoInput(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
      conn.setDoOutput(</span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
      BufferedOutputStream hurlBufOus </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> BufferedOutputStream(conn.getOutputStream());
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">拼成固定的格式传给平台</span>
      String str = String.format(Locale.CHINA, "{\"receipt-data\":\"" + receipt + "\"}"<span style="color: rgba(0, 0, 0, 1)">);
      hurlBufOus.write(str.getBytes());
      hurlBufOus.flush();

      InputStream is </span>=<span style="color: rgba(0, 0, 0, 1)"> conn.getInputStream();
      BufferedReader reader </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> BufferedReader(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> InputStreamReader(is));
      String line </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      StringBuffer sb </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringBuffer();
      </span><span style="color: rgba(0, 0, 255, 1)">while</span> ((line = reader.readLine()) != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            sb.append(line);
      }

      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sb.toString();
    }

}</span></span></pre>
</div>
<p>&nbsp;</p>
<p><span style="font-size: 15px"><strong>验证凭证的主要代码</strong></span></p>
<pre><span style="font-size: 15px"><span style="color: rgba(0, 0, 0, 1)">productId</span>:此字段是用来配制ios固定充值选项的,因为不同的金额都需要在苹果商家平台中配置产品id(<strong>此id必须是绝对唯一值,用于匹配实际订单</strong>)</span></pre>
<div class="cnblogs_code">
<pre><span style="font-size: 15px"><span style="color: rgba(0, 0, 255, 1)">package</span><span style="color: rgba(0, 0, 0, 1)"> com.sports.user.controller.dto;


</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> io.swagger.annotations.ApiModelProperty;
</span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> lombok.Data;

@Data
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> IosPayVerifyReq {

    @ApiModelProperty(value </span>= "商家订单id"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String orderId;

    @ApiModelProperty(value </span>= "用户id"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String userId;

    @ApiModelProperty(value </span>= "验证凭据"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String receiptDate;

    @ApiModelProperty(value </span>= "ios选项值"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String productId;
}</span></span></pre>
</div>
<p>&nbsp;<span style="font-size: 15px"> </span></p>
<p><span style="font-size: 15px">验证凭证的接口方法</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="font-size: 15px">   @ApiOperation("IOS内购凭证校验"<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> @ResponseBody
    UsrPayDO iosPay(@RequestBody IosPayVerifyReq req) {
      UsrPayDO usrPayDO </span>=<span style="color: rgba(0, 0, 0, 1)"> usrPayService.getById(req.getOrderId());
      </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">校验用户信息</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StrUtil.isBlank(req.getUserId())) {
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> SportsException(ResultCodeEnum.FAIL, "用户id不能为空"<span style="color: rgba(0, 0, 0, 1)">);
            }

            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">此步骤防止用户支付完成后立马退出APP,此时充值步骤未完成做的二次校验。具体思路是等用户再次登录APP后,前端查询最后一条充值信息并调用后端接口,完成充值流程。if (StringUtils.isBlank(req.getOrderId())) {</span>
            UsrPayDO usrPayNew = usrPayService.getOne(<span style="color: rgba(0, 0, 255, 1)">new</span> LambdaQueryWrapper&lt;UsrPayDO&gt;<span style="color: rgba(0, 0, 0, 1)">()
                  .eq(UsrPayDO::getUserId, req.getUserId())
                  .orderByDesc(UsrPayDO::getCreateTime)
                  .last(</span>"limit 1"<span style="color: rgba(0, 0, 0, 1)">));
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">payId:正常充值完成后会收到ios的订单id,此步骤判断是否是未完成的订单以及订单的状态是否是初始状态</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span> (StringUtils.isBlank(usrPayNew.getPayId()) &amp;&amp;<span style="color: rgba(0, 0, 0, 1)"> usrPayNew.getStatus().equals(PayStatusEnum.TYPE_0.code)) {
                </span><span style="color: rgba(0, 0, 255, 1)">if</span> (usrPayNew != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                  req.setQxbOrderId(usrPayNew.getId());
                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (StrUtil.isBlank(usrPayNew.getOtherId())) {
                        usrPayNew.setOtherId(req.getReceiptDate());
                  }
                  usrPayService.updateById(usrPayNew);
                } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                  </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> SportsException(ResultCodeEnum.FAIL, "未找到历史支付订单"<span style="color: rgba(0, 0, 0, 1)">);
                }
            }
            
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (usrPayDO == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> SportsException(ResultCodeEnum.FAIL, "未找到支付订单"<span style="color: rgba(0, 0, 0, 1)">);
            }
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (usrPayDO.getStatus().intValue() !=<span style="color: rgba(0, 0, 0, 1)"> PayStatusEnum.TYPE_0.code) {
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> SportsException(ResultCodeEnum.FAIL, "支付订单不是初始状态"<span style="color: rgba(0, 0, 0, 1)">);
            }

            String verifyResult </span>= ApplePayUtil.buyAppVerify(req.getReceiptDate(), 1<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (verifyResult == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> SportsException(ResultCodeEnum.FAIL, "网络遇到故障了,请稍后查询订单更新信息"<span style="color: rgba(0, 0, 0, 1)">);
            }
            log.info(</span>"苹果平台返回JSON:" +<span style="color: rgba(0, 0, 0, 1)"> verifyResult);
            JSONObject jsonObject </span>=<span style="color: rgba(0, 0, 0, 1)"> JSONObject.parseObject(verifyResult);

            String status </span>= jsonObject.getString("status"<span style="color: rgba(0, 0, 0, 1)">);
            </span><span style="color: rgba(0, 0, 255, 1)">if</span> ("21002"<span style="color: rgba(0, 0, 0, 1)">.equals(status)) {
                </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> SportsException(ResultCodeEnum.FAIL, "当前凭证有误,暂未查询到数据!"<span style="color: rgba(0, 0, 0, 1)">);
            }

            </span><span style="color: rgba(0, 0, 255, 1)">if</span> ("21007"<span style="color: rgba(0, 0, 0, 1)">.equals(status)) {
                </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">在沙盒测试发送平台验证</span>
                verifyResult = ApplePayUtil.buyAppVerify(req.getReceiptDate(), 0<span style="color: rgba(0, 0, 0, 1)">);
                log.info(</span>"沙盒环境,苹果平台返回JSON:" +<span style="color: rgba(0, 0, 0, 1)"> verifyResult);
                jsonObject </span>=<span style="color: rgba(0, 0, 0, 1)"> JSONObject.parseObject(verifyResult);
                status </span>= jsonObject.getString("status"<span style="color: rgba(0, 0, 0, 1)">);
            }
            log.info(</span>"苹果平台返回值param:{}" +<span style="color: rgba(0, 0, 0, 1)"> jsonObject);


            </span><span style="color: rgba(0, 0, 255, 1)">if</span> (com.sports.core.utils.StringUtils.equals(status, "0"<span style="color: rgba(0, 0, 0, 1)">)) {
                String r_receipt </span>= jsonObject.getString("receipt"<span style="color: rgba(0, 0, 0, 1)">);
                JSONObject returnJson </span>=<span style="color: rgba(0, 0, 0, 1)"> JSONObject.parseObject(r_receipt);
                JSONArray inApp </span>= (JSONArray) JSONPath.eval(returnJson, "in_app"<span style="color: rgba(0, 0, 0, 1)">);
                </span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (Object inAppStr : inApp) {
                  String transactionId </span>= String.valueOf(JSONPath.eval(inAppStr, "transaction_id"<span style="color: rgba(0, 0, 0, 1)">));
                  String productId </span>= String.valueOf(JSONPath.eval(inAppStr, "product_id"<span style="color: rgba(0, 0, 0, 1)">));

                  </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (com.sports.core.utils.StringUtils.equals(productId, req.getProductId())) {
                        </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">自己的业务逻辑,订单存库,更新状态等...</span>
<span style="color: rgba(0, 0, 0, 1)">                  }
                }
            }
         
      } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) {
            e.printStackTrace();
      }
       </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> usrPayDO;
    }</span></span></pre>
</div>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/Gengzh/p/17022099.html
頁: [1]
查看完整版本: Java开发APP对接ios内购支付功能,可直接伸手党,简单修改即可使用