Request feedback on subscription cancellation (#4590)

This commit is contained in:
Neeraj Gupta
2025-01-06 11:31:23 +05:30
committed by GitHub
9 changed files with 205 additions and 20 deletions

View File

@@ -203,7 +203,7 @@ func main() {
plans := billing.GetPlans()
defaultPlan := billing.GetDefaultPlans(plans)
stripeClients := billing.GetStripeClients()
commonBillController := commonbilling.NewController(storagBonusRepo, userRepo, usageRepo)
commonBillController := commonbilling.NewController(emailNotificationCtrl, storagBonusRepo, userRepo, usageRepo, billingRepo)
appStoreController := controller.NewAppStoreController(defaultPlan,
billingRepo, fileRepo, userRepo, commonBillController)
remoteStoreController := &remoteStoreCtrl.Controller{Repo: remoteStoreRepository}

View File

@@ -101,7 +101,7 @@
font-size: 16px; " >
<p>Hey,</p>
<p>As requested by you, we've deleted your ente account and scheduled your
<p>As requested by you, we've deleted your Ente account and scheduled your
uploaded data for deletion. If you have an App Store subscription for
Ente, please remember to cancel it too.</p>

View File

@@ -0,0 +1,147 @@
<!DOCTYPE html>
<html>
<meta content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1,
minimum-scale=1" />
<style>
body {
background-color: #f0f1f3;
font-family: "Helvetica Neue", "Segoe UI", Helvetica, sans-serif;
font-size: 16px;
line-height: 27px;
margin: 0;
color: #444;
}
pre {
background: #f4f4f4f4;
padding: 2px;
}
table {
width: 100%;
border: 1px solid #ddd;
}
table td {
border-color: #ddd;
padding: 5px;
}
.wrap {
background-color: #fff;
padding: 30px;
max-width: 525px;
margin: 0 auto;
border-radius: 5px;
}
.button {
background: #0055d4;
border-radius: 3px;
text-decoration: none !important;
color: #fff !important;
font-weight: bold;
padding: 10px 30px;
display: inline-block;
}
.button:hover {
background: #111;
}
.footer {
text-align: center;
font-size: 12px;
color: #888;
}
.footer a {
color: #888;
margin-right: 5px;
}
.gutter {
padding: 30px;
}
img {
max-width: 100%;
height: auto;
}
a {
color: #0055d4;
}
a:hover {
color: #111;
}
@media screen and (max-width: 600px) {
.wrap {
max-width: auto;
}
.gutter {
padding: 10px;
}
}
.footer-icons {
padding: 4px !important;
width: 24px !important;
}
</style>
<body>
<div class="gutter" style="padding: 4px">&nbsp;</div>
<div class="wrap" style=" background-color: rgb(255, 255, 255); padding: 2px
30px 30px 30px; max-width: 525px; margin: 0 auto; border-radius: 5px;
font-size: 16px; " >
<p>Hi there,</p>
<p>Vishnu here, CEO of Ente.</p>
<p>Thank you for giving Ente a shot, and apologies for not meeting your expectations.</p>
<p>This is my personal email address. Please let me know what we could have done better, we'd be grateful for feedback!</p>
<p>Best,
<br/>
Vishnu
<br/>
Founder & CEO at <a href="https://ente.io">Ente</a>
</p>
</div>
<br />
<div class="footer" style="text-align: center; font-size: 12px; color:
rgb(136, 136, 136)" >
<div>
<a href="https://ente.io" target="_blank" ><img
src="https://email-assets.ente.io/ente-green.png" style="width: 100px;
padding: 24px" title="Ente" alt="Ente" /></a>
</div>
<div>
<a href="https://fosstodon.org/@ente" target="_blank" ><img
src="https://email-assets.ente.io/mastodon-icon.png"
class="footer-icons" style="width: 24px; padding: 4px" title="Mastodon"
alt="Mastodon" /></a>
<a href="https://twitter.com/enteio" target="_blank" ><img
src="https://email-assets.ente.io/twitter-icon.png" class="footer-icons"
style="width: 24px; padding: 4px" title="Twitter" alt="Twitter" /></a>
<a href="https://discord.ente.io" target="_blank" ><img
src="https://email-assets.ente.io/discord-icon.png" class="footer-icons"
style="width: 24px; padding: 4px" title="Discord" alt="Discord" /></a>
<a href="https://github.com/ente-io" target="_blank" ><img
src="https://email-assets.ente.io/github-icon.png" class="footer-icons"
style="width: 24px; padding: 4px" title="GitHub" alt="GitHub" /></a>
</div>
<p>
Ente Technologies, Inc.
<br /> 1111B S Governors Ave 6032 Dover, DE 19904
</p>
<br />
</div>
</body>
</html>

View File

@@ -113,7 +113,7 @@
<p>Your subscription to Ente Photos has ended. Thank you for trying us out!</p>
<p>
If you still have data stored in ente, we encourage you to follow the steps outlined here to export your data: <a href="https://help.ente.io/photos/migration/export" target="_blank">help.ente.io/photos/migration/export</a>.
If you still have data stored in Ente, we encourage you to follow the steps outlined here to export your data: <a href="https://help.ente.io/photos/migration/export" target="_blank">help.ente.io/photos/migration/export</a>.
</p>
<p>If there's anything we could have done better, please let us know by

View File

@@ -3,11 +3,12 @@ package controller
import (
"context"
"fmt"
"github.com/ente-io/museum/pkg/controller/commonbilling"
"github.com/prometheus/common/log"
"strconv"
"strings"
"github.com/ente-io/museum/pkg/controller/commonbilling"
"github.com/prometheus/common/log"
"github.com/ente-io/stacktrace"
"github.com/gin-contrib/requestid"
"github.com/gin-gonic/gin"
@@ -124,7 +125,7 @@ func (c *AppStoreController) HandleNotification(ctx *gin.Context, notification a
return stacktrace.Propagate(err, "")
}
} else if notification.NotificationType == appstore.NotificationTypeCancel || notification.NotificationType == appstore.NotificationTypeDidRevoke {
err := c.BillingRepo.UpdateSubscriptionCancellationStatus(subscription.UserID, true)
err := c.CommonBillCtrl.OnSubscriptionCancelled(subscription.UserID)
if err != nil {
return stacktrace.Propagate(err, "")
}

View File

@@ -2,6 +2,8 @@ package commonbilling
import (
"context"
"github.com/ente-io/museum/pkg/controller/email"
"github.com/ente-io/museum/pkg/repo"
"github.com/ente-io/museum/pkg/repo/storagebonus"
"github.com/ente-io/stacktrace"
@@ -9,20 +11,26 @@ import (
)
type Controller struct {
StorageBonusRepo *storagebonus.Repository
UserRepo *repo.UserRepository
UsageRepo *repo.UsageRepository
EmailNotificationController *email.EmailNotificationController
StorageBonusRepo *storagebonus.Repository
UserRepo *repo.UserRepository
UsageRepo *repo.UsageRepository
BillingRepo *repo.BillingRepository
}
func NewController(
emailNotificationController *email.EmailNotificationController,
storageBonusRepo *storagebonus.Repository,
userRepo *repo.UserRepository,
usageRepo *repo.UsageRepository,
billingRepo *repo.BillingRepository,
) *Controller {
return &Controller{
StorageBonusRepo: storageBonusRepo,
UserRepo: userRepo,
UsageRepo: usageRepo,
EmailNotificationController: emailNotificationController,
StorageBonusRepo: storageBonusRepo,
UserRepo: userRepo,
UsageRepo: usageRepo,
BillingRepo: billingRepo,
}
}
@@ -63,3 +71,12 @@ func (c *Controller) CanDowngradeToGivenStorage(newStorage int64, userID int64)
}
return true, nil
}
func (c *Controller) OnSubscriptionCancelled(userID int64) error {
err := c.BillingRepo.UpdateSubscriptionCancellationStatus(userID, true)
if err != nil {
return stacktrace.Propagate(err, "")
}
go c.EmailNotificationController.OnSubscriptionCancelled(userID)
return nil
}

View File

@@ -24,6 +24,8 @@ const (
FilesCollectedSubject = "You've got photos!"
SubscriptionUpgradedTemplate = "subscription_upgraded.html"
SubscriptionUpgradedSubject = "Thank you for choosing Ente!"
SubscriptionCancelledSubject = "Good bye (?) from Ente"
SubscriptionCancelledTemplate = "subscription_cancelled.html"
FilesCollectedMuteDurationInMinutes = 10
StorageLimitExceededSubject = "[Alert] You have exceeded your storage limit"
ReferralSuccessfulTemplate = "successful_referral.html"
@@ -113,6 +115,19 @@ func (c *EmailNotificationController) OnAccountUpgrade(userID int64) {
}
}
func (c *EmailNotificationController) OnSubscriptionCancelled(userID int64) {
user, err := c.UserRepo.Get(userID)
if err != nil {
log.Error("Could not find user to email", err)
return
}
log.Info(fmt.Sprintf("Emailing on subscription cancellation %d", user.ID))
err = email.SendTemplatedEmail([]string{user.Email}, "vishnu@ente.io", "vishnu@ente.io", SubscriptionCancelledSubject, SubscriptionCancelledTemplate, nil, nil)
if err != nil {
log.Error("Error sending email", err)
}
}
func (c *EmailNotificationController) SendStorageLimitExceededMails() {
if c.isSendingStorageLimitExceededMails {
log.Info("Skipping sending storage limit exceeded mails as another instance is still running")

View File

@@ -3,9 +3,10 @@ package controller
import (
"context"
"errors"
"os"
"github.com/ente-io/museum/pkg/controller/commonbilling"
"github.com/ente-io/museum/pkg/repo/storagebonus"
"os"
"github.com/ente-io/stacktrace"
@@ -144,7 +145,7 @@ func (c *PlayStoreController) HandleNotification(notification playstore.Develope
return stacktrace.Propagate(err, "")
}
case playstore.SubscriptionNotificationTypeCanceled:
err := c.BillingRepo.UpdateSubscriptionCancellationStatus(subscription.UserID, true)
err := c.CommonBillCtrl.OnSubscriptionCancelled(subscription.UserID)
if err != nil {
return stacktrace.Propagate(err, "")
}

View File

@@ -503,7 +503,7 @@ func (c *StripeController) UpdateSubscription(stripeID string, userID int64) (en
return ente.SubscriptionUpdateResponse{Status: "success"}, nil
}
func (c *StripeController) UpdateSubscriptionCancellationStatus(userID int64, status bool) (ente.Subscription, error) {
func (c *StripeController) UpdateSubscriptionCancellationStatus(userID int64, shouldCancel bool) (ente.Subscription, error) {
subscription, err := c.BillingRepo.GetUserSubscription(userID)
if err != nil {
// error sql.ErrNoRows not possible as user must at least have a free subscription
@@ -513,24 +513,28 @@ func (c *StripeController) UpdateSubscriptionCancellationStatus(userID int64, st
return ente.Subscription{}, stacktrace.Propagate(ente.ErrBadRequest, "")
}
if subscription.Attributes.IsCancelled == status {
if subscription.Attributes.IsCancelled == shouldCancel {
// no-op
return subscription, nil
}
client := c.StripeClients[subscription.Attributes.StripeAccountCountry]
params := &stripe.SubscriptionParams{
CancelAtPeriodEnd: stripe.Bool(status),
CancelAtPeriodEnd: stripe.Bool(shouldCancel),
}
_, err = client.Subscriptions.Update(subscription.OriginalTransactionID, params)
if err != nil {
return ente.Subscription{}, stacktrace.Propagate(err, "")
}
err = c.BillingRepo.UpdateSubscriptionCancellationStatus(userID, status)
if shouldCancel {
err = c.CommonBillCtrl.OnSubscriptionCancelled(userID)
} else {
err = c.BillingRepo.UpdateSubscriptionCancellationStatus(userID, false)
}
if err != nil {
return ente.Subscription{}, stacktrace.Propagate(err, "")
}
subscription.Attributes.IsCancelled = status
subscription.Attributes.IsCancelled = shouldCancel
return subscription, nil
}
@@ -703,7 +707,7 @@ func (c *StripeController) CancelSubAndDeleteCustomer(subscription ente.Subscrip
return stacktrace.Propagate(err, "")
}
}
err = c.BillingRepo.UpdateSubscriptionCancellationStatus(subscription.UserID, true)
err = c.CommonBillCtrl.OnSubscriptionCancelled(subscription.UserID)
if err != nil {
return stacktrace.Propagate(err, "")
}