首页
留言
友链
关于
Search
1
思源笔记docker私有化部署及使用体验分享
2,032 阅读
2
windows11 远程提示:为安全考虑,已锁定该用户帐户,原因是登录尝试或密码更改尝试过多。
1,004 阅读
3
Pointer-Focus:一款功能强大的教学、录屏辅助软件
566 阅读
4
使用cspell对项目做拼写规范检查
524 阅读
5
解决 nginxProxyManager 申请证书时的SSL失败问题
486 阅读
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
生活
其他
转载
软件
职场
登录
Search
标签搜索
docker
DevOps
magic-boot
Linux
酷壳
RabbitMQ
Node
git
工具
MybatisPlus
clickhouse
Syncthing
规范
前端
产品
nginx
markdown
axios
H5
经纬度
朱治龙
累计撰写
137
篇文章
累计收到
7
条评论
首页
栏目
Web前端
CSS
JavaScript
交互
Vue
小程序
后端
运维
生活
其他
转载
软件
职场
页面
留言
友链
关于
搜索到
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日
57 阅读
0 评论
0 点赞