前段时间做微信支付,微信浏览器填写金额商品名之后提交跳转付款页面确认然后返回界面判断,今天来详细说下
国际惯例先贴代码
mcontroller.java
public void wxpay() {
if(this.getPara("openid")==null){
this.redirect(Conts.wxpayoauth.replace("*state*", "wxpay"));
return;
}
//上面这块主要是验证下,获得openid,验证功能改天再开一篇文章,然后把openid存一下
this.setAttr("openid", this.getPara("openid"));
//价格和info(info0+info1拼接而成)纯粹是需求需要,这里大家灵活处理
if(this.getPara("price")==null){
this.setAttr("res", "");
this.setAttr("str", "");
this.setAttr("tag",false);
this.render("wxpay.html");
return;
}
this.setAttr("tag", true);
String price = this.getPara("price");
String info0 = this.getPara("info0");
String info1 = this.getPara("info1");
if("1".equals(info1))
info1="综合费";
else if("2".equals(info1))
info1="激励基金";
else if("3".equals(info1))
info1="房屋押金";
else if("4".equals(info1))
info1="门禁卡";
else if("5".equals(info1))
info1="车位费";
String info = info0+"("+info1+")";
String jsapi_ticket =Sha1Util.getAccessToken(PropKit.get("appId"),PropKit.get("appSecret"));
//看清楚.这是ticket..用token在https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi里换的,大家一定要注意这里,这是个坑,一定要按照微信的规则得到这串,并且一定保证微信规则能得到返回值,还有个支付授权目录,这个一定要记得修改我写的是我这个方法的访问地址就是。。。/m/wxpay/.http://jingyan.baidu.com/article/ed2a5d1f340dd409f7be177c.html 这个是获得access——token的规则,其实官方的demo中工具类获取的,还有这容易出错,记得吧ip添加到白名单,这有个我搞不懂的,有些ip就是获取不到token,但是加了白名单之后就可以了,难不成遇见一个加一个?有说只有几个特殊的家进入就行,这个是个容易出错的地方,提醒下大家
String nonce_str = Sha1Util.getNonceStr();// 随机字符串
String timestamp = Sha1Util.getTimeStamp();// 时间戳
String appid = PropKit.get("appId");//APPID
String url="http://man666666n.cn/m/pay/";//发起支付的前端页面的URL地址.而且...而且必须在微信支付里面配置才行!!!
String sign = null;
try {
SortedMap packageParams = new TreeMap();
packageParams.put("jsapi_ticket", jsapi_ticket);
packageParams.put("noncestr", nonce_str);
packageParams.put("timestamp", timestamp);
packageParams.put("url", url);
sign = Sha1Util.createSHA1Sign(packageParams);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String res="appId : \"" + appid + "\",timestamp : \"" + timestamp //微信个傻逼..这里的timestamp是小写~~
+ "\", nonceStr : \"" + nonce_str
+ "\", signature : \"" + sign + "\"";
System.out.println("sign签名="+res);
this.setAttr("price", price);
this.setAttr("info", info);
this.setAttr("res", res);
this.setAttr("str", dopay(this.getPara("openid"),price,info,nonce_str,timestamp));
this.render("wxpay.html");
}
工具类如果需要在官方的demo中有
mcontroller
public String dopay(String openId,String money,String info,String nonce_str,String timestamp) {
// 网页授权后获取传递的参数
String currTime = TenpayUtil.getCurrTime();
String orderNo = currTime+Sha1Util.getNonceStr().substring(0, 18);
float sessionmoney = Float.parseFloat(money);
String finalmoney = String.format("%.2f", sessionmoney);
finalmoney = finalmoney.replace(".", "");
finalmoney = String.valueOf(Integer.parseInt(finalmoney));
// 商户相关资料
String appid = PropKit.get("appId");
String appsecret = PropKit.get("appSecret");
String mch_id = PropKit.get("mch_id");//邮件里的MCHID
String partnerkey = PropKit.get("paterner_key");;//在微信商户平台pay.weixin.com里自己生成的那个key
String body = info;
// 商户订单号
String out_trade_no = orderNo;
// 订单生成的机器 IP
String spbill_create_ip = Sha1Util.getIp2(getRequest());
// 这里notify_url是 支付完成后微信发给该链接信息,可以判断会员是否支付成功,改变订单状态等。这个就是notify_url方法,在后面贴的,微信会返回我们一些值我们判断一下,返回success就结束了订单状态了,不然会一直访问回调
String notify_url = "http://man666666n/m/notify_url";
String trade_type = "JSAPI";
String openid =openId;
Wxpaydto wx = new Wxpaydto();
wx.setBody(info);
wx.setOpenId(this.getPara("openid"));
wx.setOrderId(orderNo);
wx.setSpbillCreateIp(spbill_create_ip);
wx.setTotalFee(money);
wx.setTime(new Date());
wx.setNotifyUrl("http://man6666n.cn/m/notify_url");
wx.setIstrue("0");
wx.save();
SortedMap packageParams = new TreeMap();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", body);
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("total_fee", finalmoney);
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
packageParams.put("openid", openid);
RequestHandler reqHandler = new RequestHandler(null, null);
reqHandler.init(appid, appsecret, partnerkey);
String sign = reqHandler.createSign(packageParams);
String xml = "" + "" + appid + " "
+ ""+ mch_id + " "
+ "" + nonce_str+ " "
+ " "
+ ""
+ ""+ out_trade_no+ " "
+ ""+ finalmoney+ " "
+ "" + spbill_create_ip + " "
+ "" + notify_url + " "
+ "" + trade_type + " "
+ ""+ openid + " " + " ";
String allParameters = "";
try {
allParameters = reqHandler.genPackage(packageParams);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String prepay_id = "";
try {
prepay_id = new GetWxOrderno().getPayNo(createOrderURL, xml);
if (prepay_id.equals("")) {
System.out.println("统一支付接口获取预支付订单出错");
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
SortedMap finalpackage = new TreeMap();
String nonceStr2 = nonce_str;
String prepay_id2 = "prepay_id=" + prepay_id;
String packages = prepay_id2;
finalpackage.put("appId", appid);
finalpackage.put("timeStamp", timestamp);
finalpackage.put("nonceStr", nonceStr2);
finalpackage.put("package", packages);
finalpackage.put("signType", "MD5");
String finalsign = reqHandler.createSign(finalpackage);
System.out.println("timestamp:\"" + timestamp
+"\",appid:\"" + appid
+ "\",nonceStr:\"" + nonceStr2 + "\",package:\""
+ packages + "\",signType: \"MD5" + "\",paySign:\""
+ finalsign + "\"");
return "timestamp:\"" + timestamp
+"\",appid:\"" + appid
+ "\",nonceStr:\"" + nonceStr2 + "\",package:\""
+ packages + "\",signType: \"MD5" + "\",paySign:\""
+ finalsign + "\"";
}
//隐约记得好像两次的timestamp跟sign好像要求一样.这个就是js返回的一个逻辑,具体根据项目来写,微信会在两处都返回,一个是js一个是回调函数,官方的说法是js不作为标准可能会有错误
public void paymsg(){
String tag = this.getPara("tag");
if(tag.equals("payok")){
this.setAttr("msg", "支付成66666配合。");
}
else if(tag.equals("payfail"))
this.setAttr("msg", "支付失66666重试。");
else if(tag.equals("paycencel"))
this.setAttr("msg", "支付取消。");
else
this.setAttr("msg", "未知错误。");
this.render("msgclose.html");
}
public void notify_url() throws IOException{
//这个就是回调的判断
InputStream re = this.getRequest().getInputStream();
System.out.println("得到的contextpath"+re);
System.out.println(this.getRequest());
try {
processResponseXml(inputStream2String(re));
} catch (Exception e) {
e.printStackTrace();
}
this.renderText("SUCCESS");
}
官方工具类
WXPayUtil
package com.utils;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.*;
import java.security.MessageDigest;
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.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WXPayUtil {
public enum SignType {
MD5, HMACSHA256
}
/**
* 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("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("sign") ) {
return false;
}
String sign = data.get("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("sign") ) {
return false;
}
String sign = data.get("sign");
System.out.println("data+"+data+",key="+key+",signtype="+signType);
System.out.println("sign="+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("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));
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 生成 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);
}
}
前端 wxpay.html
前端调用这块需要好好看一下,稍微有点绕,不知道其他大神有什么更好的实现方法
自****
<#if tag>
请确认信息:
价格:${price!}元
备注:${info!}
确认信息无误请点击确认付款按钮
<#else>
{{ message}}
¥{{ message}}
#if>