Java接入微信支付詳細(xì)實(shí)現(xiàn)步驟
一:主要流程
(1)前期準(zhǔn)備工作
①注冊(cè)認(rèn)證微信公眾號(hào)/小程序
②申請(qǐng)微信商戶號(hào)
③配置API安全
(2)環(huán)境搭建和主要流程理解
①引入依賴
②確定支付方式,分析其流程
(3)核心代碼開(kāi)發(fā)
①配置參數(shù)
②向微信發(fā)送請(qǐng)求
二:注意事項(xiàng)
(1)個(gè)人類型賬號(hào)無(wú)法開(kāi)通微信支付
(2)切勿泄露APIV3秘鑰
三:步驟實(shí)現(xiàn)
官方文檔(以Native為例):產(chǎn)品介紹_Native支付|微信支付商戶文檔中心
先查看具體流程
(1)先注冊(cè)對(duì)應(yīng)的公眾號(hào)/小程序
官網(wǎng)鏈接:https://mp.weixin.qq.com/
(2)認(rèn)證對(duì)應(yīng)的公眾號(hào)/小程序
①方法一:
②方法二:
(3)注冊(cè)商戶號(hào),開(kāi)通對(duì)應(yīng)的Native支付權(quán)限
官網(wǎng)鏈接:https://pay.weixin.qq.com/
開(kāi)通步驟指引鏈接:申請(qǐng)Native支付權(quán)限指引_Native支付|微信支付商戶文檔中心
(4)綁定商戶號(hào)與APPID
對(duì)應(yīng)指引鏈接:管理商戶號(hào)綁定的APPID賬號(hào)_Native支付|微信支付商戶文檔中心
(5)獲取對(duì)應(yīng)參數(shù)開(kāi)發(fā)
對(duì)應(yīng)指引鏈接:開(kāi)發(fā)必要參數(shù)說(shuō)明_通用規(guī)則|微信支付商戶文檔中心
(6)核心代碼開(kāi)發(fā)
①推薦使用官方SDK:https://github.com/wechatpay-apiv3/wechatpay-java
②引入依賴
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.4.13</version> <!-- 注意檢查最新版本 --> </dependency>
③代碼實(shí)現(xiàn)
配置類:
@Configuration public class WeChatPayConfig { @Value("${wechat.pay.mchid}") private String mchId; @Value("${wechat.pay.mchSerialNo}") // 你的商戶API證書序列號(hào) private String mchSerialNo; @Value("${wechat.pay.privateKeyPath}") // 你的.p12證書中的私鑰文件路徑 (.pem格式更佳) private String privateKeyPath; @Value("${wechat.pay.apiV3Key}") private String apiV3Key; @Value("${wechat.pay.certPath}") // 下載的微信平臺(tái)證書路徑 private String certPath; /** * 創(chuàng)建私鑰簽名對(duì)象 */ @Bean public PrivateKey getPrivateKey() throws Exception { return PemUtil.loadPrivateKey( new FileInputStream(privateKeyPath)); } /** * 創(chuàng)建自動(dòng)更新的Verifier (驗(yàn)證微信響應(yīng)簽名) */ @Bean public Verifier getVerifier() throws Exception { // 方式1: 使用本地已下載的平臺(tái)證書 (簡(jiǎn)單,需手動(dòng)更新) // X509Certificate certificate = PemUtil.loadCertificate(new FileInputStream(certPath)); // return new StaticVerifier(mchId, mchSerialNo, certificate); // 方式2: 使用自動(dòng)更新機(jī)制 (推薦) ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier( new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, getPrivateKey())), apiV3Key.getBytes(StandardCharsets.UTF_8)); return verifier; } /** * 創(chuàng)建HttpClient (用于調(diào)用微信API) */ @Bean public CloseableHttpClient getWxPayClient() throws Exception { return WechatPayHttpClientBuilder.create() .withMerchant(mchId, mchSerialNo, getPrivateKey()) .withValidator(new WechatPay2Validator(getVerifier())) .build(); } }
服務(wù)層(統(tǒng)一下單)示例與代碼(需調(diào)整)
@Override public String createNativeOrder(String outTradeNo, int totalFee, String body) { // ... 構(gòu)建請(qǐng)求參數(shù) (類似JSAPI,但去掉payer,增加scene_info等) json.put("appid", appid); // Native也需要appid json.put("mchid", mchId); json.put("description", body); json.put("out_trade_no", outTradeNo); json.put("notify_url", "https://yourdomain.com/api/wxpay/notify"); JSONObject amount = new JSONObject(); amount.put("total", totalFee); amount.put("currency", "CNY"); json.put("amount", amount); // 場(chǎng)景信息 (可選) JSONObject sceneInfo = new JSONObject(); sceneInfo.put("payer_client_ip", "用戶IP"); // 必須傳真實(shí)IP json.put("scene_info", sceneInfo); // 發(fā)送請(qǐng)求到 Native 接口 try { HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native"); // ... 同上設(shè)置請(qǐng)求頭和實(shí)體 // 解析響應(yīng),獲取 code_url JSONObject result = JSON.parseObject(responseString); return result.getString("code_url"); // 返回二維碼鏈接 } catch (Exception e) { throw new RuntimeException("創(chuàng)建Native訂單失敗", e); } }
④回調(diào)處理
注意:此路徑必須是公網(wǎng)能訪問(wèn)的路徑
@RestController @RequestMapping("/api/wxpay") public class WeChatPayController { @Autowired private WeChatPayService weChatPayService; @PostMapping("/create-jsapi-order") public ResponseEntity<Map<String, String>> createJsapiOrder(@RequestBody PayRequest request) { // 獲取用戶openid (通常由前端通過(guò)登錄態(tài)傳遞或后端通過(guò)code換取) String openid = getUserOpenidFromSessionOrToken(request.getToken()); Map<String, String> result = weChatPayService.createJsapiOrder( openid, request.getOutTradeNo(), request.getTotalFee(), request.getBody()); return ResponseEntity.ok(result); } @PostMapping("/notify") public ResponseEntity<String> handleNotify(@RequestBody String notifyData, HttpServletRequest request) { try { // 1. 驗(yàn)證通知的簽名和有效性 (SDK提供了工具類) // 這里需要解析請(qǐng)求頭中的 Wechatpay-Signature, Wechatpay-Nonce, Wechatpay-Timestamp String signature = request.getHeader("Wechatpay-Signature"); String nonce = request.getHeader("Wechatpay-Nonce"); String timestamp = request.getHeader("Wechatpay-Timestamp"); String body = notifyData; // 請(qǐng)求體 // 使用之前配置的Verifier來(lái)驗(yàn)證 // boolean isValid = verifier.verify(timestamp, nonce, body, signature); // if (!isValid) { // return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("簽名驗(yàn)證失敗"); // } // 2. 解密通知數(shù)據(jù) (APIv3通知體是加密的) AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8)); String plainText = aesUtil.decryptToString( nonce.getBytes(StandardCharsets.UTF_8), associatedData.getBytes(StandardCharsets.UTF_8), // 關(guān)聯(lián)數(shù)據(jù) ciphertext.getBytes(StandardCharsets.UTF_8)); // 密文 // 3. 解析解密后的JSON數(shù)據(jù) JSONObject notifyDataObj = JSON.parseObject(plainText); String eventType = notifyDataObj.getString("event_type"); JSONObject resource = notifyDataObj.getJSONObject("resource"); String ciphertext = resource.getString("ciphertext"); // 加密的數(shù)據(jù)包 String associatedData = resource.getString("associated_data"); String nonce = resource.getString("nonce"); // 再次解密ciphertext得到最終的訂單信息 String orderInfo = aesUtil.decryptToString(nonce.getBytes(), associatedData.getBytes(), ciphertext.getBytes()); JSONObject orderData = JSON.parseObject(orderInfo); String outTradeNo = orderData.getString("out_trade_no"); String transactionId = orderData.getString("transaction_id"); String tradeState = orderData.getString("trade_state"); // SUCCESS為支付成功 // 4. 業(yè)務(wù)處理 if ("SUCCESS".equals(tradeState)) { // TODO: 更新你的數(shù)據(jù)庫(kù)訂單狀態(tài)為"已支付" // TODO: 執(zhí)行發(fā)貨、積分發(fā)放等后續(xù)業(yè)務(wù)邏輯 // TODO: 記錄日志 } else if ("CLOSED".equals(tradeState) || "REVOKED".equals(tradeState)) { // 支付失敗或關(guān)閉 } // 5. 返回成功響應(yīng) (必須是200且內(nèi)容為"success") return ResponseEntity.ok().body("{\"code\":\"SUCCESS\",\"message\":\"成功\"}"); } catch (Exception e) { // 記錄詳細(xì)的錯(cuò)誤日志 log.error("處理微信支付通知失敗", e); // 返回失敗,微信會(huì)在一段時(shí)間后重試 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(""); } } }
工具類
public class WeChatPayUtil { // 生成JSAPI調(diào)用所需參數(shù)的簽名 (注意:這是RSA-SHA256,不是APIv3的HMAC-SHA256) public static String sign(Map<String, String> params, String apiKey) { // 1. 參數(shù)按key ASCII排序 List<String> keys = new ArrayList<>(params.keySet()); Collections.sort(keys); StringBuilder sb = new StringBuilder(); for (String key : keys) { String value = params.get(key); if (value != null && !value.trim().isEmpty() && !"sign".equals(key)) { sb.append(key).append("=").append(value).append("&"); } } sb.append("key=").append(apiKey); // 2. 計(jì)算MD5 (舊版) 或 HMAC-SHA256 (新版推薦,但JSAPI仍多用MD5?實(shí)際看文檔) // 注意:微信JSAPI目前主流仍是MD5,但請(qǐng)查閱最新官方文檔確認(rèn)! String stringA = sb.toString(); String sign = DigestUtils.md5Hex(stringA).toUpperCase(); return sign; } }
四:總結(jié)
對(duì)接微信支付主要繁瑣的是前期商戶號(hào)、證書、配置,需仔細(xì)閱讀文檔,后續(xù)對(duì)接SDK非常簡(jiǎn)單,注意處理冪等性
到此這篇關(guān)于Java接入微信支付的文章就介紹到這了,更多相關(guān)Java接入微信支付內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Guava?Retryer實(shí)現(xiàn)接口重試的示例
本文主要介紹了Guava?Retryer實(shí)現(xiàn)接口重試的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12Java Mail與Apache Mail發(fā)送郵件示例
這篇文章主要介紹了Java Mail與Apache Mail發(fā)送郵件示例的相關(guān)資料,需要的朋友可以參考下2014-10-10SpringMVC基于注解方式實(shí)現(xiàn)上傳下載
本文主要介紹了SpringMVC基于注解方式實(shí)現(xiàn)上傳下載,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04