ITADN
thoven87/stripe-kit
thoven87/stripe-kit · 文件
文件最后提交记录最后更新时间
README.md

StripeKit

Swift SwiftNIO CI License

A Swift package for communicating with the Stripe API in Server Side Swift applications.

Stripe API Version

2026-05-27.dahlia

Requirements

  • Swift 6.2+
  • Linux, macOS 12+ / iOS 15+ / tvOS 15+ / watchOS 8+

Installation

Add StripeKit to your Package.swift:

.package(url: "https://github.com/vapor-community/stripe-kit.git", from: "22.0.0")

Then add it as a target dependency:

.target(name: "MyTarget", dependencies: [
    .product(name: "StripeKit", package: "stripe-kit")
])

Getting Started

Initialize a StripeClient with an HTTPClient and your Stripe API key:

import AsyncHTTPClient
import StripeKit

let httpClient = HTTPClient(eventLoopGroupProvider: .singleton)
let stripe = StripeClient(httpClient: httpClient, apiKey: "sk_live_...")

Each API is accessed as a property on the client:

let charge = try await stripe.charges.create(
    amount: 2500,
    currency: .usd,
    description: "A server written in Swift.",
    source: "tok_visa"
)

if charge.status == .succeeded {
    print("Payment succeeded 🎉")
}

Expandable Objects

StripeKit supports expandable objects via three property wrappers: @Expandable, @DynamicExpandable, and @ExpandableCollection.

All API routes that can return expanded objects accept an expand: [String]? parameter.

@Expandable

// Expand a single field
let paymentIntent = try await stripe.paymentIntents.create(
    amount: 2500, currency: .usd, expand: ["customer"]
)
paymentIntent.$customer?.email // "user@example.com"

// Expand multiple fields
let paymentIntent = try await stripe.paymentIntents.create(
    amount: 2500, currency: .usd, expand: ["customer", "payment_method"]
)
paymentIntent.$customer?.email          // "user@example.com"
paymentIntent.$paymentMethod?.card?.last4 // "4242"

// Expand nested fields
let paymentIntent = try await stripe.paymentIntents.create(
    amount: 2500, currency: .usd, expand: ["payment_method.customer"]
)
paymentIntent.$paymentMethod?.card?.last4    // "4242"
paymentIntent.$paymentMethod?.$customer?.email // "user@example.com"

Note: For list operations, expanded fields must start with data:

let list = try await stripe.paymentIntents.listAll(filter: ["expand[]": "data.customer"])
list.data?.first?.$customer?.email // "user@example.com"

@DynamicExpandable

Some objects can expand into one of several types. For example, ApplicationFee.originatingTransaction can be either a Charge or a Transfer:

let fee = try await stripe.applicationFees.retrieve(
    fee: "fee_1234", expand: ["originating_transaction"]
)
fee.$originatingTransaction(as: Charge.self)?.amount      // 2500
fee.$originatingTransaction(as: Transfer.self)?.destination // "acct_..."

@ExpandableCollection

let invoice = try await stripe.invoices.retrieve(invoice: "in_12345", expand: ["discounts"])

invoice.discounts           // ["di_1", "di_2", ...]  (String IDs)
invoice.$discounts?[0].id  // "di_1"  (expanded Discount objects)

Custom Headers

Use the builder-style addHeaders(_:) API to attach per-request headers such as Stripe-Account for Connect or Idempotency-Key:

// Connected account
let charge = try await stripe.charges
    .addHeaders(["Stripe-Account": "acct_12345"])
    .create(amount: 2500, currency: .usd, source: "tok_visa")

// Idempotency key
let refund = try await stripe.refunds
    .addHeaders(["Idempotency-Key": UUID().uuidString])
    .create(charge: "ch_12345", reason: .requestedByCustomer)

Note: Modified headers persist on the route instance for the lifetime of the reference. When accessing the StripeClient inside a request scope (e.g. a Vapor route handler), headers are not retained between requests.

Webhooks

Verify and decode Stripe webhook events:

func handleStripeWebhook(req: Request) async throws -> Response {
    let signature = req.headers.first(name: "Stripe-Signature") ?? ""
    try StripeClient.verifySignature(
        payload: Data(req.body.readableBytesView),
        header: signature,
        secret: "whsec_..."
    )

    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    decoder.keyDecodingStrategy = .convertFromSnakeCase

    let event = try decoder.decode(Event.self, from: Data(req.body.readableBytesView))

    switch (event.type, event.data?.object) {
    case (.paymentIntentSucceeded, .paymentIntent(let paymentIntent)):
        print("Payment succeeded: \(paymentIntent.id)")
        return Response(status: .ok)
    default:
        return Response(status: .ok)
    }
}

Using with Vapor

import Vapor
import StripeKit

extension Application {
    public var stripe: StripeClient {
        guard let key = Environment.get("STRIPE_API_KEY") else {
            fatalError("STRIPE_API_KEY environment variable is required")
        }
        return StripeClient(httpClient: self.http.client.shared, apiKey: key)
    }
}

extension Request {
    private struct StripeKey: StorageKey {
        typealias Value = StripeClient
    }

    public var stripe: StripeClient {
        if let existing = application.storage[StripeKey.self] {
            return existing
        }
        guard let key = Environment.get("STRIPE_API_KEY") else {
            fatalError("STRIPE_API_KEY environment variable is required")
        }
        let client = StripeClient(httpClient: application.http.client.shared, apiKey: key)
        application.storage[StripeKey.self] = client
        return client
    }
}

Webhook Signature Verification with Vapor

extension StripeClient {
    /// Verifies a Stripe webhook signature from a Vapor `Request`.
    /// - Parameters:
    ///   - req: The incoming `Request`.
    ///   - secret: The webhook endpoint secret (`whsec_...`).
    ///   - tolerance: Maximum age of the timestamp in seconds (default: 300).
    public static func verifySignature(
        for req: Request,
        secret: String,
        tolerance: Double = 300
    ) throws {
        guard let header = req.headers.first(name: "Stripe-Signature") else {
            throw StripeSignatureError.unableToParseHeader
        }
        guard let body = req.body.data else {
            throw StripeSignatureError.noMatchingSignatureFound
        }
        try StripeClient.verifySignature(
            payload: Data(body.readableBytesView),
            header: header,
            secret: secret,
            tolerance: tolerance
        )
    }
}

extension StripeSignatureError: AbortError {
    public var reason: String {
        switch self {
        case .unableToParseHeader:    return "Unable to parse Stripe-Signature header"
        case .noMatchingSignatureFound: return "No matching signature was found"
        case .timestampNotTolerated:  return "Timestamp was not tolerated"
        }
    }

    public var status: HTTPResponseStatus { .badRequest }
}

Implemented APIs

Core Resources

  • Balance
  • Balance Transactions
  • Charges
  • Customers
  • Customer Sessions
  • Disputes
  • Events
  • Files
  • File Links
  • Mandates
  • Payment Intents
  • Setup Intents
  • Setup Attempts
  • Payouts
  • Refunds
  • Confirmation Tokens
  • Tokens
  • Ephemeral Keys

Payment Methods

  • Payment Methods
  • Payment Method Configurations
  • Payment Method Domains
  • Bank Accounts
  • Cash Balance
  • Cash Balance Transactions
  • Cards
  • Sources

Products

  • Products
  • Prices
  • Coupons
  • Promotion Codes
  • Discounts
  • Tax Codes
  • Tax Rates
  • Shipping Rates

Checkout

  • Sessions

  • Payment Links

Billing

  • Alerts
  • Credit Notes
  • Credit Grants
  • Credit Balance Summary
  • Credit Balance Transactions
  • Customer Balance Transactions
  • Customer Portal
  • Customer Tax IDs
  • Invoices
  • Invoice Items
  • Invoice Rendering Templates
  • Meters
  • Meter Events
  • Meter Event Adjustments
  • Plans
  • Quotes
  • Quote Line Items
  • Subscriptions
  • Subscription Items
  • Subscription Schedule
  • Test Clocks
  • Usage Records

Entitlements

  • Features
  • Product Features
  • Active Entitlements

Connect

  • Account
  • Account Login Links
  • Account Links
  • Account Sessions
  • Application Fees
  • Application Fee Refunds
  • Capabilities
  • Country Specs
  • External Accounts
  • Persons
  • Top-ups
  • Transfers
  • Transfer Reversals
  • Secret Management

Fraud

  • Early Fraud Warnings
  • Reviews
  • Value Lists
  • Value List Items

Issuing

  • Authorizations
  • Cardholders
  • Cards
  • Disputes
  • Funding Instructions
  • Transactions

Terminal

  • Connection Tokens
  • Locations
  • Readers
  • Hardware Orders
  • Hardware Products
  • Hardware SKUs
  • Hardware Shipping Methods
  • Configurations

Sigma

  • Scheduled Queries

Reporting

  • Report Runs
  • Report Types

Identity

  • Verification Sessions
  • Verification Reports

Webhooks

  • Webhook Endpoints
  • Signature Verification

License

StripeKit is available under the MIT license. See LICENSE for details.