内容简介:基于Java实现微信支付(公众号支付)简单教程
最近公司需求,需要在微信公众号内完成支付,找到官方文档,文档还可以,讲的也挺详细,不过有一个地方很坑爹,就是微信内H5调起支付需要一个签名,而他给出的参考签名方式跟统一下单签名一致,害的我以为,他这个签名就是统一下单那个签名,后面找了很久看了好多博客才明白这个签名是怎么生成的(JS-SDK中微信支付有说明)。弄了半天,汗颜。下面进入正题。
微信支付分为很多种,有刷卡支付,公众号支付,扫码支付,APP支付,H5支付,小程序支付,本教程只讲解公众号支付。主要分三大块来讲解。
1.公众号支付介绍
微信支付只面向已认证客户,需要申请,申请成功后有个商家号,还要一个API密匙即key(微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置)。而公众号支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
2.开发流程
在这里贴出官方文档地址 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1 。有兴趣的可以研究研究,贴出最主要的业务流程图:
啥,看不下去,那简单来说,你要掌握三点:
1.在商家后台设置支付目录, 设置路径:商户平台-->产品中心-->开发配置。填写你项目所在域名
2.在公众号后台 设置授权域名,因为在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。
3.调通统一下单与 支付结果通知接口(详细看demo)
tip:如果不知道如何获取openid,可以看本人另外一篇博客 微信公众号开发《一》OAuth2.0网页授权认证获取用户的详细信息,实现自动登陆
3.demo展示
demo使用了包:xstream-1.3.1.jar,jdom.jar,用于把数据封装成xml格式与xml解析。首先我们把请求参数封装成类
/**
* 统一下单请求参数
* @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";
} MD5加密 工具 类,封装参数、请求工具类
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<String,Object> createOrderInfo(Map<String, String> 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<String, String> packageParams = new TreeMap<String, String>();
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<String,Object> responseMap = new HashMap<String,Object>();
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<String, String> packageParams = new TreeMap<String, String>();
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("<![CDATA[");
writer.write(text);
writer.write("]]>");
}
} else {
writer.write(text);
}
}
};
}
});
//xml解析
public static SortedMap<String, String> doXMLParseWithSorted(String strxml) throws Exception {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
SortedMap<String,String> m = new TreeMap<String,String>();
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<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
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<String, String> 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<String, String> 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<String, String> 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<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, 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, 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, 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 {
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<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();
}
/**
* 生成 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<String, String> paras = new HashMap<String, String>();
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<String,Object> toPay(@RequestParam(value="payMoney",required=true) String payMoney,@RequestParam(value="userWeixinOpenId",required=true) String userWeixinOpenId){
Map<String,Object> map = new HashMap<>();
String orderId = String.valueOf(WXPayUtil.generateUUID());
String noncestr = WXPayUtil.generateNonceStr();
Map<String,String> requestMap = new HashMap<String, String>();
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<String,Object> 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<String, String> packageParams = new TreeMap<String, String>();
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<String,String> map = WXPayUtil.doXMLParseWithSorted(sb.toString());//接受微信的通知参数
Map<String,String> map = WXPayUtil.xmlToMap(sb.toString());//接受微信的回调的通知参数
Map<String,String> return_data = new HashMap<String,String>();
//判断签名是否正确
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"%>
<c:set var="BS" value="${pageContext.request.contextPath}"></c:set>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>支付页面</title>
<script type="text/javascript" src="${BS}/js/jquery-1.7.2.min.js"></script>
</head>
<input type="hidden" id="weixinOperId" value="用户operId">
<script type="text/javascript">
var phoneWidth = parseInt(window.screen.width);
var phoneScale = phoneWidth/640;
var ua = navigator.userAgent;
if (/Android (\d+\.\d+)/.test(ua)){
var version = parseFloat(RegExp.$1);
if(version>2.3){
document.write("<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0, minimum-scale = 1.0, maximum-scale = "+phoneScale+", target-densitydpi=device-dpi\">");
}else{
document.write("<meta name=\"viewport\" content=\"width=device-width, target-densitydpi=device-dpi\">");
}
}else{
document.write("<meta name=\"viewport\" content=\"width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no,target-densitydpi=device-dpi\">");
}
function initPay(powerStationId,userId){
if(!is_weixin()){
alert("请在微信客户端打开该链接");
}else if(!weixin_version()){
alert("你微信版本太低,不支持微信支付功能,请先更新你的微信");
}else{
if(typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', initPay, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', initPay);
document.attachEvent('onWeixinJSBridgeReady', initPay);
}
}else{
toPay(powerStationId,userId);
}
}
}
function toPay(powerStationId,userId){
$.ajax({
url : "/"+getProjectName()+"/pay/toPayInit",
type:"POST",
dataType : 'json', // 服务器返回的格式,可以是json或xml等
data:{
payMoney:1,
userWeixinOpenId:'用户operId'
},
success : function(result) { // 服务器响应成功时的处理函数
if(result.result==1){//插入支付记录
var paySign = result.paySign;
var prepay_id = result.prepay_id;
var nonceStr = result.noncestr;
var timestamp = result.timestamp;
var unifiedOrderRequest = result.unifiedOrderRequest;
var spbill_create_ip = unifiedOrderRequest.spbill_create_ip;
var detail = unifiedOrderRequest.detail;
var out_trade_no = unifiedOrderRequest.out_trade_no;
$.ajax({
url : "/"+getProjectName()+"/pay/toSavePayInfo",
type:"POST",
dataType : 'json', // 服务器返回的格式,可以是json或xml等
data:{
spbill_create_ip:spbill_create_ip,
detail:detail,
out_trade_no:out_trade_no,
total_fee:1,
powerStationId:powerStationId,
userId:userId,
order_type:1
},
success : function(result) { // 服务器响应成功时的处理函数
if(result>0){//插入支付记录
onBridgeReady(paySign,prepay_id,nonceStr,timestamp);
}
},
error : function(data, status, e) { // 服务器响应失败时的处理函数
$.toptip("系统出错,请联系系统运营商", 'error');
}
});
}else{
$.toptip("初始化支付接口失败,请联系系统运营商", 'error');
}
},
error : function(data, status, e) { // 服务器响应失败时的处理函数
$.toptip("初始化支付接口失败,请联系系统运营商", 'error');
}
});
}
function onBridgeReady(paySign,prepay_id,nonceStr,timestamp){
var weixinOperId = $("#weixinOperId").val();
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":weixinOperId, //公众号名称,由商户传入
"timeStamp":timestamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package":"prepay_id="+prepay_id,
"signType":"MD5", //微信签名方式:
"paySign":paySign //微信签名 (这个签名获取看后台)
},
function(res){
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
alert("支付成功", 'success');
}else if(res.err_msg == "get_brand_wcpay_request:cancel" ) {
alert("用户取消", 'success');
}
}
);
}
</script>
<body>
<div id="addressArea" style="min-height:526px;">
<section class="SelectCityWrap" style="width:98%;">
<section class="content">
<div class="nav">
<a class="" nav="nav_1" onclick="initPay(11,1)">马上支付</a>
</div>
</section>
</section>
</div>
</body>
</html> 到此全部搞定,3个封装数据类,2个工具类,一个controller,一个jsp调用,jsp结果操作提示是用jQuery WeUI插件,很不错的一个手机网站开发插件。
如有不解或者逻辑不通的地方,欢迎评论区留言。
以上所述就是小编给大家介绍的《基于Java实现微信支付(公众号支付)简单教程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 公众号支付-从服务号申请到支付调用爬坑
- JeeWx 捷微 3.1 小程序版本发布,支持微信公众号,微信企业号,支付窗
- JeeWx 捷微 V3.3 版本发布,支持微信公众号、微信企业号与支付窗
- 微信公众号开发C#系列-9、多公众号集中管理
- Python微信公众号开发
- 优质的技术公众号这样定义
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective JavaScript
David Herman / Addison-Wesley Professional / 2012-12-6 / USD 39.99
"It's uncommon to have a programming language wonk who can speak in such comfortable and friendly language as David does. His walk through the syntax and semantics of JavaScript is both charming and h......一起来看看 《Effective JavaScript》 这本书的介绍吧!