stripe.go 32 KB

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