stripe.go 7.0 KB

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