1. 订单系统接支付宝为什么配置管理先崩了做订单系统的同学大概率都经历过这个阶段本地跑通了支付宝沙箱测试环境换一套密钥预发又换一套生产再换一套。每套环境里还分「应用私钥」「支付宝公钥」「AES 密钥」「MCP 场景受限密钥」散落在application.yml、Nacos、环境变量、甚至某个同事的聊天记录里。等到 Spring AI Agent 要同时调用本地订单 MCP Server 和支付宝 MCP Server 时问题集中爆发Agent 侧要配大模型 KeyMCP Client 侧要配多个 SSE 连接支付渠道侧还要配商户凭证三套东西混在一个配置文件里改一个参数要重启三个服务。这篇就聚焦这个痛点。场景是一个电商订单系统用户下单后Spring AI Agent 解析意图先调本地订单 MCP 生成待支付订单再调支付宝 MCP 生成支付链接用户支付完成后回调更新订单状态。核心不是教你写支付逻辑而是把「多支付渠道凭证 大模型 Key MCP 连接」统一收口到 TaoToken 的 Key/API 通道里集中管理让配置从「到处找密钥」变成「一处改、处处生效」。适合谁看已经在用 Spring Boot 写订单系统、想引入 Spring AI Agent 和 MCP 协议、但被多环境凭证管理折磨过的后端开发。读完你能拿到一份可复制的application.yml骨架、MCP 客户端配置、启动验证命令以及回调联调时最容易踩的几个坑。2. 前置准备TaoToken 统一 Key 与 MCP 通道在动手写代码前先把「凭证从哪来」这件事定下来。传统做法是每个渠道单独申请 Key支付宝一套、大模型一套、MCP 服务又一套。TaoToken 的思路是提供一个统一的 API 通道把模型调用和 MCP 相关的接入凭证集中管理你只需要维护一份 Key不同环境通过不同的 Key 或配置项区分。具体操作路径第一步打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号进入控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。第二步在控制台里创建 API Key路径是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。建议按环境建多个 Key比如order-dev、order-prod这样测试和生产互不干扰出问题也能单独吊销。第三步如果你要跑 Agent 对话调试可以用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 先验证 Key 是否可用不用写代码就能确认通道通不通。第四步长期做编码和 Agent 开发的建议看下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合需要持续调用模型的场景比按次调用更划算。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API 基础地址是 https://taotoken.net/api 注意这个地址不加 UTM 参数直接用于代码里的 base_url。注意支付宝侧的商户凭证应用私钥、支付宝公钥、MCP 场景受限密钥仍然要在支付宝开放平台申请TaoToken 管的是「调用通道」和「模型/MCP 接入 Key」不是替代支付宝的商户资质。两者是配合关系别搞混。3. 可复制配置application.yml 与 MCP 客户端骨架这一节给可直接抄的配置。整个系统分三个模块mcp-server-order本地订单 MCP Server、order-agentSpring AI Agent、以及支付宝 MCP 服务走 SSE 远程连接。先看依赖。mcp-server-order/pom.xml关键依赖dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency /dependenciesorder-agent/pom.xml关键依赖dependencies dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter-dashscope/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency /dependencies订单 MCP Server 的application.yml注意 SSE 方式必须启动 Web 容器spring: ai: mcp: server: name: mcp-server-order version: 0.0.1 datasource: url: jdbc:mysql://127.0.0.1:3306/demodb?useSSLfalseuseUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: xgourd password: xgourd123 server: port: 8081Agent 侧的application.yml是重点这里把大模型 Key 和 MCP 连接都收口spring: application: name: order-agent ai: dashscope: api-key: ${TAOTOKEN_API_KEY} chat: options: model: deepseek-v3 mcp: client: sse: connections: ali: url: https://mcp.api-inference.modelscope.net sse-endpoint: /cb8d2f1aad9341/sse order: url: http://localhost:8081 toolcallback: enabled: false logging: level: [io.modelcontextprotocol.client]: debug [io.modelcontextprotocol.spec]: debug关键点api-key用${TAOTOKEN_API_KEY}占位实际值从环境变量注入这样不同环境只改环境变量不动代码。MCP 的sse.connections下挂了两个连接ali是支付宝 MCP 服务order是本地订单服务。toolcallback.enabled: false是因为我们手动通过SyncMcpToolCallbackProvider注册避免自动装配冲突。订单 MCP Server 的工具类用Tool注解暴露能力Service Slf4j AllArgsConstructor public class OrderService { private final OrderRepository orderRepository; AllArgsConstructor Getter enum State { WAITING(0), CANCEL(1), PAYED(2), REFUND(3), TIMEOUT(4); private final String code; } private static final MapString, BigDecimal PKGS Map.of( 1, BigDecimal.valueOf(9.9), 2, BigDecimal.valueOf(29.9), 3, BigDecimal.valueOf(99.9)); Tool(description 根据客户购买的套餐本地生成一笔待支付订单) public Order createOrder(ToolParam(description 客户手机号) final String phoneNo, ToolParam(description 购买套餐的数字编号) final String pkgNo) { return orderRepository.save(new Order() .setOrderNo(System.currentTimeMillis() ) .setProductNo(pkgNo) .setOrderAmount(PKGS.get(pkgNo)) .setCustomerId(phoneNo) .setCreateTime(new Date()) .setOrderStatus(State.WAITING.getCode())); } Tool(description 完成本地订单更新本地订单状态为已支付, 并记录支付宝交易号) public void complete(ToolParam(description 本地订单号即商户订单号) final String orderNo, ToolParam(description 支付宝订单号) final String alipayTradeNo) { final Order order orderRepository.findByOrderNo(orderNo).orElseThrow(); orderRepository.save(order.setOrderStatus(State.PAYED.getCode()) .setAlipayTradeNo(alipayTradeNo).setUpdateTime(new Date())); } Tool(description 取消本地订单更新本地订单状态为已取消) public void cancel(final String orderNo) { final Order order orderRepository.findByOrderNo(orderNo).orElseThrow(); orderRepository.save(order.setOrderStatus(State.CANCEL.getCode()).setUpdateTime(new Date())); } Tool(description 发生退款更新本地订单状态为已退款) public void refund(final String orderNo) { final Order order orderRepository.findByOrderNo(orderNo).orElseThrow(); orderRepository.save(order.setOrderStatus(State.REFUND.getCode()).setUpdateTime(new Date())); } }工具注册用MethodToolCallbackProviderBean public ToolCallbackProvider dataTool(final OrderService orderService) { return MethodToolCallbackProvider.builder().toolObjects(orderService).build(); }Agent 入口代码把 MCP 客户端注入 ChatClientSpringBootApplication RestController Slf4j public class AgentApplication { private final ChatClient chatClient; public static void main(final String[] args) { SpringApplication.run(AgentApplication.class, args); } public AgentApplication(final ListMcpSyncClient mcpClients, final ChatClient.Builder builder) { this.chatClient builder .defaultToolCallbacks(new SyncMcpToolCallbackProvider(mcpClients)) .build(); } PostMapping(/buy) public void buy(RequestParam final String phone, RequestParam final String plan, final HttpServletResponse response) { final String prompt 请根据用户输入信息: 手机号: %s, 套餐号: %s 1. 生成一笔本地订单; 2. 将订单号、订单金额、订单主题套餐n:选择的套餐对应的名称[1-基础套餐、2-专业套餐、3-尊享套餐]作为参数生成支付宝订单将支付链接https://xxx返回; 3. 输出格式key为url的可解析的json字符串,eg: {url:https://xxx} 4. 请注意url的提取末尾不含括号输出json字符串末尾不含]; .formatted(phone, plan); final String resp chatClient.prompt(prompt).call().content() .replace(json, ).replace(, ); log.info(生成订单响应: {}, resp); final String url JSON.parseObject(resp).getString(url); log.info(支付链接: {}, url); try { response.sendRedirect(url); } catch (final IOException e) { log.error(重定向失败, e); } } GetMapping(/callback) public String callback(RequestParam final MapString, String params) { log.info(订单回调: {}, params); final String prompt 根据回调参数: %s,完成本地订单 1. 将本地订单状态更新为已支付,并记录支付宝交易号; 2. 请注意本地订单号是out_trade_no, 支付宝交易号是trade_no .formatted(JSON.toJSONString(params)); return chatClient.prompt(prompt).call().content(); } }4. 启动验证与支付回调联调配置写完先验证 MCP 连接是否正常。启动mcp-server-order看到Tomcat started on port 8081说明订单服务起来了。再启动order-agent重点看日志里有没有io.modelcontextprotocol.client的输出正常会打印已连接的 MCP Server 列表应该能看到ali和order两个。验证订单 MCP 工具是否注册成功可以调一次下单接口curl -X POST http://localhost:8080/buy?phone13800138000plan2预期结果是 302 重定向到支付宝支付链接。如果返回的是 JSON 而不是重定向说明 Agent 没解析出 url去看日志里生成订单响应的内容大概率是模型输出格式带了多余字符。支付回调联调用支付宝沙箱的异步通知地址指向你的/callbackcurl http://localhost:8080/callback?out_trade_no1730000000000trade_no2025010122001400000001trade_statusTRADE_SUCCESS正常返回应该是模型生成的确认文本同时数据库里对应订单的order_status从0变成2alipay_trade_no被写入。这一步实测下来最容易出问题的是回调参数名对不上支付宝沙箱和生产环境的字段偶尔有差异建议在callback里先把params全量打日志确认字段名再让模型解析。提示回调接口一定要加验签逻辑示例里为了聚焦配置管理省略了生产环境不能省。验签用的支付宝公钥同样建议通过环境变量注入和 TaoToken 的 Key 分开管理。5. 本篇常见错排查报错一No tool callbacks found或 Agent 不调用任何工具。检查toolcallback.enabled是否设成了false同时确认SyncMcpToolCallbackProvider是否真的拿到了mcpClients。如果mcpClients是空列表说明 MCP 连接没建立回去看sse.connections的 url 和 endpoint 是否拼错。报错二支付宝 MCP 用 stdio 方式调不通。这是踩过的坑。stdio 方式对参数限制比较死Spring AI 里传参容易出问题最后改成 SSE 远程连接才调通。如果你也在用 stdio建议换成 SSE配置里url和sse-endpoint分开写。报错三api-key注入失败启动报占位符未解析。确认环境变量TAOTOKEN_API_KEY是否真的导出了。Linux/macOS 用export TAOTOKEN_API_KEY你的keyWindows 用setIDEA 里跑的话在 Run Configuration 的 Environment variables 里加。别把 Key 硬编码进 yml提交到仓库就麻烦了。报错四回调后订单状态没更新。先看/callback有没有被调用到日志里订单回调有没有打印。如果打印了但状态没变看模型返回的内容可能是out_trade_no和trade_no字段名在回调参数里不叫这个实际可能是out_trade_no带了下划线以外的格式。把params全量打出来对照。报错五MCP 连接超时。支付宝 MCP 服务是远程的网络波动会导致连接失败。在logging.level里把io.modelcontextprotocol开到 debug能看到具体的连接过程。如果是本地订单 MCP 连不上确认mcp-server-order的端口和 Agent 配置里的url一致。6. 把 Key 收口之后配置管理清爽多了整套跑下来最大的感受是支付渠道对接的复杂度不在业务逻辑而在凭证和连接的散落。以前改一个支付宝参数要翻三个配置文件现在大模型 Key 和 MCP 通道统一走 TaoToken支付宝商户凭证走环境变量职责清晰。如果你也在做类似的 Agent 接入建议先把 Key 管理这块理顺再写业务代码。排障和接入细节可以对照接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 需要新建或轮换 Key 去 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。验证模型通道是否正常直接用模型对话 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 发一条消息最快。长期跑编码和 Agent 任务的Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 比按次调用更省心。最后留一个实用技巧把application.yml里的所有${}占位符列一个清单部署前用脚本检查环境变量是否齐全能省掉一大半「本地能跑线上报错」的问题。
