微信支付文檔雖然詳盡,但實際整合時常遇到坑。本文整理一套基於 Spring Boot 的 JSAPI 支付完整實作,涵蓋配置、簽名、下單、前端喚起支付、回調處理等關鍵環節,助你快速落地。
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 及過期檢查邏輯省略...
}
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 等輔助方法省略...
}
WechatPayModel:統一下單請求 XML 映射,含 sign() 簽名方法WechatNotify:支付回調通知 XML 映射(JAXB 註解)@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) { /* ... */ }
}
@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;
}
}
<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>
&key=API_KEY 再 MD5。total_fee 為「分」,非元。完整代碼已上傳 GitHub(文末鏈接),可直接作為專案骨架。建議先在沙箱環境跑通流程,再逐步接入業務邏輯。祝你支付功能一次上線!