create.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package billing
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "github.com/porter-dev/porter/api/server/handlers"
  7. "github.com/porter-dev/porter/api/server/shared"
  8. "github.com/porter-dev/porter/api/server/shared/apierrors"
  9. "github.com/porter-dev/porter/api/server/shared/config"
  10. "github.com/porter-dev/porter/api/server/shared/requestutils"
  11. "github.com/porter-dev/porter/api/types"
  12. "github.com/porter-dev/porter/internal/analytics"
  13. "github.com/porter-dev/porter/internal/models"
  14. "github.com/porter-dev/porter/internal/telemetry"
  15. )
  16. // CreateBillingHandler is a handler for creating payment methods
  17. type CreateBillingHandler struct {
  18. handlers.PorterHandlerWriter
  19. }
  20. // SetDefaultBillingHandler is a handler for setting default payment method
  21. type SetDefaultBillingHandler struct {
  22. handlers.PorterHandlerWriter
  23. }
  24. // NewCreateBillingHandler will create a new CreateBillingHandler
  25. func NewCreateBillingHandler(
  26. config *config.Config,
  27. decoderValidator shared.RequestDecoderValidator,
  28. writer shared.ResultWriter,
  29. ) *CreateBillingHandler {
  30. return &CreateBillingHandler{
  31. PorterHandlerWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  32. }
  33. }
  34. func (c *CreateBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  35. ctx, span := telemetry.NewSpan(r.Context(), "serve-create-billing-method")
  36. defer span.End()
  37. proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
  38. user, _ := ctx.Value(types.UserScope).(*models.User)
  39. clientSecret, err := c.Config().BillingManager.StripeClient.CreatePaymentMethod(ctx, proj.BillingID)
  40. if err != nil {
  41. err := telemetry.Error(ctx, span, err, "error creating payment method")
  42. c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating payment method: %w", err)))
  43. return
  44. }
  45. telemetry.WithAttributes(span,
  46. telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
  47. telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
  48. )
  49. if proj.EnableSandbox {
  50. // Grant a reward to the project that referred this user after linking a payment method
  51. err = c.grantRewardIfReferral(ctx, user.ID)
  52. if err != nil {
  53. err := telemetry.Error(ctx, span, err, "error granting credits reward")
  54. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  55. return
  56. }
  57. }
  58. c.WriteResult(w, r, clientSecret)
  59. }
  60. // NewSetDefaultBillingHandler will create a new CreateBillingHandler
  61. func NewSetDefaultBillingHandler(
  62. config *config.Config,
  63. writer shared.ResultWriter,
  64. ) *SetDefaultBillingHandler {
  65. return &SetDefaultBillingHandler{
  66. PorterHandlerWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
  67. }
  68. }
  69. func (c *SetDefaultBillingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  70. ctx, span := telemetry.NewSpan(r.Context(), "serve-set-default-billing-method")
  71. defer span.End()
  72. user, _ := r.Context().Value(types.UserScope).(*models.User)
  73. proj, _ := ctx.Value(types.ProjectScope).(*models.Project)
  74. paymentMethodID, reqErr := requestutils.GetURLParamString(r, types.URLParamPaymentMethodID)
  75. if reqErr != nil {
  76. err := telemetry.Error(ctx, span, reqErr, "error setting default payment method")
  77. c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error setting default payment method: %w", err)))
  78. return
  79. }
  80. err := c.Config().BillingManager.StripeClient.SetDefaultPaymentMethod(ctx, paymentMethodID, proj.BillingID)
  81. if err != nil {
  82. err := telemetry.Error(ctx, span, err, "error setting default payment method")
  83. c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error setting default payment method: %w", err)))
  84. return
  85. }
  86. telemetry.WithAttributes(span,
  87. telemetry.AttributeKV{Key: "project-id", Value: proj.ID},
  88. telemetry.AttributeKV{Key: "customer-id", Value: proj.BillingID},
  89. telemetry.AttributeKV{Key: "payment-method-id", Value: paymentMethodID},
  90. )
  91. _ = c.Config().AnalyticsClient.Track(analytics.PaymentMethodAttachedTrack(&analytics.PaymentMethodCreateDeleteTrackOpts{
  92. ProjectScopedTrackOpts: analytics.GetProjectScopedTrackOpts(user.ID, proj.ID),
  93. Email: user.Email,
  94. FirstName: user.FirstName,
  95. LastName: user.LastName,
  96. CompanyName: user.CompanyName,
  97. }))
  98. c.WriteResult(w, r, "")
  99. }
  100. func (c *CreateBillingHandler) grantRewardIfReferral(ctx context.Context, referredUserID uint) (err error) {
  101. ctx, span := telemetry.NewSpan(ctx, "grant-referral-reward")
  102. defer span.End()
  103. referral, err := c.Repo().Referral().GetReferralByReferredID(referredUserID)
  104. if err != nil {
  105. return telemetry.Error(ctx, span, err, "failed to find referral by referred id")
  106. }
  107. if referral == nil {
  108. return nil
  109. }
  110. referralCount, err := c.Repo().Referral().CountReferralsByProjectID(referral.ProjectID, models.ReferralStatusCompleted)
  111. if err != nil {
  112. return telemetry.Error(ctx, span, err, "failed to get referral count by referrer id")
  113. }
  114. maxReferralRewards := c.Config().BillingManager.LagoClient.MaxReferralRewards
  115. if referralCount >= maxReferralRewards {
  116. return nil
  117. }
  118. referrerProject, err := c.Repo().Project().ReadProject(referral.ProjectID)
  119. if err != nil {
  120. return telemetry.Error(ctx, span, err, "failed to find referrer project")
  121. }
  122. if referral != nil && referral.Status != models.ReferralStatusCompleted {
  123. name := "Referral reward"
  124. rewardAmount := c.Config().BillingManager.LagoClient.DefaultRewardAmountCents
  125. err := c.Config().BillingManager.LagoClient.CreateCreditsGrant(ctx, referrerProject.ID, name, rewardAmount, referrerProject.EnableSandbox)
  126. if err != nil {
  127. return telemetry.Error(ctx, span, err, "failed to grand credits reward")
  128. }
  129. referral.Status = models.ReferralStatusCompleted
  130. _, err = c.Repo().Referral().UpdateReferral(referral)
  131. if err != nil {
  132. return telemetry.Error(ctx, span, err, "error while updating referral")
  133. }
  134. }
  135. return nil
  136. }