首页
留言
友链
关于
Search
1
思源笔记docker私有化部署及使用体验分享
2,879 阅读
2
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
1,230 阅读
3
解决 nginxProxyManager 申请证书时的SSL失败问题
882 阅读
4
Pointer-Focus:一款功能强大的教学、录屏辅助软件
857 阅读
5
使用cspell对项目做拼写规范检查
720 阅读
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
Java
运维
项目
生活
其他
转载
软件
职场
登录
Search
标签搜索
docker
DevOps
magic-boot
Linux
酷壳
frp
RabbitMQ
gitlab
Node
git
工具
MybatisPlus
clickhouse
Syncthing
规范
前端
产品
nginx
markdown
axios
朱治龙
累计撰写
153
篇文章
累计收到
10
条评论
首页
栏目
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
Java
运维
项目
生活
其他
转载
软件
职场
页面
留言
友链
关于
搜索到
1
篇与
支付
的结果
2024-06-29
对接支付宝实现门户网站在线支付功能
前言近期,我所在团队负责的控制台项目计划新增在线充值功能。为实现此功能,我们首先需完成在线支付环节的对接。初期,我们将优先接入支付宝支付服务。值得一提的是,我上一次进行支付宝对接,还是在十多年前参与的笨鸟旅行项目,那时我们采用的是 jsp + mybatis 技术栈。与现在便捷的SDK调用相比,当时的开发条件相对简陋,整个项目甚至连 maven 包管理工具都未使用,前端工程化也仅处于起步阶段,CI/CD流程更是无从谈起。回顾过去,不禁让人感叹技术发展带来的日新月异。支付宝支付流程支付宝支付流程大致如下:1、 注册商家并绑定开发人员 :支付宝商家平台( https://b.alipay.com/ )注册商家,多说一嘴,注册成功后,可以在商家平台的账号中心 → 员工列表 添加员工为子账号,这样后续就不需要老是麻烦Boss帮忙扫码或提供验证码了:2、 申请开通支付产品: 商家平台的产品中心根据业务场景需要,申请开通相关支付产品。如我们申请开通了「电脑网站支付」、「手机网站支付」两款支付产品,申请后一般几分钟就开通了。3、 创建应用: 开通产品后就可以创建应用了,单击对应的产品可以进入关联应用页面创建应用并关联,这个界面需要超级管理员将开发人员添加为开放平台的管理员,这样开发人员就可以在开放平台进行应用相关配置了。4、 开发配置: 进入支付宝开放平台,进入应用详情,可在「开发设置」模块中根据流程配置加密证书等信息{gird column="2" gap="10"}{gird-item}{mtitle title="根据提示下载工具生成CSR文件"/}{/gird-item}{gird-item}{mtitle title="上传生成的CSR文件"/}{/gird-item}{gird-item}{mtitle title="手机验证"/}{/gird-item}{gird-item}{mtitle title="证书生成结果"/}{/gird-item}{/gird}5、发布上线:申请完的应用都是开发中状态,前期验证阶段踩了些小坑,以为配置好并生成证书就可以着手开发对接了,后面才发现状态为开发中的应用,是无法调用线上正式接口的,需要应用上线才行,所以我们在「开发设置」中配置好以后就直接提交审核就好,一般提交审核一天左右就可以审核通过,审核通过就可以正式开发对接了。6、开发对接:开发对接根据支付宝官网的文档来就好,现在对接支付宝可以直接使用高度封装的 SDK,开发速度上快了不少。电脑网站支付产品的文档链接为:https://opendocs.alipay.com/open/270/105898?pathHash=b3b2b667 。前期尝试了使用 v3 版本的对接方式(文档链接为: https://opendocs.alipay.com/open-v3/05w3qc ),但一直报证书方面的错误,后改用旧版本的 SDK 没发现问题。直接上代码码了那么多字,总算到最简单的环节了,我们的后端服务核心技术栈:JDK17 + SpringBoot 2.7.x + lombok + Mybatis-plus + MySQL + Hutool,使用 Maven 构建项目,所以我们这个示例工程也是基于上面的技术栈。以下是支付相关的核心代码:一、准备工作1、引入 Alipay SDK在项目 pom.xml 文件 的 dependencies 节点中加入 Alipay SDK 的依赖:<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.39.104.ALL</version> </dependency>由于 V3 版本没有验证通过,这里我们没有根据文档站中的建议采用 V3 版本。2、定义配置类跟支付宝对接相关的信息我们统一写到 application.yml 配置文件中, 使用一个配置类来快速加载配置文件中的数据。package com.paratera.console.pay.config; import com.alipay.api.AlipayConfig; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "alipay") @Data public class AlipayProperties extends AlipayConfig { /** * 支付成功通知URL */ private String notifyUrl; }然后,我们在application.yml 中添加如下配置信息:alipay: appId: 2021004153624250 serverUrl: https://openapi.alipay.com/gateway.do notifyUrl: https://paydemo.work.pojian.online/pay/ali/notify privateKey: MIIEvgIBADANBgkqhkiG9w0BAQEF....RMXJeS alipayPublicCertPath: /alipay/alipayCertPublicKey_RSA2.crt rootCertPath: /alipay/alipayRootCert.crt appCertPath: /alipay/appCertPublicKey_2021004153624250.crt encryptKey: EfaaDErfTRvO2islsjk3thAQ==3、定义签名校验方法为避免一些没必要的安全隐患,后端接收到支付宝推送的通知内容需要做验签操作,验签通过后再进行核心业务操作。/** * 验签 * @param paramMap * @return */ public boolean checkSign(Map<String, String> paramMap) { try { String alipayPublicKey = alipayProperties.getAlipayPublicKey(); if (ObjectUtil.isEmpty(alipayPublicKey)) { String alipayPublicCertPath = alipayProperties.getAlipayPublicCertPath(); alipayPublicKey = AlipaySignature.getAlipayPublicKey(alipayPublicCertPath); alipayProperties.setAlipayPublicKey(alipayPublicKey); } return AlipaySignature.rsaCheckV1(paramMap, alipayPublicKey, alipayProperties.getCharset(), "RSA2"); } catch (AlipayApiException e) { throw new RuntimeException(e); } }二、对接「统一收单下单并支付页面接口」1、controller 定义支付申请入口@PostMapping(path="/pay-prepare") @ResponseBody public String pay(PayForm payForm) throws AlipayApiException { String payHtml = alipayService.payPrepare(payForm); if (ObjectUtil.isEmpty(payHtml)) { return "支付失败"; } return payHtml; }PayForm 主要定义充值相关的字段,如充值金额、用户Id、邮箱、备注等,根据业务自己来就好,我这里采用最简的数据:package com.paratera.console.pay.model.form; import lombok.Data; import javax.validation.constraints.NotBlank; /** * 支付表单 */ @Data public class PayForm { /**0 * 用户Id */ @NotBlank(message = "用户Id不能为空") private String userId; /** * 支付金额 */ @NotBlank(message = "支付金额不能为空") private String money; }2、业务实现 AlipayService.payPrepare()public String payPrepare(PayForm payForm) throws AlipayApiException { Faker faker = new Faker(Locale.CHINA); CertAlipayRequest alipayConfig = new CertAlipayRequest(); alipayConfig.setServerUrl(alipayProperties.getServerUrl()); alipayConfig.setAppId(alipayProperties.getAppId()); alipayConfig.setPrivateKey(alipayProperties.getPrivateKey()); alipayConfig.setAlipayPublicCertPath(alipayProperties.getAlipayPublicCertPath()); alipayConfig.setCertPath(alipayProperties.getAppCertPath()); alipayConfig.setSignType("RSA2"); alipayConfig.setCharset("UTF-8"); alipayConfig.setFormat("json"); alipayConfig.setRootCertPath(alipayProperties.getRootCertPath()); alipayConfig.setEncryptor(alipayProperties.getEncryptKey()); AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig); // 实例化客户端 AlipayTradePagePayRequest payRequest = new AlipayTradePagePayRequest(); payRequest.setReturnUrl("https://paydemo.work.pojian.online/payResult.html"); payRequest.setNotifyUrl(alipayProperties.getNotifyUrl()); AlipayTradePagePayModel alipayTradePayModel = new AlipayTradePagePayModel(); // 调用 alipay.trade.pay LocalDateTime payTime = LocalDateTimeUtil.now(); String orderNo = "PAY" + DateUtil.format(payTime, DatePattern.PURE_DATE_PATTERN) + faker.number().digits(6); alipayTradePayModel.setOutTradeNo(orderNo); alipayTradePayModel.setTotalAmount(payForm.getMoney()); alipayTradePayModel.setSubject("控制台充值-" + DateUtil.format(payTime, DatePattern.PURE_DATE_PATTERN) + "-" + payForm.getMoney() + "元"); alipayTradePayModel.setProductCode("FAST_INSTANT_TRADE_PAY"); payRequest.setBizModel(alipayTradePayModel); AlipayTradePagePayResponse response = alipayClient.pageExecute(payRequest, "POST"); // 发起调用 String pageRedirectionData = response.getBody(); // log.info(pageRedirectionData); if (response.isSuccess()) { // log.info("调用成功"); // 保存充值记录 AddRechargePayForm addForm = new AddRechargePayForm(); addForm.setOrderNo(orderNo); addForm.setSubject(alipayTradePayModel.getSubject()); addForm.setPayMethod("ALIPAY"); addForm.setUserId(payForm.getUserId()); // 原单位是元,需要转换为分 BigDecimal money = new BigDecimal(payForm.getMoney()).multiply(BigDecimal.valueOf(100)); addForm.setPayMoney(money); rechargePayService.addRechargePay(addForm); return pageRedirectionData; } else { log.error("调用失败"); // sdk版本是"4.38.0.ALL"及以上,可以参考下面的示例获取诊断链接 String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response); log.error(diagnosisUrl); } return null; }上面的过程主要是生成如下所示的包含签名数据的html代码,在网页里通过自动提交表单的方式跳转到支付宝的付款页面:<form name="punchout_form" method="post" action="https://openapi.alipay.com/gateway.do?app_cert_sn=653da2afe61d430fcc9376b008245959&charset=UTF-8&alipay_root_cert_sn=687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6&method=alipay.trade.page.pay&sign=gQOZ2g8xU9lyA2k55FJNSTbleinvaFA%2FjrfuUInElycpytnWweBVDECnC0q7d2tZ%2FOQvxQc5%2Fyxm2OBTc7ERa%2Fs5%2Fi4vTAD2QyukrlKLN1SleGZP5%2FihTpcB%2BM5WkpHyQjsRrnHeFHB3i8Nzfc%2B9ICdz5xJCx8dlu%2BVN9qb51hx9y4eyTvWd2XY4%2BfmW3RHsE8PYQ6VU9YtMP8wjFonaPMExY9WnpJn74fNVj8wLHmxYemxkW80qXhbOxGik2qHY9NWfkWZsYhzMbBu5%2BUBXWdprlbakThSPqXjNpagUMmdPNiGjvLWy3XYtFHfYsOQdbIIPrPlhuQIYmmUfJF9M3w%3D%3D&return_url=https%3A%2F%2Fpaydemo.work.pojian.online%2FpayResult.html¬ify_url=https%3A%2F%2Fpaydemo.work.pojian.online%2Fpay%2Fali%2Fnotify&version=1.0&app_id=2021004153624250&sign_type=RSA2×tamp=2024-06-26+18%3A15%3A01&alipay_sdk=alipay-sdk-java-4.39.104.ALL&format=json"> <input type="hidden" name="biz_content" value="{"out_trade_no":"PAY20240626445677","product_code":"FAST_INSTANT_TRADE_PAY","subject":"控制台充值-20240626-0.1元","total_amount":"0.1"}"> <input type="submit" value="立即支付" style="display:none" > </form> <script>document.forms[0].submit();</script>二、对接支付结果的异步通知发起支付操作的时候我们有指定两个URL:return_url:用于支付完成后跳转到我们的页面给用户展示支付结果notify_url:用于支付完成后通知后端更新支付结果,由于return_url给的支付结果一般不可信,所以我们一般采用这个地址告诉支付宝,用户支付完后异步通知我们哪个接口1、定义接收通知的接口/** * 支付宝付款通知 * * https://opendocs.alipay.com/open/270/105902?pathHash=d5cd617e&ref=api * @return */ @RequestMapping("/notify") public String payNotify(HttpServletRequest request) throws UnsupportedEncodingException { log.info("====== 开始接收支付宝支付回调通知 ======"); Map<String, String> paramMap = new HashMap<>(); Map<String, String[]> requestParams = request.getParameterMap(); log.info("获取支付宝POST过来反馈信息"); for (String name : requestParams.keySet()) { String[] values = requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } paramMap.put(name, valueStr); } log.info("通知请求数据:{}", JSONUtil.toJsonStr(paramMap)); if(ObjectUtil.isNotEmpty(paramMap)) { log.info("支付宝回调URL参数:{}", JSONUtil.toJsonStr(paramMap)); boolean signVerified = alipayService.checkSign(paramMap); if (signVerified) { if ("TRADE_SUCCESS".equals(paramMap.get("trade_status"))) { rechargePayService.paid(paramMap.get("out_trade_no"), JSONUtil.toJsonStr(paramMap)); } } // {"gmt_create":["2024-06-25 15:18:10"],"charset":["UTF-8"],"gmt_payment":["2024-06-25 15:18:22"],"notify_time":["2024-06-25 15:18:23"],"subject":["Jacob Have I Loved"],"sign":["CM2qWtZ4rCmvqFyRucgANQJAz2k0SsroaU3uDEK28kgxkcHC29a64jquy4/qRfEiX4VuJKdmxkHG3+hEAC/9/qOgl8mG/GGSPlq+B90fg25aymYlKoM6wYOzEqooOLj2LCtj8Nq1WKaw9pTwYK/GlDdVLsUB8bSpkpjZ6vrhhzjeXK38wDgZuNKle9JdFCQLO/f1vcyM+h4SQpkurg/jTYB7oQQWk/ZsQAqC/XM/laKrPMPrA4SYb8hcC0UIQD8BLlCZiXaEj/+Q6CTJQffGyW2q6trTEhySgb4I3OwTjqtbn1BlafFCTm1OYgMlVLZu5U+iwvhPW2Xmz8A2v3RWIQ=="],"merchant_app_id":["2021004153624250"],"buyer_open_id":["037QDGdQKn6R2z7On4z_OJrBOzaNN0z5Obtjr_T4aftVdU9"],"invoice_amount":["0.10"],"version":["1.0"],"notify_id":["2024062501222151823038371452266751"],"fund_bill_list":["[{\"amount\":\"0.10\",\"fundChannel\":\"ALIPAYACCOUNT\"}]"],"notify_type":["trade_status_sync"],"out_trade_no":["PAY20240625635868"],"total_amount":["0.10"],"trade_status":["TRADE_SUCCESS"],"trade_no":["2024062522001438371427121571"],"auth_app_id":["2021004153624250"],"receipt_amount":["0.10"],"point_amount":["0.00"],"buyer_pay_amount":["0.10"],"app_id":["2021004153624250"],"sign_type":["RSA2"],"seller_id":["2088641848623112"]} } return "success"; }2、核心逻辑说明alipayService.checkSign()这个验签方法在前面的章节已经定义过,这里就不着重介绍了。rechargePayService.paid()是内部业务逻辑,本示例中主要是存储支付宝推送的数据并改变订单状态,在这里就不做代码罗列了。示例工程的几个核心页面效果为更好的展示示例效果,在示例工程开发过程中做了些界面展示,可通过如下链接体验完整功能:https://paydemo.work.pojian.online/相关核心界面截图如下:{gird column="2" gap="10"}{gird-item}{mtitle title="发起支付"/}{/gird-item}{gird-item}{mtitle title="用户使用支付宝完成支付"/}{/gird-item}{gird-item}{mtitle title="支付成功截图"/}{/gird-item}{gird-item}{mtitle title="订单列表"/}{/gird-item}{/gird}
2024年06月29日
68 阅读
0 评论
0 点赞