⽀付宝⽀付后回调处理(Java版)
前提:
下载
maven依赖 slf4j,fastjson
认真阅读服务器异步通知页⾯特性
陈杏衣微博服务器异步通知页⾯特性
必须保证服务器异步通知页⾯(notify_url)上⽆任何字符,如空格、HTML标签、开发系统⾃带抛出的异常提⽰信息等;
⽀付宝是⽤POST⽅式发送通知信息,因此该页⾯中获取参数的⽅式,如:request.Form(“out_trade_no”)、$_POST[‘out_trade_no’];
⽀付宝主动发起通知,该⽅式才会被启⽤;
只有在⽀付宝的交易管理中存在该笔交易,且发⽣了交易状态的改变,⽀付宝才会通过该⽅式发起服务
器通知(即时到账交易状态为“等待买家付
款”的状态默认是不会发送通知的);
服务器间的交互,不像页⾯跳转同步通知可以在页⾯上显⽰出来,这种交互⽅式是不可见的;
第⼀次交易状态改变(即时到账中此时交易状态是交易完成)时,不仅会返回同步处理结果,⽽且服务器异步通知页⾯也会收到⽀付宝发来的处理
结果通知;
程序执⾏完后必须打印输出“success”(不包含引号)。如果商户反馈给⽀付宝的字符不是success这7个字符,⽀付宝服务器会不断重发通知,
直到超过24⼩时22分钟。⼀般情况下,25⼩时以内完成8次通知(通知的间隔频率⼀般是:4m,10m,10m,1h,2h,6h,15h);
程序执⾏完成后,该页⾯不能执⾏页⾯跳转。如果执⾏页⾯跳转,⽀付宝会收不到success字符,会被⽀付宝服务器判定为该页⾯程序运⾏出现异
常,⽽重发处理结果通知;
cookies、session等在此页⾯会失效,即⽆法获取这些数据;
该⽅式的调试与运⾏必须在服务器上,即互联⽹上能访问;
该⽅式的作⽤主要防⽌订单丢失,即页⾯跳转同步通知没有处理订单更新,它则去处理;
当商户收到服务器异步通知并打印出success时,服务器异步通知参数notify_id才会失效。也就是说在⽀付宝发送同⼀条异步通知时(包含商户并
未成功打印出success导致⽀付宝重发数次通知),服务器异步通知参数notify_id是不变的。
这⾥使⽤springmvc的Controller处理,代码如下:
@Controller
public class AlipayCallbackController {
private static Logger logger = Logger(AlipayCallbackController.class);
黄秋生演的电影private ExecutorService executorService = wFixedThreadPool(20);
/**
* <pre>
* 第⼀步:验证签名,签名通过后进⾏第⼆步
* 第⼆步:按⼀下步骤进⾏验证
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际⾦额(即商户订单创建时的⾦额),
* 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作⽅(有的时候,⼀个商户可能有多个seller_id/seller_email),        * 4、验证app_id是否为该商户本⾝。上述1、2、3、4有任何⼀个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据⽀付宝不同类型的业务通知,正确的进⾏不同的业务处理,并且过滤重复的通知结果数据。
* 在⽀付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,⽀付宝才会认定为买家付款成功。
* </pre>
*
* @param params
* @return
*/
@RequestMapping("alipay_callback")
@ResponseBody
public String callback(HttpServletRequest request) {
Map<String, String> params = convertRequestParamsToMap(request); // 将异步通知中收到的待验证所有参数都存放到map中
String paramsJson = JSONString(params);
logger.info("⽀付宝回调,{}", paramsJson);
logger.info("⽀付宝回调,{}", paramsJson);
try {
AlipayConfig alipayConfig = new AlipayConfig();// ⽀付宝配置
// 调⽤SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, Alipay_public_key(),
if (signVerified) {
logger.info("⽀付宝回调签名认证成功");
// 按照⽀付结果异步通知中的描述,对⽀付结果中的业务内容进⾏1\2\3\4⼆次校验,校验成功后在response中返回success,校验失败返回failure this.check(params);
// 另起线程处理业务
@Override
public void run() {
AlipayNotifyParam param = buildAlipayNotifyParam(params);
String trade_status = TradeStatus();
// ⽀付成功
if (trade_status.equals(AlipayTradeStatus.Status())
|| trade_status.equals(AlipayTradeStatus.Status())) {
// 处理⽀付成功逻辑
try {邓家佳整容前后照片
/*
// 处理业务逻辑。。。
人类灭绝过多少动物myService.process(param);
*/
} catch (Exception e) {
<("⽀付宝回调业务处理报错,params:" + paramsJson, e);
}
} else {
<("没有处理⽀付宝回调业务,⽀付宝交易状态:{},params:{}",trade_status,paramsJson);
}
}
});
// 如果签名验证正确,⽴即返回success,后续业务另起线程单独处理
// 业务处理失败,可查看⽇志进⾏补偿,跟⽀付宝已经没多⼤关系。
return"success";
} else {
logger.info("⽀付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
return"failure";
}
} catch (AlipayApiException e) {
<("⽀付宝回调签名认证失败,paramsJson:{},errorMsg:{}", paramsJson, e.getMessage());
return"failure";
}
}
// 将request中的参数转换成Map
private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> retMap = new HashMap<String, String>();
Set<Entry<String, String[]>> entrySet = ParameterMap().entrySet();
for (Entry<String, String[]> entry : entrySet) {
String name = Key();
String[] values = Value();
int valLen = values.length;
if (valLen == 1) {
retMap.put(name, values[0]);
} else if (valLen > 1) {
StringBuilder sb = new StringBuilder();
for (String val : values) {
sb.append(",").append(val);
}
retMap.put(name, sb.toString().substring(1));
retMap.put(name, sb.toString().substring(1));
} else {
retMap.put(name, "");
}
}
return retMap;
}
private AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
String json = JSONString(params);
return JSON.parseObject(json, AlipayNotifyParam.class);
}
/**
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际⾦额(即商户订单创建时的⾦额),
* 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作⽅(有的时候,⼀个商户可能有多个seller_id/seller_email),    * 4、验证app_id是否为该商户本⾝。上述1、2、3、4有任何⼀个验证不通过,则表明本次通知是异常通知,务必忽略。
朱明虬热巴是真的嘛
* 在上述验证通过后商户必须根据⽀付宝不同类型的业务通知,正确的进⾏不同的业务处理,并且过滤重复的通知结果数据。
* 在⽀付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,⽀付宝才会认定为买家付款成功。
*
* @param params
* @throws AlipayApiException
*/
private void check(Map<String, String> params) throws AlipayApiException {
String outTradeNo = ("out_trade_no");
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
Order order = getOrderByOutTradeNo(outTradeNo); // 这个⽅法⾃⼰实现
if (order == null) {
throw new AlipayApiException("out_trade_no错误");
}
// 2、判断total_amount是否确实为该订单的实际⾦额(即商户订单创建时的⾦额),
long total_amount = new ("total_amount")).multiply(new BigDecimal(100)).longValue();
if (total_amount != PayPrice().longValue()) {
throw new AlipayApiException("error total_amount");
}
// 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作⽅(有的时候,⼀个商户可能有多个seller_id/seller_email),// 第三步可根据实际情况省略
// 4、验证app_id是否为该商户本⾝。
if (!("app_id").Appid())) {
throw new AlipayApiException("app_id不⼀致");
}
}
}
⽀付宝参数配置:
@Component
public class AlipayConfig {
// 商户ID
private String appid = "";
// 私钥
private String rsa_private_key = "";
// 异步回调地址
private String notify_url;
// 同步回调地址
private String return_url;
// 请求⽹关地址
private String gateway_url;
// 编码
private String charset = "UTF-8";
// 返回格式
private String format = "json";
// ⽀付宝公钥
private String alipay_public_key = "";
/
/ RSA2
private String signtype = "RSA2";
// 省略get set
}
⽀付宝返回结果对应的参数类:
public class AlipayNotifyParam implements Serializable {
private String appId;
医院妇产哪家好?点击了解private String tradeNo; // ⽀付宝交易凭证号
private String outTradeNo; // 原⽀付请求的商户订单号
private String outBizNo; // 商户业务ID,主要是退款通知中返回退款申请的流⽔号
private String buyerId; // 买家⽀付宝账号对应的⽀付宝唯⼀⽤户号。以2088开头的纯16位数字
private String buyerLogonId; // 买家⽀付宝账号
private String sellerId; // 卖家⽀付宝⽤户号
private String sellerEmail; // 卖家⽀付宝账号
private String tradeStatus; // 交易⽬前所处的状态,见交易状态说明
private BigDecimal totalAmount; // 本次交易⽀付的订单⾦额
private BigDecimal receiptAmount; // 商家在交易中实际收到的款项
private BigDecimal buyerPayAmount; // ⽤户在交易中⽀付的⾦额
private BigDecimal refundFee; // 退款通知中,返回总退款⾦额,单位为元,⽀持两位⼩数
private String subject; // 商品的标题/交易标题/订单标题/订单关键字等
private String body; // 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来
private Date gmtCreate; // 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtPayment; // 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtRefund; // 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S
private Date gmtClose; // 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss
private String fundBillList; // ⽀付成功的各个渠道⾦额信息,array
private String passbackParams; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。
// 省略get set
}
注意:
如果做了服务器集,考虑使⽤分布式锁,锁住outTradeNo后再处理业务,不然有可能重复处理。
做好订单状态判断,防⽌重复处理。