支付宝手机网页即时到账接口(4)之交易接口服务器异步通知

前言

       这篇文章主要讲诉系统调用支付宝手机网页即时交易接口后支付宝返回的异步通知。   

       支付宝对商户的请求数据处理完成后,会将处理的结果数据通过服务器主动通知的方式通知给商户网站。这些处理结果数据就是服务器异步通知参数。

特性

  1. 必须保证服务器异步通知页面(notify_url)上无任何字符,如空格、HTML标签、开发系统自带抛出的异常提示信息或错误页面等。
  2. 支付宝是用POST方式发送通知信息,因此该页面中获取参数的方式,如: request.Form("out_trade_no")、$_POST[‘out_trade_no‘]。
  3. 支付宝主动发起通知,该方式才会被启用。
  4. 只有在支付宝的交易管理中存在该笔交易,且发生了交易状态的改变,支付宝才会通过该方式发起服务器通知(即时到账中交易状态为“等待买家付款”的状态默认是不会发送通知的)。
  5. 服务器间的交互,不像页面跳转同步通知可以在页面上显示出来,这种交互方式是不可见的。
  6. 第一次交易状态改变(即时到账中此时交易状态是交易完成)时,不仅页面跳转同步通知页面会启用,而且服务器异步通知页面也会收到支付宝发来的处理结果通知。
  7. 程序执行完后必须打印输出“success”(不包含引号、前后无空格和其他多余字符)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:2m,10m,10m,1h,2h,6h,15h)。
  8. 程序执行完成后,该页面不能执行页面跳转。如果执行页面跳转,支付宝会收不到success字符,会被支付宝服务器判定为该页面程序运行出现异常,而重发处理结果通知。
  9. 程序处理过程中出现异常时返回“fail”,这时支付宝服务器会选择重发通知。
  10. cookies、session等在此页面会失效,即无法获取这些数据。
  11. 该方式的调试与运行必须在服务器上,即互联网上能访问。
  12. 该方式的作用主要防止订单丢失,即页面跳转同步通知没有处理订单更新,它则去处理。
  13. 当商户收到服务器异步通知并打印出success时,服务器异步通知参数notify_id才会失效。也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出success导致支付宝重发数次通知),服务器异步通知参数notify_id是不变的。
  14. 异步通知在“交易成功”和“支付成功”状态都会进行通知发送,需正确处理通知时的交易状态,处理完成之后都需返回“success”,避免出现重复通知可能导致的业务重复处理错误。

服务器异步通知参数说明

技术分享

notify_data通知业务参数列表

技术分享

样例

  • 商户使用RSA签名时收到支付宝的通知请求样例如下:
    • http://商户自定义地址/alipay/notify_url.php?service=alipay.wap.trade.create.direct&sign=Rw/y4ROnNicXhaj287Fiw5pvP6viSyg53H3iNiJ61D3YVi7zGniG2680pZv6rakMCeXX++q9XRLw8Rj6I1//qHrwMAHS1hViNW6hQYsh2TqemuL/xjXRCY3vjm1HCoZOUa5zF2jU09yG23MsMIUx2FAWCL/rgbcQcOjLe5FugTc=&sec_id=0001&v=2.0¬ify_data=g3ivqicRwI9rI5jgmSHSU2osBXV1jcxohapSAPjx4f6qiqsoAzstaRWuPuutE0gxQwzMOtwL3npZqWO3Z89J4w4dXIY/fvOLoTNn8FjExAf7OozoptUS6suBhdMyo/YJyS3lVALfCeT3s27pYWihHgQgna6cTfgi67H2MbX40xtexIpUnjgxBkmOLai8DPOUI58y4UrVwoXQgdcwnXsfn2OthhUFiFPfpINgEphUAq1nC/EPymP6ciHdTCWRI6l1BgWuCzdFy0MxJLliPSnuLyZTou7f+Z5Mw24FgOacaISB+1/G+c4XIJVKJwshCDw9Emz+NAWsPvq34FEEQXVAeQRDOphJx8bDqLK75CGZX+6fx88m5ztq4ykuRUcrmoxZLJ+PiABvYFzi5Yx2uBMP/PmknRmj1HUKEhuVWsXR0t6EWpJFXlyQA4uxbShzncWDigndD7wbfNtkNLg5xMSFFIKay+4YzJK68H9deW4xqk4JYTKsv8eom9Eg9MrJZiIrFkFpVYPuaw0y/n61UEFYdzEQZz+garCmMYehEAQCGibYUQXBlf1iwTOZdqJIxdgCpSX21MIa9N9jicmFu8OXWZJkdN+UrSyvIcpzRori+U6522ovMz5Z8EzVTfcUENu+d


    • 以上示例中的notify_data参数值为加密内容,商户需用自己的RSA私钥先进行解密后再验签。
  • 商户使用MD5签名时收到支付宝的通知样例如下:
    • http://商户自定义地址/alipay/notify_url.php?service=alipay.wap.trade.create.direct%20&sign=Rw/y4ROnNicXhaj287Fiw5pvP6viSyg53H3iNiJ61D3YVi7zGniG2680pZv6rakMCeXX++q9XRLw8Rj6I1//qHrwMAHS1hViNW6hQYsh2TqemuL/xjXRCY3vjm1HCoZOUa5zF2jU09yG23MsMIUx2FAWCL/rgbcQcOjLe5FugTc=&v=2.0&sec_id=MD5&notify_data=%3Cnotify%3E%3Cpayment_type%3E1%3C/payment_type%3E%3Csubject%3E%E6%94%B6%E9%93%B6%E5%8F%B0{1283134629741}%3C/subject%3E%3Ctrade_no%3E2014040311001004370000361525%3C/trade_no%3E%3Cbuyer_email%[email protected]%3C/buyer_email%3E%3Cgmt_create%3E2010-08-3010:17:24%3C/gmt_create%3E%3Cnotify_type%3Etrade_status_sync%3C/notify_type%3E%3Cquantity%3E1%3C/quantity%3E%3Cout_trade_no%3E1283134629741%3C/out_trade_no%3E%3Cnotify_time%3E2010-08-3010:18:15%3C/notify_time%3E%3Cseller_id%3E2088101000137799%3C/seller_id%3E%3Ctrade_status%3ETRADE_FINISHED%3C/trade_status%3E%3Cis_total_fee_adjust%3EN%3C/is_total_fee_adjust%3E%3Ctotal_fee%3E1.00%3C/total_fee%3E%3Cgmt_payment%3E2010-08-3010:18:26%3C/gmt_payment%3E%3Cseller_email%[email protected]%3C/seller_email%3E%3Cgmt_close%3E2010-08-3010:18:26%3C/gmt_close%3E%3Cprice%3E1.00%3C/price%3E%3Cbuyer_id%3E2088102001172352%3C/buyer_id%3E%3Cnotify_id%3E509ad84678759176212c247c46bec05303%3C/notify_id%3E%3Cuse_coupon%3EN%3C/use_coupon%3E%3C/notify%3E


    • 以上示例中的notify_data参数值为明文内容,无需解密。
  • 支付宝系统通知待签名数据构造规则比较特殊,为固定顺序。
    • 例如商户收到如下通知数据:
    • http://商户自定义地址/alipay/notify_url.php?service=alipay.wap.trade.create.direct&sign=Rw/y4ROnNicXhaj287Fiw5pvP6viSyg53H3iNiJ61D3YVi7zGniG2680pZv6rakMCeXX++q9XRLw8Rj6I1//qHrwMAHS1hViNW6hQYsh2TqemuL/xjXRCY3vjm1HCoZOUa5zF2jU09yG23MsMIUx2FAWCL/rgbcQcOjLe5FugTc=&v=1.0&sec_id=0001¬ify_data=<notify><payment_type>1</payment_type></notify>


      • 则只需对以下数据进行验签:
      • service=alipay.wap.trade.create.direct&v=1.0&sec_id=0001¬ify_data=<notify>…</notify>

代码示例

	@RequestMapping(value="/notify",method=RequestMethod.POST)
	@ResponseBody
	public Object notifyUrl(HttpServletRequest request, HttpServletResponse response){
		System.out.println("支付提示");
		
		//获取支付宝POST过来反馈信息
		Map<String,String> params = new HashMap<String,String>();
		Map requestParams = request.getParameterMap();
		for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
			String name = (String) iter.next();
			String[] values = (String[]) requestParams.get(name);
			String valueStr = "";
			for (int i = 0; i < values.length; i++) {
				valueStr = (i == values.length - 1) ? valueStr + values[i]
						: valueStr + values[i] + ",";
			}
			//乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
			//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
			params.put(name, valueStr);
		}
		//验证
		AlipayAuth alipayAuth = new AlipayAuth();
		alipayAuth.setKey(orderBusiness.getAliAttributes("key"));
		alipayAuth.setPartner(orderBusiness.getAliAttributes("partner"));
		boolean flag = false;
		try {
			flag = AliPayUtils.verifyNotify(params, alipayAuth);
			//获取支付宝的通知返回参数
			//XML解析notify_data数据
			Document doc_notify_data = DocumentHelper.parseText(params.get("notify_data"));
			
			//商户订单号
			String out_trade_no = doc_notify_data.selectSingleNode( "//notify/out_trade_no" ).getText();

			//支付宝交易号
			String trade_no = doc_notify_data.selectSingleNode( "//notify/trade_no" ).getText();

			//交易状态
			String trade_status = doc_notify_data.selectSingleNode( "//notify/trade_status" ).getText();
			
			//交易总金额
			String total_fee = doc_notify_data.selectSingleNode( "//notify/total_fee" ).getText();
			
			
			
			if(flag){
				//付款成功后
				//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
		
				if(trade_status.equals("TRADE_FINISHED")){
				//判断该笔订单是否在商户网站中已经做过处理
					//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
					//如果有做过处理,不执行商户的业务程序
				
				//注意:
					//该种交易状态只在两种情况下出现
					//1、开通了普通即时到账,买家付款成功后。
					//2、开通了高级即时到账,从该笔交易成功时间算起,过了签约时的可退款时限(如:三个月以内可退款、一年以内可退款等)后。

					out.println("success");	//请不要修改或删除
				} else if (trade_status.equals("TRADE_SUCCESS")){
					//判断该笔订单是否在商户网站中已经做过处理
					//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
					//如果有做过处理,不执行商户的业务程序
				
				//注意:
					//该种交易状态只在一种情况下出现——开通了高级即时到账,买家付款成功后。
			
					out.println("success");	//请不要修改或删除
				}

				//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
			} else {//
				return "fail";
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			log.error(e.getMessage());
			e.printStackTrace();
			return "fail";
		}
		
	}
AlipayAuth
public class AlipayAuth {  
      
    // 支付宝网关  
    private final static String alipayGatewayNew = "http://wappaygw.alipay.com/service/rest.htm?";  
      
    private final static String inputCharset = "utf-8";  
      
    private String key = "";//从支付宝获取的密钥  
      
    //接口名称  
    private final static String service = "alipay.wap.trade.create.direct";  
      
    //请求参数格式  
    private final static String format = "xml";  
      
    //接口版本号  
    private final static String v = "2.0";  
      
    //合作者身份id  
    public String partner = "2088000000000000";//填写从支付宝得到的id  
      
    //请求号  
    private String reqId;  
      
    //签名方式  
    private final static String secId = "MD5";  
      
    //签名  
    private String sign;  
      
    //请求业务参数  
    private String reqData;  
      
    //商品名称  
    private String subject;  
      
    //商务网站唯一订单号  
    private String outTradeNo;  
  
    //交易金额  
    private String totalFee;  
      
    //卖家支付宝账号  
    private String sellerAccountName;  
      
    //支付成功跳转路径  
    private String callBackUrl = "callBack";  
      
    //服务器异步通知页面路径 (可空)  
    private String notifyUrl = "notify";  
      
    //商户系统唯一标示(可空)  
    private String outUser;  
      
    //操作中断返回地址(可空)  
    private String merchantUrl;  
      
    //交易自动关闭时间(可空)  
    private String payExpire;  
      
    //代理人id(可空)  
    private String agentId;  
  
      
  
    public String getBasePath() {  
        return basePath;  
    }  
    public void setBasePath(String basePath) {  
        this.basePath = basePath;  
    }  
    public String getAlipayGatewayNew() {  
        return alipayGatewayNew;  
    }  
    public String getKey() {  
        return key;  
    }  
    public void setKey(String key) {  
        this.key = key;  
    }  
    public String getInputCharset() {  
        return inputCharset;  
    }  
    public String getService() {  
        return service;  
    }  
    public String getFormat() {  
        return format;  
    }  
    public String getV() {  
        return v;  
    }  
    public String getPartner() {  
        return partner;  
    }  
    public void setPartner(String partner) {  
        this.partner = partner;  
    }  
    public String getReqId() {  
        return reqId;  
    }  
    public void setReqId(String reqId) {  
        this.reqId = reqId;  
    }  
    public String getSecId() {  
        return secId;  
    }  
    public String getSign() {  
        return sign;  
    }  
    public void setSign(String sign) {  
        this.sign = sign;  
    }  
    public String getSubject() {  
        return subject;  
    }  
    public void setSubject(String subject) {  
        this.subject = subject;  
    }  
    public String getOutTradeNo() {  
        return outTradeNo;  
    }  
    public void setOutTradeNo(String outTradeNo) {  
        this.outTradeNo = outTradeNo;  
    }  
    public String getTotalFee() {  
        return totalFee;  
    }  
    public void setTotalFee(String totalFee) {  
        this.totalFee = totalFee;  
    }  
    public String getSellerAccountName() {  
        return sellerAccountName;  
    }  
    public void setSellerAccountName(String sellerAccountName) {  
        this.sellerAccountName = sellerAccountName;  
    }  
    public String getCallBackUrl() {  
        return callBackUrl;  
    }  
    public void setCallBackUrl(String callBackUrl) {  
        this.callBackUrl = callBackUrl;  
    }  
    public String getNotifyUrl() {  
        return notifyUrl;  
    }  
    public void setNotifyUrl(String notifyUrl) {  
        this.notifyUrl = notifyUrl;  
    }  
    public String getOutUser() {  
        return outUser;  
    }  
    public void setOutUser(String outUser) {  
        this.outUser = outUser;  
    }  
    public String getMerchantUrl() {  
        return merchantUrl;  
    }  
    public void setMerchantUrl(String merchantUrl) {  
        this.merchantUrl = merchantUrl;  
    }  
    public String getPayExpire() {  
        return payExpire;  
    }  
    public void setPayExpire(String payExpire) {  
        this.payExpire = payExpire;  
    }  
    public String getAgentId() {  
        return agentId;  
    }  
    public void setAgentId(String agentId) {  
        this.agentId = agentId;  
    }  
    public void setReqData(String reqData) {  
        this.reqData = reqData;  
    }  
    public String getReqData() {  
          
        reqData = "<direct_trade_create_req>"  
                    + "<subject>" + subject + "</subject>"//商品名称  
                    + "<out_trade_no>" + outTradeNo + "</out_trade_no>"//商户网站唯一订单号  
                    + "<total_fee>" + totalFee + "</total_fee>"//交易金额  
                    + "<seller_account_name>" + sellerAccountName + "</seller_account_name>"//卖家支付宝账号  
                    + "<call_back_url>" + callBackUrl + "</call_back_url>"//支付成功跳转页面  
                    + "<notify_url>" + notifyUrl + "</notify_url>"//异步通知页面  
                    //+ "<merchantUrl>" + merchantUrl + "</merchantUrl>"//操作终端返回地址(可空)  
                + "</direct_trade_create_req>";  
        return reqData;  
    }     
}  

验证从支付宝传递过来的参数

AliPayUtils.verifyNotify(params, alipayAuth)方法

/**
     * 验证消息是否是支付宝发出的合法消息,验证服务器异步通知
     * @param params 通知返回来的参数数组
     * @return 验证结果
     */
    public static boolean verifyNotify(Map<String, String> params,AlipayAuth alipayAuth) throws Exception {
    	
    	//获取是否是支付宝服务器发来的请求的验证结果
    	String responseTxt = "true";
    	try {
        	//XML解析notify_data数据,获取notify_id
	    	Document document = DocumentHelper.parseText(params.get("notify_data"));
	    	String notify_id = document.selectSingleNode( "//notify/notify_id" ).getText();
			responseTxt = verifyResponse(notify_id,alipayAuth);
    	} catch(Exception e) {
    		responseTxt = e.toString();
    	}
    	
    	//获取返回时的签名验证结果
	    String sign = "";
	    if(params.get("sign") != null) {sign = params.get("sign");}
	    boolean isSign = getSignVeryfy(params, sign,false,alipayAuth);

        //写日志记录(若要调试,请取消下面两行注释)
        //String sWord = "responseTxt=" + responseTxt + "\n isSign=" + isSign + "\n 返回回来的参数:" + AlipayCore.createLinkString(params);
	    //AlipayCore.logResult(sWord);

        //判断responsetTxt是否为true,isSign是否为true
        //responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关
        //isSign不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关
        if (isSign && responseTxt.equals("true")) {
            return true;
        } else {
            return false;
        }
    }


verifyResponse(notify_id,alipayAuth)代码块

/**
     * 获取远程服务器ATN结果,验证返回URL
     * @param notify_id 通知校验ID
     * @return 服务器ATN结果
     * 验证结果集:
     * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 
     * true 返回正确信息
     * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
     private static String verifyResponse(String notify_id,AlipayAuth alipayAuth) {
         //获取远程服务器ATN结果,验证是否是支付宝服务器发来的请求

         String partner = alipayAuth.getPartner();
         String veryfy_url = HTTPS_VERIFY_URL + "partner=" + partner + "¬ify_id=" + notify_id;

         return checkUrl(veryfy_url);
     }


/**
     * 支付宝消息验证地址
     */
    private static final String HTTPS_VERIFY_URL = "https://mapi.alipay.com/gateway.do?service=notify_verify&";


checkUrl(veryfy_url);

/**
      * 获取远程服务器ATN结果
      * @param urlvalue 指定URL路径地址
      * @return 服务器ATN结果
      * 验证结果集:
      * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 
      * true 返回正确信息
      * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
      */
      private static String checkUrl(String urlvalue) {
          String inputLine = "";

          try {
              URL url = new URL(urlvalue);
              HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
              BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection
                  .getInputStream()));
              inputLine = in.readLine().toString();
          } catch (Exception e) {
              e.printStackTrace();
              inputLine = "";
          }

          return inputLine;
      }

getSignVeryfy(params, sign,false,alipayAuth)

 /**
       * 根据反馈回来的信息,生成签名结果
       * @param Params 通知返回来的参数数组
       * @param sign 比对的签名结果
       * @param isSort 是否排序
       * @return 生成的签名结果
       */
  	private static boolean getSignVeryfy(Map<String, String> Params, String sign, boolean isSort,AlipayAuth alipayAuth) {
      	//过滤空值、sign与sign_type参数
      	Map<String, String> sParaNew = paraFilter(Params);
          //获取待签名字符串
      	String preSignStr = "";
      	if(isSort) {
      		preSignStr = createLinkString(sParaNew);
      	} else {
      		preSignStr = createLinkStringNoSort(sParaNew);
      	}
          //获得签名验证结果
          boolean isSign = false;
          isSign = verify(preSignStr, sign, alipayAuth.getKey(), alipayAuth.getInputCharset());
          return isSign;
      }

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。