开发者中心 · 让接入 更简单

完整的 API 文档、 SDK 与最佳实践,助您 10 分钟完成支付、钱包、对账等能力集成

接入准备

GET STARTED

在开始调用 KairWallet API 之前,您需要完成以下准备工作:

  1. 注册 KairWallet 账号并完成企业认证
  2. 进入商户后台 → 开发者中心 → 获取 AppKey 与 AppSecret
  3. 配置 IP 白名单与回调地址(生产环境必需)
  4. 阅读签名算法说明,完成首次接口调用
提示: 所有 API 请求必须使用 HTTPS 协议,基础地址为 https://api.kairwallet.com

系统架构关系图

KairWallet 采用分层架构设计,商户侧系统与平台之间通过标准 API 交互,密钥全程自持,确保数据安全与合规。

商户侧系统
商户业务集群(多实例)
PaymentClient(SDK)
异步上报队列
本地账单同步工具
KairWallet 平台
API 网关层
SaaS 服务层
数据存储层
支付渠道
微信支付 V3
支付宝
K商户密钥自持,平台不存储
R异步上报,本地失败重试
@HTTPS 加密传输
#云端自动对账

通用响应结构

所有接口统一采用以下响应格式,业务状态通过 code 判断:

{
  "code": 0,
  "message": "success",
  "data": { ... },
  "request_id": "req_8f2a9c7b8d9e3817",
  "timestamp": 1718700000
}

多实例部署指南

当您的业务系统采用 Kubernetes、Docker Swarm 或负载均衡集群等多实例部署架构时, SDK 默认的进程内内存存储将无法满足跨实例去重、证书共享和指标聚合的需求。 KairWallet SDK 通过 SPI(Service Provider Interface)机制提供了三个可扩展点, 让您只需注入自定义实现,即可无缝切换到分布式存储,无需修改任何业务代码。

核心设计原则:SDK 定义标准接口,业务方提供具体实现,实现解耦。SPI 接口位于 com.kairwallet.payment.api.spi 包下。

多实例部署面临的四大挑战

1. 幂等性失效

同一订单的重复支付请求被路由到不同实例,各实例内存独立,无法识别跨实例的重复请求,可能导致重复扣款。

2. 证书重复下载

微信支付平台证书每个实例独立下载,造成不必要的 API 调用和性能浪费,且证书更新时可能出现短暂不一致。

3. 指标无法聚合

各实例独立统计调用指标,无法查看整体成功率、延迟等数据,实例重启后统计数据全部丢失。

4. 分账规则不一致

分账规则默认基于进程内缓存,多实例时各实例独立缓存,SaaS 控制台修改规则后各实例生效时间不一,且缓存过期后重复调用 SaaS 拉取,增加不必要的网络开销。

SPI 扩展点总览

SPI 接口 默认实现 多实例推荐实现 解决的问题
IdempotencyStore InMemoryIdempotencyStore Redis (setIfAbsent + TTL) 跨实例幂等去重
CertificateStore InMemoryCertificateStore Redis Hash 跨实例证书共享
MetricsCollector DefaultMetricsCollector Prometheus / Micrometer 多实例指标聚合
SplitRuleCache InMemorySplitRuleCache Redis Key-Value(30 分钟 TTL) 分账规则跨实例缓存

SPI 一:IdempotencyStore — 跨实例幂等去重

IdempotencyStore 是 SDK 幂等性机制的核心存储接口。当同一商户订单号在窗口期内被多次调用时,SDK 会检测并阻止重复请求。 默认实现基于 ConcurrentHashMap 的进程内存储,仅适用于单实例。

接口定义

public interface IdempotencyStore {

    /**
     * 检测并记录一次调用
     * @param compositeKey 组合键(如 "PAY:ORDER_12345")
     * @param timestampMs 当前时间戳(毫秒)
     * @param windowMs    窗口时间(毫秒)
     * @return true 表示窗口期内已有记录(重复调用)
     */
    boolean checkAndRecord(String compositeKey, long timestampMs, long windowMs);

    /** 清理过期记录(Redis 实现可为空,由 TTL 自动处理) */
    default void cleanExpired(long windowMs) {}

    /** 获取当前记录数(用于监控) */
    default int getRecordCount() { return -1; }

    /** 关闭存储(释放资源) */
    default void close() {}
}

幂等模式:IdempotencyMode

SDK 支持两种幂等处理策略,通过 Builder 链式配置:

WARN_ONLY(默认)

检测到重复请求时仅记录指标,不阻断执行。适用于业务方已有自己的幂等机制(如订单状态机)的场景。

BLOCK

检测到重复请求时直接抛出 PayException(错误码 DUPLICATE_REQUEST),阻断执行。适用于需要 SDK 强制防重的场景。

Redis 实现示例

// 1. 实现 IdempotencyStore 接口(基于 Spring Data Redis)
public class RedisIdempotencyStore implements IdempotencyStore {

    private final StringRedisTemplate redis;
    private static final String PREFIX = "pay:idem:";

    public RedisIdempotencyStore(StringRedisTemplate redis) {
        this.redis = redis;
    }

    @Override
    public boolean checkAndRecord(String compositeKey, long timestampMs, long windowMs) {
        String key = PREFIX + compositeKey;
        // setIfAbsent = SETNX + EXPIRE,原子操作保证线程安全
        Boolean firstTime = redis.opsForValue()
            .setIfAbsent(key, String.valueOf(timestampMs), windowMs, TimeUnit.MILLISECONDS);
        return firstTime != null && !firstTime;
    }

    // Redis TTL 自动过期,cleanExpired 无需实现
    @Override
    public void cleanExpired(long windowMs) { // no-op }

    @Override
    public int getRecordCount() {
        return redis.keys(PREFIX + "*").size();
    }
}

// 2. 注入到 PaymentClient
PaymentClient client = PaymentClients.builder()
    .wechat(WechatChannelConfig.builder()
        .mchId("1600000000")
        .appId("wx_xxxxxxxxxxxx")
        .apiV3Key(readEnv("WECHAT_V3_KEY"))
        .merchantPrivateKey(readEnv("WECHAT_PRIVATE_KEY"))
        .merchantCertSerialNumber(readEnv("WECHAT_CERT_SN"))
        .build())
    .idempotencyStore(new RedisIdempotencyStore(redisTemplate))  // 注入自定义实现
    .idempotencyMode(IdempotencyMode.BLOCK)  // 可选:启用阻断模式
    .build();
原理说明:Redis 的 SETNX + EXPIRE(即 setIfAbsent)是原子操作,无论多少个实例同时发起相同订单的请求,只有一个实例能写入成功,其余实例会检测到 key 已存在,从而实现跨实例去重。

SPI 二:CertificateStore — 跨实例证书共享

微信支付 V3 API 要求使用微信平台证书验签回调。SDK 默认在进程内存中缓存证书,每个实例独立下载。 多实例部署时,通过共享缓存可以避免重复下载,并在证书轮换时保证所有实例一致更新。

接口定义

public interface CertificateStore {

    /** 获取证书(返回 null 表示缓存未命中,SDK 将自动下载) */
    byte[] getCertificate(String serialNo);

    /** 保存证书 */
    void putCertificate(String serialNo, byte[] certBytes, long expireTime);

    /** 获取所有已缓存证书序列号 */
    Set<String> getAllSerialNumbers();

    /** 获取/设置最近刷新时间 */
    default long getLastRefreshTime() { return 0L; }
    default void setLastRefreshTime(long timestampMs) {}

    /** 清空 / 关闭 */
    default void clear() {}
    default void close() {}
}

Redis Hash 实现示例

public class RedisCertificateStore implements CertificateStore {

    private final StringRedisTemplate redis;
    private static final String HASH_KEY = "pay:certificates";

    public RedisCertificateStore(StringRedisTemplate redis) {
        this.redis = redis;
    }

    @Override
    public byte[] getCertificate(String serialNo) {
        String base64 = redis.opsForHash().get(HASH_KEY, serialNo + ":cert");
        return base64 != null ? Base64.getDecoder().decode(base64) : null;
    }

    @Override
    public void putCertificate(String serialNo, byte[] certBytes, long expireTime) {
        String base64 = Base64.getEncoder().encodeToString(certBytes);
        redis.opsForHash().put(HASH_KEY, serialNo + ":cert", base64);
        redis.opsForHash().put(HASH_KEY, serialNo + ":expire", String.valueOf(expireTime));
    }

    @Override
    public Set<String> getAllSerialNumbers() {
        return redis.opsForHash().keys(HASH_KEY).stream()
            .filter(k -> k.toString().endsWith(":cert"))
            .map(k -> k.toString().replace(":cert", ""))
            .collect(Collectors.toSet());
    }
}

// 注入到 PaymentClient
PaymentClient client = PaymentClients.builder()
    .wechat(wechatConfig)
    .certificateStore(new RedisCertificateStore(redisTemplate))
    .build();
收益:假设您有 4 个实例,微信证书有效期 24 小时。使用共享缓存后,每天仅需 1 次 API 调用下载证书(而非 4 × 1 = 4 次),且证书轮换时所有实例立即同步。

SPI 三:MetricsCollector — 多实例指标聚合

SDK 内置指标采集器统计支付成功率、响应时间、回调验签、重复请求等数据。默认实现在单实例内存中统计,多实例部署时需要接入外部监控系统实现聚合。

核心采集指标

指标 说明 告警阈值
pay_success_rate 支付成功率 < 99%
pay_latency_ms 平均响应时间 > 3000ms
pay_http_5xx HTTP 5xx 错误数 错误率 > 5%
pay_callback_fail 回调验签失败数 > 0
pay_duplicate 重复请求检测数 异常升高

Prometheus / Micrometer 实现示例

public class PrometheusMetricsCollector implements MetricsCollector {

    private final MeterRegistry registry;

    // 计数器
    private final Counter totalOps;
    private final Counter successOps;
    private final Counter failedOps;
    private final Counter duplicateReqs;
    private final Counter callbackFails;

    // 计时器
    private final Timer opLatency;

    public PrometheusMetricsCollector(MeterRegistry registry) {
        this.registry = registry;
        this.totalOps = Counter.builder("pay_operations_total")
            .description("支付操作总数").register(registry);
        this.successOps = Counter.builder("pay_operations_success")
            .description("支付成功数").register(registry);
        this.failedOps = Counter.builder("pay_operations_failed")
            .description("支付失败数").register(registry);
        this.duplicateReqs = Counter.builder("pay_duplicate_requests")
            .description("重复请求数").register(registry);
        this.callbackFails = Counter.builder("pay_callback_verify_fail")
            .description("回调验签失败数").register(registry);
        this.opLatency = Timer.builder("pay_operations_latency")
            .description("操作延迟").register(registry);
    }

    @Override
    public void recordOperation(String operation, String channel,
                                  boolean success, long latencyMs, String errorCode) {
        totalOps.increment();
        if (success) successOps.increment(); else failedOps.increment();
        opLatency.record(latencyMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public void recordDuplicateRequest(String operation, String channel, String key) {
        duplicateReqs.increment();
    }

    // ... 其他 recordXxx 方法类似
}

// 注入到 PaymentClient
PaymentClient client = PaymentClients.builder()
    .wechat(wechatConfig)
    .idempotencyStore(new RedisIdempotencyStore(redisTemplate))
    .certificateStore(new RedisCertificateStore(redisTemplate))
    .metricsCollector(new PrometheusMetricsCollector(meterRegistry))
    .build();

SPI 四:SplitRuleCache — 分账规则跨实例缓存

当使用 ruleId 模式发起分账时,SDK 需要从 SaaS 平台拉取分账规则。 默认实现 InMemorySplitRuleCache 基于进程内缓存,多实例部署时每个实例独立缓存,导致:

  • 不同实例缓存的规则版本可能不一致(修改规则后各实例生效时间有差)
  • 规则缓存 TTL 到期后各实例重复调用 SaaS 拉取,增加不必要的网络开销

接口定义

public interface SplitRuleCache {

    /** 获取分账规则(未命中返回 null,由调用方查 SaaS 后回填) */
    SplitRule get(Long ruleId);

    /** 写入分账规则(TTL 由实现方控制,Redis 建议 30 分钟) */
    void put(Long ruleId, SplitRule rule);

    /** 主动失效指定规则缓存(Webhook 收到 SPLIT_RULE_CHANGED 时调用) */
    void invalidate(Long ruleId);

    /** 清空所有缓存(默认空实现,谨慎使用) */
    default void invalidateAll() {}
}

Redis 实现示例

import com.fasterxml.jackson.databind.ObjectMapper;
import com.kairwallet.payment.api.model.SplitRule;
import com.kairwallet.payment.api.spi.SplitRuleCache;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

// 基于 Spring Data Redis 的分布式分账规则缓存实现
public class RedisSplitRuleCache implements SplitRuleCache {

    private final StringRedisTemplate redis;
    private final ObjectMapper mapper;
    private static final String KEY_PREFIX = "pay:split:rule:";
    private static final long TTL_MINUTES = 30;  // 30 分钟自动过期

    public RedisSplitRuleCache(StringRedisTemplate redis, ObjectMapper mapper) {
        this.redis = redis;
        this.mapper = mapper;
    }

    @Override
    public SplitRule get(Long ruleId) {
        String json = redis.opsForValue().get(KEY_PREFIX + ruleId);
        if (json == null) {
            return null;  // 缓存未命中
        }
        try {
            return mapper.readValue(json, SplitRule.class);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void put(Long ruleId, SplitRule rule) {
        try {
            String json = mapper.writeValueAsString(rule);
            redis.opsForValue().set(
                KEY_PREFIX + ruleId,
                json,
                TTL_MINUTES,
                TimeUnit.MINUTES
            );
        } catch (Exception e) {
            // 记录日志但不抛出异常,缓存失败不影响主流程
            log.warn("分账规则缓存写入失败: ruleId={}", ruleId, e);
        }
    }

    @Override
    public void invalidate(Long ruleId) {
        redis.delete(KEY_PREFIX + ruleId);
    }
}

注入到 PaymentClient

PaymentClient client = PaymentClients.builder()
    .wechat(wechatConfig)
    .idempotencyStore(new RedisIdempotencyStore(redisTemplate))
    .certificateStore(new RedisCertificateStore(redisTemplate))
    .metricsCollector(new PrometheusMetricsCollector(meterRegistry))
    .splitRuleCache(new RedisSplitRuleCache(redisTemplate, objectMapper))  // 4. 分账规则缓存
    .build();
缓存失效机制: 商户在 SaaS 控制台修改规则后,SaaS 会通过 Webhook 推送 SPLIT_RULE_CHANGED 事件。商户系统接收后调用 invalidate(ruleId) 清空缓存,确保下次分账请求时所有实例重新拉取最新规则。若未配置 Webhook,则依靠 TTL 自然过期(默认 30 分钟)。

Webhook 处理示例

@RestController
@RequestMapping("/api/webhook")
public class SaasWebhookController {

    private final PaymentClient paymentClient;

    @PostMapping
    "/saas"
    public ResponseEntity handleWebhook(@RequestBody SaasWebhookEvent event) {
        if ("SPLIT_RULE_CHANGED".equals(event.getEventType())) {
            Long ruleId = event.getPayload().getRuleId();
            SplitRuleCache cache = paymentClient.getSplitRuleCache();
            if (cache != null) {
                cache.invalidate(ruleId);
                log.info("已失效分账规则缓存: ruleId={}", ruleId);
            }
        }
        return ResponseEntity.ok("OK");
    }
}

完整配置:一次性注入全部 SPI

生产环境推荐同时注入四个 SPI 扩展点,通过 Builder 链式调用一次性完成配置:

// ================================================================
// 生产环境完整分布式配置(Spring Boot 示例)
// ================================================================

@Bean
public PaymentClient paymentClient(StringRedisTemplate redis,
                                      MeterRegistry meterRegistry,
                                      ObjectMapper objectMapper) {
    return PaymentClients.builder()
        .wechat(WechatChannelConfig.builder()
            .mchId("1600000000")
            .appId("wx_xxxxxxxxxxxx")
            .apiV3Key(System.getenv("WECHAT_V3_KEY"))
            .merchantPrivateKey(System.getenv("WECHAT_PRIVATE_KEY"))
            .merchantCertSerialNumber(System.getenv("WECHAT_CERT_SN"))
            .build())
        .alipay(AlipayChannelConfig.builder()
            .appId("2021000000000000")
            .merchantPrivateKey(System.getenv("ALIPAY_PRIVATE_KEY"))
            .alipayPublicKey(System.getenv("ALIPAY_PUBLIC_KEY"))
            .build())
        // ===== 四个 SPI 扩展点 =====
        .idempotencyStore(new RedisIdempotencyStore(redis))       // 1. 跨实例去重
        .certificateStore(new RedisCertificateStore(redis))     // 2. 证书共享
        .metricsCollector(new PrometheusMetricsCollector(meterRegistry)) // 3. 指标聚合
        .splitRuleCache(new RedisSplitRuleCache(redis, objectMapper))   // 4. 分账规则缓存
        .idempotencyMode(IdempotencyMode.BLOCK)  // 可选:阻断模式
        .build();
}

// 应用退出时优雅关闭
@PreDestroy
public void shutdown() {
    paymentClient.shutdown();  // 释放线程池、定时任务等资源
}

最佳实践与注意事项

1. Redis Key 设计与隔离

# 幂等性记录(60 秒自动过期)
pay:idem:PAY:ORDER_20260627_001    -> 1751100000000
pay:idem:REFUND:RF_20260627_001    -> 1751100000000

# 证书缓存(Hash 结构)
pay:certificates:
  ├─ {serialNo}:cert   -> Base64(证书内容)
  └─ {serialNo}:expire -> 过期时间戳

# 分账规则缓存(Key-Value,30 分钟自动过期)
pay:split:rule:10086  -> {"ruleId":10086,"ruleName":"标准分账","mode":"RATIO","receivers":[...]}
pay:split:rule:10087  -> {"ruleId":10087,"ruleName":"主播分成","mode":"RATIO","receivers":[...]}

# 多环境隔离:使用 Key 前缀区分
pay:idem:dev:PAY:xxx    # 开发环境
pay:idem:prod:PAY:xxx   # 生产环境
pay:split:rule:dev:10086  # 开发环境规则
pay:split:rule:prod:10086 # 生产环境规则

2. 幂等窗口时间

  • SDK 默认窗口为 60 秒IdempotencyManager.DEFAULT_WINDOW_MS
  • 窗口过短可能导致网络抖动时的重试请求未被拦截
  • 窗口过长可能导致正常重复下单被误拦(如用户取消后重新支付同一订单号)
  • 建议:保持默认 60 秒,业务层使用唯一订单号(如 UUID / 雪花 ID)避免冲突

3. 资源优雅关闭

  • 在应用退出时调用 PaymentClient.shutdown()
  • 确保线程池、定时任务、证书刷新任务等资源被正确释放
  • Spring Boot 中使用 @PreDestroy 注解
  • SplitRuleCache 实现类无需特殊关闭(Redis 连接由连接池统一管理),若使用本地文件缓存则需在 shutdown 时清空

4. 监控告警建议

  • 支付成功率 < 99%,持续 5 分钟 → 告警
  • 平均响应时间 > 3 秒,持续 5 分钟 → 告警
  • 回调验签失败数 > 0 → 立即告警(可能密钥泄露或证书过期)
  • HTTP 5xx 错误率 > 5% → 告警(支付渠道故障)
  • 重复请求数异常升高 → 排查业务方是否频繁重试
  • 为每个实例设置唯一标识(hostname / pod-name),便于问题定位

5. VAS 自动上报的幂等性

自 v1.2.0 起,VAS 自动单据上报监听器(AutoReceiptUploadListener)会自动透传您注入的 IdempotencyStore,确保多实例部署时单据上报也具备跨实例幂等能力,无需额外配置。

单实例 vs 多实例对比

维度 单实例部署 多实例部署
幂等性存储 InMemoryIdempotencyStore
(进程内 ConcurrentHashMap)
RedisIdempotencyStore
(SETNX + TTL 原子操作)
证书缓存 InMemoryCertificateStore
(每实例独立下载)
RedisCertificateStore
(Hash 共享缓存)
指标采集 DefaultMetricsCollector
(本地内存)
PrometheusMetricsCollector
(Micrometer + Grafana)
配置方式 无需额外配置
(开箱即用)
注入 3 个 SPI 实现
(Builder 链式调用)
额外依赖 Redis + Micrometer
(Spring Boot 已内置)
迁移提示:从单实例迁移到多实例时,业务代码无需任何修改。只需在构建 PaymentClient 时通过 Builder 注入对应的 SPI 实现即可,SDK 内部所有逻辑自动切换为分布式模式。
官方 SDK: Java · 已通过生产环境测试,开箱即用,内置签名、重试、加密等最佳实践。其他语言 SDK 正在开发中,敬请期待。
J

Java SDK

v1.0.0 · 2026-06-10

兼容 Java 8+,Spring Boot 开箱即用

文档 GitHub

基础版快速接入示例 · Java

Maven 引入依赖,一行配置即可完成初始化:

<!-- pom.xml -->
<dependency>
  <groupId>com.kairwallet</groupId>
  <artifactId>payment-sdk-java</artifactId>
  <version>1.0.0</version>
</dependency>

// Java 代码
import com.kairwallet.payment.api.*;
import com.kairwallet.payment.api.model.*;

PaymentClient client = PaymentClients.builder()
    .wechat(WechatChannelConfig.builder()
        .mchId("1600000000")
        .appId("wx_xxxxxxxxxxxx")
        .apiV3Key(System.getenv("WECHAT_V3_KEY"))
        .merchantPrivateKey(System.getenv("WECHAT_PRIVATE_KEY"))
        .build())
    .build();

// 发起支付
PaymentResponse resp = client.pay(PaymentRequest.builder()
    .outTradeNo("ORD-20260618-001824")
    .amount(new BigDecimal("2480.00"))
    .channel(Channel.WECHAT)
    .subject("企业服务年费")
    .build());

System.out.println("支付链接: " + resp.getPayUrl());

SDK 核心能力

签名与认证

自动处理 HMAC-SHA256 签名,无需手动拼接参数

自动重试

内置指数退避重试策略,应对网络抖动与限流

超时控制

连接、读取、写入三段超时可独立配置

完整日志

支持请求/响应日志脱敏记录,便于问题排查

沙箱环境

内置 sandbox 环境切换,开发调试无缝衔接

类型安全

强类型 DTO 定义,编译期发现参数错误