Java微信支付——app支付
一、获取微信支付四大参数
首先获取两个账号:
1、微信开放平台(开通微信支付功能):
1》appId 2》appSecret
2、微信商户平台:
商户标识码 mch_id
注意:获取API密钥

二、开发流程
微信支付原理是通过引用官方提供的统一支付接口文档,并在该接口中完成统一支付接口中的下单功能模块的调用。系统会将微信服务器返回的相关参数进行进一步处理和优化后并最终传递回至APP客户端进行处理。
官方文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
其中必填参数:
1、appid
2、mch_id(商户号)
3、nonce_str(随机字符串)
4、sign(签名)
5、body(商品描述)
6、out_trade_no (商品订单号)
7、total_fee(标价金额)
8、spbill_create_ip(终端IP)
9、notify_url(通知地址)
10、trade_type(交易类型)
1》首先从官网把SDK与DEMO下载下来,可以使用里面的WXPayUtil工具类
2》获取必填项的值
1、appid 在刚开始获取微信支付四大参数中获的
2、mch_id 在刚开始获取微信支付四大参数中获的,商户号ID
nonce_str 随机编码用WXPayUtil中的generateNonceStr()方法生成
4、用户所涉及的身份信息(注意:必须确保身份信息以UTF-8编码存储)
5、out_trade_no 订单号,一定要保证唯一
6、total_fee 支付金额 ,单位是分(一定要换算成分进行操作)
7、spbill_create_ip IP地址
8、notify_url 回调地址
9、trade_type 支付类型 此支付类型为 "APP"
10、调用WXPayUtil类中的generateSignature方法进行签名操作时,请注意以下事项:首先将除sign之外的其他9个必要参数放入map中;其次设置好key字段取自四大配置参数中的API密钥属性值( paternerKey)。
3》把以上10个参数转换成xml形式,发送post请求来获取prepayid
4》再次整合信息发送到APP前端:
1、appid
2、partnerid 商户Id
3、prepayid 上一步Map中获取的prepayid
4、package “Sign=WXPay”
5、noncestr 随机数
6、timestamp 时间戳
该段描述了如何通过WXPayUtil的方法生成签名操作。具体来说,在finalMap<String,String>数据结构中包含除签名之外的六个参数,并通过指定API密钥字段(partnerKey)来完成签名过程
5》支付成功回调方法
三、示例
1》db.properties
#微信支付配置
#应用ID
appid=xxxxxxxxx
#应用密钥
appSecret=xxxxxxxxxx
#应用key
appkey=xxxxxxxxxxx
#商户号
mch_id=xxxxxx
#发送请求地址
orderPayUrl=https://api.mch.weixin.qq.com/pay/unifiedorder
#回调地址
notify_url=xxxxxxx
2》整合信息发送到app端
package com.jeeplus.modules.account.service;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.jeeplus.common.json.AjaxJson;
import com.jeeplus.modules.account.entity.OrderMsg;
import com.jeeplus.modules.account.entity.Royalty;
import com.jeeplus.modules.account.utils.HttpRequest;
import com.jeeplus.modules.account.utils.WXPayUtil;
import com.jeeplus.modules.sys.entity.AppUser;
import com.jeeplus.modules.sys.entity.SuperiorUser;
import com.jeeplus.modules.sys.service.AppUserService;
/** * 微信支付
* * @author Administrator
* */
@Service
@Transactional(readOnly = true)
public class WeChatPayService {
private static ResourceBundle rb = null;
private static String appid = null;
private static String appSecret = null;
private static String mch_id = null;
private static String orderPayUrl = null;
private static String notify_url = null;
private static String appkey = null;
@Autowired
private PayMessage payMessage;
@Autowired
private AppUserService appUserService;
@Autowired
private RoyaltyService royaltyService;
@Autowired
private IncomeMoneyService incomeMoneyService;
@Autowired
private BillofladingService billofladingService;
@Autowired
private OrderMsgRecordService orderMsgRecordService;
@Autowired
private BalanceService balanceService;
private static DecimalFormat df = new DecimalFormat("######0");
/** * 获取资源
*/
private void getResoures() {
rb = ResourceBundle.getBundle("WeChatPay");
appid = rb.getString("appid");
appSecret = rb.getString("appSecret");
mch_id = rb.getString("mch_id");
orderPayUrl = rb.getString("orderPayUrl");
notify_url = rb.getString("notify_url");
appkey = rb.getString("appkey");
}
/** * 获取支付信息
* * @param aj
* @param myCouponId
* 我的优惠券
* @param subId
* @param subType
* 课程类型
* @param payType
* 支付类型
* @param userId
*/
@Transactional(readOnly = false)
public AjaxJson getMessage(AjaxJson aj, String myCouponId, String linkId,
int subType, int payType, String userId, HttpServletRequest request) {
try {
if (rb == null) {
getResoures();
}
Map<String, Object> orderMap = payMessage.getOrderMsg(linkId,
subType, myCouponId);//自己获取的逻辑
Map<String, String> payMapMsg = getPayMapMsg(orderMap, request);
String backXml = HttpRequest.sendPost(orderPayUrl,
WXPayUtil.mapToXml(payMapMsg));
Map<String, String> backMap = WXPayUtil.xmlToMap(backXml);
if (backMap != null && !backMap.isEmpty()
&& "SUCCESS".equals(backMap.get("result_code"))) {
payMapMsg = getBackMapMsg(backMap);
aj.setSuccess(true);
aj.put("executeType", payType);
aj.put("payResponse", payMapMsg);
orderMsgRecordService.saveOrder(userId, myCouponId, linkId,
orderMap.get("orderNo") + "", subType, payType,
orderMap.get("actualPay") + "",
orderMap.get("payable") + "",
Integer.parseInt(orderMap.get("isCoupon") + ""));
}
} catch (Exception e) {
e.printStackTrace();
}
return aj;
}
/** * 获取支付map信息
* * @param orderMap
* 订单信息
* @return
*/
private Map<String, String> getPayMapMsg(Map<String, Object> orderMap,
HttpServletRequest request) {
Map<String, String> map = new HashMap<String, String>();
map.put("appid", appid);
map.put("mch_id", mch_id);
map.put("nonce_str", WXPayUtil.generateNonceStr());
map.put("body", orderMap.get("subject") + "");
map.put("device_info", "WEB");
map.put("out_trade_no", orderMap.get("orderNo").toString());// 订单号
/* * map.put("total_fee", df.format(Double.parseDouble(orderMap
* .get("actualPay") + "") * 100));
*/// 单位分
map.put("total_fee", "1");
map.put("spbill_create_ip", WXPayUtil.getRemoteAddrIp(request));
map.put("notify_url", notify_url);
map.put("trade_type", "APP");
try {
map.put("sign", WXPayUtil.generateSignature(map, appkey));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(map);
return map;
}
/** * 获取返回的信息
* * @param backXml
* @return
* @throws Exception
*/
private Map<String, String> getBackMapMsg(Map<String, String> backMap)
throws Exception {
Map<String, String> map = new HashMap<String, String>();
map.put("appid", appid);
map.put("partnerid", mch_id);
map.put("prepayid", backMap.get("prepay_id"));
map.put("package", "Sign=WXPay");
map.put("noncestr", WXPayUtil.generateNonceStr());
map.put("timestamp", Long.toString(WXPayUtil.getCurrentTimestamp()));
map.put("sign", WXPayUtil.generateSignature(map, appkey));
return map;
}
/** * 微信支付返回
* * @param request
* @return
*/
@Transactional(readOnly = false)
public String callBackSuccess(HttpServletRequest request) {
try {
InputStream inputStream = request.getInputStream();
String xml = WXPayUtil.inputStream2String(inputStream, "UTF-8");
Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);// 将微信发的xml转map
if (notifyMap != null && !notifyMap.isEmpty()
&& notifyMap.get("return_code").equals("SUCCESS")) {
String out_trade_no = notifyMap.get("out_trade_no");// 订单号
String trade_no = notifyMap.get("transaction_id");// 内部交易号
//支付成功之后逻辑
}
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
} catch (Exception e) {
e.getMessage();
}
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[FAIL]]></return_msg></xml>";
}
}
3》工具类
1、WXPayUtil.java
import com.jeeplus.modules.account.utils.WXPayConstants.SignType;
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.servlet.http.HttpServletRequest;
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.ByteArrayOutputStream;
import java.io.IOException;
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;
}
}
/** * 日志
* * @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/** * 将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;
}
/** * 获取当前时间戳,单位秒
* * @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/** * 获取当前时间戳,单位毫秒
* * @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/** * 获取ip 地址
* * @param request
* @return
*/
public static String getRemoteAddrIp(HttpServletRequest request) {
// 获取请求ip地址
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip.indexOf(",") != -1) {
String[] ips = ip.split(",");
ip = ips[0].trim();
}
return ip;
}
/** * 获取随机字符串 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);
}
/** * 流转换xml字符串
* * @param in
* @param charset
* @return
*/
public static String inputStream2String(InputStream in, String charset) {
if (charset == null) {
charset = "UTF-8";
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
String result = null;
int bufferSize = 2048;
byte[] data = new byte[bufferSize];
int count = -1;
try {
while ((count = in.read(data, 0, bufferSize)) != -1) {
outputStream.write(data, 0, count);
}
result = new String(outputStream.toByteArray(), charset);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/** * 生成签名
* * @param data
* 待签名数据
* @param key
* API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data,
String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
/** * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
* * @param data
* 待签名数据
* @param key
* API密钥
* @param signType
* 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data,
String key, 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 (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
} else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
} else {
throw new Exception(
String.format("Invalid sign_type: %s", signType));
}
}
/** * 生成 MD5
* * @param data
* 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F' };
try {
MessageDigest mdInst = MessageDigest.getInstance("MD5");
mdInst.update(data.getBytes("UTF-8"));
byte[] md = mdInst.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/** * 生成 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();
}
}
2、WXPayXmlUtil.java
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/** * 2018/7/3
*/
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();
}
}
3、HttpRequest.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
public class HttpRequest {
/** * 向指定URL发送GET方法的请求
* * @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
System.out.println(urlNameString);
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/** * 向指定 URL 发送POST方法的请求
* * @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流(进行编码)
out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(),"UTF-8"));
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
conn.getInputStream(), "utf-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}
四、body中文乱码的问题
1、一定要body中文是utf-8格式
2、MD5加密一定是utf-8格式
3、在签名发送获取prepayid 时也要转成utf-8格式
