ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CodeGuide 场景训练:基于 DDD 的商品下单支付场景设计与支付宝沙箱对接实战

CodeGuide 场景训练:基于 DDD 的商品下单支付场景设计与支付宝沙箱对接实战 文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载本篇技术指南聚焦于 CodeGuide 开源仓库中《小场景训练营》系列的「商品下单支付」场景以支付宝沙箱Sandbox为支付渠道先以最小可运行案例打通支付闭环再以 DDD 领域驱动设计将订单、支付单、回调、补偿纳入工程实现。读完本文你将掌握支付宝沙箱的申请与配置方法、Alipay 官方 SDK 的对接要点、DDD 分层工程app / domain / infrastructure / trigger中订单创建与支付回调的实现思路以及围绕「掉单、回调失败、MQ 发送失败」的最终一致性与定时补偿设计。一、场景说明为什么支付对接要「先建订单再造支付单」凡是对接第三方支付的业务商城、出行、外卖……在真正调用支付宝之前都会先在自己的系统里创建一条订单再基于订单的唯一 ID 创建支付单。原因在于外部支付接口与自己系统的流程无法放进同一个数据库事务两者之间必然存在异步与网络不确定性因此必须考虑最终一致性通过幂等可重试来兜底。整个支付场景的流程节点可以归纳为创建订单 → 创建支付单由购物车或商品触发下单先保存订单再用订单唯一 ID 生成支付单并调用支付宝沙箱接口拿到支付表单或支付链接。支付回调商品支付完成后支付宝会回调你提供的一个接口用于接收支付完成通知。该通知用于变更支付单状态并触发自己系统内的异步 MQ 消息或事件总线继续处理后续流程发货、充值、开会员等。补偿机制支付掉单、回调失败、MQ 发送失败在各个流程节点都有唯一 ID所以都可以通过定时任务扫描、补偿处理保证最终一致。换言之只要流程中存在不是一次数据库事务提交就能完成的操作http 接口、rpc 接口、mq 消息就必须有任务做补偿处理接口必须携带唯一幂等字段确保重复消费时也只会产生一条记录或一次变更。这一点在仓库的另一篇配套文档 《小型支付商城系统》第3-5节对接支付 中也有同样的强调。二、支付申请支付宝沙箱应用、秘钥工具与秘钥填写支付宝开放平台提供沙箱环境任何人都可以申请不需要企业资质非常适合学习支付对接。申请与配置主要分三步。1. 创建沙箱应用进入支付宝开放平台的开发者中心开放平台 → 开发管理创建/开启沙箱应用。开启后页面会提供沙箱应用信息包含应用 IDAPPID后续代码里的app_id就取自这里。沙箱账号提供了后续在网页支付页面输入的账号、密码和支付密码模拟买家。沙箱工具提供了安卓版测试软件可在手机端扫码支付。2. 下载秘钥工具生成密钥对到支付宝开放平台文档中心下载「支付宝开放平台秘钥工具」。使用工具创建一对 RSA2 密钥应用公钥 / 应用私钥工具生成的一对密钥公钥要填写到开放平台网页上私钥留给程序在本地使用即下文merchant_private_key。支付宝公钥当你在网页上填写保存应用公钥后开放平台会返回对应的支付宝公钥即下文alipay_public_key用于验证支付宝回传内容的签名。3. 秘钥填写将秘钥工具中生成的应用公钥复制到开放平台的密钥配置页面中填写保存保存后即可看到对应的支付宝公钥。至此沙箱应用、应用私钥、支付宝公钥三要素齐备就可以编码对接了。对应仓库文档《小型支付商城系统》第4-3节支付宝沙箱申请使用 与本篇同源可交叉阅读。三、简单案例用官方 SDK 跑通第一个支付申请完成后先不要直接进入 DDD 工程而是用一个最小可运行的单测把支付链路打通。这个支付案例位于工程代码xfg-dev-tech-app的ApiTest下。1. 后端代码ApiTest源码类cn.bugstack.xfg.dev.tech.test.ApiTestSlf4j public class ApiTest { // 「沙箱环境」应用ID - 您的APPID收款账号既是你的APPID对应支付宝账号 public static String app_id 9021000132689924; // 「沙箱环境」商户私钥你的PKCS8格式RSA2私钥 public static String merchant_private_key 上文中【秘钥工具】所创建的公户私钥; // 「沙箱环境」支付宝公钥 public static String alipay_public_key 上文中【秘钥填写】后提供给你的支付宝公钥; // 「沙箱环境」服务器异步通知页面路径用 natapp.cn 内网穿透暴露到公网 public static String notify_url https://xfg.natapp.cn/api/v1/alipay/alipay_notify_url; // 「沙箱环境」页面跳转同步通知页面路径需 http:// 格式的完整路径必须外网可正常访问才会同步跳转 public static String return_url https://gaga.plus; // 「沙箱环境」网关地址固定值 public static String gatewayUrl https://openapi-sandbox.dl.alipaydev.com/gateway.do; // 签名方式固定值 public static String sign_type RSA2; // 字符编码格式固定值 public static String charset utf-8; Test public void test_AliPay() throws AlipayApiException { AlipayClient alipayClient new DefaultAlipayClient(gatewayUrl, app_id, merchant_private_key, json, charset, alipay_public_key, sign_type); AlipayTradePagePayRequest request new AlipayTradePagePayRequest(); // 发送请求的 Request类 request.setNotifyUrl(notify_url); request.setReturnUrl(return_url); JSONObject bizContent new JSONObject(); bizContent.put(out_trade_no, daniel82AAAA000032333361X02); // 我们自己生成的订单编号 bizContent.put(total_amount, 0.01); // 订单的总金额 bizContent.put(subject, 测试商品); // 支付的名称 bizContent.put(product_code, FAST_INSTANT_TRADE_PAY); // 固定配置 request.setBizContent(bizContent.toString()); String form alipayClient.pageExecute(request).getBody(); log.info(测试结果{}, form); } }2. 支付配置参数说明参数含义与取值app_id应用 IDAPPID收款账号即你 APPID 对应的支付宝账号取自开放平台沙箱应用merchant_private_key商户私钥通过秘钥工具创建公钥填写到网页上私钥程序里使用alipay_public_key支付宝公钥网页上填写应用公钥后由平台返回notify_url服务器异步通知回调地址即支付完成后支付宝调用的地址。本地测试外网访问不到需用 natapp 做内网穿透return_url支付完成后浏览器同步跳转的地址gatewayUrl支付宝沙箱环境网关地址固定值sign_type签名方式固定RSA2charset字符编码固定utf-83. 调用配置参数说明参数含义out_trade_no你的商户订单号用你的单号生成支付单信息total_amount支付金额沙箱可填 0.01 验证subject商品名称product_code固定值FAST_INSTANT_TRADE_PAY即时到账电脑网站支付4. 测试结果运行test_AliPay后日志会输出一份 HTML 脚本alipayClient.pageExecute(request).getBody()的返回值形如form namepunchout_form methodpost actionhttps://openapi-sandbox.dl.alipaydev.com/gateway.do?charsetutf-8methodalipay.trade.page.paysign...return_url...notify_url...version1.0app_id9021000132689924sign_typeRSA2timestamp...alipay_sdkalipay-sdk-java-4.38.157.ALLformatjson input typehidden namebiz_content value{quot;out_trade_noquot;:quot;daniel82AAAA000032333361X03quot;,quot;total_amountquot;:quot;0.01quot;,quot;subjectquot;:quot;测试商品quot;,quot;product_codequot;:quot;FAST_INSTANT_TRADE_PAYquot;} input typesubmit value立即支付 styledisplay:none /form scriptdocument.forms[0].submit();/script运行后得到的就是一份可支付表单脚本在浏览器打开即可跳转到支付宝沙箱支付页。如果只是想少量存储信息也可以把必要的数据存到数据库不必全量存储。5. 前端代码form.html把后端运行生成的 HTML 脚本完整粘贴到form.html文件中之后用浏览器打开。打开后会跳转到支付宝支付页面此时可以从开放平台的「沙箱账号」中获取测试账号信息完成支付。至此支付宝沙箱的申请与最小案例已跑通接下来进入 DDD 场景化工程实现。四、场景介绍从能支付到会设计支付本身是一个对接渠道真正有价值的是把它结合到实际业务中流程的执行、订单的创建、数据的保存、回调的处理等都是实际业务要做的内容。1. 业务流程典型流程为用户在商品页面点击下单 → 后端基于购物车实体创建订单聚合商品、订单等实体→ 保存订单后用订单 ID 创建支付单并调用支付宝沙箱 → 返回支付表单 → 用户完成支付 → 支付宝异步回调pay_notify→ 验签通过后更新订单为已支付 → 通过事件总线/MQ 触发后续业务。2. 库表设计围绕该场景至少需要两类核心表订单表记录商品 ID、商品名称、订单号唯一、订单金额、订单状态CREATE 创建 / PAY_WAIT 待支付 / 已支付等、下单时间。支付单订单关联表记录支付单号、商户订单号out_trade_no、支付宝交易号trade_no、支付金额、支付回调时间gmt_payment、支付状态、支付 URL 等。所有流程节点都有唯一 ID这正是后面掉单补偿、回调失败补偿、MQ 补偿能够落地的数据结构基础。具体的初始化库表脚本可参考工程dev-ops/mysql/sql/xfg-dev-tech-alipay-sandbox.sql见下文环境安装。五、工程实现DDD 四层架构下的支付场景本场景工程为「小场景训练营」的一部分采用 DDD 分层架构整体结构如下分层思想可对照仓库内 DDD 专题案例一《初识领域驱动设计 DDD 落地》 学习xfg-dev-tech-alipay-sandbox ├── docs # 工程所需资源docker 安装 mysql、natapp 内网穿透、front 前端页面 ├── app # 应用启动层config 配置、resources 资源加载、test 测试 ├── domain # 领域层核心业务逻辑 ├── infrastructure # 基础设施层管理持久化数据 └── trigger # 触发器层统一管理 http、mq、listener、job 等入口调用分层职责一览docs提供工程所需的资源包括 docker 安装 MySQL、natapp 内网穿透配置需要自己申请一个渠道、front 前端页面。app应用的启动层处理 config 配置和 resources 资源加载以及 test 模块的测试。domain领域层处理核心业务逻辑订单服务、仓储接口、领域模型与聚合。infrastructure基础设施层如管理持久化数据仓储实现、DAO。trigger触发器层统一管理 http、mq、listener、job 等入口调用支付下单接口、回调接口都属于这一层。1. 环境安装MySQL工程docs下提供了 docker-compose 编排执行docker-compose up -d即可一键拉起 MySQL 8.0 phpMyAdmin并自动将dev-ops/mysql/sql下的建表脚本导入数据库version: 3.9 services: mysql: image: mysql:8.0.32 container_name: mysql command: --default-authentication-pluginmysql_native_password restart: always environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: 123456 MYSQL_USER: xfg MYSQL_PASSWORD: 123456 depends_on: - mysql-job-dbdata ports: - 13306:3306 volumes: - ./sql:/docker-entrypoint-initdb.d volumes_from: - mysql-job-dbdata healthcheck: test: [ CMD, mysqladmin ,ping, -h, localhost ] interval: 5s timeout: 10s retries: 10 start_period: 15s networks: - my-network # phpmyadmin phpmyadmin: image: phpmyadmin:5.2.1 container_name: phpmyadmin hostname: phpmyadmin ports: - 8899:80 environment: - PMA_HOSTmysql - PMA_PORT3306 - MYSQL_ROOT_PASSWORD123qwe!#QWE depends_on: mysql: condition: service_healthy networks: - my-network # 自动加载数据 mysql-job-dbdata: image: alpine:3.18.2 container_name: mysql-job-dbdata volumes: - /var/lib/mysql networks: my-network: driver: bridge在 Docker 下安装环境时会自动把dev-ops/mysql/sql下的脚本创建到数据库中。同时提供 phpMyAdmin可直接在网页上管理数据库端口 8899宿主端口映射 13306 到容器 3306。如果本地已有 MySQL 8.x也可以直接导入数据库脚本dev-ops/mysql/sql/xfg-dev-tech-alipay-sandbox.sql低版本数据库需要自行创建库表。2. 环境安装natapp 内网穿透支付宝的回调只能访问公网地址本地开发环境必须借助内网穿透工具让支付宝能回调到你本地的服务。登录 natapp开通自己的免费/付费渠道用于内网穿透。本地内网穿透的地址即可被公网访问也就是可以被支付宝回调。开通后登录复制自己的渠道 token粘贴到config.ini文件的authtoken下Windows 电脑也可以在官网直接下载客户端。提示在完整的对接如公众号回调、支付宝回调中建议选择付费渠道以保证稳定性与域名固定可参考仓库中 微信扫码登录场景 的内网穿透经验。3. 工程配置AliPayConfig源码cn.bugstack.xfg.dev.tech.config.AliPayConfigConfiguration EnableConfigurationProperties(AliPayConfigProperties.class) public class AliPayConfig { Bean(name alipayClient) ConditionalOnProperty(value alipay.enabled, havingValue true, matchIfMissing false) public AlipayClient alipayClient(AliPayConfigProperties properties){ return new DefaultAlipayClient(properties.getGatewayUrl(), properties.getApp_id(), properties.getMerchant_private_key(), properties.getFormat(), properties.getCharset(), properties.getAlipay_public_key(), properties.getSign_type()); } }对应配置文件application-dev.yml# 支付宝支付 - 沙箱 alipay: enabled: true app_id: 9021000132689924 merchant_private_key: alipay_public_key: notify_url: http://xfg-studio.natapp1.cc/api/v1/alipay/pay_notify return_url: https://gaga.plus gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.doConditionalOnProperty(value alipay.enabled, havingValue true, matchIfMissing false)使支付宝客户端只有在alipay.enabledtrue时才注入 Spring 容器方便环境切换与本地调试。参照【简单案例】配置app_id、merchant_private_key、alipay_public_key、notify_url等参数即可。notify_url这里配置的是 natapp 映射出的公网地址http://xfg-studio.natapp1.cc/api/v1/alipay/pay_notify把本地8091端口映射到了公网。4. 代码实现订单服务接口源码cn.bugstack.xfg.dev.tech.domain.service.IOrderServicepublic interface IOrderService { /** * 通过购物车实体对象创建支付单实体用于支付—— 所有的订单下单都从购物车开始触发 * * param shopCartEntity 购物车实体 * return 支付单实体 */ PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception; /** * 更新订单状态 * param orderId 订单ID */ void changeOrderPaySuccess(String orderId); }createOrder接收购物车实体userId productId内部完成查掉单 → 建订单 → 保存订单 → 建支付单的完整编排返回带payUrl的支付单实体。changeOrderPaySuccess供回调场景使用按订单 ID 将订单状态更新为已支付。5. 代码实现模板模式编排下单流程源码cn.bugstack.xfg.dev.tech.domain.service.AbstractOrderServiceOverride public PayOrderEntity createOrder(ShopCartEntity shopCartEntity) throws Exception{ // 1. 查询当前用户是否存在掉单和未支付订单 OrderEntity unpaidOrderEntity repository.queryUnPayOrder(shopCartEntity); if (null ! unpaidOrderEntity OrderStatusVO.PAY_WAIT.equals(unpaidOrderEntity.getOrderStatus())) { log.info(创建订单-存在已存在未支付订单。userId:{} productId:{} orderId:{}, shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId()); return PayOrderEntity.builder() .orderId(unpaidOrderEntity.getOrderId()) .payUrl(unpaidOrderEntity.getPayUrl()) .build(); } else if (null ! unpaidOrderEntity OrderStatusVO.CREATE.equals(unpaidOrderEntity.getOrderStatus())) { log.info(创建订单-存在存在未创建支付单订单创建支付单开始 userId:{} productId:{} orderId:{}, shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getOrderId()); PayOrderEntity payOrderEntity this.doPrepayOrder(shopCartEntity.getUserId(), shopCartEntity.getProductId(), unpaidOrderEntity.getProductName(), unpaidOrderEntity.getOrderId(), unpaidOrderEntity.getTotalAmount()); return PayOrderEntity.builder() .orderId(payOrderEntity.getOrderId()) .payUrl(payOrderEntity.getPayUrl()) .build(); } // 2. 查询商品 聚合订单 ProductEntity productEntity repository.queryProductByProductId(shopCartEntity.getProductId()); OrderEntity orderEntity OrderEntity.builder() .productId(productEntity.getProductId()) .productName(productEntity.getProductName()) .orderId(RandomStringUtils.randomNumeric(16)) .orderTime(new Date()) .orderStatus(OrderStatusVO.CREATE) .build(); CreateOrderAggregate orderAggregate CreateOrderAggregate.builder() .userId(shopCartEntity.getUserId()) .productEntity(productEntity) .orderEntity(orderEntity) .build(); // 3. 保存订单 - 保存一份订单再用订单生成ID生成支付单信息 this.doSaveOrder(orderAggregate); // 4. 创建支付单 PayOrderEntity payOrderEntity this.doPrepayOrder(shopCartEntity.getUserId(), productEntity.getProductId(), productEntity.getProductName(), orderEntity.getOrderId(), productEntity.getPrice()); log.info(创建订单-完成生成支付单。userId: {} orderId: {} payUrl: {}, shopCartEntity.getUserId(), orderEntity.getOrderId(), payOrderEntity.getPayUrl()); return PayOrderEntity.builder() .orderId(payOrderEntity.getOrderId()) .payUrl(payOrderEntity.getPayUrl()) .build(); }这段代码正是场景说明中最终一致 幂等可重试的落地体现幂等防重下单前先queryUnPayOrder查询掉单/未支付订单。若存在未支付订单PAY_WAIT直接返回原支付单的payUrl避免重复下单重复调支付若存在已建订单但未创建支付单CREATE则基于原订单补齐支付单。只有完全不存在时才新建订单。唯一 ID订单号通过RandomStringUtils.randomNumeric(16)生成 16 位纯数字唯一单号作为贯穿下单、支付、回调、补偿全流程的幂等键。模板模式抽象类AbstractOrderService定义了下单流程的标准骨架查掉单、查商品、聚合并保存订单、创建支付单把可变的子步骤如支付预下单doPrepayOrder、保存订单doSaveOrder作为抽象方法交由子类实现便于扩展不同支付渠道。6. 代码实现支付下单接口源码cn.bugstack.xfg.dev.tech.trigger.http.AliPayController#createParOrderRequestMapping(value create_pay_order, method RequestMethod.POST) public ResponseString createParOrder(RequestParam String userId, RequestParam String productId) { try { log.info(商品下单根据商品ID创建支付单开始 userId:{} productId:{}, userId, productId); ShopCartEntity shopCartEntity ShopCartEntity.builder().userId(userId).productId(productId).build(); PayOrderEntity payOrderEntity orderService.createOrder(shopCartEntity); log.info(商品下单根据商品ID创建支付单完成 userId:{} productId:{} orderId:{}, userId, productId, payOrderEntity.getOrderId()); return Response.Stringbuilder() .code(ResponseCode.SUCCESS.getCode()) .info(ResponseCode.SUCCESS.getInfo()) .data(payOrderEntity.getPayUrl()) .build(); } catch (Exception e) { log.error(商品下单根据商品ID创建支付单失败 userId:{} productId:{}, userId, productId, e); return Response.Stringbuilder() .code(ResponseCode.UN_ERROR.getCode()) .info(ResponseCode.UN_ERROR.getInfo()) .build(); } }本地测试地址http://localhost:8091/api/v1/alipay/create_pay_order?userId1001productId100001说明这里模拟了一个通过调用接口创建支付单的实现——HTTP 入口trigger 层只负责参数接收与统一响应封装真实业务编排下沉到 domain 层orderService.createOrder这就是 DDD 分层入口薄、领域厚的典型写法。返回的data字段即为支付宝支付表单payUrl。7. 代码实现支付回调接口源码cn.bugstack.xfg.dev.tech.trigger.http.AliPayController#payNotifyRequestMapping(value pay_notify, method RequestMethod.POST) public String payNotify(HttpServletRequest request) { try { log.info(支付回调消息接收 {}, request.getParameter(trade_status)); if (request.getParameter(trade_status).equals(TRADE_SUCCESS)) { MapString, String params new HashMap(); MapString, String[] requestParams request.getParameterMap(); for (String name : requestParams.keySet()) { params.put(name, request.getParameter(name)); } String tradeNo params.get(out_trade_no); String gmtPayment params.get(gmt_payment); String alipayTradeNo params.get(trade_no); String sign params.get(sign); String content AlipaySignature.getSignCheckContentV1(params); boolean checkSignature AlipaySignature.rsa256CheckContent(content, sign, alipayPublicKey, UTF-8); // 验证签名 // 支付宝验签 if (checkSignature) { // 验签通过 log.info(支付回调交易名称: {}, params.get(subject)); log.info(支付回调交易状态: {}, params.get(trade_status)); log.info(支付回调支付宝交易凭证号: {}, params.get(trade_no)); log.info(支付回调商户订单号: {}, params.get(out_trade_no)); log.info(支付回调交易金额: {}, params.get(total_amount)); log.info(支付回调买家在支付宝唯一id: {}, params.get(buyer_id)); log.info(支付回调买家付款时间: {}, params.get(gmt_payment)); log.info(支付回调买家付款金额: {}, params.get(buyer_pay_amount)); log.info(支付回调支付回调更新订单 {}, tradeNo); // 更新订单未已支付 orderService.changeOrderPaySuccess(tradeNo); // 推送消息【自己的业务场景中可以使用MQ消息】 eventBus.post(tradeNo); } } return success; } catch (Exception e) { log.error(支付回调处理失败, e); return false; } }回调地址http://xfg-studio.natapp1.cc/api/v1/alipay/pay_notify本地服务默认https://localhost:8091通过 natapp 把本地地址 端口映射到公网地址。说明回调是支付宝调用我们的接口。处理要点如下先判断交易状态仅当trade_status TRADE_SUCCESS时才视为支付成功。验签收集全部回调参数后用AlipaySignature.getSignCheckContentV1(params)拼装签名原文再用AlipaySignature.rsa256CheckContent(content, sign, alipayPublicKey, UTF-8)以支付宝公钥校验 RSA2 签名。验签是支付回调的安全底线——防止伪造回调、越权改单。更新订单验签通过后按商户订单号out_trade_no调用orderService.changeOrderPaySuccess(tradeNo)将订单置为已支付。异步触发后续通过eventBus.post(tradeNo)推送支付成功消息生产环境可替换为 MQ由监听器处理发货、充值、开会员等下游动作。应答约定处理成功返回字符串success异常返回false。支付宝会对未收到success的回调进行重试这也要求回调处理本身具备幂等性重复回调重复更新同一订单是安全的。六、功能验证从启动到支付完成的全链路1. 前置准备安装 MySQL 8.0 数据库并初始化库表执行 docker-compose 或导入dev-ops/mysql/sql/xfg-dev-tech-alipay-sandbox.sql。如果是低版本数据库需要自行创建库表。打开 natapp 配置一个自己的内网穿透渠道渠道内配置工程映射地址8091若为其他端口则配置对应地址之后本地启动 natapp。启动xfg-dev-tech-alipay-sandbox应用。2. 测试步骤启动 natapp本地运行 natapp 客户端观察公网地址映射成功如xfg-studio.natapp1.cc。启动工程启动 SpringBoot 应用服务监听8091端口。下单页面进入工程docs/dev-ops/front目录打开index.html文件点击下单此时会进入支付页面。商品支付页面跳转到支付宝沙箱支付页使用【沙箱账号】提供的账号、密码、支付密码完成支付也可以使用沙箱工具手机扫码支付。库表数据支付完成后可到 phpMyAdmin 或本地数据库查看订单表、支付单表的数据状态变更。3. 日志分析以下是整个项目运行过程的典型日志可以清楚看到每一步的操作过程23-12-17.15:11:55.692 [http-nio-8091-exec-4] INFO AliPayController - 商品下单根据商品ID创建支付单开始 userId:xiaofuge productId:100010090091 23-12-17.15:11:55.908 [http-nio-8091-exec-4] INFO AbstractOrderService - 创建订单-完成生成支付单。userId: xiaofuge orderId: 1620564694789743 payUrl: form namepunchout_form ... actionhttps://openapi-sandbox.dl.alipaydev.com/gateway.do?... input typehidden namebiz_content value{quot;out_trade_noquot;:quot;1620564694789743quot;,quot;total_amountquot;:quot;1.68quot;,quot;subjectquot;:quot;测试商品quot;,quot;product_codequot;:quot;FAST_INSTANT_TRADE_PAYquot;} input typesubmit value立即支付 styledisplay:none /form scriptdocument.forms[0].submit();/script 23-12-17.15:11:55.909 [http-nio-8091-exec-4] INFO AliPayController - 商品下单根据商品ID创建支付单完成 userId:xiaofuge productId:100010090091 orderId:1620564694789743 23-12-17.15:12:37.553 [http-nio-8091-exec-5] INFO AliPayController - 支付回调消息接收 TRADE_SUCCESS 23-12-17.15:12:37.558 [http-nio-8091-exec-5] INFO AliPayController - 支付回调交易名称: 测试商品 23-12-17.15:12:37.558 [http-nio-8091-exec-5] INFO AliPayController - 支付回调交易状态: TRADE_SUCCESS 23-12-17.15:12:37.558 [http-nio-8091-exec-5] INFO AliPayController - 支付回调支付宝交易凭证号: 2023121722001413370501437659 23-12-17.15:12:37.558 [http-nio-8091-exec-5] INFO AliPayController - 支付回调商户订单号: 1620564694789743 23-12-17.15:12:37.558 [http-nio-8091-exec-5] INFO AliPayController - 支付回调交易金额: 1.68 23-12-17.15:12:37.562 [http-nio-8091-exec-5] INFO AliPayController - 支付回调买家在支付宝唯一id: 2088722024513370 23-12-17.15:12:37.562 [http-nio-8091-exec-5] INFO AliPayController - 支付回调买家付款时间: 2023-12-17 15:12:35 23-12-17.15:12:37.562 [http-nio-8091-exec-5] INFO AliPayController - 支付回调买家付款金额: 1.68 23-12-17.15:12:37.562 [http-nio-8091-exec-5] INFO AliPayController - 支付回调支付回调更新订单 1620564694789743 23-12-17.15:12:37.584 [http-nio-8091-exec-5] INFO OrderPaySuccessListener - 收到支付成功消息可以做接下来的事情了【发货、充值、开会员】orderId1620564694789743从日志可以完整复现整条链路下单接口创建订单并返回支付表单 → 用户支付 → 支付宝回调TRADE_SUCCESS→ 验签通过后打印交易明细 → 更新订单状态 → 事件总线发布支付成功消息 → 监听器收到消息处理后续业务。这与 《小型支付商城系统》第3-6节支付回调处理 中回调更新订单状态 发 MQ 掉单补偿/超时关单的诉求完全一致。七、总结一套可以复用到其他支付场景的设计骨架本节虽然以支付宝沙箱为载体但沉淀下来的设计骨架是通用的申请侧沙箱应用 秘钥工具生成 RSA2 密钥对 应用公钥/支付宝公钥互填任何第三方支付微信支付、虎皮椒、蓝兔等都是类似流程。对接侧DefaultAlipayClient网关、appId、商户私钥、格式、字符集、支付宝公钥、签名方式封装全部签名与调用细节AlipayTradePagePayRequest组装biz_content订单号、金额、标题、产品码。业务侧订单与支付单分离、唯一订单号幂等、queryUnPayOrder掉单复用、回调验签 状态机更新 事件/MQ 异步化、定时任务补偿共同保证非单事务流程的最终一致性。架构侧HTTP 入口收敛在 trigger 层业务编排在 domain 层模板模式持久化在 infrastructure 层配置与启动在 app 层——这正是 DDD 分层在真实支付场景中的落地范式可继续在仓库的 DDD 专题案例一 中深入学习其分层思想与聚合建模方法。赞分享文档教程后端【免费下载链接】CodeGuide:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总旨在为大家提供一个清晰详细的学习教程侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助请给予支持(关注、点赞、分享)项目地址https://gitcode.com/gh_mirrors/code/CodeGuide点击查看免费下载相关推荐终极简单的PHP支付宝支付集成方案单文件搞定所有支付场景终极简单的PHP支付宝支付集成方案单文件搞定所有支付场景 在当今电商时代支付宝支付已经成为网站和移动应用不可或缺的功能。然而传统的支付宝支付集成往往需要复杂后端金融科技AntdUI支付宝集成支付宝支付与商户功能对接AntdUI支付宝集成支付宝支付与商户功能对接 前言桌面应用支付集成的痛点 你是否曾经在开发WinForms桌面应用时为支付功能的集成而头疼传统的桌面应UI组件桌面应用Namo Turn Detector v1 - Korean vs 传统方法为什么97.3%准确率足以颠覆韩语对话系统Namo Turn Detector v1 Korean vs 传统方法为什么97.3%准确率足以颠覆韩语对话系统 在当今快速发展的对话式人工智能领域韩语对上一篇如何高效计算文件哈希值OpenHashTab核心组件与实现原理详解下一篇Plasmo框架Storybook集成扩展UI组件的独立开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表