一步步教你如何在SpringBoot项目中引入支付功能

简介: 支付功能如今已经成为一个需要盈利的网站的基本功能了,如今的网站如果想要做支付功能,往往都是将支付宝或者微信的支付功能集成进来。尽管支付宝已经给出了许多文档和代码,但是这项工作并没有那么简单。今天我就一步步带大家去实现在SpringBoot项目中对支付宝的功能引入。

听说微信搜索《Java鱼仔》会变更强哦!


本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看哦


(一)引言


支付功能如今已经成为一个需要盈利的网站的基本功能了,如今的网站如果想要做支付功能,往往都是将支付宝或者微信的支付功能集成进来。尽管支付宝已经给出了许多文档和代码,但是这项工作并没有那么简单。今天我就一步步带大家去实现在SpringBoot项目中对支付宝的功能引入。


(二)功能介绍


我们要实现的功能很简单,当传入用户购买的信息之后,生成一个二维码供支付使用,同时提供一个查询接口查询该笔订单是否已支付,这种支付方式叫做当面付。学会这一种支付方式之后,支付宝的其他的功能也会很容易上手。


首先给出当面付的文档地址opendocs.alipay.com/open/194


文档中很详细地描述了支付的整体逻辑。


(三)开发前准备


首先需要引入相关的依赖,我把这个项目中会用到的依赖一次性给出:


<!--支付宝依赖--><!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java --><dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.10.218.ALL</version><exclusions><exclusion><artifactId>commons-logging</artifactId><groupId>commons-logging</groupId></exclusion></exclusions></dependency><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><dependency><groupId>commons-configuration</groupId><artifactId>commons-configuration</artifactId><version>1.8</version><exclusions><exclusion><artifactId>commons-logging</artifactId><groupId>commons-logging</groupId></exclusion></exclusions></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.11</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.2.1</version></dependency><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-core</artifactId><version>1.3</version><scope>test</scope></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.6</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>


同时官方已经提供了Demo,我们也直接下载下来:


opendocs.alipay.com/open/54/104…


网络异常,图片无法展示
|


另外支付功能还涉及到私钥公钥的加签,支付宝给我们提供了密钥加密工具,也需要下载:opendocs.alipay.com/open/291/10…


最后还要一个沙箱环境的地址,这个地址用于测试:


opendocs.alipay.com/open/200/10…


(四)项目搭建


首先搭建一个SpringBoot项目,这一步就跳过了,打开我们上面下载的Demo文件,里面有一个TradePayDemo和TradePaySDK,TradePaySDK是支付过程中需要调用的一些类,因此需要把TradePaySDK中的代码引进来:


网络异常,图片无法展示
|
将上面这四个文件引入到我们的项目中:


网络异常,图片无法展示
|


TradePayDemo中提供了具体代码如何调用的示例,src目录下有一个文件叫做:


zfbinfo.properties,把这个文件放入到resource目录下。关于zfbinfo.properties文件,里面有五个参数是需要我们自己去填写的: 注意,由于是测试环境,因此将open_api_domain修改成:openapi.alipaydev.com/gateway.do


网络异常,图片无法展示
|


pid是每个人自己账号的Id,登陆沙箱环境后,点击右上角的账号,选中账户中心,里面的账号ID就是pid。


网络异常,图片无法展示
|


在沙箱环境中,你还能看到自己的appid,将这个appid赋值到配置文件中的appid处:


网络异常,图片无法展示
|


接下来就是公钥和私钥以及支付宝的公钥,上面让大家下载了工具,打开后直接用默认的加密方式生成公私钥:


网络异常,图片无法展示
|


将上面的私钥和公钥分别放入对应的private_key和public_key中。


在沙箱环境中,将上面生成的公钥复制上去,可以得到支付宝的公钥,配置文件就齐全了


网络异常,图片无法展示
|


(五)业务开发


完成上面一长串工作后,就可以开始写业务了,Demo文件中还要一个包叫做TradePayDemo的,我们主要参考里面的Main方法。


5.1 二维码生成功能


新建一个Service叫做TradeService,再新建他的实体类TradeServiceImpl


@Slf4j@ServicepublicclassTradeServiceImplimplementsTradeService {
// 支付宝当面付2.0服务privatestaticAlipayTradeServicetradeService;
@PostConstructprivatevoidinit(){
/** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数*  Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录*/Configs.init("zfbinfo.properties");
/** 使用Configs提供的默认参数*  AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new*/tradeService=newAlipayTradeServiceImpl.ClientBuilder().build();
    }
@OverridepublicStringtradeQrCode(OrderDetailorderDetail){
//支付二维码的访问路径StringqrCodePath=null;
// (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,// 需保证商户系统端不能重复,建议通过数据库sequence生成,StringoutTradeNo="tradeprecreate"+System.currentTimeMillis()
+ (long) (Math.random() *10000000L);
// (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”Stringsubject=orderDetail.getSubject();
// (必填) 订单总金额,单位为元,不能超过1亿元// 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】StringtotalAmount=orderDetail.getTotalAmount();
// (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段// 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】StringundiscountableAmount="0";
// 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)// 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PIDStringsellerId="";
// 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"Stringbody=String.format("购买商品%s件共%s元",orderDetail.getGoodsDetail().size(),totalAmount);
// 商户操作员编号,添加此参数可以为商户操作员做销售统计StringoperatorId="javayz";
// (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持StringstoreId="javayz001";
// 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持ExtendParamsextendParams=newExtendParams();
extendParams.setSysServiceProviderId("2088100200300400500");
// 支付超时,定义为120分钟StringtimeoutExpress="120m";
// 商品明细列表,需填写购买商品详细信息,List<GoodsDetail>goodsDetailList=newArrayList<GoodsDetail>();
orderDetail.getGoodsDetail().stream().forEach((item)->{
// 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetailGoodsDetailgoods1=GoodsDetail.newInstance(item.getGoodsId(), item.getGoodsName(), Long.valueOf(item.getPrice())*100, Math.toIntExact(item.getQuantity()));
// 创建好一个商品后添加至商品明细列表goodsDetailList.add(goods1);
        });
// 创建扫码支付请求builder,设置请求参数AlipayTradePrecreateRequestBuilderbuilder=newAlipayTradePrecreateRequestBuilder()
                .setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
                .setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
                .setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
                .setTimeoutExpress(timeoutExpress)
//                .setNotifyUrl("http://www.test-notify-url.com")//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置                .setGoodsDetailList(goodsDetailList);
AlipayF2FPrecreateResultresult=tradeService.tradePrecreate(builder);
switch (result.getTradeStatus()) {
caseSUCCESS:
log.info("支付宝预下单成功: )");
AlipayTradePrecreateResponseresponse=result.getResponse();
dumpResponse(response);
// 需要修改为运行机器上的路径StringfilePath=String.format("F:/qrcode/static/qrcode/qr-%s.png",
response.getOutTradeNo());
log.info("filePath:"+filePath);
//创建二维码ZxingUtils.getQRCodeImge(response.getQrCode(), 256, filePath);
qrCodePath=filePath;
break;
caseFAILED:
log.error("支付宝预下单失败!!!");
break;
caseUNKNOWN:
log.error("系统异常,预下单状态未知!!!");
break;
default:
log.error("不支持的交易状态,交易返回异常!!!");
break;
        }
returnqrCodePath;
    }
// 简单打印应答privatevoiddumpResponse(AlipayResponseresponse) {
if (response!=null) {
log.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
if (StringUtils.isNotEmpty(response.getSubCode())) {
log.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
response.getSubMsg()));
            }
log.info("body:"+response.getBody());
        }
    }
}


代码注释已经解释的很清楚了,就是设置参数,生成二维码。


新建一个类OrderController,创建一个Post请求的接口:


@RestController@RequestMapping("/order")
publicclassOrderControllerextendsBaseController {
@AutowiredprivateTradeServicetradeService;
//创建支付二维码@PostMapping("/qrcode")
publicCommonResultgetQrCode(@RequestBodyOrderDetailorderDetail){
Stringpath=tradeService.tradeQrCode(orderDetail);
if (StringUtils.isNotEmpty(path)){
returnCommonResult.success(path);
        }
returnCommonResult.fail();
    }
}


访问http://localhost:8190/order/qrcode,参数可参考如下:


网络异常,图片无法展示
|


输出二维码的地址:


网络异常,图片无法展示
|


验证支付功能务必要使用沙盒地址中的钱包:


网络异常,图片无法展示
|


5.2 验证订单是否被支付


一样的逻辑,从Demo中找到验证订单的代码,放入TradeServiceImpl中


@OverridepublicStringalipayTradeQuery(StringorderSn){
//返回信息StringresponseResult="";
// (必填) 商户订单号,通过此商户订单号查询当面付的交易状态StringoutTradeNo=orderSn;
// 创建查询请求builder,设置请求参数AlipayTradeQueryRequestBuilderbuilder=newAlipayTradeQueryRequestBuilder()
                .setOutTradeNo(outTradeNo);
AlipayF2FQueryResultresult=tradeService.queryTradeResult(builder);
switch (result.getTradeStatus()) {
caseSUCCESS:
responseResult="查询返回该订单支付成功";
log.info("查询返回该订单支付成功: )");
AlipayTradeQueryResponseresponse=result.getResponse();
dumpResponse(response);
log.info(response.getTradeStatus());
if (Utils.isListNotEmpty(response.getFundBillList())) {
for (TradeFundBillbill : response.getFundBillList()) {
log.info(bill.getFundChannel() +":"+bill.getAmount());
                    }
                }
break;
caseFAILED:
responseResult="查询返回该订单支付失败或被关闭";
log.error("查询返回该订单支付失败或被关闭!!!");
break;
caseUNKNOWN:
responseResult="系统异常,订单支付状态未知";
log.error("系统异常,订单支付状态未知!!!");
break;
default:
responseResult="不支持的交易状态,交易返回异常";
log.error("不支持的交易状态,交易返回异常!!!");
break;
        }
returnresponseResult;
    }


在Controller中加一个接口


//查询订单情况@PostMapping("/queryOrderStatus")
publicCommonResultqueryOrderStatus(@RequestParam("orderSn") StringorderSn){
Stringresult=tradeService.alipayTradeQuery(orderSn);
if (StringUtils.isEmpty(result)){
returnCommonResult.fail();
        }else {
returnCommonResult.success(result);
        }
    }

5.3 设置一个回调的接口


可以通过5.2中的方法定时轮询订单是否被支付,也可以写一个回调接口给支付宝调用,但是这个接口必须确保能被支付宝外网访问到,这里我给出代码示例:


@PostMapping("payCallback")
publicvoidpayCallback(){
Map<String,String>map=newHashMap<>();
Enumeration<String>parameterNames=getRequest().getParameterNames();
while (parameterNames.hasMoreElements()){
Stringparameter=parameterNames.nextElement();
if (!parameter.toLowerCase().equals("sign_type")){
map.put(parameter,getRequest().getParameter(parameter));
            }
        }
try {
booleanresult=AlipaySignature.rsaCertCheckV2(map, Configs.getPublicKey(), "utf-8", Configs.getSignType());
PrintWriterwriter=getResponse().getWriter();
if (result){
writer.print("success");
            }else {
writer.print("unSuccess");
            }
        } catch (AlipayApiExceptione) {
e.printStackTrace();
        } catch (IOExceptione) {
e.printStackTrace();
        }
    }


然后在生成二维码的代码中增加回调接口


网络异常,图片无法展示
|

(六)总结


到这里,当面付的功能我们就开发好了,说简单也不简单,各种步骤都比较繁琐,但是只要把整体逻辑理清楚了,后续其他支付功能的开发就会很简单了。我是鱼仔,我们下期再见!

相关文章
|
1月前
|
消息中间件 缓存 Java
手写模拟Spring Boot启动过程功能
【11月更文挑战第19天】Spring Boot自推出以来,因其简化了Spring应用的初始搭建和开发过程,迅速成为Java企业级应用开发的首选框架之一。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,帮助读者深入理解其工作机制。
41 3
|
1月前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
50 0
|
1月前
|
Java 应用服务中间件
SpringBoot获取项目文件的绝对路径和相对路径
SpringBoot获取项目文件的绝对路径和相对路径
87 1
SpringBoot获取项目文件的绝对路径和相对路径
|
1月前
|
分布式计算 关系型数据库 MySQL
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型 图像处理 光通信 分布式计算 算法语言 信息技术 计算机应用
51 8
|
1月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
41 2
|
1月前
|
前端开发 Java easyexcel
SpringBoot操作Excel实现单文件上传、多文件上传、下载、读取内容等功能
SpringBoot操作Excel实现单文件上传、多文件上传、下载、读取内容等功能
96 8
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
77 2
|
1月前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
50 2
|
1月前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
45 1
|
26天前
|
JavaScript 前端开发 Java
SpringBoot项目的html页面使用axios进行get post请求
SpringBoot项目的html页面使用axios进行get post请求
36 0