Skip to content

Commit 48071b3

Browse files
committed
Add ability to expire user subscriptions
1 parent b11bfdc commit 48071b3

4 files changed

Lines changed: 102 additions & 0 deletions

File tree

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+
// UserSubscriptionExpired is raised when a user subscription is cancelled
10+
const UserSubscriptionExpired = "user.subscription.expired"
11+
12+
// UserSubscriptionExpiredPayload stores the data for the UserSubscriptionExpired event
13+
type UserSubscriptionExpiredPayload struct {
14+
UserID entities.UserID `json:"user_id"`
15+
SubscriptionExpiredAt time.Time `json:"subscription_expired_at"`
16+
SubscriptionEndsAt time.Time `json:"subscription_ends_at"`
17+
IsCancelled bool `json:"is_cancelled"`
18+
SubscriptionID string `json:"subscription_id"`
19+
SubscriptionName entities.SubscriptionName `json:"subscription_name"`
20+
SubscriptionStatus string `json:"subscription_status"`
21+
}

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.UserSubscriptionExpired: l.OnUserSubscriptionExpired,
3940
}
4041
}
4142

@@ -102,3 +103,22 @@ func (listener *UserListener) OnUserSubscriptionCancelled(ctx context.Context, e
102103

103104
return nil
104105
}
106+
107+
// OnUserSubscriptionExpired handles the events.UserSubscriptionExpired event
108+
func (listener *UserListener) OnUserSubscriptionExpired(ctx context.Context, event cloudevents.Event) error {
109+
ctx, span := listener.tracer.Start(ctx)
110+
defer span.End()
111+
112+
var payload events.UserSubscriptionExpiredPayload
113+
if err := event.DataAs(&payload); err != nil {
114+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
115+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
116+
}
117+
118+
if err := listener.service.ExpireSubscription(ctx, &payload); err != nil {
119+
msg := fmt.Sprintf("cannot expire subscription for user with ID [%s] for event with ID [%s]", payload.UserID, event.ID())
120+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
121+
}
122+
123+
return nil
124+
}

api/pkg/services/lemonsqueezy_service.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"strings"
7+
"time"
78

89
"github.com/NdoleStudio/httpsms/pkg/repositories"
910

@@ -100,6 +101,41 @@ func (service *LemonsqueezyService) HandleSubscriptionCanceledEvent(ctx context.
100101
return nil
101102
}
102103

104+
// HandleSubscriptionExpiredEvent handles the subscription_expired lemonsqueezy event
105+
func (service *LemonsqueezyService) HandleSubscriptionExpiredEvent(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.UserSubscriptionExpiredPayload{
116+
UserID: user.ID,
117+
SubscriptionExpiredAt: time.Now().UTC(),
118+
SubscriptionID: request.Data.ID,
119+
IsCancelled: request.Data.Attributes.Cancelled,
120+
SubscriptionName: service.subscriptionName(request.Data.Attributes.VariantName),
121+
SubscriptionEndsAt: *request.Data.Attributes.EndsAt,
122+
SubscriptionStatus: request.Data.Attributes.Status,
123+
}
124+
125+
event, err := service.createEvent(events.UserSubscriptionExpired, source, payload)
126+
if err != nil {
127+
msg := fmt.Sprintf("cannot created [%s] event for user [%s]", events.UserSubscriptionExpired, 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] expired for user [%s]", payload.SubscriptionName, payload.SubscriptionID, payload.UserID))
136+
return nil
137+
}
138+
103139
func (service *LemonsqueezyService) subscriptionName(variant string) entities.SubscriptionName {
104140
if strings.Contains(strings.ToLower(variant), "pro") {
105141
if strings.Contains(strings.ToLower(variant), "monthly") {

api/pkg/services/user_service.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,28 @@ func (service *UserService) CancelSubscription(ctx context.Context, params *even
229229

230230
return nil
231231
}
232+
233+
// ExpireSubscription starts a subscription for an entities.User
234+
func (service *UserService) ExpireSubscription(ctx context.Context, params *events.UserSubscriptionExpiredPayload) error {
235+
ctx, span := service.tracer.Start(ctx)
236+
defer span.End()
237+
238+
user, err := service.repository.Load(ctx, params.UserID)
239+
if err != nil {
240+
msg := fmt.Sprintf("could not get [%T] with with ID [%s]", user, params.UserID)
241+
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
242+
}
243+
244+
user.SubscriptionID = nil
245+
user.SubscriptionName = entities.SubscriptionNameFree
246+
user.SubscriptionRenewsAt = nil
247+
user.SubscriptionStatus = nil
248+
user.SubscriptionEndsAt = nil
249+
250+
if err = service.repository.Update(ctx, user); err != nil {
251+
msg := fmt.Sprintf("could not update [%T] with with ID [%s] after expired subscription update", user, params.UserID)
252+
return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
253+
}
254+
255+
return nil
256+
}

0 commit comments

Comments
 (0)