热门标签 | 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;
}


推荐阅读
  • 笔记说明重学前端是程劭非(winter)【前手机淘宝前端负责人】在极客时间开的一个专栏,每天10分钟,重构你的前端知识体系& ... [详细]
  • Java虚拟机及其发展历程
    Java虚拟机(JVM)是每个Java开发者日常工作中不可或缺的一部分,但其背后的运作机制却往往显得神秘莫测。本文将探讨Java及其虚拟机的发展历程,帮助读者深入了解这一关键技术。 ... [详细]
  • 本文详细介绍如何在SSM(Spring + Spring MVC + MyBatis)框架中实现分页功能。包括分页的基本概念、数据准备、前端分页栏的设计与实现、后端分页逻辑的编写以及最终的测试步骤。 ... [详细]
  • Asynchronous JavaScript and XML (AJAX) 的流行很大程度上得益于 Google 在其产品如 Google Suggest 和 Google Maps 中的应用。本文将深入探讨 AJAX 在 .NET 环境下的工作原理及其实现方法。 ... [详细]
  • 本文探讨了如何在PHP与MySQL环境中实现高效的分页查询,包括基本的分页实现、性能优化技巧以及高级的分页策略。 ... [详细]
  • Docker安全策略与管理
    本文探讨了Docker的安全挑战、核心安全特性及其管理策略,旨在帮助读者深入理解Docker安全机制,并提供实用的安全管理建议。 ... [详细]
  • 本文详细介绍了Oracle 11g中的创建表空间的方法,以及如何设置客户端和服务端的基本配置,包括用户管理、环境变量配置等。 ... [详细]
  • Maven + Spring + MyBatis + MySQL 环境搭建与实例解析
    本文详细介绍如何使用MySQL数据库进行环境搭建,包括创建数据库表并插入示例数据。随后,逐步指导如何配置Maven项目,整合Spring框架与MyBatis,实现高效的数据访问。 ... [详细]
  • 本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ... [详细]
  • ArcBlock 发布 ABT 节点 1.0.31 版本更新
    2020年11月9日,ArcBlock 区块链基础平台发布了 ABT 节点开发平台的1.0.31版本更新,此次更新带来了多项功能增强与性能优化。 ... [详细]
  • 解决Win10 1709版本文件共享安全警告问题
    每当Windows 10发布新版本时,由于兼容性问题往往会出现各种故障。近期,一些用户在升级至1709版本后遇到了无法访问共享文件夹的问题,系统提示‘文件共享不安全,无法连接’。本文将提供多种解决方案,帮助您轻松解决这一难题。 ... [详细]
  • 为何Compose与Swarm之后仍有Kubernetes的诞生?
    探讨在已有Compose和Swarm的情况下,Kubernetes是如何以其独特的设计理念和技术优势脱颖而出,成为容器编排领域的领航者。 ... [详细]
  • 本文深入探讨了Go语言中的接口型函数,通过实例分析其灵活性和强大功能,帮助开发者更好地理解和运用这一特性。 ... [详细]
  • 从理想主义者的内心深处萌发的技术信仰,推动了云原生技术在全球范围内的快速发展。本文将带你深入了解阿里巴巴在开源领域的贡献与成就。 ... [详细]
  • 本文探讨了如何通过Service Locator模式来简化和优化在B/S架构中的服务命名访问,特别是对于需要频繁访问的服务,如JNDI和XMLNS。该模式通过缓存机制减少了重复查找的成本,并提供了对多种服务的统一访问接口。 ... [详细]
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社区 版权所有