Passed
Push — main ( 2f58a8...713109 )
by Acho
01:36
created

services.*LemonsqueezyService.GetUserID   A

Complexity

Conditions 3

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
dl 0
loc 17
c 0
b 0
f 0
rs 9.8
nop 2
1
package services
2
3
import (
4
	"context"
5
	"fmt"
6
	"strings"
7
	"time"
8
9
	"github.com/NdoleStudio/httpsms/pkg/repositories"
10
11
	"github.com/NdoleStudio/httpsms/pkg/entities"
12
	"github.com/NdoleStudio/httpsms/pkg/events"
13
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
14
	"github.com/NdoleStudio/lemonsqueezy-go"
15
	"github.com/palantir/stacktrace"
16
)
17
18
// LemonsqueezyService is responsible for managing lemonsqueezy events
19
type LemonsqueezyService struct {
20
	service
21
	logger          telemetry.Logger
22
	tracer          telemetry.Tracer
23
	eventDispatcher *EventDispatcher
24
	userRepository  repositories.UserRepository
25
}
26
27
// NewLemonsqueezyService creates a new LemonsqueezyService
28
func NewLemonsqueezyService(
29
	logger telemetry.Logger,
30
	tracer telemetry.Tracer,
31
	repository repositories.UserRepository,
32
	eventDispatcher *EventDispatcher,
33
) (s *LemonsqueezyService) {
34
	return &LemonsqueezyService{
35
		logger:          logger.WithService(fmt.Sprintf("%T", s)),
36
		tracer:          tracer,
37
		userRepository:  repository,
38
		eventDispatcher: eventDispatcher,
39
	}
40
}
41
42
// GetUserID gets the user ID from the request
43
func (service *LemonsqueezyService) GetUserID(ctx context.Context, request *lemonsqueezy.WebhookRequestSubscription) (entities.UserID, error) {
44
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
45
	defer span.End()
46
47
	userID, ok := request.Meta.CustomData["user_id"].(string)
48
	if ok {
49
		return entities.UserID(userID), nil
50
	}
51
52
	ctxLogger.Info(fmt.Sprintf("user_id not found in request meta data. Searching via email [%s]", request.Data.Attributes.UserEmail))
53
	user, err := service.userRepository.LoadByEmail(ctx, request.Data.Attributes.UserEmail)
54
	if err != nil {
55
		msg := fmt.Sprintf("cannot load user with email [%s]", request.Data.Attributes.UserEmail)
56
		return "", service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
57
	}
58
59
	return user.ID, nil
60
}
61
62
// HandleSubscriptionCreatedEvent handles the subscription_created lemonsqueezy event
63
func (service *LemonsqueezyService) HandleSubscriptionCreatedEvent(ctx context.Context, source string, request *lemonsqueezy.WebhookRequestSubscription) error {
64
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
65
	defer span.End()
66
67
	userID, err := service.GetUserID(ctx, request)
68
	if err != nil {
69
		msg := fmt.Sprintf("cannot get user ID for subscription created event with ID [%s]", request.Data.ID)
70
		return stacktrace.Propagate(err, msg)
71
	}
72
73
	payload := &events.UserSubscriptionCreatedPayload{
74
		UserID:                userID,
75
		SubscriptionCreatedAt: request.Data.Attributes.CreatedAt,
76
		SubscriptionID:        request.Data.ID,
77
		SubscriptionName:      service.subscriptionName(request.Data.Attributes.VariantName),
78
		SubscriptionRenewsAt:  request.Data.Attributes.RenewsAt,
79
		SubscriptionStatus:    request.Data.Attributes.Status,
80
	}
81
82
	event, err := service.createEvent(events.UserSubscriptionCreated, source, payload)
83
	if err != nil {
84
		msg := fmt.Sprintf("cannot create [%s] event for user [%s]", events.UserSubscriptionCreated, payload.UserID)
85
		return stacktrace.Propagate(err, msg)
86
	}
87
88
	if err = service.eventDispatcher.Dispatch(ctx, event); err != nil {
89
		msg := fmt.Sprintf("cannot dispatch [%s] event for user [%s]", events.UserSubscriptionCreated, payload.UserID)
90
		return stacktrace.Propagate(err, msg)
91
	}
92
	ctxLogger.Info(fmt.Sprintf("[%s] subscription [%s] created for user [%s]", payload.SubscriptionName, payload.SubscriptionID, payload.UserID))
93
	return nil
94
}
95
96
// HandleSubscriptionCanceledEvent handles the subscription_cancelled lemonsqueezy event
97
func (service *LemonsqueezyService) HandleSubscriptionCanceledEvent(ctx context.Context, source string, request *lemonsqueezy.WebhookRequestSubscription) error {
98
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
99
	defer span.End()
100
101
	user, err := service.userRepository.LoadBySubscriptionID(ctx, request.Data.ID)
102
	if err != nil {
103
		msg := fmt.Sprintf("cannot load user with subscription ID [%s]", request.Data.ID)
104
		return stacktrace.Propagate(err, msg)
105
	}
106
107
	payload := &events.UserSubscriptionCancelledPayload{
108
		UserID:                  user.ID,
109
		SubscriptionCancelledAt: request.Data.Attributes.CreatedAt,
110
		SubscriptionID:          request.Data.ID,
111
		SubscriptionName:        service.subscriptionName(request.Data.Attributes.VariantName),
112
		SubscriptionEndsAt:      *request.Data.Attributes.EndsAt,
113
		SubscriptionStatus:      request.Data.Attributes.Status,
114
	}
115
116
	event, err := service.createEvent(events.UserSubscriptionCancelled, source, payload)
117
	if err != nil {
118
		msg := fmt.Sprintf("cannot created [%s] event for user [%s]", events.UserSubscriptionCancelled, payload.UserID)
119
		return stacktrace.Propagate(err, msg)
120
	}
121
122
	if err = service.eventDispatcher.Dispatch(ctx, event); err != nil {
123
		msg := fmt.Sprintf("cannot dispatch [%s] event for user [%s]", events.UserSubscriptionCancelled, payload.UserID)
124
		return stacktrace.Propagate(err, msg)
125
	}
126
	ctxLogger.Info(fmt.Sprintf("[%s] subscription [%s] cancelled for user [%s]", payload.SubscriptionName, payload.SubscriptionID, payload.UserID))
127
	return nil
128
}
129
130
// HandleSubscriptionUpdatedEvent handles the subscription_cancelled lemonsqueezy event
131
func (service *LemonsqueezyService) HandleSubscriptionUpdatedEvent(ctx context.Context, source string, request *lemonsqueezy.WebhookRequestSubscription) error {
132
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
133
	defer span.End()
134
135
	user, err := service.userRepository.LoadBySubscriptionID(ctx, request.Data.ID)
136
	if err != nil {
137
		msg := fmt.Sprintf("cannot load user with subscription ID [%s]", request.Data.ID)
138
		return stacktrace.Propagate(err, msg)
139
	}
140
141
	payload := &events.UserSubscriptionUpdatedPayload{
142
		UserID:                user.ID,
143
		SubscriptionUpdatedAt: request.Data.Attributes.UpdatedAt,
144
		SubscriptionID:        request.Data.ID,
145
		SubscriptionName:      service.subscriptionName(request.Data.Attributes.VariantName),
146
		SubscriptionEndsAt:    request.Data.Attributes.EndsAt,
147
		SubscriptionRenewsAt:  request.Data.Attributes.RenewsAt,
148
		SubscriptionStatus:    request.Data.Attributes.Status,
149
	}
150
151
	event, err := service.createEvent(events.UserSubscriptionUpdated, source, payload)
152
	if err != nil {
153
		msg := fmt.Sprintf("cannot created [%s] event for user [%s]", events.UserSubscriptionUpdated, payload.UserID)
154
		return stacktrace.Propagate(err, msg)
155
	}
156
157
	if err = service.eventDispatcher.Dispatch(ctx, event); err != nil {
158
		msg := fmt.Sprintf("cannot dispatch [%s] event for user [%s]", event.Type(), payload.UserID)
159
		return stacktrace.Propagate(err, msg)
160
	}
161
	ctxLogger.Info(fmt.Sprintf("[%s] subscription [%s] updated for user [%s]", payload.SubscriptionName, payload.SubscriptionID, payload.UserID))
162
	return nil
163
}
164
165
// HandleSubscriptionExpiredEvent handles the subscription_expired lemonsqueezy event
166
func (service *LemonsqueezyService) HandleSubscriptionExpiredEvent(ctx context.Context, source string, request *lemonsqueezy.WebhookRequestSubscription) error {
167
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
168
	defer span.End()
169
170
	user, err := service.userRepository.LoadBySubscriptionID(ctx, request.Data.ID)
171
	if err != nil {
172
		msg := fmt.Sprintf("cannot load user with subscription ID [%s]", request.Data.ID)
173
		return stacktrace.Propagate(err, msg)
174
	}
175
176
	payload := &events.UserSubscriptionExpiredPayload{
177
		UserID:                user.ID,
178
		SubscriptionExpiredAt: time.Now().UTC(),
179
		SubscriptionID:        request.Data.ID,
180
		IsCancelled:           request.Data.Attributes.Cancelled,
181
		SubscriptionName:      service.subscriptionName(request.Data.Attributes.VariantName),
182
		SubscriptionEndsAt:    *request.Data.Attributes.EndsAt,
183
		SubscriptionStatus:    request.Data.Attributes.Status,
184
	}
185
186
	event, err := service.createEvent(events.UserSubscriptionExpired, source, payload)
187
	if err != nil {
188
		msg := fmt.Sprintf("cannot created [%s] event for user [%s]", events.UserSubscriptionExpired, payload.UserID)
189
		return stacktrace.Propagate(err, msg)
190
	}
191
192
	if err = service.eventDispatcher.Dispatch(ctx, event); err != nil {
193
		msg := fmt.Sprintf("cannot dispatch [%s] event for user [%s]", event.Type(), payload.UserID)
194
		return stacktrace.Propagate(err, msg)
195
	}
196
	ctxLogger.Info(fmt.Sprintf("[%s] subscription [%s] expired for user [%s]", payload.SubscriptionName, payload.SubscriptionID, payload.UserID))
197
	return nil
198
}
199
200
func (service *LemonsqueezyService) subscriptionName(variant string) entities.SubscriptionName {
201
	if strings.Contains(strings.ToLower(variant), "pro") {
202
		if strings.Contains(strings.ToLower(variant), "monthly") {
203
			return entities.SubscriptionNameProMonthly
204
		}
205
		return entities.SubscriptionNameProYearly
206
	}
207
208
	if strings.Contains(strings.ToLower(variant), "ultra") {
209
		if strings.Contains(strings.ToLower(variant), "monthly") {
210
			return entities.SubscriptionNameUltraMonthly
211
		}
212
		return entities.SubscriptionNameUltraYearly
213
	}
214
215
	if strings.Contains(strings.ToLower(variant), "20k") {
216
		if strings.Contains(strings.ToLower(variant), "monthly") {
217
			return entities.SubscriptionName20KMonthly
218
		}
219
		return entities.SubscriptionName20KYearly
220
	}
221
222
	if strings.Contains(strings.ToLower(variant), "100k") {
223
		if strings.Contains(strings.ToLower(variant), "monthly") {
224
			return entities.SubscriptionName100KMonthly
225
		}
226
	}
227
228
	if strings.Contains(strings.ToLower(variant), "50k") {
229
		if strings.Contains(strings.ToLower(variant), "monthly") {
230
			return entities.SubscriptionName50KMonthly
231
		}
232
	}
233
234
	if strings.Contains(strings.ToLower(variant), "200k") {
235
		if strings.Contains(strings.ToLower(variant), "monthly") {
236
			return entities.SubscriptionName200KMonthly
237
		}
238
	}
239
240
	return entities.SubscriptionNameFree
241
}
242