stripe.go 34 KB

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