springBoot对接微信APP支付和支付宝APP支付

974 阅读8分钟

springBoot对接微信APP支付和支付宝APP支付

微信APP支付

微信开发文档: pay.weixin.qq.com/wiki/doc/ap…

55f34cc1e8d1929c0fa492fc8ad0c06.png 引入微信sdk <dependency> <groupId>com.github.javen205</groupId> <artifactId>IJPay-WxPay</artifactId> <version>2.7.4</version> </dependency>

1、相关配置:

#由微信生成的应用ID
appId: 
#商户号
mchId: 
#api秘钥
apiKey:
#apiv3秘钥
apiKey3: 
#秘钥 这里需要用绝对路径
keyPath:
#CA证书 格式.pem 这里需要用绝对路径
certPath: 
#CA证书 格式.12这里需要用绝对路径
certP12Path: 
#平台证书 格式.pem 这里需要用绝对路径
platformCertPath: 
#平台证书 格式.pem 这里需要用绝对路径
platformCertPathWindows: 
# 支付回调地址
domain: 

WxPayV3Bean相关代码:

@Component
@Data
@ConfigurationProperties(prefix = "v3")
public class WxPayV3Bean {
    private String appId;
    private String keyPath;
    private String certPath;
    private String certP12Path;
    private String platformCertPath;
    private String mchId;
    private String apiKey;
    private String apiKey3;
    private String domain;
    private String platformCertPathWindows;
}

1.1在内网环境中测试支付时,回调地址必须公网可以访问才行,否则微信调用不到我们的回调接口,可以使用内网穿透映射自己本地服务的ip端口从而进行测试,相关操作请自行百度

blog.csdn.net/weixin_4206…

1.2api证书文件的获取:

请登录商户平台进入【账户中心】->【账户设置】->【API安全】根据提示指引下载证书。

image.png

1.3api密钥设置

请登录商户平台进入【账户中心】->【账户设置】->【API安全】->【APIv3密钥】中设置 API 密钥。

1.4平台证书需要我们自己去生成相关代码如下:

生成平台证书Service层代码:

public String get() {
    try {
        String path = getPath("apiclient_key.pem");
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethod.GET,
                WxDomain.CHINA.toString(),
                WxApiType.GET_CERTIFICATES.toString(),
                wxPayV3Bean.getMchId(),
                getSerialNumber(),
                null,
                path,
                ""
        );

![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e2aee2a6f2c4533b1ead624660b9cf3~tplv-k3u1fbpfcp-watermark.image?)
        String timestamp = response.getHeader("Wechatpay-Timestamp");
        String nonceStr = response.getHeader("Wechatpay-Nonce");
        String serialNumber = response.getHeader("Wechatpay-Serial");
        String signature = response.getHeader("Wechatpay-Signature");

        String body = response.getBody();
        int status = response.getStatus();

        logger.info("serialNumber: {}", serialNumber);
        logger.info("status: {}", status);
        logger.info("body: {}", body);
        int isOk = 200;
        if (status == isOk) {
            JSONObject jsonObject = JSONUtil.parseObj(body);
            JSONArray dataArray = jsonObject.getJSONArray("data");
            // 默认认为只有一个平台证书
            JSONObject encryptObject = dataArray.getJSONObject(0);
            JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
            String associatedData = encryptCertificate.getStr("associated_data");
            String cipherText = encryptCertificate.getStr("ciphertext");
            String nonce = encryptCertificate.getStr("nonce");
            String serialNo = encryptObject.getStr("serial_no");
            final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText,
                    getPlatformCertPath());
            logger.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
        }
        // 根据证书序列号查询对应的证书来验证签名结果
        boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCertPath());
        System.out.println("verifySignature:" + verifySignature);
        return body;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

保存平台证书:

private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
    try {
        AesUtil aesUtil = new AesUtil(wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
        // 平台证书密文解密
        // encrypt_certificate 中的  associated_data nonce  ciphertext
        String publicKey = aesUtil.decryptToString(
                associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                cipherText
        );
        // 保存证书
        FileWriter writer = new FileWriter(certPath);
        writer.write(publicKey);
        writer.close();
        // 获取平台证书序列号
        X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
        return certificate.getSerialNumber().toString(16).toUpperCase();
    } catch (Exception e) {
        e.printStackTrace();
        return e.getMessage();
    }
}

获取证书序列号:

private String getSerialNumber() throws IOException {
    if (StrUtil.isEmpty(serialNo)) {
        // 获取证书序列号
        String path = getPath("apiclient_cert.pem");
        X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(path));
        serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
    }
    return serialNo;
}

1.5证书路径获取相关代码

private String getPath(String fileName) throws IOException {
    String path = "";
    String osName = System.getProperties().getProperty("os.name");
    if (osName.equals("Windows")) {
        path = getCerPath(fileName);
        return path;
    }else {
        InputStream stream = this.getClass().getClassLoader().getResourceAsStream("cert/" + fileName);
        String proFilePath = System.getProperty("user.dir");
        path = proFilePath + File.separator + fileName;
        File file = new File(path);
        if (!file.exists()) {
            FileUtils.copyInputStreamToFile(stream,file);
        }
    }
    return path;
}

本地获取证书路径方法:

private String getCerPath(String cerName){
    ClassLoader classLoader = this.getClass().getClassLoader();
    URL resource = classLoader.getResource("cert");
    String path = resource.getPath() + File.separatorChar + cerName;
    return path;
}

服务器获取证书路径:

private String getPlatformCertPath() {
    String platformCertPath = "";
    String osName = System.getProperties().getProperty("os.name");
    if (osName.equals("Linux")) {
        platformCertPath = wxPayV3Bean.getPlatformCertPath();
    } else {
        platformCertPath = wxPayV3Bean.getPlatformCertPathWindows();
    }
    File file = new File(platformCertPath);
    if (!file.getParentFile().isDirectory()) {
        file.getParentFile().mkdirs();
    }
    if (!file.exists()) {
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return platformCertPath;
}

1.6本地获取相关证书路径使用getResource()方法是可以的,但是在服务器上是不行的,因为服务器上是jar包运行的,获取不到jar包中的相关资源,会报找不到文件的异常,最终使用this.getClass().getClassLoader().getResourceAsStream("cert/" + fileName)方法获取到相关文件的流,将他写入到docker容器下的一个目录中,使用这个临时文件来进行生成平台证书的操作,然后挂载出平台证书;

2、核心代码:

2.1微信下单

@Override
public ResponseDto appPay(WxOrderParam param) {

    StockCarsManagement stockCar = stockCarsManagementService.getOne(new LambdaQueryWrapper<StockCarsManagement>()
            .eq(StockCarsManagement::getVehicleId, param.getVehicleId()));
    stockCar = Optional.ofNullable(stockCar).orElseThrow(() -> new BizException(BizExceptionEnum.INVALID_ARGUMENT,
            "车辆信息不存在"));
    OrderManagement orderManagement = orderService.getOne(new LambdaQueryWrapper<OrderManagement>()
            .eq(OrderManagement::getOrderNo, param.getOrderNo()));
    orderManagement =
            Optional.ofNullable(orderManagement).orElseThrow(() -> new BizException(BizExceptionEnum.INVALID_ARGUMENT,
                    "订单信息不存在"));
    if(!OrderStatusEnum.WAIT.getStatusCode().equals(orderManagement.getStatus())){
        throw new BizException(BizExceptionEnum.DATA_NOT_EXIST, "订单状态有误");
    }
    try {
        BigDecimal money = param.getAmount().multiply(BigDecimal.valueOf(100d));//微信1分是100
        //统一下单参数封装,过期时间为24小时
        String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 1440);
        UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
                .setAppid(wxPayV3Bean.getAppId())
                .setMchid(wxPayV3Bean.getMchId())
                .setDescription(stockCar.getTheTitle())
                .setOut_trade_no(param.getOrderNo())
                .setTime_expire(timeExpire)
                .setNotify_url(wxPayV3Bean.getDomain())
                .setAmount(new Amount().setTotal(money.intValue()));
        logger.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethod.POST,
                WxDomain.CHINA.toString(),
                WxApiType.APP_PAY.toString(),
                wxPayV3Bean.getMchId(),
                getSerialNumber(),
                null,
                getPath("apiclient_key.pem"),
                JSONUtil.toJsonStr(unifiedOrderModel)
        );
        boolean verifySignature = WxPayKit.verifySignature(response, getPlatformCertPath());
        logger.info("verifySignature: {}", verifySignature);
        if (verifySignature) {
            String body = response.getBody();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            String prepayId = jsonObject.getStr("prepay_id");
            Map<String, String> map = appPrepayIdCreateSign(wxPayV3Bean.getAppId(), wxPayV3Bean.getMchId(),
                    prepayId, getPath("apiclient_key.pem"));
            logger.info("唤起支付参数:{}", map);
            return ReturnUtil.returnSuccess(map);
        } else {
            return ReturnUtil.returnError();
        }

    } catch (Exception e) {
        e.printStackTrace();
        return ReturnUtil.returnError(null, e.getMessage());
    }
}

2.2微信支付异步回调通知:

@Override
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
    Map<String, String> map = new HashMap<>(12);
    try {
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");

        logger.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
        String result = HttpKit.readData(request);
        logger.info("支付通知密文 {}", result);
        // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
        String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
                wxPayV3Bean.getApiKey3(), getPlatformCertPath());
        logger.info("支付通知明文 {}", plainText);
        if (StringUtils.isNotEmpty(plainText)) {
            JSONObject jsonObject = JSONUtil.parseObj(plainText);
            String out_trade_no = jsonObject.getStr("out_trade_no");
            String transaction_id = jsonObject.getStr("transaction_id");
            Date success_time = jsonObject.getDate("success_time");
            String trade_state = jsonObject.getStr("trade_state");
            OrderManagement orderManagement = orderService.getOne(new LambdaQueryWrapper<OrderManagement>()
                    .eq(OrderManagement::getOrderNo, out_trade_no));
            orderManagement.setPayNo(transaction_id);
            orderManagement.setPayType(PayTypeEnum.WECHAT.getStatusCode());
            orderManagement.setPayTime(success_time);
            if ("SUCCESS".equals(trade_state)
                    && "支付成功".equals(jsonObject.getStr("trade_state_desc"))) {
                orderManagement.setStatus(OrderStatusEnum.DOING.getStatusCode());
            }
            PayLog payLog = new PayLog();
            payLog.setAmount(orderManagement.getAmount());
            payLog.setOrderNo(out_trade_no);
            payLog.setPayNo(orderManagement.getOrderNo());
            payLog.setPayType(PayTypeEnum.WECHAT.getStatusCode());
            payLog.setCustomerPhone(orderManagement.getCustomerPhone());
            payLog.setPayNo(transaction_id);
            payLog.setPayTime(success_time);
            payLog.setStatus(jsonObject.getStr("trade_state"));
            payLog.setCreateTime(new Date());

            switch (trade_state) // 判断交易结果
            {
                case "SUCCESS":
                    payLog.setMessage("支付成功");
                    break;
                case "REFUND":
                    payLog.setMessage("转入退款");
                    break;
                case "NOTPAY":
                    payLog.setMessage("未支付");
                    break;
                case "CLOSED":
                    payLog.setMessage("已关闭");
                    break;
                case "REVOKED":
                    payLog.setMessage("已撤销(付款码支付)");
                    break;
                case "USERPAYING":
                    payLog.setMessage("用户支付中(付款码支付)");
                    break;
                case "PAYERROR":
                    payLog.setMessage("支付失败(其他原因,如银行返回失败)");
                    break;
                default:
                    break;
            }
            orderService.updateById(orderManagement);
            payLogService.save(payLog);
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", jsonObject.getStr("trade_state_desc"));
        } else {
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "签名错误");
        }
        response.setHeader("Content-type", ContentType.JSON.toString());
        response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2.3重新封装的APP支付签名

public static Map<String, String> appPrepayIdCreateSign(String appId, String partnerId, String prepayId,
                                                        String keyPath) throws Exception {
    Map<String, String> packageParams = new HashMap<>(8);
    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
    String noncestr = PayKit.generateStr();
    packageParams.put("appid", appId);
    packageParams.put("partnerid", partnerId);
    packageParams.put("prepayid", prepayId);
    packageParams.put("package", "Sign=WXPay");
    packageParams.put("noncestr", noncestr);
    packageParams.put("timestamp", timestamp);
    ArrayList<String> list = new ArrayList<>();
    list.add(appId);
    list.add(timestamp);
    list.add(noncestr);
    list.add(prepayId);
    String packageSign = PayKit.createSign(
            PayKit.buildSignMessage(list),
            keyPath
    );
    packageParams.put("sign", packageSign);
    return packageParams;
}

到这微信APP支付就结束了!!!!

支付宝APP支付

支付宝开发文档: pay.weixin.qq.com/wiki/doc/ap…

5f0832cbbf3fa060226b377a633be42.png

1、相关配置

引入支付宝sdk包

<dependency>
   <groupId>com.alipay.sdk</groupId>
   <artifactId>alipay-sdk-java</artifactId>
   <version>4.13.0.ALL</version>
</dependency>
@Configuration
public class AlipayConfig {
    // 1.商户appid
    public static String APPID = "";

    // 2.私钥 pkcs8格式的
    public static String RSA_PRIVATE_KEY = "";

    // 3.支付宝公钥
    public static String ALIPAY_PUBLIC_KEY = "";

    // 4.服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参 数,必须外网可以正常访问
    public static String notify_url = "";

    // 6.请求支付宝的网关地址,测试环境可以使用沙箱环境
    public static String URL = "https://openapi.alipay.com/gateway.do";

    // 7.编码
    public static String CHARSET = "UTF-8";

    // 8.返回格式
    public static String FORMAT = "json";

    // 9.加密类型
    public static String SIGNTYPE = "RSA2";
}

1.1测试环境可以使用支付宝沙箱环境进行验证测试

image.png

1.2正式环境的相关配置需要登录支付宝商户端去配置

支付宝开放平台->控制台->开发设置中去获取

image.png

2、核心代码

2.1获取支付宝加签后台的订单信息字符串

//记录支付宝支付记录
        String orderInfo = "";
        try {
            // 实例化客户端
            AlipayClient alipayClient = AlipayUtils.getAlipayClient();
            // 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
            // SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
//            model.setBody("我是测试数据");
            model.setSubject(stockCar.getTheTitle());// 商品名称
            model.setOutTradeNo(payDto.getOrderNo());// 交易订单号
            model.setTimeoutExpress("24h");// 交易超时时间
            model.setTotalAmount(payDto.getAmount().toString());// 支付金额
            model.setProductCode("QUICK_MSECURITY_PAY");// 销售产品码(固定值)
            request.setBizModel(model);
            request.setNotifyUrl(AlipayConfig.notify_url);// 异步回调地址
//            request.setReturnUrl(AlipayConfig.notify_url);
            // 这里和普通的接口调用不同,使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
            logger.info("支付宝返回加签订单号:" + response.getBody());
            if (!response.isSuccess()) {
                throw new RuntimeException(response.getMsg());
            }
            orderInfo = response.getBody();
        } catch (AlipayApiException e) {
            e.printStackTrace();
            logger.error("===================支付宝支付接口,调用异常错误");
        }
        return orderInfo;

2.2支付宝支付成功后,异步请求该接口

这个接口中返回支付宝APP支付的真正结果,我们可以在这做一些业务逻辑,比如说状态的变更,日志记录等

public String notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
    System.out.println("异步回调开始:---------------------------------------------------------------");
    // 1.从支付宝回调的request域中取值
    // 获取支付宝返回的参数集合
    Map<String, String[]> aliParams = request.getParameterMap();
    // 用以存放转化后的参数集合
    Map<String, String> conversionParams = new HashMap<String, String>();
    for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext(); ) {
        String key = iter.next();
        String[] values = aliParams.get(key);
        String valueStr = "";
        for (int i = 0; i < values.length; i++) {
            valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
        }
        // 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
        conversionParams.put(key, valueStr);
    }
    String status = alipayService.notify(conversionParams);
    return status;
}
@Override
    public String notify(Map<String, String> conversionParams) throws Exception {
        logger.info("支付宝异步回调返回结果:" + conversionParams.toString());
        // 签名验证(对支付宝返回的数据验证,确定是支付宝返回的)
        boolean signVerified = false;
        try {
            // 调用SDK验证签名
            signVerified = AlipaySignature.rsaCheckV1(conversionParams, AlipayConfig.ALIPAY_PUBLIC_KEY,
                    AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);
        } catch (AlipayApiException e) {
            System.out.println(e.getErrCode());
            System.out.println(e.getErrMsg());
            throw new Exception("支付宝回调签名认证失败");
        }
        System.out.println(signVerified);
        if (signVerified) {
            // 验签通过
            // 获取需要保存的数据
            String appId = conversionParams.get("app_id");// 支付宝分配给开发者的应用Id
            String notifyTime = conversionParams.get("notify_time");// 通知时间:yyyy-MM-dd HH:mm:ss
            String gmtCreate = conversionParams.get("gmt_create");// 交易创建时间:yyyy-MM-dd HH:mm:ss
            String gmtPayment = conversionParams.get("gmt_payment");// 交易付款时间
            String gmtRefund = conversionParams.get("gmt_refund");// 交易退款时间
            String gmtClose = conversionParams.get("gmt_close");// 交易结束时间

            String tradeNo = conversionParams.get("trade_no");// 支付宝的交易号
            String outTradeNo = conversionParams.get("out_trade_no");// 获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
            String outBizNo = conversionParams.get("out_biz_no");// 商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
            String buyerLogonId = conversionParams.get("buyer_logon_id");// 买家支付宝账号
            String sellerId = conversionParams.get("seller_id");// 卖家支付宝用户号
            String sellerEmail = conversionParams.get("seller_email");// 卖家支付宝账号
            Double totalAmount = Double.parseDouble(conversionParams.get("total_amount"));// 订单金额:本次交易支付的订单金额,单位为人民币(元)
            String receiptAmount = conversionParams.get("receipt_amount");// 实收金额:商家在交易中实际收到的款项,单位为元
            String invoiceAmount = conversionParams.get("invoice_amount");// 开票金额:用户在交易中支付的可开发票的金额
            String buyerPayAmount = conversionParams.get("buyer_pay_amount");// 付款金额:用户在交易中支付的金额
            String tradeStatus = conversionParams.get("trade_status");// 获取交易状态
            // 支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id)
            OrderManagement orderManagement = orderService.getOne(new LambdaQueryWrapper<OrderManagement>()
                    .eq(OrderManagement::getOrderNo, outTradeNo));

            if (orderManagement != null && AlipayConfig.APPID.equals(appId) && orderManagement.getOrderNo().equals(outTradeNo)) {
                PayLog payLog = new PayLog();
                payLog.setAmount(orderManagement.getAmount());
                payLog.setOrderNo(orderManagement.getOrderNo());
                payLog.setPayType(PayTypeEnum.ALIPAY.getStatusCode());
                payLog.setCustomerPhone(orderManagement.getCustomerPhone());
                payLog.setPayNo(tradeNo);
//                SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
//                Date date2 = sdf2.parse(gmtClose);
                payLog.setPayTime(new Date());
                payLog.setStatus(tradeStatus);
                payLog.setMessage(conversionParams.get("msg"));
                payLog.setCreateTime(new Date());
                // 修改数据库支付宝订单表(因为要保存每次支付宝返回的信息到数据库里,以便以后查证)
                try {
                    orderManagement.setPayNo(tradeNo);
                    orderManagement.setPayType(PayTypeEnum.ALIPAY.getStatusCode());
                    orderManagement.setPayTime(new Date());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                switch (tradeStatus) // 判断交易结果
                {
                    case "TRADE_FINISHED": // 交易结束并不可退款
                        payLog.setMessage("交易结束并不可退款");
                        break;
                    case "TRADE_SUCCESS": // 交易支付成功
                        payLog.setMessage("交易支付成功");
                        orderManagement.setStatus(OrderStatusEnum.DOING.getStatusCode());
                        break;
                    case "TRADE_CLOSED": // 未付款交易超时关闭或支付完成后全额退款
                        payLog.setMessage("未付款交易超时关闭或支付完成后全额退款");
                        break;
                    case "WAIT_BUYER_PAY": // 交易创建并等待买家付款
                        payLog.setMessage("交易创建并等待买家付款");
                        break;
                    default:
                        break;
                }
                boolean returnResult = orderService.updateById(orderManagement);
                payLogService.save(payLog);
                if (tradeStatus.equals("TRADE_SUCCESS")) { // 只处理支付成功的订单: 修改交易表状态,支付成功

                    if (returnResult) {
                        return "success";
                    } else {
                        return "fail";
                    }
                } else {
                    return "fail";
                }
            } else {
                return "fail";
            }

        } else { // 验签不通过
            return "fail";
        }
    }

到这里支付宝APP支付的简单操作基本就完成了,当然还涉及一些退款、订单查询等操作,感兴趣的可以去官方文档中查看,第一次写文档有啥不足的地方求指教,谢谢!!!