知識(shí)
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!
您當(dāng)前位置>首頁(yè) » 新聞資訊 » 小程序相關(guān) >
java實(shí)現(xiàn)微信小程序支付(springboot框架)
發(fā)表時(shí)間:2020-10-6
發(fā)布人:葵宇科技
瀏覽次數(shù):71
java實(shí)現(xiàn)微信小程序支付
準(zhǔn)備工作
請(qǐng)仔細(xì)閱讀微信支付文檔 微信支付文檔地址
到微信公眾平臺(tái)開通微信小程序應(yīng)用得到 appid 和 secret
支付代碼
/**
* @param orderNo 訂單號(hào)
* @param amount 訂單總金額
* @param deviceCode 設(shè)備編號(hào)
* @DESCRIPTION: 訂單支付
* @return: ResponseData
*/
public ResponseData payOrder(String openid, String orderNo, double amount, HttpServletRequest request) {
log.info("======================= 發(fā)起微信預(yù)支付請(qǐng)求,調(diào)用統(tǒng)一下單開始 =======================");
log.info("準(zhǔn)備請(qǐng)求數(shù)據(jù)...");
try {
//這里可以校驗(yàn)訂單是否存在、是否支付
//生成需要的參數(shù)集合
Map<String, String> packageParams = this.createPackageParams(orderItemDto, openid, amount, request);
//MD5運(yùn)算生成簽名,這里是第一次簽名,用于調(diào)用統(tǒng)一下單接口(簽名中所有字符大寫toUpperCase())
//WeChat.KEY:微信支付的商戶密鑰,需要在微信后臺(tái)設(shè)置,包括后面用到的 KEY 都是這個(gè)值
String mySign = this.sign(this.createLinkString(packageParams), WeChat.KEY, "utf-8").toUpperCase();
packageParams.put("sign", mySign);
log.info("==== 第一次簽名:" + mySign + " ====\n\t");
//拼接統(tǒng)一下單接口使用的xml數(shù)據(jù),要將上一步生成的簽名一起拼接進(jìn)去
String xml = this.GetMapToXML(packageParams);
log.info("統(tǒng)一下單接口請(qǐng)求的拼接XML數(shù)據(jù)【" + xml + "】\n\t");
//調(diào)用統(tǒng)一下單接口,并接受返回的結(jié)果
String result = httpUtils .httpRequest(WeChat.PAY_URL, "POST", xml);
if (ToolUtil.isEmpty(result)) {
log.info("調(diào)用統(tǒng)一下單接口返回的XML數(shù)據(jù)為空,支付失敗");
return ResponseData.error("支付失敗");
}
log.info("統(tǒng)一下單接口返回的XML數(shù)據(jù)【" + result + "】\n\t");
// 將解析結(jié)果存儲(chǔ)在HashMap中
Map map = this.doXMLParse(result);
//返回給小程序端需要的參數(shù)
Map<String, Object> wxResponse = new HashMap<>();
if (WeChat.SUCCESS.equals(map.get("return_code")) && WeChat.SUCCESS.equals(map.get("result_code"))) {
//這里可以寫你的業(yè)務(wù)邏輯,看具體情況,一般業(yè)務(wù)邏輯會(huì)寫在支付回調(diào)函數(shù)里面
Long timeStamp = System.currentTimeMillis() / 1000;
wxResponse.put("nonceStr", packageParams.get("nonce_str"));
wxResponse.put("package", "prepay_id=" + map.get("prepay_id"));
//這邊要將返回的時(shí)間戳轉(zhuǎn)化成字符串,不然小程序端調(diào)用wx.requestPayment方法會(huì)報(bào)簽名錯(cuò)誤
wxResponse.put("timeStamp", timeStamp + "");
//拼接簽名需要的參數(shù)
String stringSignTemp = "appId=" + appid + "&nonceStr=" + packageParams.get("nonce_str") + "&package=prepay_id=" + map.get("prepay_id") + "&signType=MD5&timeStamp=" + timeStamp;
//再次簽名,這個(gè)簽名用于小程序端調(diào)用wx.requesetPayment方法
String paySign = this.sign(stringSignTemp, WeChat.KEY, "utf-8").toUpperCase();
log.info("==== 第二次簽名:" + paySign + " ====");
wxResponse.put("paySign", paySign);
} else {
log.info("調(diào)用統(tǒng)一下單接口返回異常 return_code【" + map.get("return_code") + "】" + " result_code【" + map.get("result_code") + "】 支付失敗");
return null;
}
wxResponse.put("appid", appid);
wxResponse.put("orderNo", orderNo);
log.info("======================= 發(fā)起微信支付請(qǐng)求,調(diào)用統(tǒng)一下單結(jié)束 =======================");
return ResponseData.success(wxResponse);
} catch (Exception e) {
log.info("調(diào)用統(tǒng)一下單接口異常:" + e.getMessage());
log.info("用戶【" + openid + "】支付失敗");
}
return ResponseData.error("支付失敗");
}
簽名類
簽名請(qǐng)參照微信官方簽名算法 地址:簽名算法
/**
* 簽名字符串
*
* @param text 需要簽名的字符串
* @param key 商戶平臺(tái)設(shè)置的密鑰
* @param input_charset 編碼格式
* @return 簽名結(jié)果
*/
public static String sign(String text, String key, String input_charset) {
text = text + "&key=" + key;
return DigestUtils.md5Hex(getContentBytes(text, input_charset));
}
轉(zhuǎn)換xml
public static String GetMapToXML(Map<String, String> param) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (Map.Entry<String, String> entry : param.entrySet()) {
sb.append("<" + entry.getKey() + ">");
sb.append(entry.getValue());
sb.append("</" + entry.getKey() + ">");
}
sb.append("</xml>");
return sb.toString();
}
解析xml
/**
* 解析xml,返回第一級(jí)元素鍵值對(duì)。如果第一級(jí)元素有子節(jié)點(diǎn),則此節(jié)點(diǎn)的值是子節(jié)點(diǎn)的xml數(shù)據(jù)。
*
* @param strxml
* @return
* @throws org.jdom2.JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws Exception {
if (stringUtils.isEmpty(strxml)) {
return null;
}
Map m = new HashMap();
InputStream in = String2Inputstream(strxml);
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if (children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = getChildrenText(children);
}
m.put(k, v);
}
//關(guān)閉流
in.close();
return m;
}
排序并按規(guī)則拼接
/**
* 把數(shù)組所有元素排序,并按照“參數(shù)=參數(shù)值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并參與字符拼接的參數(shù)組
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {
// 拼接時(shí),不包括最后一個(gè)&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
參數(shù)集合
根據(jù)微信支付文檔的參數(shù)來配置 微信支付文檔地址
private Map<String, String> createPackageParams(OrderItemDto orderItemDto, String openid, double amount, HttpServletRequest request) {
//支付金額,字符串類型,單位 分
String total_fee = String.valueOf((int) (amount * 100));
//生成的隨機(jī)字符串
String nonce_str = PayUtil.getRandomStringByLength(32);
//設(shè)置商戶訂單號(hào)(年月日+六位酒店id,不夠補(bǔ)零+隨機(jī)4位 例:20190525000001a2sa)
String out_trade_no = "";
//獲取客戶端的ip地址
String spbill_create_ip = PayUtil.getIpAddr(request);
//組裝參數(shù),用戶生成統(tǒng)一下單接口的簽名
Map<String, String> packageParams = new HashMap<>();
packageParams.put("appid", appid);
packageParams.put("mch_id", WeChat.MCH_ID);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", WeChat.PAY_ORDER_BODY);
//商戶訂單號(hào),自己的訂單ID
packageParams.put("out_trade_no", out_trade_no);
//支付金額,這邊需要轉(zhuǎn)成字符串類型,否則后面的簽名會(huì)失敗
packageParams.put("total_fee", total_fee);
//客戶端ip
packageParams.put("spbill_create_ip", spbill_create_ip);
//支付成功后的回調(diào)地址
packageParams.put("notify_url", WeChat.NOTIFY_URL);
//支付方式
packageParams.put("trade_type", WeChat.TRADETYPE);
//用戶的openID,自己獲取
packageParams.put("openid", openid + "");
return packageParams;
}
http工具類
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
@Slf4j
public class httpUtils {
// 連接超時(shí)時(shí)間,默認(rèn)10秒
private static int socketTimeout = 10000;
// 傳輸超時(shí)時(shí)間,默認(rèn)30秒
private static int connectTimeout = 30000;
// HTTP請(qǐng)求器
private static CloseableHttpClient httpClient;
// 請(qǐng)求器的配置
private static RequestConfig requestConfig;
/**
* 加載證書
*/
private static void initCert() throws Exception {
// 證書密碼,默認(rèn)為商戶ID
String key = "";
// 商戶證書的路徑
//public static final String CERT_PATH = "E:/workspace/apiclient_cert.p12"; //本地路徑
String CERT_PATH = "/usr/local/dev/guns/cert/"; // 服務(wù)器路徑
String path = CERT_PATH + "qcdr_cert.p12";
InputStream instream = null;
try {
// 讀取本機(jī)存放的PKCS12證書文件
instream = new FileInputStream(new File(path));
/*ClassPathResource classPathResource = new ClassPathResource(path);
//獲取文件流
instream = classPathResource.getInputStream();
instream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);*/
} catch (Exception e) {
log.error("商戶證書不正確-->>" + e);
}
try {
// 指定讀取證書格式為PKCS12
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// 指定PKCS12的密碼(商戶ID)
keyStore.load(instream, key.toCharArray());
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();
// 指定TLS版本
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
// 設(shè)置httpclient的SSLSocketFactory
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (Exception e) {
log.error("商戶秘鑰不正確-->>" + e);
} finally {
instream.close();
}
}
/**
* 通過Https往API post xml數(shù)據(jù)
*
* @param url 退款地址
* @param xmlObj 要提交的XML數(shù)據(jù)對(duì)象
* @return
*/
public static String postData(String url, String xmlObj) {
// 加載證書
try {
initCert();
} catch (Exception e) {
e.printStackTrace();
}
String result = null;
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8編碼,否則到API服務(wù)器XML的中文不能被成功識(shí)別
StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
// 根據(jù)默認(rèn)超時(shí)限制初始化requestConfig
requestConfig = RequestConfig.custom()
.setSocketTimeout(socketTimeout)
.setConnectTimeout(connectTimeout)
.build();
// 設(shè)置請(qǐng)求器的配置
httpPost.setConfig(requestConfig);
try {
HttpResponse response = null;
try {
response = httpClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
}
HttpEntity entity = response.getEntity();
try {
result = EntityUtils.toString(entity, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
} finally {
httpPost.abort();
}
return result;
}
/**
* @param requestUrl 請(qǐng)求地址
* @param requestMethod 請(qǐng)求方法
* @param outputStr 參數(shù)
*/
public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
// 創(chuàng)建SSLContext
StringBuffer buffer = null;
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
//往服務(wù)器端寫內(nèi)容
if (null != outputStr) {
OutputStream os = conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
// 讀取服務(wù)器端返回的內(nèi)容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
return buffer.toString();
}
}
下面是小程序支付回調(diào)函數(shù)
回調(diào)函數(shù)的路徑需要放在上面支付的參數(shù)集合里,這樣才能收到微信支付結(jié)果通知,通知url必須為外網(wǎng)可訪問的url,不能攜帶參數(shù)
詳細(xì)情況可以看 微信支付文檔
回調(diào)函數(shù)代碼
/**
* 支付回調(diào)函數(shù)
*/
public void payOrderCallback(HttpServletRequest request, HttpServletResponse response) {
log.info("======================= 微信支付回調(diào)開始 =======================");
//通知微信服務(wù)器已經(jīng)支付是否成功
String resXml = "<xml>";
try {
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
String notityXml = sb.toString();
log.info("微信回調(diào)信息:" + notityXml);
//解析回調(diào)xml
Map map = this.doXMLParse(notityXml);
//獲得回調(diào)狀態(tài)碼
String returnCode = (String) map.get("return_code");
String resultCode = (String) map.get("result_code");
log.info("微信回調(diào)狀態(tài)碼return_code【 " + returnCode + " 】");
if ("SUCCESS".equals(returnCode) && returnCode.equals(resultCode)) {
log.info("微信支付狀態(tài)碼result_code【 " + resultCode + " 】");
//獲取返回的金額與系統(tǒng)訂單的金額進(jìn)行比對(duì)
String totalFee = map.get("total_fee").toString();
//查詢出訂單金額
QcOrder order = orderMapper.selectByOrderNo(map.get("out_trade_no").toString());
log.info("微信返回訂單金額【" + totalFee + "分】 系統(tǒng)訂單金額【" + (int) (order.getAmount() * 100) + "分】");
//根據(jù)自己需求校驗(yàn),此處官方推薦校驗(yàn)金額
//根據(jù)微信官網(wǎng)的介紹,此處不僅對(duì)回調(diào)的參數(shù)進(jìn)行驗(yàn)簽,還需要對(duì)返回的金額與系統(tǒng)訂單的金額進(jìn)行比對(duì)等
if (PayUtil.verify(map, WeChat.KEY, "utf-8")) {
if (totalFee.equals(String.valueOf((int) (order.getAmount() * 100)))) {
//到這里就已經(jīng)支付成功了,可以添加業(yè)務(wù)邏輯了
//返回給微信支付成功的信息
resXml += "<return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>";
} else {
resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[支付金額錯(cuò)誤]]></return_msg>";
log.info("微信回調(diào)失敗,支付金額錯(cuò)誤。" + "微信返回訂單金額【" + totalFee + "分】 系統(tǒng)訂單金額【" + (int) (order.getAmount() * 100) + "分】");
}
} else {
resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[簽名校驗(yàn)錯(cuò)誤]]></return_msg>";
log.info("微信回調(diào)簽名驗(yàn)證【 失敗 】");
}
} else {
resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[報(bào)文為空]]></return_msg>";
log.info("微信回調(diào)【 失敗 】");
}
} catch (Exception e) {
resXml += WeChat.CALLBACK_RETURN_FAIL + "<return_msg><![CDATA[回調(diào)異常]]></return_msg>";
log.info("微信支付回調(diào)異常:" + e.getMessage());
e.printStackTrace();
}
resXml += "</xml> ";
try {
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
} catch (Exception e) {
log.info("給微信返回狀態(tài)時(shí)異常");
e.printStackTrace();
}
log.info("支付回調(diào)通知微信的xml【" + resXml + "】");
log.info("======================= 微信支付回調(diào)結(jié)束 =======================");
}
驗(yàn)簽
/**
* 驗(yàn)證簽名字符串
*
* @param map 需要簽名的數(shù)據(jù)
* @param key 微信商戶支付密鑰
* @param input_charset 編碼格式
* @return 簽名結(jié)果
*/
public static boolean verify(Map<String, String> map, String key, String input_charset) {
//驗(yàn)證簽名是否正確
//回調(diào)驗(yàn)簽時(shí)需要去除sign和空值參數(shù)
Map<String, String> validParams = this.paraFilter(map);
//把參數(shù)按規(guī)則排序拼接
String prestr = this.createLinkString(validParams);
String sign = map.get("sign");
String mysign = this.sign(prestr, key, input_charset).toUpperCase();
log.info("返回簽名:" + sign + " 生成簽名:" + mysign);
if (mysign.equals(sign)) {
log.info("支付回調(diào)返回?cái)?shù)據(jù)簽名驗(yàn)證通過!!!");
return true;
} else {
return false;
}
}
去除無效值
/**
* 除去數(shù)組中的空值和簽名參數(shù)
*
* @param sArray 簽名參數(shù)組
* @return 去掉空值與簽名參數(shù)后的新簽名參數(shù)組
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
|| key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
至此整個(gè)微信小程序支付完成,如果有什么問題歡迎指出
微信小程序 退款 詳細(xì)代碼地址:微信退款代碼地址
相關(guān)案例查看更多
相關(guān)閱讀
- 汽車報(bào)廢系統(tǒng)
- 云南網(wǎng)站建設(shè)靠譜公司
- 迪慶小程序開發(fā)
- 怎么做網(wǎng)站
- 報(bào)廢車管理
- 網(wǎng)站建設(shè)開發(fā)
- 汽車拆解管理系統(tǒng)
- 云南做網(wǎng)站
- 云南小程序開發(fā)公司哪家好
- 商標(biāo)注冊(cè)
- 公眾號(hào)模板消息
- 楚雄小程序開發(fā)
- 云南建站公司
- 百度小程序
- vue開發(fā)小程序
- 北京小程序開發(fā)
- 網(wǎng)站維護(hù)
- 報(bào)廢車回收管理系統(tǒng)
- 網(wǎng)站建設(shè)特性
- 小程序退款
- 汽車拆解系統(tǒng)
- 云南小程序設(shè)計(jì)
- 云南小程序開發(fā)制作公司
- 云南小程序公司
- 網(wǎng)頁(yè)制作
- asp網(wǎng)站
- 正規(guī)網(wǎng)站建設(shè)公司
- 開通微信小程序被騙
- 開發(fā)制作小程序
- 云南小程序開發(fā)公司