springboot整合支付宝沙箱支付

时间:2022-10-14 09:54:41

springboot整合支付宝沙箱支付

1.简介

支付宝开发平台地址:https://open.alipay.com/develop/sandbox/app

对于学生来说,目前网上确实没有比较统一而且质量好的支付教程。因为支付对个人开发者尤其是学生来说不太友好。因此,自己折腾两天,算是整理了一篇关于支付宝沙箱支付的文章。况且个人是不能申请支付(wx和alipay)都一样。幸亏有支付宝沙箱这个环境。其实跟正式环境差不多,就换下配置即可。

整体流程

springboot整合支付宝沙箱支付

2.配置说明

要记住这几个重要的配置

  • appId 这个是appId
  • privateKey 商户私钥
  • publicKey 支付宝公钥, 即对应APPID下的支付宝公钥
  • notifyUrl 支付成功后异步回调地址(注意是必须是公网地址)
  • returnUrl #支付后回调地址
  • signType 签名类型 一般写 RSA2
  • charset utf-8
  • format json
  • #网关地址 在支付宝开发平台复制拷贝下来
  • gatewayUrl: https://openapi.alipaydev.com/gateway.do
  • logPath: F:\ 日志路径

springboot整合支付宝沙箱支付

springboot整合支付宝沙箱支付

3.springboot整合支付宝沙箱支付代码

需要导入依赖

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

3.1 配置类

package com.hjt.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;



@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "alipay")
@Data
/***
 * @author: hjt
 * @Date: 2020/11/13/19:19
 * @Description: 支付宝配置类(读取配置文件)
 */
public class MyAliPayConfig {
    /**
     * APPID
     */
    private String appId;

    /**
     * 商户私钥, 即PKCS8格式RSA2私钥
     */
    private String privateKey;

    /**
     * 支付宝公钥
     */
    private String publicKey;

    /**
     * 服务器异步通知页面路径,需http://格式的完整路径
     * 踩坑:不能加?type=abc这类自定义参数
     */
    private String notifyUrl;

    /**
     * 页面跳转同步通知页面路径,需http://格式的完整路径
     * 踩坑:不能加?type=abc这类自定义参数
     */
    private String returnUrl;

    /**
     * 签名方式
     */
    private String signType;

    /**
     * 字符编码格式
     */
    private String charset;

    /***
     * 参数编码格式  json
     */
    private String format;

    /**
     * 支付宝网关
     */
    private String gatewayUrl;

    /**
     * 日志打印地址
     */
    private String logPath;
}

3.2 生成订单信息(以java为例,官网例子)

public void   doPost (HttpServletRequest httpRequest,
                      HttpServletResponse httpResponse)   throws  ServletException, IOException  {
    AlipayClient alipayClient =  new  DefaultAlipayClient( "https://openapi.alipay.com/gateway.do" , APP_ID, APP_PRIVATE_KEY, FORMAT, CHARSET, ALIPAY_PUBLIC_KEY, SIGN_TYPE);  //获得初始化的AlipayClient 
    AlipayTradePagePayRequest alipayRequest =  new  AlipayTradePagePayRequest(); //创建API对应的request 
    alipayRequest.setReturnUrl( "http://domain.com/CallBack/return_url.jsp" );
    alipayRequest.setNotifyUrl( "http://domain.com/CallBack/notify_url.jsp" ); //在公共参数中设置回跳和通知地址 
    alipayRequest.setBizContent( "{"  +
         "    \"out_trade_no\":\"20150320010101001\","  +
         "    \"product_code\":\"FAST_INSTANT_TRADE_PAY\","  +
         "    \"total_amount\":88.88,"  +
         "    \"subject\":\"Iphone6 16G\","  + 
         "    \"body\":\"Iphone6 16G\","  +
         "    \"passback_params\":\"merchantBizType%3d3C%26merchantBizNo%3d2016010101111\","  +
         "    \"extend_params\":{"  +
         "    \"sys_service_provider_id\":\"2088511833207846\""  +
         "    }" +
         "  }" ); //填充业务参数 
    String form= "" ;
     try  {
        form = alipayClient.pageExecute(alipayRequest).getBody();  //调用SDK生成表单 
    }  catch  (AlipayApiException e) {
        e.printStackTrace();
    }
    httpResponse.setContentType( "text/html;charset="  + CHARSET);
    httpResponse.getWriter().write(form); //直接将完整的表单html输出到页面 
    httpResponse.getWriter().flush();
    httpResponse.getWriter().close();
}

以下是我自己业务代码的例子

@Override
public void pay(PayInfoDto payInfoDTO, HttpServletResponse httpResponse) throws IOException {
    /*查询订单id是否存在*/
    Long orderId = payInfoDTO.getOrderId();
    /*商品名称*/
    String subject = "";

    /*判断订单是否存在*/
    R<Order> orderInfo = remoteOrderService.getOrderInfo(orderId);
    if (orderInfo.getCode() != 200) {
        throw new BaseException(PayException.ORDER_ERROR_PAY_ORDER);
    }
    Order order = orderInfo.getData();
    //订单总金额
    BigDecimal total = order.getTotal();
    String proId = order.getProId();
    /*商品名称以,分割*/
    String[] split = proId.split(",");
    for (int i = 0; i < split.length; i++) {
        R<Product> productInfo = remoteOrderService.getProductInfo(Long.parseLong(split[i]));
        if (productInfo.getCode() != 200) {
            throw new BaseException(PayException.ORDER_ERROR_PAY_PRODUCT);
        }
        Product product = productInfo.getData();
        subject = subject + product.getProTitle() + ",";
    }
    //进行支付宝支付
    AlipayOrder alipayOrder = new AlipayOrder();
    //商户订单号
    alipayOrder.setOut_trade_no(String.valueOf(orderId));
    //订单名称
    alipayOrder.setSubject(subject);
    alipayOrder.setDescription(subject);
    //订单总金额
    alipayOrder.setTotal_amount(total.toString());
    /*进行支付*/
    String payBody = alipayUtil.payByAlipay(alipayOrder);
    log.info("-----------支付信息实体---------{}",payBody);
    //并把消息推送到mq查询是否支付成功
    if(StringUtils.isBlank(payBody)){
        throw new BaseException(PayException.ORDER_ERROR_PAY_BODY);
    }
    /*把支付信息写到html网页中*/
    httpResponse.setContentType("text/html;charset=" + CHARSET);
    // 直接将完整的表单html输出到页面
    httpResponse.getWriter().write(payBody);
    httpResponse.getWriter().flush();
    httpResponse.getWriter().close();

}

3.3支付成功后的异步通知

@Override
public String payNotifyUrl(HttpServletRequest request) throws AlipayApiException {
    log.info("-----------开始进行支付后的异步通知回调-------");
    /*接收参数*/
    Map<String, String> params = this.exchangeParams(request);
    String payContent = JSONUtil.toJsonStr(params);
    log.info("---------接收的参数--- -----{}",params);
    /*验证签名(支付宝公钥) 调用SDK验证签名*/
    boolean  signVerified = AlipaySignature.rsaCheckV1(params, aliPayConfig.getPublicKey(), aliPayConfig.getCharset(), aliPayConfig.getSignType());
    if (signVerified){
        /*收到支付宝异步通知,返回success,支付宝不再通知 否则会通知你三天三夜*/
        log.info("-----验签成功-----");
        /*应该马上返回"success",另起线程执行自己的业务逻辑*/
        ExecutorService executor = ExecutorBuilder.create()
                .setCorePoolSize(5)
                .setMaxPoolSize(10)
                .setWorkQueue(new LinkedBlockingQueue<>(100))
                .build();
        executor.execute(new Runnable(){
            @Override
            public void run() {
                //TODO 幂等性问题后续也要考虑
                /*支付状态*/
                String trade_status = params.get("trade_status");
                /*订单id*/
                String out_trade_no = params.get("out_trade_no");
                /*支付成功*/
                if (PayConstant.TRADE_FINISHED.equals(trade_status) || PayConstant.TRADE_SUCCESS.equals(trade_status)) {

                    Long orderId = Long.valueOf(out_trade_no);
                    /*把对于的订单id改为已支付状态*/
                    R<Order> order = remoteOrderService.updateOrderById(orderId);
                    if(order.getCode()!=PayConstant.CODE){
                        throw new BaseException(PayException.ORDER_ERROR_PAY_ORDER);
                    }
                    /*存对于的支付信息*/
                    PayInfo payInfo = null;
                    payInfo = PayInfo.builder()
                            .orderIds(out_trade_no)
                            .callbackContent(payContent)
                            .callbackTime(LocalDateTime.now())
                            .tradeStatus(trade_status)
                            .tradeNo(params.get("trade_no"))
                            .buyerId(params.get("buyer_id"))
                            .totalAmount(params.get("total_amount"))
                            .version(params.get("version"))
                            .sellerId(params.get("seller_id"))
                            .receiptAmount(params.get("receipt_amount"))
                            .gmtCreate(params.get("gmt_create"))
                            .gmtPayment(params.get("gmt_payment"))
                            .fundBillList(params.get("fund_bill_list"))
                            .build();
                    payInfoMapper.insert(payInfo);
                    log.info("-----支付信息插入成功-------");
                }
                /* 支付失败 */
                else{
                    log.error("-------支付失败, 订单id:------{}",out_trade_no);
                    throw new BaseException(PayException.ORDER_ERROR_PAY_BODY);
                }

            }
        });
        return "success";
        // TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商家自身业务处理,校验失败返回failure
    } else {
        log.info("-----验签失败-----");
        return "failure";
        // TODO 验签失败则记录异常日志,并在response中返回failure.
    }
}

注意!!!异步通知的地址必须是公网地址,这里我采用的是frp内网穿透到本地的地址

需要注意的是,异步通知必须要严格进行验签。

运行效果图:

先生成订单

springboot整合支付宝沙箱支付

然后再浏览器直接调用接口

http://localhost:4401/pay/api/v1/pay-info/pay?Authorization=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YWRtaW4.VaJOloHfQLjacnm6-__pSaeNZ1JbLAdlgeJT3JEptos&orderId=769532559209283584

会直接跳转到支付支付界面

springboot整合支付宝沙箱支付

账号密码都是可以在你沙箱账号看得到

springboot整合支付宝沙箱支付

支付成功后可见已经回调到我们异步通知自定义的接口了

即我们在这配置的路径

notifyUrl 支付成功后异步回调地址(注意是必须是公网地址)

springboot整合支付宝沙箱支付

3.4退款操作

参数 类型 是否必填 最大长度 描述 示例值
trade_no String 特殊可选 64 支付宝交易号。 和商户订单号不能同时为空 2021081722001419121412730660
out_trade_no String 特殊可选 64 商户订单号。 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no 2014112611001004680073956707
out_request_no String 必选 64 退款请求号。 请求退款接口时,传入的退款请求号,如果在退款请求时未传入,则该值为创建交易时的商户订单号。 HZ01RF001
query_options String[] 可选 1024 查询选项,商户通过上送该参数来定制同步需要额外返回的信息字段,数组格式。枚举支持: refund_detail_item_list:本次退款使用的资金渠道; gmt_refund_pay:退款执行成功的时间; deposit_back_info:银行卡冲退信息; refund_detail_item_list

商户可使用该接口查询自已通过alipay.trade.refund提交的退款请求是否执行成功。

注意:1. 该接口的返回码10000,仅代表本次查询操作成功,不代表退款成功,当接口返回的refund_status值为REFUND_SUCCESS时表示退款成功,否则表示退款没有执行成功。
\2. 如果退款未成功,商户可以调用退款接口重试,重试时请务必保证退款请求号和退款金额一致,防止重复退款。
\3. 发起退款查询接口的时间不能离退款请求时间太短,建议之间间隔10秒以上。

个人搭建项目代码地址:
https://github.com/hongjiatao/spring-boot-anyDemo

欢迎收藏点赞三连。谢谢!有问题可以留言博主会24小时内无偿回复。