stripe.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package billing
  2. import (
  3. "context"
  4. "fmt"
  5. "strconv"
  6. "github.com/porter-dev/porter/api/types"
  7. "github.com/porter-dev/porter/internal/models"
  8. "github.com/porter-dev/porter/internal/telemetry"
  9. "github.com/stripe/stripe-go/v76"
  10. "github.com/stripe/stripe-go/v76/customer"
  11. "github.com/stripe/stripe-go/v76/paymentmethod"
  12. "github.com/stripe/stripe-go/v76/setupintent"
  13. )
  14. // StripeBillingManager interacts with the Stripe API to manage payment methods
  15. // and customers
  16. type StripeBillingManager struct {
  17. StripeSecretKey string
  18. StripePublishableKey string
  19. }
  20. // CreateCustomer will create a customer in Stripe only if the project doesn't have a BillingID
  21. func (s *StripeBillingManager) CreateCustomer(ctx context.Context, userEmail string, proj *models.Project) (customerID string, err error) {
  22. ctx, span := telemetry.NewSpan(ctx, "create-stripe-customer")
  23. defer span.End()
  24. stripe.Key = s.StripeSecretKey
  25. telemetry.WithAttributes(span,
  26. telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
  27. )
  28. if proj.BillingID == "" {
  29. // Create customer if not exists
  30. customerName := fmt.Sprintf("project_%s", proj.Name)
  31. projectIDStr := strconv.FormatUint(uint64(proj.ID), 10)
  32. params := &stripe.CustomerParams{
  33. Name: stripe.String(customerName),
  34. Email: stripe.String(userEmail),
  35. Metadata: map[string]string{
  36. "porter_project_id": projectIDStr,
  37. },
  38. }
  39. // Create in Stripe
  40. customer, err := customer.New(params)
  41. if err != nil {
  42. return "", telemetry.Error(ctx, span, err, "failed to create Stripe customer")
  43. }
  44. customerID = customer.ID
  45. }
  46. return customerID, nil
  47. }
  48. // DeleteCustomer will delete the customer from the billing provider
  49. func (s *StripeBillingManager) DeleteCustomer(ctx context.Context, proj *models.Project) (err error) {
  50. ctx, span := telemetry.NewSpan(ctx, "delete-stripe-customer")
  51. defer span.End()
  52. stripe.Key = s.StripeSecretKey
  53. telemetry.WithAttributes(span,
  54. telemetry.AttributeKV{Key: "billing-id", Value: proj.BillingID},
  55. )
  56. if proj.BillingID != "" {
  57. params := &stripe.CustomerParams{}
  58. _, err := customer.Del(proj.BillingID, params)
  59. if err != nil {
  60. return telemetry.Error(ctx, span, err, "failed to delete Stripe customer")
  61. }
  62. }
  63. return nil
  64. }
  65. // CheckPaymentEnabled will return true if the project has a payment method added, false otherwise
  66. func (s *StripeBillingManager) CheckPaymentEnabled(ctx context.Context, proj *models.Project) (paymentEnabled bool, err error) {
  67. _, span := telemetry.NewSpan(ctx, "check-stripe-payment-enabled")
  68. defer span.End()
  69. stripe.Key = s.StripeSecretKey
  70. params := &stripe.PaymentMethodListParams{
  71. Customer: stripe.String(proj.BillingID),
  72. Type: stripe.String(string(stripe.PaymentMethodTypeCard)),
  73. }
  74. result := paymentmethod.List(params)
  75. return result.Next(), nil
  76. }
  77. // ListPaymentMethod will return all payment methods for the project
  78. func (s *StripeBillingManager) ListPaymentMethod(ctx context.Context, proj *models.Project) (paymentMethods []types.PaymentMethod, err error) {
  79. ctx, span := telemetry.NewSpan(ctx, "list-stripe-payment-method")
  80. defer span.End()
  81. stripe.Key = s.StripeSecretKey
  82. // Get configured payment methods
  83. params := &stripe.PaymentMethodListParams{
  84. Customer: stripe.String(proj.BillingID),
  85. Type: stripe.String(string(stripe.PaymentMethodTypeCard)),
  86. }
  87. result := paymentmethod.List(params)
  88. defaultPaymentExists, defaultPaymentID, err := s.checkDefaultPaymentMethod(proj.BillingID)
  89. if err != nil {
  90. return paymentMethods, telemetry.Error(ctx, span, err, "failed to list Stripe payment method")
  91. }
  92. for result.Next() {
  93. stripePaymentMethod := result.PaymentMethod()
  94. var isDefaultPaymentMethod bool
  95. if stripePaymentMethod.ID == defaultPaymentID {
  96. isDefaultPaymentMethod = true
  97. }
  98. paymentMethods = append(paymentMethods, types.PaymentMethod{
  99. ID: stripePaymentMethod.ID,
  100. DisplayBrand: stripePaymentMethod.Card.DisplayBrand,
  101. Last4: stripePaymentMethod.Card.Last4,
  102. ExpMonth: stripePaymentMethod.Card.ExpMonth,
  103. ExpYear: stripePaymentMethod.Card.ExpYear,
  104. Default: isDefaultPaymentMethod,
  105. })
  106. }
  107. // Set default payment method when project has payment methods enabled but
  108. // no default setup
  109. if len(paymentMethods) > 0 && !defaultPaymentExists {
  110. err = s.SetDefaultPaymentMethod(ctx, paymentMethods[len(paymentMethods)-1].ID, proj)
  111. if err != nil {
  112. return paymentMethods, telemetry.Error(ctx, span, err, "failed to list Stripe payment method")
  113. }
  114. }
  115. return paymentMethods, nil
  116. }
  117. // CreatePaymentMethod will add a new payment method to the project in Stripe
  118. func (s *StripeBillingManager) CreatePaymentMethod(ctx context.Context, proj *models.Project) (clientSecret string, err error) {
  119. ctx, span := telemetry.NewSpan(ctx, "create-stripe-payment-method")
  120. defer span.End()
  121. stripe.Key = s.StripeSecretKey
  122. params := &stripe.SetupIntentParams{
  123. Customer: stripe.String(proj.BillingID),
  124. AutomaticPaymentMethods: &stripe.SetupIntentAutomaticPaymentMethodsParams{
  125. Enabled: stripe.Bool(false),
  126. },
  127. PaymentMethodTypes: []*string{stripe.String("card")},
  128. }
  129. intent, err := setupintent.New(params)
  130. if err != nil {
  131. return "", telemetry.Error(ctx, span, err, "failed to create Stripe payment method")
  132. }
  133. return intent.ClientSecret, nil
  134. }
  135. // SetDefaultPaymentMethod will add a new payment method to the project in Stripe
  136. func (s *StripeBillingManager) SetDefaultPaymentMethod(ctx context.Context, paymentMethodID string, proj *models.Project) (err error) {
  137. ctx, span := telemetry.NewSpan(ctx, "set-default-stripe-payment-method")
  138. defer span.End()
  139. stripe.Key = s.StripeSecretKey
  140. params := &stripe.CustomerParams{
  141. InvoiceSettings: &stripe.CustomerInvoiceSettingsParams{
  142. DefaultPaymentMethod: stripe.String(paymentMethodID),
  143. },
  144. }
  145. _, err = customer.Update(proj.BillingID, params)
  146. if err != nil {
  147. return telemetry.Error(ctx, span, err, "failed to set default Stripe payment method")
  148. }
  149. return nil
  150. }
  151. // DeletePaymentMethod will remove a payment method for the project in Stripe
  152. func (s *StripeBillingManager) DeletePaymentMethod(ctx context.Context, paymentMethodID string) (err error) {
  153. ctx, span := telemetry.NewSpan(ctx, "delete-stripe-payment-method")
  154. defer span.End()
  155. stripe.Key = s.StripeSecretKey
  156. _, err = paymentmethod.Detach(paymentMethodID, nil)
  157. if err != nil {
  158. return telemetry.Error(ctx, span, err, "failed to delete Stripe payment method")
  159. }
  160. return nil
  161. }
  162. // GetPublishableKey returns the Stripe publishable key
  163. func (s *StripeBillingManager) GetPublishableKey(ctx context.Context) (key string) {
  164. _, span := telemetry.NewSpan(ctx, "get-stripe-publishable-key")
  165. defer span.End()
  166. return s.StripePublishableKey
  167. }
  168. func (s *StripeBillingManager) checkDefaultPaymentMethod(customerID string) (defaultPaymentExists bool, defaultPaymentID string, err error) {
  169. // Get customer to check default payment method
  170. customer, err := customer.Get(customerID, nil)
  171. if err != nil {
  172. return defaultPaymentExists, defaultPaymentID, err
  173. }
  174. if customer.InvoiceSettings != nil && customer.InvoiceSettings.DefaultPaymentMethod != nil {
  175. defaultPaymentExists = true
  176. defaultPaymentID = customer.InvoiceSettings.DefaultPaymentMethod.ID
  177. }
  178. return defaultPaymentExists, defaultPaymentID, err
  179. }