微信服务号开发-整合微信支付

最近的项目在对接微信支付,所以抽出一些时间,将方法总结一下:

欢迎加群交流: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> unifiedOrder(WXPayInfo wxPayInfo, HttpServletRequest req, HttpServletResponse resp) { Json> json = new Json>(); if (StringUtils.isBlank(wxPayInfo.getOpenid())) { json.setFalid(Json.PARAM_EXCEPTION, "用户标识openid不能为空"); logger.info("用户标识openid不能为空"); return json; } if (StringUtils.isBlank(wxPayInfo.getTotalFee())) { json.setFalid(Json.PARAM_EXCEPTION, "充值金额totalFee不能为空"); logger.info("充值金额totalFee不能为空"); return json; } try { json = wxPayService.unifiedOrder(wxPayInfo, json); } catch (BusinessException e) { } // {result_code=SUCCESS, sign=B7C41F5D85E0F4579BC839FE9C864518B1C5F4DEF900F57AA5E4B1367159074F, mch_id=1496788202, prepay_id=wx20180214211654305951b3800266781582, return_msg=OK, appid=wxf79c25d666f76ee0, nonce_str=BDhKVLBBzukDxPAl, return_code=SUCCESS, trade_type=JSAPI} return json; } /** * *

Discription:[支付结果通知]

* Created on 2018年2月16日 * @param openid * @param request * @param response * @return * @author:[soong] */ @RequestMapping(value = "notify", method = RequestMethod.POST) @ResponseBody public Json> notify(String openid, HttpServletRequest request, HttpServletResponse response) { Json> json = new Json>(); // if (StringUtils.isBlank(openid)) {// // json.setFalid(Json.PARAM_EXCEPTION, "openid不能为空"); // return json; // } logger.info("支付结果通知"); // try { // json = wxPayService.unifiedOrder(json); // } catch (BusinessException e) { // // } // {result_code=SUCCESS, sign=B7C41F5D85E0F4579BC839FE9C864518B1C5F4DEF900F57AA5E4B1367159074F, mch_id=1496788202, prepay_id=wx20180214211654305951b3800266781582, return_msg=OK, appid=wxf79c25d666f76ee0, nonce_str=BDhKVLBBzukDxPAl, return_code=SUCCESS, trade_type=JSAPI} return json; } /** * *

Discription:[再次生成签名,js发起微信支付请求]

* Created on 2018年2月16日 * @param openid * @param request * @param response * @return * @author:[soong] */ @RequestMapping(value = "generatePaySign", method = RequestMethod.POST) @ResponseBody public Json> generatePaySign(HttpServletRequest request, HttpServletResponse response) { Json> json = new Json>(); String prepay_id = request.getParameter("prepay_id");//预支付订单id if (StringUtils.isBlank(prepay_id)) { json.setFalid(Json.PARAM_EXCEPTION, "预支付订单ID(prepay_id)不能为空"); } String appId = GlobalConstants.getAppid();//公众号id String timeStamp = GlobalConstants.getTimestamp();//时间戳 String nonceStr = GlobalConstants.getUUID();//随机字符串 String signType = SignType.HMACSHA256 + "";//加签类型 Map map = new HashMap(); map.put("appId", appId); map.put("timeStamp", timeStamp); map.put("nonceStr", nonceStr); map.put("package", prepay_id); map.put("signType", signType); try { String paySign = WXPayUtil.generateSignature(map, GlobalConstants.getApiKey(), SignType.HMACSHA256); map.put("paySign", paySign); json.setSuccess(Json.SUCCESS, "再次生成支付签名成功", map); } catch (Exception e) { e.printStackTrace(); } return json; } /** * *

Discription:[查询订单]

* Created on 2018年2月17日 * @param request * @param response * @return * @author:[soong] */ @RequestMapping(value = "orderQuery", method = RequestMethod.POST) @ResponseBody public Json> orderQuery(HttpServletRequest request, HttpServletResponse response) { Json> json = new Json>(); try { } catch (Exception e) { e.printStackTrace(); } return 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

你可能感兴趣的:(微信服务号)