APP微信支付---Java服务端接口(不使用证书)
- 开通微信支付
- 介绍
- 详细接口
-
- service层,参数用户id,移动端传过来的
- 获取预支付id
- 返会给移动端的调用微信支付参数,商户号和APPID移动端存储
- 回调接口请求微信统一下单的时候传过去的
- 上面接口调用这个方法
- 查询微信支付订单状态接口
- 工具类
-
- 生成签名方法,将请求参数转换为xml格式的string的方法 工具类
- 加密类,生成签名时使用
- HttpClient请求类
- 解析XML工具类,将xml字符串转换成map
- redis工具类,这个就不发了,网上一堆,把调用redis工具类的地方改成自己的方法就行
- 改进
- 总结
开通微信支付
参考微信支付接入商户文档 https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Pay/Vendor_Service_Center.html
APP支付API文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1
直接使用微信正式环境测试
介绍
简单的集成APP支付,把商户号和APPID存到Android和IOS代码里面,防止调接口返回字段时,把这两个字段暴漏出去,私钥存到
Java后台代码里面,生成签名都是从后台生成,然后在返回给移动端。
详细接口
移动端请求接口获取调起微信支付的参数,服务端生成商户订单号,将订单号存入redis,设置时间大于2小时(微信支付生成的预支付订单号有效时长2小时),这个订单号后面回调的时候会用到,只是为了方便没有存入数据库,也可以存到数据库,下面是代码,工具类在最下面
service层,参数用户id,移动端传过来的
public String wxPaySubmitOrder(String accountId) throws Exception {
//生成商户订单号
//格式化当前时间
SimpleDateFormat sfDate = new SimpleDateFormat("yyyyMMddHHmmssSSS");
String strDate = sfDate.format(new Date());
String random = "TEST"+strDate;
//订单号存入redis,过期时间12个小时,微信生成的与支付的订单号有效时长也是两个小时,值:用户id
RedisHelper.setEX(random, accountId , 12 * 60);
//测试默认费用1分钱,微信支付单位是分
String pay = "1";
//获取预支付id
String paySubmitOrder = paySubmitOrder("测试", pay, random);
if (paySubmitOrder != null) {
//app调起微信支付参数
Map<Object, Object> mmap = getWXPayParam(paySubmitOrder);
return JSONUtil.toJSONString(mmap);
}
return null;
}
获取预支付id
微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
/**
* 获取预支付id接口
* @param desc 描述
* @param totleFee 花费 微信支付单位 分
* @param outTradeNo 商户订单号
* @return
* @throws Exception
*/
public String paySubmitOrder(String desc, String totleFee, String outTradeNo) throws Exception {
//组装参数,微信支付参数正序
SortedMap<Object, Object> packageParams = new TreeMap<>();
packageParams.put("appid", “123456”);//APPID,微信开放平台上可以看到
packageParams.put("mch_id", “123456”); //商户编号,微信开放平台上可以看到
packageParams.put("nonce_str", String.valueOf(System.currentTimeMillis())); //随机字符串
packageParams.put("body", desc); //商品描述
packageParams.put("out_trade_no", outTradeNo); //商户订单号
packageParams.put("total_fee", totleFee); //花费 单位分
//回调地址,接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
packageParams.put("notify_url", "http://www.weixin.qq.com/wxpay/pay.php");
packageParams.put("trade_type", “APP”); //支付类型
packageParams.put("spbill_create_ip", InetAddress.getLocalHost().getHostAddress()); //请求ip,可以从移动端传过来
//生成签名的方法在最文章后面
String sign = PayCommonUtil.createSign("UTF-8", packageParams, “123456”); //生成签名,最后一个参数是你的私钥,微信开放平台查看
packageParams.put("sign", sign); //签名;
//微信参数封装成XML格式的字符串,方法在文章最后面
String requestXML = PayCommonUtil.getRequestXml(packageParams);
//调用微信统一下单接口,得到含有prepay_id的XML,方法在文章最后面
String resXml = HttpUtil.postData(“https://api.mch.weixin.qq.com/pay/unifiedorder”, requestXML);
//解析XML转换成Map,方法在文章最后面
Map map = XMLUtil.doXMLParse(resXml);
//是否生成与支付订单号
if(map.get("return_code").toString().equals("SUCCESS") && map.get("result_code").toString().equals("SUCCESS")){
//获取预支付订单号,返回到service层
return map.get("prepay_id").toString();
}
return null;
}
返会给移动端的调用微信支付参数,商户号和APPID移动端存储
微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12&index=2
/**
* app调起微信支付参数
* @param prepayid 预支付id
* 返回参数移除APPID和商户id,移动端代码中有,接口不返回
* @return
*/
public Map<Object, Object> getWXPayParam(String prepayid) {
SortedMap<Object, Object> packageParams = new TreeMap<>();
packageParams.put("appid", “123456”); //APPID
packageParams.put("partnerid", “123456”); //商户号
packageParams.put("prepayid",prepayid); //预支付id
packageParams.put("noncestr",String.valueOf(System.currentTimeMillis())); //随机字符串
packageParams.put("timestamp",System.currentTimeMillis()/1000); //时间戳
packageParams.put("package","Sign=WXPay"); //默认参数
String sign = PayCommonUtil.createSign("UTF-8", packageParams, “123456”); //生成签名,最后一个参数是你的私钥,微信开放平台查看
packageParams.put("sign", sign); //签名
packageParams.remove("appid"); //移除APPID
packageParams.remove("partnerid"); //移除商户id
return packageParams;
}
回调接口请求微信统一下单的时候传过去的
public String wxPayNotify(HttpServletRequest request) {
SortedMap<Object, Object> packageParams = new TreeMap<>();
if (wxPayNotifyOperate(request)) {
packageParams.put("return_code", "SUCCESS");
packageParams.put("return_msg", "OK");
return PayCommonUtil.getRequestXml(packageParams);
}
packageParams.put("return_code", "FAIL");
packageParams.put("return_msg", "FAIL");
return PayCommonUtil.getRequestXml(packageParams);
}
上面接口调用这个方法
public boolean wxPayNotifyOperate(HttpServletRequest request) {
//获取结果集
InputStream inputStream;
StringBuffer sb = new StringBuffer();
try {
inputStream = request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(
inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
//将返回的xml转换成map
Map<String, String> map = XMLUtil.doXMLParse(sb.toString());
if (map.get("return_code").equals("SUCCESS") && map.get("result_code").equals("SUCCESS")) {
String transaction_id = map.get("transaction_id"); //微信订单号
String out_trade_no = map.get("out_trade_no"); //商户订单号
//查询redis中是否存在该商户订单号
String data = RedisHelper.get(out_trade_no);
if (data != null && !data.equals("")) {
//data == 用户id
String id = data;
//查询支付结果
boolean payResult = findPayResult(transaction_id, out_trade_no);
//支付成功,进行数据库操作
if (payResult) {
//进行数据库操作,省略,业务不同。
//操作完成删除key
RedisHelper.del(out_trade_no);
} else {
//状态异常
return false;
}
}
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
查询微信支付订单状态接口
微信官方api文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_2&index=4
/**
* 查询订单结果
* @param transactionId 微信订单号
* @param outTradeNo 商户订单号
* @return
* @throws Exception
*/
public boolean findPayResult(String transactionId, String outTradeNo) throws Exception {
String timeMillis = String.valueOf(System.currentTimeMillis());
SortedMap<Object, Object> packageParams = new TreeMap<>();
packageParams.put("appid", "123456"); //APPID
packageParams.put("mch_id", "123456"); //商户号
packageParams.put("transaction_id", transactionId); //微信订单号
packageParams.put("out_trade_no", outTradeNo); //商户订单号
packageParams.put("nonce_str", timeMillis); //随机字符串
String sign = PayCommonUtil.createSign("UTF-8", packageParams, “123456”); //生成签名,最后一个参数是你的私钥,微信开放平台查看
packageParams.put("sign", sign); //签名
String requestXML = PayCommonUtil.getRequestXml(packageParams);//组装参数xml格式
//得到含有prepay_id的XML
String resXml = HttpUtil.postData(“https://api.mch.weixin.qq.com/pay/orderquery”, requestXML);
//解析XML存入Map
Map map = XMLUtil.doXMLParse(resXml);
//查询支付结果 SUCCESS—支付成功
if(map.get("return_code").toString().equals("SUCCESS") && map.get("result_code").toString().equals("SUCCESS")){
//支付状态 == SUCCESS ,支付成功
if(map.get("trade_state").toString().equals("SUCCESS")){
return true;
}
}
return false;
}
工具类
生成签名方法,将请求参数转换为xml格式的string的方法 工具类
public class PayCommonUtil {
/**
* @Description:sign签名
* @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 = entry.getKey().toString();
String v = entry.getValue().toString();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = WeixMD5.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @Description:将请求参数转换为xml格式的string
* @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 = entry.getKey().toString();
String v = entry.getValue().toString();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + v + "</" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
}
加密类,生成签名时使用
public class WeixMD5
{
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5","6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
HttpClient请求类
public class HttpUtil {
private final static int CONNECT_TIMEOUT = 100000 ;
private final static String DEFAULT_ENCODING = "UTF-8";
public static String postData(String urlStr, String data) {
BufferedReader reader = null;
try {
URL url = new URL(urlStr);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(CONNECT_TIMEOUT);
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream(), DEFAULT_ENCODING);
if (data == null)
data = "";
writer.write(data);
writer.flush();
writer.close();
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), DEFAULT_ENCODING));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\r\n");
}
return sb.toString();
} catch (IOException e) {
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
}
}
return null;
}
}
解析XML工具类,将xml字符串转换成map
public class XMLUtil
{
public static Map doXMLParse(String strxml) throws Exception
{
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;
}
/**
* 获取子结点的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();
}
}
redis工具类,这个就不发了,网上一堆,把调用redis工具类的地方改成自己的方法就行
改进
我把生成的商户订单号存入redis中,设置了12个小时,但是也可能会过期,所以最好存到数据库。
总结
主要流程是:移动端请求>>服务端生成商户订单号,存入redis,请求统一下单接口,获取预支付id,组装移动端调起微信支付参数>>返回移动端>>移动端组装参数,调起微信支付>>支付成功,回调本地接口,获取接口中商户订单号,查询redis中是否存在相应的key,如果存在,判断交易状态是否是成功,如果成功,进行业务操作,删除redis中等于商户订单号的key。
写的非常简单,有很多不足,希望可以帮我改进。