mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
231 lines
6 KiB
Go
231 lines
6 KiB
Go
package stripe
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/stripe/stripe-go/v83"
|
|
)
|
|
|
|
// Option configures a Client instance.
|
|
type Option func(*Client)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// CreatePaymentLinkParams encapsulates the data required to generate a Stripe payment link.
|
|
type CreatePaymentLinkParams struct {
|
|
Amount float64
|
|
Currency string
|
|
BookingID uint
|
|
Description string
|
|
PaymentMethodTypes []string
|
|
}
|
|
|
|
// CreatePaymentLink creates a payment link for the provided booking metadata and amount.
|
|
func (c *Client) CreatePaymentLink(ctx context.Context, params CreatePaymentLinkParams) (string, error) {
|
|
linkParams, err := buildPaymentLinkCreateParams(params)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
linkParams.Context = ctx
|
|
|
|
pl, err := c.api.V1PaymentLinks.Create(ctx, linkParams)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create payment link: %w", err)
|
|
}
|
|
|
|
return pl.URL, nil
|
|
}
|
|
|
|
func buildPaymentLinkCreateParams(params CreatePaymentLinkParams) (*stripe.PaymentLinkCreateParams, error) {
|
|
if params.Amount <= 0 {
|
|
return nil, errors.New("amount must be greater than zero")
|
|
}
|
|
currency := strings.ToLower(strings.TrimSpace(params.Currency))
|
|
if currency == "" {
|
|
return nil, errors.New("currency is required")
|
|
}
|
|
if params.BookingID == 0 {
|
|
return nil, errors.New("booking id is required")
|
|
}
|
|
|
|
amountCents := int64(math.Round(params.Amount * 100))
|
|
metadata := map[string]string{
|
|
"booking_id": strconv.FormatUint(uint64(params.BookingID), 10),
|
|
}
|
|
|
|
linkParams := &stripe.PaymentLinkCreateParams{
|
|
LineItems: []*stripe.PaymentLinkCreateLineItemParams{
|
|
{
|
|
PriceData: &stripe.PaymentLinkCreateLineItemPriceDataParams{
|
|
Currency: stripe.String(currency),
|
|
ProductData: &stripe.PaymentLinkCreateLineItemPriceDataProductDataParams{
|
|
Name: stripe.String(strings.TrimSpace(params.Description)),
|
|
},
|
|
UnitAmount: new(amountCents),
|
|
},
|
|
Quantity: stripe.Int64(1),
|
|
},
|
|
},
|
|
Metadata: metadata,
|
|
PaymentIntentData: &stripe.PaymentLinkCreatePaymentIntentDataParams{
|
|
Metadata: metadata,
|
|
},
|
|
}
|
|
|
|
if linkParams.LineItems[0].PriceData.ProductData.Name == nil || *linkParams.LineItems[0].PriceData.ProductData.Name == "" {
|
|
linkParams.LineItems[0].PriceData.ProductData.Name = stripe.String(fmt.Sprintf("Booking %d", params.BookingID))
|
|
}
|
|
|
|
if len(params.PaymentMethodTypes) > 0 {
|
|
linkParams.PaymentMethodTypes = stripe.StringSlice(params.PaymentMethodTypes)
|
|
}
|
|
|
|
return linkParams, 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
|
|
}
|