SpringBoot+MyBatis集成微信支付實(shí)現(xiàn)示例
下面我將詳細(xì)介紹使用 Spring Boot + MyBatis 實(shí)現(xiàn)微信支付(JSAPI支付)的完整流程和代碼示例。
微信支付核心流程(JSAPI支付)
- 商戶系統(tǒng)生成訂單
- 獲取用戶OpenID(微信公眾號支付)
- 調(diào)用微信統(tǒng)一下單API
- 生成JSAPI調(diào)起支付參數(shù)
- 前端調(diào)起微信支付
- 微信異步通知支付結(jié)果
- 商戶處理支付結(jié)果更新訂單狀態(tài)
- 返回處理結(jié)果給微信
代碼實(shí)現(xiàn)示例
1. 添加依賴
<!-- pom.xml --> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis & MySQL --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 微信支付SDK --> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.7</version> </dependency> <!-- XML處理 --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
2. 微信支付配置類
@Configuration public class WxPayConfig { @Value("${wxpay.app_id}") private String appId; @Value("${wxpay.mch_id}") private String mchId; @Value("${wxpay.mch_key}") private String mchKey; @Value("${wxpay.notify_url}") private String notifyUrl; @Value("${wxpay.cert_path}") private String certPath; // 證書路徑(apiclient_cert.p12) @Value("${wxpay.api_v3_key}") private String apiV3Key; // 微信支付HttpClient @Bean public CloseableHttpClient wxPayHttpClient() throws Exception { // 加載商戶私鑰 PrivateKey merchantPrivateKey = getPrivateKey(); // 使用自動更新平臺證書的驗(yàn)證器 AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8)); // 初始化httpClient return WechatPayHttpClientBuilder.create() .withMerchant(mchId, mchSerialNo, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)) .build(); } // 獲取商戶私鑰 private PrivateKey getPrivateKey() throws Exception { InputStream inputStream = new ClassPathResource(certPath).getInputStream(); KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(inputStream, mchId.toCharArray()); return (PrivateKey) keyStore.getKey(mchId, mchId.toCharArray()); } // 微信支付服務(wù) @Bean public WxPayService wxPayService(CloseableHttpClient wxPayHttpClient) { WxPayConfig payConfig = new WxPayConfig(); payConfig.setAppId(appId); payConfig.setMchId(mchId); payConfig.setKey(mchKey); payConfig.setNotifyUrl(notifyUrl); payConfig.setApiV3Key(apiV3Key); return new WxPayServiceImpl(payConfig, wxPayHttpClient); } }
3. 實(shí)體類和Mapper
// 訂單實(shí)體 @Data public class Order { private Long id; private String orderNo; // 商戶訂單號 private BigDecimal amount;// 支付金額 private Integer status; // 0-待支付, 1-已支付 private LocalDateTime createTime; private String openid; // 微信用戶openid } // MyBatis Mapper @Mapper public interface OrderMapper { @Insert("INSERT INTO orders(order_no, amount, status, create_time, openid) " + "VALUES(#{orderNo}, #{amount}, 0, NOW(), #{openid})") @Options(useGeneratedKeys = true, keyProperty = "id") void insert(Order order); @Update("UPDATE orders SET status = #{status} WHERE order_no = #{orderNo}") void updateStatus(@Param("orderNo") String orderNo, @Param("status") int status); @Select("SELECT * FROM orders WHERE order_no = #{orderNo}") Order findByOrderNo(String orderNo); }
4. 支付服務(wù)類
@Service public class WxPayService { @Autowired private WxPayService wxPayService; @Autowired private OrderMapper orderMapper; // 創(chuàng)建支付訂單 public Map<String, String> createPayOrder(Order order) throws Exception { orderMapper.insert(order); // 保存訂單到數(shù)據(jù)庫 // 構(gòu)建統(tǒng)一下單請求 WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest(); request.setBody("商品支付"); request.setOutTradeNo(order.getOrderNo()); request.setTotalFee(order.getAmount().multiply(new BigDecimal(100)).intValue()); // 微信支付單位為分 request.setSpbillCreateIp("用戶IP"); // 實(shí)際獲取用戶IP request.setNotifyUrl(wxPayConfig.getNotifyUrl()); request.setTradeType("JSAPI"); request.setOpenid(order.getOpenid()); // 調(diào)用統(tǒng)一下單API WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request); // 生成JSAPI調(diào)起支付參數(shù) Map<String, String> payParams = new HashMap<>(); payParams.put("appId", appId); payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); payParams.put("nonceStr", WxPayUtil.generateNonceStr()); payParams.put("package", "prepay_id=" + result.getPrepayId()); payParams.put("signType", "RSA"); // 生成簽名 String sign = WxPayUtil.generateSignature(payParams, mchKey); payParams.put("paySign", sign); return payParams; } // 處理支付結(jié)果通知 public String handleNotify(HttpServletRequest request) { try { // 解析通知內(nèi)容 String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding()); WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlResult); // 驗(yàn)證簽名和業(yè)務(wù)結(jié)果 if ("SUCCESS".equals(notifyResult.getResultCode())) { // 更新訂單狀態(tài) String orderNo = notifyResult.getOutTradeNo(); orderMapper.updateStatus(orderNo, 1); // 更新為已支付 // 返回成功響應(yīng) return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; } } catch (Exception e) { // 記錄日志 } return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[處理失敗]]></return_msg></xml>"; } }
5. 控制器
@RestController @RequestMapping("/wxpay") public class WxPayController { @Autowired private WxPayService wxPayService; // 創(chuàng)建支付訂單(返回調(diào)起支付所需參數(shù)) @PostMapping("/create") public Map<String, String> createOrder(@RequestParam BigDecimal amount, @RequestParam String openid) throws Exception { Order order = new Order(); order.setOrderNo(UUID.randomUUID().toString().replace("-", "")); order.setAmount(amount); order.setOpenid(openid); return wxPayService.createPayOrder(order); } // 微信支付結(jié)果通知(需要公網(wǎng)可訪問) @PostMapping(value = "/notify", produces = "application/xml; charset=UTF-8") public String wxpayNotify(HttpServletRequest request) { return wxPayService.handleNotify(request); } // 查詢訂單狀態(tài) @GetMapping("/status") public String getOrderStatus(@RequestParam String orderNo) { Order order = orderMapper.findByOrderNo(orderNo); if (order == null) { return "訂單不存在"; } return order.getStatus() == 1 ? "已支付" : "未支付"; } }
6. 前端調(diào)用示例(Vue.js)
<template> <div> <button @click="createOrder">微信支付</button> </div> </template> <script> import axios from 'axios'; export default { methods: { async createOrder() { try { // 1. 獲取用戶openid(實(shí)際項(xiàng)目中需要通過OAuth2授權(quán)獲取) const openid = '用戶openid'; // 2. 創(chuàng)建訂單 const response = await axios.post('/wxpay/create', { amount: 100, // 支付金額(元) openid: openid }); // 3. 調(diào)起微信支付 const payParams = response.data; wx.chooseWXPay({ ...payParams, success: (res) => { console.log('支付成功', res); // 可跳轉(zhuǎn)到支付成功頁面 }, fail: (err) => { console.error('支付失敗', err); } }); } catch (error) { console.error('創(chuàng)建訂單失敗', error); } } } } </script>
7. 配置文件
# application.properties # 微信支付配置 wxpay.app_id=wx1234567890abcdef wxpay.mch_id=1234567890 wxpay.mch_key=your_mch_key wxpay.api_v3_key=your_api_v3_key wxpay.notify_url=http://your-domain.com/wxpay/notify wxpay.cert_path=classpath:cert/apiclient_cert.p12 # MySQL配置 spring.datasource.url=jdbc:mysql://localhost:3306/wxpay_demo spring.datasource.username=root spring.datasource.password=123456
關(guān)鍵流程說明
獲取用戶OpenID
- 微信公眾號支付需要獲取用戶的openid
- 通過微信OAuth2授權(quán)流程獲?。ㄐ柙谖⑿殴娞柡笈_配置授權(quán)域名)
調(diào)用統(tǒng)一下單API
- 使用
WxPayUnifiedOrderRequest
構(gòu)建請求 - 關(guān)鍵參數(shù):訂單號、金額(分)、openid、回調(diào)地址
- 返回 prepay_id(預(yù)支付交易會話標(biāo)識)
- 使用
生成JSAPI調(diào)起支付參數(shù)
- 包含 appId、timeStamp、nonceStr、package、signType
- 使用商戶密鑰生成簽名(paySign)
前端調(diào)起支付
- 使用微信JSAPI的 chooseWXPay 方法
- 傳入支付參數(shù)調(diào)起支付界面
接收異步通知
- 必須驗(yàn)證簽名(防止偽造請求)
- 檢查 result_code 是否為 SUCCESS
- 更新訂單狀態(tài)(注意處理冪等性)
安全注意事項(xiàng)
- 支付金額需與訂單金額比對(防止金額篡改)
- 敏感操作記錄日志
- 異步通知處理需要保證冪等性
微信支付與支付寶支付的區(qū)別
特性 | 微信支付 | 支付寶支付 |
---|---|---|
支付方式 | JSAPI、Native、App、H5等 | 電腦網(wǎng)站、手機(jī)網(wǎng)站、App等 |
金額單位 | 分 | 元 |
簽名算法 | HMAC-SHA256/RSA | RSA/RSA2 |
通知格式 | XML | Form表單/JSON |
證書要求 | 需要API證書 | 不需要證書 |
OpenID | 需要獲取用戶openid | 不需要用戶標(biāo)識 |
支付流程 | 需要前端調(diào)起支付 | 自動跳轉(zhuǎn)支付頁面 |
常見問題解決方案
支付金額單位錯誤
- 微信支付單位為分,支付寶單位為元
- 轉(zhuǎn)換公式:微信金額 = 支付寶金額 × 100
簽名驗(yàn)證失敗
- 檢查商戶密鑰是否正確
- 確認(rèn)簽名算法一致(微信默認(rèn)HMAC-SHA256)
- 驗(yàn)證參數(shù)是否完整且順序正確
異步通知處理
- 必須返回XML格式的響應(yīng)
- 處理速度要快(微信會在30秒內(nèi)重試)
- 保證冪等性(防止重復(fù)處理)
跨域問題
- 前端調(diào)起支付需要在微信內(nèi)置瀏覽器
- 確保公眾號JS接口安全域名配置正確
證書管理
- 定期更新API證書
- 證書文件妥善保管(不要提交到代碼倉庫)
實(shí)際開發(fā)中需要根據(jù)具體業(yè)務(wù)需求進(jìn)行調(diào)整,并注意支付安全相關(guān)事項(xiàng)。
到此這篇關(guān)于SpringBoot+MyBatis集成微信支付實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)SpringBoot MyBatis微信支付內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot實(shí)現(xiàn)微信支付接口調(diào)用及回調(diào)函數(shù)(商戶參數(shù)獲取)
- java?Springboot對接開發(fā)微信支付詳細(xì)流程
- SpringBoot對接小程序微信支付的實(shí)現(xiàn)
- Springboot整合微信支付(訂單過期取消及商戶主動查單)
- UniApp?+?SpringBoot?實(shí)現(xiàn)微信支付和退款功能
- SpringBoot實(shí)現(xiàn)整合微信支付方法詳解
- springboot對接微信支付的完整流程(附前后端代碼)
- 一篇文章帶你入門Springboot整合微信登錄與微信支付(附源碼)
- springboot整合微信支付sdk過程解析
相關(guān)文章
mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn)代碼
這篇文章主要介紹了mybatis-plus數(shù)據(jù)權(quán)限實(shí)現(xiàn),結(jié)合了mybatis-plus的插件方式,做出了自己的注解方式的數(shù)據(jù)權(quán)限,雖然可能存在一部分的局限性,但很好的解決了我們自己去解析SQL的功能,需要的朋友可以參考下2023-06-06IDEA 2020 無法啟動的解決辦法(啟動崩盤)附IDEA 2020 新功能
這篇文章主要介紹了IDEA 2020 無法啟動的解決辦法(啟動崩盤)附IDEA 2020 新功能,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Java自帶消息隊(duì)列Queue的使用教程詳細(xì)講解
這篇文章主要介紹了Java自帶消息隊(duì)列Queue的使用教程,Java中的queue類是隊(duì)列數(shù)據(jù)結(jié)構(gòu)管理類,在它里邊的元素可以按照添加它們的相同順序被移除,隊(duì)列通常以FIFO的方式排序各個元素,感興趣想要詳細(xì)了解可以參考下文2023-05-05Jmeter連接Mysql數(shù)據(jù)庫實(shí)現(xiàn)過程詳解
這篇文章主要介紹了Jmeter連接Mysql數(shù)據(jù)庫實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Idea如何使用System.getenv()獲取環(huán)境變量的值
文章介紹了如何在Java中使用`System.getenv()`方法讀取環(huán)境變量的值,并提供了兩種配置環(huán)境變量的方法:啟動項(xiàng)配置和系統(tǒng)環(huán)境變量配置,對于系統(tǒng)環(huán)境變量,文章特別指出需要重啟電腦或程序才能使其生效2024-11-11IntelliJ IDEA 常用設(shè)置(配置)吐血整理(首次安裝必需)
這篇文章主要介紹了IntelliJ IDEA 常用設(shè)置(配置)吐血整理(首次安裝必需),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06