rentease/internal/driver/stripe/client.go
Ruidy 6ca13cdffd
chore(stripe): upgrade to stripe-go v83
Upgrade Stripe SDK from v79 to v83 across the codebase. Update all
imports to use github.com/stripe/stripe-go/v83 and refactor client usage
to match the new API, including changes to PaymentIntents listing.
Update documentation and plans to reference the new version. Remove
references to the old version from go.mod and go.sum.
2025-10-05 21:09:06 +02:00

169 lines
4.1 KiB
Go

package stripe
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/stripe/stripe-go/v83"
)
// Option configures a Client instance.
type Option func(*Client)
// WithAccount sets the Stripe connected account identifier used for requests.
func WithAccount(account string) Option {
return func(c *Client) {
c.account = strings.TrimSpace(account)
}
}
// Client wraps Stripe's SDK to expose the subset of functionality needed by the
// application while keeping the rest of the codebase decoupled from the SDK.
type Client struct {
api *stripe.Client
account string
}
// New constructs a Client using the provided secret key. The key must not be empty.
func New(secretKey string, opts ...Option) (*Client, error) {
trimmed := strings.TrimSpace(secretKey)
if trimmed == "" {
return nil, errors.New("stripe secret key is required")
}
api := stripe.NewClient(trimmed)
client := &Client{api: api}
for _, opt := range opts {
opt(client)
}
return client, nil
}
// Payment represents the subset of payment intent data consumed by the booking service.
type Payment struct {
ID string
Amount float64
Currency string
Status string
PaymentMethod string
BookingID *uint
Created time.Time
}
// NormalizePaymentIntent converts a Stripe payment intent into the simplified Payment structure used
// by the application. Fields that are absent default to their zero values.
func NormalizePaymentIntent(pi *stripe.PaymentIntent) Payment {
if pi == nil {
return Payment{}
}
amount := float64(pi.AmountReceived) / 100.0
if amount == 0 {
amount = float64(pi.Amount) / 100.0
}
return Payment{
ID: pi.ID,
Amount: amount,
Currency: strings.ToUpper(string(pi.Currency)),
Status: string(pi.Status),
PaymentMethod: deriveMethod(pi),
BookingID: extractBookingID(pi.Metadata),
Created: time.Unix(int64(pi.Created), 0),
}
}
// ListPaymentsParams defines the time boundaries used when fetching Stripe payments.
type ListPaymentsParams struct {
From time.Time
To time.Time
}
// ListPayments fetches payment intents created within the provided time range. The
// results are normalised into Payment structs suitable for downstream processing.
func (c *Client) ListPayments(ctx context.Context, params ListPaymentsParams) ([]Payment, error) {
listParams := &stripe.PaymentIntentListParams{}
listParams.Context = ctx
listParams.AddExpand("data.latest_charge")
listParams.AddExpand("data.payment_method")
if !params.From.IsZero() {
listParams.Filters.AddFilter("created", "gte", strconv.FormatInt(params.From.Unix(), 10))
}
if !params.To.IsZero() {
listParams.Filters.AddFilter("created", "lte", strconv.FormatInt(params.To.Unix(), 10))
}
if c.account != "" {
listParams.SetStripeAccount(c.account)
}
seq := c.api.V1PaymentIntents.List(ctx, listParams)
payments := make([]Payment, 0)
var listErr error
seq(func(pi *stripe.PaymentIntent, err error) bool {
if err != nil {
listErr = err
return false
}
if pi == nil {
return true
}
payments = append(payments, NormalizePaymentIntent(pi))
return true
})
if listErr != nil {
return nil, fmt.Errorf("stripe payment intents iteration failed: %w", listErr)
}
return payments, nil
}
func deriveMethod(pi *stripe.PaymentIntent) string {
if pi == nil {
return ""
}
if pi.LatestCharge != nil && pi.LatestCharge.PaymentMethodDetails != nil {
typ := pi.LatestCharge.PaymentMethodDetails.Type
if typ != "" {
return string(typ)
}
}
if pi.PaymentMethod != nil && pi.PaymentMethod.Type != "" {
return string(pi.PaymentMethod.Type)
}
if len(pi.PaymentMethodTypes) > 0 {
return pi.PaymentMethodTypes[0]
}
return ""
}
func extractBookingID(metadata map[string]string) *uint {
if len(metadata) == 0 {
return nil
}
keys := []string{"booking_id", "bookingId", "bookingID"}
for _, key := range keys {
if raw, ok := metadata[key]; ok {
if raw == "" {
continue
}
if id, err := strconv.ParseUint(raw, 10, 32); err == nil {
value := uint(id)
return &value
}
}
}
return nil
}