微信支付java版(含視頻講解)


1.背景

實際開發中用到微信支付的概率非常大,

至於為什么這里不必要我多少......

微信支付大體需要對接的核心接口有

其實大部分支付都是這些,就像上一節我們講的支付寶支付一樣

這里以常用的H5支付為例,其他的都是差不多的....

值得注意的時候,微信支付最常用的就是H5支付和JSAPI支付,這兩者的主要區別在於

H5支付必須在非微信瀏覽器打開;

JSAPI支付只能在微信的瀏覽器打開;

微信支付官方文檔:https://pay.weixin.qq.com/wiki/doc/api/index.html

1.統一下單
2.查詢訂單
3.支付結果通知
4.申請退款
5.查詢退款
6.下載對賬單

2.需要准備的環境

微信支付目前還沒有沙箱測試環境,要開開發支付功能必須要有企業資質申請公眾號和商戶號才可以

主要的是:

公眾號appid

商戶號

商戶號的apiSercet密碼

商戶號的證書

商戶號上綁定好的回調域名

3.開發步驟

3.1.統一下單

代碼如下:

package com.ldp.user.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.ldp.user.common.constant.PayConstant;
import com.ldp.user.common.constant.PayEnum;
import com.ldp.user.common.constant.TestData;
import com.ldp.user.common.constant.WeChatConstant;
import com.ldp.user.common.exception.ParamException;
import com.ldp.user.common.exception.WeChatException;
import com.ldp.user.common.util.wechat.WXPayUtil;
import com.ldp.user.entity.bo.PayBO;
import com.ldp.user.entity.vo.PayVO;
import com.ldp.user.service.IPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Copyright (C) XXXXXX科技股份技有限公司
 * @Author: lidongping
 * @Date: 2021-01-07 18:47
 * @Description:
 */
@Service
@Slf4j
public class WeChatPayService implements IPayService {
    @Override
    public String getPayType() {
        return PayConstant.WECHAT;
    }

    @Override
    public PayVO getPayInfo(PayBO payBO) throws Exception {
        // 根據訂單號查詢訂單數據 和 支付賬號數據
        // 這里測試 直接將數據寫在代碼里
        payBO.setAppId(TestData.WX_APP_ID)
                .setMerchantId(TestData.WX_MERCHANT_ID)
                .setApiSecretKey(TestData.WX_SECRET)
                .setPayTitle("測試支付")
                .setPayMoney(0.01)
                .setProductNo("P001")
                .setAsynchronousNotifyUrl("http://lidongping.free.idcfengye.com/api/async/wechat/payment")
                .setOpenId("ozwLvwah3xtA93csvZTWr0wON_IU");
        // 以下是封裝微信支付數據
        Map dic = new HashMap<String, String>();
        dic.put("appid", payBO.getAppId());
        dic.put("mch_id", payBO.getMerchantId());
        String nonceStr = RandomUtil.randomString(30);
        dic.put("nonce_str", nonceStr);
        dic.put("body", payBO.getPayTitle());
        dic.put("out_trade_no", payBO.getOrderNo());
        String amount = NumberUtil.roundStr(payBO.getPayMoney() * 100, 0);
        dic.put("total_fee", amount);
        dic.put("spbill_create_ip", payBO.getUserIp());
        dic.put("notify_url", payBO.getAsynchronousNotifyUrl());
        // 生成的支付信息一小時內有效
        dic.put("time_expire", DateUtil.format(DateUtil.offsetHour(new Date(), 1), "yyyyMMddHHmmss"));
        // 根據類型請求支付
        PayEnum payType = PayEnum.valueOf(payBO.getPayType());
        switch (payType) {
            case WECHAT_QR:
                dic.put("trade_type", "NATIVE");
                dic.put("product_id", payBO.getProductNo());
                break;
            case WECHAT_JSAPI:
                dic.put("trade_type", "JSAPI");
                if (StrUtil.isEmpty(payBO.getOpenId())) {
                    throw new ParamException("openid 為空");
                }
                dic.put("openid", payBO.getOpenId());
                break;
            case WECHAT_H5:
                dic.put("trade_type", "MWEB");
                break;
            default:
                throw new ParamException("沒有對應的支付方式");
        }
        log.info("簽名前:" + JSON.toJSONString(dic));
        dic.put("sign", WXPayUtil.generateSignature(dic, payBO.getApiSecretKey()));
        String dataXml = WXPayUtil.mapToXml(dic);
        log.info("微信請求下單url:" + WeChatConstant.PAY_URL);
        log.info("微信請求下單參數:" + dataXml);
        String resp = HttpUtil.post(WeChatConstant.PAY_URL, dataXml, 60 * 1000);
        log.info("微信請求下單返回:" + resp);
        Map<String, String> resMap = WXPayUtil.xmlToMap(resp);
        // 請求失敗
        if (!resMap.get("return_code").equalsIgnoreCase(WeChatConstant.SUCCESS)) {
            throw new WeChatException(resMap.get("return_msg"));
        }
        // 下單失敗
        if (!resMap.get("result_code").equalsIgnoreCase(WeChatConstant.SUCCESS)) {
            throw new WeChatException(resMap.get("err_code_des"));
        }
        // 校驗支付返回結果簽名
        if (!checkSign(resMap, payBO.getApiSecretKey())) {
            throw new WeChatException("驗證簽名失敗");
        }
        // 處理返回結果
        PayVO payVO = new PayVO();
        payVO.setOrderNo(payBO.getOrderNo());
        switch (payType) {
            case WECHAT_QR:
                payVO.setPayInfo(resMap.get("code_url"));
                break;
            case WECHAT_JSAPI:
                Map jsapi = new HashMap<String, String>();
                jsapi.put("appId", payBO.getAppId());
                // 當前時間秒
                jsapi.put("timeStamp", System.currentTimeMillis() / 1000 + "");
                jsapi.put("nonceStr", nonceStr);
                jsapi.put("package", "prepay_id=" + resMap.get("prepay_id"));
                jsapi.put("signType", "MD5");
                jsapi.put("paySign", WXPayUtil.generateSignature(jsapi, payBO.getApiSecretKey()));
                payVO.setPayInfo(jsapi);
                break;
            default:
                payVO.setPayInfo(resMap.get("mweb_url"));
        }
        return payVO;
    }

    /**
     * 微信響應數據簽名檢查
     *
     * @param map
     * @param key
     * @return
     * @throws Exception
     */
    private static boolean checkSign(Map<String, String> map, String key) throws Exception {
        String sign = map.get("sign");
        map.remove("sign");
        String orign = WXPayUtil.generateSignature(map, key);
        return orign.equalsIgnoreCase(sign);
    }
}
View Code

3.2.異步通知

代碼如下:

package com.ldp.user.controller;

import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.ldp.user.common.constant.TestData;
import com.ldp.user.common.constant.WeChatConstant;
import com.ldp.user.common.util.RedisUtil;
import com.ldp.user.common.util.wechat.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/10 11:18
 * @description
 */
@RestController
@Slf4j
public class WeChatController {
    /**
     * 注意:
     * 1.這里是只支付寶微信xml的參數接收,微信最近也提供了json的方式,本質上都是一樣的
     * 2.xml參數回調文檔:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=8
     *
     * @param request
     * @return
     * @throws Exception
     */
    @PostMapping("/async/wechat/payment")
    public String notifyPaymentResult(HttpServletRequest request) throws Exception {
        log.info("wechat異步回調....");
        String paramXml = ServletUtil.getBody(request);
        Map<String, String> paramsMap = WXPayUtil.xmlToMap(paramXml);
        //解析參數
        String returnCode = paramsMap.get("return_code");
        String tradeStatus = paramsMap.get("result_code");
        String appid = paramsMap.get("appid");
        String totalFee = paramsMap.get("total_fee");
        String orderNo = paramsMap.get("out_trade_no");
        if (!WeChatConstant.SUCCESS.equals(returnCode)) {
            log.error("回調狀態錯誤:returnCode={}", returnCode);
            return WeChatConstant.RETURN_FAIL;
        }
        // TODO 這里實際開發時根據訂單號獲取apiSecret秘鑰
        String apiSecret = TestData.WX_SECRET;
        // 驗簽
        if (!WXPayUtil.isSignatureValid(paramsMap, apiSecret)) {
            log.error("驗簽失敗");
            return WeChatConstant.RETURN_FAIL;
        }
        if (!WeChatConstant.SUCCESS.equals(tradeStatus)) {
            log.error("支付狀態失敗:tradeStatus={}", tradeStatus);
            return WeChatConstant.RETURN_FAIL;
        }
        // TODO 更新數據庫訂單數據和緩存 ... 這里略
        // 這里只是模擬方放入緩存,便於前端查詢訂單  60 * 60 * 2L 表示緩存2小時
        RedisUtil.set(orderNo, JSON.toJSON(paramsMap), 60 * 60 * 2L);
        log.info("異步處理成功.............");
        return WeChatConstant.RETURN_SUCCESS;
    }
}
View Code

3.3.測試

獲取微信支付信息

@Test
    void getPayInfo() {
        String url = urlLocal + "/userOrder/payInfo";
        System.out.println("請求地址:" + url);
        HttpRequest request = HttpUtil.createRequest(Method.GET, url);
        Map<String, Object> map = new HashMap<>();
        map.put("orderNo", "NO" + System.currentTimeMillis());
        // 微信支付 wechat  支付寶 alipay
        map.put("payCategory", "wechat");
        // 201 jsapi支付 , 202 微信H5  101支付寶H5
        map.put("payType", "202");
        request.form(map);
        System.out.println("請求參數:" + map);
        request.header("X-Real_IP", "192.168.5.195");
        request.setConnectionTimeout(60 * 1000);
        String respone = request.execute().body();
        System.out.println("響應結果:" + respone);
    }
View Code

測試結果

請求地址:http://localhost:8080/api/userOrder/payInfo
請求參數:{payCategory=wechat, orderNo=NO1610250837438, payType=202}
響應結果:{"message":"success","code":100,"data":{"orderNo":"NO1610250837438","payInfo":"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx10115509234051b333adbe01d032b80000&package=2821154835"}}

從響應的結果看,我們已經拿到了payInfo,也就是跳轉到微信的url

正常情況下,只要在非微信瀏覽器打開就可以了,但是微信那邊對請求來源的域名做了校驗,我們這里測試不是很方便,就不繼續演示下去了

因為實際開發中只要走到這一步,對於開發人員來說代碼就已經合格了,如果只會支付不了,那多半是微信商戶號上的配置有問題....

學習我們就到這里,如果在實際開發中有問題可以咨詢我,共同交流技術!

4.總結

1.這里只是給大家演示了統一下單和支付回調的代碼,也是微信支付的核心代碼,相信只要這調通了,其他的接口類似!

2.為了更好讓大家快速掌握微信支付,這篇博客已經做了配套的視頻講解,大家可以結合視頻講解學習,或者單獨問我!

完美!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM