首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

微信 JSAPI 支付整合完整指南(Spring Boot 版)

     編輯:Java 綜合教學

前言

微信支付文檔雖然詳盡,但實際整合時常遇到坑。本文整理一套基於 Spring Boot 的 JSAPI 支付完整實作,涵蓋配置、簽名、下單、前端喚起支付、回調處理等關鍵環節,助你快速落地。

核心配置類

1. Configure.java - 參數集中管理

public class Configure {
    // 微信支付商戶平台 API 密鑰 (32 位)
    private static String key = "YOUR_API_KEY";
    // 公眾號 AppID
    private static String appID = "YOUR_APP_ID";
    // 公眾號 AppSecret
    private static String appSecret = "YOUR_APP_SECRET";
    // 商戶號
    private static String mchID = "YOUR_MCH_ID";
    // 回調通知地址
    public static String NOTIFY_URL = "https://your.domain.com/wxpay/notify.htm";

    // 介面地址
    public static final String UNIFIEDORDER_API = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    public static final String TOKEN_API = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
    public static final String TICKET_API = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";
    public static final String OPENID_API = "https://api.weixin.qq.com/sns/oauth2/access_token";

    // Access Token 與 JSAPI Ticket 快取(含過期檢查)
    private static String token, jsapiTicket;
    private static Date tokenTime, jsapiTicketTime;

    // Getter/Setter 及過期檢查邏輯省略...
}

2. SignMD5.java - MD5 簽名工具

public class SignMD5 implements PasswordEncoder {
    public String encode(CharSequence raw) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(raw.toString().getBytes(StandardCharsets.UTF_8));
            return bytesToHex(digest);
        } catch (Exception e) { throw new RuntimeException(e); }
    }
    // bytesToHex、createNonceStr、createTimeStamp 等輔助方法省略...
}

3. 實體類

  • WechatPayModel:統一下單請求 XML 映射,含 sign() 簽名方法
  • WechatNotify:支付回調通知 XML 映射(JAXB 註解)

服務層實作

WechatPayService.java

@Service
public class WechatPayService {
    @Autowired private SignMD5 encoder;
    @Autowired private RestTemplate restTemplate;

    /** 統一下單 */
    public String unifiedOrder(String notifyUrl, String openId, String body, String totalFee, String outTradeNo) {
        WechatPayModel model = new WechatPayModel();
        model.setAppid(Configure.getAppid());
        model.setMch_id(Configure.getMchid());
        model.setNonce_str(encoder.createNonceStr());
        model.setBody(body);
        model.setOut_trade_no(outTradeNo);
        model.setTotal_fee(totalFee); // 單位:分
        model.setSpbill_create_ip(Configure.getIP());
        model.setNotify_url(notifyUrl);
        model.setTrade_type("JSAPI");
        model.setOpenid(openId);
        model.sign(encoder);
        return restTemplate.postForObject(Configure.UNIFIEDORDER_API, model, String.class);
    }

    /** 組裝前端 JSAPI 支付參數 */
    public void prepareJsapiPayment(Model model, String unifiedOrderXml, String currentUrl) {
        // 解析 XML 取得 prepay_id
        // 獲取 jsapi_ticket
        // 產生 config 簽名 (jsapi_ticket, noncestr, timestamp, url)
        // 產生 paySign 簽名 (appId, nonceStr, package, signType, timeStamp, key)
        // 將參數放入 Model 供 JSP 使用
    }

    /** 獲取 Access Token (快取 2 小時) */
    public String getAccessToken() { /* ... */ }

    /** 獲取 JSAPI Ticket (快取 2 小時) */
    public String getJsapiTicket() { /* ... */ }

    /** 透過 code 換取 OpenID */
    public String getOpenId(String code) { /* ... */ }
}

控制器層

WechatPayController.java

@Controller
@RequestMapping("/wxpay")
public class WechatPayController {
    @Autowired private WechatPayService payService;

    /** 支付頁入口:code -> openId -> 下單 -> 組裝參數 -> 跳轉支付頁 */
    @GetMapping("/payment.htm")
    public String payment(@RequestParam(required=false) String code, Model model, HttpServletRequest request) {
        String openId = payService.getOpenId(code);
        if (StringUtils.isEmpty(openId)) return "pay/wxpay_error";

        String outTradeNo = String.valueOf(System.currentTimeMillis());
        String xml = payService.unifiedOrder(Configure.NOTIFY_URL, openId, "商品描述", "1", outTradeNo);
        payService.prepareJsapiPayment(model, xml, getCurrentUrl(request));
        return "pay/wxpay"; // 對應 wechat_pay.jsp
    }

    /** 支付回調通知 */
    @PostMapping("/notify.htm")
    @ResponseBody
    public String notify(HttpServletRequest request) {
        WechatNotify notify = WechatUtil.parseNotify(request);
        if (notify != null && "SUCCESS".equals(notify.getResult_code()) && "SUCCESS".equals(notify.getReturn_code())) {
            // TODO: 業務邏輯(驗簽、更新訂單狀態、發貨等)
            return Configure.NOTIFY_SUCCESS;
        }
        return Configure.NOTIFY_FAIL;
    }
}

前端頁面

wechat_pay.jsp - 唤起微信支付

<script src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script>
function pay() {
    wx.config({
        debug: false,
        appId: '${start.appId}',
        timestamp: '${start.timestamp}',
        nonceStr: '${start.nonceStr}',
        signature: '${start.signature}',
        jsApiList: ['chooseWXPay']
    });

    wx.ready(function() {
        wx.chooseWXPay({
            timestamp: '${start.timestamp}',
            nonceStr: '${start.nonceStr}',
            package: '${pay.packageStr}', // prepay_id=xxx
            signType: '${pay.signType}',
            paySign: '${pay.paySign}',
            success: function(res) { location.href = '/success.htm'; },
            cancel: function(res) { location.href = '/cancel.htm'; },
            fail: function(res) { location.href = '/fail.htm'; }
        });
    });
    wx.error(function(res) { alert("配置失敗: " + JSON.stringify(res)); });
}
</script>
<body onload="pay()">正在喚起微信支付...</body>

常見坑點提醒

  1. 授權目錄:微信公眾平台需正確設定「網頁授權獲取用戶基本信息」的授權回調頁域名。
  2. 簽名順序:參數必須按 ASCII 碼升序拼接,最後加 &key=API_KEY 再 MD5。
  3. 金額單位:統一下單 total_fee 為「分」,非元。
  4. 回調驗簽:務必在回調中驗證簽名,防止偽造通知。
  5. HTTPS:生產環境回調地址必須為 HTTPS。

結語

完整代碼已上傳 GitHub(文末鏈接),可直接作為專案骨架。建議先在沙箱環境跑通流程,再逐步接入業務邏輯。祝你支付功能一次上線!

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved