热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

支付宝手机网页即时到账接口(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¬ify_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%3Edinglang@a.com%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%3Echenf003@yahoo.cn%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=1


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

代码示例

	@RequestMapping(value="/notify",method=RequestMethod.POST)
@ResponseBody
public Object notifyUrl(HttpServletRequest request, HttpServletResponse response){
System.out.println("支付提示");

//获取支付宝POST过来反馈信息
Map params = new HashMap();
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 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 = ""
+ "" + subject + ""//商品名称
+ "" + outTradeNo + ""//商户网站唯一订单号
+ "" + totalFee + ""//交易金额
+ "" + sellerAccountName + ""//卖家支付宝账号
+ "" + callBackUrl + ""//支付成功跳转页面
+ "" + notifyUrl + ""//异步通知页面
//+ "" + merchantUrl + ""//操作终端返回地址(可空)
+ "
";
return reqData;
}
}

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

AliPayUtils.verifyNotify(params, alipayAuth)方法

/**
* 验证消息是否是支付宝发出的合法消息,验证服务器异步通知
* @param params 通知返回来的参数数组
* @return 验证结果
*/
public static boolean verifyNotify(Map 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 Params, String sign, boolean isSort,AlipayAuth alipayAuth) {
//过滤空值、sign与sign_type参数
Map 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;
}


推荐阅读
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
author-avatar
咸咸
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有