精华内容
参与话题
问答
  • 会员免费看,...微信h5支付开发是子恒老师《子恒说微信开发》视频教程的第17部。详细讲解了微信H5支付开发,内容包含开通H5支付,实现微信h5支付,订单查询,关闭订单等等。欢迎反馈,微信号:QQ68183131
  • 微信H5支付

    万次阅读 热门讨论 2017-08-23 16:35:37
    微信H5支付

    本篇文件来聊聊微信服务商模式以及商户模式下微信H5支付

    先说一个事情。8月1号开始微信公众平台支付的开发配置页面迁移至商户平台 详细说明参考这个或者看下面的截图

    平台公告   微信支付商户平台

    官方文档
    普通商户版-微信H5支付
    服务商版-微信H5支付

    1、申请开通微信H5支付

    服务商模式下H5支付

    服务商模式下点击申请开通会立即开通,邀请子商户时需要填写子商户的信息。

    服务商模式下与商户模式下主要的区别在于商户模式下不用填写特约商户信息、只需要填写商户H5支付相关的信息 如下图

    服务商H5支付

    服务商H5支付产品设置 - 微信支付商户平台

    注意
    填写的H5支付域名必须是对应网站备案的域名且备案主体还得与商户的主体信息一致。否则申请的时候不给予通过。审核反馈的周期不是很长一般是隔天就可以给出反馈。

    2、接口流程图以及常见错误

    接口流程图

    简单点概括
    1、用户使用非微信客户端浏览器下单
    2、调用微信支付接口下单得到H5支付URL的跳转链接
    3、跳转到微信唤起微信支付
    4、支付成功异步通过
    5、处理支付结果

    详细流程介绍

    1、用户在商户侧完成下单,使用微信支付进行支付
    2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB
    3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页
    4、中间页进行H5权限的校验,安全性检查(此处常见错误请见下文)
    5、如支付成功,商户后台会接收到微信侧的异步通知
    6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)
    7、商户在展示页面,引导用户主动发起支付结果的查询
    8,9、商户后台判断是否接到收微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态
    10、展示最终的订单支付结果给用户

    常见错误:

    1、网络环境未能通过安全验证,请稍后再试(IP改变导致的)
    2、商家参数格式有误,请联系商家解决(H5支付的referer为空导致)
    3、商家存在未配置的参数,请联系商家解决(H5支付的域名问题)
    4、支付请求已失效,请重新发起支付(有效期为5分钟)
    5、请在微信外打开订单,进行支付(H5支付不能直接在微信客户端内调起)

    3、统一下单获取支付的URL

    你做过扫码支付、公众号支付以及App支付吗?如果做过那H5支付就相当简单了,与前面的提到的几种支付方式统一下单的URL是一样的只是个别请求参数不同而已(trade_type为MWEB、scene_info必填)。如果没有做过也没有关系下面就是详细的案例了。

    /**
    	 * 微信H5 支付
    	 */
    	public void wapPay(){
    		String ip = IpKit.getRealIp(getRequest());
    		if (StrKit.isBlank(ip)) {
    			ip = "127.0.0.1";
    		}
    		
    		H5ScencInfo sceneInfo = new H5ScencInfo();
    		
    		H5 h5_info = new H5();
    		h5_info.setType("Wap");
    		h5_info.setWap_url("https://pay.qq.com");
    		h5_info.setWap_name("腾讯充值");
    		sceneInfo.setH5_info(h5_info);
    		
    		Map<String, String> params = WxPayApiConfigKit.getWxPayApiConfig()
    				.setAttach("IJPay H5支付测试  -By Javen")
    				.setBody("IJPay H5支付测试  -By Javen")
    				.setSpbillCreateIp(ip)
    				.setTotalFee("520")
    				.setTradeType(TradeType.MWEB)
    				.setNotifyUrl(notify_url)
    				.setOutTradeNo(String.valueOf(System.currentTimeMillis()))
    				.setSceneInfo(h5_info.toString())
    				.build();
    		
    		String xmlResult = WxPayApi.pushOrder(false,params);
    log.info(xmlResult);
    		Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
    		
    		String return_code = result.get("return_code");
    		String return_msg = result.get("return_msg");
    		if (!PaymentKit.codeIsOK(return_code)) {
    			ajax.addError(return_msg);
    			renderJson(ajax);
    			return;
    		}
    		String result_code = result.get("result_code");
    		if (!PaymentKit.codeIsOK(result_code)) {
    			ajax.addError(return_msg);
    			renderJson(ajax);
    			return;
    		}
    		// 以下字段在return_code 和result_code都为SUCCESS的时候有返回
    		
    		String prepay_id = result.get("prepay_id");
    		String mweb_url = result.get("mweb_url");
    		
    		renderText("prepay_id:"+prepay_id+" mweb_url:"+mweb_url);
    	}
    

    在手机浏览器中访问mweb_url即可唤起微信进行支付

    更正 20170830
    1、上面复制mweb_url到手机浏览器访问会出现网络环境未能通过安全验证,请稍后再试 为什么呢? 因为发起支付与唤起支付的IP不一致
    2、微信H5支付必须在设置的域名(商户平台–“产品中心”–“开发配置”)网页中发起支付不然会出现商家参数格式有误,请联系商家解决 为什么呢? 因为微信H5支付需要验证支付的referer

    更正后的代码如下:

    /**
    	 * 微信H5 支付
    	 * 注意:必须再web页面中发起支付且域名已添加到开发配置中
    	 */
    	public void wapPay(){
    		String ip = IpKit.getRealIp(getRequest());
    		if (StrKit.isBlank(ip)) {
    			ip = "127.0.0.1";
    		}
    		
    		H5ScencInfo sceneInfo = new H5ScencInfo();
    		
    		H5 h5_info = new H5();
    		h5_info.setType("Wap");
    		//此域名必须在商户平台--"产品中心"--"开发配置"中添加
    		h5_info.setWap_url("https://pay.qq.com");
    		h5_info.setWap_name("腾讯充值");
    		sceneInfo.setH5_info(h5_info);
    		
    		Map<String, String> params = WxPayApiConfigKit.getWxPayApiConfig()
    				.setAttach("IJPay H5支付测试  -By Javen")
    				.setBody("IJPay H5支付测试  -By Javen")
    				.setSpbillCreateIp(ip)
    				.setTotalFee("520")
    				.setTradeType(TradeType.MWEB)
    				.setNotifyUrl(notify_url)
    				.setOutTradeNo(String.valueOf(System.currentTimeMillis()))
    				.setSceneInfo(h5_info.toString())
    				.build();
    		
    		String xmlResult = WxPayApi.pushOrder(false,params);
    log.info(xmlResult);
    		Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
    		
    		String return_code = result.get("return_code");
    		String return_msg = result.get("return_msg");
    		if (!PaymentKit.codeIsOK(return_code)) {
    			ajax.addError(return_msg);
    			renderJson(ajax);
    			return;
    		}
    		String result_code = result.get("result_code");
    		if (!PaymentKit.codeIsOK(result_code)) {
    			ajax.addError(return_msg);
    			renderJson(ajax);
    			return;
    		}
    		// 以下字段在return_code 和result_code都为SUCCESS的时候有返回
    		
    		String prepay_id = result.get("prepay_id");
    		String mweb_url = result.get("mweb_url");
    		
    		System.out.println("prepay_id:"+prepay_id+" mweb_url:"+mweb_url);
    		redirect(mweb_url);
    	}
    
    

    在Web页面中发起支付

    如果你下载的是Jfinal版本的Demo 访问的URL为
    http://域名:端口号/toWxH5Pay

    具体实现可以参考开源项目 IJPay 让支付触手可及

    推荐阅读
    极速开发微信公众号
    基于 CentOS 搭建 FTP 文件服务
    基于 CentOS Mysql 5.7.19 安装与主从同步配置详解
    Redis4.0.1安装以及主从复制详解
    10分钟搭建属于自己的ngork服务器,实现内网穿透

    展开全文
  • 一、首先先确定H5支付权限已经申请! 二、开发流程 1、用户在商户侧完成下单,使用微信支付进行支付 2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB 3、统一下单接口返回...

    一、首先先确定H5支付权限已经申请!

    二、开发流程

    1、用户在商户侧完成下单,使用微信支付进行支付

    2、由商户后台向微信支付发起下单请求(调用统一下单接口)注:交易类型trade_type=MWEB

    3、统一下单接口返回支付相关参数给商户后台,如支付跳转url(参数名“mweb_url”),商户通过mweb_url调起微信支付中间页

    4、中间页进行H5权限的校验,安全性检查(此处常见错误请见下文)

    5、如支付成功,商户后台会接收到微信侧的异步通知

    6、用户在微信支付收银台完成支付或取消支付,返回商户页面(默认为返回支付发起页面)

    7、商户在展示页面,引导用户主动发起支付结果的查询

    8,9、商户后台判断是否接到收微信侧的支付结果通知,如没有,后台调用我们的订单查询接口确认订单状态

    10、展示最终的订单支付结果给用户

    三、开发过程

    1、配置相关参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class WechatPayConf
    {
         
        const APPID = '';//APPID
        const MCH_ID = '';//商户号
        const KEY = '';//商户key
        const NOTIFY_URL = '';//回调地址
     
    }

     

    2、统一下单

    接口链接

    URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder

    请求参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <xml>
    <appid>wx2421b1c4370ec43b</appid>
    <attach>支付测试</attach>
    <body>H5支付测试</body>
    <mch_id>10000100</mch_id>
    <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
    <notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
    <openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid>
    <out_trade_no>1415659990</out_trade_no>
    <spbill_create_ip>14.23.150.211</spbill_create_ip>
    <total_fee>1</total_fee>
    <trade_type>MWEB</trade_type>
    <scene_info>{"h5_info": {"type":"IOS","app_name""王者荣耀","package_name""com.tencent.tmgp.sgame"}}</scene_info>
    <sign>0CB01533B8C1EF103065174F50BCA001</sign>
    </xml>

      

    PHP代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    /**
     * 统一支付接口类
     */
    class UnifiedOrder_pub extends Wxpay_client_pub
    {  
        function __construct()
        {
            //设置接口链接
            $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            //设置curl超时时间
            $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT;
        }
         
        /**
         * 生成接口参数xml
         */
        function createXml()
        {
            try
            {
                //检测必填参数
                if($this->parameters["out_trade_no"] == null)
                {
                    throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."<br>");
                }elseif($this->parameters["body"] == null){
                    throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."<br>");
                }elseif ($this->parameters["total_fee"] == null ) {
                    throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."<br>");
                }elseif ($this->parameters["notify_url"] == null) {
                    throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."<br>");
                }elseif ($this->parameters["trade_type"] == null) {
                    throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."<br>");
                }elseif ($this->parameters["trade_type"] == "JSAPI" &&
                    $this->parameters["openid"] == NULL){
                    throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."<br>");
                }
                $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID
                $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号
                $this->parameters["spbill_create_ip"] = $_SERVER['REMOTE_ADDR'];//终端ip      
                $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串
                $this->parameters["sign"] = $this->getSign($this->parameters);//签名
                return  $this->arrayToXml($this->parameters);
            }catch (SDKRuntimeException $e)
            {
                die($e->errorMessage());
            }
        }
         
        /**
         * 获取prepay_id
         */
        function getPrepayId()
        {
            $this->postXml();
            $this->result = $this->xmlToArray($this->response);
            $prepay_id $this->result["prepay_id"];
            return $prepay_id;
             
        }
         
    }

     

    如果调用正常,就会得到一个支付url

     

    其它常见错误

     

    序号问题错误描述解决方法
    1 268498465 网络环境未能通过安全验证,请稍后再试 1. 商户侧统一下单传的终端IP(spbill_create_ip)与用户实际调起支付时微信侧检测到的终端IP不一致导致的,这个问题一般是商户在统一下单时没有传递正确的终端IP到spbill_create_ip导致,详细可参见客户端ip获取指引

    2. 统一下单与调起支付时的网络有变动,如统一下单时是WIFI网络,下单成功后切换成4G网络再调起支付,这样可能会引发我们的正常拦截,请保持网络环境一致的情况下重新发起支付流程

    2 268443815 商家参数格式有误,请联系商家解决

    1. 当前调起H5支付的referer为空导致,一般是因为直接访问页面调起H5支付,请按正常流程进行页面跳转后发起支付,或自行抓包确认referer值是否为空


    2. 如果是APP里调起H5支付,需要在webview中手动设置referer,如(
    Map<string,string> extraHeaders = new HashMap<string,string>();
    extraHeaders.put("Referer", "商户申请H5时提交的授权域名");//例如 http://www.baidu.com ))

    3 268443816 商家存在未配置的参数,请联系商家解决 1,当前调起H5支付的域名(微信侧从referer中获取)与申请H5支付时提交的授权域名不一致,如需添加或修改授权域名,请登陆商户号对应的商户平台--"产品中心"--"开发配置"自行配置 

    2,如果设置了回跳地址redirect_url,请确认设置的回跳地址的域名与申请H5支付时提交的授权域名是否一致
    4 268498468 支付请求已失效,请重新发起支付 统一下单返回的MWEB_URL生成后,有效期为5分钟,如超时请重新生成MWEB_URL后再发起支付
    6 请在微信外打开订单,进行支付 H5支付不能直接在微信客户端内调起,请在外部浏览器调起
     
     
    展开全文
  • 微信h5支付,微信外浏览器支付实现

    万次阅读 热门讨论 2017-10-30 14:15:19
    h5支付的资料还真叫个少,不过找到一个好的方式,按着大神的步骤去实现还真就ok了,话不多说,开始准备吧 看一下官方文档还是很必要的,知道必不可少的参数是什么:...

    h5支付的资料还真叫个少,不过找到一个好的方式,按着大神的步骤去实现还真就ok了,话不多说,开始准备吧

    看一下官方文档还是很必要的,知道必不可少的参数是什么:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1

    微信支付的坑很多,特别在平台的设置上

    首先需要APPID,微信支付商户号mch_id,API密钥key,Appsecret(secret),说明在这里https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=3_1

    然后设置授权域名,在接口设置中就能找到,包括js接口安全域名和网页授权域名:

     

    这个点进去之后会看到最下面两个:js接口安全域名,这个可以设置三个,就是填写你访问页面的域名即可

     

    下面这个是网页授权回调域名,用于你支付完毕后回调的域名,将下载的文件放到服务器的根路径,确保可以访问,我是放在tomcat的webapp中

    设置的域名要备案

     

    然后设置支付域名,设置路径:商户平台-->产品中心-->开发配置中设置域名,

    如果是公众号支付就设置对应的,要注意的是公众号支付授权域名为请求的前一级,比如你要请求http://xxx/wx/abc,那么你就设置http://xxx/wx即可

    h5支付设置h5域名就行,不用后缀,直接写你要设置的域名

    partnerkey需要在API中设置,需要安装证书,这个根据提示安装即可,自行设置32位partnerkey

     

    我用的是一个大神的IJPay的springboot版,写成自己的SpringMVC版,后续都会给链接

    主要用的的是封装的jar,现在maven库中已经有了,用0.8版本以上的吧,这样jdk兼容问题已经解决

    选一个适合自己的

     

    着手代码

     

    public static WxPayApiConfig getApiConfig() {
    		return WxPayApiConfig.New()
    				       .setAppId(appID)
    				       .setMchId(mchID)
    				       .setPaternerKey(partnerKey)
    				       .setPayModel(WxPayApiConfig.PayModel.BUSINESSMODEL);
    	}

     

    		/**
    	 * 微信H5 支付--------------------好使
    	 * 注意:必须再web页面中发起支付且域名已添加到开发配置中
    	 */
    	@RequestMapping(value ="/pay.do",method = {RequestMethod.POST,RequestMethod.GET})
    	public void wapPay(HttpServletRequest request,HttpServletResponse response){
    		System.out.println("--pay start--");
                    String notify_url = "https://你的域名/pay_notify.do";//这是回调地址,方法在下面
    		//获取ip
    
            String ip = IpKit.getRealIp(request);
    		if (com.jpay.ext.kit.StrKit.isBlank(ip)) {
    			ip = "127.0.0.1";
    		}
    		
    		H5ScencInfo sceneInfo = new H5ScencInfo();
    		
    		H5 h5_info = new H5();
    		h5_info.setType("Wap");
    		//此域名必须在商户平台--"产品中心"--"开发配置"中添加
    		
    		h5_info.setWap_url("http://www.xxx.com");
    		h5_info.setWap_name("公司官网");
    		sceneInfo.setH5_info(h5_info);
    		WxPayApiConfig wxPayApiConfig=getApiConfig();
    		Map<String, String> params=WxPayApiConfig.New()
    				                           .setAppId(appID)
    				                           .setMchId(mchID)
    				                           .setBody("H5支付测试")
    				                           .setSpbillCreateIp(ip)
    				                           .setTotalFee("520")
    				                           .setTradeType(WxPayApi.TradeType.MWEB)
    				                           .setNotifyUrl(notify_url)
    				                           .setPaternerKey(partnerKey)
    				                           .setOutTradeNo(String.valueOf(System.currentTimeMillis()))
    				                           .setSceneInfo("{\"h5_info\": {\"type\":\"IOS\",\"app_name\": \"mtgg\",\"package_name\": \"com.tencent.tmgp.sgame\"}}")
    				                           .setAttach("H5支付测试")
    				                           .build();
    		String xmlResult = WxPayApi.pushOrder(false,params);
    		Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
    		//返回结果
    		String return_code = result.get("return_code");
    		String return_msg = result.get("return_msg");
    		if (!PaymentKit.codeIsOK(return_code)) {
    			log.error("return_code>"+return_code+" return_msg>"+return_msg);
    			throw new RuntimeException(return_msg);
    		}
    		String result_code = result.get("result_code");
    		if (!PaymentKit.codeIsOK(result_code)) {
    			log.error("result_code>"+result_code+" return_msg>"+return_msg);
    			throw new RuntimeException(return_msg);
    		}
    		// 以下字段在return_code 和result_code都为SUCCESS的时候有返回
    		
    		String prepay_id = result.get("prepay_id");
    		String mweb_url = result.get("mweb_url");
    		
    		log.info("prepay_id:"+prepay_id+" mweb_url:"+mweb_url);
    		try {
    			response.sendRedirect(mweb_url);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}

    先给出H5ScencInfo

    package com.mtgg.entity;
    
    import com.jfinal.kit.JsonKit;
    
    
    /**
     * @author Javen
     */
    public class H5ScencInfo {
    	private H5 h5_info;
    	
    	public H5 getH5_info() {
    		return h5_info;
    	}
    	
    	public void setH5_info(H5 h5_info) {
    		this.h5_info = h5_info;
    	}
    	
    	
    	@Override
    	public String toString() {
    		return JsonKit.toJson(h5_info);
    	}
    	
    	
    	public static class H5{
    		private String type;
    		private String app_name;
    		private String bundle_id;
    		private String package_name;
    		private String wap_url;
    		private String wap_name;
    		public String getType() {
    			return type;
    		}
    		public void setType(String type) {
    			this.type = type;
    		}
    		public String getApp_name() {
    			return app_name;
    		}
    		public void setApp_name(String app_name) {
    			this.app_name = app_name;
    		}
    		public String getBundle_id() {
    			return bundle_id;
    		}
    		public void setBundle_id(String bundle_id) {
    			this.bundle_id = bundle_id;
    		}
    		public String getPackage_name() {
    			return package_name;
    		}
    		public void setPackage_name(String package_name) {
    			this.package_name = package_name;
    		}
    		public String getWap_url() {
    			return wap_url;
    		}
    		public void setWap_url(String wap_url) {
    			this.wap_url = wap_url;
    		}
    		public String getWap_name() {
    			return wap_name;
    		}
    		public void setWap_name(String wap_name) {
    			this.wap_name = wap_name;
    		}
    	}
    }
    
    
    

     

    注意notify_url要保证能够访问,用域名访问

    最后发送mweb_url就可以打开微信进行支付了

    给出支付成功的返回

    @RequestMapping(value = "/pay_notify.do",method={RequestMethod.POST,RequestMethod.GET})
    	@ResponseBody
    	public void pay_notify(HttpServletRequest request,HttpServletResponse response) {
    		// 支付结果通用通知文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
    		System.out.println("---------------支付回调----------------");
    		String xmlMsg = HttpKit.readData(request);
    		System.out.println("pay notice---------"+xmlMsg);
    		Map<String, String> params = PaymentKit.xmlToMap(xmlMsg);
    //		String appid  = params.get("appid");
    //		//商户号
    //		String mch_id  = params.get("mch_id");
    		String result_code  = params.get("result_code");
    //		String openId      = params.get("openid");
    //		//交易类型
    //		String trade_type      = params.get("trade_type");
    //		//付款银行
    //		String bank_type      = params.get("bank_type");
    //		// 总金额
    		String total_fee     = params.get("total_fee");
    		total_fee = total_fee.substring(0,total_fee.length()-2);
    //		//现金支付金额
    //		String cash_fee     = params.get("cash_fee");
    //		// 微信支付订单号
    //		String transaction_id      = params.get("transaction_id");
    //		// 商户订单号
    //		String out_trade_no      = params.get("out_trade_no");
    //		// 支付完成时间,格式为yyyyMMddHHmmss
    //		String time_end      = params.get("time_end");
    		
    		/以下是附加参数///
    		
    		String account      = params.get("attach");
    		System.out.println("回调total_fee-->"+total_fee);
    		System.out.println("回调account-->"+account);
    //		String fee_type      = params.get("fee_type");
    //		String is_subscribe      = params.get("is_subscribe");
    //		String err_code      = params.get("err_code");
    //		String err_code_des      = params.get("err_code_des");
    		// 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
    		// 避免已经成功、关闭、退款的订单被再次更新
    //		Order order = Order.dao.getOrderByTransactionId(transaction_id);
    //		if (order==null) {
    		String resXml = "";
    		WxPayApiConfigKit.setThreadLocalWxPayApiConfig(getApiConfig());
    		if(PaymentKit.verifyNotify(params, WxPayApiConfigKit.getWxPayApiConfig().getPaternerKey())){
    			if (("SUCCESS").equals(result_code)) {
    				// TODO 根据商户订单号更改押金状态
    				System.out.println("成功,存储");
    				
    				resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
    						+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
    				
    			} else {
    				//TODO FAIL支付失败
    				log.debug("支付失败的回调消息");
    				resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
    						+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
    
    			}
    			BufferedOutputStream out = null;
    			try {
    				out = new BufferedOutputStream(
    						response.getOutputStream());
    				out.write(resXml.getBytes());
    				out.flush();
    				out.close();
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    //		}
    
    	}

     

    这里需要注意最后的xml.put(),return PaymentKit.toXml(xml)一定要返回给微信,SUCCESS表示商户接收通知成功并校验成功,这样微信才会知道商户支付成功,否则会不断通知,这样就会重复处理数据,这个错误是致命的

    上面回调我改了一下,可以做到闭嘴,不会重复通知

    下面就是测试调起微信支付

     

    常见错误:

    1、网络环境未能通过安全验证,请稍后再试(IP改变导致的) 
    2、商家参数格式有误,请联系商家解决(H5支付的referer为空导致) 
    3、商家存在未配置的参数,请联系商家解决(H5支付的域名问题) 
    4、支付请求已失效,请重新发起支付(有效期为5分钟) 

    5、请在微信外打开订单,进行支付(H5支付不能直接在微信客户端内调起)

     

    有些地方可能有些旧了,大家可以看下最新的文档,现在接入已经不是很难了

    支付说到底就是封装,安全调用,微信都是返回预支付id,要细心

     

    我的demo地址:注意回调不用demo中的,用这篇文档的回调方式,可以闭嘴(包括公众号支付):http://download.csdn.net/download/goligory/10044575

    借鉴:http://blog.csdn.net/zyw_java/article/details/77507835,感兴趣可以看他的更多相关支付

     

    展开全文
  • 微信h5支付

    2018-06-21 14:40:20
    首先需要查看文档 知道h5支付的步骤这里注意测试的时候需要用手机打开浏览器测试,如果用电脑的浏览器测试跳不出支付页面(可能是电脑检测不到微信h5支付很简单 h5支付不需授权直接调用统一下单微信支付结果通知-...

    首先需要查看文档 知道h5支付的步骤

    这里注意测试的时候需要用手机打开浏览器测试,如果用电脑的浏览器测试跳不出支付页面(可能是电脑检测不到微信)

    h5支付很简单 h5支付不需授权直接调用统一下单微信支付结果通知-废话不多说直接上代码

    1.前台页面

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta content="text/html; charset=utf-8" http-equiv=content-type>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1">
    <meta name="renderer" content="webkit">
    <title>支付</title>
    </head>
    <body>
    <a href="javascript:f_authcode();" style="color:#000;">公众号支付</a>
     <a href="javascript:f_test();" style="color:#000;">H5充值</a>
    </body>
    <script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>

    <script type=text/javascript>


    function f_test(){
        $.ajax({
            url:"http://wx.hsjsns.com/v1/api/pay/MWEB/unifiedorder",
            contentType: "application/json",
            type:"post",
            //data:"{'id': " + openid +",'id': " + openid +",'id': " + openid +"}"
            data:JSON.stringify({money:"0.01",goods:"testH5充值",appid:"H5WG",account:"18068794602"}),//注释这些参数根据自己业务传
            dataType: "json",
            success: function(v_res){
            alert(JSON.stringify(v_res.data.mweb_url));
                console.log(v_res);
                if(v_res.code==0){
                    //onBridgeReady(v_res.data);
                    document.location.href=v_res.data.mweb_url;
                }
            }
        });
    }
    </script>

    </html>

    2.后台接口直接调用统一下单

    @RestController
    @RequestMapping(value = "v1/api")
    public class WXApiController {

    /**
    * 微信支付统一下单
    * @param order
    * @return sn
    */
    @RequestMapping(value = "/pay/{tradetype}/unifiedorder", method = RequestMethod.POST)
    public ResponseEntity<JsonResult> unifiedorder (@PathVariable(value = "tradetype") String tradetype,@RequestBody(required=false) Map<String, String> order, HttpServletRequest request){
    JsonResult r = null;
    try {

    if(!"JSAPI".equals(tradetype) && !"APP".equals(tradetype) && !"MWEB".equals(tradetype)) throw new ServiceException("微信支付交易类型有误!");
    //请求验证
    String ver_str = VerifyUtilsEx.verifyData(order, new String[] {"account","money","goods","appid"});

    if(StringUtils.isNotBlank(ver_str)){
    throw new ServiceException(ver_str);
    }
    String passback = order.containsKey("passback") ? order.get("passback") : "";

    if("JSAPI".equals(tradetype) && !order.containsKey("openid")){
    throw new ServiceException("公众号支付缺少openid");
    }
    String openid = order.containsKey("openid") ? order.get("openid") : "";
    String ip = "";
    //设定IP
    if(order.containsKey("ip") && StringUtilsEx.IsIp(order.get("ip"))){
    ip = order.get("ip");
    }else{
    ip = CommonUtilsEx.getIpAddr(request);
    }

    String moneystr = order.get("money");

    Float money = Float.parseFloat(moneystr);

    /**
    * 首先创建订单
    */
    String sn = wxApiService.createOrder(order.get("account"), money, order.get("goods"), order.get("appid"), ip, tradetype);//注释这个需要根据自己的业务区创建临时订单插入数据库,然后返回订单号

    if(StringUtils.isBlank(sn)) throw new ServiceException("创建临时订单失败!");

    Map<String, String> resData = null;

    String callback = callback_domain+"pay/paycomplate";//微信支付结果通知


    resData = wxApiService.unifiedorder_H5(openid, sn, money, order.get("goods"), passback, ip, callback);


    r= new JsonResult("0", "", resData);
    } catch(ServiceException se){
    r= new JsonResult("-1", se.getMessage());

    } catch (Exception e) {
    r= new JsonResult("-2", "系统错误");
    logger.error(e.getMessage());
    e.printStackTrace();
    }
    return ResponseEntity.ok(r);
    }

    /**
    * 微信支付结果通知
    * @param request
    * @param response
    * @return
    */
    @RequestMapping(value = "/pay/paycomplate", method = RequestMethod.POST)
    public ResponseEntity<String> paycomplate(HttpServletRequest request, HttpServletResponse response){
    String inputLine;
    String notityXml = "";
    String resXml = WXCommonUtil.setXML("FAIL", "");


    try {
    while ((inputLine = request.getReader().readLine()) != null) {
    notityXml += inputLine;
    }
    request.getReader().close();

    logger.info("接收到的报文:" + notityXml);
    SortedMap<String, String> res = WXCommonUtil.doXMLParse(notityXml);

    //校验是否接收成功
    if(res != null && !res.isEmpty() && "SUCCESS".equals(res.get("result_code"))){

    //校验参数格式是否正确
    if(!res.containsKey("sign") || !res.containsKey("out_trade_no") || !res.containsKey("openid") || !res.containsKey("transaction_id") || !res.containsKey("total_fee")){
    resXml = WXCommonUtil.setXML("FAIL", "参数格式校验错误");
    logger.error("wx/paycomplate:WARN: ---------------------参数格式校验错误");
    }else{
    String sign = WXCommonUtil.createSign(res);

    String v_signString = res.get("sign");

    //校验签名
    if(sign.equals(v_signString)){
    resXml = WXCommonUtil.setXML("SUCCESS", "OK");

    String sn = res.get("out_trade_no");

    String transaction_id = res.get("transaction_id");

    Double money = Double.parseDouble(res.get("total_fee"));

    String passback = res.get("attach");

    //微信传递金额为分 故需要转为元 /100 
    wxApiService.payComplate(sn, transaction_id, String.valueOf(money/100), passback);
    }else{
    resXml = WXCommonUtil.setXML("FAIL", "签名校验失败");
    logger.error("wx/paycomplate:WARN: ---------------------签名校验失败");
    //sysErrLogService.addErrInfo("WX_PAY", "签名校验失败", notityXml);
    }
    }

    }else{
    resXml = WXCommonUtil.setXML("FAIL", "报文为空");
    logger.error("wx/paycomplate:WARN: ---------------------报文为空");
    //sysErrLogService.addErrInfo("WX_PAY", "报文为空", "");
    }

    } catch (ServiceException se) {
    logger.warn("wx/paycomplate:WARN:"+se.getMessage());
    }catch (Exception e) {
    //e.printStackTrace();
    logger.error(e.getMessage());
    e.printStackTrace();
    }

    return ResponseEntity.ok(resXml);

    }

    }

    3.涉及到的工具类

    public class WXCommonUtil {

    public static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code
                + "]]></return_code><return_msg><![CDATA[" + return_msg
                + "]]></return_msg></xml>";

    }



    /**
    * 解析接收到的xml报文
    * @param strxml
    * @return SortedMap
    * @throws JDOMException
    * @throws IOException
    */
    public static SortedMap<String, String> doXMLParse(String strxml) throws JDOMException, IOException {  
            strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");  


            if(null == strxml || "".equals(strxml)) {  
                return null;  
            }  
              
            SortedMap m = new TreeMap();  
              
            InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));  
            SAXBuilder builder = new SAXBuilder();  
            Document doc = builder.build(in);  
            Element root = doc.getRootElement();  
            List list = root.getChildren();  
            Iterator it = list.iterator();  
            while(it.hasNext()) {  
                Element e = (Element) it.next();  
                String k = e.getName();  
                String v = "";  
                List children = e.getChildren();  
                if(children.isEmpty()) {  
                    v = e.getTextNormalize();  
                } else {  
                    v = getChildrenText(children);  
                }  
                  
                m.put(k, v);  
            }  
              
            //关闭流  
            in.close();  
              
            return m;  

    }

    /**
    * @author Mark
    * @Description:sign签名
    * @param characterEncoding 编码格式
    * @param parameters 请求参数
    * @return
    */
    public static String createSign(SortedMap<String,String> parameters){
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if(null != v && !"".equals(v) 
                    && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        
        String key= "";
        
        if(parameters.containsKey("key") && StringUtils.isNotBlank(parameters.get("key"))){
        key = parameters.get("key");
        }else{//通过商户ID获取秘钥KEY
        WXConfigService service = SpringUtil.getBean(WXConfigService.class);
        key = service.getKeyByMchid(parameters.get("mch_id")); 
        }
        //System.out.println("WXCommUtile:------>> key:"+key);
        sb.append("key=" + key);
        System.out.println(sb);
        String sign = Md5UtilsEx.getMD5Str(sb.toString()).toUpperCase();
        return sign;
    }

    }

    public class VerifyUtilsEx {

    static String KEY = "RVqq0cifgVJCfeCSxnWU6LoyGIpwYOa6";
    static long TIMESTAMP = 5*60; //时间戳5分钟 5*60

    /**
    * 接口必要参数签名验证
    * @return String
    * @throws UnsupportedEncodingException 
    */
    public static String verifyParam(HttpServletRequest request,String[] args) throws UnsupportedEncodingException{
    Map params = transToMAP(request.getParameterMap());
    return verifyData(params,args);
    }

    public static String verifyData(Map<String, String> data,String[] args) throws UnsupportedEncodingException{

    String sign = "";
    String arg_cnt = "";
    String msg = "";

    if(data == null || data.isEmpty()) return "没有检索到参数!";

    for(String arg : args){

    if(data.keySet().contains(arg)){
    if(data.get(arg)==null || !StringUtils.hasText(String.valueOf(data.get(arg)))){
    msg = "参数【"+arg+"】未明确,请确认!";
    break;
    }

    if("sign".equals(arg)){
    sign = String.valueOf(data.get(arg));
    }

    if("ts".equals(arg)){
    long systime = System.currentTimeMillis()/1000;

    long v_ts = Long.parseLong(data.get(arg));
    //时间戳校验
    if(Math.abs(systime-v_ts) > TIMESTAMP){
    msg = "请求超出有效期!";
    break;
    }
    }
    }else{
    msg = "参数【"+arg+"】未明确,请确认!";
    break;
    }
    }

    //参数都已明确并需要签名验证
    if(StringUtils.hasText(sign) && !StringUtils.hasText(msg)){
    for(String arg1 : args){
    if(!"sign".equals(arg1)){
    arg_cnt += String.valueOf(data.get(arg1));
    }
    }


    //System.out.println(URLEncoder.encode(arg_cnt+KEY,"utf-8"));
    String v_sign = Md5UtilsEx.getMD5Str(URLEncoder.encode(arg_cnt+KEY,"utf-8").toLowerCase());
    //System.out.println(v_sign);
    if(!sign.toLowerCase().equals(v_sign.toLowerCase())){
    msg = "校验码错误!";
    }
    }

    return msg;
    }

    private static Map transToMAP(Map parameterMap) throws UnsupportedEncodingException{
          // 返回值Map
          Map returnMap = new HashMap();
          Iterator entries = parameterMap.entrySet().iterator();
          Map.Entry entry;
          String name = "";
          String value = "";
          while (entries.hasNext()) {
              entry = (Map.Entry) entries.next();
              name = (String) entry.getKey();
              Object valueObj = entry.getValue();
              if(null == valueObj){
                  value = "";
              }else if(valueObj instanceof String[]){
                  String[] values = (String[])valueObj;
                  for(int i=0;i<values.length;i++){
                      value = values[i] + ",";
                  }
                  value = value.substring(0, value.length()-1);
              }else{
                  value = valueObj.toString();
              }
              System.out.println(value);
              returnMap.put(name, value);
          }
          return  returnMap;
      }


    }



    展开全文
  • 微信 H5支付

    2018-08-17 10:32:10
    这几天做WAP网站,涉及到微信支付和支付宝支付,在开通应用前只是针对技术文档做了预研,了解下程序的逻辑实现。结果导致后期开发中支付宝很顺利,毕竟API很强大。微信有些坑要填补,真是悲催。 &gt;第一个坑:...

空空如也

1 2 3 4 5 ... 20
收藏数 2,848
精华内容 1,139
关键字:

微信h5支付