一、简介

最近在项目中有需要对接到支付宝手机移动端网站支付的功能,刚接到需求,就开始找支付宝官方文档,看了很多支付宝支付对接的文档,今天将自己的一些总结记录下来,方便以后回来复习和查看。

手机网站支付:手机网站支付功能适用于商家在移动端网页应用中集成支付宝支付功能。 商家在网页中调用支付宝提供的网页支付接口调起支付宝客户端内的支付模块,商家网页会跳转到支付宝中完成支付,支付完后跳回到商家网页内,最后展示支付结果。

官网对接文档的地址:https://opendocs.alipay.com/open/203/105288。

二、对接详细过程

下面将通过示例详细说明如何对接支付宝-手机网站支付的接口,注意其他支付方式的对接大同小异,只不过传的参数不同而已。

【a】搭建和配置开发环境

为了帮助开发者调用开放接口,支付宝官网提供了开放平台服务端SDK,包含JAVA、Python、PHP、NodeJS 和 .NET等语言版本,封装了签名&验签、HTTP接口请求等基础功能。为了快速接入并避免接入过程中的签名验签问题,请您先下载对应语言版本的SDK并引入您的开发工程。下载地址:https://opendocs.alipay.com/open/54/106682。

java支付宝支付demo java支付宝支付对接_支付宝

拷贝demo里面的支付相关jar,可以上传到自己的maven私服仓库,然后在pom.xml中进行引用。当然也可以直接引入阿里官网提供的maven依赖:如:

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.8.73.ALL</version>
</dependency>

扩展说明:

手机网站支付产品包含两类API:

  • 页面跳转类:需要从前端页面以Form表单的形式发起请求,浏览器会自动跳转至支付宝的相关页面(一般是收银台或签约页面),用户在该页面完成相关业务操作后再回跳到商户指定页面。例如本产品中的手机网站支付接口alipay.trade.wap.pay。
  • 系统调用类:直接从服务端发起HTTP请求,支付宝会同步返回请求结果。例如本产品中的交易查询等配套API。

注意:此处我们使用的是手机网站支付,属于页面跳转类API,所以我们只是请求支付宝生成支付表单的HTML,然后我们进行展示而已。

支付宝调用大体流程如下图:

java支付宝支付demo java支付宝支付对接_手机网站支付_02

更多接口调用使用说明:https://opendocs.alipay.com/open/203/105285

支付接口请求参数说明:

https://opendocs.alipay.com/open/203/107090

【b】生成秘钥

支付宝开放平台开发助手提供了一键生成密钥功能,便于开发者生成一对 RSA 密钥(应用公钥、应用私钥)以及公钥证书申请 CSR 文件(在线申请应用公钥证书需要)。

加密过程:使用公钥(public key)为系统进行加密,并将密文发送给解密者,解密者使用私钥(private key)解密将密文解码为明文。公钥证书模式中上传的文件,无论是 CSR 文件或者开发者自己申请的公钥证书文件,必须和用户本地代码中加密的应用私钥是匹配的,否则会导致支付宝开放平台验签失败。

说明:

  • 应用公钥(public key)需提供给支付宝账号管理者上传到支付宝开放平台。
  • 应用私钥(private key)由开发者自己保存,需填写到代码中供签名时使用。
  • 生成的私钥需妥善保管,避免遗失,不要泄露。
  • 密钥和应用(APPID)一一对应,即开发者需要为名下的每个应用分别设置密钥,且不同应用的密钥不能混用。

注:生成秘钥工具下载地址:https://opendocs.alipay.com/open/291/105971

下载完成后,点击生成秘钥,支付宝会为我们自动生成商户应用公钥(public key)和应用私钥(private key),如下图所示:

 

java支付宝支付demo java支付宝支付对接_H5支付宝支付_03

如何生成RSA,RSA2密钥?可参考:https://openclub.alipay.com/club/history/read/1833

【c】配置沙箱环境

沙箱应用地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info

如何接入沙箱?

沙箱环境是开放平台提供给开发者调试接口的环境,具体操作步骤见沙箱接入指南。

目前支付宝沙箱环境支持手机网站支付,并且在安装了沙箱支付宝钱包后,可以唤起沙箱支付宝钱包进行支付。 

手机网站支付沙箱接入注意点:

  1. 手机网站支付支持沙箱接入;在沙箱调通接口后,必须在线上进行测试与验收,所有返回码及业务逻辑以线上为准;
  2. 手机网站支付只支持余额支付,不支持银行卡、余额宝等其他支付方式;
  3. 支付时,请使用沙箱买家账号支付,在登录支付宝,输入手机号的页面,点击右下角支付宝账户登录;

(一)、沙箱环境密钥设置:

 

java支付宝支付demo java支付宝支付对接_支付宝支付_04

java支付宝支付demo java支付宝支付对接_手机网站支付_05

 注意:支付宝支付核心需要的参数是(APPID,PRIVATE_KEY,ALIPAY_PUBLIC_KEY)

  1. APPID:创建应用后就有的APPID。
  2. PRIVATE_KEY:应用私钥
  3. ALIPAY_PUBLIC_KEY:支付宝公钥
  • 上面的2,3的参数得自己弄到,参考文档:https://docs.open.alipay.com/291/105971/
  • 获取到的应用公钥配置到:蚂蚁金服开放平台中在 “应用信息” - “开发设置” - “加签方式”处点击 “设置应用公钥”。获取到的应用私钥就是:PRIVATE_KEY。支付宝公钥ALIPAY_PUBLIC_KEY:当设置应用公钥完成后就可以查看支付宝公钥内容。)

(二)、编写代码时,需要注意一下几点:

  • 请求网关修改为:https://openapi.alipaydev.com/gateway.do;
  • appid 切换为沙箱的 appid;
  • 签名方式使用 RSA2;
  • 应用私钥(private_key)使用秘钥工具生成的 RSA2 (SHA256) 的私钥(请根据开发语言进行选择原始或 pkcs8 格式);
  • 支付宝公钥(public_key)切换为第 1 步配置后应用公钥后,点击查看支付宝公钥看到的公钥;

java支付宝支付demo java支付宝支付对接_java支付宝支付demo_06

 

更多沙箱环境使用说明可见:https://opendocs.alipay.com/open/200/105311.

【d】请求支付宝跳转支付页面

为了后期方便维护,将下列参数都配置成了系统参数方式:

java支付宝支付demo java支付宝支付对接_支付宝支付_07

【e】编写后端接口:主要调用SDK进行签名、封装请求支付信息,支付宝返回支付页面表单的HTML。 

/**
     * 跳转支付宝手机网页支付页面
     *
     * @param params 支付相关参数
     * @desc 说明:<br/>
     * 1. 需要事先配置好支付宝相关参数,如APPID、privateKey私钥、publicKey支付宝公钥、gatewayUrl网关地址等;
     * <p>
     * 2. 开发时使用沙箱环境进行测试,应用上线后替换APPID、privateKey私钥、publicKey支付宝公钥、gatewayUrl网关地址这些参数;
     * <p>
     * 3. 对于页面跳转类API,SDK不会也无法像系统调用类API一样自动请求支付宝并获得结果,而是在接受request请求对象后,为开发者生成前台页面请求需要的完整form表单的html(包含自动提交脚本),商户直接将这个表单的String输出到http response中即可;
     * <p>
     * 4. notify_url参数解析: 服务器后台通知,这个页面是程序后台运行的(买家和卖家都看不到),买家付完款后,支付宝会调用notify_url这个页面所在的页面并把相应的参数传递到这个页面,这个页面根据支付宝传递过来的参数修改网站订单的状态,
     * 更新完订单后需要在页面上打印出一个success给支付宝,如果反馈给支付宝的不是success,支付宝会继续调用这个页面.
     * 使用参考地址:https://opensupport.alipay.com/support/helpcenter/193/201602472200
     * <p>
     * 5. return_url参数解析: 买家付款成功后,如果接口中指定有return_url ,买家付完款后会跳到 return_url所在的页面,这个页面可以展示给客户看,这个页面只有付款成功才会跳转。
     * 同步跳转地址,支付接口的公共请求参数;使用参考地址:https://opensupport.alipay.com/support/helpcenter/193/201602494660
     */
    @Override
    public CommonResult toAliPay(Map<String, Object> params) {
        try {
            List<Map<String, Object>> payParamsList = openapiMapper.getPayParamsList();
            if (CollectionUtils.isEmpty(payParamsList)) {
                logger.error("请先在系统管理中配置支付宝相关参数!");
                return new CommonResult("", "请先在系统管理中配置支付宝相关参数!", "0");
            }

            //1.查询系统管理中设置的支付宝-商户appid
            String aliPayAppId = this.getParamValue(payParamsList, "Alipay-APPID");
            if (StringUtils.isBlank(aliPayAppId)) {
                logger.error("请先在系统管理中设置支付宝应用商店APPID!");
                return new CommonResult("", "请先在系统管理中设置支付宝应用商店APPID!", "0");
            }

            //2.查询系统管理中设置的支付宝-私钥(pkcs8格式)
            String aliPayPrivateKey = this.getParamValue(payParamsList, "Alipay-PRIVATE_KEY");
            if (StringUtils.isBlank(aliPayPrivateKey)) {
                logger.error("请先在系统管理中设置支付宝商户私钥!");
                return new CommonResult("", "请先在系统管理中设置支付宝商户私钥!", "0");
            }

            //3.查询系统管理中设置的支付宝-服务器异步通知页面路径(需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问)
            String aliPayNotifyUrl = this.getParamValue(payParamsList, "Alipay-NOTIFY_URL");
            if (StringUtils.isBlank(aliPayNotifyUrl)) {
                logger.error("请先在系统管理中设置支付宝-服务器异步通知页面路径!");
                return new CommonResult("", "请先在系统管理中设置支付宝-服务器异步通知页面路径!", "0");
            }

            //4.查询系统管理中设置的支付宝-页面跳转同步通知页面路径(需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址)
            String aliPayReturnUrl = this.getParamValue(payParamsList, "Alipay-RETURN_URL");
            if (StringUtils.isBlank(aliPayReturnUrl)) {
                logger.error("请先在系统管理中设置支付宝-页面跳转同步通知页面路径!");
                return new CommonResult("", "请先在系统管理中设置支付宝-页面跳转同步通知页面路径!", "0");
            }

            //5.查询系统管理中设置的支付宝-请求网关地址
            String aliPayGatewayUrl = this.getParamValue(payParamsList, "Alipay-GATEWAY_URL");
            if (StringUtils.isBlank(aliPayGatewayUrl)) {
                logger.error("请先在系统管理中设置支付宝-请求网关地址!");
                return new CommonResult("", "请先在系统管理中设置支付宝-请求网关地址!", "0");
            }

            //6.查询系统管理中设置的支付宝-支付宝公钥
            String aliPayPublicKey = this.getParamValue(payParamsList, "Alipay-PUBLIC_KEY");
            if (StringUtils.isBlank(aliPayPublicKey)) {
                logger.error("请先在系统管理中设置支付宝公钥!");
                return new CommonResult("", "请先在系统管理中设置支付宝公钥!", "0");
            }

            //7.查询系统管理中设置的支付宝-收款支付宝账号对应的支付宝唯一用户号
            String aliPaySellerId = this.getParamValue(payParamsList, "Alipay-SELLER_ID");
            if (StringUtils.isBlank(aliPaySellerId)) {
                logger.error("请先在系统管理中设置收款支付宝账号对应的支付宝唯一用户号!");
                return new CommonResult("", "请先在系统管理中设置收款支付宝账号对应的支付宝唯一用户号!", "0");
            }

            //订单ID
            String orderPkid = null != params.get("orderPkid") ? params.get("orderPkid").toString() : "";
            //订单名称
            String orderName = null != params.get("orderName") ? params.get("orderName").toString() : "";

            //8. 根据订单ID查询是否存在未支付的订单信息
            PayOrderDTO payOrderDTO = payOrderPOMapper.getOrderInfoByPkid(orderPkid, "1");
            if (null == payOrderDTO) {
                logger.error("抱歉,暂未查询到未支付的订单信息!");
                return new CommonResult("", "抱歉,暂未查询到未支付的订单信息!", "0");
            }

            //9. 查询系统管理设置的可用渠道、禁用渠道
            List<Map<String, Object>> payChannels = openapiMapper.getPayChannels();
            //支付启用渠道
            String enablePayChannel = this.getParamValue(payChannels, "ENABLED_ALIPAY_PAY_CHANNELS");
            //支付禁用渠道
            String disablePayChannel = this.getParamValue(payChannels, "DISABLED_ALIPAY_PAY_CHANNELS");

            Map<String, Object> payParams = new HashMap<>(100);
            payParams.put("orderNumber", payOrderDTO.getDdh());
            payParams.put("orderName", orderName);
            payParams.put("totalAmount", formatStringToBigDecimal(payOrderDTO.getJyje().toString()).toString());
            String aliPayFormHtml = sendAliPayRequest(aliPayAppId, aliPayPrivateKey, aliPayPublicKey, aliPayGatewayUrl, aliPayNotifyUrl, aliPayReturnUrl, aliPaySellerId, enablePayChannel, disablePayChannel, payParams);
            if (StringUtils.isNotBlank(aliPayFormHtml)) {
                return new CommonResult(aliPayFormHtml, "请求支付宝成功,正在打开支付页面!", "1");
            } else {
                logger.error("请求支付宝支付失败,请稍后重试!");
                return new CommonResult("", "请求支付宝支付失败,请稍后重试!", "0");
            }
        } catch (Exception e) {
            e.printStackTrace();
            NHExpHandleUtils.throwesException(e);
        }
        logger.error("抱歉,请求支付宝支付失败,请稍后重试!");
        return new CommonResult("", "抱歉,请求支付宝支付失败,请稍后重试!", "0");
    }

/**
     * 统一发送支付请求支付宝,生成支付页HTML
     *
     * @param aliPayAppId        商户APP_ID
     * @param aliPayPrivateKey   商户私钥
     * @param aliPayPublicKey    支付宝公钥
     * @param aliPayGatewayUrl   支付宝网关地址
     * @param aliPayNotifyUrl    支付宝异步通知URL
     * @param aliPayReturnUrl    支付宝同步通知URL
     * @param aliPaySellerId     支付宝唯一用户号
     * @param enablePayChannels  启用支付渠道
     * @param disablePayChannels 禁用支付渠道
     * @param params             业务参数
     * @return 支付宝支付表单HTML
     */
    public static String sendAliPayRequest(String aliPayAppId, String aliPayPrivateKey, String aliPayPublicKey, String aliPayGatewayUrl, String aliPayNotifyUrl,
                                           String aliPayReturnUrl, String aliPaySellerId, String enablePayChannels, String disablePayChannels, Map<String, Object> params) {
        try {
            //调用RSA签名方式
            AlipayClient client = new DefaultAlipayClient(
                    aliPayGatewayUrl,  //支付网关URL
                    aliPayAppId, //商户ID
                    aliPayPrivateKey,  //商户私钥
                    RESULT_TYPE_FORMAT,  //返回结果格式(json)
                    CHARSET, //编码(UTF-8)
                    aliPayPublicKey, //支付宝公钥
                    SIGNTYPE); //签名方式(RSA2)
            // 封装请求支付信息
            AlipayTradeWapPayRequest aliPayRequest = new AlipayTradeWapPayRequest();
            AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
            //商户订单号,商户网站订单系统中唯一订单号,必填
            String orderNumber = null != params.get("orderNumber") ? params.get("orderNumber").toString() : "";
            model.setOutTradeNo(orderNumber);
            //订单名称,必填
            String orderName = null != params.get("orderName") ? params.get("orderName").toString() : "";
            model.setSubject(orderName);
            //付款金额,必填
            String totalAmount = null != params.get("totalAmount") ? params.get("totalAmount").toString() : "";
            model.setTotalAmount(totalAmount);
            //商品描述,可空
            model.setBody("支付宝H5支付");
            //收款支付宝账号对应的支付宝唯一用户号
            model.setSellerId(aliPaySellerId);
            // 该笔订单允许的最晚付款时间,逾期将关闭交易。超时时间,可空, 取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。
            String timeoutExpress = "90m";
            model.setTimeoutExpress(timeoutExpress);
            // 销售产品码 必填
            String productCode = "QUICK_WAP_WAY";
            model.setProductCode(productCode);
            //设置支付渠道,参考文档https://opendocs.alipay.com/open/common/wifww
            //可用渠道,用户只能在指定渠道范围内支付当有多个渠道时用“,”分隔注:与disable_pay_channels互斥
            if (StringUtils.isNotBlank(enablePayChannels)) {
                model.setEnablePayChannels(enablePayChannels);
            }
            //禁用渠道,用户不可用指定渠道支付当有多个渠道时用“,”分隔注:与enable_pay_channels互斥
            if (StringUtils.isNotBlank(disablePayChannels)) {
                model.setDisablePayChannels(disablePayChannels);
            }
            aliPayRequest.setBizModel(model);
            // 设置异步通知地址
            aliPayRequest.setNotifyUrl(aliPayNotifyUrl);
            // 设置同步通知地址
            aliPayRequest.setReturnUrl(aliPayReturnUrl);
            //调用SDK生成form表单
            return client.pageExecute(aliPayRequest).getBody();
        } catch (Exception e) {
            e.printStackTrace();
            NHExpHandleUtils.throwesException(e);
        }
        return null;
    }

private String getParamValue(List<Map<String, Object>> payParamsMapList, String csbz) {
        String paramsValue = "";
        for (Map<String, Object> map : payParamsMapList) {
            String currentCsbz = null != map.get("CSBZ") ? map.get("CSBZ").toString() : "";
            String currentCsz = null != map.get("CSZ") ? map.get("CSZ").toString() : "";
            if (currentCsbz.equals(csbz)) {
                paramsValue = currentCsz;
                break;
            }
        }
        return paramsValue;
    }

 前端: 

 

document.write(data.data); //data.data就是后端接口返回的HTML

【f】编写异步通知接口

对于手机网站支付产生的交易,支付宝会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统。

  • notify_url:服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问。

注意:开发环境下,如果接口不能使用外网访问,可以使用utools的内网穿透功能,让你的接口可以通过外网访问。异步验证从request中取出了支付宝的请求参数,然后封装为map,支付宝回每隔一段时间就请求一次这个接口,直到返回success字符串才停止请求。

 

java支付宝支付demo java支付宝支付对接_H5支付宝支付_08

服务器后台通知,这个页面是程序后台运行的(买家和卖家都看不到),买家付完款后,支付宝会调用notify_url这个页面所在的页面并把相应的参数传递到这个页面,这个页面根据支付宝传递过来的参数修改网站订单的状态,更新完订单后需要在页面上打印出一个success给支付宝,如果反馈给支付宝的不是success,支付宝会继续调用这个页面。

/**
     * 支付宝支付异步通知接口
     *
     * @param request  请求对象
     * @param response 响应对象
     * @desc 说明:<br/>
     * 1. 接口必须返回"success"或者"fail"
     * 2. 验签传入支付宝公钥,不是应用公钥
     */
    @Override
    @Transactional
    public String aliPayAsyncNotification(HttpServletRequest request, HttpServletResponse response) {
        try {
            //将异步通知中收到的所有参数都存放到map中
            Map<String, String> params = new HashMap<>(200);
            Map<String, String[]> requestParams = request.getParameterMap();
            for (Object object : requestParams.keySet()) {
                String name = (String) object;
                String[] values = 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);
            }

            // 商户订单号@支付商户PKID
            String outTradeNoAndZfshPkid = formatString(request.getParameter("out_trade_no"));
            if (StringUtils.isBlank(outTradeNoAndZfshPkid)) {
                return "fail";
            }
            String[] outTradeNoAndZfshPkidArray = outTradeNoAndZfshPkid.split("@");
            if (null == outTradeNoAndZfshPkidArray || outTradeNoAndZfshPkidArray.length < 2) {
                return "fail";
            }
            //支付商户表主键ID
            String zfshPkid = outTradeNoAndZfshPkidArray[1];
            PayOrderPO payOrderPO = payOrderPOMapper.getPayOrderDetailByTradeNo(outTradeNoAndZfshPkid);
            if (null == payOrderPO) {
                //订单不存在,返回success,反馈支付宝不需要继续异步通知
                return "success";
            }
            logger.info("订单详情: PayOrderPO : {}", payOrderPO.toString());

            //记录通知日志
            ZfglCsbPO zfglCsbPO = new ZfglCsbPO();
            zfglCsbPO.setPkid(UUID.randomUUID().toString());
            zfglCsbPO.setCslx("2");
            zfglCsbPO.setDdid(payOrderPO.getPkid());
            zfglCsbPO.setTzcs(JSON.toJSONString(params));
            zfglCsbPO.setCjsj(new Date());
            zfglCsbPOMapper.insert(zfglCsbPO);

            //根据支付商户PKID查询相关设置信息
            ZfglZfshbVO zfshInfo = openapiMapper.getZfshInfo(zfshPkid);
            if (null == zfshInfo) {
                return "fail";
            }

            //查询系统管理设置的支付宝公钥、支付宝唯一用户号
            String aliPayPublicKey = zfshInfo.getGyxx();
            String aliPaySellerId = zfshInfo.getShwyyhh();
            if (StringUtils.isBlank(aliPayPublicKey) || StringUtils.isBlank(aliPaySellerId)) {
                return "fail";
            }

            logger.info("【支付宝支付异步通知】验签参数: params: {}, public_key: {}", params, aliPayPublicKey);
            //根据支付宝公钥进行验证签名, 计算得出通知验证结果.  注意:此处是传入支付宝公钥,不是应用公钥,否则验签失败
            boolean verifyResult = AlipaySignature.rsaCheckV1(params, aliPayPublicKey, CHARSET, SIGNTYPE);
            logger.info("【支付宝支付异步通知】验签结果: " + verifyResult);

            //验证成功,验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
            if (verifyResult) {
                //获取支付宝的通知返回参数
                // 支付宝交易号
                String tradeNo = formatString(request.getParameter("trade_no"));
                // 交易状态
                String tradeStatus = formatString(request.getParameter("trade_status"));
                // 付款金额
                String totalAmount = formatString(request.getParameter("total_amount"));
                //交易人
                String sellerId = formatString(request.getParameter("seller_id"));
                logger.info("【支付宝支付成功异步通知结果】 商户订单号: {}, 实付金额: {}, 订单状态: {}, 交易人: {}, 支付宝交易号: {}", outTradeNoAndZfshPkid, totalAmount, tradeStatus, sellerId, tradeNo);

                //交易支付成功,可退款
                if ("TRADE_SUCCESS".equals(tradeStatus)) {
                    //根据商户订单号查询到订单详情信息
                    BigDecimal actualAmount = new BigDecimal(StringUtils.isBlank(totalAmount) ? "0" : totalAmount);

                    //交易金额
                    BigDecimal jyje = payOrderPO.getJyje();
                    //判断订单详情里面的交易人与交易金额是否与支付宝通知过来的交易人、交易金额相同
                    if (formatStringToBigDecimal(jyje.toString()).toString().equals(actualAmount.toString()) && sellerId.equals(aliPaySellerId)) {
                        //根据订单ID查询出对应的订单明细
                        //进行具体的业务逻辑

                    }
                }
                return "success";
            } else {//验证失败
                logger.error("【支付宝支付异步通知结果】验签失败!");
                return "fail";
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("【支付宝异步通知出现异常】, 异常信息: {}", e.getMessage());
            NHExpHandleUtils.throwesException(e);
        }
        return "fail";
    }

如果遇到收不到异步通知,可参考https://opensupport.alipay.com/support/helpcenter/193/201602475759?ant_source=antsupport进行排查。

更多异步通知介绍可参考:

https://opensupport.alipay.com/support/helpcenter/193/201602472200

https://opendocs.alipay.com/open/203/105286

【f】编写同步通知接口

  • return_url:可以是接口路径或者是前端页面的URL。

买家付款成功后,如果接口中指定有return_url ,买家付完款后会跳到 return_url所在的页面,这个页面可以展示给客户看,这个页面只有付款成功才会跳转。

/**
    * 支付宝支付同步通知接口
    *
    * @param request  请求对象
    * @param response 响应对象
    * @desc 说明:<br/>
    * 1. 验签传入支付宝公钥,不是应用公钥
    * 2. 根据支付宝get提交过来的订单号、支付宝交易号,查询该订单状态,如果已支付,修改订单状态为已支付.
    */
   @Override
   @Transactional
   public CommonResult aliPaySyncNotification(HttpServletRequest request, HttpServletResponse response) {
      try {
         //获取支付宝GET过来反馈信息
         Map<String, String> params = new HashMap<>(100);
         Map requestParams = request.getParameterMap();
         for (Object object : requestParams.keySet()) {
            String name = (String) object;
            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 = formatString(valueStr);
            params.put(name, valueStr);
         }

         List<Map<String, Object>> payParamsList = openapiMapper.getPayParamsList();
         if (CollectionUtils.isEmpty(payParamsList)) {
            logger.error("请先在系统管理中配置支付宝相关参数!");
            return new CommonResult("", "请先在系统管理中配置支付宝相关参数!", "0");
         }

         //查询系统管理设置的支付宝公钥
         String aliPayAppId = this.getParamValue(payParamsList, "Alipay-APPID");
         if (StringUtils.isBlank(aliPayAppId)) {
            logger.error("请先在系统管理中设置支付宝应用商店APPID!");
            return new CommonResult("", "请先在系统管理中设置支付宝应用商店APPID!", "0");
         }

         String aliPayPrivateKey = this.getParamValue(payParamsList, "Alipay-PRIVATE_KEY");
         if (StringUtils.isBlank(aliPayPrivateKey)) {
            logger.error("请先在系统管理中设置支付宝商户私钥!");
            return new CommonResult("", "请先在系统管理中设置支付宝商户私钥!", "0");
         }

         String aliPayPublicKey = this.getParamValue(payParamsList, "Alipay-PUBLIC_KEY");
         if (StringUtils.isBlank(aliPayPublicKey)) {
            logger.error("请先在系统管理中设置支付宝公钥!");
            return new CommonResult("", "请先在系统管理中设置支付宝公钥!", "0");
         }

         String aliPayGatewayUrl = this.getParamValue(payParamsList, "Alipay-GATEWAY_URL");
         if (StringUtils.isBlank(aliPayGatewayUrl)) {
            logger.error("请先在系统管理中设置支付宝-请求网关地址!");
            return new CommonResult("", "请先在系统管理中设置支付宝-请求网关地址!", "0");
         }

         logger.info("【支付宝支付同步通知结果】验签参数: params: {}, public_key: {}", params, aliPayPublicKey);
         //计算得出通知验证结果
         boolean verifyResult = AlipaySignature.rsaCheckV1(params, aliPayPublicKey, CHARSET, SIGNTYPE);
         logger.info("【支付宝支付同步通知结果】验签结果: verifyResult : {}", verifyResult);
         //验证成功
         if (verifyResult) {
            logger.info("【支付宝支付同步通知结果】验签通过!");
            //获取支付宝的通知返回参数
            //商户订单号
            String outTradeNo = formatString(request.getParameter("out_trade_no"));
            //支付宝交易号
            String tradeNo = formatString(request.getParameter("trade_no"));
            //获得初始化的AlipayClient
            AlipayClient alipayClient = new DefaultAlipayClient(
                  aliPayGatewayUrl,
                  aliPayAppId,
                  aliPayPrivateKey,
                  RESULT_TYPE_FORMAT,
                  CHARSET,
                  aliPayPublicKey,
                  SIGNTYPE);
            //创建API对应的request类
            AlipayTradeQueryRequest alipayTradeQueryRequest = new AlipayTradeQueryRequest();
            //设置业务参数
            Map<String, Object> bizContentParams = new HashMap<>(100);
            bizContentParams.put("out_trade_no", outTradeNo);
            bizContentParams.put("trade_no", tradeNo);
            alipayTradeQueryRequest.setBizContent(JSON.toJSONString(bizContentParams));

            //设置业务参数
//          alipayTradeQueryRequest.setBizContent("{" +
//                " \"out_trade_no\":\"" + outTradeNo + "\"," +
//                " \"trade_no\":\"" + tradeNo + "\"" +
//                " }");

            //通过alipayClient调用API,获得对应的response类
            AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.execute(alipayTradeQueryRequest);
            String aliPayTradeQueryResponseBody = alipayTradeQueryResponse.getBody();
            logger.info("【支付宝查询订单】订单信息: {}", aliPayTradeQueryResponseBody);

            //根据response中的结果继续业务逻辑处理

         } else {
            logger.error("【支付宝支付同步通知结果】验签失败!");
            return new CommonResult("", "【支付宝支付同步通知结果】验签失败!", "0");
         }
      } catch (Exception e) {
         e.printStackTrace();
         NHExpHandleUtils.throwesException(e);
      }
      logger.error("支付失败,请稍后重试!");
      return new CommonResult("", "支付失败,请稍后重试!", "0");
   }

同步验签失败您可参考该文档进行排查:https://opensupport.alipay.com/support/helpcenter/192/201602487655?ant_source=zsearch 

更多同步通知介绍可参考:https://opensupport.alipay.com/support/helpcenter/193/201602494660

【g】测试

 

java支付宝支付demo java支付宝支付对接_支付宝支付_09

点击确认支付,将会调整到支付宝手机网站支付页面,我们选择继续浏览器支付:

java支付宝支付demo java支付宝支付对接_支付宝_10

 

java支付宝支付demo java支付宝支付对接_支付宝_11

java支付宝支付demo java支付宝支付对接_手机网站支付_12

 

 

下面我们演示使用沙箱版支付钱包APP,下载地址:https://openhome.alipay.com/platform/appDaily.htm?tab=tool

选择支付宝APP进行支付:

java支付宝支付demo java支付宝支付对接_支付宝支付_13

 

java支付宝支付demo java支付宝支付对接_手机网站支付_14

后台日志如下所示: 

java支付宝支付demo java支付宝支付对接_支付宝_15

java支付宝支付demo java支付宝支付对接_支付宝支付_16

支付完成之后,登录沙箱中心查看沙箱账户中的余额是否都正常变动: 

java支付宝支付demo java支付宝支付对接_java支付宝支付demo_17

三、总结

以上就是关于对接支付宝-手机网站支付的详细过程,关键的步骤就是配置app_id、private_key商户私钥、public_key支付宝公钥、gateway_url网关地址。

注意:以上只是沙箱测试环境,正式环境的秘钥、网关地址都不一样,使用沙箱测通之后,建议使用正式环境继续测试,保证上线后出现最少的问题。