Spring Boot 海外支付中心实战:Stripe + Google Play + Apple
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 服务账号 JSON、Apple 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:定义
createPaymentIntent、handleWebhook、queryChannelStatus等。 - 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 等对齐,如
incomplete、succeeded、failed、canceled、refunded等。 - channel_type:
stripe/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:支付渠道,如
stripe、google、apple。 - currency:货币代码,如
USD、CNY。 - customerEmail / customerName:Stripe 等渠道的客户信息。
- successUrl / cancelUrl:Stripe 跳转 URL。
- productId:应用内购买时的商品 ID。
项目中对 orderNo、amount 使用了 @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=200,data 为 PaymentIntentVo,例如包含:
- clientSecret:Stripe 前端确认支付所用。
- paymentIntentId:Stripe PaymentIntent ID。
- currency / amount:货币与金额。
- extraData:各渠道扩展信息(如 Apple 的 session 等)。
6.3 查询支付状态
请求体:至少包含 orderNo;若为 Google/Apple 内购,可带 purchaseToken、productId、receiptData、transactionId 等便于渠道主动查询。
响应: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.json 是 Google 服务账号的密钥文件(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 如何获取真实密钥?
- 打开 Google Cloud Console,选择或新建项目。
- 进入 IAM 与管理 → 服务账号,创建或选择用于 Google Play 的服务账号。
- 在 Google Play 控制台 的 用户与权限 中,将该服务账号邮箱添加为具备「查看财务数据、订单和取消订阅」等权限的账号。
- 在 Google Cloud Console 中,对该服务账号:密钥 → 添加密钥 → 创建新密钥 → 选择 JSON,下载生成的 JSON。
- 将下载的 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.p8 是 App Store Connect API 的私钥文件(PEM 格式),用于对请求做 JWT 签名,调用 App 内购买、订阅、Server Notifications V2 等接口。项目中通过 application.yml 的 apple.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 如何获取真实密钥?
- 登录 App Store Connect。
- 进入 用户和访问 → 密钥(或 Integrations → App Store Connect API)。
- 在 App Store Connect API 下点击 生成 API 密钥,填写名称并选择访问权限(如 App 内购买、订阅等)。
- 生成后下载 .p8 文件(仅能下载一次,请妥善保存)。
- 将下载的
.p8重命名为AuthKey.p8,放到src/main/resources/apple/目录下。 - 在
application.yml中配置apple.pay.private-key-path、apple.pay.key-id、apple.pay.issuer-id、apple.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());
}
}
十二、安全与开源注意事项
-
勿提交真实密钥
不要将application-dev.yml、google-service-account.json、AuthKey.p8等包含密码、私钥、服务账号的文件提交到仓库;.gitignore已忽略这些文件,仅提交对应的.example示例。 -
若历史提交中曾包含敏感信息
需在仓库中删除对应文件或使用git filter-repo/git filter-branch清理历史,并立即更换所有已泄露的密钥。 -
生产环境
数据库、Redis、JWT、Stripe、Apple、Google 等密钥建议通过环境变量或密钥管理服务注入,不要写死在配置文件中。 -
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 扩展新渠道
- 新增枚举值(如
ChannelType.PAYPAL)。 - 新建
XxxPaymentStrategy实现PaymentStrategy,实现createPaymentIntent、handleWebhook、queryChannelStatus。 - 在配置类中按需增加该渠道的配置项与 Client。
- 保证新策略被 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。
更多推荐

所有评论(0)