Springboot整合微信支付

请添加图片描述

1.接口介绍
1.JSAPI下单

JSAPI下单 - JSAPI支付 | 微信支付商户文档中心 (qq.com)

商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后生成交易串调起支付。

支持商户:普通商户

请求URL:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

请求方式:POST

请求参数:

{
    "appid" : "wxd678efh567hg6787",    // 小程序id
    "mchid" : "1230000109",            // 商户号
    "description" : "Image形象店-深圳腾大-QQ公仔",       // 商品描述
    "out_trade_no" : "1217752501201407033233368018",   // 商户系统内容订单号,唯一
    "time_expire" : "2018-06-08T10:34:56+08:00",
    "attach" : "自定义数据说明",
    "notify_url" : " https://www.weixin.qq.com/wxpay/pay.php", // 接收微信支付结果通知的回调地址
    "goods_tag" : "WXG",
    "support_fapiao" : true,
    "amount" : {        // 订单金额信息
      "total" : 100,
      "currency" : "CNY"
    },
    "payer" : {         // 支付者信息
      "openid" : "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o\t"
    }
}

返回参数:

{
  // 预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时
  "prepay_id" : "wx201410272009395522657a690389285100"
}
2.小程序调起支付

小程序调起支付 - 小程序支付 | 微信支付商户文档中心 (qq.com)

通过JSAPI下单接口获取到发起支付的必要参数 prepay_id,然后使用微信支付提供的小程序方法调起小程序支付。

支付商户:普通商户

接口名称:wx.requestPayment,详细说明支付 / wx.requestPayment (qq.com)

请求参数:

{
    // 时间戳
    "timeStamp": "1414561699",
    // 随机字符串
    "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
    // 下单接口返回的prepay_id参数值,格式为 prepay_id=***
    "package": "prepay_id=wx201410272009395522657a690389285100",
    // 签名类型,仅支持 RSA 
    "signType": "RSA",
    // 签名,使用字段appid、timeStamp、nonceStr、package 计算得出的签名值
    "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==",
    "success":function(res){},
    "fail":function(res){},
    "complete":function(res){}
}

回调结果:

回调类型errMsg说明
successrequestPayment:ok调用支付成功
failrequestPayment:fail cancel用户取消支付
failrequestPayment:fail (detail message)调用支付失败,其中 detail message 为后台返回的详细失败原因
2.准备工作
  1. 接入微信支付
    1. 提交资料:在线提交营业执照、身份证、银行账户等基本信息,并按指引完成账户验证
    2. 签署协议:微信支付团队会在1-2个工作日内完成审核,审核通过后请在线签约,即可体验各项产品能力
    3. 绑定场景:如需自行开发完成收款,需将商户号与APPID进行绑定,或开通微信收款商业版(免开发)完成收款
  2. 获取微信平台支付证书、商户私钥文件
  3. 获取临时域名:支付成功后微信服务通过该域名回调我们的程序
3.代码配置
  1. 在 application.yml 文件中进行微信支付相关配置:

    sky:
      wechat:
        appid: 微信小程序的 appid
        secret: 微信小程序的密钥
        mchid: 商户号
        mchSerialNo: 商户API证书的证书序列号
        privateKeyFilePath: 商户私钥文件
        apiV3Key: 证书解密的密钥,在商户平台配置
        weChatPayCertFilePath: 平台证书
        notifyUrl: 支付成功的回调地址
        refundNotifyUrl: 退款成功的回调地址
    
  2. 引入微信支付依赖

    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-apache-httpclient</artifactId>
        <version>0.4.8</version>
    </dependency>
    
  3. 创建微信支付配置属性类

    @Component
    @ConfigurationProperties(prefix = "sky.wechat")
    @Data
    public class WeChatProperties {
    
        private String appid; //小程序的appid
        private String secret; //小程序的秘钥
        private String mchid; //商户号
        private String mchSerialNo; //商户API证书的证书序列号
        private String privateKeyFilePath; //商户私钥文件
        private String apiV3Key; //证书解密的密钥
        private String weChatPayCertFilePath; //平台证书
        private String notifyUrl; //支付成功的回调地址
        private String refundNotifyUrl; //退款成功的回调地址
    
    }
    
  4. 创建微信支付工具类实现微信支付的相关功能

    @Component
    public class WeChatPayUtil {
    
        //微信支付下单接口地址
        public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
    
        //申请退款接口地址
        public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
    
        @Autowired
        private WeChatProperties weChatProperties;
    
        /**
         * 获取调用微信接口的客户端工具对象
         *
         * @return
         */
        private CloseableHttpClient getClient() {
            PrivateKey merchantPrivateKey = null;
            try {
                //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题
                merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
                //加载平台证书文件
                X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
                //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉
                List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);
    
                WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                        .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
                        .withWechatPay(wechatPayCertificates);
    
                // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
                CloseableHttpClient httpClient = builder.build();
                return httpClient;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 发送post方式请求
         *
         * @param url
         * @param body
         * @return
         */
        private String post(String url, String body) throws Exception {
            CloseableHttpClient httpClient = getClient();
    
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
            httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
            httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
            httpPost.setEntity(new StringEntity(body, "UTF-8"));
    
            CloseableHttpResponse response = httpClient.execute(httpPost);
            try {
                String bodyAsString = EntityUtils.toString(response.getEntity());
                return bodyAsString;
            } finally {
                httpClient.close();
                response.close();
            }
        }
    
        /**
         * 发送get方式请求
         *
         * @param url
         * @return
         */
        private String get(String url) throws Exception {
            CloseableHttpClient httpClient = getClient();
    
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
            httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
            httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
    
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                String bodyAsString = EntityUtils.toString(response.getEntity());
                return bodyAsString;
            } finally {
                httpClient.close();
                response.close();
            }
        }
    
        /**
         * jsapi下单
         *
         * @param orderNum    商户订单号
         * @param total       总金额
         * @param description 商品描述
         * @param openid      微信用户的openid
         * @return
         */
        private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("appid", weChatProperties.getAppid());
            jsonObject.put("mchid", weChatProperties.getMchid());
            jsonObject.put("description", description);
            jsonObject.put("out_trade_no", orderNum);
            jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
    
            JSONObject amount = new JSONObject();
            amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
            amount.put("currency", "CNY");
    
            jsonObject.put("amount", amount);
    
            JSONObject payer = new JSONObject();
            payer.put("openid", openid);
    
            jsonObject.put("payer", payer);
    
            String body = jsonObject.toJSONString();
            return post(JSAPI, body);
        }
    
        /**
         * 小程序支付
         *
         * @param orderNum    商户订单号
         * @param total       金额,单位 元
         * @param description 商品描述
         * @param openid      微信用户的openid
         * @return
         */
        public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
            //统一下单,生成预支付交易单
            String bodyAsString = jsapi(orderNum, total, description, openid);
            //解析返回结果
            JSONObject jsonObject = JSON.parseObject(bodyAsString);
            System.out.println(jsonObject);
    
            String prepayId = jsonObject.getString("prepay_id");
            if (prepayId != null) {
                String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
                String nonceStr = RandomStringUtils.randomNumeric(32);
                ArrayList<Object> list = new ArrayList<>();
                list.add(weChatProperties.getAppid());
                list.add(timeStamp);
                list.add(nonceStr);
                list.add("prepay_id=" + prepayId);
                //二次签名,调起支付需要重新签名
                StringBuilder stringBuilder = new StringBuilder();
                for (Object o : list) {
                    stringBuilder.append(o).append("\n");
                }
                String signMessage = stringBuilder.toString();
                byte[] message = signMessage.getBytes();
    
                Signature signature = Signature.getInstance("SHA256withRSA");
                signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
                signature.update(message);
                String packageSign = Base64.getEncoder().encodeToString(signature.sign());
    
                //构造数据给微信小程序,用于调起微信支付
                JSONObject jo = new JSONObject();
                jo.put("timeStamp", timeStamp);
                jo.put("nonceStr", nonceStr);
                jo.put("package", "prepay_id=" + prepayId);
                jo.put("signType", "RSA");
                jo.put("paySign", packageSign);
    
                return jo;
            }
            return jsonObject;
        }
    
        /**
         * 申请退款
         *
         * @param outTradeNo    商户订单号
         * @param outRefundNo   商户退款单号
         * @param refund        退款金额
         * @param total         原订单金额
         * @return
         */
        public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("out_trade_no", outTradeNo);
            jsonObject.put("out_refund_no", outRefundNo);
    
            JSONObject amount = new JSONObject();
            amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
            amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
            amount.put("currency", "CNY");
    
            jsonObject.put("amount", amount);
            jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());
    
            String body = jsonObject.toJSONString();
    
            //调用申请退款接口
            return post(REFUNDS, body);
        }
    }
    
  5. 创建一个 PayNotifyController 类来实现支付回调相关接口

    @RestController
    @RequestMapping("/notify")
    @Slf4j
    public class PayNotifyController {
        @Autowired
        private OrderService orderService;
        @Autowired
        private WeChatProperties weChatProperties;
    
        /**
         * 支付成功回调
         *
         * @param request
         */
        @RequestMapping("/paySuccess")
        public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //读取数据
            String body = readData(request);
            log.info("支付成功回调:{}", body);
    
            //数据解密
            String plainText = decryptData(body);
            log.info("解密后的文本:{}", plainText);
    
            JSONObject jsonObject = JSON.parseObject(plainText);
            String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
            String transactionId = jsonObject.getString("transaction_id");//微信支付交易号
    
            log.info("商户平台订单号:{}", outTradeNo);
            log.info("微信支付交易号:{}", transactionId);
    
            //业务处理,修改订单状态、来单提醒
            orderService.paySuccess(outTradeNo);
    
            //给微信响应
            responseToWeixin(response);
        }
    
        /**
         * 读取数据
         *
         * @param request
         * @return
         * @throws Exception
         */
        private String readData(HttpServletRequest request) throws Exception {
            BufferedReader reader = request.getReader();
            StringBuilder result = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        }
    
        /**
         * 数据解密
         *
         * @param body
         * @return
         * @throws Exception
         */
        private String decryptData(String body) throws Exception {
            JSONObject resultObject = JSON.parseObject(body);
            JSONObject resource = resultObject.getJSONObject("resource");
            String ciphertext = resource.getString("ciphertext");
            String nonce = resource.getString("nonce");
            String associatedData = resource.getString("associated_data");
    
            AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
            //密文解密
            String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                    nonce.getBytes(StandardCharsets.UTF_8),
                    ciphertext);
    
            return plainText;
        }
    
        /**
         * 给微信响应
         * @param response
         */
        private void responseToWeixin(HttpServletResponse response) throws Exception{
            response.setStatus(200);
            HashMap<Object, Object> map = new HashMap<>();
            map.put("code", "SUCCESS");
            map.put("message", "SUCCESS");
            response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
            response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        }
    }
    
  6. 创建一个订单支付VO类,来保存预支付交易单

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class OrderPaymentVO implements Serializable {
    
        private String nonceStr; //随机字符串
        private String paySign; //签名
        private String timeStamp; //时间戳
        private String signType; //签名算法
        private String packageStr; //统一下单接口返回的 prepay_id 参数值
    
    }
    
  7. 创建 OrdersPaymentDTO 类来传递数据

    @Data
    public class OrdersPaymentDTO implements Serializable {
        //订单号
        private String orderNumber;
    
        //付款方式
        private Integer payMethod;
    }
    
  8. 在 OrderController 中添加订单支付方法,此方法会返回预支付交易单

    @PutMapping("/payment")
    @ApiOperation("订单支付")
    public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
    	log.info("订单支付:{}", ordersPaymentDTO);
    	OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);
    	log.info("生成预支付交易单:{}", orderPaymentVO);
    	return Result.success(orderPaymentVO);
    }
    
  9. 在 OrderServiceImpl 中实现订单支付方法,支付成功后订单状态修改方法

    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.getById(userId);
    
        //调用微信支付接口,生成预支付交易单
        JSONObject jsonObject = weChatPayUtil.pay(
            ordersPaymentDTO.getOrderNumber(), //商户订单号,用户下单时生成
            new BigDecimal(0.01), //支付金额,单位 元
            "苍穹外卖订单", //商品描述
            user.getOpenid() //微信用户的openid
        );
    
        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
            throw new OrderBusinessException("该订单已支付");
        }
    
        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));
    
        return vo;
    }
    
    public void paySuccess(String outTradeNo) {
    
        // 根据订单号查询订单
        Orders ordersDB = orderMapper.getByNumber(outTradeNo);
    
        // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
        Orders orders = Orders.builder()
            .id(ordersDB.getId())
            .status(Orders.TO_BE_CONFIRMED)
            .payStatus(Orders.PAID)
            .checkoutTime(LocalDateTime.now())
            .build();
    
        orderMapper.update(orders);
    }
    
4.参考

内容来自黑马程序员的苍穹外卖项目中的微信支付部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值