stripe.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. package controller
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "strconv"
  9. "time"
  10. "github.com/ente-io/museum/pkg/controller/commonbilling"
  11. "github.com/ente-io/museum/pkg/controller/discord"
  12. "github.com/ente-io/museum/pkg/controller/offer"
  13. "github.com/ente-io/museum/pkg/repo/storagebonus"
  14. "github.com/ente-io/museum/ente"
  15. emailCtrl "github.com/ente-io/museum/pkg/controller/email"
  16. "github.com/ente-io/museum/pkg/repo"
  17. "github.com/ente-io/museum/pkg/utils/billing"
  18. "github.com/ente-io/museum/pkg/utils/email"
  19. "github.com/ente-io/stacktrace"
  20. log "github.com/sirupsen/logrus"
  21. "github.com/spf13/viper"
  22. "github.com/stripe/stripe-go/v72"
  23. "github.com/stripe/stripe-go/v72/client"
  24. "github.com/stripe/stripe-go/v72/webhook"
  25. "golang.org/x/text/currency"
  26. )
  27. // StripeController provides abstractions for handling billing on Stripe
  28. type StripeController struct {
  29. StripeClients ente.StripeClientPerAccount
  30. BillingPlansPerAccount ente.BillingPlansPerAccount
  31. BillingRepo *repo.BillingRepository
  32. FileRepo *repo.FileRepository
  33. UserRepo *repo.UserRepository
  34. StorageBonusRepo *storagebonus.Repository
  35. DiscordController *discord.DiscordController
  36. EmailNotificationCtrl *emailCtrl.EmailNotificationController
  37. OfferController *offer.OfferController
  38. CommonBillCtrl *commonbilling.Controller
  39. }
  40. // A flag we set on Stripe subscriptions to indicate that we should skip on
  41. // sending out the email when the subscription has been cancelled.
  42. //
  43. // This is needed e.g. if this cancellation was as part of a user initiated
  44. // account deletion.
  45. const SkipMailKey = "skip_mail"
  46. const BufferPeriodOnPaymentFailureInDays = 7
  47. // Return a new instance of StripeController
  48. func NewStripeController(plans ente.BillingPlansPerAccount, stripeClients ente.StripeClientPerAccount, billingRepo *repo.BillingRepository, fileRepo *repo.FileRepository, userRepo *repo.UserRepository, storageBonusRepo *storagebonus.Repository, discordController *discord.DiscordController, emailNotificationController *emailCtrl.EmailNotificationController, offerController *offer.OfferController, commonBillCtrl *commonbilling.Controller) *StripeController {
  49. return &StripeController{
  50. StripeClients: stripeClients,
  51. BillingRepo: billingRepo,
  52. FileRepo: fileRepo,
  53. UserRepo: userRepo,
  54. BillingPlansPerAccount: plans,
  55. StorageBonusRepo: storageBonusRepo,
  56. DiscordController: discordController,
  57. EmailNotificationCtrl: emailNotificationController,
  58. OfferController: offerController,
  59. CommonBillCtrl: commonBillCtrl,
  60. }
  61. }
  62. // GetCheckoutSession handles the creation of stripe checkout session for subscription purchase
  63. func (c *StripeController) GetCheckoutSession(productID string, userID int64, redirectRootURL string) (string, error) {
  64. if productID == "" {
  65. return "", stacktrace.Propagate(ente.ErrBadRequest, "")
  66. }
  67. subscription, err := c.BillingRepo.GetUserSubscription(userID)
  68. if err != nil {
  69. // error sql.ErrNoRows not possible as user must at least have a free subscription
  70. return "", stacktrace.Propagate(err, "")
  71. }
  72. hasActivePaidSubscription := billing.IsActivePaidPlan(subscription)
  73. hasStripeSubscription := subscription.PaymentProvider == ente.Stripe
  74. if hasActivePaidSubscription {
  75. if hasStripeSubscription {
  76. return "", stacktrace.Propagate(ente.ErrBadRequest, "")
  77. } else if !subscription.Attributes.IsCancelled {
  78. return "", stacktrace.Propagate(ente.ErrBadRequest, "")
  79. }
  80. }
  81. if hasStripeSubscription {
  82. client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
  83. stripeSubscription, err := client.Subscriptions.Get(subscription.OriginalTransactionID, nil)
  84. if err != nil {
  85. return "", stacktrace.Propagate(err, "")
  86. }
  87. if stripeSubscription.Status != stripe.SubscriptionStatusCanceled {
  88. return "", stacktrace.Propagate(ente.ErrBadRequest, "")
  89. }
  90. }
  91. stripeSuccessURL := redirectRootURL + viper.GetString("stripe.path.success")
  92. stripeCancelURL := redirectRootURL + viper.GetString("stripe.path.cancel")
  93. allowPromotionCodes := true
  94. params := &stripe.CheckoutSessionParams{
  95. ClientReferenceID: stripe.String(strconv.FormatInt(userID, 10)),
  96. SuccessURL: stripe.String(stripeSuccessURL),
  97. CancelURL: stripe.String(stripeCancelURL),
  98. Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)),
  99. LineItems: []*stripe.CheckoutSessionLineItemParams{
  100. {
  101. Price: stripe.String(productID),
  102. Quantity: stripe.Int64(1),
  103. },
  104. },
  105. AllowPromotionCodes: &allowPromotionCodes,
  106. }
  107. var stripeClient *client.API
  108. if subscription.PaymentProvider == ente.Stripe {
  109. stripeClient = c.StripeClients[subscription.Attributes.StripeAccountCountry]
  110. // attach the subscription to existing customerID
  111. params.Customer = stripe.String(subscription.Attributes.CustomerID)
  112. } else {
  113. stripeClient = c.StripeClients[ente.DefaultStripeAccountCountry]
  114. user, err := c.UserRepo.Get(userID)
  115. if err != nil {
  116. return "", stacktrace.Propagate(err, "")
  117. }
  118. // attach user's emailID to the checkout session and subsequent subscription bought
  119. params.CustomerEmail = stripe.String(user.Email)
  120. }
  121. s, err := stripeClient.CheckoutSessions.New(params)
  122. if err != nil {
  123. return "", stacktrace.Propagate(err, "")
  124. }
  125. return s.ID, nil
  126. }
  127. // GetVerifiedSubscription verifies and returns the verified subscription
  128. func (c *StripeController) GetVerifiedSubscription(userID int64, sessionID string) (ente.Subscription, error) {
  129. var stripeSubscription stripe.Subscription
  130. var err error
  131. if sessionID != "" {
  132. log.Info("Received session ID: " + sessionID)
  133. // Get verified subscription request was received from success redirect page
  134. stripeSubscription, err = c.getStripeSubscriptionFromSession(userID, sessionID)
  135. } else {
  136. log.Info("Did not receive a session ID")
  137. // Get verified subscription request for a subscription update
  138. stripeSubscription, err = c.getUserStripeSubscription(userID)
  139. }
  140. if err != nil {
  141. return ente.Subscription{}, stacktrace.Propagate(err, "")
  142. }
  143. log.Info("Received stripe subscription with ID: " + stripeSubscription.ID)
  144. subscription, err := c.getEnteSubscriptionFromStripeSubscription(userID, stripeSubscription)
  145. if err != nil {
  146. return ente.Subscription{}, stacktrace.Propagate(err, "")
  147. }
  148. log.Info("Returning ente subscription with ID: " + strconv.FormatInt(subscription.ID, 10))
  149. return subscription, nil
  150. }
  151. func (c *StripeController) HandleUSNotification(payload []byte, header string) error {
  152. event, err := webhook.ConstructEvent(payload, header, viper.GetString("stripe.us.webhook-secret"))
  153. if err != nil {
  154. return stacktrace.Propagate(err, "")
  155. }
  156. return c.handleWebhookEvent(event, ente.StripeUS)
  157. }
  158. func (c *StripeController) HandleINNotification(payload []byte, header string) error {
  159. event, err := webhook.ConstructEvent(payload, header, viper.GetString("stripe.in.webhook-secret"))
  160. if err != nil {
  161. return stacktrace.Propagate(err, "")
  162. }
  163. return c.handleWebhookEvent(event, ente.StripeIN)
  164. }
  165. func (c *StripeController) handleWebhookEvent(event stripe.Event, country ente.StripeAccountCountry) error {
  166. // The event body would already have been logged by the upper layers by the
  167. // time we get here, so we can only handle the events that we care about. In
  168. // case we receive an unexpected event, we do log an error though.
  169. handler := c.findHandlerForEvent(event)
  170. if handler == nil {
  171. log.Error("Received an unexpected webhook from stripe:", event.Type)
  172. return nil
  173. }
  174. eventLog, err := handler(event, country)
  175. if err != nil {
  176. return stacktrace.Propagate(err, "")
  177. }
  178. if eventLog.UserID == 0 {
  179. // Do not try to log if we do not have an associated user. This can
  180. // happen, e.g. with out of order webhooks.
  181. // Or in case of offer application, where events are logged by the Storage Bonus Repo
  182. //
  183. // See: Ignore webhooks received before user has been created
  184. return nil
  185. }
  186. err = c.BillingRepo.LogStripePush(eventLog)
  187. return stacktrace.Propagate(err, "")
  188. }
  189. func (c *StripeController) findHandlerForEvent(event stripe.Event) func(event stripe.Event, country ente.StripeAccountCountry) (ente.StripeEventLog, error) {
  190. switch event.Type {
  191. case "checkout.session.completed":
  192. return c.handleCheckoutSessionCompleted
  193. case "customer.subscription.updated":
  194. return c.handleCustomerSubscriptionUpdated
  195. case "invoice.paid":
  196. return c.handleInvoicePaid
  197. case "payment_intent.payment_failed":
  198. return c.handlePaymentIntentFailed
  199. default:
  200. return nil
  201. }
  202. }
  203. // Payment is successful and the subscription is created.
  204. // You should provision the subscription.
  205. func (c *StripeController) handleCheckoutSessionCompleted(event stripe.Event, country ente.StripeAccountCountry) (ente.StripeEventLog, error) {
  206. var session stripe.CheckoutSession
  207. json.Unmarshal(event.Data.Raw, &session)
  208. if session.ClientReferenceID != "" { // via payments.ente.io, where we inserted the userID
  209. userID, _ := strconv.ParseInt(session.ClientReferenceID, 10, 64)
  210. newSubscription, err := c.GetVerifiedSubscription(userID, session.ID)
  211. if err != nil {
  212. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  213. }
  214. stripeSubscription, err := c.getStripeSubscriptionFromSession(userID, session.ID)
  215. if err != nil {
  216. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  217. }
  218. currentSubscription, err := c.BillingRepo.GetUserSubscription(userID)
  219. if err != nil {
  220. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  221. }
  222. if currentSubscription.ExpiryTime >= newSubscription.ExpiryTime &&
  223. currentSubscription.ProductID != ente.FreePlanProductID {
  224. log.Warn("Webhook is reporting an outdated purchase that was already verified stripeSubscription:", stripeSubscription.ID)
  225. return ente.StripeEventLog{UserID: userID, StripeSubscription: stripeSubscription, Event: event}, nil
  226. }
  227. err = c.BillingRepo.ReplaceSubscription(
  228. currentSubscription.ID,
  229. newSubscription,
  230. )
  231. isUpgradingFromFreePlan := currentSubscription.ProductID == ente.FreePlanProductID
  232. if isUpgradingFromFreePlan {
  233. go func() {
  234. cur := currency.MustParseISO(string(session.Currency))
  235. amount := fmt.Sprintf("%v%v", currency.Symbol(cur), float64(session.AmountTotal)/float64(100))
  236. c.DiscordController.NotifyNewSub(userID, "stripe", amount)
  237. }()
  238. go func() {
  239. c.EmailNotificationCtrl.OnAccountUpgrade(userID)
  240. }()
  241. }
  242. if err != nil {
  243. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  244. }
  245. return ente.StripeEventLog{UserID: userID, StripeSubscription: stripeSubscription, Event: event}, nil
  246. } else {
  247. priceID, err := c.getPriceIDFromSession(session.ID)
  248. if err != nil {
  249. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  250. }
  251. email := session.CustomerDetails.Email
  252. err = c.OfferController.ApplyOffer(email, priceID)
  253. if err != nil {
  254. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  255. }
  256. }
  257. return ente.StripeEventLog{}, nil
  258. }
  259. // Stripe fires this when a subscription starts or changes. For example,
  260. // renewing a subscription, adding a coupon, applying a discount, adding an
  261. // invoice item, and changing plans all trigger this event. In our case, we use
  262. // this only to track plan changes or subscriptions going past due. The rest
  263. // (subscription creations, deletions, renewals and failures) are tracked by
  264. // individual events.
  265. func (c *StripeController) handleCustomerSubscriptionUpdated(event stripe.Event, country ente.StripeAccountCountry) (ente.StripeEventLog, error) {
  266. var stripeSubscription stripe.Subscription
  267. json.Unmarshal(event.Data.Raw, &stripeSubscription)
  268. currentSubscription, err := c.BillingRepo.GetSubscriptionForTransaction(stripeSubscription.ID, ente.Stripe)
  269. if err != nil {
  270. if errors.Is(err, sql.ErrNoRows) {
  271. // See: Ignore webhooks received before user has been created
  272. log.Warn("Webhook is reporting an event for un-verified subscription stripeSubscriptionID:", stripeSubscription.ID)
  273. return ente.StripeEventLog{}, nil
  274. }
  275. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  276. }
  277. userID := currentSubscription.UserID
  278. if stripeSubscription.Status == stripe.SubscriptionStatusPastDue {
  279. user, err := c.UserRepo.Get(userID)
  280. if err != nil {
  281. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  282. }
  283. err = email.SendTemplatedEmail([]string{user.Email}, "ente", "support@ente.io",
  284. ente.AccountOnHoldEmailSubject, ente.OnHoldTemplate, map[string]interface{}{
  285. "PaymentProvider": "Stripe",
  286. }, nil)
  287. if err != nil {
  288. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  289. }
  290. }
  291. newSubscription, err := c.getEnteSubscriptionFromStripeSubscription(userID, stripeSubscription)
  292. if err != nil {
  293. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  294. }
  295. // If the customer has changed the plan, we update state in the database. If
  296. // the plan has not changed, we will ignore this webhook and rely on other
  297. // events to update the state
  298. if currentSubscription.ProductID != newSubscription.ProductID {
  299. c.BillingRepo.ReplaceSubscription(currentSubscription.ID, newSubscription)
  300. }
  301. return ente.StripeEventLog{UserID: userID, StripeSubscription: stripeSubscription, Event: event}, nil
  302. }
  303. // Continue to provision the subscription as payments continue to be made.
  304. func (c *StripeController) handleInvoicePaid(event stripe.Event, country ente.StripeAccountCountry) (ente.StripeEventLog, error) {
  305. var invoice stripe.Invoice
  306. json.Unmarshal(event.Data.Raw, &invoice)
  307. stripeSubscriptionID := invoice.Subscription.ID
  308. currentSubscription, err := c.BillingRepo.GetSubscriptionForTransaction(stripeSubscriptionID, ente.Stripe)
  309. if err != nil {
  310. if errors.Is(err, sql.ErrNoRows) {
  311. // See: Ignore webhooks received before user has been created
  312. log.Warn("Webhook is reporting an event for un-verified subscription stripeSubscriptionID:", stripeSubscriptionID)
  313. return ente.StripeEventLog{}, nil
  314. }
  315. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  316. }
  317. userID := currentSubscription.UserID
  318. client := c.StripeClients[currentSubscription.Attributes.StripeAccountCountry]
  319. stripeSubscription, err := client.Subscriptions.Get(stripeSubscriptionID, nil)
  320. if err != nil {
  321. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  322. }
  323. newExpiryTime := stripeSubscription.CurrentPeriodEnd * 1000 * 1000
  324. if currentSubscription.ExpiryTime == newExpiryTime {
  325. //outdated invoice
  326. log.Warn("Webhook is reporting an outdated purchase that was already verified stripeSubscriptionID:", stripeSubscription.ID)
  327. return ente.StripeEventLog{UserID: userID, StripeSubscription: *stripeSubscription, Event: event}, nil
  328. }
  329. err = c.BillingRepo.UpdateSubscriptionExpiryTime(
  330. currentSubscription.ID, newExpiryTime)
  331. if err != nil {
  332. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  333. }
  334. return ente.StripeEventLog{UserID: userID, StripeSubscription: *stripeSubscription, Event: event}, nil
  335. }
  336. // Event used to ONLY handle failures to SEPA payments, since we set
  337. // SubscriptionPaymentBehaviorAllowIncomplete only for SEPA. Other payment modes
  338. // will fail and will be handled synchronously
  339. func (c *StripeController) handlePaymentIntentFailed(event stripe.Event, country ente.StripeAccountCountry) (ente.StripeEventLog, error) {
  340. var paymentIntent stripe.PaymentIntent
  341. json.Unmarshal(event.Data.Raw, &paymentIntent)
  342. isSEPA := paymentIntent.LastPaymentError.PaymentMethod.Type == stripe.PaymentMethodTypeSepaDebit
  343. if !isSEPA {
  344. // Ignore events for other payment methods, since they will be handled
  345. // synchronously
  346. log.Info("Ignoring payment intent failed event for non-SEPA payment method")
  347. return ente.StripeEventLog{}, nil
  348. }
  349. client := c.StripeClients[country]
  350. invoiceID := paymentIntent.Invoice.ID
  351. invoice, err := client.Invoices.Get(invoiceID, nil)
  352. if err != nil {
  353. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  354. }
  355. stripeSubscriptionID := invoice.Subscription.ID
  356. currentSubscription, err := c.BillingRepo.GetSubscriptionForTransaction(stripeSubscriptionID, ente.Stripe)
  357. if err != nil {
  358. if errors.Is(err, sql.ErrNoRows) {
  359. // See: Ignore webhooks received before user has been created
  360. log.Warn("Webhook is reporting an event for un-verified subscription stripeSubscriptionID:", stripeSubscriptionID)
  361. }
  362. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  363. }
  364. userID := currentSubscription.UserID
  365. stripeSubscription, err := client.Subscriptions.Get(stripeSubscriptionID, nil)
  366. if err != nil {
  367. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  368. }
  369. productID := stripeSubscription.Items.Data[0].Price.ID
  370. // If the current subscription is not the same as the one in the webhook,
  371. // then ignore
  372. fmt.Printf("productID: %s, currentSubscription.ProductID: %s\n", productID, currentSubscription.ProductID)
  373. if currentSubscription.ProductID != productID {
  374. // no-op
  375. log.Warn("Webhook is reporting un-verified subscription update", stripeSubscription.ID, "invoiceID:", invoiceID)
  376. return ente.StripeEventLog{UserID: userID, StripeSubscription: *stripeSubscription, Event: event}, nil
  377. }
  378. // If the current subscription is the same as the one in the webhook, then
  379. // we need to expire the subscription, and send an email to the user.
  380. newExpiryTime := time.Now().UnixMicro()
  381. err = c.BillingRepo.UpdateSubscriptionExpiryTime(
  382. currentSubscription.ID, newExpiryTime)
  383. if err != nil {
  384. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  385. }
  386. // Send an email to the user
  387. user, err := c.UserRepo.Get(userID)
  388. if err != nil {
  389. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  390. }
  391. // TODO: Inform customer that payment_failed.html with invoice.HostedInvoiceURL
  392. err = email.SendTemplatedEmail([]string{user.Email}, "ente", "support@ente.io",
  393. ente.AccountOnHoldEmailSubject, ente.OnHoldTemplate, map[string]interface{}{
  394. "PaymentProvider": "Stripe",
  395. "InvoiceURL": invoice.HostedInvoiceURL,
  396. }, nil)
  397. if err != nil {
  398. return ente.StripeEventLog{}, stacktrace.Propagate(err, "")
  399. }
  400. return ente.StripeEventLog{UserID: userID, StripeSubscription: *stripeSubscription, Event: event}, nil
  401. }
  402. func (c *StripeController) UpdateSubscription(stripeID string, userID int64) (ente.SubscriptionUpdateResponse, error) {
  403. subscription, err := c.BillingRepo.GetUserSubscription(userID)
  404. if err != nil {
  405. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(err, "")
  406. }
  407. newPlan, newStripeAccountCountry, err := c.getPlanAndAccount(stripeID)
  408. if err != nil {
  409. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(err, "")
  410. }
  411. if subscription.PaymentProvider != ente.Stripe || subscription.ProductID == stripeID || subscription.Attributes.StripeAccountCountry != newStripeAccountCountry {
  412. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(ente.ErrBadRequest, "")
  413. }
  414. if newPlan.Storage < subscription.Storage { // Downgrade
  415. canDowngrade, canDowngradeErr := c.CommonBillCtrl.CanDowngradeToGivenStorage(newPlan.Storage, userID)
  416. if canDowngradeErr != nil {
  417. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(canDowngradeErr, "")
  418. }
  419. if !canDowngrade {
  420. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(ente.ErrCannotDowngrade, "")
  421. }
  422. log.Info("Usage is good")
  423. }
  424. client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
  425. params := stripe.SubscriptionParams{}
  426. params.AddExpand("default_payment_method")
  427. stripeSubscription, err := client.Subscriptions.Get(subscription.OriginalTransactionID, &params)
  428. if err != nil {
  429. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(err, "")
  430. }
  431. isSEPA := false
  432. if stripeSubscription.DefaultPaymentMethod != nil {
  433. isSEPA = stripeSubscription.DefaultPaymentMethod.Type == stripe.PaymentMethodTypeSepaDebit
  434. } else {
  435. log.Info("No default payment method found")
  436. }
  437. var paymentBehavior stripe.SubscriptionPaymentBehavior
  438. if isSEPA {
  439. paymentBehavior = stripe.SubscriptionPaymentBehaviorAllowIncomplete
  440. } else {
  441. paymentBehavior = stripe.SubscriptionPaymentBehaviorPendingIfIncomplete
  442. }
  443. params = stripe.SubscriptionParams{
  444. ProrationBehavior: stripe.String(string(stripe.SubscriptionProrationBehaviorAlwaysInvoice)),
  445. Items: []*stripe.SubscriptionItemsParams{
  446. {
  447. ID: stripe.String(stripeSubscription.Items.Data[0].ID),
  448. Price: stripe.String(stripeID),
  449. },
  450. },
  451. PaymentBehavior: stripe.String(string(paymentBehavior)),
  452. }
  453. params.AddExpand("latest_invoice.payment_intent")
  454. newStripeSubscription, err := client.Subscriptions.Update(subscription.OriginalTransactionID, &params)
  455. if err != nil {
  456. stripeError := err.(*stripe.Error)
  457. switch stripeError.Type {
  458. case stripe.ErrorTypeCard:
  459. return ente.SubscriptionUpdateResponse{Status: "requires_payment_method"}, nil
  460. default:
  461. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(err, "")
  462. }
  463. }
  464. if isSEPA {
  465. if newStripeSubscription.Status == stripe.SubscriptionStatusPastDue {
  466. if newStripeSubscription.LatestInvoice.PaymentIntent.Status == stripe.PaymentIntentStatusRequiresAction {
  467. return ente.SubscriptionUpdateResponse{Status: "requires_action", ClientSecret: newStripeSubscription.LatestInvoice.PaymentIntent.ClientSecret}, nil
  468. } else if newStripeSubscription.LatestInvoice.PaymentIntent.Status == stripe.PaymentIntentStatusRequiresPaymentMethod {
  469. return ente.SubscriptionUpdateResponse{Status: "requires_payment_method"}, nil
  470. } else if newStripeSubscription.LatestInvoice.PaymentIntent.Status == stripe.PaymentIntentStatusProcessing {
  471. return ente.SubscriptionUpdateResponse{Status: "success"}, nil
  472. }
  473. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(ente.ErrBadRequest, "")
  474. }
  475. } else {
  476. if newStripeSubscription.PendingUpdate != nil {
  477. switch newStripeSubscription.LatestInvoice.PaymentIntent.Status {
  478. case stripe.PaymentIntentStatusRequiresAction:
  479. return ente.SubscriptionUpdateResponse{Status: "requires_action", ClientSecret: newStripeSubscription.LatestInvoice.PaymentIntent.ClientSecret}, nil
  480. case stripe.PaymentIntentStatusRequiresPaymentMethod:
  481. inv := newStripeSubscription.LatestInvoice
  482. client.Invoices.VoidInvoice(inv.ID, nil)
  483. return ente.SubscriptionUpdateResponse{Status: "requires_payment_method"}, nil
  484. }
  485. return ente.SubscriptionUpdateResponse{}, stacktrace.Propagate(ente.ErrBadRequest, "")
  486. }
  487. }
  488. return ente.SubscriptionUpdateResponse{Status: "success"}, nil
  489. }
  490. func (c *StripeController) UpdateSubscriptionCancellationStatus(userID int64, status bool) (ente.Subscription, error) {
  491. subscription, err := c.BillingRepo.GetUserSubscription(userID)
  492. if err != nil {
  493. // error sql.ErrNoRows not possible as user must at least have a free subscription
  494. return ente.Subscription{}, stacktrace.Propagate(err, "")
  495. }
  496. if subscription.PaymentProvider != ente.Stripe {
  497. return ente.Subscription{}, stacktrace.Propagate(ente.ErrBadRequest, "")
  498. }
  499. if subscription.Attributes.IsCancelled == status {
  500. // no-op
  501. return subscription, nil
  502. }
  503. client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
  504. params := &stripe.SubscriptionParams{
  505. CancelAtPeriodEnd: stripe.Bool(status),
  506. }
  507. _, err = client.Subscriptions.Update(subscription.OriginalTransactionID, params)
  508. if err != nil {
  509. return ente.Subscription{}, stacktrace.Propagate(err, "")
  510. }
  511. err = c.BillingRepo.UpdateSubscriptionCancellationStatus(userID, status)
  512. if err != nil {
  513. return ente.Subscription{}, stacktrace.Propagate(err, "")
  514. }
  515. subscription.Attributes.IsCancelled = status
  516. return subscription, nil
  517. }
  518. func (c *StripeController) GetStripeCustomerPortal(userID int64, redirectRootURL string) (string, error) {
  519. subscription, err := c.BillingRepo.GetUserSubscription(userID)
  520. if err != nil {
  521. return "", stacktrace.Propagate(err, "")
  522. }
  523. if subscription.PaymentProvider != ente.Stripe {
  524. return "", stacktrace.Propagate(ente.ErrBadRequest, "")
  525. }
  526. client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
  527. params := &stripe.BillingPortalSessionParams{
  528. Customer: stripe.String(subscription.Attributes.CustomerID),
  529. ReturnURL: stripe.String(redirectRootURL),
  530. }
  531. ps, err := client.BillingPortalSessions.New(params)
  532. if err != nil {
  533. return "", stacktrace.Propagate(err, "")
  534. }
  535. return ps.URL, nil
  536. }
  537. func (c *StripeController) getStripeSubscriptionFromSession(userID int64, checkoutSessionID string) (stripe.Subscription, error) {
  538. subscription, err := c.BillingRepo.GetUserSubscription(userID)
  539. if err != nil {
  540. return stripe.Subscription{}, stacktrace.Propagate(err, "")
  541. }
  542. var stripeClient *client.API
  543. if subscription.PaymentProvider == ente.Stripe {
  544. stripeClient = c.StripeClients[subscription.Attributes.StripeAccountCountry]
  545. } else {
  546. stripeClient = c.StripeClients[ente.DefaultStripeAccountCountry]
  547. }
  548. params := &stripe.CheckoutSessionParams{}
  549. params.AddExpand("subscription")
  550. checkoutSession, err := stripeClient.CheckoutSessions.Get(checkoutSessionID, params)
  551. if err != nil {
  552. return stripe.Subscription{}, stacktrace.Propagate(err, "")
  553. }
  554. if (*checkoutSession.Subscription).Status != stripe.SubscriptionStatusActive {
  555. return stripe.Subscription{}, stacktrace.Propagate(&stripe.InvalidRequestError{}, "")
  556. }
  557. return *checkoutSession.Subscription, nil
  558. }
  559. func (c *StripeController) getPriceIDFromSession(sessionID string) (string, error) {
  560. stripeClient := c.StripeClients[ente.DefaultStripeAccountCountry]
  561. params := &stripe.CheckoutSessionListLineItemsParams{}
  562. params.AddExpand("data.price")
  563. items := stripeClient.CheckoutSessions.ListLineItems(sessionID, params)
  564. for items.Next() { // Return the first PriceID that has been fetched
  565. return items.LineItem().Price.ID, nil
  566. }
  567. return "", stacktrace.Propagate(ente.ErrNotFound, "")
  568. }
  569. func (c *StripeController) getUserStripeSubscription(userID int64) (stripe.Subscription, error) {
  570. subscription, err := c.BillingRepo.GetUserSubscription(userID)
  571. if err != nil {
  572. return stripe.Subscription{}, stacktrace.Propagate(err, "")
  573. }
  574. if subscription.PaymentProvider != ente.Stripe {
  575. return stripe.Subscription{}, stacktrace.Propagate(ente.ErrCannotSwitchPaymentProvider, "")
  576. }
  577. client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
  578. stripeSubscription, err := client.Subscriptions.Get(subscription.OriginalTransactionID, nil)
  579. if err != nil {
  580. return stripe.Subscription{}, stacktrace.Propagate(err, "")
  581. }
  582. return *stripeSubscription, nil
  583. }
  584. func (c *StripeController) getPlanAndAccount(stripeID string) (ente.BillingPlan, ente.StripeAccountCountry, error) {
  585. for stripeAccountCountry, billingPlansCountryWise := range c.BillingPlansPerAccount {
  586. for _, plans := range billingPlansCountryWise {
  587. for _, plan := range plans {
  588. if plan.StripeID == stripeID {
  589. return plan, stripeAccountCountry, nil
  590. }
  591. }
  592. }
  593. }
  594. return ente.BillingPlan{}, "", stacktrace.Propagate(ente.ErrNotFound, "")
  595. }
  596. func (c *StripeController) getEnteSubscriptionFromStripeSubscription(userID int64, stripeSubscription stripe.Subscription) (ente.Subscription, error) {
  597. productID := stripeSubscription.Items.Data[0].Price.ID
  598. plan, stripeAccountCountry, err := c.getPlanAndAccount(productID)
  599. if err != nil {
  600. return ente.Subscription{}, stacktrace.Propagate(err, "")
  601. }
  602. s := ente.Subscription{
  603. UserID: userID,
  604. PaymentProvider: ente.Stripe,
  605. ProductID: productID,
  606. Storage: plan.Storage,
  607. Attributes: ente.SubscriptionAttributes{CustomerID: stripeSubscription.Customer.ID, IsCancelled: false, StripeAccountCountry: stripeAccountCountry},
  608. OriginalTransactionID: stripeSubscription.ID,
  609. ExpiryTime: stripeSubscription.CurrentPeriodEnd * 1000 * 1000,
  610. }
  611. return s, nil
  612. }
  613. func (c *StripeController) UpdateBillingEmail(subscription ente.Subscription, newEmail string) error {
  614. params := &stripe.CustomerParams{Email: &newEmail}
  615. client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
  616. _, err := client.Customers.Update(
  617. subscription.Attributes.CustomerID,
  618. params,
  619. )
  620. if err != nil {
  621. return stacktrace.Propagate(err, "failed to update stripe customer emailID")
  622. }
  623. return nil
  624. }
  625. func (c *StripeController) CancelSubAndDeleteCustomer(subscription ente.Subscription, logger *log.Entry) error {
  626. client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
  627. if !subscription.Attributes.IsCancelled {
  628. prorateRefund := true
  629. logger.Info("cancelling sub with prorated refund")
  630. updateParams := &stripe.SubscriptionParams{}
  631. updateParams.AddMetadata(SkipMailKey, "true")
  632. _, err := client.Subscriptions.Update(subscription.OriginalTransactionID, updateParams)
  633. if err != nil {
  634. stripeError := err.(*stripe.Error)
  635. errorMsg := fmt.Sprintf("subscription updation failed during account deletion: %s, %s", stripeError.Msg, stripeError.Code)
  636. log.Error(errorMsg)
  637. c.DiscordController.Notify(errorMsg)
  638. if stripeError.HTTPStatusCode == http.StatusNotFound {
  639. log.Error("Ignoring error since an active subscription could not be found")
  640. return nil
  641. } else if stripeError.HTTPStatusCode == http.StatusBadRequest {
  642. log.Error("Bad request while trying to delete account")
  643. return nil
  644. }
  645. return stacktrace.Propagate(err, "")
  646. }
  647. _, err = client.Subscriptions.Cancel(subscription.OriginalTransactionID, &stripe.SubscriptionCancelParams{
  648. Prorate: &prorateRefund,
  649. })
  650. if err != nil {
  651. stripeError := err.(*stripe.Error)
  652. logger.Error(fmt.Sprintf("subscription cancel failed msg= %s for userID=%d"+stripeError.Msg, subscription.UserID))
  653. // ignore if subscription doesn't exist, already deleted
  654. if stripeError.HTTPStatusCode != 404 {
  655. return stacktrace.Propagate(err, "")
  656. }
  657. }
  658. err = c.BillingRepo.UpdateSubscriptionCancellationStatus(subscription.UserID, true)
  659. if err != nil {
  660. return stacktrace.Propagate(err, "")
  661. }
  662. }
  663. logger.Info("deleting customer from stripe")
  664. _, err := client.Customers.Del(
  665. subscription.Attributes.CustomerID,
  666. &stripe.CustomerParams{},
  667. )
  668. if err != nil {
  669. stripeError := err.(*stripe.Error)
  670. switch stripeError.Type {
  671. case stripe.ErrorTypeInvalidRequest:
  672. if stripe.ErrorCodeResourceMissing == stripeError.Code {
  673. return nil
  674. }
  675. return stacktrace.Propagate(err, fmt.Sprintf("failed to delete customer %s", subscription.Attributes.CustomerID))
  676. default:
  677. return stacktrace.Propagate(err, fmt.Sprintf("failed to delete customer %s", subscription.Attributes.CustomerID))
  678. }
  679. }
  680. return nil
  681. }