官方有关扫码支付的相关API
https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
业务流程说明:
(1)商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
(2)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(3)微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包,详细请见"本节3.1回调数据输入参数"
(4)商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。
(5)商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)
(6)微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
(7)商户后台系统得到交易会话标识prepay_id(2小时内有效)。
(8)商户后台系统将prepay_id返回给微信支付系统。返回数据见"本节3.2回调数据输出参数"
(9)微信支付系统根据交易会话标识,发起用户端授权支付流程。
(10)用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
(11)微信支付系统验证后扣款,完成支付交易。
(12)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(13)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(14)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(15)商户确认订单已支付后给用户发货。
二维码生成调用第三方插件
谷歌的 zxing
生成二维码的代码如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String appId = WxConfig.appId;
String mchId = WxConfig.mchId;
String apiKey = WxConfig.apiKey;
String currTime = PayCommonUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayCommonUtil.buildRandom(4) + "";
String nonce_str = strTime + strRandom;
Calendar ca = Calendar.getInstance();
ca.setTime(new Date());
ca.add(Calendar.DATE, 1);
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", appId);//公众帐号ID
packageParams.put("mch_id", mchId);//商户号
packageParams.put("time_stamp",currTime);//时间戳
packageParams.put("nonce_str", nonce_str); //随机字符串
packageParams.put("product_id", nonce_str);//商品ID
String sign = PayCommonUtil.createSign("UTF-8", packageParams, apiKey);
packageParams.put("sign", sign);
String requestXML = ToUrlParams(packageParams);
//String payurl = "weixin://wxpay/bizpayurl?" + requestXML;
String payurl = "weixin://wxpay/bizpayurl?sign=" + sign+"&appid="+appId+"&mch_id="+mchId+"&time_stamp="+currTime+"&nonce_str="+nonce_str+"&product_id="+nonce_str;
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(payurl,BarcodeFormat.QR_CODE, defaultWidthAndHeight, 200);
OutputStream out = response.getOutputStream();
MatrixToImageWriter.writeToStream(bitMatrix, "png", out);//输出二维码
out.flush();
out.close();
} catch (WriterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
回调方法
public static String PAY_API = "https://api.mch.weixin.qq.com/pay/unifiedorder"; -- 调用统一下单方法
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
String apiKey = WxConfig.apiKey;
/**
* 获取用户扫描二维码后,微信返回的信息
*/
InputStream inStream = req.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 result = new String(outSteam.toByteArray(),"utf-8");
/**
* 调用统一下单
*
* */
SortedMap<Object, Object> reParams = new TreeMap<Object, Object>();
try {
reParams = XMLUtil.xmlConvertToSortedMap(result);
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String retXml="";//反馈给微信服务器
//验证签名
if (PayCommonUtil.isTenpaySign("UTF-8", reParams, apiKey)) {
//统一下单
String appId = WxConfig.appId;
String mchId = WxConfig.mchId;
String currTime = PayCommonUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayCommonUtil.buildRandom(4) + "";
String nonce_str = strTime + strRandom;
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", appId);
packageParams.put("openid", reParams.get("openid"));
packageParams.put("mch_id", mchId);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", "测试");
packageParams.put("out_trade_no", reParams.get("product_id"));
packageParams.put("total_fee", "1");
packageParams.put("spbill_create_ip", "127.0.0.1");
packageParams.put("trade_type", "NATIVE");
packageParams.put("notify_url", "http://netbar1.legentec.com/riskmanage/unifiedOrder");//支付后返回结果
String sign = PayCommonUtil.createSign("UTF-8", packageParams, apiKey);
packageParams.put("sign", sign);
String requestXML = PayCommonUtil.getRequestXml(packageParams);
System.out.println("请求xml::::"+requestXML);
String resXml = HttpUtil.postData(PAY_API, requestXML);
System.out.println("返回xml::::"+resXml);
SortedMap<Object, Object> sortParams = new TreeMap<Object, Object>();
try {
sortParams = XMLUtil.xmlConvertToSortedMap(resXml);
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//验签
if (PayCommonUtil.isTenpaySign("UTF-8", sortParams, apiKey)) {
String resultCode = (String) sortParams.get("result_code");
if(resultCode.equals("SUCCESS")) {
// 统一下单返回的参数
String prepay_id = (String)sortParams.get("prepay_id");//交易会话标识 2小时内有效
SortedMap<Object,Object> resParams = new TreeMap<Object,Object>();
resParams.put("return_code", "SUCCESS"); // 必须
resParams.put("return_msg", "OK");
resParams.put("appid", appId); // 必须
resParams.put("mch_id", mchId);
resParams.put("nonce_str", nonce_str); // 必须
resParams.put("prepay_id", prepay_id); // 必须
resParams.put("result_code", "SUCCESS"); // 必须
resParams.put("err_code_des", "OK");
String sign1 = PayCommonUtil.createSign("UTF-8", resParams,apiKey);
resParams.put("sign", sign1); //签名
retXml = PayCommonUtil.getRequestXml(resParams);
} else{
retXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA["+(String) sortParams.get("err_code_des")+"]]></return_msg>" + "</xml> ";
}
}else{
retXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[222222]]></return_msg>" + "</xml> ";
}
}else{
retXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[签名错误]]></return_msg>" + "</xml> ";
}
//------------------------------ //处理业务完毕 //------------------------------
BufferedOutputStream out = new BufferedOutputStream(
resp.getOutputStream());
out.write(retXml.getBytes());
out.flush();
out.close();
super.doPost(req, resp);
}
使用到的工具类
PayCommonUtil
package com.legentec.wechat.pay;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
@SuppressWarnings("rawtypes")
public class PayCommonUtil
{
/**
* 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
*
* @return boolean
*/
public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY)
{
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 (!"sign".equals(k) && null != v && !"".equals(v))
{
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
// 算出摘要
String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String tenpaySign = ((String) packageParams.get("sign")).toLowerCase();
// System.out.println(tenpaySign + " " + mysign);
return tenpaySign.equals(mysign);
}
/**
* @author
* @date 2016-4-22
* @Description:sign签名
* @param characterEncoding
* 编码格式
* @param parameters
* 请求参数
* @return
*/
public static String createSign(String characterEncoding, SortedMap<Object, Object> packageParams, String API_KEY)
{
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 + "&");
}
}
sb.append("key=" + API_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @author
* @date 2016-4-22
* @Description:将请求参数转换为xml格式的string
* @param parameters
* 请求参数
* @return
*/
public static String getRequestXml(SortedMap<Object, Object> parameters)
{
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.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 ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k))
{
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
}
else
{
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* 取出一个指定长度大小的随机正整数.
*
* @param length
* int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数。
*/
public static int buildRandom(int length)
{
int num = 1;
double random = Math.random();
if (random < 0.1)
{
random = random + 0.1;
}
for (int i = 0; i < length; i++)
{
num = num * 10;
}
return (int) ((random * num));
}
/**
* 获取当前时间 yyyyMMddHHmmss
*
* @return String
*/
public static String getCurrTime()
{
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
}
XMLUtil
package com.legentec.wechat.pay;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
@SuppressWarnings("rawtypes")
public class XMLUtil
{
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
*
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
@SuppressWarnings("unchecked")
public static Map doXMLParse(String strxml) throws JDOMException, IOException
{
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml))
{
return null;
}
Map m = new HashMap();
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 = XMLUtil.getChildrenText(children);
}
m.put(k, v);
}
// 关闭流
in.close();
return m;
}
public static SortedMap xmlConvertToSortedMap(String strxml) throws JDOMException, IOException
{
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml))
{
return null;
}
SortedMap<Object, Object> m = new TreeMap<Object, Object>();
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 = XMLUtil.getChildrenText(children);
}
m.put(k, v);
}
// 关闭流
in.close();
return m;
}
/**
* 获取子结点的xml
*
* @param children
* @return String
*/
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(XMLUtil.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
}
使用JAR包
支付完成后调用统一下配置的地址(回调地址一定为POST请求)
package com.wxpay;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jdom.JDOMException;
import com.legentec.wechat.WxConfig;
import com.legentec.wechat.pay.PayCommonUtil;
import com.legentec.wechat.pay.XMLUtil;
public class unifiedOrder extends HttpServlet {
protected static String getReturnXML(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code
+ "]]></return_code><return_msg><![CDATA[" + return_msg
+ "]]></return_msg></xml>";
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 读取参数
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = req.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(
inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
try {
packageParams = XMLUtil.xmlConvertToSortedMap(sb.toString());
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 账号信息
String key = WxConfig.apiKey;
String resXml = "";
// 反馈给微信服务器
// 判断签名是否正确
if (PayCommonUtil.isTenpaySign("UTF-8", packageParams, key)) {
// ------------------------------ // 处理业务开始 //
// ------------------------------
if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
// 这里是支付成功 // 执行自己的业务逻辑
String mch_id = (String) packageParams.get("mch_id");
String openid = (String) packageParams.get("openid");
String is_subscribe = (String) packageParams
.get("is_subscribe");
String out_trade_no = (String) packageParams
.get("out_trade_no");
String total_fee = (String) packageParams.get("total_fee");
// 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>"
+ "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
resXml = "<xml>"
+ "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>"
+ "</xml> ";
}
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
}
// ------------------------------ // 处理业务完毕 //
BufferedOutputStream out = new BufferedOutputStream(
resp.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
super.doGet(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
super.doPost(req, resp);
}
}