Spring Boot 海外支付中心实战:Stripe + Google Play + Apple

一、前言与背景

在移动应用或 SaaS 产品中,支付能力往往是核心一环:既要支持网页/信用卡支付(如 Stripe),又要支持 Android / iOS 应用内购买(Google Play、App Store)。若各渠道各自对接,会出现重复代码多、状态不一致、Webhook 分散难维护等问题。

本文介绍一个基于 Spring Boot 3支付中心微服务项目:在一个服务内统一对接 Stripe、Google Play 应用内购买、Apple App Store 内购,提供创建支付、Webhook 回调、支付状态查询等能力;并重点说明本地如何跑起来,以及 Google 服务账号 JSONApple AuthKey.p8 的获取方式与示例文件用法,方便在开源到 Gitee 或自用时既保留示例又不泄露真实密钥。

阅读本文你将了解:

  • 支付中心的整体架构与策略模式设计;
  • 数据库表设计、核心 API 与请求/响应格式;
  • 从零配置到运行的完整步骤;
  • Google / Apple 密钥的获取与示例文件使用;
  • 安全与开源注意事项、常见问题与扩展思路。

二、项目功能概览

在这里插入图片描述

功能 说明
多渠道支付 Stripe 在线支付、Google Play 内购、Apple App Store 内购,统一入口
创建支付意图 按渠道创建支付单,返回前端所需参数(如 Stripe 的 clientSecret、Apple/Google 的订单信息)
Webhook 处理 接收各渠道回调,验签后更新订单状态,并可通知业务系统
状态查询 按订单号查询;若本地为中间状态,则主动向渠道拉取最新状态(补偿)
策略模式 各渠道独立策略实现,便于后续扩展新渠道(如 PayPal、支付宝等)
定时任务 轮询长时间未完成的支付(如 Stripe),做状态同步与补偿

三、技术栈与环境要求

3.1 技术栈

类别 技术
语言 Java 17
框架 Spring Boot 3.3.x、Spring Cloud(Consul、OpenFeign 可选)
数据库 MySQL 8、MyBatis
缓存与分布式锁 Redis、Redisson
支付 SDK Stripe Java SDK、Google Android Publisher API、Apple App Store Server Library

3.2 环境要求

  • JDK 17+
  • Maven 3.6+
  • MySQL 8.0+
  • Redis(用于缓存与分布式锁)

四、整体架构与设计思路

4.1 分层与职责

  • Controller 层:对外 HTTP 接口(创建支付、Webhook、查询状态)。
  • Service 层:业务编排,根据渠道选择对应策略。
  • Strategy 层:各渠道具体实现(Stripe / Google / Apple),实现创建支付、处理 Webhook、主动查询状态。
  • Repository 层:支付单的持久化(MyBatis)。
  • Config / Client:第三方 API 配置与客户端封装。

4.2 策略模式与工厂

不同支付渠道的流程差异较大(Stripe 用 PaymentIntent,Google/Apple 用应用内购买),因此采用策略模式统一接口,由工厂按渠道码选择具体策略:

  • PaymentStrategy:定义 createPaymentIntenthandleWebhookqueryChannelStatus 等。
  • StripePaymentStrategy / GooglePayPaymentStrategy / ApplePayPaymentStrategy:各自实现。
  • PaymentStrategyFactory:启动时将所有策略注入为 List<PaymentStrategy>,按 getChannelType() 转为 Map,根据请求中的 channel 取对应策略。

这样新增渠道时只需新增一个 Strategy 实现类并保证被 Spring 扫描即可,无需改业务层代码。

4.3 核心流程简述

  • 创建支付:前端传入订单号、金额、渠道等 → Service 根据渠道选策略 → 策略内创建/更新本地支付单并调用渠道 API → 返回前端所需参数(如 Stripe 的 clientSecret)。
  • Webhook:各渠道向 /payment/webhook/{channel} 推送回调 → 根据 channel 选策略 → 验签 → 更新支付单状态 → 可再通知订单/业务系统。
  • 查询状态:按订单号查本地库;若状态非终态,则由对应策略向渠道发起主动查询并回写库。

五、数据库设计

支付单表 payment 设计为渠道无关,用通用字段存储各渠道的流水号、用户标识等,便于扩展。

5.1 建库建表

在 MySQL 中执行项目内 src/main/resources/payment.sql,会创建数据库 app_payment 及表 payment。核心片段如下:

USE app_payment;
CREATE TABLE payment (
    id                        BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
    app_id                    INT COMMENT '应用id',
    order_no                  VARCHAR(64) NOT NULL COMMENT '订单号',
    user_id                   VARCHAR(64) COMMENT '用户id',
    amount                    DECIMAL(10,2) NOT NULL COMMENT '支付金额',
    status                    VARCHAR(30) DEFAULT 'incomplete' NOT NULL COMMENT '支付状态',
    pay_method                VARCHAR(50) COMMENT '支付方式',
    channel_type              VARCHAR(20) COMMENT '渠道:stripe, google, apple',
    create_time               DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
    update_time               DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL,
    transaction_id            VARCHAR(128) COMMENT '渠道交易流水号',
    channel_user_id           VARCHAR(128) COMMENT '渠道用户标识',
    channel_payment_method_id VARCHAR(128) COMMENT '渠道支付方式标识',
    channel_message           VARCHAR(255) COMMENT '渠道返回信息(JSON等)',
    os                        VARCHAR(50) COMMENT '操作系统',
    language                  VARCHAR(20) COMMENT '语言',
    pay_time                  DATETIME COMMENT '实际支付成功时间',
    pay_timeout               DATETIME COMMENT '支付超时时间',
    close_time                DATETIME COMMENT '支付关闭时间',
    client_ip                 VARCHAR(50) COMMENT '客户端IP',
    schedule_time             DATETIME COMMENT '定时任务执行时间',
    schedule_status           VARCHAR(50) COMMENT '定时任务执行状态',
    product_id                VARCHAR(128) COMMENT '商品ID(内购等)',
    CONSTRAINT uk_order_no UNIQUE (order_no)
) COMMENT '支付表';
CREATE INDEX idx_transaction_id ON payment (transaction_id);

5.2 字段说明摘要

  • order_no:业务侧订单号,唯一。
  • status:与 Stripe 等对齐,如 incompletesucceededfailedcanceledrefunded 等。
  • channel_typestripe / google / apple
  • transaction_id:渠道侧交易号(Stripe PaymentIntent ID、Apple transactionId 等)。
  • channel_user_id / channel_payment_method_id / channel_message:渠道相关扩展信息。

六、核心 API 与请求/响应示例

6.1 接口一览

方法 路径 说明
POST /payment/create-intent 创建支付意图,请求体为 PaymentIntentDTO
POST /payment/webhook/{channel} Webhook 回调,channel 为 stripe / google / apple
POST /payment/getPaymentStatusByOrderNo 根据订单号查询支付状态

6.2 创建支付意图

请求体(PaymentIntentDTO):必填与常用字段如下。

  • orderNo(必填):业务订单号。
  • amount(必填):支付金额,需大于 0。
  • channelType:支付渠道,如 stripegoogleapple
  • currency:货币代码,如 USDCNY
  • customerEmail / customerName:Stripe 等渠道的客户信息。
  • successUrl / cancelUrl:Stripe 跳转 URL。
  • productId:应用内购买时的商品 ID。

项目中对 orderNoamount 使用了 @NotBlank@NotNull@DecimalMin 等校验,非法请求会直接返回 400。

请求示例(Stripe)

{
  "orderNo": "ORD202501001",
  "amount": 99.99,
  "currency": "USD",
  "channelType": "stripe",
  "customerEmail": "user@example.com",
  "successUrl": "https://yoursite.com/success",
  "cancelUrl": "https://yoursite.com/cancel"
}

响应:统一使用 Result<T>,成功时 code=200dataPaymentIntentVo,例如包含:

  • clientSecret:Stripe 前端确认支付所用。
  • paymentIntentId:Stripe PaymentIntent ID。
  • currency / amount:货币与金额。
  • extraData:各渠道扩展信息(如 Apple 的 session 等)。

6.3 查询支付状态

请求体:至少包含 orderNo;若为 Google/Apple 内购,可带 purchaseTokenproductIdreceiptDatatransactionId 等便于渠道主动查询。

响应Result<PaymentStatusVo>,其中包含 status(支付状态码)、playDateTime(支付时间等)。

6.4 Webhook 地址

各渠道在控制台配置的回调地址示例:

  • Stripe:https://你的域名/payment/webhook/stripe
  • Google Play:https://你的域名/payment/webhook/google
  • Apple:https://你的域名/payment/webhook/apple

服务会根据 path 中的 channel 选择对应策略,并进行签名校验后再更新订单状态。


七、快速开始:从零到运行

7.1 克隆项目

git clone https://gitee.com/zhang-jinlong1/payment-center-service.git
cd payment-center-service

7.2 数据库初始化

在 MySQL 中执行 src/main/resources/payment.sql,创建数据库 app_payment 及表 payment(见第五节)。

7.3 配置文件说明

  • 主配置application.yml 中数据库、Redis、应用名等使用占位符(如 ${db.host}${redis.host}),由环境变量或 profile 覆盖。
  • 本地开发:将 application-dev.yml.example 复制为 application-dev.yml,填入本地数据库、Redis、Stripe 等配置;切勿将 application-dev.yml 提交到仓库

application-dev.yml.example 结构示例:

spring:
  cloud:
    consul:
      enabled: false

db:
  host: localhost:3306
  username: your_db_username
  password: your_db_password

redis:
  host: 127.0.0.1
  port: 6379

stripe:
  api-key: ${STRIPE_API_KEY:sk_test_xxx}
  publishable-key: ${STRIPE_PUBLISHABLE_KEY:pk_test_xxx}
  webhook-secret: ${STRIPE_WEBHOOK_SECRET:whsec_xxx}
  currency: USD

必选/常用配置项汇总:

配置项 说明 示例
db.host / db.username / db.password 数据库连接 localhost:3306 / root / ***
redis.host / redis.port Redis 127.0.0.1 / 6379
JWT_SECRET 鉴权密钥(若启用) 自定义长字符串
stripe.api-key / stripe.webhook-secret Stripe 密钥与 Webhook 密钥 sk_test_xxx / whsec_xxx
google.pay.package-name / service-account-key-path Google 应用包名与密钥路径 见下文
apple.pay.private-key-path / key-id / issuer-id / bundle-id Apple 私钥路径与密钥信息 见下文

7.4 运行

mvn spring-boot:run

默认端口 8083;激活 profile 为 dev 时会加载 application-dev.yml


八、Google Play 服务账号密钥(google-service-account.json)

8.1 这个文件是什么?

google-service-account.jsonGoogle 服务账号的密钥文件(JSON 格式),用于调用 Google Play Android Publisher API,例如校验应用内购买(IAP)、查询订单等。项目通过 application.yml 中的 google.pay.service-account-key-path(如 classpath:play/google-service-account.json)加载该文件。

8.2 仓库里的示例文件

为避免在开源仓库中提交真实密钥,项目提供了仅含结构的示例文件

  • 可提交src/main/resources/play/google-service-account.json.example
  • 勿提交google-service-account.json(真实密钥),已通过 .gitignore 忽略

示例文件结构大致如下(占位内容,需替换为真实密钥):

{
  "type": "service_account",
  "project_id": "your-gcp-project-id",
  "private_key_id": "your-private-key-id",
  "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
  "client_email": "xxx@xxx.iam.gserviceaccount.com",
  "client_id": "...",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/...",
  "universe_domain": "googleapis.com"
}

8.3 如何获取真实密钥?

  1. 打开 Google Cloud Console,选择或新建项目。
  2. 进入 IAM 与管理服务账号,创建或选择用于 Google Play 的服务账号。
  3. Google Play 控制台用户与权限 中,将该服务账号邮箱添加为具备「查看财务数据、订单和取消订阅」等权限的账号。
  4. 在 Google Cloud Console 中,对该服务账号:密钥添加密钥创建新密钥 → 选择 JSON,下载生成的 JSON。
  5. 将下载的 JSON 重命名为 google-service-account.json,放到 src/main/resources/play/ 目录下(与 .example 同级)。

本地使用时可先复制示例再替换内容:

cp google-service-account.json.example google-service-account.json
# 再用从 Google Cloud 下载的真实 JSON 内容替换 google-service-account.json

更多说明见项目内 src/main/resources/play/README.md


九、Apple App Store 私钥(AuthKey.p8)

9.1 这个文件是什么?

AuthKey.p8App Store Connect API 的私钥文件(PEM 格式),用于对请求做 JWT 签名,调用 App 内购买、订阅、Server Notifications V2 等接口。项目中通过 application.ymlapple.pay.private-key-path(如 classpath:apple/AuthKey.p8)加载。

9.2 仓库里的示例文件

  • 可提交src/main/resources/apple/AuthKey.p8.example
  • 勿提交AuthKey.p8(真实私钥),已通过 .gitignore 忽略

示例内容仅为占位,格式如下:

-----BEGIN PRIVATE KEY-----
REPLACE_THIS_WITH_YOUR_APPLE_APP_STORE_CONNECT_P8_KEY_CONTENT
-----END PRIVATE KEY-----

9.3 如何获取真实密钥?

  1. 登录 App Store Connect
  2. 进入 用户和访问密钥(或 IntegrationsApp Store Connect API)。
  3. App Store Connect API 下点击 生成 API 密钥,填写名称并选择访问权限(如 App 内购买、订阅等)。
  4. 生成后下载 .p8 文件仅能下载一次,请妥善保存)。
  5. 将下载的 .p8 重命名为 AuthKey.p8,放到 src/main/resources/apple/ 目录下。
  6. application.yml 中配置 apple.pay.private-key-pathapple.pay.key-idapple.pay.issuer-idapple.pay.bundle-id 等。

本地使用示例:

cp AuthKey.p8.example AuthKey.p8
# 用从 App Store Connect 下载的真实 .p8 内容替换 AuthKey.p8,或直接重命名下载文件为 AuthKey.p8 放到此目录

更多说明见项目内 src/main/resources/apple/README.md


十、项目结构说明

src/main/java/com/appfactory/payment/
├── PaymentApplication.java          # 启动类
├── controller/
│   └── PaymentController.java       # 支付相关 HTTP 接口
├── service/
│   ├── PaymentService.java         # 支付服务接口
│   └── impl/
│       └── PaymentServiceImpl.java  # 根据 channel 选择策略并委托
├── strategy/
│   ├── PaymentStrategy.java        # 策略接口:创建支付、Webhook、查询状态
│   ├── AbstractPaymentStrategy.java # 公共逻辑(如锁、落库)
│   └── impl/
│       ├── StripePaymentStrategy.java
│       ├── GooglePayPaymentStrategy.java
│       └── ApplePayPaymentStrategy.java
├── config/                          # Stripe、Apple、Google、Redis、Web 等配置
├── client/                          # 第三方 API 客户端(如 StripeClient)
├── repository/
│   └── PaymentMapper.java           # MyBatis Mapper
├── model/                           # DTO、VO、枚举、异常、统一结果类
├── factory/
│   └── PaymentStrategyFactory.java # 按 channel 返回对应策略
├── task/
│   └── PaymentTask.java             # 定时轮询待支付订单(如 Stripe)
└── util/                            # 工具类(如 WebClient 封装)

十一、核心代码一览

11.1 策略接口(PaymentStrategy)

各渠道统一实现该接口,便于 Service 层无差别调用:

public interface PaymentStrategy {
    PaymentIntentVo createPaymentIntent(PaymentIntentDTO dto, User user, ClientInfo clientInfo) throws StripeException;
    String getChannelType();  // "stripe" / "google" / "apple"
    String handleWebhook(String payload, Map<String, String> headers);
    default Payment queryChannelStatus(Payment payment, PaymentStatusDto extraData) { return payment; }
}

11.2 策略工厂(PaymentStrategyFactory)

启动时将所有 PaymentStrategy 实现按 getChannelType() 注册到 Map,运行时根据请求中的渠道码获取策略:

@Component
public class PaymentStrategyFactory {
    private final Map<String, PaymentStrategy> strategyMap;

    public PaymentStrategyFactory(List<PaymentStrategy> paymentStrategies) {
        strategyMap = paymentStrategies.stream()
            .collect(Collectors.toConcurrentMap(PaymentStrategy::getChannelType, Function.identity()));
    }

    public PaymentStrategy getStrategy(String channelCode) {
        return strategyMap.get(channelCode);
    }
}

11.3 Controller 创建支付(简化)

创建支付时校验通过后,直接委托给 Service,由 Service 根据 DTO 中的 channelType 选择策略并调用:

@PostMapping("/create-intent")
public Result<PaymentIntentVo> createPaymentIntent(@RequestBody @Validated PaymentIntentDTO dto, ClientInfo clientInfo) {
    User user = new User();  // 实际可从拦截器/Token 获取
    try {
        PaymentIntentVo vo = paymentService.createPaymentIntent(dto, user, clientInfo);
        return Result.success(vo);
    } catch (BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    } catch (StripeException e) {
        return Result.error(ErrorCode.STRIPE_PAYMENT_INTENT_CREATION_FAILED, "Create payment intent failed: " + e.getMessage());
    }
}

十二、安全与开源注意事项

  1. 勿提交真实密钥
    不要将 application-dev.ymlgoogle-service-account.jsonAuthKey.p8 等包含密码、私钥、服务账号的文件提交到仓库;.gitignore 已忽略这些文件,仅提交对应的 .example 示例。

  2. 若历史提交中曾包含敏感信息
    需在仓库中删除对应文件或使用 git filter-repo / git filter-branch 清理历史,并立即更换所有已泄露的密钥。

  3. 生产环境
    数据库、Redis、JWT、Stripe、Apple、Google 等密钥建议通过环境变量或密钥管理服务注入,不要写死在配置文件中。

  4. Webhook 验签
    各渠道回调务必在策略内做签名校验(Stripe 的 webhook secret、Apple 的 JWS、Google 的 Pub/Sub 等),防止伪造回调。


十三、常见问题与扩展建议

13.1 常见问题

  • Q:本地没有配置 Google/Apple 密钥,服务能启动吗?
    A:可以。相关配置类在密钥缺失时一般会记录警告或返回 null,不影响其他渠道;仅在使用对应渠道时会报错。

  • Q:Webhook 收不到回调?
    A:检查公网能否访问你的 /payment/webhook/{channel} 地址;Stripe/Google/Apple 控制台中配置的 URL 是否与之一致;防火墙/反向代理是否放行。

  • Q:如何只启用部分渠道?
    A:不配置某渠道的密钥或配置项即可,策略内会做空判断;也可通过配置开关在工厂或策略中跳过某渠道。

13.2 扩展新渠道

  1. 新增枚举值(如 ChannelType.PAYPAL)。
  2. 新建 XxxPaymentStrategy 实现 PaymentStrategy,实现 createPaymentIntenthandleWebhookqueryChannelStatus
  3. 在配置类中按需增加该渠道的配置项与 Client。
  4. 保证新策略被 Spring 扫描到,工厂会自动注册,无需改 Service/Controller。

十四、小结

本文从架构设计、数据库、API、配置、密钥管理安全与扩展,介绍了基于 Spring Boot 的支付中心服务的完整实践,并重点说明了:

  • Google Play:使用 google-service-account.json.example 作为格式参考,真实密钥从 Google Cloud Console 下载后放入 google-service-account.json,且不提交到仓库。
  • Apple App Store:使用 AuthKey.p8.example 作为格式参考,真实 .p8 从 App Store Connect 下载后放入 AuthKey.p8,且不提交到仓库。

项目地址(示例):https://gitee.com/zhang-jinlong1/payment-center-service

如有问题或建议,欢迎在仓库提 Issue 或 Pull Request。


Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐