支付宝支付(一)—H5手机网站支付2.0(alipay.trade.wap.pay)

  写这篇文章的初衷是因为最近项目中涉及到较多支付模块,于是打算从这篇文章开始,陆续整理一下支付宝和微信的支付模块。每篇文末会给出最新整理的支付demo,会随着文章进度不断更新,一方面是自己的总结过程,另一方面希望能帮助到更多像我这种小白的程序汪。其实支付宝的官方文档和demo也很详细,本文只是做简要总结。

一、Wap支付产品介绍

  这篇文章我们先来介绍一下支付宝Wap支付(也叫作手机网站支付),手机网站支付功能适用于商家在移动端网页应用中集成支付宝支付功能。 商家在网页中调用支付宝提供的网页支付接口调起支付宝客户端内的支付模块,商家网页会跳转到支付宝中完成支付,支付完后跳回到商家网页内,最后展示支付结果。

  注意:手机网站支付产品不建议在APP端使用;如果需要在APP端中使用支付,请接入APP支付产品,接入文档详见 APP支付开发文档。(之后我会介绍APP支付)

1、应用场景

(1)用户已安装支付宝支付流程

  步骤1:用户在浏览器中访问商家网页应用,选择商品下单、确认购买,进入支付环节,选择支付宝付款,用户点击去支付,如下图1;

  步骤2:进入到支付宝支付路由页面,支付宝处理支付请求,并尝试唤起支付宝客户端,如下图2(此页无法自定义删除);

  步骤3:进入到支付宝页面,调起支付宝支付,出现确认支付界面,如下图3;

  步骤4:用户确认收款方和金额,点击立即支付后出现输入密码界面,如下图4;

  步骤5:输入正确密码后,支付宝端显示支付结果,如下图5;

  步骤6:自动回跳到浏览器中,商家根据付款结果个性化展示订单处理结果,如下图6。

注意:

  在iOS系统中,唤起支付宝App支付完成后,不会自动回到浏览器或商户App。用户可手工切回到浏览器或商户App。

(2)用户未安装支付宝支付流程

  步骤1:若用户未安装支付宝客户端,用户可先点击支付宝支付路由页面里的 点这里下载支付宝APP 蓝色链接,下载支付宝,如图 7;

  步骤2:成功下载并安装支付宝客户端后,点击支付宝支付路由页面里的 使用支付宝APP付款 按钮进行付款,如图 8;

  步骤3:点击 使用支付宝APP付款 按钮后,重新开始如上所述的“用户已安装支付宝支付流程”步骤。

 注意事项:

  (1)支付的时候可以配置退出地址和同步支付成功地址,后面我会提到

  (2)现在新商户的wap支付已经取消了浏览器网页登录支付宝账号付款的功能,上面截图给出的就是新申请的商户支付截图,如果是老商户或者沙箱环境下,支付主页面会多一个“继续浏览器付款”的选项,至于为什么是“继续”,当然是因为wap支付是浏览器H5产品调用的,哈哈哈

  (3)不要在微信浏览器中调取支付宝WAP支付,虽然微信浏览器也是浏览器,但是我们都知道这两家是死对头的,所以别问我为什么不能

  (4)如果你没有正是环境的产品,可以设置沙箱环境,本人就是使用的沙箱环境

2、准入条件

  • 申请前必须拥有经过实名认证的支付宝账户;

  • 企业或个体工商户可申请;

  • 需提供真实有效的营业执照,且支付宝账户名称需与营业执照主体一致;

  • 网站能正常访问且页面显示完整,网站需要明确经营内容且有完整的商品信息;

  • 网站必须通过ICP备案。如为个体工商户,网站备案主体需要与支付宝账户主体名称一致;

  • 如为个体工商户,则团购不开放,且古玩、珠宝等奢侈品、投资类行业无法申请本产品。

3、计费模式

  • 费率按单笔计算;

  • 一般行业费率:0.6%;自2018年5月9日起,特殊行业新签约费率从1.2%调整为1%,特殊行业范围包括:休闲游戏;网络游戏点卡、渠道代理;游戏系统商;网游周边服务、交易平台;网游运营商(含网页游戏)。

4、使用说明

  手机网站支付产品包含的接口和描述如下:

接口英文名

接口中文名

描述
alipay.trade.wap.pay 手机网页支付接口 通过此接口传入订单参数,同时唤起支付宝手机网页支付页面
alipay.trade.close

交易关闭接口

通过此接口关闭此前已创建的交易,关闭后,用户将无法继续付款。仅能关闭创建后未支付的交易。
alipay.trade.query

交易状态查询接口

通过此接口查询某笔交易的状态,交易状态:交易创建,等待买家付款;未付款交易超时关闭,或支付完成后全额退款;交易支付成功;交易结束,不可退款。
alipay.trade.refund

交易退款接口

通过此接口对单笔交易进行退款操作。
alipay.trade.fastpay.refund.query

退款查询

查询退款订单的状态。
alipay.data.dataservice.bill.downloadurl.query

账单查询接口

调用此接口获取账单的下载链

  注意:其实除了alipay.trade.wap.pay 手机网站支付接口,其他接口对于其他产品都是通用的

 

二、准备工作

  先上官方文档入口,支付宝文档入口模仿微信最最近做了更新,变得更简洁明了。

  【官方手机网站支付快速接入文档

1、创建应用并获取APPID

  这个比较简单可以参考《开放平台应用创建指南》

  这个部分我就不详细说了,一般我们作为开发人员不需要自己去处理这些应用申请的事情,会有专门的人员申请号相关产品,给到申请完成的支付宝账号密码登录,然后我们自己配置开发环境就ok了。因为申请应用会涉及到营业执照、备案域名等等信息要求,比较耽误时间,而且琐碎,所以这个我们就不用自己操心了,做好开发就行了。

2、配置应用环境

  开发者调用接口前需要先生成RSA密钥,RSA密钥包含应用私钥(APP_PRIVATE_KEY)、应用公钥(APP_PUBLIC_KEY)。生成密钥后在开放平台管理中心进行密钥配置,配置完成后可以获取支付宝公钥(ALIPAY_PUBLIC_KEY)。详细步骤请参考《配置应用环境》

  不得不说支付宝简直是太贴心了,为了方便开发者生成一对RSA密钥支付宝提供一键生成工具,具体如何生成与配置密钥详见签名专区

WINDOWS
MAC_OSX

  下载该工具后,解压打开文件夹,运行“RSA签名验签工具.bat”(WINDOWS)或“RSA签名验签工具.command”(MAC_OSX)。

  以下演示截图

  步骤1:下载工具保存在本地并傻瓜式安装,注意安装目录不要有空格和中文,保持优秀的开发习惯

  步骤2:cmd命令运行或者点击桌面的快捷方式

   步骤3:设置密钥,将生成的“公钥字符串”粘贴保存设置,支付宝会生成对应的“支付宝公钥”,保存好你的公私钥信息,后面配置文件会用到。

注意事项:

  生成的私钥需妥善保管,避免遗失,不要泄露。应用私钥需填写到代码中供签名时使用。应用公钥需提供给支付宝账号管理者上传到支付宝开放平台。

3、配置沙箱环境

  注意:沙箱环境的密钥最好与正式上线的应用进行区分避免一些不必要的麻烦。WAP支付支持沙箱环境而app支付不支持沙箱环境

  【官方介绍质料-箱环境使用说明

   上传对应的公钥,沙箱账号待会在测试的时候回使用

 

 4、服务端实现(集成并且配置SDK)

  【官方资源下载地址

(1)引入 

关于sdk引入集成分别有以下两种方法

方法一:直接下载sdk包并修改pom.xml文件,引入sdk,步骤如下:

  步骤1:下载sdk包到本地

  步骤2:maven项目中使用本地jar包,首先我在项目根目录中创建一个lib文件夹,将jar包拷贝到lib文件夹下

   步骤3:然后我们在maven的pom.xml中配置

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>20161129201425</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/alipay-sdk-java20161129201425.jar</systemPath>
</dependency>
    

  注意:这里的groupId和artifactId以及version都是可以随便填写的 ,scope必须填写为system,而systemPath我们现在我们jar包的地址就可以了

  步骤4:我们必须在maven打包的过程中加入我们这个jar包。因为项目运行的时候需要这个Jar,并且我们得拷贝在WEB-INF/lib目录下。若不修改配置的话,打包不进去该sdk

<groupId>org.apache.maven.plugins</groupId>  
<artifactId>maven-war-plugin</artifactId>  
<configuration>  
    <webResources>  
        <resource>  
            <directory>${project.basedir}/lib</directory>  
            <targetPath>WEB-INF/lib</targetPath>  
            <filtering>false</filtering>  
            <includes>  
                <include>**/*.jar</include>  
            </includes>  
        </resource>  
    </webResources>  
</configuration>  
<version>2.1.1</version>  

方法二:直接maven的pom.xml引入中央仓库配置,过程如下截图

  步骤1:点击“maven项目依赖”

   步骤2:选择你想引入的包文件,本人选择的是“3.3.87.ALL”

   步骤3:复制maven配置到你的pom.xml中

 (2)集成

  在使用SDK调用具体API前,需要先配置通用接入参数
参数

  1、APP_ID 使用沙箱模式中的APP_ID.
  2、APP_PRIVATE_KEY ALIPAY_PUBLIC_KEY 使用你之前保存的“商户私钥”和支付宝给你生成的“支付宝公钥”
  3、CHARSET 默认使用UTF-8

  然后,使用上述接入参数初始化AlipayClient,这里只是描述过程,后面会告诉你具体怎么配置

AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",APP_ID,APP_PRIVATE_KEY,"json",CHARSET,ALIPAY_PUBLIC_KEY);

  接下来,就可以用alipayClient来调用具体的API了。

  注意:

  (1)alipayClient只需要初始化一次,后续调用不同的API都可以使用同一个alipayClient对象。

  (2)手机网站支付不支持第三方授权,不能代商家发起请求。

5、调用接口

  手机网站支付产品包含两类API:
页面跳转类:需要从前端页面以Form表单的形式发起请求,浏览器会自动跳转至支付宝的相关页面(一般是收银台或签约页面),用户在该页面完成相关业务操作后再回跳到商户指定页面。例如本产品中的手机网站支付接口alipay.trade.wap.pay

  系统调用类:直接从服务端发起HTTP请求,支付宝会同步返回请求结果。例如本产品中的交易查询等配套API。

6、调用流程图

 

三、服务端具体封装和操作过程

  对于页面跳转类API,SDK不会也无法像系统调用类API一样自动请求支付宝并获得结果,而是在接受request请求对象后,为开发者生成前台页面请求需要的完整form表单的html(包含自动提交脚本),商户直接将这个表单的String输出到http response中即可。

  哈哈,前面讲了那么多的准备工作,相信你对支付宝的wap支付流程已经有了一个初步的了解,现在我们来看一下具体的封装过程

1、将支付宝通用参数独立配置在一个属性文件中方便管理

   支付宝alipay.properties文件配置信息如下:

#==================================公司测试账户线下环境====================================
#应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
ali_app_id=*********
#商户私钥(应用私钥),您的PKCS8格式RSA2私钥
ali_merchant_private_key=*******
#商户公钥(应用公钥)
ali_merchant_public_key=*******
#支付宝公钥,对应APPID下的支付宝公钥。
ali_alipay_public_key=**********
#沙箱账号:uinrgn0730@sandbox.com   沙箱登录/支付密码:111111

#服务器异步通知页面路径
ali_notify_url=*******
#页面跳转同步通知页面路径
ali_return_url=******
#用户付款中途退出返回商户网站的地址
ali_quit_url=******

#签名方式
ali_sign_type=RSA2
#字符编码格式
ali_charset=utf-8
#支付宝网关(注意这是沙箱的网关,正式的网关为:https://openapi.alipay.com/gateway.do)
ali_gatewayUrl=https://openapi.alipaydev.com/gateway.do
#角色身份
ali_pid=2088621954885028
#过期时间
ali_timeout_express=30m

2、读取配置文件信息

   AliPayProperties.java文件的具体内容如下,与alipay.properties的属性信息一致,读取的就是配置文件的信息,封装成实体类,方便直接使用,后续若修改切换支付宝信息,只需要修改alipay.properties文件信息即可,例如切换沙箱环境为正式环境

package com.example.learn.config.properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:/alipay.properties")
public class AliPayProperties {

    /**
     * 应用ID
     */
    @Value("${ali_app_id}")
    private String ali_app_id;
    /**
     * 商户私钥
     */
    @Value("${ali_merchant_private_key}")
    private String ali_merchant_private_key;
    /**
     * 商户公钥
     */
    @Value("${ali_merchant_public_key}")
    private String ali_merchant_public_key;
    /**
     * 支付宝公钥
     */
    @Value("${ali_alipay_public_key}")
    private String ali_alipay_public_key;
    
    /**
     * 服务器异步通知页面路径
     */
    @Value("${ali_notify_url}")
    private String ali_notify_url;
    /**
     * 页面跳转同步通知页面路径
     */
    @Value("${ali_return_url}")
    private String ali_return_url;
    /**
     * 用户付款中途退出返回商户网站的地址
     */
    @Value("${ali_quit_url}")
    private String ali_quit_url;
    
    
    /**
     * 签名方式
     */
    @Value("${ali_sign_type}")
    private String ali_sign_type;
    /**
     * 字符编码格式
     */
    @Value("${ali_charset}")
    private String ali_charset;
    /**
     * 支付宝网关
     */
    @Value("${ali_gatewayUrl}")
    private String ali_gatewayUrl;
    /**
     * 角色身份
     */
    @Value("${ali_pid}")
    private String ali_pid;
    /**
     * 过期时间
     */
    @Value("${ali_timeout_express}")
    private String ali_timeout_express;
    public String getAli_app_id() {
        return ali_app_id;
    }
    public String getAli_merchant_private_key() {
        return ali_merchant_private_key;
    }
    public String getAli_merchant_public_key() {
        return ali_merchant_public_key;
    }
    public String getAli_alipay_public_key() {
        return ali_alipay_public_key;
    }
    public String getAli_notify_url() {
        return ali_notify_url;
    }
    public String getAli_return_url() {
        return ali_return_url;
    }
    public String getAli_quit_url() {
        return ali_quit_url;
    }
    public String getAli_sign_type() {
        return ali_sign_type;
    }
    public String getAli_charset() {
        return ali_charset;
    }
    public String getAli_gatewayUrl() {
        return ali_gatewayUrl;
    }
    public String getAli_pid() {
        return ali_pid;
    }
    public String getAli_timeout_express() {
        return ali_timeout_express;
    }
    public void setAli_app_id(String ali_app_id) {
        this.ali_app_id = ali_app_id;
    }
    public void setAli_merchant_private_key(String ali_merchant_private_key) {
        this.ali_merchant_private_key = ali_merchant_private_key;
    }
    public void setAli_merchant_public_key(String ali_merchant_public_key) {
        this.ali_merchant_public_key = ali_merchant_public_key;
    }
    public void setAli_alipay_public_key(String ali_alipay_public_key) {
        this.ali_alipay_public_key = ali_alipay_public_key;
    }
    public void setAli_notify_url(String ali_notify_url) {
        this.ali_notify_url = ali_notify_url;
    }
    public void setAli_return_url(String ali_return_url) {
        this.ali_return_url = ali_return_url;
    }
    public void setAli_quit_url(String ali_quit_url) {
        this.ali_quit_url = ali_quit_url;
    }
    public void setAli_sign_type(String ali_sign_type) {
        this.ali_sign_type = ali_sign_type;
    }
    public void setAli_charset(String ali_charset) {
        this.ali_charset = ali_charset;
    }
    public void setAli_gatewayUrl(String ali_gatewayUrl) {
        this.ali_gatewayUrl = ali_gatewayUrl;
    }
    public void setAli_pid(String ali_pid) {
        this.ali_pid = ali_pid;
    }
    public void setAli_timeout_express(String ali_timeout_express) {
        this.ali_timeout_express = ali_timeout_express;
    }
    @Override
    public String toString() {
        return "AliPayProperties [ali_app_id=" + ali_app_id + ", ali_merchant_private_key=" + ali_merchant_private_key
                + ", ali_merchant_public_key=" + ali_merchant_public_key + ", ali_alipay_public_key="
                + ali_alipay_public_key + ", ali_notify_url=" + ali_notify_url + ", ali_return_url=" + ali_return_url
                + ", ali_quit_url=" + ali_quit_url + ", ali_sign_type=" + ali_sign_type + ", ali_charset=" + ali_charset
                + ", ali_gatewayUrl=" + ali_gatewayUrl + ", ali_pid=" + ali_pid + ", ali_timeout_express="
                + ali_timeout_express + "]";
    }
    
}

3、加载接入参数并初始化AlipayClient

  AliPayUtil.java的实例化代码信息如下:

package com.example.learn.utils.pay.alipay;

/**
 * 支付宝工具类
 * @author Administrator
 *
 */
@Component
public class AliPayUtil {
    
    @Autowired
    private AliPayProperties aliPayProperties ; 

    private static AliPayProperties staticAliPayProperties;
    
    static AlipayClient alipayClient;
    
    /**
     * 初始化
     */
    @PostConstruct 
    public void init() {
        staticAliPayProperties = aliPayProperties;
        alipayClient = new DefaultAlipayClient(staticAliPayProperties.getAli_gatewayUrl(), staticAliPayProperties.getAli_app_id(), staticAliPayProperties.getAli_merchant_private_key(), "json", staticAliPayProperties.getAli_charset(), staticAliPayProperties.getAli_alipay_public_key(), staticAliPayProperties.getAli_sign_type());
    } 
}

注意:

  (1)alipayClient只需要初始化一次,后续调用不同的API都可以使用同一个alipayClient对象。

  (2)我为了后面在业务层中直接调取该静态API里面封装好的方法(后面会给出对应方法),将其定义为了一个工具类,而该工具类需要注入读取 AliPayProperties 里面的属性,所以需要在该类上打上 @Component 注解

  (3)我为了后面将其他支付API全部定义为静态方法直接调用,这里初始化的时候将 alipayClient 定义为了静态变量,所以需要将 AliPayProperties 也定义为静态变量,但是由于 @Autowired 注解不能作用于静态变量,所以借助静态变量 staticAliPayProperties 作为过渡,并在初始化的方法里面,让 staticAliPayProperties = aliPayProperties;

4、利用sdk封装wap支付代码

  通用模块,就在demo里面的 AliPayUtil.java 里面

  /**
     * alipay.trade.wap.pay:H5手机网站支付接口2.0(外部商户创建订单并支付)
     * @param out_trade_no:商户订单号
     * @param total_amount:支付金额,单位:元
     * @return
     */
    public static String alipayTradeWapPay(String out_trade_no, String total_amount){
        try {
            //(1)封装bizmodel信息
            AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
            //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
            model.setOutTradeNo(out_trade_no);
            model.setSubject("支付宝手机网站支付");
            model.setBody("支付宝手机网站支付");
            model.setProductCode("QUICK_WAP_WAY");
            model.setTotalAmount(total_amount);
            model.setTimeoutExpress(staticAliPayProperties.getAli_timeout_express());
            model.setQuitUrl(staticAliPayProperties.getAli_quit_url());
            //(2)设置请求参数
            AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
            alipayRequest.setReturnUrl(staticAliPayProperties.getAli_return_url());
            alipayRequest.setNotifyUrl(staticAliPayProperties.getAli_notify_url());
            alipayRequest.setBizModel(model);                                                                                                                                                               
            //(3)请求
            String form = alipayClient.pageExecute(alipayRequest).getBody();
            System.out.println("*********************\n返回结果为:"+form);
            return form;
        } catch (AlipayApiException e) {
            e.printStackTrace();
            return null;
        }
    }

5、模拟支付请求

  步骤1:控制层 AliPayController.java 写请求方法 alipayTradeWapPay

  步骤2:调取的接口和实现类

注意:

  (1)这里我随机生成了商户订单号 out_trade_no,交易金额 total_amount 写死了,正常业务处理中,我们是根据你的业务实际情况处理

  (2)里面涉及到的其他的工具类,最后的demo里面会提供。当然,你也可以自己随机商城,保持不重复的原则即可

  (3)实际操作中,在调取之前,我们需要处理具体的业务逻辑,因为本文只总结支付宝相关的,就不补充了。例如:订单金额计算处理;商户订单和支付宝订单的关联和存储,商户订单和支付宝订单可能是多对一的关系;支付宝订单需要保存,因为在回调的时候你需要根据支付宝订单的 out_trade_no 识别支付宝通知的订单结果;你可以把它存储在redis或者持久化到数据库中。

  步骤3:模拟支付请求

  这里,我是写了一个html页面,通过前端请求到后台调取控制层方法模拟实际支付请求的

  对于页面跳转类API,SDK不会也无法像系统调用类API一样自动请求支付宝并获得结果,而是在接受request请求对象后,为开发者生成前台页面请求需要的完整form表单的html(包含自动提交脚本),对于返回的该form表单,通常有两种方式处理,一种是后端,一种是前端

  前端:获取后端返回的form数据对象,取到表单对象并直接自动提交到支付宝,也就是上面和demo中的这种处理方式

  后端:商户直接将这个表单的String输出到http response中即可,处理方式可以如下(以下两种方式是我在项目过程中的处理,并不在此次demo中,仅供参考),处理差异和细节已经重点标出来了

  方法一:

  方法二:

   注意的是:这种处理方式的请求一般不是上面的json请求模式,而是直接get页面请求跳转的模式

6、运行查看测试结果

  右键运行demo中的 PayDemoApplication.java 文件,浏览器输入 localhost:8080查看,当然你也可以修改application.properties配置文件中的端口号

  点击“H5手机网站支付”按钮,其他按钮功能开发后面有时间会发博文介绍,不过demo里面已经有了,你也可以自己先测试,请求后控制台日志显示

   详细form内容如下:

<form name="punchout_form" method="post" action="https://openapi.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.wap.pay&sign=ctYtQCctKweZCxjDAn8D2y4VxnjAI6uu0SYtIaxkPxf2LskIT4fakZF5xciExeHJW5dbOdiujHltgjaTMK9ikxcdDpDiRhH78E8p83Vs6NQFpHjlBRDTf8dO24PC%2Be3DIbqR8VDImCeBcJKX5yDf4fiqdjoPRHZeb7LrktlmoXRk3TW%2BQbgKigwlCYGBwyBV4%2FTLVDo5piS6FJT1SjCiSZ3K02k7MzcKvFBTIJeiA9dDxyfLfh2SW14uVjU8ny6e7f5n62y0%2B5HcUsUrjN%2BLuVSaj6h76T0PvwVUhBW4gJGiL2n36MiaQTZUdhOrP1qHefzloRnxoeO%2Biwsl2SYCpQ%3D%3D&return_url=http%3A%2F%2Ftrt.wgzvip.com%2Fapi%2FaliReturnPay%2Frecharge%2FreturnPaySynchronizationRecharge&notify_url=http%3A%2F%2Ftrt.wgzvip.com%2Fapi%2FaliReturnPay%2Frecharge%2FreturnPayAsynchronousRecharge&version=1.0&app_id=2021000116673834&sign_type=RSA2&timestamp=2020-08-01+16%3A48%3A10&alipay_sdk=alipay-sdk-java-3.3.87.ALL&format=json">
<input type="hidden" name="biz_content" value="{&quot;body&quot;:&quot;支付宝手机网站支付&quot;,&quot;out_trade_no&quot;:&quot;20200801164810929688437&quot;,&quot;product_code&quot;:&quot;QUICK_WAP_WAY&quot;,&quot;quit_url&quot;:&quot;http:\/\/trt.wgzvip.com\/alipay\/aliPayQuitUrl&quot;,&quot;subject&quot;:&quot;支付宝手机网站支付&quot;,&quot;timeout_express&quot;:&quot;30m&quot;,&quot;total_amount&quot;:&quot;0.01&quot;}">
<input type="submit" value="立即支付" style="display:none" >
</form>
<script>document.forms[0].submit();</script>

  我们可以很清楚的看到,支付宝返回的是一个form表单的内容,我们在前端接收后按照上面前端的处理方法处理后,发现点击“H5手机网站支付”按钮之后,直接跳到了支付宝下面页面,按照以下步骤操作就行

 

注意事项:

  (1)由于我用的是沙箱环境,所以输入的是沙箱买家账号和密码测试的,出来的页面有“继续浏览器付款”和“使用支付宝APP付款”两个选项,如果选择支付宝APP付款需要在手机上安装沙箱钱包才行。现在新商户接入的正式环境调取页面是没有这个选项的,必须安装支付宝钱包的,具体区别在产品介绍模块有提到。页面如下:

   (2)在测试的时候我们发现如果返回不支付(退出、取消支付等)和支付完成之后,支付宝跳到了相关的页面,这个原因我们在后面同步异步回调模块会介绍到。

 

 四、同异步结果处理

  你以为在成功调取支付宝页面支付了,我们的操作就结束了么,远远没有,因为我们发现我们只是调取了支付宝的支付页面,关于支付响应和结果,也就是是否支付了,支付是否成功了,我们并不知道。聪明的支付宝早就为我们考虑到了,所以他为我们提供了三个参数来处理,我们回过头来看看配置文件和调取方法,分别如下:

 

1、quit_url:用户付款中途退出返回商户网站的地址,手机网站支付选填参数
  (1)设置要求:完整的路径名,携带http://或https://
  (2)参数说明:添加该参数后在 h5支付收银台会出现返回按钮,可用于用户付款中途退出并返回到该参数指定的商户网站地址。
  (3)注意:该参数对支付宝钱包标准收银台下的跳转不生效。2019年8月份后手机网站支付未安装支付宝钱包,默认提示下载钱包,不走H5页面支付。该参数设置无效。
2、return_url:页面跳转同步通知页面路径
  (1)设置要求:完整的路径名,携带http://或https://
  (2)参数说明:添加该参数后,支付宝会以get的方式向该地址发送通知,核实同步页面是否有跳转,手机网站支付接口如果同步页面没有进行跳转是不会发送对应的同步回调数据内容。
  (3)注意:不能以同步的跳转作为支付结果的判断,支付宝最终的支付结果必须以异步通知或者商户主动查询结果为主,如果同步没有跳转,参考以下网址排查:https://opensupport.alipay.com/support/knowledge/01/201602474937?ant_source=antsupport 
3、notify_url:服务器异步通知页面路径
  (1)设置要求:手机网站支付接口的参数notify_url必须是公网能post访问的接口地址;不建议加自定义参数,异步通知可通过请求参数设置passback_params公用回传参数,该参数异步通知返回。
  (2)参数说明:添加该参数之后,支付宝会以post方式发送支付结果通知,需要在异步地址页面使用post方式进行接收。
  (3)注意:如果没有收到支付宝的异步通知,参考以下网址排查:https://opensupport.alipay.com/support/knowledge/01/201602475759?ant_source=antsupport

1、同步回调通知

  步骤1:设计支付宝同步通知的controller入口,接收支付宝的通知

  注意:该controller的入口路径必须与你的配置文件里面的return_url保持一致,并且支持get接收

  步骤2:设计接口和实现类验签,做你想做的

  其中涉及到的工具类如下:

package com.example.learn.utils.string;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

public class RequestMap {

    /** 
     * 从request中获得参数Map,并返回可读的Map 
     *  
     * @param request 
     * @return 
     */  
    @SuppressWarnings({ "unchecked", "rawtypes" })  
    public static Map getParameterMap(HttpServletRequest request) {  
        // 参数Map  
        Map properties = request.getParameterMap();  
        // 返回值Map  
        Map returnMap = new HashMap();  
        Iterator entries = properties.entrySet().iterator();  
        Map.Entry entry;  
        String name = "";  
        String value = "";  
        while (entries.hasNext()) {  
            entry = (Map.Entry) entries.next();  
            name = (String) entry.getKey();  
            Object valueObj = entry.getValue();  
            if(null == valueObj){  
                value = "";  
            }else if(valueObj instanceof String[]){  
                String[] values = (String[])valueObj;  
                for(int i=0;i<values.length;i++){  
                    value = values[i] + ",";  
                }  
                value = value.substring(0, value.length()-1);  
            }else{  
                value = valueObj.toString();  
            }  
            returnMap.put(name, value);  
        }  
        return returnMap;  
    } 

}

  注意:在同步通知中,我们一般只需要验签,根据验签结果返回到具体页面,不做其他处理,千万不能以支付宝的同步通知结果作为支付结果判断成功与否。在我测试过程中,发现在沙箱环境下,同步通知一直显示验签失败,但是异步成功,验签方法都一样,当然不是我的问题了,问了阿里的技术,沙箱环境的bug,沙箱测试环境不稳定这个很正常。

  我么可以观察一下支付宝同步通知给了什么参数信息过来,信息如下:

{
  charset=utf-8,
  out_trade_no=20200801170240119363676,
  method=alipay.trade.wap.pay.return,
  total_amount=0.01,
  sign=FZol3JVgDcbgNtFIxCACmnTj8ASYllvTJK8cNi2a2sgj0Q7vZ4Z56fI0qnR7fP12p7GAlLldGdHrYI4lHE8dyxVc2Mp4hHNGs4NgzbQluocYhajSR7rejJxjLuF+rkGQiJd5c1ixyIXDUEXeIT3sOShs6m+139DNvUpcfk/YxY07uNZxKBsJfIS2kT/WFZk4hH3/QD3hHwv5Z0CTRHhMD23fTXHXK5h92/Ux02+QaQ2oXb/POV0gr9pS/otE/A9rDVUg7SyA41X4JEnuSibL8k7q9L8ZY5QJD/vMiQAvqjr+CDqSdJMsFqBv03m7K7aOjvaC+fRwZh2k3ucQ5q7zlg==,
  trade_no=2020080122001491140510105364,
  auth_app_id=2021000116673834,
  version=1.0,
  app_id=2021000116673834,
  sign_type=RSA2,
  seller_id=2088621954885028,
  timestamp=2020-08-01 17:02:57
}

2、异步回调通知

   步骤1:设计支付宝异步通知的controller入口,接收支付宝的通知

   收到结果之后,验签完毕之后需要及时向支付宝返回结果,否则支付宝收不到结果会连续通知,具体细节后面介绍

  步骤2:设计接口和实现类验签,做你想做的

  一般我们在接收到支付宝的异步通知之后,会先及时验签将结果立马返回给支付宝,如果支付宝没有及时收到反馈结果,他会认为你没有收到重复通知你,所以你不仅要及时通知反馈给他,还要异步处理你的业务逻辑结果,而不是让支付宝等待你处理完你的逻辑。

  我么可以观察一下支付宝异步通知给了什么参数信息过来,信息如下:

{
  gmt_create=2020-08-01 17:02:52,
  charset=utf-8,
  seller_email=knqmbr3206@sandbox.com,
  subject=支付宝手机网站支付,
  sign=YlFLi+efcQVJyEppUiTE3XT25MrP6GWnIORf4/CiodPgiFHpWt/mjXczFupkIgkBdrvUH4IzvtFDS4Wu2+sdpO2tiiNzQLqzwWuVlpuz6EeBabligpR/kDEFuHLYhG0Wmbg+/bZiUKrRP/RbMeRwXZqNMVd62On3vlg2xUacQto2CEZ9Jirw46bSJl1i0oXPWmpatzmx6bavnfGJ515Ubzjli53q/6D9cxTIy5ZYTuTrFWcHeXXhuaxNdOXpIgajm9im1NgvLIhhrak2uSsOttNObHF8v05yXo81SI7McMTgvPvTFG55y5mN6mBi0+wdIIh2AiHZTi0yQt1tWnSGWg==,
  body=支付宝手机网站支付, buyer_id=2088102181191145, invoice_amount=0.01, notify_id=2020080100222170255091140507330314, fund_bill_list=[{"amount":"0.01","fundChannel":"ALIPAYACCOUNT"}],
  notify_type=trade_status_sync,
  trade_status=TRADE_SUCCESS,
  receipt_amount=0.01,
  buyer_pay_amount=0.01,
  app_id=2021000116673834,
  sign_type=RSA2,
  seller_id=2088621954885028,
  gmt_payment=2020-08-01 17:02:54,
  notify_time=2020-08-01 17:02:55,
  version=1.0,
  out_trade_no=20200801170240119363676,
  total_amount=0.01,
  trade_no=2020080122001491140510105364,
  auth_app_id=2021000116673834,
  buyer_logon_id=uin***@sandbox.com,
  point_amount=0.00
}

   步骤3:异步根据支付宝的返回结果处理你的业务逻辑

   注意我注释的那段文字,描述了在处理的时候需要注意的地方

 

五、demo示例下载

  链接:https://pan.baidu.com/s/1Ru71zxFvlfj8l_GPpEZe8A 

  提取码:7rxc 

  注意:因为涉及到私密账号的原因,所以我注释掉了alipay.properties 文件中的部分重要配置信息,下载后需要根据你自己的账号信息修改配置

  最后说一点,这个demo里面不仅仅是该篇文章介绍的手机wap支付,有兴趣的小伙伴可以自己尝试一下,后面有时间我会继续更新博客,陆续介绍电脑网站支付、app支付、扫码支付和条码支付,包括介绍查询、退款、取消、关闭订单等。针对该文有疑问或者有更好建议的小伙伴可以留言。

 

  

 

posted @ 2020-08-02 13:21  沧海一粟hr  阅读(1731)  评论(2编辑  收藏