最近做了微信公众号开发,用户使用微信进行账户余额的充值,开发支付功能使用微信的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...
}
支付成功。。。