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來判斷
*/
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支付 說明詳見;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;
/**
* 商品描述 商品簡單描述,該字段請(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)可訪問的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)頁授權(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)頁授權(quán) 獲取“code”請(qǐng)求地址
*/
public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
/**
* 微信網(wǎng)頁授權(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)過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;
}
/**
* 獲取一定長度的隨機(jī)字符串
*
* @param length 指定字符串長度
* @return 一定長度的字符串
*/
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跨域訪問后端問題。
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)多次的情況,我就遇到過這種情況。
*/
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-02
JAVA 實(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-07
Java中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)行訪問,這篇文章主要給大家介紹了關(guān)于Java中g(shù)et/post的https請(qǐng)求忽略ssl證書認(rèn)證的相關(guān)資料,需要的朋友可以參考下2024-01-01
Spring使用@Conditional進(jìn)行條件裝配的實(shí)現(xiàn)
在spring中有些bean需要滿足某些環(huán)境條件才創(chuàng)建某個(gè)bean,這個(gè)時(shí)候可以在bean定義上使用@Conditional注解來修飾,所以本文給大家介紹了Spring使用@Conditional進(jìn)行條件裝配的實(shí)現(xiàn),文中通過代碼示例給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
SpringBoot集成Redis—使用RedisRepositories詳解
這篇文章主要介紹了SpringBoot集成Redis—使用RedisRepositories詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
通過RedisTemplate連接多個(gè)Redis過程解析
這篇文章主要介紹了通過RedisTemplate連接多個(gè)Redis過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
Springboot-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

