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

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

欢迎加群交流: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<String, String> data = new HashMap<String, String>();
        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<String, String> 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
     * <p>Discription:[获取微信公众号唯一标识:appid]</p>
     * Created on 2018年1月24日
     * @return
     * @author:[soong]
     */
    public abstract String getAppID();

    /**
     * 获取 Mch ID
     * <p>Discription:[获取微信支付商户号:商户申请微信支付后,由微信支付分配的商户收款账号]</p>
     * Created on 2018年1月24日
     * @return
     * @author:[soong]
     */
    public abstract String getMchID();

    /**
     * 获取 API 密钥
     * <p>Discription:[交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播;
     * 商户可根据邮件提示登录微信商户平台进行设置;
     * 也可按一下路径设置:微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置]</p>
     * Created on 2018年1月24日
     * @return
     * @author:[soong]
     */
    public abstract String getKey();

    /**
     * 获取开发者密码
     * <p>Discription:[AppSecret是APPID对应的接口密码,用于获取接口调用凭证access_token时使用
     * 在微信支付中,先通过OAuth2.0接口获取用户openid,此openid用于微信内网页支付模式下单接口使用]</p>
     * 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等不同场景生成交易串调起支付
	 * <p>Discription:[商户server调用统一下单接口请求订单,生成预支付交易单]</p>
	 * Created on 2018年2月15日
	 * @param wxPayInfo
	 * @param req
	 * @param resp
	 * @return
	 * @author:[soong]
	 */
	@RequestMapping(value = "unifiedOrder", method = RequestMethod.POST)
	@ResponseBody
	public Json<Map<String, String>> unifiedOrder(WXPayInfo wxPayInfo, HttpServletRequest req, HttpServletResponse resp) {
		Json<Map<String, String>> json = new Json<Map<String, String>>();
		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;
	}
	
	/**
	 * 
	 * <p>Discription:[支付结果通知]</p>
	 * Created on 2018年2月16日
	 * @param openid
	 * @param request
	 * @param response
	 * @return
	 * @author:[soong]
	 */
	@RequestMapping(value = "notify", method = RequestMethod.POST)
	@ResponseBody
	public Json<Map<String, String>> notify(String openid, HttpServletRequest request, HttpServletResponse response) {
		Json<Map<String, String>> json = new Json<Map<String, String>>();
//		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;
	}
	
	/**
	 * 
	 * <p>Discription:[再次生成签名,js发起微信支付请求]</p>
	 * Created on 2018年2月16日
	 * @param openid
	 * @param request
	 * @param response
	 * @return
	 * @author:[soong]
	 */
	@RequestMapping(value = "generatePaySign", method = RequestMethod.POST)
	@ResponseBody
	public Json<Map<String, String>> generatePaySign(HttpServletRequest request, HttpServletResponse response) {
		Json<Map<String, String>> json = new Json<Map<String, String>>();
		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<String, String> map = new HashMap<String, String>();

		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;
	}
	
	/**
	 * 
	 * <p>Discription:[查询订单]</p>
	 * Created on 2018年2月17日
	 * @param request
	 * @param response
	 * @return
	 * @author:[soong]
	 */
	@RequestMapping(value = "orderQuery", method = RequestMethod.POST)
	@ResponseBody
	public Json<Map<String, String>> orderQuery(HttpServletRequest request, HttpServletResponse response) {
		Json<Map<String, String>> json = new Json<Map<String, String>>();
		try {
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return json;
	}
}
业务实现类WxPayServcieImpl:
@Service("wxPayService")
public class WxPayServiceImpl implements WxPayService{

	@Override
	public Json<Map<String, String>> unifiedOrder(WXPayInfo wxPayInfo, Json<Map<String, String>> json) {
		User u = UserUtils.getUser();
		Map<String, String> data = new HashMap<String, String>();
		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<String, String> 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

猜你喜欢

转载自blog.csdn.net/soongp/article/details/79410873