Spring Boot 微信小程序接入微信支付功能
1、導(dǎo)入相關(guān)依賴 pom.yml
<!-- 微信支付 SDK --> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
2、文件配置微信公眾號(hào)的基礎(chǔ)信息 application.yml
# 微信支付配置 notifyUrl:微信支付異步回調(diào)地址 pay: appId: #小程序應(yīng)用id(小程序平臺(tái)mp.weixin.qq.com) apiKey: #商戶私鑰key(微信商戶平臺(tái)(pay.weixin.qq.com)-->賬戶設(shè)置-->API安全-->密鑰設(shè)置) mchId: #商戶號(hào)(微信商戶平臺(tái)(pay.weixin.qq.com)-->產(chǎn)品中心-->開發(fā)配置-->商戶號(hào)) appSecret: #小程序密鑰(小程序平臺(tái)mp.weixin.qq.com) notifyUrl: #支付回調(diào)地址(服務(wù)器地址https://xxxxxx)
3、設(shè)置配置文件 WxPayConfig.java
package com.ckm.ball.config.wxpay; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; /** * 微信支付配置 * @author lf * @date 2023/8/30 */ @Data @Component @Configuration @ConfigurationProperties(prefix = "pay") public class WxPayConfig { /** * 微信公眾號(hào)appid */ private String appId; /** * 公眾號(hào)設(shè)置的API v2密鑰 */ private String apiKey; /** * 微信商戶平臺(tái) 商戶id */ private String mchId; /** *小程序密鑰 */ private String appSecret; /** * 小程序支付異步回調(diào)地址 */ private String notifyUrl; }
4、微信支付預(yù)下單實(shí)體類 WeChatPay.java
package com.ckm.ball.config.wxpay; import lombok.Data; import lombok.experimental.Accessors; /** * 微信支付預(yù)下單實(shí)體類 */ @Data @Accessors(chain = true) public class WeChatPay { /** * 返回狀態(tài)碼 此字段是通信標(biāo)識(shí),非交易標(biāo)識(shí),交易是否成功需要查看result_code來(lái)判斷 */ public String return_code; /** * 返回信息 當(dāng)return_code為FAIL時(shí)返回信息為錯(cuò)誤原因 ,例如 簽名失敗 參數(shù)格式校驗(yàn)錯(cuò)誤 */ private String return_msg; /** * 公眾賬號(hào)ID 調(diào)用接口提交的公眾賬號(hào)ID */ private String appid; /** * 商戶號(hào) 調(diào)用接口提交的商戶號(hào) */ private String mch_id; /** * api密鑰 詳見:https://pay.weixin.qq.com/index.php/extend/employee */ private String api_key; /** * 設(shè)備號(hào) 自定義參數(shù),可以為請(qǐng)求支付的終端設(shè)備號(hào)等 */ private String device_info; /** * 隨機(jī)字符串 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的隨機(jī)字符串 */ private String nonce_str; /** * 簽名 微信返回的簽名值,詳見簽名算法:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3 */ private String sign; /** * 簽名類型 */ private String sign_type; /** * 業(yè)務(wù)結(jié)果 SUCCESS SUCCESS/FAIL */ private String result_code; /** * 錯(cuò)誤代碼 當(dāng)result_code為FAIL時(shí)返回錯(cuò)誤代碼,詳細(xì)參見下文錯(cuò)誤列表 */ private String err_code; /** * 錯(cuò)誤代碼描述 當(dāng)result_code為FAIL時(shí)返回錯(cuò)誤描述,詳細(xì)參見下文錯(cuò)誤列表 */ private String err_code_des; /** * 交易類型 JSAPI JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付 說(shuō)明詳見;https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2 */ private String trade_type; /** * 預(yù)支付交易會(huì)話標(biāo)識(shí) 微信生成的預(yù)支付會(huì)話標(biāo)識(shí),用于后續(xù)接口調(diào)用中使用,該值有效期為2小時(shí) */ private String prepay_id; /** * 二維碼鏈接 weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 trade_type=NATIVE時(shí)有返回,此url用于生成支付二維碼,然后提供給用戶進(jìn)行掃碼支付。注意:code_url的值并非固定,使用時(shí)按照URL格式轉(zhuǎn)成二維碼即可 */ private String code_url; /** * 商品描述 商品簡(jiǎn)單描述,該字段請(qǐng)按照規(guī)范傳遞,具體請(qǐng)見 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2 */ private String body; /** * 商家訂單號(hào) 商戶系統(tǒng)內(nèi)部訂單號(hào),要求32個(gè)字符內(nèi),只能是數(shù)字、大小寫字母_-|* 且在同一個(gè)商戶號(hào)下唯一。詳見商戶訂單號(hào) https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2 */ private String out_trade_no; /** * 標(biāo)價(jià)金額 訂單總金額,單位為分,詳見支付金額 https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_2 */ private String total_fee; /** * 終端IP 支持IPV4和IPV6兩種格式的IP地址。用戶的客戶端IP */ private String spbill_create_ip; /** * 通知地址 異步接收微信支付結(jié)果通知的回調(diào)地址,通知url必須為外網(wǎng)可訪問(wèn)的url,不能攜帶參數(shù)。公網(wǎng)域名必須為https,如果是走專線接入,使用專線NAT IP或者私有回調(diào)域名可使用http */ private String notify_url; /** * 子商戶號(hào) sub_mch_id 非必填(商戶不需要傳入,服務(wù)商模式才需要傳入) 微信支付分配的子商戶號(hào) */ private String sub_mch_id; /** * 附加數(shù)據(jù),在查詢API和支付通知中原樣返回,該字段主要用于商戶攜帶訂單的自定義數(shù)據(jù) */ private String attach; /** * 商戶系統(tǒng)內(nèi)部的退款單號(hào),商戶系統(tǒng)內(nèi)部唯一,只能是數(shù)字、大小寫字母_-|*@ ,同一退款單號(hào)多次請(qǐng)求只退一筆。 */ private String out_refund_no; /** * 退款總金額,單位為分,只能為整數(shù),可部分退款。詳見支付金額 https://pay.weixin.qq.com/wiki/doc/api/native_sl.php?chapter=4_2 */ private String refund_fee; /** * 退款原因 若商戶傳入,會(huì)在下發(fā)給用戶的退款消息中體現(xiàn)退款原因 注意:若訂單退款金額≤1元,且屬于部分退款,則不會(huì)在退款消息中體現(xiàn)退款原因 */ private String refund_desc; /** * 交易結(jié)束時(shí)間 訂單失效時(shí)間,格式為yyyyMMddHHmmss,如2009年12月27日9點(diǎn)10分10秒表示為20091227091010。其他詳見時(shí)間規(guī)則 注意:最短失效時(shí)間間隔必須大于5分鐘 */ private String time_expire; /** * 用戶標(biāo)識(shí) trade_type=JSAPI,此參數(shù)必傳,用戶在主商戶appid下的唯一標(biāo)識(shí)。openid和sub_openid可以選傳其中之一,如果選擇傳sub_openid,則必須傳sub_appid。下單前需要調(diào)用【網(wǎng)頁(yè)授權(quán)獲取用戶信息: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 】接口獲取到用戶的Openid。 */ private String openid; /** * 時(shí)間戳 */ private String time_stamp; /** * 會(huì)員類型 */ private String memberShipType; }
5、微信支付API地址 WeChatPayUrlConstants.java
package com.ckm.ball.config.wxpay; /** * 微信支付API地址 * @author lf * @date 2023/8/30 */ public class WeChatPayUrlConstants { /** * 統(tǒng)一下單預(yù)下單接口url */ public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * 訂單狀態(tài)查詢接口URL */ public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery"; /** * 訂單申請(qǐng)退款 */ public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund"; /** * 付款碼 支付 */ public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay"; /** * 微信網(wǎng)頁(yè)授權(quán) 獲取“code”請(qǐng)求地址 */ public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize"; /** * 微信網(wǎng)頁(yè)授權(quán) 獲取“code” 回調(diào)地址 */ public static final String GainCodeRedirect_uri = "http://i5jmxe.natappfree.cc/boss/WeChatPayMobile/SkipPage.html"; }
6、預(yù)下單成功之后返回結(jié)果 OrderReturnInfo.java
package com.ckm.ball.config.wxpay; import lombok.Data; @Data public class OrderReturnInfo { private String return_code; private String return_msg; private String result_code; private String appid; private String mch_id; private String nonce_str; private String sign; private String prepay_id; private String trade_type; }
7、查詢訂單返回的實(shí)體類 QueryReturnInfo.java
package com.ckm.ball.config.wxpay; import lombok.Data; /** * 查詢訂單返回實(shí)體類 * @author lf * @date 2023/9/1 */ @Data public class QueryReturnInfo { private String return_code; private String return_msg; private String result_code; private String err_code; private String err_code_des; private String appid; private String mch_id; private String nonce_str; private String sign; private String prepay_id; private String trade_type; private String device_info; private String openid; private String is_subscribe; private String trade_state; private String bank_type; private int total_fee; private int settlement_total_fee; private String fee_type; private int cash_fee; private String cash_fee_type; private int coupon_fee; private int coupon_count; private String coupon_type_$n; private String coupon_id_$n; private String transaction_id; private String out_trade_no; private String time_end; private String trade_state_desc; }
8、簽名實(shí)體類 SignInfo.java
package com.ckm.ball.config.wxpay; import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.Data; /** * 簽名實(shí)體類 * @author lf * @date 2023/9/1 */ @Data public class SignInfo { private String appId;//小程序ID private String timeStamp;//時(shí)間戳 private String nonceStr;//隨機(jī)串 @XStreamAlias("package") private String repay_id; private String signType;//簽名方式 public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimeStamp() { return timeStamp; } public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getRepay_id() { return repay_id; } public void setRepay_id(String repay_id) { this.repay_id = repay_id; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } }
9、Http工具類 HttpRequest.java
package com.ckm.ball.utils; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.net.ssl.X509TrustManager; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; /** * Http工具類 * @author lf * @date 2023/9/1 */ public class HttpRequest { //連接超時(shí)時(shí)間,默認(rèn)10秒 private static final int socketTimeout = 10000; //傳輸超時(shí)時(shí)間,默認(rèn)30秒 private static final int connectTimeout = 30000; /** * post請(qǐng)求 * * @throws IOException * @throws ClientProtocolException * @throws NoSuchAlgorithmException * @throws KeyStoreException * @throws KeyManagementException * @throws UnrecoverableKeyException */ public static String sendPost(String url, Object xmlObj) throws ClientProtocolException, IOException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException { HttpPost httpPost = new HttpPost(url); //解決XStream對(duì)出現(xiàn)雙下劃線的bug XStream xStreamForRequestPostData = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_"))); xStreamForRequestPostData.alias("xml", xmlObj.getClass()); //將要提交給API的數(shù)據(jù)對(duì)象轉(zhuǎn)換成XML格式數(shù)據(jù)Post給API String postDataXML = xStreamForRequestPostData.toXML(xmlObj); //得指明使用UTF-8編碼,否則到API服務(wù)器XML的中文不能被成功識(shí)別 StringEntity postEntity = new StringEntity(postDataXML, "UTF-8"); httpPost.addHeader("Content-Type", "text/xml"); httpPost.setEntity(postEntity); //設(shè)置請(qǐng)求器的配置 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build(); httpPost.setConfig(requestConfig); HttpClient httpClient = HttpClients.createDefault(); HttpResponse response = httpClient.execute(httpPost); HttpEntity entity = response.getEntity(); String result = EntityUtils.toString(entity, "UTF-8"); return result; } /** * 自定義證書管理器,信任所有證書 * * @author pc */ public static class MyX509TrustManager implements X509TrustManager { @Override public void checkClientTrusted( java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } } }
10、微信簽名 SignUtils.java
package com.ckm.ball.config.wxpay; import com.ckm.ball.utils.MD5; import com.thoughtworks.xstream.annotations.XStreamAlias; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; /** * 微信簽名 * @author lf * @date 2023/9/1 */ public class SignUtils { /** * 簽名算法 * * @param o 要參與簽名的數(shù)據(jù)對(duì)象 * @return 簽名 * @throws IllegalAccessException */ public static String getSign(Object o,String key) throws IllegalAccessException { ArrayList<String> list = new ArrayList<String>(); Class cls = o.getClass(); Field[] fields = cls.getDeclaredFields(); for (Field f : fields) { f.setAccessible(true); if (f.get(o) != null && f.get(o) != "") { String name = f.getName(); XStreamAlias anno = f.getAnnotation(XStreamAlias.class); if (anno != null) { name = anno.value(); } list.add(name + "=" + f.get(o) + "&"); } } int size = list.size(); String[] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { sb.append(arrayToSort[i]); } String result = sb.toString(); result += "key=" + key; System.out.println("簽名數(shù)據(jù):" + result); result = MD5.MD5Encode(result).toUpperCase(); return result; } public static String getSign(Map<String, Object> map,String key) { ArrayList<String> list = new ArrayList<String>(); for (Map.Entry<String, Object> entry : map.entrySet()) { if (entry.getValue() != "") { list.add(entry.getKey() + "=" + entry.getValue() + "&"); } } int size = list.size(); String[] arrayToSort = list.toArray(new String[size]); Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { sb.append(arrayToSort[i]); } String result = sb.toString(); result += "key=" + key; //Util.log("Sign Before MD5:" + result); result = MD5.MD5Encode(result).toUpperCase(); //Util.log("Sign Result:" + result); return result; } }
11、MD5加密工具類 MD5.java
package com.ckm.ball.utils; import java.security.MessageDigest; /** * MD5加密工具類 * @author lf * @date 2023/9/1 */ public class MD5 { private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; /** * 轉(zhuǎn)換字節(jié)數(shù)組為16進(jìn)制字串 * * @param b 字節(jié)數(shù)組 * @return 16進(jìn)制字串 */ public static String byteArrayToHexString(byte[] b) { StringBuilder resultSb = new StringBuilder(); for (byte aB : b) { resultSb.append(byteToHexString(aB)); } return resultSb.toString(); } /** * 轉(zhuǎn)換byte到16進(jìn)制 * * @param b 要轉(zhuǎn)換的byte * @return 16進(jìn)制格式 */ private static String byteToHexString(byte b) { int n = b; if (n < 0) { n = 256 + n; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } /** * MD5編碼 * * @param origin 原始字符串 * @return 經(jīng)過(guò)MD5加密之后的結(jié)果 */ public static String MD5Encode(String origin) { String resultString = null; try { resultString = origin; MessageDigest md = MessageDigest.getInstance("MD5"); resultString = byteArrayToHexString(md.digest(resultString.getBytes())); } catch (Exception e) { e.printStackTrace(); } return resultString; } }
12、微信支付service接口 WxPayInfoService.java
package com.ckm.ball.config.wxpay; import java.util.Map; /** * 微信小程序支付-業(yè)務(wù)接口層 * @author lf * @date 2023/8/31 */ public interface WxPayInfoService { /** * 插入訂單記錄 */ Map<String, Object> insertPayRecord(); /** * 查詢訂單 * @param out_trade_no 訂單號(hào) * @return 返回結(jié)果 */ Map<String, Object> orderQuery(String out_trade_no); }
13、微信支付service接口實(shí)現(xiàn)類 WxPayInfoServiceImpl.java
package com.ckm.ball.config.wxpay; import cn.hutool.core.util.ObjectUtil; import com.github.wxpay.sdk.WXPayConstants; import com.github.wxpay.sdk.WXPayUtil; import com.ckm.ball.utils.HttpRequest; import com.thoughtworks.xstream.XStream; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.*; /** * 微信小程序支付-業(yè)務(wù)接口實(shí)現(xiàn)層 * @author lf * @date 2023/8/31 */ @Service @Slf4j public class WxPayInfoServiceImpl implements WxPayInfoService { @Resource private WxPayConfig payProperties; private static final DecimalFormat df = new DecimalFormat("#"); /** * 插入訂單記錄 * @param payParameterVO 用戶ID 會(huì)員套餐ID * @return 返回結(jié)果 */ @Override @Transactional public Map<String, Object> insertPayRecord() { //接收返回的參數(shù) Map<String, Object> map = new HashMap<>(); String title = "ckm"; //金額 * 100 以分為單位 BigDecimal fee = BigDecimal.valueOf(1); BigDecimal RMB = new BigDecimal(100); BigDecimal totalFee = fee.multiply(RMB); try { WeChatPay weChatPay = new WeChatPay(); weChatPay.setAppid(payProperties.getAppId()); weChatPay.setMch_id(payProperties.getMchId()); weChatPay.setNonce_str(getRandomStringByLength(32)); weChatPay.setBody(title); weChatPay.setOut_trade_no(getRandomStringByLength(32)); weChatPay.setTotal_fee( df.format(Double.parseDouble(String.valueOf(totalFee)))); weChatPay.setSpbill_create_ip("58.213.48.98"); weChatPay.setNotify_url(payProperties.getNotifyUrl()); weChatPay.setTrade_type("JSAPI"); //這里直接使用當(dāng)前用戶的openid weChatPay.setOpenid("o_qV97S-4HaPN51ZbQe4qML625j4"); // weChatPay.setSign_type("MD5"); //生成簽名 String sign = SignUtils.getSign(weChatPay,payProperties.getApiKey()); System.out.println(sign); weChatPay.setSign(sign); String result = HttpRequest.sendPost(WeChatPayUrlConstants.Uifiedorder, weChatPay); System.out.println(result); //將返回結(jié)果從xml格式轉(zhuǎn)換為map格式 Map<String, String> wxResultMap = WXPayUtil.xmlToMap(result); if (ObjectUtil.isNotEmpty(wxResultMap.get("return_code")) && wxResultMap.get("return_code").equals("SUCCESS")){ if (wxResultMap.get("result_code").equals("FAIL")){ map.put("msg", "統(tǒng)一下單失敗"); map.put("status",500); map.put("data", wxResultMap.get("err_code_des")); return map; } } XStream xStream = new XStream(); // 允許特定類的反序列化 xStream.allowTypes(new Class[] { OrderReturnInfo.class }); // 設(shè)置別名 xStream.alias("xml", OrderReturnInfo.class); // 從XML反序列化 OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result); // 二次簽名 if ("SUCCESS".equals(returnInfo.getReturn_code()) && returnInfo.getReturn_code().equals(returnInfo.getResult_code())) { SignInfo signInfo = new SignInfo(); signInfo.setAppId(payProperties.getAppId()); long time = System.currentTimeMillis() / 1000; signInfo.setTimeStamp(String.valueOf(time)); signInfo.setNonceStr(WXPayUtil.generateNonceStr()); signInfo.setRepay_id("prepay_id=" + returnInfo.getPrepay_id()); signInfo.setSignType("MD5"); //生成簽名 String sign1 = SignUtils.getSign(signInfo,payProperties.getApiKey()); Map<String, String> payInfo = new HashMap<>(); payInfo.put("timeStamp", signInfo.getTimeStamp()); payInfo.put("nonceStr", signInfo.getNonceStr()); payInfo.put("package", signInfo.getRepay_id()); payInfo.put("signType", signInfo.getSignType()); payInfo.put("paySign", sign1); map.put("status", 200); map.put("msg", "統(tǒng)一下單成功!"); map.put("data", payInfo); //預(yù)下單成功,處理業(yè)務(wù)邏輯 //****************************// // 業(yè)務(wù)邏輯結(jié)束 回傳給小程序端喚起支付 return map; } map.put("status", 500); map.put("msg", "統(tǒng)一下單失敗!"); map.put("data", null); return map; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 查詢訂單 * @param out_trade_no 訂單號(hào) * @return 返回結(jié)果 */ @Override public Map<String, Object> orderQuery(String out_trade_no){ Map<String, Object> map = new HashMap<>(); try { WeChatPay weChatPay = new WeChatPay(); weChatPay.setAppid(payProperties.getAppId()); weChatPay.setMch_id(payProperties.getMchId()); weChatPay.setNonce_str(WXPayUtil.generateNonceStr()); weChatPay.setOut_trade_no(out_trade_no); //order.setSign_type("MD5"); //生成簽名 String sign = SignUtils.getSign(weChatPay,payProperties.getApiKey()); weChatPay.setSign(sign); String result = HttpRequest.sendPost(WXPayConstants.ORDERQUERY_URL, weChatPay); System.out.println(result); XStream xStream = new XStream(); xStream.alias("xml", QueryReturnInfo.class); QueryReturnInfo returnInfo = (QueryReturnInfo) xStream.fromXML(result); map.put("status", 500); map.put("msg", "統(tǒng)一下單失敗!"); map.put("data", returnInfo); return map; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 獲取一定長(zhǎng)度的隨機(jī)字符串 * * @param length 指定字符串長(zhǎng)度 * @return 一定長(zhǎng)度的字符串 */ public static String getRandomStringByLength(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(base.length()); sb.append(base.charAt(number)); } return sb.toString(); } }
14、微信支付Controller層 WeChatPayController.java
package com.ckm.ball.config.wxpay; import com.ckm.ball.utils.StreamUtils; import com.github.wxpay.sdk.WXPayUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/ballFishing/wechat") @Api(value = "微信支付接口") @CrossOrigin // @CrossOrigin注解 解決uniapp跨域訪問(wèn)后端問(wèn)題。 public class WeChatPayController { @Resource private WxPayInfoService wxPayInfoService; /** * 小程序支付下單接口 * @return 返回結(jié)果 */ @ApiOperation("小程序支付功能") @PostMapping("/pay") public Map<String,Object> wxPay(){ return wxPayInfoService.insertPayRecord(); } /** * 查詢訂單 */ @ApiOperation("訂單查詢") @PostMapping("/wx/query") public Map<String, Object> orderQuery(@RequestParam("out_trade_no") String out_trade_no) { return wxPayInfoService.orderQuery(out_trade_no); } /** * 微信小程序支付成功回調(diào) * @param request 請(qǐng)求 * @param response 響應(yīng) * @return 返回結(jié)果 * @throws Exception 異常處理 */ @RequestMapping("/weixin/callback") public String callBack(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("接口已被調(diào)用"); ServletInputStream inputStream = request.getInputStream(); String notifyXml = StreamUtils.inputStream2String(inputStream, "utf-8"); System.out.println(notifyXml); // 解析返回結(jié)果 Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyXml); // 判斷支付是否成功 if ("SUCCESS".equals(notifyMap.get("result_code"))) { //支付成功時(shí)候,處理業(yè)務(wù)邏輯 System.out.println("支付成功"); System.out.println("<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "); /** * 注意 * 因?yàn)槲⑿呕卣{(diào)會(huì)有八次之多,所以當(dāng)?shù)谝淮位卣{(diào)成功了,那么我們就不再執(zhí)行邏輯了 * return返回的結(jié)果一定是這種格式,當(dāng)result_code返回的結(jié)果是SUCCESS時(shí),則不進(jìn)行調(diào)用了 * 如果不返回下面的格式,業(yè)務(wù)邏輯會(huì)出現(xiàn)回調(diào)多次的情況,我就遇到過(guò)這種情況。 */ return "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } // 創(chuàng)建響應(yīng)對(duì)象:微信接收到校驗(yàn)失敗的結(jié)果后,會(huì)反復(fù)的調(diào)用當(dāng)前回調(diào)函數(shù) Map<String, String> returnMap = new HashMap<>(); returnMap.put("return_code", "FAIL"); returnMap.put("return_msg", ""); String returnXml = WXPayUtil.mapToXml(returnMap); response.setContentType("text/xml"); System.out.println("校驗(yàn)失敗"); return returnXml; } }
簽名失敗
如果出現(xiàn)簽名錯(cuò)誤,請(qǐng)檢查后再試。去重置秘鑰:微信商戶平臺(tái)(pay.weixin.qq.com)–>賬戶設(shè)置–>API安全–>密鑰設(shè)置
簽名成功
到此這篇關(guān)于Spring Boot 微信小程序接入微信支付的文章就介紹到這了,更多相關(guān)Spring Boot 微信支付內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot整合mqtt實(shí)現(xiàn)消息訂閱和推送功能
mica-mqtt-client-spring-boot-starter是一個(gè)方便、高效、可靠的MQTT客戶端啟動(dòng)器,適用于需要使用MQTT協(xié)議進(jìn)行消息通信的Spring Boot應(yīng)用程序,這篇文章主要介紹了springboot整合mqtt實(shí)現(xiàn)消息訂閱和推送功能,需要的朋友可以參考下2024-02-02JAVA 實(shí)現(xiàn)二叉樹(鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu))
本篇文章主要介紹用JAVA 實(shí)現(xiàn)二叉樹,并提供實(shí)例.對(duì)二叉樹數(shù)據(jù)結(jié)構(gòu)很好的學(xué)習(xí)實(shí)踐,有需要的朋友可以參考下2016-07-07Java中g(shù)et/post的https請(qǐng)求忽略ssl證書認(rèn)證淺析
因?yàn)镴ava在安裝的時(shí)候,會(huì)默認(rèn)導(dǎo)入某些根證書,所以有些網(wǎng)站不導(dǎo)入證書,也可以使用Java進(jìn)行訪問(wèn),這篇文章主要給大家介紹了關(guān)于Java中g(shù)et/post的https請(qǐng)求忽略ssl證書認(rèn)證的相關(guān)資料,需要的朋友可以參考下2024-01-01Spring使用@Conditional進(jìn)行條件裝配的實(shí)現(xiàn)
在spring中有些bean需要滿足某些環(huán)境條件才創(chuàng)建某個(gè)bean,這個(gè)時(shí)候可以在bean定義上使用@Conditional注解來(lái)修飾,所以本文給大家介紹了Spring使用@Conditional進(jìn)行條件裝配的實(shí)現(xiàn),文中通過(guò)代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12SpringBoot集成Redis—使用RedisRepositories詳解
這篇文章主要介紹了SpringBoot集成Redis—使用RedisRepositories詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03通過(guò)RedisTemplate連接多個(gè)Redis過(guò)程解析
這篇文章主要介紹了通過(guò)RedisTemplate連接多個(gè)Redis過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08Springboot-yaml配置和自動(dòng)配置原理分析
這篇文章主要介紹了Springboot-yaml配置和自動(dòng)配置原理分析,自動(dòng)配置原理是配置文件配置debug: true可以在控制臺(tái)打印自動(dòng)配置報(bào)告.可以打印所有的啟動(dòng)的自動(dòng)配置和沒有啟動(dòng)的自動(dòng)配置類,需要的朋友可以參考下2021-06-06