SpringBoot+MyBatis集成微信支付實(shí)現(xiàn)示例
下面我將詳細(xì)介紹使用 Spring Boot + MyBatis 實(shí)現(xiàn)微信支付(JSAPI支付)的完整流程和代碼示例。
微信支付核心流程(JSAPI支付)
- 商戶系統(tǒng)生成訂單
- 獲取用戶OpenID(微信公眾號(hào)支付)
- 調(diào)用微信統(tǒng)一下單API
- 生成JSAPI調(diào)起支付參數(shù)
- 前端調(diào)起微信支付
- 微信異步通知支付結(jié)果
- 商戶處理支付結(jié)果更新訂單狀態(tài)
- 返回處理結(jié)果給微信
代碼實(shí)現(xiàn)示例
1. 添加依賴(lài)
<!-- 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. 微信支付配置類(lèi)
@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; // 證書(shū)路徑(apiclient_cert.p12)
@Value("${wxpay.api_v3_key}")
private String apiV3Key;
// 微信支付HttpClient
@Bean
public CloseableHttpClient wxPayHttpClient() throws Exception {
// 加載商戶私鑰
PrivateKey merchantPrivateKey = getPrivateKey();
// 使用自動(dòng)更新平臺(tái)證書(shū)的驗(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í)體類(lèi)和Mapper
// 訂單實(shí)體
@Data
public class Order {
private Long id;
private String orderNo; // 商戶訂單號(hào)
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ù)類(lèi)
@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ù)庫(kù)
// 構(gòu)建統(tǒng)一下單請(qǐ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)可訪問(wèn))
@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)目中需要通過(guò)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)到支付成功頁(yè)面
},
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)鍵流程說(shuō)明
獲取用戶OpenID
- 微信公眾號(hào)支付需要獲取用戶的openid
- 通過(guò)微信OAuth2授權(quán)流程獲?。ㄐ柙谖⑿殴娞?hào)后臺(tái)配置授權(quán)域名)
調(diào)用統(tǒng)一下單API
- 使用
WxPayUnifiedOrderRequest構(gòu)建請(qǐng)求 - 關(guān)鍵參數(shù):訂單號(hào)、金額(分)、openid、回調(diào)地址
- 返回 prepay_id(預(yù)支付交易會(huì)話標(biāo)識(shí))
- 使用
生成JSAPI調(diào)起支付參數(shù)
- 包含 appId、timeStamp、nonceStr、package、signType
- 使用商戶密鑰生成簽名(paySign)
前端調(diào)起支付
- 使用微信JSAPI的 chooseWXPay 方法
- 傳入支付參數(shù)調(diào)起支付界面
接收異步通知
- 必須驗(yàn)證簽名(防止偽造請(qǐng)求)
- 檢查 result_code 是否為 SUCCESS
- 更新訂單狀態(tài)(注意處理冪等性)
安全注意事項(xiàng)
- 支付金額需與訂單金額比對(duì)(防止金額篡改)
- 敏感操作記錄日志
- 異步通知處理需要保證冪等性
微信支付與支付寶支付的區(qū)別
| 特性 | 微信支付 | 支付寶支付 |
|---|---|---|
| 支付方式 | JSAPI、Native、App、H5等 | 電腦網(wǎng)站、手機(jī)網(wǎng)站、App等 |
| 金額單位 | 分 | 元 |
| 簽名算法 | HMAC-SHA256/RSA | RSA/RSA2 |
| 通知格式 | XML | Form表單/JSON |
| 證書(shū)要求 | 需要API證書(shū) | 不需要證書(shū) |
| OpenID | 需要獲取用戶openid | 不需要用戶標(biāo)識(shí) |
| 支付流程 | 需要前端調(diào)起支付 | 自動(dòng)跳轉(zhuǎn)支付頁(yè)面 |
常見(jiàn)問(wèn)題解決方案
支付金額單位錯(cuò)誤
- 微信支付單位為分,支付寶單位為元
- 轉(zhuǎn)換公式:微信金額 = 支付寶金額 × 100
簽名驗(yàn)證失敗
- 檢查商戶密鑰是否正確
- 確認(rèn)簽名算法一致(微信默認(rèn)HMAC-SHA256)
- 驗(yàn)證參數(shù)是否完整且順序正確
異步通知處理
- 必須返回XML格式的響應(yīng)
- 處理速度要快(微信會(huì)在30秒內(nèi)重試)
- 保證冪等性(防止重復(fù)處理)
跨域問(wèn)題
- 前端調(diào)起支付需要在微信內(nèi)置瀏覽器
- 確保公眾號(hào)JS接口安全域名配置正確
證書(shū)管理
- 定期更新API證書(shū)
- 證書(shū)文件妥善保管(不要提交到代碼倉(cāng)庫(kù))
實(shí)際開(kāi)發(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot實(shí)現(xiàn)微信支付接口調(diào)用及回調(diào)函數(shù)(商戶參數(shù)獲取)
- java?Springboot對(duì)接開(kāi)發(fā)微信支付詳細(xì)流程
- SpringBoot對(duì)接小程序微信支付的實(shí)現(xiàn)
- Springboot整合微信支付(訂單過(guò)期取消及商戶主動(dòng)查單)
- UniApp?+?SpringBoot?實(shí)現(xiàn)微信支付和退款功能
- SpringBoot實(shí)現(xiàn)整合微信支付方法詳解
- springboot對(duì)接微信支付的完整流程(附前后端代碼)
- 一篇文章帶你入門(mén)Springboot整合微信登錄與微信支付(附源碼)
- springboot整合微信支付sdk過(guò)程解析
相關(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-06
IDEA 2020 無(wú)法啟動(dòng)的解決辦法(啟動(dòng)崩盤(pán))附IDEA 2020 新功能
這篇文章主要介紹了IDEA 2020 無(wú)法啟動(dòng)的解決辦法(啟動(dòng)崩盤(pán))附IDEA 2020 新功能,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
比較常用UML類(lèi)圖幾種關(guān)系的小結(jié)
本文給大家總結(jié)了UML類(lèi)圖中幾種比較常用的關(guān)系小總結(jié),需要的朋友可以參考下2015-10-10
Java自帶消息隊(duì)列Queue的使用教程詳細(xì)講解
這篇文章主要介紹了Java自帶消息隊(duì)列Queue的使用教程,Java中的queue類(lèi)是隊(duì)列數(shù)據(jù)結(jié)構(gòu)管理類(lèi),在它里邊的元素可以按照添加它們的相同順序被移除,隊(duì)列通常以FIFO的方式排序各個(gè)元素,感興趣想要詳細(xì)了解可以參考下文2023-05-05
Jmeter連接Mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了Jmeter連接Mysql數(shù)據(jù)庫(kù)實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Idea如何使用System.getenv()獲取環(huán)境變量的值
文章介紹了如何在Java中使用`System.getenv()`方法讀取環(huán)境變量的值,并提供了兩種配置環(huán)境變量的方法:?jiǎn)?dòng)項(xiàng)配置和系統(tǒng)環(huán)境變量配置,對(duì)于系統(tǒng)環(huán)境變量,文章特別指出需要重啟電腦或程序才能使其生效2024-11-11
IntelliJ IDEA 常用設(shè)置(配置)吐血整理(首次安裝必需)
這篇文章主要介紹了IntelliJ IDEA 常用設(shè)置(配置)吐血整理(首次安裝必需),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

