微信的API的流程图 :
理解流程图特别重要,方便用来规范的写Java。
微信支付API流程图
商户接入微信支付,调用API必须遵循以下规则:
表4.1 接口规则
传输方式 | 为保证交易安全性,采用HTTPS传输 |
---|---|
提交方式 | 采用POST方法提交 |
数据格式 | 提交和返回数据都为XML格式,根节点名为xml |
字符编码 | 统一采用UTF-8字符编码 |
签名算法 | MD5,后续会兼容SHA1、SHA256、HMAC等。 |
签名要求 | 请求和接收数据均需要校验签名,详细方法请参考安全规范-签名算法 |
证书要求 | 调用申请退款、撤销订单接口需要商户证书 |
判断逻辑 | 先判断协议字段返回,再判断业务返回,最后判断交易状态 |
下面是代码讲解:
首先是三个Javabean:
OrderInfo,OrderReturnInfo,SignInfo
//OrderInfo的Java文件
package com.weixinpay.model;
/**
* 预订单
*
* @author zuoliangzhu
*
*/
public class OrderInfo {
private String appid;// 小程序ID
private String mch_id;// 商户号
private String nonce_str;// 随机字符串
private String sign_type;//签名类型
private String sign;// 签名
private String body;// 商品描述
private String out_trade_no;// 商户订单号
private int total_fee;// 标价金额 ,单位为分
private String spbill_create_ip;// 终端IP
private String notify_url;// 通知地址
private String trade_type;// 交易类型
private String openid;//用户标识
public String getSign_type() {
return sign_type;
}
public void setSign_type(String sign_type) {
this.sign_type = sign_type;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public int getTotal_fee() {
return total_fee;
}
public void setTotal_fee(int total_fee) {
this.total_fee = total_fee;
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
}
//OrderReturnInfo的Java文件
package com.weixinpay.model;
public class OrderReturnInfo {
private String return_code;
private String return_msg;
private String result_code;
private String appid;
private String mch_id;
private String nonce_str;
private String sign;
private String prepay_id;
private String trade_type;
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
}
//SingInfo的Java文件
package com.weixinpay.model;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 签名信息
* @author zuoliangzhu
*
*/
public class SignInfo {
private String appId;//小程序ID
private String timeStamp;//时间戳
private String nonceStr;//随机串
@XStreamAlias("package")
private String repay_id;
private String signType;//签名方式
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public String getRepay_id() {
return repay_id;
}
public void setRepay_id(String repay_id) {
this.repay_id = repay_id;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
}
//GetOpenId.java
package com.weixinpay;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
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.HttpClients;
import org.apache.http.util.EntityUtils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.weixinpay.common.Configure;
/**
* Servlet implementation class GetOpenId
*/
public class GetOpenId extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public GetOpenId() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String code = request.getParameter("code");
HttpGet httpGet = new HttpGet("https://api.weixin.qq.com/sns/jscode2session?appid="+Configure.getAppID()+"&secret="+Configure.getSecret()+"&js_code="+code+"&grant_type=authorization_code");
//设置请求器的配置
HttpClient httpClient = HttpClients.createDefault();
HttpResponse res = httpClient.execute(httpGet);
HttpEntity entity = res.getEntity();
String result = EntityUtils.toString(entity, "UTF-8");
response.getWriter().append(result);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
这一步,是得到code,然后将APPID,小程序秘钥和code拼接起来,发送到微信支付的服务商后台,返回的结果是一个键值对组。
openID的结果:{"session_key":"XXXXXXXXXXXXXXXXXXX==","openid":"XXXXXXXXXXXXXXXXXXXXXXXXXX"}
得到openID之后就可以预下单处理了。
package com.weixinpay;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.alibaba.fastjson.JSONObject;
import com.thoughtworks.xstream.XStream;
import com.weixinpay.common.Configure;
import com.weixinpay.common.HttpRequest;
import com.weixinpay.common.RandomStringGenerator;
import com.weixinpay.common.Signature;
import com.weixinpay.model.OrderInfo;
import com.weixinpay.model.OrderReturnInfo;
/**
* 统一下单接口
*/
public class Xiadan extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger L = Logger.getLogger(Xiadan.class);
/**
* @see HttpServlet#HttpServlet()
*/
public Xiadan() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
try {
String openid = request.getParameter("openid"); //得到openID
OrderInfo order = new OrderInfo();
order.setAppid(Configure.getAppID());
order.setMch_id(Configure.getMch_id());
order.setNonce_str(RandomStringGenerator.getRandomStringByLength(32));
String name="asdasd"; //商品名称,如果需要中文的商品名称需要转成 UTF-8的形式,否则会报错!
order.setBody(name);
order.setOut_trade_no(RandomStringGenerator.getRandomStringByLength(32));//随机的32位字符串
order.setTotal_fee(1); //这里的1 是1分钱的意思,a*100就是a块钱
order.setSpbill_create_ip("需要返回到你的服务器额IP地址");
order.setNotify_url("http://localhost:8080/weixinpay/PayResult");
order.setTrade_type("JSAPI");
order.setOpenid(openid);
order.setSign_type("MD5");
//生成签名
String sign = Signature.getSign(order);
order.setSign(sign);
String result = HttpRequest.sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder", order);
System.out.println("___________________________"+result);//测试看返回结果,结果是一个XML的形式
L.info("---------下单返回:"+result);
XStream xStream = new XStream();
xStream.alias("xml", OrderReturnInfo.class);
OrderReturnInfo returnInfo = (OrderReturnInfo)xStream.fromXML(result);
JSONObject json = new JSONObject();
json.put("prepay_id", returnInfo.getPrepay_id());//从结果中读取prepay_id
response.getWriter().append(json.toJSONString());
} catch (Exception e) {
e.printStackTrace();
L.error("-------", e);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
String result = HttpRequest.sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder", order);
把内容保存为一个对象,
传过去这个对象,返回的结果是xml的形式,取出prepay_id,json传输
下面是签名sign
//签名
package com.weixinpay;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.alibaba.fastjson.JSONObject;
import com.weixinpay.common.Configure;
import com.weixinpay.common.RandomStringGenerator;
import com.weixinpay.common.Signature;
import com.weixinpay.model.SignInfo;
/**
* 再签名
*/
public class Sign extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger L = Logger.getLogger(Sign.class);
public Sign() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String repay_id = request.getParameter("repay_id");
SignInfo signInfo = new SignInfo();
signInfo.setAppId(Configure.getAppID());
long time = System.currentTimeMillis()/1000;
signInfo.setTimeStamp(String.valueOf(time));
signInfo.setNonceStr(RandomStringGenerator.getRandomStringByLength(32));
signInfo.setRepay_id("prepay_id="+repay_id);
signInfo.setSignType("MD5");
//生成签名
System.out.println("_________________________________________________________");
String sign = Signature.getSign(signInfo);
JSONObject json = new JSONObject();
json.put("timeStamp", signInfo.getTimeStamp());
json.put("nonceStr", signInfo.getNonceStr());
json.put("package", signInfo.getRepay_id());
json.put("signType", signInfo.getSignType());
json.put("paySign", sign);
L.info("-------再签名:"+json.toJSONString());
response.getWriter().append(json.toJSONString());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
L.error("-------", e);
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
MD5 是一种摘要生成算法, 通过在签名原始串后加上商户通信密钥的内容进行 MD5 运算, 形成的摘要字符串即为签名结果。
最后进行支付结果的判断:
package com.weixinpay;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.StringReader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import com.weixinpay.common.StreamUtil;
/**
* 接收支付结果
*/
public class PayResult extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger L = Logger.getLogger(PayResult.class);
/**
* @see HttpServlet#HttpServlet()
*/
public PayResult() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//response.getWriter().append("Served at: ").append(request.getContextPath());
String reqParams = StreamUtil.read(request.getInputStream());
L.info("-------支付结果:"+reqParams);
StringBuffer sb = new StringBuffer("SUCCESS OK ");
response.getWriter().append(sb.toString());
L.error("___________________________________");
L.error("微信支付----验证签名成功");
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
通知参数
字段名 变量名 必填 类型 示例值 描述 返回状态码 return_code 是 String(16) SUCCESS SUCCESS/FAIL
此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
返回信息 return_msg 否 String(128) 签名失败 返回信息,如非空,为错误原因
签名失败
参数格式校验错误
StringBuffer sb = new StringBuffer("
"); SUCCESS OK 就像这样。
大概就是这么些东西,以后等我再研究研究就在更新下。
github还没发过东西,暂时先在CSDN上发一下代码吧。
大二新手做的,如果有错误,欢迎指出,谢谢大家!
代码链接:
https://download.csdn.net/download/lzjstudy/10548783