最近做了微信公众号开发,用户使用微信进行账户余额的充值,开发支付功能使用微信的JSSDK。公众号支付,开发文档 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1。通过文档熟悉流程。
开发前置条件
相关参数:
AppId:公众号的唯一标识(登陆微信企业号后台 - 设置 - 账号信息 - CorpID)
AppSecret:(微信企业号后台 - 设置 - 权限管理 - 新建一个拥有所有应用权限的普通管理组 - Secret)
Key:商户API密钥(登陆微信商户后台 - 账户中心 - API安全 - API密钥)
MchId:商户ID(微信企业号后台 - 服务中心 - 微信支付 - 微信支付 -商户信息 - 商户号)
后台设置:
微信企业号后台 - 服务中心 - 微信支付 - 微信支付 - 开发配置 :
1.测试授权目录,改成线上支付页面的目录(例:http://www.dynamic.com/wxpay/)
2.测试白名单,加上测试用户的白名单,只有白名单用户可以付款
支付流程:
1.前台发起请求,获取必须要的数据,订单号,支付金额。设置上面所说的必要的参数,配置授权目录。
2.请求发起后,根据微信开发文档,统一下单工具类,设置提交给支付网关的数据的格式为XML,包含必要数据和密钥。
3.HTTP发送请求给微信,获得返回的内容。是否为Success。是,页面出现支付的控件。
4.输入支付密码,提交支付授权,验证授权。
5.调用参数中的回调函数,对返回的数据进行校验,验证通过,则执行业务代码把数据保存到数据库中
前台页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>js sdk 调起页面</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script>
function submit(){
$.ajax({
type: 'POST',
url: 'wechatPay/jsOarder.do',
data: {'detail':'测试','desc':'测试','goodSn':'20000000101','orderSn':'200000001111','amount':'0.01'},
success: function(data){
console.log(data.obj);
var appId=data.obj.appId;
var timeStamp=data.obj.timeStamp;
var nonceStr=data.obj.nonceStr;
var package=data.obj.package;
var paySign=data.obj.paySign;
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package":package,
"signType":"MD5", //微信签名方式:
"paySign":paySign //微信签名
},
function(res){
WeixinJSBridge.log(res.err_msg);
if(res.err_msg == "get_brand_wcpay_request:ok"){
<!--支付成功调用-->
<!--history.go(0); -->
//alert("成功");
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
<!--取消支付调用-->
//alert("取消");
}else{
<!--支付失败-->
//alert("失败");
}
}
);
} ,
dataType: "json"});
}
$(function(){
$("#sub").click(function(){
submit();
});
});
</script>
</head>
<body>
<input type="text" placeholder="请输入金额"/><br>
<input type="button" id="sub" value="提交">
</body>
</html>
WechatPayControler,请求和回调
@Controller @RequestMapping("/wechatPay") public class WechatPayControler extends BaseControler { /** * 微信公众号调起 * @param detail 商品描述 * @param desc 商品详情 * @param goodSn 商品编号 * @param openId 用户openid * @param orderSn 订单号 * @param amount 金额 * @return 返回包装了调起jssdk所需要的函数 * @throws Exception */ @RequestMapping("/jsOarder.do") @ResponseBody public Object jsOrder(String detail, String desc, String goodSn,String openId, String orderSn, String amount) throws Exception { JSONObject result = WechatOrderUtils.createOrder(detail, desc, "og5IqwbQCiFn03tw9IPtg1X4vO9U", "10.0.0.1", goodSn, orderSn, amount, "JSAPI"); return result; }
}/** * 微信支付异步通知处理接口 * * @param request * @param response * @return * @throws IOException */ @RequestMapping("weipayCallBack") public void weipayCallBack(HttpServletRequest request, HttpServletResponse response) throws IOException { logger.info("**************************微信支付异步回调通知开始***********************"); InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); String resultStr = new String(outSteam.toByteArray(), "utf-8"); Map<String, Object> resultMap = new HashMap<String, Object>(); try { resultMap = XMLParser.getMapFromXML(resultStr); String out_trade_no = (String) resultMap.get("out_trade_no"); String return_code = (String) resultMap.get("return_code"); Double total_fee = (Double.parseDouble((String) resultMap.get("total_fee")) / 100); System.out.println("用户充值金额------------->" + total_fee); // 充值 String bank = (String) resultMap.get("bank_type"); String transaction_id = (String) resultMap.get("transaction_id"); BigDecimal fee = new BigDecimal(total_fee); // 签名验证 boolean valid = Signature.checkIsSignValidFromResponseString(resultStr); if (return_code.equals("SUCCESS") && valid) { //支付成功,进行业务处理,返回的数据入库 } } catch (ParserConfigurationException e) { logger.info("************微信支付异步回调异常" + e.getMessage() + "微信返回" + resultStr); e.printStackTrace(); } catch (SAXException e) { logger.info("************微信支付异步回调异常" + e.getMessage() + "微信返回" + resultStr); e.printStackTrace(); } // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.[一定别手贱传return_msg回去,他们傻逼会继续回调的] String success = "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>"; response.getOutputStream().write(new String(success).getBytes()); logger.info("**************************微信支付异步回调通知结束***********************"); }
WechatOrderUtils
package cn.cuco.controller.wechat.util; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import cn.cuco.constant.Constant; import cn.cuco.httpservice.HttpClientUtils; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; /** * 微信统一下单工具类 */ public class WechatOrderUtils { /** * 统一下单 * * @param detail * 订单详情,必填 * @param desc * 商品或订单描述,必填 * @param openid * 公众号调起时需要的OPENID,选填,不填传“” * @param ip * 下订单时的IP,必填 * @param goodSn * 业务系统商品编号,必填 * @param orderSn * 业务系统订单编号,必填 * @param amount * 金额,必填 * @param type * 支付类型,分为三种,JSAPI表示公众号调起的支付,NATIVE用于PC端网页调起的扫码支付,APP用于APP端调起的支付 * @return 返回对象中封装了网页和APP调起支付控件需要的参数,根据不同的支付类型,有不同的返回参数 */ public static synchronized JSONObject createOrder(String detail, String desc, String openid, String ip, String goodSn, String orderSn, String amount, String type) { JSONObject result = new JSONObject(); System.out.println("openid is ----- > " + openid); // 1、参数校验 if (StringUtils.isBlank(detail) || StringUtils.isBlank(desc) || StringUtils.isBlank(ip) || StringUtils.isBlank(goodSn) || StringUtils.isBlank(orderSn) || StringUtils.isBlank(amount) || StringUtils.isBlank(type)) { Log.error("微信支付统一下单请求错误:请求参数不足", null); result.put("status", "error"); result.put("msg", "请求参数不足"); result.put("obj", null); return result; } double relAmount = 0;// 对应微信支付的真实数目 try {// 进行格式转换异常获取,保证数目正确 relAmount = Double.parseDouble(amount) * 100; } catch (Exception e) { Log.error("微信支付统一下单请求错误:请求金额格式错误", e); result.put("status", "error"); result.put("msg", "请求金额格式错误"); result.put("obj", null); return result; } if (relAmount == 0) {// 微信支付的支付金额必须为大于0的int类型,单位为分 Log.error("微信支付统一下单请求错误:请求金额不能为0", null); result.put("status", "error"); result.put("msg", "请求金额不能为0"); result.put("obj", null); return result; } if (!("JSAPI".equalsIgnoreCase(type) || "NATIVE".equalsIgnoreCase(type) || "APP".equalsIgnoreCase(type))) { Log.error("微信支付统一下单请求错误:支付类型为空", null); result.put("status", "error"); result.put("msg", "支付类型为空"); result.put("obj", null); return result; } /* 公众号调起微信支付的时候,必须要有openID */ if ("JSAPI".equalsIgnoreCase(type) && StringUtils.isBlank(openid)) { Log.error("微信支付统一下单请求错误:请求参数不足", null); result.put("status", "error"); result.put("msg", "请求参数不足"); result.put("obj", null); return result; } // 2、获取系统配置信息 String wx_order = // 获取统一下单接口地址 String mchappid = // 商户appid String mchid = // 商户ID String wx_callback =http://+"域名+'/'+项目名"/wechatPay/weipayCallBack";// 获取微信支付回调接口 String wx_key = // 微信商户后台设置的key if (StringUtils.isBlank(wx_order) || StringUtils.isBlank(mchappid) || StringUtils.isBlank(mchid) || StringUtils.isBlank(wx_callback)) { Log.error("微信支付统一下单请求错误:系统配置信息缺失", null); result.put("status", "error"); result.put("msg", "系统配置信息缺失"); result.put("obj", null); return result; } // 发送报文模板,其中部分字段是可选字段 String xml = "" + "<xml>" + "<appid>APPID</appid>" + // 公众号ID "<device_info>WEB</device_info>" + // 设备信息 "<detail>DETAIL</detail>" + // 商品详情 "<body>BODY</body>" + // 商品描述 "<mch_id>MERCHANT</mch_id>" + // 微信给的商户ID "<nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>" + // 32位随机字符串,不改 "<notify_url><![CDATA[URL_TO]]></notify_url>" + // 信息通知页面 "<openid>UserFrom</openid>" + // 支付的用户ID "<fee_type>CNY</fee_type>" + // 支付货币,不改 "<spbill_create_ip>IP</spbill_create_ip>" + // 用户IP "<time_start>START</time_start>" + // 订单开始时间 "<time_expire>STOP</time_expire>" + // 订单结束时间 "<goods_tag>WXG</goods_tag>" + // 商品标记,不改 "<product_id>GOODID</product_id>" + // 商品ID "<limit_pay>no_credit</limit_pay>" + // 支付范围,默认不支持信用卡支付,不改 "<out_trade_no>PAY_NO</out_trade_no>" + // 商城生成的订单号 "<total_fee>TOTAL</total_fee>" + // 总金额 "<trade_type>TYPE</trade_type>" + // 交易类型,JSAPI,NATIVE,APP,WAP "<sign>SIGN</sign>" + // 加密字符串 "</xml>"; // 生成订单起始时间,订单7天内有效 DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss"); String start_time = df.format(new Date()); String stop_time = df.format(new Date().getTime() + 7 * 24 * 60 * 60 * 1000); // 3、xml数据封装 // 公众号调起的商户号 xml = xml.replace("MERCHANT", mchid); xml = xml.replace("APPID", mchappid); xml = xml.replace("DETAIL", detail); xml = xml.replace("BODY", desc); xml = xml.replace("URL_TO", wx_callback); xml = xml.replace("IP", ip); xml = xml.replace("START", start_time); xml = xml.replace("STOP", stop_time); xml = xml.replace("GOODID", goodSn); xml = xml.replace("PAY_NO", orderSn); xml = xml.replace("TOTAL", (int) relAmount + ""); xml = xml.replace("TYPE", type); xml = xml.replace("UserFrom", openid); // 4、加密 Map<String, String> map = new HashMap<String, String>(); map.put("device_info", "WEB"); map.put("detail", detail); map.put("body", desc); map.put("mch_id", mchid); map.put("appid", mchappid); map.put("nonce_str", "1add1a30ac87aa2db72f57a2375d8fec"); map.put("notify_url", wx_callback); map.put("fee_type", "CNY"); map.put("spbill_create_ip", ip); map.put("time_start", start_time); map.put("time_expire", stop_time); map.put("goods_tag", "WXG"); map.put("product_id", goodSn); map.put("limit_pay", "no_credit"); map.put("out_trade_no", orderSn); map.put("total_fee", (int) relAmount + ""); map.put("trade_type", type); if (("JSAPI".equalsIgnoreCase(type))) { map.put("openid", openid); } String sign = SignatureUtils.signature(map, wx_key); xml = xml.replace("SIGN", sign); // 5、请求 String response = ""; try {// 注意,此处的httputil一定发送请求的时候一定要注意中文乱码问题,中文乱码问题会导致在客户端加密是正确的,可是微信端返回的是加密错误 System.out.println("xml is ----->" + xml.toString()); // response = HttpUtils.post(wx_order, xml); response = HttpClientUtils.sendPost(wx_order, xml); } catch (Exception e) { Log.error("微信支付统一下单失败:http请求失败", e); result.put("status", "error"); result.put("msg", "http请求失败"); result.put("obj", null); return result; } // 6、处理请求结果 XStream s = new XStream(new DomDriver()); s.alias("xml", WechatOrder.class); WechatOrder order = (WechatOrder) s.fromXML(response); if ("SUCCESS".equals(order.getReturn_code()) && "SUCCESS".equals(order.getResult_code())) { Log.error("微信支付统一下单请求成功:" + order.getPrepay_id(), null); } else { Log.error("微信支付统一下单请求错误:" + order.getReturn_msg() + order.getErr_code(), null); result.put("status", "error"); result.put("msg", "http请求失败"); result.put("obj", null); return result; } HashMap<String, String> back = new HashMap<String, String>(); // 生成客户端调时需要的信息对象 if ("JSAPI".equalsIgnoreCase(type)) { // 网页调起的时候 String time = Long.toString(System.currentTimeMillis()); back.put("appId", mchappid); back.put("timeStamp", time); back.put("nonceStr", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS"); back.put("package", "prepay_id=" + order.getPrepay_id()); back.put("signType", "MD5"); String sign2 = SignatureUtils.signature(back, wx_key); JSONObject jsonObject = new JSONObject(); jsonObject.put("appId", mchappid); jsonObject.put("timeStamp", time); jsonObject.put("nonceStr", "5K8264ILTKCH16CQ2502SI8ZNMTM67VS"); jsonObject.put("package", "prepay_id=" + order.getPrepay_id()); jsonObject.put("signType", "MD5"); jsonObject.put("paySign", sign2); result.put("status", "success"); result.put("msg", "下单成功"); result.put("obj", jsonObject); return result; } return result; } }
SignatureUtils 加密工具类
import java.security.MessageDigest; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; public class SignatureUtils { /** * 微信支付加密工具 */ public static String signature(Map<String, String> map, String key) { Set<String> keySet = map.keySet(); String[] str = new String[map.size()]; StringBuilder tmp = new StringBuilder(); // 进行字典排序 str = keySet.toArray(str); Arrays.sort(str); for (int i = 0; i < str.length; i++) { String t = str[i] + "=" + map.get(str[i]) + "&"; tmp.append(t); } if (StringUtils.isNotBlank(key)) { tmp.append("key=" + key); } String tosend = tmp.toString(); MessageDigest md = null; byte[] bytes = null; try { md = MessageDigest.getInstance("MD5"); bytes = md.digest(tosend.getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); } String singe = byteToStr(bytes); return singe.toUpperCase(); } /** * 微信支付加密工具 */ public static String signatureSHA1(Map<String, String> map) { Set<String> keySet = map.keySet(); String[] str = new String[map.size()]; StringBuilder tmp = new StringBuilder(); // 进行字典排序 str = keySet.toArray(str); Arrays.sort(str); for (int i = 0; i < str.length; i++) { String t = str[i] + "=" + map.get(str[i]) + "&"; tmp.append(t); } String tosend = tmp.toString().substring(0, tmp.length() - 1); MessageDigest md = null; byte[] bytes = null; try { md = MessageDigest.getInstance("SHA-1"); bytes = md.digest(tosend.getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); } String singe = byteToStr(bytes); return singe.toLowerCase(); } public static String sha1Check(String[] str) { StringBuilder tmp = new StringBuilder(); Arrays.sort(str); for (int i = 0; i < str.length; i++) { String t = str[i]; tmp.append(t); } String tosend = tmp.toString(); MessageDigest md = null; byte[] bytes = null; try { md = MessageDigest.getInstance("SHA-1"); bytes = md.digest(tosend.getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); } String singe = byteToStr(bytes); return singe.toUpperCase(); } /** * 字节数组转换为字符串 * * @param byteArray * @return */ public static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest; } /** * 字节转换为字符串 * * @param mByte * @return */ public static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F]; String s = new String(tempArr); return s; } public static void main(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("noncestr", "Wm3WZYTPz0wzccnW"); map.put("jsapi_ticket", "sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg"); map.put("timestamp", "1414587457"); map.put("url", "http://mp.weixin.qq.com?params=value"); } }
HttpUtils 给微信网关发送请求
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair; public class HttpUtils { public static HttpClient client; static { client = HttpClientBuilder.create().build(); } public static String post(String url, Map<String, String> map) throws Exception { // 处理请求地址 URI uri = new URI(url); HttpPost post = new HttpPost(uri); // 添加参数 List<NameValuePair> params = new ArrayList<NameValuePair>(); for (String str : map.keySet()) { params.add(new BasicNameValuePair(str, map.get(str))); } post.setEntity(new UrlEncodedFormEntity(params,"utf-8")); // 执行请求 HttpResponse response = client.execute(post); if (response.getStatusLine().getStatusCode() == 200) { // 处理请求结果 StringBuffer buffer = new StringBuffer(); InputStream in = null; try { in = response.getEntity().getContent(); BufferedReader reader = new BufferedReader( new InputStreamReader(in)); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); } } finally { // 关闭流 if (in != null) in.close(); } return buffer.toString(); } else { return null; } } public static String post(String url, String str) throws Exception { // 处理请求地址 URI uri = new URI(url); HttpPost post = new HttpPost(uri); post.setEntity(new StringEntity(str,"utf-8")); // 执行请求 HttpResponse response = client.execute(post); if (response.getStatusLine().getStatusCode() == 200) { // 处理请求结果 StringBuffer buffer = new StringBuffer(); InputStream in = null; try { in = response.getEntity().getContent(); BufferedReader reader = new BufferedReader( new InputStreamReader(in)); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); } } finally { // 关闭流 if (in != null) in.close(); } return buffer.toString(); } else { return null; } } public static String get(String url) throws Exception { URI uri = new URI(url); HttpGet get = new HttpGet(uri); HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() == 200) { StringBuffer buffer = new StringBuffer(); InputStream in = null; try { in = response.getEntity().getContent(); BufferedReader reader = new BufferedReader( new InputStreamReader(in)); String line = null; while ((line = reader.readLine()) != null) { buffer.append(line); } } finally { if (in != null) in.close(); } return buffer.toString(); } else { return null; } } }
WechatOrder 微信返回的订单实体
/** * 微信返回的订单实体 * @author hxy * */ public class WechatOrder { 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 prepay_id;//预支付交易会话标识 private String trade_type;//交易类型 private String err_code;//错误代码 private String err_code_des;//错误代码描述 private String code_url;//二维码链接 get... set... }
支付成功。。。