Skip to content

Commit ce442d1

Browse files
committed
Add code to update the user entity on subscription update
1 parent b6841ed commit ce442d1

7 files changed

Lines changed: 130 additions & 2 deletions

File tree

api/pkg/entities/user.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,21 @@ func (user User) IsOnProPlan() bool {
8989
return user.SubscriptionName == SubscriptionNameProLifetime || user.SubscriptionName == SubscriptionNameProMonthly || user.SubscriptionName == SubscriptionNameProYearly
9090
}
9191

92+
// IsOnFreePlan checks if a user is on the free plan
93+
func (user User) IsOnFreePlan() bool {
94+
return user.SubscriptionName == SubscriptionNameFree || user.SubscriptionName == ""
95+
}
96+
9297
// IsOnUltraPlan checks if a user is on the ultra plan
9398
func (user User) IsOnUltraPlan() bool {
9499
return user.SubscriptionName == SubscriptionNameUltraMonthly || user.SubscriptionName == SubscriptionNameUltraYearly
95100
}
96101

102+
// IsOn20kPlan checks if a user is on the 20k plan
103+
func (user User) IsOn20kPlan() bool {
104+
return user.SubscriptionName == SubscriptionName20KMonthly || user.SubscriptionName == SubscriptionName20KYearly
105+
}
106+
97107
// UserTimeString converts the time to the user's timezone
98108
func (user User) UserTimeString(timestamp time.Time) string {
99109
location, err := time.LoadLocation(user.Timezone)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package events
2+
3+
import (
4+
"time"
5+
6+
"github.com/NdoleStudio/httpsms/pkg/entities"
7+
)
8+
9+
// UserSubscriptionUpdated is raised when a user subscription is updated
10+
const UserSubscriptionUpdated = "user.subscription.updated"
11+
12+
// UserSubscriptionUpdatedPayload stores the data for the UserSubscriptionUpdated event
13+
type UserSubscriptionUpdatedPayload struct {
14+
UserID entities.UserID `json:"user_id"`
15+
SubscriptionUpdatedAt time.Time `json:"subscription_updated_at"`
16+
SubscriptionEndsAt time.Time `json:"subscription_ends_at"`
17+
SubscriptionRenewsAt time.Time `json:"subscription_renews_at"`
18+
SubscriptionID string `json:"subscription_id"`
19+
SubscriptionName entities.SubscriptionName `json:"subscription_name"`
20+
SubscriptionStatus string `json:"subscription_status"`
21+
}

api/pkg/handlers/lemonsqueezy_handler.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ func (h *LemonsqueezyHandler) handleRequest(ctx context.Context, c *fiber.Ctx) e
100100
return stacktrace.Propagate(err, fmt.Sprintf("cannot marshall [%s] to [%T]", c.Body(), request))
101101
}
102102
return h.service.HandleSubscriptionExpiredEvent(ctx, c.OriginalURL(), &request)
103+
case "subscription_updated":
104+
var request lemonsqueezy.WebhookRequestSubscription
105+
err := json.Unmarshal(c.Body(), &request)
106+
if err != nil {
107+
return stacktrace.Propagate(err, fmt.Sprintf("cannot marshall [%s] to [%T]", c.Body(), request))
108+
}
109+
return h.service.HandleSubscriptionUpdatedEvent(ctx, c.OriginalURL(), &request)
103110
default:
104111
return stacktrace.NewError(fmt.Sprintf("invalid event [%s] received with request [%s]", eventName, c.Body()))
105112
}

api/pkg/listeners/user_listener.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func NewUserListener(
3636
events.EventTypePhoneHeartbeatDead: l.onPhoneHeartbeatDead,
3737
events.UserSubscriptionCreated: l.OnUserSubscriptionCreated,
3838
events.UserSubscriptionCancelled: l.OnUserSubscriptionCancelled,
39+
events.UserSubscriptionUpdated: l.OnUserSubscriptionUpdated,
3940
events.UserSubscriptionExpired: l.OnUserSubscriptionExpired,
4041
}
4142
}
@@ -122,3 +123,22 @@ func (listener *UserListener) OnUserSubscriptionExpired(ctx context.Context, eve
122123

123124
return nil
124125
}
126+
127+
// OnUserSubscriptionUpdated handles the events.UserSubscriptionUpdated event
128+
func (listener *UserListener) OnUserSubscriptionUpdated(ctx context.Context, event cloudevents.Event) error {
129+
ctx, span := listener.tracer.Start(ctx)
130+
defer span.End()
131+
132+
var payload events.UserSubscriptionUpdatedPayload
133+
if err := event.DataAs(&payload); err != nil {
134+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
135+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
136+
}
137+
138+
if err := listener.service.UpdateSubscription(ctx, &payload); err != nil {
139+
msg := fmt.Sprintf("cannot expire subscription for user with ID [%s] for event with ID [%s]", payload.UserID, event.ID())
140+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
141+
}
142+
143+
return nil
144+
}

api/pkg/services/billing_service.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func (service *BillingService) sendUsageAlert(ctx context.Context, userID entiti
207207
}
208208

209209
func (service *BillingService) shouldSendAlert(user *entities.User, usage *entities.BillingUsage) bool {
210-
if !user.IsOnProPlan() && (usage.TotalMessages() == 160 || usage.TotalMessages() == 180 || usage.TotalMessages() == 190) {
210+
if user.IsOnFreePlan() && (usage.TotalMessages() == 160 || usage.TotalMessages() == 180 || usage.TotalMessages() == 190) {
211211
return true
212212
}
213213

@@ -219,5 +219,9 @@ func (service *BillingService) shouldSendAlert(user *entities.User, usage *entit
219219
return true
220220
}
221221

222+
if user.IsOn20kPlan() && (usage.TotalMessages() == 16000 || usage.TotalMessages() == 18000 || usage.TotalMessages() == 19000) {
223+
return true
224+
}
225+
222226
return false
223227
}

api/pkg/services/lemonsqueezy_service.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,41 @@ func (service *LemonsqueezyService) HandleSubscriptionCanceledEvent(ctx context.
101101
return nil
102102
}
103103

104+
// HandleSubscriptionUpdatedEvent handles the subscription_cancelled lemonsqueezy event
105+
func (service *LemonsqueezyService) HandleSubscriptionUpdatedEvent(ctx context.Context, source string, request *lemonsqueezy.WebhookRequestSubscription) error {
106+
ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
107+
defer span.End()
108+
109+
user, err := service.userRepository.LoadBySubscriptionID(ctx, request.Data.ID)
110+
if err != nil {
111+
msg := fmt.Sprintf("cannot load user with subscription ID [%s]", request.Data.ID)
112+
return stacktrace.Propagate(err, msg)
113+
}
114+
115+
payload := &events.UserSubscriptionUpdatedPayload{
116+
UserID: user.ID,
117+
SubscriptionUpdatedAt: request.Data.Attributes.UpdatedAt,
118+
SubscriptionID: request.Data.ID,
119+
SubscriptionName: service.subscriptionName(request.Data.Attributes.VariantName),
120+
SubscriptionEndsAt: *request.Data.Attributes.EndsAt,
121+
SubscriptionRenewsAt: request.Data.Attributes.RenewsAt,
122+
SubscriptionStatus: request.Data.Attributes.Status,
123+
}
124+
125+
event, err := service.createEvent(events.UserSubscriptionUpdated, source, payload)
126+
if err != nil {
127+
msg := fmt.Sprintf("cannot created [%s] event for user [%s]", events.UserSubscriptionUpdated, payload.UserID)
128+
return stacktrace.Propagate(err, msg)
129+
}
130+
131+
if err = service.eventDispatcher.Dispatch(ctx, event); err != nil {
132+
msg := fmt.Sprintf("cannot dispatch [%s] event for user [%s]", event.Type(), payload.UserID)
133+
return stacktrace.Propagate(err, msg)
134+
}
135+
ctxLogger.Info(fmt.Sprintf("[%s] subscription [%s] updated for user [%s]", payload.SubscriptionName, payload.SubscriptionID, payload.UserID))
136+
return nil
137+
}
138+
104139
// HandleSubscriptionExpiredEvent handles the subscription_expired lemonsqueezy event
105140
func (service *LemonsqueezyService) HandleSubscriptionExpiredEvent(ctx context.Context, source string, request *lemonsqueezy.WebhookRequestSubscription) error {
106141
ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)

api/pkg/services/user_service.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func (service *UserService) CancelSubscription(ctx context.Context, params *even
280280
return nil
281281
}
282282

283-
// ExpireSubscription starts a subscription for an entities.User
283+
// ExpireSubscription finishes a subscription for an entities.User
284284
func (service *UserService) ExpireSubscription(ctx context.Context, params *events.UserSubscriptionExpiredPayload) error {
285285
ctx, span := service.tracer.Start(ctx)
286286
defer span.End()
@@ -304,3 +304,34 @@ func (service *UserService) ExpireSubscription(ctx context.Context, params *even
304304

305305
return nil
306306
}
307+
308+
// UpdateSubscription updates a subscription for an entities.User
309+
func (service *UserService) UpdateSubscription(ctx context.Context, params *events.UserSubscriptionUpdatedPayload) error {
310+
ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
311+
defer span.End()
312+
313+
user, err := service.repository.Load(ctx, params.UserID)
314+
if err != nil {
315+
msg := fmt.Sprintf("could not get [%T] with with ID [%s]", user, params.UserID)
316+
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
317+
}
318+
319+
if params.SubscriptionStatus != "active" {
320+
msg := fmt.Sprintf("subscription status is [%s] for [%T] with with ID [%s]", params.SubscriptionStatus, user, params.UserID)
321+
ctxLogger.Info(msg)
322+
return nil
323+
}
324+
325+
user.SubscriptionID = &params.SubscriptionID
326+
user.SubscriptionName = params.SubscriptionName
327+
user.SubscriptionEndsAt = &params.SubscriptionEndsAt
328+
user.SubscriptionRenewsAt = &params.SubscriptionRenewsAt
329+
user.SubscriptionStatus = &params.SubscriptionStatus
330+
331+
if err = service.repository.Update(ctx, user); err != nil {
332+
msg := fmt.Sprintf("could not update [%T] with with ID [%s] after subscription update", user, params.UserID)
333+
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
334+
}
335+
336+
return nil
337+
}

0 commit comments

Comments
 (0)