文章目錄
- 前言
- 一、首先匯入生成二維碼和微信支付環境
- 二、在application.yml檔案配置微信所有需的基本配置
- 1.匯入
- 2.創建MyWXPayConfig類引入配置資訊
- 三、引入 WxPayServiceImpl 實作類
- 四、引入WxPayService層
- 五、引入Util類
- 引入WXPayXmlUtil類
- 六、引入WxPayController類
- 七、MD5加密
- 總結
- 程式員不怕踩坑,就拍沒坑可踩,加油打工人!
前言
一、首先匯入生成二維碼和微信支付環境
<!-- 生成二維碼工具 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.2.0</version>
</dependency>
<!-- 微信支付所需sdk -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
二、在application.yml檔案配置微信所有需的基本配置
1.匯入
代碼如下(示例):
# 服務器域名地址
server:
service-domain: //這里寫你的域名地址
#微信app支付
pay:
wxpay:
app:
appID: 微信appid
mchID: 商戶號
key: //這個key實在微信支付公眾品臺自己定義的key 要求36位
certPath: static/cert/wxpay/apiclient_cert.p12 # 從微信商戶平臺下載的安全證書存放的路徑、我放在resources下面,切記一定要看看target目錄下的class檔案下有沒有打包apiclient_cert.p12檔案
payNotifyUrl: # 微信支付成功的異步通知介面 這里引入你的回呼介面,
//這里直接寫https://域名:埠/介面地址,注意一定是線上的介面,因為微信訪問不到你本地的介面
2.創建MyWXPayConfig類引入配置資訊
代碼如下(示例):
package com.example.gasstation.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.InputStream;
@Data
@Component
@ConfigurationProperties(prefix = "pay.wxpay.app")
public class MyWXPayConfig implements WXPayConfig{
/**
* appID
*/
private String appID;
/**
* 商戶號
*/
private String mchID;
/**
* API 密鑰
*/
private String key;
/**
* API證書絕對路徑 (本專案放在了 resources/cert/wxpay/apiclient_cert.p12")
*/
private String certPath;
/**
* HTTP(S) 連接超時時間,單位毫秒
*/
private int httpConnectTimeoutMs = 8000;
/**
* HTTP(S) 讀資料超時時間,單位毫秒
*/
private int httpReadTimeoutMs = 10000;
/**
* 微信支付異步通知地址
*/
private String payNotifyUrl;
/**
* 微信退款異步通知地址
*/
private String refundNotifyUrl;
/**
* 統一下單url
*/
private final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/** 這里實作了一個service層**/
@Override
public InputStream getCertStream() {
InputStream certStream =getClass().getClassLoader().getResourceAsStream(certPath);
return certStream;
}
//在同層級下面新建WXPayConfig service層
package com.example.gasstation.config;
import java.io.InputStream;
public interface WXPayConfig {
InputStream getCertStream();//不要問我為啥不另起一行,因為我懶
}
}
三、引入 WxPayServiceImpl 實作類
package com.example.gasstation.server.impl;
import com.example.gasstation.config.MyWXPayConfig;
import com.example.gasstation.entity.Result;
import com.example.gasstation.mapper.PayMapper;
import com.example.gasstation.model.Money_transfer;
import com.example.gasstation.model.Pay;
import com.example.gasstation.server.WxPayService;
import com.example.gasstation.util.HttpClientUtil;
import com.example.gasstation.util.WXPayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
@Service
public class WxPayServiceImpl implements WxPayService {
@Autowired
private MyWXPayConfig wxPayAppConfig;
@Autowired
private PayMapper payMapper;
@Override
public String save(String orderNo, double amount, String body,Integer uid) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設定日期格式
// 1. 生成訂單
// 訂單號,流水號,金額,付款狀態,創建時間
String product_id = WXPayUtils.generateUUID();
Pay pay = new Pay();//這里新建一個物體類 用處存入資料庫
pay.setTradeNo(product_id);
pay.setOutTradeNo(orderNo);
pay.setBody(body);
pay.setPaystatus(1);
pay.setUid(uid);
pay.setTotalAmount(amount);
pay.setGmtCreate(df.format(new Date()));
pay.setTradeStatus("0");
pay.setAppId(wxPayAppConfig.getAppID());
// 生成預支付訂單,保存到資料庫
payMapper.insert(pay);
// 呼叫統一下單方法,回傳 codeUrl 地址
String codeUrl = unifiedOrder(product_id,orderNo,amount,body);
return codeUrl;
}
private String unifiedOrder(String product_id, String orderNo, double amount, String body){
// 生成簽名
try{
SortedMap<String, String> params = new TreeMap<>();
params.put("appid",wxPayAppConfig.getAppID());
params.put("mch_id",wxPayAppConfig.getMchID());
params.put("nonce_str", WXPayUtils.generateUUID());
params.put("body",body); // 商品描述
params.put("out_trade_no",orderNo); // 商戶訂單號
params.put("total_fee",String.valueOf((int)(amount*100))); // 標價金額(單位為分)
params.put("spbill_create_ip", "這里寫服務器IP"); // 終端IP
params.put("notify_url", wxPayAppConfig.getPayNotifyUrl()); // 異步接收微信支付結果通知的回呼地址
params.put("trade_type","NATIVE"); // 交易型別
params.put("product_id",product_id); // 微信支付要求NATIVE支付,此引數必填
// sign 簽名
String sign = WXPayUtils.createSign(params, wxPayAppConfig.getKey());
params.put("sign",sign);
System.out.println(sign);
// map轉xml
String payXml = WXPayUtils.mapToXml(params);
System.out.println(payXml);
// 統一下單
String s = HttpClientUtil.doPost(wxPayAppConfig.getUNIFIED_ORDER_URL(), payXml, 10000);
if(null == s){
return null;
}
Map<String, String> unifiedOrderMap = WXPayUtils.xmlToMap(s);
System.out.println(unifiedOrderMap.toString());
if(unifiedOrderMap != null){
// 前臺添加定時器,進行輪詢操作,直到支付完畢
return unifiedOrderMap.get("code_url");
}
} catch (Exception e){
e.printStackTrace();
}
return null;
}
}
四、引入WxPayService層
package com.example.gasstation.server;
import com.example.gasstation.model.Money_transfer;
public interface WxPayService {
String save(String orderNo, double amount, String body,Integer uid);
boolean callBackPayUpdate(String outTradeNo,String totalFee);
}
五、引入Util類
package com.example.gasstation.util;
import com.github.wxpay.sdk.WXPayUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.*;
/**
* @Author qjp
*/
public class WXPayUtils {
/**
* XML格式字串轉換為Map
*
* @param strXML XML字串
* @return XML資料轉換后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtils.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 將Map轉換為XML格式的字串
*
* @param data Map型別資料
* @return XML格式的字串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
Document document = WXPayXmlUtil.newDocument();
Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* 生成微信支付sign
*/
public static String createSign(SortedMap<String, String> params, String key){
StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> es = params.entrySet();
Iterator<Map.Entry<String, String>> it = es.iterator();
while(it.hasNext()){
Map.Entry<String, String> entry = it.next();
String k = entry.getKey();
String v = entry.getValue();
if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
sb.append(k + "=" + v + "&");
}
}
sb.append("key=").append(key);
String sign = MD5Util.MD5(sb.toString()).toUpperCase();
return sign;
}
/**
* 校驗簽名
* @param params
* @param key
* @return
*/
public static Boolean isCorrectSign(SortedMap<String, String> params, String key){
String sign = createSign(params, key);
String wxPaySign = params.get("sign").toUpperCase();
return wxPaySign.equals(sign);
}
/**
* 獲取有序map
* @param map
*/
public static SortedMap<String, String> getSortedMap(Map<String, String> map){
SortedMap<String, String> sortedMap = new TreeMap<>();
Iterator<String> it = map.keySet().iterator();
while(it.hasNext()){
String key = it.next();
String value = map.get(key);
String temp = "";
if(null != value){
temp = value.trim();
}
sortedMap.put(key, value);
}
return sortedMap;
}
/**
* 日志
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 獲取當前時間戳,單位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 獲取當前時間戳,單位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* 生成UUID(用來表示一筆訂單)
* @return
*/
public static String generateUUID(){
String uuid = UUID.randomUUID().toString()
.replaceAll("-","")
.substring(0,32);
return uuid;
}
}
引入WXPayXmlUtil類
package com.example.gasstation.util;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
public final class WXPayXmlUtil {
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}
六、引入WxPayController類
提示:到這里沒有報錯咱們已經成功一半啦
@RestController
@RequestMapping("/wxPay")
public class WxPayController {
@Autowired
private WxPayService wxPayService;
@Autowired
private MyWXPayConfig wxPayConfig;
@Autowired
private WebMvcConfigurer webMvcConfigurer;
/**
* 微信支付 生成二維碼
*
* @param money
* @return
*/
@GetMapping("/pay")
public void wxPay(Double money,String body,Integer uid ,HttpServletResponse response){
Double amount = money;//金額
SimpleDateFormat date = new SimpleDateFormat("yyyyMMddHHmmss");
String orderNo = date.format(new Date()) + WXPayUtils.getCurrentTimestampMs();
String url_code = wxPayService.save(orderNo, amount, body,uid);
System.out.println("url_code:----------"+url_code);
if(url_code == null){
throw new NullPointerException();
}
try {
// 生成二維碼配置
Map<EncodeHintType, Object> hints = new HashMap<>();
// 設定糾錯等級
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
// 編碼型別
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
BitMatrix bitMatrix = new MultiFormatWriter().encode(url_code, BarcodeFormat.QR_CODE, 400, 400, hints);
OutputStream outputStream = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* 微信支付回呼介面
*/
@RequestMapping("/callback")
public void OrderCallBack(HttpServletRequest request, HttpServletResponse response) {
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
// BufferedReader是包裝設計模式,性能更高
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuffer stringBuffer = new StringBuffer();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line);
}
bufferedReader.close();
inputStream.close();
Map<String, String> callBackMap = WXPayUtils.xmlToMap(stringBuffer.toString());
System.out.println(callBackMap.toString());
SortedMap<String, String> sortedMap = WXPayUtils.getSortedMap(callBackMap);
// 校驗簽名是否正確
if (WXPayUtils.isCorrectSign(sortedMap, wxPayConfig.getKey())) {
System.out.println("簽名校驗成功!");
// 更新訂單狀態
if ("SUCCESS".equals(sortedMap.get("result_code"))) {
String outTradeNo = sortedMap.get("out_trade_no"); // 流水號
String totalFee = sortedMap.get("total_fee"); // 交易金額
if (wxPayService.callBackPayUpdate(outTradeNo, totalFee)) { // 通知微信訂單處理成功
response.setContentType("text/xml");
response.setContentType("content-type");
response.getWriter().println("<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml>");
//這里說明告訴微信你已經成功啦,別給老子重復回呼我的方法啦,這里有一個坑,
response.setContentType("text/xml");
response.getWriter().println("SUCCESS")
//本身我就只有這兩句話,然后就導致微信一直回呼我的方法,廢了半天的勁才搞好啦,
//原因就是格式不對,給他回傳的值他不認識,這里可以看一下微信的支付開發檔案,雖然檔案寫的很垃圾
}
}
// 未成功,就都處理為失敗訂單
response.setContentType("text/html");
response.getWriter().println("fail");
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
七、MD5加密
@Slf4j
public class MD5Util {
public static String MD5(String source) {
return encodeMd5(source.getBytes());
}
private static String encodeMd5(byte[] source) {
try {
return encodeHex(MessageDigest.getInstance("MD5").digest(source));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
private static String encodeHex(byte[] bytes) {
StringBuffer buffer = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
if(((int) bytes[i] & 0xff) < 0x10) {
buffer.append("0");
}
buffer.append(Long.toString((int) bytes[i] & 0xff, 16));
}
return buffer.toString();
}
}
總結
有什么不對的地方歡迎各位碼友指出問題,因為我也是第一次做這個微信掃碼支付
回呼方法回傳型別是void 你設定其他回傳型別,就會跟你 給微信返的起沖突,就會導致報錯
有知道怎么解決的,歡迎指導,
此文章如有沖突,請聯系我下架,上傳這個只為記錄,不作為其他用處
程式員不怕踩坑,就拍沒坑可踩,加油打工人!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/253078.html
標籤:java
上一篇:this關鍵字
