Request feedback on subscription cancellation (#4590)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
147
server/mail-templates/subscription_cancelled.html
Normal file
147
server/mail-templates/subscription_cancelled.html
Normal 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"> </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>
|
||||
@@ -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
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user