苹果支付回调核心是校验凭证的有效性,同时防止利用低价格商品凭证校验高价格商品
//一个凭证下可能会存在多个订单凭证,所以最好进行自动补单。利用第三方订单幂等原则,生成唯一订单id
我们实现机制是客户端下单支付,完成之后 客户端向服务器发起回调,服务器进行相应的订单回调(同时检测,实现补单功能)
//回调函数
private Map<String, Object> doCallBack(ApplePurchaseBO purchaseBO) { // 检查服务器是否已经保存 并获取支付的url类型 ApplePayUrlTypeEnum payUrlTypeEnum = checkAndSaveCertificateFmDB(purchaseBO); // 去苹果官网验证凭证的真实性 AppleCertificateBO certificateBO = getAndCheckCertificateFmApple(purchaseBO.getCertificate(), payUrlTypeEnum); ShopOrder shopOrder = shopService.getOrder(purchaseBO.getOid()); // 校验是否为我们平台的凭证 checkBundleId(shopOrder, certificateBO.getBundleId()); // 校验订单回掉的有效性,如果是新版本且尚未回掉,则进行补单 操作 checkTIdPIdAndSupplementOrder(shopOrder, purchaseBO, certificateBO); return getResult(purchaseBO); } private ApplePurchaseBO getRequestDecorate(Object originRequest) { boolean isHttpRequest = false; HttpServletRequest request = null; ApplePayNotifyDTO notifyDTO = null; Map<String, String> params = null; if (originRequest instanceof HttpServletRequest) { isHttpRequest = true; request = (HttpServletRequest) originRequest; params = UrlUtils.convertRequestBodyToMap(request); logger.info("[ApplePay][callBack] http body <{}>", params); } else { notifyDTO = (ApplePayNotifyDTO) originRequest; logger.info("[ApplePay][callBack] interface body <{}>", notifyDTO); } return new ApplePurchaseBO() .setOid(isHttpRequest ? params.get("oid") : notifyDTO.getOid()) .setCertificate(isHttpRequest ? params.get("receipt") : notifyDTO.getCertificate()) .setOriginTransactionId(isHttpRequest ? params.get("transactionId") : notifyDTO.getTransactionId()) .setOriginProductId(isHttpRequest ? params.get("productId") : notifyDTO.getProductId()) .setChannel(isHttpRequest ? request.getHeader("app_channel") : notifyDTO.getChannel()) .setVersion(isHttpRequest ? request.getHeader("app_version") : notifyDTO.getVersion()) .setPackageName(isHttpRequest ? request.getHeader("app_package_name") : notifyDTO.getPackageName()) .setSubChannel(isHttpRequest ? request.getHeader("sub_channel") : "") .setHttpRequest(isHttpRequest) .setPayType(PayTypeEnum.Apple.payType()) .setDeviceType(DeviceTypeEnum.ios.type()) .setUid(isHttpRequest ? Long.valueOf(request.getHeader("uid")) : notifyDTO.getUid()) .setNewVersion(isHttpRequest && newVersion.equals(params.get("pay_version"))); } private ApplePayUrlTypeEnum checkAndSaveCertificateFmDB(ApplePurchaseBO purchaseBO) { boolean paramError = StringUtils.isEmpty(purchaseBO.getCertificate()) || StringUtils.isEmpty(purchaseBO.getOid()) || StringUtils.isEmpty(purchaseBO.getOriginProductId()) || purchaseBO.getUid() == null || StringUtils.isEmpty(purchaseBO.getOriginTransactionId()); if (paramError) { throw new BusinessException(ResultEnum.PARA_ERR); } ApplePayUrlTypeEnum payUrlTypeEnum; ApplePurchaseCertificate certificateInfo = certificateService.getCertificateByOidTransactionId(purchaseBO.getOid(), purchaseBO.getOriginTransactionId()); if (certificateInfo == null) { payUrlTypeEnum = getPayUrl(purchaseBO.getChannel(), purchaseBO.getVersion(), purchaseBO.getPackageName()); saveApplePurchaseCertificate(purchaseBO.getOid(), purchaseBO.getCertificate(), purchaseBO.getOriginProductId(), purchaseBO.getOriginTransactionId(), payUrlTypeEnum); } else { if (AppleCertificateStateEnum.fake.state().equals(certificateInfo.getState())) { throw new BusinessException(ResultEnum.APPLE_PAY_GOODS_EXIST); } payUrlTypeEnum = ApplePayUrlTypeEnum.find(certificateInfo.getPayUrlType()); } return payUrlTypeEnum; } //凭证校验 private AppleCertificateBO getAndCheckCertificateFmApple(String certificate, ApplePayUrlTypeEnum payUrlTypeEnum) { Object o = JSON.parseObject(getAppleRspBody(certificate, payUrlTypeEnum.url())); Object receipt = ((JSONObject) o).get("receipt"); if ("0".compareTo(((JSONObject) o).get("status").toString()) != 0) { dealException(o); } AppleCertificateBO certificateBO = JSONObject.parseObject(receipt.toString(), AppleCertificateBO.class); boolean isFakeCertificate = certificateBO == null || certificateBO.getInAppArray() == null || certificateBO.getInAppArray().length == 0; if (isFakeCertificate) { //校验凭证规则 throw new BusinessException(ResultEnum.APPLE_PAY_RECEIPT_UNUSUAL); } return certificateBO; } private void checkBundleId(ShopOrder shopOrder, String bundleId) { boolean isApplePay = shopOrder != null && shopOrder.getPayType().equals(PayTypeEnum.Apple.payType()); if (appPackageManager.getAppPackage(bundleId) == null || !isApplePay) { throw new BusinessException(ResultEnum.APPLE_PAY_RECEIPT_UNKNOW); } } private void checkTIdPIdAndSupplementOrder(ShopOrder shopOrder, ApplePurchaseBO purchaseBO, AppleCertificateBO certificateBO) { List<InApplePurchaseBO> inApplePurchaseBOS = new ArrayList<>(); boolean isFakeGood = true; String transactionId; String productionId; if (!purchaseBO.getOriginProductId().equals(goodsManager.getGoodsByGoodsId(shopOrder.getGid()).getProductId())) { throw new BusinessException(ResultEnum.APPLE_PAY_GOODS_EXIST); } for (int i = 0; i < certificateBO.getInAppArray().length; i++) { productionId = certificateBO.getInAppArray()[i].getProductId(); transactionId = certificateBO.getInAppArray()[i].getTransactionId(); boolean isFind = transactionId.equals(purchaseBO.getOriginTransactionId()) && purchaseBO.getOriginProductId().equals(productionId); if (isFind) { goodsManager.getGoodsByProductId(purchaseBO.getOriginProductId()); isFakeGood = false; } else {//如果未找到凭证 且是 新版本的则进行补单操作 boolean supplementOrder = purchaseBO.getNewVersion() && certificateService.getCertificateByTId(transactionId) == null; if (supplementOrder) { inApplePurchaseBOS.add(new InApplePurchaseBO(transactionId, productionId)); } } } supplementOrder(purchaseBO, inApplePurchaseBOS); if (isFakeGood) { //判断商品是不是有效 certificateService.updateApplePurchaseCertificateByOid(purchaseBO.getOid(), AppleCertificateStateEnum.fake, AppleCertificateStateEnum.init); throw new BusinessException(ResultEnum.APPLE_PAY_GOODS_EXIST); } } private void saveApplePurchaseCertificate(String oid, String certificate, String productionId, String transactionId, ApplePayUrlTypeEnum urlTypeEnum) { ApplePurchaseCertificate purchaseCertificate = new ApplePurchaseCertificate(); purchaseCertificate.setOid(oid); purchaseCertificate.setCertificate(certificate); purchaseCertificate.setProductId(productionId); purchaseCertificate.setTransactionId(transactionId); purchaseCertificate.setPayUrlType(urlTypeEnum.urlType()); certificateService.savePurchaseCertificate(purchaseCertificate); } @Override public Map<String, Object> queryPay(String oid, String certificate) { return null; } private String getAppleRspBody(String purchaseCertificate, String url) { String responseBody = HttpInvokeUtil.httpsPostJson(url, JSON.parseObject(JSON.toJSONString(ImmutableMap.of("receipt-data", purchaseCertificate)))); logger.info("[ApplePay][callBack] responseBody:{}", JSON.toJSONString(responseBody)); return responseBody; } private Map<String, Object> getResult(ApplePurchaseBO purchaseBO) { Map<String, Object> result = new HashMap<>(); result.put(PayConstant.certificate, purchaseBO.getCertificate()); result.put(PayConstant.oid, purchaseBO.getOid()); result.put(PayConstant.result, PayConstant.paySuccess); return result; } private void dealException(Object o) { switch (((JSONObject) o).get("status").toString()) { case "21002": throw new BusinessException(ResultEnum.APPLE_PAY_RECEIPT_ERROR02); case "21003": throw new BusinessException(ResultEnum.APPLE_PAY_RECEIPT_ERROR03); case "21005": throw new BusinessException(ResultEnum.APPLE_PAY_RECEIPT_ERROR05); case "21007": throw new BusinessException(ResultEnum.APPLE_PAY_RECEIPT_ERROR07); case "21008": throw new BusinessException(ResultEnum.APPLE_PAY_RECEIPT_ERROR08); default: break; } throw new BusinessException(ResultEnum.PAY_THIRD_FAIL); } private ApplePayUrlTypeEnum getPayUrl(String channel, String version, String packageName) { ApplePaymentConfigVO applePaymentConfigVO = null; if (channel != null && version != null && packageName != null) { applePaymentConfigVO = payConfigService.getApplePaymentConfigVO(channel, version, packageName); } if (applePaymentConfigVO != null) { return ApplePayUrlTypeEnum.find(applePaymentConfigVO.getType()); } return ApplePayUrlTypeEnum.production; } private boolean canRetry(Exception e) { if (e instanceof BusinessException) { ResultEnum resultEnum = ((BusinessException) e).getResultEnum(); if (resultEnum != null) { return false; } } return true; } private void playDoRetryNotify(ApplePayNotifyDTO notifyDTO) { applePayNotifyExecutor.execute(new CasFailOverRunnable(3) { @Override public int doRun() { Map<String, Object> res = payService.onPayResultNotify(PayTypeEnum.Apple.payType(), notifyDTO); if (PayConstant.paySuccess.equals(res.get(PayConstant.result))) { return 1; } return 0; } }); } private void supplementOrder(ApplePurchaseBO purchaseBO, List<InApplePurchaseBO> inApplePurchaseBOS) { if (inApplePurchaseBOS.isEmpty()) { return; } supplementOrderExecutor.execute(new CasFailOverRunnable(3) { @Override public int doRun() { for (InApplePurchaseBO inApplePurchaseBO : inApplePurchaseBOS) { logger.info("[ApplePay][callBack] ApplePurchaseBO :{} inApplePurchaseBO <{}>", purchaseBO, inApplePurchaseBO); Goods goods = goodsManager.getGoodsByProductId(inApplePurchaseBO.getProductId()); ShopOrderApplyResponse response = shopService.applyOrder(purchaseBO.getUid(), ShopOrderApplyRequest.covert(inApplePurchaseBO.getProductId(), inApplePurchaseBO.getTransactionId(), goods, purchaseBO)); playDoRetryNotify(ApplePayNotifyDTO.covert(purchaseBO, response.getOid(), inApplePurchaseBO.getTransactionId(), inApplePurchaseBO.getProductId())); } return 1; } }); }