最近公司需求,需要在微信公众号内完成支付,找到官方文档,文档还可以,讲的也挺详细,不过有一个地方很坑爹,就是微信内H5调起支付需要一个签名,而他给出的参考签名方式跟统一下单签名一致,害的我以为,他这个签名就是统一下单那个签名,后面找了很久看了好多博客才明白这个签名是怎么生成的(JS-SDK中微信支付有说明)。弄了半天,汗颜。下面进入正题。
微信支付分为很多种,有刷卡支付,公众号支付,扫码支付,APP支付,H5支付,小程序支付,本教程只讲解公众号支付。主要分三大块来讲解。
1.在商家后台设置支付目录, 设置路径:商户平台-->产品中心-->开发配置。填写你项目所在域名2.在公众号后台 设置授权域名,因为在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。3.调通统一下单与 支付结果通知接口(详细看demo)
tip:如果不知道如何获取openid,可以看本人另外一篇博客微信公众号开发《一》OAuth2.0网页授权认证获取用户的详细信息,实现自动登陆
/**
* 统一下单请求参数
* @author lhao
*
*/
public class UnifiedOrderRequest {
//变量名 字段名 必填 类型 示例值 描述
private String appid;// 公众账号ID 是 String(32) wxd678efh567hg6787 微信支付分配的公众账号ID(企业号corpid即为此appId)
private String mch_id;//商户号 必填 String(32) 1230000109 微信支付分配的商户号
private String device_info; //设备号 否 String(32) 013467007045764 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
private String nonce_str;//随机字符串 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法
private String sign;//签名 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法
private String sign_type;//签名类型 sign_type 否 String(32) HMAC-SHA256 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
private String body;//商品描述 body 是 String(128) 腾讯充值中心-QQ会员充值 商品简单描述,该字段请按照规范传递,具体请见参数规定
private String detail;//商品详情 detail 否 String(6000) 单品优惠字段(暂未上线)
private String attach;//附加数据 attach 否 String(127) 深圳分店 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
private String out_trade_no;//商户订单号 out_trade_no 是 String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号
private String fee_type;//标价币种 fee_type 否 String(16) CNY 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型
private String total_fee;//标价金额 total_fee 是 Int 88 订单总金额,单位为分,详见支付金额
private String spbill_create_ip;//终端IP spbill_create_ip 是 String(16) 123.12.12.123 APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
private String time_start;//交易起始时间 time_start 否 String(14) 20091225091010 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
private String time_expire;//交易结束时间 time_expire 否 String(14) 20091227091010 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
private String goods_tag;//订单优惠标记 goods_tag 否 String(32) WXG 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠
private String notify_url;//通知地址 notify_url 是 String(256) http://www.weixin.qq.com/wxpay/pay.php 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
private String trade_type;//交易类型 trade_type 是 String(16) JSAPI 取值如下:JSAPI,NATIVE,APP等,说明详见参数规定
private String product_id;//商品ID product_id 否 String(32) 12235413214070356458058 trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
private String limit_pay;//指定支付方式 limit_pay 否 String(32) no_credit 上传此参数no_credit--可限制用户不能使用信用卡支付
private String openid;//用户标识 openid 否 String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
//省略get,set方法
}
/**
* 统一下单返回参数
* @author lhao
*
*/
public class UnifiedOrderRespose {
private String return_code; //返回状态码
private String return_msg; //返回信息
private String appid; //公众账号ID
private String mch_id; //商户号
private String device_info; //设备号
private String nonce_str; //随机字符串
private String sign; //签名
private String result_code; //业务结果
private String err_code; //错误代码
private String err_code_des; //错误代码描述
private String trade_type; //交易类型
private String prepay_id; //预支付交易会话标识
private String code_url; //二维码链接
//省略get/set方法
}
/**
* 常量
*/
public class WXPayConstants {
public enum SignType {
MD5, HMACSHA256
}
public static final String FAIL = "FAIL";
public static final String SUCCESS = "SUCCESS";
public static final String HMACSHA256 = "HMAC-SHA256";
public static final String MD5 = "MD5";
public static final String FIELD_SIGN = "sign";
public static final String FIELD_SIGN_TYPE = "sign_type";
}
import java.security.MessageDigest;
/**
* MD5工具类
* @author lh
*/
public class MD5Util {
public final static String MD5(String s) {
char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
try {
byte[] btInput = s.getBytes();
// 鑾峰緱MD5鎽樿绠楁硶鐨�MessageDigest 瀵硅�?
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 浣跨敤鎸囧畾鐨勫瓧鑺傛洿鏂版憳瑕�?
mdInst.update(btInput);
// 鑾峰緱�?�嗘�?
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];
}
String md5Str = new String(str);
return md5Str;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.security.MessageDigest;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import wp.WXPayConstants.SignType;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
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 org.apache.commons.lang.RandomStringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 支付工具类
* @author lh
*/
public class WXPayUtil {
private static Logger log = LoggerFactory.getLogger(WXPayUtil.class);
/**
* 生成订单对象信息
* @param orderId 订单号
* @param appId 微信appId
* @param mch_id 微信分配的商户ID
* @param body 支付介绍主体
* @param price 支付价格(放大100倍)
* @param spbill_create_ip 终端IP
* @param notify_url 异步直接结果通知接口地址
* @param noncestr
* @return
*/
public static Map createOrderInfo(Map requestMap) {
//生成订单对象
UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest();
unifiedOrderRequest.setAppid(requestMap.get("appId"));//公众账号ID
unifiedOrderRequest.setBody(requestMap.get("body"));//商品描述
unifiedOrderRequest.setMch_id(requestMap.get("mch_id"));//商户号
unifiedOrderRequest.setNonce_str(requestMap.get("noncestr"));//随机字符串
unifiedOrderRequest.setNotify_url(requestMap.get("notify_url"));//通知地址
unifiedOrderRequest.setOpenid(requestMap.get("userWeixinOpenId"));
unifiedOrderRequest.setDetail(requestMap.get("detail"));//详情
unifiedOrderRequest.setOut_trade_no(requestMap.get("out_trade_no"));//商户订单号
unifiedOrderRequest.setSpbill_create_ip(requestMap.get("spbill_create_ip"));//终端IP
unifiedOrderRequest.setTotal_fee(requestMap.get("payMoney")); //金额需要扩大100倍:1代表支付时是0.01
unifiedOrderRequest.setTrade_type("JSAPI");//JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
SortedMap packageParams = new TreeMap();
packageParams.put("appid", unifiedOrderRequest.getAppid());
packageParams.put("body", unifiedOrderRequest.getBody());
packageParams.put("mch_id", unifiedOrderRequest.getMch_id());
packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str());
packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
packageParams.put("openid", unifiedOrderRequest.getOpenid());
packageParams.put("detail", unifiedOrderRequest.getDetail());
packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());
packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());
packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());
packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());
try {
unifiedOrderRequest.setSign(generateSignature(packageParams,"你的密匙"));//签名
} catch (Exception e) {
e.printStackTrace();
}
//将订单对象转为xml格式
xstream.alias("xml", UnifiedOrderRequest.class);//根元素名需要是xml
System.out.println("封装好的统一下单请求数据:"+xstream.toXML(unifiedOrderRequest).replace("__", "_"));
Map responseMap = new HashMap();
responseMap.put("orderInfo_toString", xstream.toXML(unifiedOrderRequest).replace("__", "_"));
responseMap.put("unifiedOrderRequest",unifiedOrderRequest);
return responseMap;
}
/**
* 生成签名
* @param appid_value
* @param mch_id_value
* @param productId
* @param nonce_str_value
* @param trade_type
* @param notify_url
* @param spbill_create_ip
* @param total_fee
* @param out_trade_no
* @return
*/
private static String createSign(UnifiedOrderRequest unifiedOrderRequest) {
//根据规则创建可排序的map集合
SortedMap packageParams = new TreeMap();
packageParams.put("appid", unifiedOrderRequest.getAppid());
packageParams.put("body", unifiedOrderRequest.getBody());
packageParams.put("mch_id", unifiedOrderRequest.getMch_id());
packageParams.put("nonce_str", unifiedOrderRequest.getNonce_str());
packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());
packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());
packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());
packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();//字典序
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
//为空不参与签名、参数名区分大小写
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
//第二步拼接key,key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
sb.append("key="+"你的密匙");
String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密
log.error("方式一生成的签名="+sign);
return sign;
}
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
String NodeName = "";
@SuppressWarnings("unchecked")
public void startNode(String name, Class clazz) {
NodeName = name;
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
if(!NodeName.equals("detail")){
writer.write(text);
}else{
writer.write("");
}
} else {
writer.write(text);
}
}
};
}
});
//xml解析
public static SortedMap doXMLParseWithSorted(String strxml) throws Exception {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
SortedMap m = new TreeMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
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);
}
//关闭流
in.close();
return m;
}
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if(!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("" + name + ">");
}
}
return sb.toString();
}
/**
* 调统一下单API
* @param orderInfo
* @return
*/
public static UnifiedOrderRespose httpOrder(String orderInfo) {
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
//加入数据
conn.setRequestMethod("POST");
conn.setDoOutput(true);
BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
buffOutStr.write(orderInfo.getBytes("UTF-8"));
buffOutStr.flush();
buffOutStr.close();
//获取输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line = null;
StringBuffer sb = new StringBuffer();
while((line = reader.readLine())!= null){
sb.append(line);
}
//将请求返回的内容通过xStream转换为UnifiedOrderRespose对象
xstream.alias("xml", UnifiedOrderRespose.class);
UnifiedOrderRespose unifiedOrderRespose = (UnifiedOrderRespose)xstream.fromXML(sb.toString());
return unifiedOrderRespose;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map xmlToMap(String strXML) throws Exception {
try {
Map data = new HashMap();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.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 data) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.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 data, String key) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map data, String key, 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 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 data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map data, String key, 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 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 data, String key, SignType signType) throws Exception {
Set 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 {
log.error("获取签名失败,失败原因:"+String.format("Invalid sign_type: %s", signType));
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 获取随机字符串 Nonce Str
* @return String 随机字符串
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* Map转xml数据
*/
public static String GetMapToXML(Map param){
StringBuffer sb = new StringBuffer();
sb.append("");
for (Map.Entry entry : param.entrySet()) {
sb.append("<"+ entry.getKey() +">");
sb.append(entry.getValue());
sb.append(""+ entry.getKey() +">");
}
sb.append(" ");
return sb.toString();
}
/**
* 生成 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();
}
/**
* 生成 uuid, 即用来标识一笔单,也用做 nonce_str
* @return
*/
public static String generateUUID() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 支付签名
* @param timestamp
* @param noncestr
* @param packages
* @return
* @throws UnsupportedEncodingException
*/
public static String paySign(String timestamp, String noncestr,String packages,String appId){
Map paras = new HashMap();
paras.put("appid", appId);
paras.put("timestamp", timestamp);
paras.put("noncestr", noncestr);
paras.put("package", packages);
paras.put("signType", "MD5");
StringBuffer sb = new StringBuffer();
Set es = paras.entrySet();//字典序
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
//为空不参与签名、参数名区分大小写
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
String sign = MD5Util.MD5(sb.toString()).toUpperCase();//MD5加密
return sign;
}
}
一些准备就绪,下面我们看看调用方法
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.photovoltaic.model.ResultEnum;
import com.photovoltaic.model.UserOrderInfo;
import com.photovoltaic.model.UserPayInfo;
import com.photovoltaic.util.publicUtil;
import com.photovoltaic.weixin.model.TemplateData;
import com.photovoltaic.weixin.model.TimedTask;
import com.photovoltaic.weixin.model.UnifiedOrderRequest;
import com.photovoltaic.weixin.model.UnifiedOrderRespose;
import com.photovoltaic.weixin.model.WxTemplate;
import com.photovoltaic.weixin.util.WXPayUtil;
import com.photovoltaic.weixin.util.WeixinUtil;
/**
* 微信支付controller
* 1.用户发起微信支付,初始化数据、调用统一下单接口。生成JSAPI页面调用的支付参数并签名(paySign,prepay_id,nonceStr,timestamp)
* 2.js如果返回Ok,提示支付成功,实际支付结果已收到通知为主。
* 3.在微信支付结果通知中,获取微信提供的最终用户支付结果信息,支付结果等信息更新用户支付记录中
* 4.根据微信支付结果通知中的微信订单号调用查询接口,如果查询是已经支付成功,则发送支付成功模板信息给客户
* @author hl
*
*/
@Controller
@RequestMapping(value = "/pay")
public class WXPayController extends WeixinBaseController{
private static Logger log = LoggerFactory.getLogger(WXPayController.class);
/**
* 获取终端IP
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
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();
}
return ip;
}
/**
* 支付初始化
* @param payMoney
* @return
*/
@RequestMapping("/toPayInit")
@ResponseBody
public Map toPay(@RequestParam(value="payMoney",required=true) String payMoney,@RequestParam(value="userWeixinOpenId",required=true) String userWeixinOpenId){
Map map = new HashMap<>();
String orderId = String.valueOf(WXPayUtil.generateUUID());
String noncestr = WXPayUtil.generateNonceStr();
Map requestMap = new HashMap();
requestMap.put("appId", "wx3fda6310becfe801");
requestMap.put("userWeixinOpenId",userWeixinOpenId);
requestMap.put("out_trade_no",orderId);
requestMap.put("mch_id", "商家号");
requestMap.put("payMoney",payMoney);
requestMap.put("spbill_create_ip", getIpAddr(request));
requestMap.put("notify_url", "支付结果回调通知路径");
requestMap.put("noncestr", noncestr);
requestMap.put("body","一元联系");
requestMap.put("detail","获取电站用户的联系方式");
Map requestInfo = WXPayUtil.createOrderInfo(requestMap);
String orderInfo_toString = (String) requestInfo.get("orderInfo_toString");
//判断返回码
UnifiedOrderRespose orderResponse = WXPayUtil.httpOrder(orderInfo_toString);// 调用统一下单接口
//根据微信文档return_code 和result_code都为SUCCESS的时候才会返回code_url
if(null!=orderResponse && "SUCCESS".equals(orderResponse.getReturn_code()) && "SUCCESS".equals(orderResponse.getResult_code())){
String timestamp = String.valueOf(WXPayUtil.getCurrentTimestamp());
map.put("timestamp",timestamp);
map.put("noncestr",noncestr);
UnifiedOrderRequest unifiedOrderRequest = (UnifiedOrderRequest) requestInfo.get("unifiedOrderRequest");
map.put("unifiedOrderRequest",unifiedOrderRequest);
SortedMap packageParams = new TreeMap();
packageParams.put("appId","你的appId");
packageParams.put("signType","MD5");
packageParams.put("nonceStr", noncestr);
packageParams.put("timeStamp", timestamp);
String packages = "prepay_id="+orderResponse.getPrepay_id();
packageParams.put("package",packages);
String sign = null;//这个梗,就是开头说的,弄了半天才弄出来的
try {
sign = WXPayUtil.generateSignature(packageParams,"你的密匙");
} catch (Exception e) {
map.put("result",-1);
e.printStackTrace();
}
if(sign!=null && !"".equals(sign)){
map.put("paySign",sign);
map.put("result",1);
}else{
map.put("result",-1);
}
map.put("prepay_id",orderResponse.getPrepay_id());
return map;
}else{ //不成功
String text = "调用微信支付出错,返回状态码:"+orderResponse.getReturn_code()+",返回信息:"+orderResponse.getReturn_msg();
if(orderResponse.getErr_code()!=null && !"".equals(orderResponse.getErr_code())){
text = text +",错误码:"+orderResponse.getErr_code()+",错误描述:"+orderResponse.getErr_code_des();
}
log.error(text);
map.put("result",-1);
return map;
}
}
/**
* 异步回调接口
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value="/paymentNotice",produces="text/html;charset=utf-8")
@ResponseBody
public String WeixinParentNotifyPage(HttpServletRequest request,HttpServletResponse response) throws Exception{
ServletInputStream instream = request.getInputStream();
StringBuffer sb = new StringBuffer();
int len = -1;
byte[] buffer = new byte[1024];
while((len = instream.read(buffer)) != -1){
sb.append(new String(buffer,0,len));
}
instream.close();
// log.error("支付通知回调信息:"+sb.toString());
// Map map = WXPayUtil.doXMLParseWithSorted(sb.toString());//接受微信的通知参数
Map map = WXPayUtil.xmlToMap(sb.toString());//接受微信的回调的通知参数
Map return_data = new HashMap();
//判断签名是否正确
if(WXPayUtil.isSignatureValid(map, "你的密匙")){
if(map.get("return_code").toString().equals("FAIL")){
return_data.put("return_code", "FAIL");
return_data.put("return_msg", map.get("return_msg"));
}else if(map.get("return_code").toString().equals("SUCCESS")){
String result_code = map.get("result_code").toString();
String out_trade_no = map.get("out_trade_no").toString();
//获得你自己的订单详情
UserPayInfo payInfo = wxPayService.getUserPayInfo(out_trade_no);
if(payInfo == null){
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "订单不存在");
return WeixinUtil.GetMapToXML(return_data);
}else{
//2 已支付(不确定是否支付成功)3 支付完成 4 取消支付 5支付失败
if(result_code.equals("SUCCESS")){//支付成功
//如果订单已经支付直接返回成功
if(payInfo.getPayStatus()==3){
return_data.put("return_code", "SUCCESS");
return_data.put("return_msg", "OK");
return WXPayUtil.GetMapToXML(return_data);
}else{
String sign = map.get("sign").toString();
String total_fee = map.get("total_fee").toString();//订单金额
if(!publicUtil.subZeroAndDot3(payInfo.getTotal_fee().toString()).equals(total_fee)){//订单金额是否一致
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "金额异常");
}else{
String time_end = map.get("time_end").toString();
String bank_type = map.get("bank_type").toString();
String settlement_total_fee = map.get("settlement_total_fee");
if(settlement_total_fee==null || "".equals(settlement_total_fee)){
settlement_total_fee = "0";
}
payInfo.setSign(sign);
payInfo.setResult_code(result_code);
payInfo.setPayStatus(3);
payInfo.setTime_end(time_end);
payInfo.setSettlement_total_fee(settlement_total_fee);
payInfo.setBank_type(bank_type);
payInfo.setCoupon_fee("0");
int result = wxPayService.updatePayInfo(payInfo);
if(result<=0){
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "更新订单失败");
return WeixinUtil.GetMapToXML(return_data);
}else{
UserOrderInfo orderInfo = new UserOrderInfo();
orderInfo.setId(payInfo.getOrderId());
orderInfo.setStatus(2);
result = wxPayService.updateOrderInfo(orderInfo);
if(result<=0){
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "更新订单失败");
return WeixinUtil.GetMapToXML(return_data);
}else{
return_data.put("return_code", "SUCCESS");
return_data.put("return_msg", "OK");
return WeixinUtil.GetMapToXML(return_data);
}
}
}
}
}else{//支付失败,更新支付结果
if(payInfo!=null){
payInfo.setResult_code(result_code);
payInfo.setPayStatus(5);
payInfo.setErr_code(map.get("err_code").toString());
payInfo.setErr_code_des(map.get("err_code_des").toString());
wxPayService.updatePayInfo(payInfo);
}
return_data.put("return_code", "FAIL");
return_data.put("return_msg",map.get("return_msg").toString());
return WeixinUtil.GetMapToXML(return_data);
}
}
}
}else{
return_data.put("return_code", "FAIL");
return_data.put("return_msg", "签名错误");
}
String xml = WXPayUtil.GetMapToXML(return_data);
log.error("支付通知回调结果:"+xml);
return xml;
}
}
前端唤起微信支付,看jsp代码
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
支付页面
到此全部搞定,3个封装数据类,2个工具类,一个controller,一个jsp调用,jsp结果操作提示是用jQuery WeUI插件,很不错的一个手机网站开发插件。