支付宝支付

一、准备

1、注册支付宝开发者、配置沙箱环境

1.1、配置沙箱账号

image-20220502101719227

1.2、配置沙箱应用

image-20220502101621042
配置公钥私钥需要下载支付宝开发者工具生成应用公钥,再去开发者平台将环境绑定
image-20220502102015162

1.3、手机下载沙箱版支付宝

image-20220502101810556

2、公网地址准备

在用户扫码支付成功后,支付平台会调用回调地址,将支付结果返回商家系统。所以必须保证你的配的的回调地址,在公网可以访问的

常用的技术:内网穿透(可以使用花生壳、sunny-ngrok等工具)

二、支付流程

在这里插入图片描述

三、spring boot 集成

3.1、依赖导入

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

3.2、配置文件绑定

3.2.1、application.yml配置
# 支付宝支付参数配置
alipay:
  # 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
  app_id: 207697
  # 商户私钥,您的PKCS8格式RSA2私钥
  merchant_private_key: MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCV5x7VamtaP1knZTtJ/5taEO9+14iy8YN9JcQ==
  # 支付宝公钥
  alipay_public_key: MIIBIjrOPjpcldED2XbIV1jZYqLZn8Y9/PBc8WKARlctmsuXqiBYzOb6hXweK9DwIDAQAB
  # 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
  notify_url: http://05h0g893.dongtaiyuming.net/notify_url
  # 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
  # return_url: https://api.itbooking.net/admin/alipay/returnUrl
  return_url: http://05h0g893.dongtaiyuming.net/return_url
  # 签名方式
  sign_type: RSA2
  # 字符编码格式
  charset: utf-8
  # 支付宝网关
  gatewayUrl: https://openapi.alipaydev.com/gateway.do
  # 保存支付日志的地址
  log_path: c:/tmp/
3.2.2、配置类
@Component
@Data
@PropertySource(value = "classpath:application.yml")
public class PayAliConfig {
    // 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
    @Value("${alipay.app_id}")
    public  String app_id;
    // 商户私钥,您的PKCS8格式RSA2私钥
    @Value("${alipay.merchant_private_key}")
    public String merchant_private_key;
    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    @Value("${alipay.alipay_public_key}")
    public String alipay_public_key;
    // 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    @Value("${alipay.notify_url}")
    public  String notify_url;
    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    @Value("${alipay.return_url}")
    public  String return_url;
    // 签名方式
    @Value("${alipay.sign_type}")
    public  String sign_type;
    // 字符编码格式
    @Value("${alipay.charset}")
    public  String charset;
    // 支付宝网关
    @Value("${alipay.gatewayUrl}")
    public String gatewayUrl;
    // 日志存放
    @Value("${alipay.log_path}")
    public String log_path;
}

3.3、支付业务实现

3.3.1、获取支付码
请求参数
参数类型是否必填最大长度描述示例值
out_trade_noString必选64商户订单号。 由商家自定义,64个字符以内,仅支持字母、数字、下划线且需保证在商户端不重复。20150320010101001
total_amountPrice必选11订单总金额,单位为元,精确到小数点后两位,取值范围为 [0.01,100000000]。金额不能为0。88.88
subjectString必选256订单标题。 注意:不可使用特殊字符,如 /,=,& 等。Iphone6 16G
product_codeString必选64销售产品码,与支付宝签约的产品码名称。注:目前电脑支付场景下仅支持FAST_INSTANT_TRADE_PAYFAST_INSTANT_TRADE_PAY
qr_pay_modeString可选2PC扫码支付的方式。 支持前置模式和跳转模式。 前置模式是将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以 iframe 方式请求支付宝页面。具体支持的枚举值有以下几种: 0:订单码-简约前置模式,对应 iframe 宽度不能小于600px,高度不能小于300px; 1:订单码-前置模式,对应iframe 宽度不能小于 300px,高度不能小于600px; 3:订单码-迷你前置模式,对应 iframe 宽度不能小于 75px,高度不能小于75px; 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小。 跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。支持传入的枚举值有: 2:订单码-跳转模式1
qrcode_widthNumber可选4商户自定义二维码宽度。 注:qr_pay_mode=4时该参数有效100
goods_detailGoodsDetail[]可选订单包含的商品列表信息,json格式。
time_expireString可选32订单绝对超时时间。 格式为yyyy-MM-dd HH:mm:ss。 注:time_expire和timeout_express两者只需传入一个或者都不传,两者均传入时,优先使用time_expire。2016-12-31 10:05:01
sub_merchantSubMerchant可选二级商户信息。 直付通模式和机构间连模式下必传,其它场景下不需要传入。
extend_paramsExtendParams可选业务扩展参数
business_paramsString可选512商户传入业务信息,具体值要和支付宝约定,应用于安全,营销等参数直传场景,格式为json格式{“data”:“123”}
promo_paramsString可选512优惠参数。为 JSON 格式。注:仅与支付宝协商后可用{“storeIdType”:“1”}
integration_typeString可选16请求后页面的集成方式。 枚举值: ALIAPP:支付宝钱包内 PCWEB:PC端访问 默认值为PCWEB。PCWEB
request_from_urlString可选256请求来源地址。如果使用ALIAPP的集成方式,用户中途取消支付会返回该地址。https://
store_idString可选32商户门店编号。 指商户创建门店时输入的门店编号。NJ_001
merchant_order_noString可选32商户原始订单号,最大长度限制 32 位20161008001
invoice_infoInvoiceInfo可选开票信息

支付宝依赖中提供了参数模板类AlipayTradePrecreateModel可用改类镜像参数封装

AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
            model.setOutTradeNo("2022112223227"); // 自定义订单号
            model.setTotalAmount("111");// 支付金额
            model.setSubject("凯迪拉克");// 支付的产品名称
            model.setBody(params);// 支付的请求体参数
            model.setTimeoutExpress("30m");// 支付的超时时间
            model.setStoreId("111"+"");// 支付的库存id

其中body中的数据用户可以自定义商品的支付信息为了之后便于解析,一般为json格式,在支付回调时后,支付平台会将改参数返回,可根据该参数更新本地数据。

  • JSONObject json = new JSONObject();
    json.put("userId", "111");
    json.put("orderNumber", "2022112223227");
    json.put("money", 111);
    json.put("ptype","productcourse");// 支付类型
    json.put("courseId","3");
    String params = json.toString();
    

参数准备完成

客户端封装

支付宝提供了专用的请求客户端AlipayClient

AlipayClient alipayClient =
        new DefaultAlipayClient(
    payAliConfig.getGatewayUrl(),  //配置类中的支付宝网关
    payAliConfig.getApp_id(),      //配置类中的支付宝appid
    payAliConfig.getMerchant_private_key(), //配置类中的支付宝秘钥
                "JSON",            //参数类型
    payAliConfig.getCharset(),     //字符编码
    payAliConfig.getAlipay_public_key(),   //配置类中的支付宝公钥
    payAliConfig.getSign_type());   //签名方式
请求封装

支付宝提供了专用的请求对象AlipayTradePrecreateRequest

AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
// 3:设置请求参数的集合,最大长度不限
request.setBizModel(model);  //将上面封装的数据,绑定在请求中
// 设置异步回调地址
request.setNotifyUrl(payAliConfig.getNotify_url());
// 设置同步回调地址
request.setReturnUrl(payAliConfig.getReturn_url());
发送请求
 alipayClient.execute(request);
返回响应

支付宝提供了专用的响应对象AlipayTradePrecreateResponse支付宝返回的响应中,body中封装了业务数据,为了方便解析返回的的数据,我们可以封装一个响应对象AliResponse

public class AliResponse {

    private QrCodeResponse alipay_trade_precreate_response;

    private String sign;

    public QrCodeResponse getAlipay_trade_precreate_response() {
        return alipay_trade_precreate_response;
    }

    public void setAlipay_trade_precreate_response(QrCodeResponse alipay_trade_precreate_response) {
        this.alipay_trade_precreate_response = alipay_trade_precreate_response;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }
}

QrCodeResponse

public class QrCodeResponse {
    /**
     * 返回的状态码
     */
    private String code;

    /**
     * 返回的信息
     */
    private String msg;

    /**
     * 交易的流水号
     */
    private String out_trade_no;

    /**
     * 生成二维码的内容
     */
    private String qr_code;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String getOut_trade_no() {
        return out_trade_no;
    }

    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }

    public String getQr_code() {
        return qr_code;
    }

    public void setQr_code(String qr_code) {
        this.qr_code = qr_code;
    }

    @Override
    public String toString() {
        return "QrCodeResponse{" +
                "code='" + code + '\'' +
                ", msg='" + msg + '\'' +
                ", out_trade_no='" + out_trade_no + '\'' +
                ", qr_code='" + qr_code + '\'' +
                '}';
    }
}

其中qr_code便是二维码生成链接

参数解析
AliResponse aliResponse = JSON.parseObject(alipayTradePrecreateResponse.getBody(), AliResponse.class);
return aliResponse.getAlipay_trade_precreate_response();
//将QrCodeResponse返回到前端便可
3.3.2、支付码生成

最简单的做法可以利用vue插件在前端生成

<div>
    <qriously
              :value="payObj.qr_code"
              :size="220"
              />
    <div style="text-align: center;line-height: 25px;margin-bottom: 40px;">
        请使用支付宝扫一扫<br>
        扫描二维码支付
    </div>
</div>
效果展示:

image-20220502121022138

3.3.3、扫码支付,结果回调

下载支付宝沙箱版登陆之后,扫码支付
image-20220502121400045
点击立即支付后,支付宝支付成功后,会调用回调地址,将支付结果、以及支付信息返回,可以判断支付成功后,解析支付信息参数,更新本地支付记录、订单数据校验,返回success,否则支付平台会接着重试。

支付结果解析

支付宝回调返回的参数

AliPayDto(
gmt_create=[2022-05-01 17:06:58], 
charset=[utf-8], 
seller_email=[towpcq0063@sandbox.com], 
subject=[凯迪拉克], 
sign=[NQYGTZBoqHj+7vEu4EGSUOd8/ixOsn2Pj247srOi23lvldV3RdcJJ3qczYPkr4U8z0Yan7Uv+Pb54c+D7k/6d9XI35V3v8RYx1pCqp3nvF/0bBjPKzLpESaxxRwF82FnGtPU+0herGQjxRZbPNrtfJ9FeuIRVKqs0XhR/TMNwMl+TZLwlnOdyada87UW658igkNkT0GYfxSossgb4v3Rr3FSo0sL605jnvivJMbjo+6Jw+9HvYuYMx63tTt109Q==], 
body=[{
	 "orderNumber":"2022112223227",
	 "money":111,
	 "ptype":"productcourse",
	 "userId":"111",
	 "courseId":"3"
	 }], 
 buyer_id=[2088622956330370], 
 invoice_amount=[111.00], 
 notify_id=[2022050100222170709030370521355515], 
 fund_bill_list=[[{"amount":"111.00","fundChannel":"ALIPAYACCOUNT"}]], 
 notify_type=[trade_status_sync], 
 trade_status=[TRADE_SUCCESS], 
 receipt_amount=[111.00], 
 buyer_pay_amount=[111.00], 
 app_id=[2021000117697224], 
 sign_type=[RSA2], 
 seller_id=[2088621956217164], 
 gmt_payment=[2022-05-01 17:07:08], 
 notify_time=[2022-05-01 17:07:09], 
 version=[1.0], 
 out_trade_no=[2022112223227], 
 total_amount=[111.00], 
 trade_no=[2022050122001430370502275683], 
 auth_app_id=[2021000117697224], 
 buyer_logon_id=[lwx***@sandbox.com], 
 point_amount=[0.00])

支付信息是map格式,可以使用FastJson解析成自己封装的对象。

Map<String, String[]> parameterMap = request.getParameterMap();
String string = JSON.toJSONString(parameterMap);
AliPayDto aliPayDto = JSON.parseObject(string, AliPayDto.class);

自己封装的对象

@Data
public class AliPayDto {
    private String[] gmt_create;
    private String[] charset;
    private String[] seller_email;
    private String[] subject;
    private String[] sign;
    private String[] body;
    private String[] buyer_id;
    private String[] invoice_amount;
    private String[] notify_id;
    private String[] fund_bill_list;
    private String[] notify_type;
    private String[] trade_status;
    private String[] receipt_amount;
    private String[] buyer_pay_amount;
    private String[] app_id;
    private String[] sign_type;
    private String[] seller_id;
    private String[] gmt_payment;
    private String[] notify_time;
    private String[] version;
    private String[] out_trade_no;
    private String[] total_amount;
    private String[] trade_no;
    private String[] auth_app_id;
    private String[] buyer_logon_id;
    private String[] point_amount;
}

解析结果:

AliPayDto(gmt_create=[2022-05-02 12:48:22], charset=[utf-8], seller_email=[towpcq0063@sandbox.com], subject=[凯迪拉克], 
sign=[IUs73oHiL4STH4/Q928H+YSMCH9pNdoAVeNVM4X2XONaGyXJD3eRJP9lYVJmgQtutV4RFyvKkTKXaQfs9+XGkPQbjkrVkOps61B3raT+wNzuur6+/o6jOWa526L/l65T2Ix4oH5+TPjk0D4zXT4NXcFek5uqnkt54g2kYO9HNRhRIMg1sQVN7KtyVuOuk3o1oCxgJnO8OCwZEzmNRRJ/m6xlhYwQ801izIK1Q/kC3GZhuftpbEY616KV5NXIJfbDndQZxTzRFYIi70H7ovTtJNjGqO+qSj932N+PRZYvrTPe1h0oC5HebDV23sUTvABOzXvdlFZi025XWRkbtwxpsQ==], 
body=[{"orderNumber":"2022112223229","money":111,"ptype":"productcourse","userId":"111","courseId":"3"}], buyer_id=[2088622956330370], invoice_amount=[111.00], notify_id=[2022050200222124834030370521366673], fund_bill_list=[[{"amount":"111.00","fundChannel":"ALIPAYACCOUNT"}]], notify_type=[trade_status_sync], trade_status=[TRADE_SUCCESS], receipt_amount=[111.00], buyer_pay_amount=[111.00], 
app_id=[2021000117697224], sign_type=[RSA2], seller_id=[2088621956217164], gmt_payment=[2022-05-02 12:48:33], notify_time=[2022-05-02 12:48:34], version=[1.0], out_trade_no=[2022112223229], total_amount=[111.00], trade_no=[2022050222001430370502275841], auth_app_id=[2021000117697224], buyer_logon_id=[lwx***@sandbox.com], point_amount=[0.00])
后续操作:
  • 根据解析结果返回的数据更新支付记录、回写订单数据

  • (1)前端定时任务查询订单数据,回写成功后跳转页面。
    该方式会在短时间内发起大量请求,造成一定的服务器资源开销
    (2)集成webSoket让服务器主动向前端推送支付结果。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐