最近的项目在对接微信支付,所以抽出一些时间,将方法总结一下:
欢迎加群交流:724225958
官方开发文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=1_1;
文档中分别对支付账户(参数)、接口规则、支付业务场景,流程、API做了详细介绍,并提供了SDK以及DEMO ,大部分有一定基础的开发或研发,均可参照文档及其demo,一步一步的梳理整合。只需注意最重要的2点即可:
① js调起微信支付时,需二次加签,统一下单返回的签名无法调起微信支付
② 二次加签的加签类型SignType是‘HMAC-SHA256’,此处不可为‘MD5’
微信支付业务场景最终都可以抽象为技术上的2个点:前端发起支付请求,后端响应请求。
首先,从前端入手,整合微信JS-SDK,
官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115。
按照文档指示的步骤开发即可,此处不做详细介绍。在支付调用页面注入jssdk即可:
/**
* 配置jssdk
*/
function jssdk(url){
if(null == url || url == ''){
url = location.href.split('#')[0];
}
$.ajax({
type:'post',
url:buildUrl('villa/jssdk'),
data:{
url:url
},
cache:false,
dataType:'json',
success:function(data){
if (data.code == '200') {
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: data.obj.appId, // 必填,公众号的唯一标识
timestamp: data.obj.timestamp, // 必填,生成签名的时间戳
nonceStr: data.obj.nonceStr, // 必填,生成签名的随机串
signature: data.obj.signature,// 必填,签名,见附录1
jsApiList: ['checkJsApi', 'chooseWXPay'
] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
wx.error(function(res){
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});
}
}
});
}
此函数方法可以调整,将使用的接口作为参数,传入函数,灵活使用。
发起支付的函数为:
function recharge(){
//业务代码省略
//统一下单
$.ajax({
type:'post',
url:buildUrl('villa/a/wxpay/unifiedOrder'),
data:{
openid:’’,//用户openid必需,或后台单独获取
totalFee:totalFee
},
cache:false,
dataType:'json',
success:function(data){
if (data.code == '200') {
var obj = data.obj;
var prepay_id = 'prepay_id=' + obj.prepay_id;
//再次生成支付签名
$.ajax({
type:'post',
url:buildUrl('villa/a/wxpay/generatePaySign'),
data:{
prepay_id:prepay_id //预支付订单id,二次加签必需
},
cache:false,
dataType:'json',
success:function(data){
//业务代码
if (data.code == '200') {
var pay_obj = data.obj;
//调起微信支付
chooseWXPay(pay_obj.timeStamp, pay_obj.nonceStr, prepay_id, pay_obj.signType, pay_obj.paySign);
} else {
$("#errorMessage").html(data.msg);
loadDialog('dialog');
}
}
});
} else {
$("#errorMessage").html(data.msg);
loadDialog('dialog');
}
}
});
}
如果签名正确,基本就可以看到微信支付调用效果。
下面为后端整合代码:
微信支付DEMO中给出了一个统一下单的调用示例:
public class WXPayExample {
public static void main(String[] args) throws Exception {
MyWXPayConfig config = new MyWXPayConfig();
WXPay wxpay = new WXPay(config);
Map data = new HashMap();
data.put("body", "腾讯充值中心-QQ会员充值");
data.put("out_trade_no", "2016090910595900000012");
data.put("device_info", "");
data.put("fee_type", "CNY");
data.put("total_fee", "1");
data.put("spbill_create_ip", "123.12.12.123");
data.put("notify_url", "http://www.masaike.com/wxpay/notify");
// data.put("trade_type", "NATIVE"); // 此处指定为扫码支付
data.put("trade_type", "JSAPI");
data.put("openid", "masaike");
data.put("product_id", "12");
try {
Map resp = wxpay.unifiedOrder(data);
System.out.println(resp);
} catch (Exception e) {
e.printStackTrace();
}
//成功返还结果
// {result_code=SUCCESS, sign=A7D34A90163C12BAFB887FC752B9EF22D8C75C05BE581917E0E2806C2E73F2F1, mch_id=1496788202, prepay_id=wx201802142113131695db46a40709589533, return_msg=OK, appid=’masaike’, nonce_str=ULtDr28SorCa5haa, return_code=SUCCESS, trade_type=JSAPI}
}
}
调用失败的原因可能只有一个,那就是没有找到你的支付安全证书,证书的获取方法在上一篇博文《微信服务号之公众号支付配置》:http://blog.csdn.net/soongp/article/details/79405161查看。
WxPayInfo类为自己封装的一个微信参数类,用于传参:
public class WXPayInfo {
/**用户唯一标识*/
private String openid;
/**充值金额*/
private String totalFee;
/**商品描述*/
private String body;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getTotalFee() {
return totalFee;
}
public void setTotalFee(String totalFee) {
this.totalFee = totalFee;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
WxPayConfig为微信支付配置抽象类,WxPayConfig为其实现,说白了就是一些必要参数的获取,例如:appid(服务号账号id),appsecret(服务号秘钥),mchid(支付账户账号),key(支付秘钥,在微信商户平台配置),算了,还是把代码粘出来吧:
public abstract class WXPayConfig {
/**
* 获取appid
* Discription:[获取微信公众号唯一标识:appid]
* Created on 2018年1月24日
* @return
* @author:[soong]
*/
public abstract String getAppID();
/**
* 获取 Mch ID
* Discription:[获取微信支付商户号:商户申请微信支付后,由微信支付分配的商户收款账号]
* Created on 2018年1月24日
* @return
* @author:[soong]
*/
public abstract String getMchID();
/**
* 获取 API 密钥
* Discription:[交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播;
* 商户可根据邮件提示登录微信商户平台进行设置;
* 也可按一下路径设置:微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置]
* Created on 2018年1月24日
* @return
* @author:[soong]
*/
public abstract String getKey();
/**
* 获取开发者密码
* Discription:[AppSecret是APPID对应的接口密码,用于获取接口调用凭证access_token时使用
* 在微信支付中,先通过OAuth2.0接口获取用户openid,此openid用于微信内网页支付模式下单接口使用]
* Created on 2018年1月24日
* @return
* @author:[soong]
*/
public abstract String getAppsecret();
/**
* 获取商户证书内容
*
* @return 商户证书内容
*/
public abstract InputStream getCertStream();
/**
* HTTP(S) 连接超时时间,单位毫秒
*
* @return
*/
public int getHttpConnectTimeoutMs() {
return 6*1000;
}
/**
* HTTP(S) 读数据超时时间,单位毫秒
*
* @return
*/
public int getHttpReadTimeoutMs() {
return 8*1000;
}
/**
* 获取WXPayDomain, 用于多域名容灾自动切换
* @return
*/
public abstract IWXPayDomain getWXPayDomain();
/**
* 是否自动上报。
* 若要关闭自动上报,子类中实现该函数返回 false 即可。
*
* @return
*/
public boolean shouldAutoReport() {
return true;
}
/**
* 进行健康上报的线程的数量
*
* @return
*/
public int getReportWorkerNum() {
return 6;
}
/**
* 健康上报缓存消息的最大数量。会有线程去独立上报
* 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
*
* @return
*/
public int getReportQueueMaxSize() {
return 10000;
}
/**
* 批量上报,一次最多上报多个数据
*
* @return
*/
public int getReportBatchSize() {
return 10;
}
}
public class MyWXPayConfig extends WXPayConfig{
/**证书二级制字节流*/
private byte[] certData;
private static MyWXPayConfig INSTANCE;
public MyWXPayConfig() throws Exception{
String certPath = GlobalConstants.getCertPath();
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
public static MyWXPayConfig getInstance() throws Exception{
if (INSTANCE == null) {
synchronized (MyWXPayConfig.class) {
if (INSTANCE == null) {
INSTANCE = new MyWXPayConfig();
}
}
}
return INSTANCE;
}
@Override
public String getAppID() {
return GlobalConstants.getAppid();
}
@Override
public String getMchID() {
return GlobalConstants.getMchID();
}
@Override
public String getKey() {
return GlobalConstants.getApiKey();
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public IWXPayDomain getWXPayDomain() {
return WXPayDomainSimpleImpl.instance();
}
@Override
public String getAppsecret() {
return GlobalConstants.getAppSecret ();
}
}
GlobalConstants.getAppid();等方法的内部实现,其实就是获取了配置文件中的参数,只不过多封装了一层做了垂直解耦。
控制器WxPayController:
@Controller
@RequestMapping(value = "${adminPath}/wxpay")
public class WXPayController extends BaseController{
@Autowired
private WxPayService wxPayService;
/**
* 除被扫支付场景以外,商户系统先调用该接口在微信支付服务后台生成预支付交易单,
* 返回正确的预支付交易回话标识后再按扫码、JSAPI、APP等不同场景生成交易串调起支付
* Discription:[商户server调用统一下单接口请求订单,生成预支付交易单]
* Created on 2018年2月15日
* @param wxPayInfo
* @param req
* @param resp
* @return
* @author:[soong]
*/
@RequestMapping(value = "unifiedOrder", method = RequestMethod.POST)
@ResponseBody
public Json
业务实现类WxPayServcieImpl:
@Service("wxPayService")
public class WxPayServiceImpl implements WxPayService{
@Override
public Json> unifiedOrder(WXPayInfo wxPayInfo, Json> json) {
User u = UserUtils.getUser();
Map data = new HashMap();
String body = wxPayInfo.getBody();
if (StringUtils.isBlank(body)) {
body = GlobalConstants.getWxpayBody();
}
data.put("body", body);//商品描述
String out_trade_no = String.valueOf(WXPayUtil.getCurrentTimestamp());
data.put("out_trade_no", out_trade_no);//商户订单号
data.put("fee_type", WXPayConstants.FEE_TYPE);//标价币种
data.put("total_fee", "1");
// String totalFee = wxPayInfo.getTotalFee();
// totalFee = String.valueOf(Integer.valueOf(totalFee) * 100);
// data.put("total_fee", totalFee);//标价金额,单价为分(乘以100转化为元)
data.put("spbill_create_ip", u.getLoginIp());//终端IP
data.put("notify_url", GlobalConstants.getNotifyUrl());//通知地址
data.put("trade_type", WXPayConstants.TRADE_TYPE_JSAPI);//支付类型
data.put("openid", wxPayInfo.getOpenid());//公众号支付必需:用户标识
try {
MyWXPayConfig config = new MyWXPayConfig();
WXPay wxpay = new WXPay(config);
Map resp = wxpay.unifiedOrder(data);
//通信成功判断
if (null != resp.get("return_code") && resp.get("return_code").toString().equals("SUCCESS")) {
//交易成功判断
if (null != resp.get("result_code") && resp.get("result_code").toString().equals("SUCCESS")) {
json.setSuccess(Json.SUCCESS, "统一下单成功", resp);
} else {
String err_code_des = "系统异常,请用相同参数重新调用";
if (null != resp.get("err_code_des")) {
err_code_des = resp.get("err_code_des").toString();
}
json.setFalid(Json.BUSINESS_EXCEPTION, "统一下单失败-交易失败:" + err_code_des, resp);
}
} else {
String return_msg = "签名失败";
if (null != resp.get("return_msg")) {
return_msg = resp.get("return_msg").toString();
}
json.setFalid(Json.BUSINESS_EXCEPTION, "统一下单失败-通信失败:" + return_msg, resp);
}
} catch (Exception e) {
e.printStackTrace();
json.setFalid(Json.BUSINESS_EXCEPTION, "统一下单失败:" + e.getMessage());
throw new BusinessException(Json.BUSINESS_EXCEPTION, "统一下单失败:" + e.getMessage());
}
return json;
}
}
其实,只要肯花时间,仔细认真的阅读文档即可,整合jssdk,能看懂支付DEMO就ok,剩下的就是一些细节调试,以及在调试过程中遇到问题,并解决的一个过程;
交流QQ群:724225958