β

支付宝支付后回调处理(Java版)

Harries Blog™ 43 阅读

支付宝 回调处理文档

前提:

服务器异步通知页面特性

  • 必须保证服务器异步通知页面(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是不变的。

这里使用 spring mvc的Controller处理, 代码 如下:

@Controller
public class AlipayCallbackController {
    private static Logger logger = LoggerFactory.getLogger(AlipayCallbackController.class);

    @Autowired
    private AlipayConfig alipayConfig; // 支付宝支付配置

    private ExecutorService executorService = Executors.newFixedThreadPool(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 = JSON.toJSONString(params);
        logger.info("支付宝回调,{}", paramsJson);
        try {
            // 调用SDK验证签名
            boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipay_public_key(),
                    alipayConfig.getCharset(), alipayConfig.getSigntype());
            if (signVerified) {
                logger.info("支付宝回调签名认证成功");
                // 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1/2/3/4二次校验,校验成功后在response中返回success,校验失败返回failure
                this.check(params);
                // 另起线程处理业务
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        AlipayNotifyParam param = buildAlipayNotifyParam(params);
                        String trade_status = param.getTradeStatus();
                        // 支付成功
                        if (trade_status.equals(AlipayTradeStatus.TRADE_SUCCESS.getStatus())
                                || trade_status.equals(AlipayTradeStatus.TRADE_FINISHED.getStatus())) {
                            // 处理支付成功逻辑
                            try {
                                /*
                                    // 处理业务逻辑。。。
                                    myService.process(param);
                                */                      

                            } catch (Exception e) {
                                logger.error("支付宝回调业务处理报错,params:" + paramsJson, e);
                            }
                        } else {
                            logger.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}",trade_status,paramsJson);
                        }
                    }
                });
                // 如果签名验证正确,立即返回success,后续业务另起线程单独处理
                // 业务处理失败,可查看日志进行补偿,跟支付宝已经没多大关系。
                return "success";
            } else {
                logger.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
                return "failure";
            }
        } catch (AlipayApiException e) {
            logger.error("支付宝回调签名认证失败,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 = request.getParameterMap().entrySet();

        for (Entry<String, String[]> entry : entrySet) {
            String name = entry.getKey();
            String[] values = entry.getValue();
            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));
            } else {
                retMap.put(name, "");
            }
        }

        return retMap;
    }

    private AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
        String json = JSON.toJSONString(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 = params.get("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 BigDecimal(params.get("total_amount")).multiply(new BigDecimal(100)).longValue();       
        if (total_amount != order.getPayPrice().longValue()) {
            throw new AlipayApiException("error total_amount");
        }

        // 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
        // 第三步可根据实际情况省略

        // 4、验证app_id是否为该商户本身。
        if (!params.get("app_id").equals(alipayConfig.getAppid())) {
            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";

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getRsa_private_key() {
        return rsa_private_key;
    }

    public void setRsa_private_key(String rsa_private_key) {
        this.rsa_private_key = rsa_private_key;
    }

    public String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getReturn_url() {
        return return_url;
    }

    public void setReturn_url(String return_url) {
        this.return_url = return_url;
    }

    public String getGateway_url() {
        return gateway_url;
    }

    public void setGateway_url(String gateway_url) {
        this.gateway_url = gateway_url;
    }

    public String getCharset() {
        return charset;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public String getFormat() {
        return format;
    }

    public void setFormat(String format) {
        this.format = format;
    }

    public String getAlipay_public_key() {
        return alipay_public_key;
    }

    public void setAlipay_public_key(String alipay_public_key) {
        this.alipay_public_key = alipay_public_key;
    }

    public String getSigntype() {
        return signtype;
    }

    public void setSigntype(String signtype) {
        this.signtype = signtype;
    }

}

支付宝返回结果对应的参数类:

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; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。    


    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getAppId() {
        return this.appId;
    }

    /** 设置 支付宝交易凭证号,对应字段 trade_record_alipay_detail.trade_no */
    public void setTradeNo(String tradeNo) {
        this.tradeNo = tradeNo;
    }
    /** 获取 支付宝交易凭证号,对应字段 trade_record_alipay_detail.trade_no */
    public String getTradeNo() {
        return this.tradeNo;
    }

    /** 设置 原支付请求的商户订单号,对应字段 trade_record_alipay_detail.out_trade_no */
    public void setOutTradeNo(String outTradeNo) {
        this.outTradeNo = outTradeNo;
    }
    /** 获取 原支付请求的商户订单号,对应字段 trade_record_alipay_detail.out_trade_no */
    public String getOutTradeNo() {
        return this.outTradeNo;
    }

    /** 设置 商户业务ID,主要是退款通知中返回退款申请的流水号,对应字段 trade_record_alipay_detail.out_biz_no */
    public void setOutBizNo(String outBizNo) {
        this.outBizNo = outBizNo;
    }
    /** 获取 商户业务ID,主要是退款通知中返回退款申请的流水号,对应字段 trade_record_alipay_detail.out_biz_no */
    public String getOutBizNo() {
        return this.outBizNo;
    }

    /** 设置 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字,对应字段 trade_record_alipay_detail.buyer_id */
    public void setBuyerId(String buyerId) {
        this.buyerId = buyerId;
    }
    /** 获取 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字,对应字段 trade_record_alipay_detail.buyer_id */
    public String getBuyerId() {
        return this.buyerId;
    }

    /** 设置 买家支付宝账号,对应字段 trade_record_alipay_detail.buyer_logon_id */
    public void setBuyerLogonId(String buyerLogonId) {
        this.buyerLogonId = buyerLogonId;
    }
    /** 获取 买家支付宝账号,对应字段 trade_record_alipay_detail.buyer_logon_id */
    public String getBuyerLogonId() {
        return this.buyerLogonId;
    }

    /** 设置 卖家支付宝用户号,对应字段 trade_record_alipay_detail.seller_id */
    public void setSellerId(String sellerId) {
        this.sellerId = sellerId;
    }
    /** 获取 卖家支付宝用户号,对应字段 trade_record_alipay_detail.seller_id */
    public String getSellerId() {
        return this.sellerId;
    }

    /** 设置 卖家支付宝账号,对应字段 trade_record_alipay_detail.seller_email */
    public void setSellerEmail(String sellerEmail) {
        this.sellerEmail = sellerEmail;
    }
    /** 获取 卖家支付宝账号,对应字段 trade_record_alipay_detail.seller_email */
    public String getSellerEmail() {
        return this.sellerEmail;
    }

    /** 设置 交易目前所处的状态,见交易状态说明,对应字段 trade_record_alipay_detail.trade_status */
    public void setTradeStatus(String tradeStatus) {
        this.tradeStatus = tradeStatus;
    }
    /** 获取 交易目前所处的状态,见交易状态说明,对应字段 trade_record_alipay_detail.trade_status */
    public String getTradeStatus() {
        return this.tradeStatus;
    }

    /** 设置 本次交易支付的订单金额,对应字段 trade_record_alipay_detail.total_amount */
    public void setTotalAmount(BigDecimal totalAmount) {
        this.totalAmount = totalAmount;
    }
    /** 获取 本次交易支付的订单金额,对应字段 trade_record_alipay_detail.total_amount */
    public BigDecimal getTotalAmount() {
        return this.totalAmount;
    }

    /** 设置 商家在交易中实际收到的款项,对应字段 trade_record_alipay_detail.receipt_amount */
    public void setReceiptAmount(BigDecimal receiptAmount) {
        this.receiptAmount = receiptAmount;
    }
    /** 获取 商家在交易中实际收到的款项,对应字段 trade_record_alipay_detail.receipt_amount */
    public BigDecimal getReceiptAmount() {
        return this.receiptAmount;
    }

    /** 设置 用户在交易中支付的金额,对应字段 trade_record_alipay_detail.buyer_pay_amount */
    public void setBuyerPayAmount(BigDecimal buyerPayAmount) {
        this.buyerPayAmount = buyerPayAmount;
    }
    /** 获取 用户在交易中支付的金额,对应字段 trade_record_alipay_detail.buyer_pay_amount */
    public BigDecimal getBuyerPayAmount() {
        return this.buyerPayAmount;
    }

    /** 设置 退款通知中,返回总退款金额,单位为元,支持两位小数,对应字段 trade_record_alipay_detail.refund_fee */
    public void setRefundFee(BigDecimal refundFee) {
        this.refundFee = refundFee;
    }
    /** 获取 退款通知中,返回总退款金额,单位为元,支持两位小数,对应字段 trade_record_alipay_detail.refund_fee */
    public BigDecimal getRefundFee() {
        return this.refundFee;
    }

    /** 设置 商品的标题/交易标题/订单标题/订单关键字等,对应字段 trade_record_alipay_detail.subject */
    public void setSubject(String subject) {
        this.subject = subject;
    }
    /** 获取 商品的标题/交易标题/订单标题/订单关键字等,对应字段 trade_record_alipay_detail.subject */
    public String getSubject() {
        return this.subject;
    }

    /** 设置 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来,对应字段 trade_record_alipay_detail.body */
    public void setBody(String body) {
        this.body = body;
    }
    /** 获取 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来,对应字段 trade_record_alipay_detail.body */
    public String getBody() {
        return this.body;
    }

    /** 设置 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_create */
    public void setGmtCreate(Date gmtCreate) {
        this.gmtCreate = gmtCreate;
    }
    /** 获取 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_create */
    public Date getGmtCreate() {
        return this.gmtCreate;
    }

    /** 设置 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_payment */
    public void setGmtPayment(Date gmtPayment) {
        this.gmtPayment = gmtPayment;
    }
    /** 获取 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_payment */
    public Date getGmtPayment() {
        return this.gmtPayment;
    }

    /** 设置 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S,对应字段 trade_record_alipay_detail.gmt_refund */
    public void setGmtRefund(Date gmtRefund) {
        this.gmtRefund = gmtRefund;
    }
    /** 获取 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S,对应字段 trade_record_alipay_detail.gmt_refund */
    public Date getGmtRefund() {
        return this.gmtRefund;
    }

    /** 设置 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_close */
    public void setGmtClose(Date gmtClose) {
        this.gmtClose = gmtClose;
    }
    /** 获取 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_close */
    public Date getGmtClose() {
        return this.gmtClose;
    }

    /** 设置 支付成功的各个渠道金额信息,array,对应字段 trade_record_alipay_detail.fund_bill_list */
    public void setFundBillList(String fundBillList) {
        this.fundBillList = fundBillList;
    }
    /** 获取 支付成功的各个渠道金额信息,array,对应字段 trade_record_alipay_detail.fund_bill_list */
    public String getFundBillList() {
        return this.fundBillList;
    }

    /** 设置 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。,对应字段 trade_record_alipay_detail.passback_params */
    public void setPassbackParams(String passbackParams) {
        this.passbackParams = passbackParams;
    }
    /** 获取 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。,对应字段 trade_record_alipay_detail.passback_params */
    public String getPassbackParams() {
        return this.passbackParams;
    }

}

原文

https ://blog.csdn.net/thc1987/article/details/80269181

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。 PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处: Harries Blog™ » 支付宝支付后回调处理(Java版)

作者:Harries Blog™
追心中的海,逐世界的梦
原文地址:支付宝支付后回调处理(Java版), 感谢原作者分享。

发表评论