1、场景:公司需要在网站上进行微信支付。
2、API:使用微信开放平台的接入微信支付.
-扫码支付。微信支付开发者平台链接
3、分析:
-
接入扫码支付(包括PC网站支付)包括3个阶段,问这里只讲使用,也就是第2阶段的《启动设计和开发》。
-
点击查看开发者文档(扫码支付)后,这里感觉微信的文档没有支付宝好理解(略微吐槽下~~~),不过我们疏忽1切,直接进入模式2:模式2最简单直接,不需要在商户后台进行配置,推荐大家使用,微信也说流程更加简单,我这里也讲的是模式2,模式1大家有兴趣可以自行研究下。
-
如上图,总流程有14步,主要流程是生成定单、调统1下单API、将返回的支付交易链接生成2维码展现;我这边主要就是将这3步结合springmvc后,成功生儿2维码以后,用户就能够扫码支付了。后面的回调跟跟我的另外一篇博文基本类似,大家鉴戒下就好了:支付宝:web页面扫码支付、网站支付、支付宝即时到账 + springmvc
4、实现:
-
准备:根据统1下单接口API我先定义了3个对象:UnifiedOrderRequest(统1下单要求参数(必填))、UnifiedOrderRequestExt(统1下单要求参数(非必填))、UnifiedOrderRespose(统1下单返回参数);具体以下代码,get、set方法可自行生产,太占篇幅。
UnifiedOrderRequest.class
/**
* 统1下单要求参数(必填)
* @author Y
*
*/
public class UnifiedOrderRequest {
private String appid; //公众账号ID
private String mch_id; //商户号
private String nonce_str; //随机字符串
private String sign; //签名
private String body; //商品描写
private String out_trade_no; //商户定单号
private String total_fee; //总金额
private String spbill_create_ip; //终端IP
private String notify_url; //通知地址
private String trade_type; //交易类型
}
UnifiedOrderRequestExt.class
/**
* 统1下单要求参数(非必填)
* @author Y
*
*/
public class UnifiedOrderRequestExt extends UnifiedOrderRequest{
private String device_info; //装备号
private String detail; //商品详情
private String attach; //附加数据
private String fee_type; //货币类型
private String time_start; //交易起始时间
private String time_expire; //交易结束时间
private String goods_tag; //商品标记
private String product_id; //商品ID
private String limit_pay; //指定支付方式
private String openid; //用户标识
}
UnifiedOrderRespose.class
/**
* 统1下单返回参数
* @author Y
*
*/
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; //2维码链接
}
-
Controller主入口:
/**
* 创建2维码
*/
@RequestMapping("createQRCode")
public void createQRCode(String orderId, HttpServletResponse response) {
//生成定单
String orderInfo = createOrderInfo(orderId);
//调统1下单API
String code_url = httpOrder(orderInfo);
//将返回预支付交易链接(code_url)生成2维码图片
//这里使用的是zxing 说明1(见文末) try {
int width = 200;
int height = 200;
String format = "png";
Hashtable hints = new Hashtable();
hints.put(EncodeHintType.CHARACTER_SET, "utf⑻");
BitMatrix bitMatrix = new MultiFormatWriter().encode(code_url, BarcodeFormat.QR_CODE, width, height, hints);
OutputStream out = null;
out = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, format, out);
out.flush();
out.close();
} catch (Exception e) {
}
}
-
生成定单:分两部份:1部份是业务需求的定单信息,就是发起支付前的定单信息,业务系统自行创建存储;另外一部份是满足统1下单API要求的定单信息(也是我们这里要讲的)。“xxxxxx”:是你需要自己填写的对应信息:
/**
* 生成定单
* @param orderId
* @return
*/
private String createOrderInfo(String orderId) {
//生成定单对象
UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest();
unifiedOrderRequest.setAppid("xxxxxxxxxxxxx");//公众账号ID
unifiedOrderRequest.setMch_id("xxxxxxxxx");//商户号
unifiedOrderRequest.setNonce_str(StringUtil.makeUUID());//随机字符串 说明2(见文末) unifiedOrderRequest.setBody("xxxxxx");//商品描写
unifiedOrderRequest.setOut_trade_no(orderId);//商户定单号
unifiedOrderRequest.setTotal_fee("x"); //金额需要扩大100倍:1代表支付时是0.01
unifiedOrderRequest.setSpbill_create_ip("xxxxxxxxxxxxx");//终端IP
unifiedOrderRequest.setNotify_url("xxxxxxxxxxxxxx");//通知地址
unifiedOrderRequest.setTrade_type("NATIVE");//JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付
unifiedOrderRequest.setSign(createSign(unifiedOrderRequest));//签名说明5(见文末,签名方法1并给出) //将定单对象转为xml格式
XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_"))); //说明3(见文末) xStream.alias("xml", UnifiedOrderRequest.class);//根元素名需要是xml
return xStream.toXML(unifiedOrderRequest);
}
-
调统1下单API:根据要求将生成定单中返回的xml向微信给定的统1下单URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder,发送要求,成功并取得2维码。
/**
* 调统1下单API
* @param orderInfo
* @return
*/
private String 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());
buffOutStr.flush();
buffOutStr.close();
//获得输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = null;
StringBuffer sb = new StringBuffer();
while((line = reader.readLine())!= null){
sb.append(line);
}
XStream xStream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));//说明3(见文末)
//将要求返回的内容通过xStream转换为UnifiedOrderRespose对象
xStream.alias("xml", UnifiedOrderRespose.class);
UnifiedOrderRespose unifiedOrderRespose = (UnifiedOrderRespose) xStream.fromXML(sb.toString());
//根据微信文档return_code 和result_code都为SUCCESS的时候才会返回code_url
//说明4(见文末) if(null!=unifiedOrderRespose
&& "SUCCESS".equals(unifiedOrderRespose.getReturn_code())
&& "SUCCESS".equals(unifiedOrderRespose.getResult_code())){
return unifiedOrderRespose.getCode_url();
}else{
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
-
将返回的支付交易链接生成2维码展现:没有异常的情况下,在页面中使用标签接收就行。实际使用时,结合前端和业务的需求放置2维码。可以在扫码支付/案例及规范中找到部份素材和界面规范来设计微信风格的支付页面。
-
用户可以通过维系客户端进行扫码支付。支付完成后回调我们notify_url设置的url,通过成功的回调来更改业务系统中的定单状态或1些业务需求。这里回调没有写出可以参考支付宝:web页面扫码支付、网站支付、支付宝即时到账
+ springmvc中的回调。
5、说明:
-
2维码可以查看zxing实现2维码生成和解析;微信这边也提供了2维码的学习,大家有兴趣可以看看:http://www.thonky.com/qr-code-tutorial/ 和http://coolshell.cn/articles/10590.html
-
随机字符串:微信对随机字符串的要求是不超过32位。我这边是这样生成的,用时间戳。
/**
* 创建UUID
* @return
*/
public static synchronized String makeUUID() {
Date date = new Date();
StringBuffer s = new StringBuffer(DateUtil.formatYmdhmsm(date));
return s.append((new Random().nextInt(900) + 100)).toString();
}
-
使用Xstream时,由于微信定义的变量名大部份使用了“_”,但是在Xstream中它是关键字,所以会自动变成“__”,引发报错。详情请看:XStream异常:对象转为XML时,会把"_"转成"__";报错:(Lcom/thoughtworks/xstream/io/naming/NameCoder;)V
-
获得2维码链接时,只有在return_code 和result_code都为SUCCESS的时候有返回;这里我就简单的满足时返回,不满足返回null,您写的时候需要结合业务斟酌下,是不是需要增加判断,从而满足不同的业务场景。统1下单API
-
签名在上面1直没有详细说明,首先查看微信的安全规范中签名算法。key值,需要自己填写
/**
* 生成签名
*
* @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 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 + "&");
}
}
//第2步拼接key,key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
sb.append("key=" +"xxxxxxxxxxxxxxxxx");
String sign = MD5Util.MD5Encode(sb.toString(), "utf⑻")
.toUpperCase();//MD5加密
return sign;
}
相干文章:支付宝:web页面扫码支付、网站支付、支付宝即时到账 + springmvc