这是微信下单支付的建议时序图,我们在日常开发过程中结合自身的订单业务场景,进行订单的处理。我这里就以一个简单的购买单个产品的业务逻辑展示微信支付的Java后台代码。这里是微信官方文档:
https://pay.weixin.qq.com/wiki/doc/api/external/jsapi.php?chapter=9_1
1.准备好工具类
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
package com.demo.utils.wxpay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
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.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 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) {
WXPayUtil.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 {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.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();
org.w3c.dom.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 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, WXPayConstants.SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (WXPayConstants.SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
java.security.MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 日志
* @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();
}
}
package com.demo.util;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonElement;
import org.apache.commons.codec.Charsets;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.ArrayList;
public final class HttpUtil {
private static final Logger logger = LoggerFactory.getLogger(HttpUtil.class);
private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36";
private HttpUtil() {
}
public static String httpClientPost(String url) {
String result = "";
HttpClient client = new HttpClient();
GetMethod getMethod = new GetMethod(url);
try {
client.executeMethod(getMethod);
result = getMethod.getResponseBodyAsString();
} catch (Exception e) {
logger.error(e.toString());
} finally {
getMethod.releaseConnection();
}
return result;
}
public static String httpClientPost(String url, ArrayList<NameValuePair> list) {
String result = "";
HttpClient client = new HttpClient();
PostMethod postMethod = new PostMethod(url);
try {
NameValuePair[] params = new NameValuePair[list.size()];
for (int i = 0; i < list.size(); i++) {
params[i] = list.get(i);
}
postMethod.addParameters(params);
client.executeMethod(postMethod);
result = postMethod.getResponseBodyAsString();
} catch (Exception e) {
logger.error(e.toString());
} finally {
postMethod.releaseConnection();
}
return result;
}
/**
* if response == null -> code != 200
*
* @param url
* @param json
* @return
* @throws IOException
*/
public static JsonElement postJson(String url, String json) throws Exception {
return postJsonWithHeader(url, json, null);
}
public static JsonElement postJsonWithHeader(String url, String json, Header[] headers) throws Exception {
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(url);
JsonElement response = null;
try {
StringEntity s = new StringEntity(json);
s.setContentEncoding("UTF-8");
s.setContentType("application/json");
post.setEntity(s);
if (ArrayUtils.isNotEmpty(headers)) {
post.setHeaders(headers);
}
HttpResponse res = client.execute(post);
String result = EntityUtils.toString(res.getEntity());
response = GsonUtil.jsonParser().parse(result);
if (response != null) {
if (response.isJsonObject())
response.getAsJsonObject().addProperty("statusCode", res.getStatusLine().getStatusCode());
if (response.isJsonArray()) {
if (response.getAsJsonArray() != null)
response.getAsJsonArray().get(0).getAsJsonObject()
.addProperty("statusCode", res.getStatusLine().getStatusCode());
}
}
} catch (IOException e) {
LogUtils.error(logger, "url:{}, param:{}", e, url, json);
throw e;
}
return response;
}
public static JsonElement patchJsonWithHeader(String url, String json, Header[] headers) throws Exception {
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpPatch patch = new HttpPatch(url);
JsonElement response = null;
try {
StringEntity s = new StringEntity(json);
s.setContentEncoding("UTF-8");
s.setContentType("application/json");
patch.setEntity(s);
if (ArrayUtils.isNotEmpty(headers)) {
patch.setHeaders(headers);
}
HttpResponse res = client.execute(patch);
String result = EntityUtils.toString(res.getEntity());
response = GsonUtil.jsonParser().parse(result);
if (response != null) {
if (response.isJsonObject())
response.getAsJsonObject().addProperty("statusCode", res.getStatusLine().getStatusCode());
if (response.isJsonArray()) {
if (response.getAsJsonArray() != null)
response.getAsJsonArray().get(0).getAsJsonObject()
.addProperty("statusCode", res.getStatusLine().getStatusCode());
}
}
} catch (IOException e) {
LogUtils.error(logger, "url:{}, param:{}", e, url, json);
throw e;
}
return response;
}
public static <T> T postJson(String url, String json, Class<T> tClass) throws Exception {
return postJsonWithHeader(url, json, null, tClass);
}
public static <T> T postJsonWithHeader(String url, String json, Header[] headers, Class<T> tClass) throws Exception {
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost(url);
T payload;
try {
StringEntity s = new StringEntity(json);
s.setContentEncoding("UTF-8");
s.setContentType("application/json");
post.setEntity(s);
if (ArrayUtils.isNotEmpty(headers)) {
post.setHeaders(headers);
}
HttpResponse res = client.execute(post);
String result = EntityUtils.toString(res.getEntity());
payload = JSONObject.parseObject(result, tClass);
} catch (IOException e) {
LogUtils.error(logger, "url:{}, param:{}", e, url, json);
throw e;
}
return payload;
}
public static String postXml(String url, String xmlStr) throws Exception {
int timeout = 5 * 1000;
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(timeout)
.setConnectTimeout(timeout)
.setSocketTimeout(timeout)
.build();
CloseableHttpClient client = HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.build();
HttpPost post = new HttpPost(url);
try {
StringEntity s = new StringEntity(xmlStr, "UTF-8");
post.setEntity(s);
post.setHeader("Content-Type", "text/xml; charset=UTF-8");
HttpResponse res = client.execute(post);
String result = EntityUtils.toString(res.getEntity(), "UTF-8");
return result;
} catch (IOException e) {
LogUtils.error(logger, "url:{}, param:{}", e, url, xmlStr);
throw e;
}
}
public static String postSSL(String url, String data, String certPath, String certPass) {
HttpsURLConnection conn = null;
OutputStream out = null;
InputStream inputStream = null;
BufferedReader reader = null;
try {
KeyStore clientStore = KeyStore.getInstance("PKCS12");
clientStore.load(new FileInputStream(certPath), certPass.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(clientStore, certPass.toCharArray());
KeyManager[] kms = kmf.getKeyManagers();
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(kms, null, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
URL _url = new URL(url);
conn = (HttpsURLConnection) _url.openConnection();
conn.setConnectTimeout(25000);
conn.setReadTimeout(25000);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("User-Agent", DEFAULT_USER_AGENT);
conn.connect();
out = conn.getOutputStream();
out.write(data.getBytes(Charsets.toCharset("UTF-8")));
out.flush();
inputStream = conn.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charsets.toCharset("UTF-8")));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(reader);
IOUtils.closeQuietly(inputStream);
if (conn != null) {
conn.disconnect();
}
}
}
}
2.申请微信订单预支付参数
/**
* 微信提交预订单
* @author wanghzi
*/
public JSONObject submitOrder() {
JSONObject result = new JSONObject();
try {
//构建请求参数
String xmlStr = produceToWXOrderXml();
//发送下单请求
String xmlResult = HttpUtil.postXml("https://api.mch.weixin.qq.com/pay/unifiedorder", xmlStr);
//得到微信下单结果,转换成Map
Map<String,String> resultMap = WXPayUtil.xmlToMap(xmlResult);
if (!"SUCCESS".equals(resultMap.get("return_code"))) {
throw new Exception("Request to make wechat order returned FAIL");
}
if (resultMap.get("prepay_id") == null){
throw new Exception("prepayId is null");
}
Long timeStamp = System.currentTimeMillis()/1000;
//重新计算sign
Map<String,String> signData = new HashMap<>();
signData.put("appId","你的appid");
signData.put("timeStamp",String.valueOf(timeStamp));
signData.put("nonceStr",resultMap.get("nonce_str"));
signData.put("package","prepay_id="+resultMap.get("prepay_id").toString());
signData.put("signType","MD5");
String paySign = WXPayUtil.generateSignature(signData, "wxPayAPIKey");//你的支付apikey,计算签名需要
//封装返回前端的参数对象
result.put("trade_type", resultMap.get("trade_type"));
result.put("prepay_id", resultMap.get("prepay_id"));
result.put("nonce_str", resultMap.get("nonce_str"));
result.put("sign_type", "MD5");
result.put("sign", paySign);
result.put("timestamp", timeStamp);
result.put("orderId", "当时生成的业务订单号");
return result;
} catch (Exception e) {
logger.error("Error trying to get data from wechat.", e);
throw new DPRuntimeException(Error.WECHAT_PAY_FAILED_TO_MAKE_ORDER);
}
}
public String produceToWXOrderXml() throws Exception {
//订单支付时间
Map<String, String> map = new HashMap();
map.put("appid", "你的appid");
map.put("body", "商品名称");
map.put("mch_id", "你的商户号");
map.put("nonce_str", WXPayUtil.generateNonceStr());
map.put("notify_url", "localhost:8080/ns/qk/order/paid");//支付成功后微信服务器会调用你的通知接口
map.put("out_trade_no","我们自己业务生成的全局唯一订单号");//业务orderId
map.put("spbill_create_ip", "用户id,根据request获取");
map.put("total_fee", String.valueOf(100));//支付金额(单位:分)
map.put("trade_type", "MWEB");//支付方式(JSAPI、NATIVE、APP、MWEB)
//map.put("openid", openId);//trade_type=JSAPI时必传
map.put("time_expire", "20200101121212");//yyyyMMddHHmmss格式的当前时间
map.put("fee_type", "CNY");//币种
return WXPayUtil.generateSignedXml(map, "wxPayAPIKey");//你的支付apikey,计算签名需要
}
3.后台编写回调接口
注意:回调接口一定要外网能访问的接口,且不能带有权限限制和参数限制。
@ApiOperation(value = "微信支付回调接口")
@PostMapping("/paid")
public String paymentNotify(HttpServletRequest request) throws Exception {
try {
ServletInputStream inputStream = request.getInputStream();
String body = IOUtils.toString(inputStream, Charsets.UTF_8);
Map<String, String> data = WXPayUtil.xmlToMap(body);
//自己的业务逻辑,比如修改订单为成功
} catch (Exception e) {
return "ERROR";
}
Map<String, String> result = new HashMap<>();
result.put("return_code", WXPayConstants.SUCCESS);
result.put("return_msg", WXPayConstants.OK);
return WXPayUtil.mapToXml(result);
}