开发微信公众号支付准备资料
①APPID,这个数据我们可以从“申请微信支付成功”的邮件中获取。
②AppSecret,这个数据,大家可以看上图中获取。
③Mch_id,这是值是微信支付商户号,大家可以从邮件中获取
④KEY,这个参数KEY是在商户后台配置的一个32位的key,微信商户平台-账户设置-安全设置-api安全,在这里设置。这个值是可以自行设置的。
开发微信支付首先要看微信支付的业务流程,看官方文档。
微信支付的流程:
→用户访问微信OAuth2.0网站,通过OAuth2.0的重定向获得code
→根据code获得用户的标识符openid,这个参数在调用统一下单接口中会用到
→调用统一下单API获得prepay_id
→获得prepay_id后,接下来要调用通过“网页端调起支付API”,这个调用的API需要一系列参数,这些参数我们在后台进行组装,通过JSON传到前台去
→ 获得数据后通过调用JSPAI发起微信支付
→ 用户输入支付密码,支付完成
→ 等待微信回调,回调中处理业务
→流程结束
微信支付,这里我给大家详细介绍
①maven项目的pom.xml
<dependencies>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.6version>
dependency>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.1.3version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>fluent-hcartifactId>
<version>4.3.6version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.3.6version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclient-cacheartifactId>
<version>4.3.6version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpcoreartifactId>
<version>4.3.3version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpmimeartifactId>
<version>4.3.6version>
dependency>
<dependency>
<groupId>org.jsoupgroupId>
<artifactId>jsoupartifactId>
<version>1.7.2version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.1version>
dependency>
dependencies>
②获得openid
官方文档https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4
先要设置OAuth2.0授权回调页面域名,不然会出现“redirect uri 参数错误”
微信公众平台→开发→接口权限→网页服务→网页账号
设置好后,根据文档组装OAuth2.0的URL
String url = "https://open.weixin.qq.com/connect/oauth2/authorize"//这个是文档里面提供的url
+ "?appid=" + appid//这个填写之前准备的appid
+ "&redirect_uri=" + REDIRECT_URI //这个是用户确认授权后重定向的url,url需要进行Enconding编码,微信会在这个url上附加参数code,后台可以用 String code = request.getParameter("code"); 获取到
+ "&response_type=code" //必填,这个参数不用改
+ "&scope=" + SCOPE// 这个参数建议填写"snsapi_base",就是让用户默认授权,即不会弹出是否授权界面
+ "&state=" +STATE//这个值可以随便修改,会附加在回调url中
+ "#wechat_redirect"; //必填,这个参数不用改
组装好url后,让用户访问。在我设置的回调url中,我们可以通过
String code = request.getParameter("code");
获得code值。
直接我写了个工具类是关于微信支付一些简单的调用
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import ***********************.WeChatConst;//一些常量
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* Created by Arthur on 2015/11/10 16:45.
*/
public class WeChatUtil {
/**
* 通过Code 获取用户 openid 和 access_token
* @param code
* @return
* @throws IOException
*/
public static Map getOpenIdByCode(String code) throws IOException {
/**
* 设置访问路径
*/
HttpPost httppost = new HttpPost("https://api.weixin.qq.com/sns/oauth2/access_token");
/**
* 组装请求参数
*/
String reqEntityStr = "appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
reqEntityStr = reqEntityStr.replace("APPID", WeChatConst.APPID);
reqEntityStr = reqEntityStr.replace("SECRET", WeChatConst.APP_SECRET);
reqEntityStr = reqEntityStr.replace("CODE", code);
StringEntity reqEntity = new StringEntity(reqEntityStr);
/**
* 设置浏览器
*/
DefaultHttpClient httpclient = new DefaultHttpClient();
/**
* 设置参数
*/
httppost.setEntity(reqEntity);
/**
* 发起请求
*/
HttpResponse response = httpclient.execute(httppost);
/**
* 获得请求内容
*/
String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
/**
* 分析内容
*/
JSONObject jsonObject = (JSONObject) JSON.parse(strResult);
Map map = new HashMap<>();
map.put("openid",jsonObject.get("openid"));
map.put("access_token",jsonObject.get("access_token"));
map.put("refresh_token",jsonObject.get("refresh_token"));
return map;
}
/**
* 统一下单
* 获得PrePayId
* @param body 商品或支付单简要描述
* @param out_trade_no 商户系统内部的订单号,32个字符内、可包含字母
* @param total_fee 订单总金额,单位为分
* @param IP APP和网页支付提交用户端ip
* @param notify_url 接收微信支付异步通知回调地址
* @param openid 用户openId
* @throws IOException
*/
public static String unifiedorder(String body,String out_trade_no,Integer total_fee,String IP,String notify_url,String openid)throws IOException {
/**
* 设置访问路径
*/
HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
/**
* 组装请求参数
* 按照ASCII排序
*/
String reqEntityStr = "appid=APPID" +
"&body=BODY" +
"&mch_id=MCH_ID" +
"&nonce_str=NONCE_STR" +
"¬ify_url=NOTIFY_URL" +
"&openid=OPENID" +
"&out_trade_no=OUT_TRADE_NO" +
"&spbill_create_ip=IP" +
"&total_fee=TOTAL_FEE" +
"&trade_type=TRADE_TYPE"
;//这个字段是用于之后MD5加密的,字段要按照ascii码顺序排序
/**
* 设置数据
*/
String nonce_str = getNonceStr().toUpperCase();//随机数据
reqEntityStr = reqEntityStr.replace("APPID", WeChatConst.APPID);
reqEntityStr = reqEntityStr.replace("MCH_ID", WeChatConst.MCH_ID);
reqEntityStr = reqEntityStr.replace("NONCE_STR", nonce_str);
reqEntityStr = reqEntityStr.replace("BODY", body);
reqEntityStr = reqEntityStr.replace("OUT_TRADE_NO", out_trade_no);
reqEntityStr = reqEntityStr.replace("TOTAL_FEE", total_fee.toString());
reqEntityStr = reqEntityStr.replace("IP", IP);
reqEntityStr = reqEntityStr.replace("NOTIFY_URL", notify_url);
reqEntityStr = reqEntityStr.replace("TRADE_TYPE", WeChatConst.TRADE_TYPE);
reqEntityStr = reqEntityStr.replace("OPENID", openid);
/**
* 生成sign的数据
*/
String sign = reqEntityStr + "&key="+WeChatConst.KEY;
sign = MD5Util.MD5(sign).toUpperCase();
/**
* 组装XML
*/
StringBuilder sb = new StringBuilder("");
sb.append("" );
setXmlKV(sb,"appid",WeChatConst.APPID);
setXmlKV(sb,"body",body);
setXmlKV(sb,"mch_id",WeChatConst.MCH_ID);
setXmlKV(sb,"nonce_str",nonce_str);
setXmlKV(sb,"notify_url",notify_url);
setXmlKV(sb,"openid",openid);
setXmlKV(sb,"out_trade_no",out_trade_no);
setXmlKV(sb,"spbill_create_ip",IP);
setXmlKV(sb,"total_fee",total_fee.toString());
setXmlKV(sb,"trade_type",WeChatConst.TRADE_TYPE);
setXmlKV(sb,"sign",sign);
sb.append("");
// reqEntityStr = reqEntityStr.replace("sign", sign);
StringEntity reqEntity = new StringEntity(new String (sb.toString().getBytes("UTF-8"),"ISO8859-1"));//这个处理是为了防止传中文的时候出现签名错误
httppost.setEntity(reqEntity);
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpResponse response = httpclient.execute(httppost);
String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
// System.out.println(strResult);
return getPrePayId(strResult);
}
/**
* 获得32位随机字符串
* @return
*/
public static String getNonceStr(){
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
Random rd = new Random();
for(int i = 0 ; i < 32 ; i ++ ){
sb.append(str.charAt(rd.nextInt(str.length())));
}
return sb.toString();
}
/**
* 插入XML标签
* @param sb
* @param Key
* @param value
* @return
*/
public static StringBuilder setXmlKV(StringBuilder sb,String Key,String value){
sb.append("<");
sb.append(Key);
sb.append(">");
sb.append(value);
sb.append("");
sb.append(Key);
sb.append(">");
return sb;
}
/**
* 解析XML 获得 PrePayId
* @param xml
* @return
*/
public static String getPrePayId(String xml){
int start = xml.indexOf("" );
int end = xml.indexOf("");
if(start < 0 && end < 0){
return null;
}
return xml.substring(start + "" .length(),end)
.replace(","").replace("]]>","");
}
}
public interface WeChatConst {
String APPID = "";//填写APPID
String APP_SECRET = "";//填写AppSecret
String MCH_ID = "";//Mch_id,这是值是微信支付商户号,大家可以从邮件中获取
String KEY = "";//这个参数KEY是在商户后台配置的一个32位的key,微信商户平台-账户设置-安全设置-api安全
/**
* 交易类型
*/
String TRADE_TYPE = "JSAPI";
}
我们可以调用里面的“getOpenIdByCode”方法通过code 获得openid,这个openid我们先记录下来,稍后会用到的,接下来我们要经行调用统一下单接口。
③前端页面中,让用户选择产品
JS代码
/**
* 充值 提交信息
*/
function recharge(){
var $select = $('.price_caseSelected');
var productId = $select.attr("productId");
var options = {
dataType : 'json',
data:{
'productId':productId
},
success : function(data) {
if (data.success) {
prepay_id = data.prepay_id;
paySign = data.paySign;
appId = data.appId;
timeStamp = data.timeStamp;
nonceStr = data.nonceStr;
packageStr = data.packageStr;
signType = data.signType;
callpay();
} else {
alert(data.message);
}
}
};
$("#recharge_form").ajaxSubmit(options);
}
/************************** 微信支付 *************************/
var prepay_id ;
var paySign ;
var appId ;
var timeStamp ;
var nonceStr ;
var packageStr ;
var signType ;
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId" : appId, //公众号名称,由商户传入
"timeStamp" : timeStamp, //时间戳,自1970年以来的秒数
"nonceStr" : nonceStr , //随机串
"package" : packageStr,
"signType" : signType, //微信签名方式:
"paySign" : paySign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//这里填写支付成功的事件
} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
function callpay()
{
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
}
/******************* 微信支付 结束**************************************/
④后台调佣统一下单的API,获得prepay_id
**这步我们先要设置支付授权目录**
微信公众平台→微信支付→开发配置→测试授权目录和测试白名单。
直接配置支付授权目录也行。
后台获得到需要用户支付的金额,以及一些列的产品信息后,系统经行预下单,也是就调用调用统一下单的API,生成一个需要支付的订单,获得prepay_id,该值用于在前端页面,通过JSAPI中调用微信支付需要用到,代码看下面的⑤中的controller的方法,以及WeChatUtil。
统一下单大家可以调用我刚刚提供给大家的工具类中的WeChatUtil.unifiedorder这个方法,参数在方法上面的注释有写清楚。
这里如果你们想自行封装需要注意一个编码问题,这个会导致你回调产生签名错误。
⑤获得prepay_id后,我们需要在后台组装,调用微信JSAPI的接口数据。
/**
* 进行预下单
* @param request
* @return
*/
@RequestMapping(value = Mobile.URL.TEL.DO_RECHARGE)
@ResponseBody
public ReturnBean doRecharge(HttpServletRequest request,String productId){
String openid = getMsg(request, CacheConst.SESSION,CacheConst.OPENID);//获得openid,从缓存中取出
String openid = getMsg(request, CacheConst.SESSION,CacheConst.OPENID);//获得openid,从缓存中取出
String IP = HttpUtil.getIpAddr(request);//获得IP
/*************************************/
Map map = XXXXXXXXXX;//业务处理,根据自己的业务经行处理
//在业务层调用WeChatUtil.unifiedorder方法获得prepay_id
/*************************************/
String prepay_id = (String) map.get("prepay_id");
String timeStamp = String.valueOf((System.currentTimeMillis() / 1000));//1970年到现在的秒数
String nonceStr = WeChatUtil.getNonceStr().toUpperCase();//数据字符串
String packageStr = "prepay_id="+prepay_id;//prepay_id
String signType = "MD5";
String paySign =
"appId=" +WeChatConst.APPID+
"&nonceStr=" +nonceStr+
"&package=prepay_id=" + prepay_id +
"&signType=" + signType +
"&timeStamp=" + timeStamp+
"&key="+ WeChatConst.KEY;//注意这里的参数要根据ASCII码 排序
paySign = MD5Util.MD5(paySign).toUpperCase();//将数据MD5加密
/**
* 这里是将一系列的信息传到前台
*/
ReturnBean rb = new ReturnBean(true,"下单成功");
rb.setAppId(WeChatConst.APPID);
rb.setTimeStamp(timeStamp);
rb.setNonceStr(nonceStr);
rb.setPackageStr(packageStr);
rb.setSignType(signType);
rb.setPaySign(paySign);
rb.setPrepay_id(prepay_id);
return rb;
}
⑥前端获得到需要的值后,调用callpay()方法,经行调用微信支付JSAPI,然后用户输入支付密码,完成微信支付。后台系统等待微信支付的回调,也是在统一下单时候填写的url。
⑦回调controller
/**
* 微信回调
* @param request
* @param response
*/
@RequestMapping(value = Mobile.URL.CALLBACK.WE_CHAT_BACK)
public void weChatBack(HttpServletRequest request,HttpServletResponse response){
Document document = null;
try(
InputStream is = request.getInputStream()//获取流
) {
SAXReader reader = new SAXReader();
document = reader.read(is);//将流放到document中去
} catch (IOException | DocumentException e) {
wcbackLogger.error("",e);
}
Element root = document.getRootElement();
WeChatBean weChatBean = new WeChatBean();
/**
* 分析xml文件的内容
*/
weChatBean.setAppid(root.elementText("appid"));
weChatBean.setBank_type(root.elementText("bank_type"));
weChatBean.setCash_fee(root.elementText("cash_fee"));
weChatBean.setFee_type(root.elementText("fee_type"));
weChatBean.setIs_subscribe(root.elementText("is_subscribe"));
weChatBean.setMch_id(root.elementText("mch_id"));
weChatBean.setNonce_str(root.elementText("nonce_str"));
weChatBean.setOpenid(root.elementText("openid"));
weChatBean.setOut_trade_no(root.elementText("out_trade_no"));
weChatBean.setResult_code(root.elementText("result_code"));
weChatBean.setReturn_code(root.elementText("return_code"));
weChatBean.setSign(root.elementText("sign"));
weChatBean.setTime_end(root.elementText("time_end"));
weChatBean.setTotal_fee(root.elementText("total_fee"));
weChatBean.setTrade_type(root.elementText("trade_type"));
weChatBean.setTransaction_id(root.elementText("transaction_id"));
/**
* 判断签名是否正确,防止别人恶意调用
*/
if(!weChatBean.calculateSign().equals(weChatBean.getSign())
|| !"SUCCESS".equals(weChatBean.getResult_code())
|| !"SUCCESS".equals(weChatBean.getReturn_code()) ){
try (
Writer writer = response.getWriter()
){
writer.write("fail");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 调用业务
*/
Object result = XXXXXXXXXX;//这里是业务调用,大家自行修改
String message = "SUCCESS";
if(result instanceof String
|| (result instanceof Boolean && !(Boolean) result)){
message = "FAIL";
}
/**
* 输出信息,如果信息是SUCCESS,微信就不会继续调用了,不然微信会连续调用8次,确保我们收到信息
*/
try (
Writer writer = response.getWriter()
){
writer.write(message);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**每个字段具体含义大家可以去看文档,这里我就不一一列举了*/
public class WeChatBean {
private String appid;
private String bank_type;
private String cash_fee;
private String fee_type;
private String is_subscribe;
private String mch_id;
private String nonce_str;
private String openid;
private String out_trade_no;
private String result_code;
private String return_code;
private String sign;
private String time_end;
private String total_fee;
private String trade_type;
private String transaction_id;
/*** get set 方法 ***/
/**
* 计算出sign的值
* @return
*/
public String calculateSign(){
String str = "appid=" + appid +
"&bank_type=" + bank_type +
"&cash_fee=" + cash_fee +
"&fee_type=" + fee_type+
"&is_subscribe=" + is_subscribe +
"&mch_id=" + mch_id +
"&nonce_str=" + nonce_str +
"&openid=" + openid +
"&out_trade_no=" + out_trade_no +
"&result_code=" + result_code +
"&return_code=" + return_code +
"&time_end=" + time_end +
"&total_fee=" + total_fee +
"&trade_type=" + trade_type +
"&transaction_id=" + transaction_id +
"&key=" + WeChatConst.KEY;
return MD5Util.MD5(str).toUpperCase();
}
}
到这里整个微信支付就完成了。
第一次发表博客,有什么不足还请大家多多指教。