最近在做一个网站,后端采用了SpringBoot,需求集成付出宝进行线上付出,在这个过程中研究了很多付出宝的集成资料,也走了一些弯路,现在总结出来,相信你读完也能轻松集成付出宝付出。

在开端集成付出宝付出之前,咱们需求预备一个付出宝商家账户,假如是个人开发者,能够经过注册公司或者让有公司资质的单位进行授权,后续在集成相关API的时候需求供给这些信息。

下面我以电脑网页端在线付出为例,介绍整个从集成、测验到上线的具体流程。

1. 预期效果展现

在开端之前咱们先看下咱们要达到的最后效果,具体如下:

  1. 前端点击付出跳转到付出宝界面
  2. 付出宝界面展现付款二维码
  3. 用户手机端付出
  4. 完结付出,付出宝回调开发者指定的url。

SpringBoot集成支付宝支付 - 少走弯路就看这篇

2. 开发流程

2.1 沙盒调试

付出宝为咱们预备了完善的沙盒开发环境,咱们能够先在沙盒环境调试好程序,后续新建好运用并成功上线后,把程序中对应的参数替换为线上参数即可。

1. 创立沙盒运用

直接进入 open.alipay.com/develop/san… 创立沙盒运用即可,

SpringBoot集成支付宝支付 - 少走弯路就看这篇

这儿由于是测验环境,咱们就挑选系统默许密钥就行了,下面挑选公钥模式,另外运用网关地址便是用户完结付出之后,付出宝会回调的url。在开发环境中,咱们能够采用内网穿透的方式,将咱们本机的端口暴露在某个公网地址上,这儿引荐 natapp.cn/ ,能够免费注册运用。

2. SpringBoot代码完结

在创立好沙盒运用,获取到密钥,APPID,商家账户PID等信息之后,就能够在测验环境开发集成对应的API了。这儿我以电脑端付出API为例,介绍怎么进行集成。

关于电脑网站付出的具体产品介绍和API接入文档能够参考:opendocs.alipay.com/open/repo-0… 和 opendocs.alipay.com/open/270/01…

  • 步骤1, 增加alipay sdk对应的Maven依赖。
<!-- alipay -->
<dependency>  
   <groupId>com.alipay.sdk</groupId>  
   <artifactId>alipay-sdk-java</artifactId>  
   <version>4.35.132.ALL</version>  
</dependency>
  • 步骤2,增加付出宝下单、付出成功后同步调用和异步调用的接口。

这儿需求留意,同步接口是用户完结付出后会主动跳转的地址,因此需求是Get恳求。异步接口,是用户完结付出之后,付出宝会回调来告诉付出成果的地址,所以是POST恳求。

@RestController
@RequestMapping("/alipay")  
public class AliPayController {  
    @Autowired  
    AliPayService aliPayService;  
    @PostMapping("/order")  
    public GenericResponse<Object> placeOrderForPCWeb(@RequestBody AliPayRequest aliPayRequest) {  
        try {  
            return aliPayService.placeOrderForPCWeb(aliPayRequest);  
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }  
    }  
    @PostMapping("/callback/async")  
    public String asyncCallback(HttpServletRequest request) {  
        return aliPayService.orderCallbackInAsync(request);  
    }  
    @GetMapping("/callback/sync")  
    public void syncCallback(HttpServletRequest request, HttpServletResponse response) {  
        aliPayService.orderCallbackInSync(request, response);  
    }  
}
  • 步骤3,完结Service层代码

这儿针对上面controller中的三个接口,分别完结service层对应的方法。下面是整个付出的中心流程,其间有些地方需求依据你自己的实际情况进行保存订单到DB或者查看订单状况的操作,这个能够依据实际事务需求进行规划。

public class AliPayService {
    @Autowired  
    AliPayHelper aliPayHelper;  
    @Resource  
    AlipayConfig alipayConfig;  
    @Transactional(rollbackFor = Exception.class)  
    public GenericResponse<Object> placeOrderForPCWeb(AliPayRequest aliPayRequest) throws IOException {  
        log.info("【恳求开端-在线购买-买卖创立】*********共同下单开端*********");  
        String tradeNo = aliPayHelper.generateTradeNumber();  
        String subject = "购买套餐1";  
        Map<String, Object> map = aliPayHelper.placeOrderAndPayForPCWeb(tradeNo, 100, subject);  
        if (Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))) {  
            log.info("【恳求开端-在线购买-买卖创立】共同下单成功,开端保存订单数据");  
            //保存订单信息  
            // 增加你自己的事务逻辑,主要是保存订单数据
            log.info("【恳求成功-在线购买-买卖创立】*********共同下单完毕*********");  
            return new GenericResponse<>(ResponseCode.SUCCESS, map.get("body"));  
        }else{  
            log.info("【失利:恳求失利-在线购买-买卖创立】*********共同下单完毕*********");  
            return new GenericResponse<>(ResponseCode.INTERNAL_ERROR, String.valueOf(map.get("subMsg")));  
        }  
    }  
    // sync return page  
    public void orderCallbackInSync(HttpServletRequest request, HttpServletResponse response) {  
        try {  
            OutputStream outputStream = response.getOutputStream();  
            //经过设置呼应头操控浏览器以UTF-8的编码显现数据,假如不加这句话,那么浏览器显现的将是乱码  
            response.setHeader("content-type", "text/html;charset=UTF-8");  
            String outputData = "付出成功,请回来网站并刷新页面。";  
            /**  
             * data.getBytes()是一个将字符转换成字节数组的过程,这个过程中一定会去查码表,  
             * 假如是中文的操作系统环境,默许便是查找查GB2312的码表,  
             */  
            byte[] dataByteArr = outputData.getBytes("UTF-8");//将字符转换成字节数组,指定以UTF-8编码进行转换  
            outputStream.write(dataByteArr);//运用OutputStream流向客户端输出字节数组  
        } catch (IOException e) {  
            throw new RuntimeException(e);  
        }  
    }  
    public String orderCallbackInAsync(HttpServletRequest request) {  
        try {  
            Map<String, String> map = aliPayHelper.paramstoMap(request);  
            String tradeNo = map.get("out_trade_no");  
            String sign = map.get("sign");  
            String content = AlipaySignature.getSignCheckContentV1(map);  
            boolean signVerified = aliPayHelper.CheckSignIn(sign, content);  
            // check order status  
            // 这儿在DB中查看order的状况,假如现已付出成功,无需再次验证。
            if(从DB中拿到order,而且判断order是否付出成功过){  
                log.info("订单:" + tradeNo + " 现已付出成功,无需再次验证。");  
                return "success";  
            }  
            //验证事务数据是否共同  
            if(!checkData(map, order)){  
                log.error("回来事务数据验证失利,订单:" + tradeNo );  
                return "回来事务数据验证失利";  
            }  
            //签名验证成功  
            if(signVerified){  
                log.info("付出宝签名验证成功,订单:" + tradeNo);  
                // 验证付出状况  
                String tradeStatus = request.getParameter("trade_status");  
                if(tradeStatus.equals("TRADE_SUCCESS")){  
                    log.info("付出成功,订单:"+tradeNo);  
			        // 更新订单状况,履行一些事务逻辑
                    return "success";  
                }else{  
                    System.out.println("付出失利,订单:" + tradeNo );  
                    return "付出失利";  
                }  
            }else{  
                log.error("签名验证失利,订单:" + tradeNo );  
                return "签名验证失利.";  
            }  
        } catch (IOException e) {  
            log.error("IO exception happened ", e);  
            throw new RuntimeException(ResponseCode.INTERNAL_ERROR, e.getMessage());  
        }  
    }  
    public boolean checkData(Map<String, String> map, OrderInfo order) {  
        log.info("【恳求开端-买卖回调-订单承认】*********校验订单承认开端*********");  
        //验证订单号是否精确,而且订单状况为待付出  
        if(验证订单号是否精确,而且订单状况为待付出){  
            float amount1 = Float.parseFloat(map.get("total_amount"));  
            float amount2 = (float) order.getOrderAmount();  
            //判断金额是否相等  
            if(amount1 == amount2){  
                //验证收款商户id是否共同  
                if(map.get("seller_id").equals(alipayConfig.getPid())){  
                    //判断appid是否共同  
                    if(map.get("app_id").equals(alipayConfig.getAppid())){  
                        log.info("【成功:恳求开端-买卖回调-订单承认】*********校验订单承认成功*********");  
                        return true;                    }  
                }  
            }  
        }  
        log.info("【失利:恳求开端-买卖回调-订单承认】*********校验订单承认失利*********");  
        return false;    }  
}
  • 步骤4,完结alipayHelper类。这个类里面对付出宝的接口进行封装。
public class AliPayHelper {
    @Resource  
    private AlipayConfig alipayConfig;  
    //回来数据格式  
    private static final String FORMAT = "json";  
    //编码类型  
    private static final String CHART_TYPE = "utf-8";  
    //签名类型  
    private static final String SIGN_TYPE = "RSA2";  
    /*付出出售产品码,目前付出宝只支撑FAST_INSTANT_TRADE_PAY*/  
    public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";  
    private static AlipayClient alipayClient = null;  
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");  
    private static final Random random = new Random();  
    @PostConstruct  
    public void init(){  
        alipayClient = new DefaultAlipayClient(  
                alipayConfig.getGateway(),  
                alipayConfig.getAppid(),  
                alipayConfig.getPrivateKey(),  
                FORMAT,  
                CHART_TYPE,  
                alipayConfig.getPublicKey(),  
                SIGN_TYPE);  
    };  
    /*================PC网页付出====================*/  
    /**  
     * 共同下单并调用付出页面接口  
     * @param outTradeNo  
     * @param totalAmount  
     * @param subject  
     * @return  
     */  
    public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){  
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();  
        request.setNotifyUrl(alipayConfig.getNotifyUrl());  
        request.setReturnUrl(alipayConfig.getReturnUrl());  
        JSONObject bizContent = new JSONObject();  
        bizContent.put("out_trade_no", outTradeNo);  
        bizContent.put("total_amount", totalAmount);  
        bizContent.put("subject", subject);  
        bizContent.put("product_code", PRODUCT_CODE);  
        request.setBizContent(bizContent.toString());  
        AlipayTradePagePayResponse response = null;  
        try {  
            response = alipayClient.pageExecute(request);  
        } catch (AlipayApiException e) {  
            e.printStackTrace();  
        }  
        Map<String, Object> resultMap = new HashMap<>();  
        resultMap.put("isSuccess", response.isSuccess());  
        if(response.isSuccess()){  
            log.info("调用成功");  
            log.info(JSON.toJSONString(response));  
            resultMap.put("body", response.getBody());  
        } else {  
            log.error("调用失利");  
            log.error(response.getSubMsg());  
            resultMap.put("subMsg", response.getSubMsg());  
        }  
        return resultMap;  
    }  
    /**  
     * 买卖订单查询  
     * @param out_trade_no  
     * @return  
     */  
    public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){  
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();  
        JSONObject bizContent = new JSONObject();  
        bizContent.put("trade_no", out_trade_no);  
        request.setBizContent(bizContent.toString());  
        AlipayTradeQueryResponse response = null;  
        try {  
            response = alipayClient.execute(request);  
        } catch (AlipayApiException e) {  
            e.printStackTrace();  
        }  
        Map<String, Object> resultMap = new HashMap<>();  
        resultMap.put("isSuccess", response.isSuccess());  
        if(response.isSuccess()){  
            System.out.println("调用成功");  
            System.out.println(JSON.toJSONString(response));  
            resultMap.put("status", response.getTradeStatus());  
        } else {  
            System.out.println("调用失利");  
            System.out.println(response.getSubMsg());  
            resultMap.put("subMsg", response.getSubMsg());  
        }  
        return resultMap;  
    }  
    /**  
     * 验证签名是否正确  
     * @param sign  
     * @param content  
     * @return  
     */  
    public boolean CheckSignIn(String sign, String content){  
        try {  
            return AlipaySignature.rsaCheck(content, sign, alipayConfig.getPublicKey(), CHART_TYPE, SIGN_TYPE);  
        } catch (AlipayApiException e) {  
            e.printStackTrace();  
        }  
        return false;  
    }  
    /**  
     * 将异步告诉的参数转化为Map  
     * @return  
     */  
    public Map<String, String> paramstoMap(HttpServletRequest request) throws UnsupportedEncodingException {  
        Map<String, String> params = new HashMap<String, String>();  
        Map<String, String[]> requestParams = request.getParameterMap();  
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {  
            String name = (String) iter.next();  
            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] + ",";  
            }  
            // 乱码解决,这段代码在呈现乱码时运用。  
//            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");  
            params.put(name, valueStr);  
        }  
        return params;  
    }  
}
  • 步骤5,封装config类,用于存放一切的装备特点。
@Data
@Component  
@ConfigurationProperties(prefix = "alipay")  
public class AlipayConfig {  
    private String gateway;  
    private String appid;  
    private String pid;  
    private String privateKey;  
    private String publicKey;  
    private String returnUrl;  
    private String notifyUrl;  
}

另外需求在application.properties中,预备好上述对应的特点。

# alipay config
alipay.gateway=https://openapi.alipaydev.com/gateway.do  
alipay.appid=your_appid
alipay.pid=your_pid  
alipay.privatekey=your_private_key
alipay.publickey=your_public_key
alipay.returnurl=完结付出后的同步跳转地址 
alipay.notifyurl=完结付出后,付出宝会异步回调的地址

3. 前端代码完结

前端代码只需求完结两个功用,

  1. 依据用户的恳求向后端发起付出恳求。
  2. 直接提交回来数据完结跳转。

下面的例子中,我用typescript完结了用户点击付出之后的功用,

async function onPositiveClick() {
   paymentLoading.value = true  
   const { data } = await placeAlipayOrder<string>({  
	//你的一些恳求参数,例如金额等等
   })  
   const div = document.createElement('divform')  
   div.innerHTML = data  
   document.body.appendChild(div)  
   document.forms[0].setAttribute('target', '_blank')  
   document.forms[0].submit()  
   showModal.value = false  
   paymentLoading.value = false  
}

2.2 创立并上线APP

完结沙盒调试没问题之后,咱们需求创立对应的付出宝网页运用并上线。

登录 open.alipay.com/develop/man… 并挑选创立网页运用,

SpringBoot集成支付宝支付 - 少走弯路就看这篇

填写运用相关信息:

SpringBoot集成支付宝支付 - 少走弯路就看这篇

创立好运用之后,首先在开发设置中,设置好接口加签方式以及运用网关。

SpringBoot集成支付宝支付 - 少走弯路就看这篇

留意密钥挑选RSA2,其他按照上面的操作攻略一步步走即可,留意保管好自己的私钥和公钥。

之后在产品绑定页,绑定对应的API,比如咱们这儿是PC网页端付出,找到对应的API绑定就能够了。假如第一次绑定,或许需求填写相关的信息进行审阅,按需填写即可,一般审阅一天就经过了。

SpringBoot集成支付宝支付 - 少走弯路就看这篇

最后假如一切就绪,咱们就能够把APP提交上线了,上线成功之后,咱们需求把下面SpringBoot中的properties替换为线上APP的信息,然后就能够在生产环境调用付出宝的接口进行付出了。

# alipay config
alipay.gateway=https://openapi.alipaydev.com/gateway.do  
alipay.appid=your_appid
alipay.pid=your_pid  
alipay.privatekey=your_private_key
alipay.publickey=your_public_key
alipay.returnurl=完结付出后的同步跳转地址 
alipay.notifyurl=完结付出后,付出宝会异步回调的地址

参考:

  • blog.csdn.net/xqnode/arti…
  • blog.51cto.com/u_15754099/…
  • zhuanlan.zhihu.com/p/596771147
  • segmentfault.com/a/119000004…

欢迎重视公众号【码老思】,只讲最通俗易懂的原创技术干货。