@darraghor/nest-backend-libs 5.7.4 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +87 -36
  2. package/dist/cli/commands/WriteApiClientCommand.js +1 -2
  3. package/dist/cli/commands/WriteApiClientCommand.js.map +1 -1
  4. package/dist/database-sqlite/SqliteTypeOrmConfigurationProvider.js +5 -4
  5. package/dist/database-sqlite/SqliteTypeOrmConfigurationProvider.js.map +1 -1
  6. package/dist/exports/authorization.d.ts +14 -0
  7. package/dist/exports/authorization.js +12 -0
  8. package/dist/exports/authorization.js.map +1 -0
  9. package/dist/exports/core.d.ts +11 -0
  10. package/dist/exports/core.js +10 -0
  11. package/dist/exports/core.js.map +1 -0
  12. package/dist/exports/database.d.ts +5 -0
  13. package/dist/exports/database.js +6 -0
  14. package/dist/exports/database.js.map +1 -0
  15. package/dist/exports/email.d.ts +4 -0
  16. package/dist/exports/email.js +4 -0
  17. package/dist/exports/email.js.map +1 -0
  18. package/dist/exports/invitations.d.ts +6 -0
  19. package/dist/exports/invitations.js +6 -0
  20. package/dist/exports/invitations.js.map +1 -0
  21. package/dist/exports/organisations.d.ts +11 -0
  22. package/dist/exports/organisations.js +12 -0
  23. package/dist/exports/organisations.js.map +1 -0
  24. package/dist/exports/stripe-legacy.d.ts +6 -0
  25. package/dist/exports/stripe-legacy.js +7 -0
  26. package/dist/exports/stripe-legacy.js.map +1 -0
  27. package/dist/exports/stripe.d.ts +11 -0
  28. package/dist/exports/stripe.js +9 -0
  29. package/dist/exports/stripe.js.map +1 -0
  30. package/dist/exports/twitter.d.ts +5 -0
  31. package/dist/exports/twitter.js +5 -0
  32. package/dist/exports/twitter.js.map +1 -0
  33. package/dist/exports/users.d.ts +10 -0
  34. package/dist/exports/users.js +11 -0
  35. package/dist/exports/users.js.map +1 -0
  36. package/dist/index.d.ts +11 -0
  37. package/dist/index.js +11 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/organisation-subscriptions/entities/organisation-subscription.entity.js +2 -1
  40. package/dist/organisation-subscriptions/entities/organisation-subscription.entity.js.map +1 -1
  41. package/dist/stripe-client/StripeClientConfigurationService.js +1 -0
  42. package/dist/stripe-client/StripeClientConfigurationService.js.map +1 -1
  43. package/dist/stripe-client/StripeClientProvider.js +1 -1
  44. package/dist/stripe-client/StripeClientProvider.js.map +1 -1
  45. package/dist/stripe-client/controllers/stripe-checkout-controller.d.ts +1 -0
  46. package/dist/stripe-client/controllers/stripe-checkout-controller.js +1 -0
  47. package/dist/stripe-client/controllers/stripe-checkout-controller.js.map +1 -1
  48. package/dist/stripe-client/controllers/stripe-events-controller.js +1 -0
  49. package/dist/stripe-client/controllers/stripe-events-controller.js.map +1 -1
  50. package/dist/stripe-client/controllers/stripe-unauthenticated-checkout-controller.js +1 -0
  51. package/dist/stripe-client/controllers/stripe-unauthenticated-checkout-controller.js.map +1 -1
  52. package/dist/stripe-client/controllers/stripe-webhook-controller.js +1 -4
  53. package/dist/stripe-client/controllers/stripe-webhook-controller.js.map +1 -1
  54. package/dist/stripe-client/services/auth-stripe-checkout.service.js +1 -0
  55. package/dist/stripe-client/services/auth-stripe-checkout.service.js.map +1 -1
  56. package/dist/stripe-client/services/queued-payment-event.handler.d.ts +1 -0
  57. package/dist/stripe-client/services/queued-payment-event.handler.js +2 -0
  58. package/dist/stripe-client/services/queued-payment-event.handler.js.map +1 -1
  59. package/dist/stripe-client/services/stripe-checkout.service.d.ts +1 -0
  60. package/dist/stripe-client/services/stripe-checkout.service.js +2 -0
  61. package/dist/stripe-client/services/stripe-checkout.service.js.map +1 -1
  62. package/dist/stripe-client/stripe-account.module.d.ts +1 -0
  63. package/dist/stripe-client/stripe-account.module.js +2 -0
  64. package/dist/stripe-client/stripe-account.module.js.map +1 -1
  65. package/dist/stripe-client/stripe-account.options.d.ts +2 -0
  66. package/dist/stripe-client/stripe-account.options.js.map +1 -1
  67. package/dist/stripe-payments/controllers/secure-stripe-checkout.controller.d.ts +10 -0
  68. package/dist/stripe-payments/controllers/secure-stripe-checkout.controller.js +65 -0
  69. package/dist/stripe-payments/controllers/secure-stripe-checkout.controller.js.map +1 -0
  70. package/dist/stripe-payments/controllers/secure-stripe-events.controller.d.ts +9 -0
  71. package/dist/stripe-payments/controllers/secure-stripe-events.controller.js +65 -0
  72. package/dist/stripe-payments/controllers/secure-stripe-events.controller.js.map +1 -0
  73. package/dist/stripe-payments/controllers/secure-stripe-webhook.controller.d.ts +14 -0
  74. package/dist/stripe-payments/controllers/secure-stripe-webhook.controller.js +112 -0
  75. package/dist/stripe-payments/controllers/secure-stripe-webhook.controller.js.map +1 -0
  76. package/dist/stripe-payments/entities/stripe-checkout-attempt.entity.d.ts +15 -0
  77. package/dist/stripe-payments/entities/stripe-checkout-attempt.entity.js +88 -0
  78. package/dist/stripe-payments/entities/stripe-checkout-attempt.entity.js.map +1 -0
  79. package/dist/stripe-payments/entities/stripe-payment-event.entity.d.ts +16 -0
  80. package/dist/stripe-payments/entities/stripe-payment-event.entity.js +101 -0
  81. package/dist/stripe-payments/entities/stripe-payment-event.entity.js.map +1 -0
  82. package/dist/stripe-payments/entities/stripe-payment-state.entity.d.ts +9 -0
  83. package/dist/stripe-payments/entities/stripe-payment-state.entity.js +53 -0
  84. package/dist/stripe-payments/entities/stripe-payment-state.entity.js.map +1 -0
  85. package/dist/stripe-payments/models/secure-checkout.dto.d.ts +14 -0
  86. package/dist/stripe-payments/models/secure-checkout.dto.js +105 -0
  87. package/dist/stripe-payments/models/secure-checkout.dto.js.map +1 -0
  88. package/dist/stripe-payments/models/secure-payment-response.dto.d.ts +7 -0
  89. package/dist/stripe-payments/models/secure-payment-response.dto.js +30 -0
  90. package/dist/stripe-payments/models/secure-payment-response.dto.js.map +1 -0
  91. package/dist/stripe-payments/services/default-stripe-payments-entitlement-store.d.ts +12 -0
  92. package/dist/stripe-payments/services/default-stripe-payments-entitlement-store.js +49 -0
  93. package/dist/stripe-payments/services/default-stripe-payments-entitlement-store.js.map +1 -0
  94. package/dist/stripe-payments/services/secure-stripe-checkout.service.d.ts +24 -0
  95. package/dist/stripe-payments/services/secure-stripe-checkout.service.js +213 -0
  96. package/dist/stripe-payments/services/secure-stripe-checkout.service.js.map +1 -0
  97. package/dist/stripe-payments/services/secure-stripe-client.provider.d.ts +2 -0
  98. package/dist/stripe-payments/services/secure-stripe-client.provider.js +15 -0
  99. package/dist/stripe-payments/services/secure-stripe-client.provider.js.map +1 -0
  100. package/dist/stripe-payments/services/secure-stripe-product.service.d.ts +9 -0
  101. package/dist/stripe-payments/services/secure-stripe-product.service.js +54 -0
  102. package/dist/stripe-payments/services/secure-stripe-product.service.js.map +1 -0
  103. package/dist/stripe-payments/services/secure-stripe-webhook.service.d.ts +14 -0
  104. package/dist/stripe-payments/services/secure-stripe-webhook.service.js +58 -0
  105. package/dist/stripe-payments/services/secure-stripe-webhook.service.js.map +1 -0
  106. package/dist/stripe-payments/services/secure-subscription.service.d.ts +18 -0
  107. package/dist/stripe-payments/services/secure-subscription.service.js +217 -0
  108. package/dist/stripe-payments/services/secure-subscription.service.js.map +1 -0
  109. package/dist/stripe-payments/services/stripe-payment-event.processor.d.ts +15 -0
  110. package/dist/stripe-payments/services/stripe-payment-event.processor.js +78 -0
  111. package/dist/stripe-payments/services/stripe-payment-event.processor.js.map +1 -0
  112. package/dist/stripe-payments/services/stripe-payment-event.service.d.ts +20 -0
  113. package/dist/stripe-payments/services/stripe-payment-event.service.js +178 -0
  114. package/dist/stripe-payments/services/stripe-payment-event.service.js.map +1 -0
  115. package/dist/stripe-payments/services/stripe-payment-fulfillment.service.d.ts +31 -0
  116. package/dist/stripe-payments/services/stripe-payment-fulfillment.service.js +331 -0
  117. package/dist/stripe-payments/services/stripe-payment-fulfillment.service.js.map +1 -0
  118. package/dist/stripe-payments/services/stripe-payments-operations.service.d.ts +25 -0
  119. package/dist/stripe-payments/services/stripe-payments-operations.service.js +59 -0
  120. package/dist/stripe-payments/services/stripe-payments-operations.service.js.map +1 -0
  121. package/dist/stripe-payments/services/stripe-payments-telemetry.service.d.ts +7 -0
  122. package/dist/stripe-payments/services/stripe-payments-telemetry.service.js +40 -0
  123. package/dist/stripe-payments/services/stripe-payments-telemetry.service.js.map +1 -0
  124. package/dist/stripe-payments/stripe-payment.config.d.ts +19 -0
  125. package/dist/stripe-payments/stripe-payment.config.js +98 -0
  126. package/dist/stripe-payments/stripe-payment.config.js.map +1 -0
  127. package/dist/stripe-payments/stripe-payment.config.test.d.ts +1 -0
  128. package/dist/stripe-payments/stripe-payment.config.test.js +59 -0
  129. package/dist/stripe-payments/stripe-payment.config.test.js.map +1 -0
  130. package/dist/stripe-payments/stripe-payments.extensions.d.ts +69 -0
  131. package/dist/stripe-payments/stripe-payments.extensions.js +30 -0
  132. package/dist/stripe-payments/stripe-payments.extensions.js.map +1 -0
  133. package/dist/stripe-payments/stripe-payments.module.d.ts +12 -0
  134. package/dist/stripe-payments/stripe-payments.module.js +118 -0
  135. package/dist/stripe-payments/stripe-payments.module.js.map +1 -0
  136. package/dist/stripe-payments/stripe-payments.options.d.ts +21 -0
  137. package/dist/stripe-payments/stripe-payments.options.js +2 -0
  138. package/dist/stripe-payments/stripe-payments.options.js.map +1 -0
  139. package/dist/typeorm/entities.d.ts +15 -0
  140. package/dist/typeorm/entities.js +32 -0
  141. package/dist/typeorm/entities.js.map +1 -0
  142. package/package.json +85 -38
@@ -0,0 +1,213 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ var SecureStripeCheckoutService_1;
14
+ import { ConflictException, Inject, Injectable, Logger, ServiceUnavailableException, } from "@nestjs/common";
15
+ import Stripe from "stripe";
16
+ import { InjectRepository } from "@nestjs/typeorm";
17
+ import { QueryFailedError, Repository } from "typeorm";
18
+ import { StripeCheckoutAttempt } from "../entities/stripe-checkout-attempt.entity.js";
19
+ import { SecureStripeProductService } from "./secure-stripe-product.service.js";
20
+ import { STRIPE_PAYMENTS_ACCESS_POLICY, STRIPE_PAYMENTS_ENTITLEMENT_STORE, } from "../stripe-payments.extensions.js";
21
+ import { StripePaymentsTelemetryService } from "./stripe-payments-telemetry.service.js";
22
+ const idempotencyKeyPattern = /^[A-Za-z0-9._-]{8,255}$/;
23
+ let SecureStripeCheckoutService = SecureStripeCheckoutService_1 = class SecureStripeCheckoutService {
24
+ stripe;
25
+ attemptRepository;
26
+ productService;
27
+ entitlementStore;
28
+ accessPolicy;
29
+ telemetry;
30
+ logger = new Logger(SecureStripeCheckoutService_1.name);
31
+ constructor(stripe, attemptRepository, productService, entitlementStore, accessPolicy, telemetry) {
32
+ this.stripe = stripe;
33
+ this.attemptRepository = attemptRepository;
34
+ this.productService = productService;
35
+ this.entitlementStore = entitlementStore;
36
+ this.accessPolicy = accessPolicy;
37
+ this.telemetry = telemetry;
38
+ }
39
+ async createCheckoutSession(request, user, idempotencyKey) {
40
+ const key = this.requireIdempotencyKey(idempotencyKey);
41
+ await this.accessPolicy.assertCanManageOrganisation({
42
+ organisationUuid: request.organisationUuid,
43
+ user,
44
+ operation: "checkout",
45
+ });
46
+ const product = this.productService.getByKey(request.productKey);
47
+ const successUrl = this.productService.buildRedirectUrl(request.successFrontendPath);
48
+ const cancelUrl = request.cancelFrontendPath
49
+ ? this.productService.buildRedirectUrl(request.cancelFrontendPath)
50
+ : undefined;
51
+ const existingAttempt = await this.attemptRepository.findOne({
52
+ where: { idempotencyKey: key },
53
+ });
54
+ if (existingAttempt) {
55
+ this.assertMatchingAttempt(existingAttempt, request, user, product.priceId);
56
+ if (existingAttempt.status === "created" &&
57
+ existingAttempt.stripeSessionId &&
58
+ existingAttempt.stripeSessionUrl) {
59
+ return {
60
+ stripeSessionId: existingAttempt.stripeSessionId,
61
+ stripeSessionUrl: existingAttempt.stripeSessionUrl,
62
+ };
63
+ }
64
+ if (existingAttempt.updatedDate.getTime() >
65
+ Date.now() - 10 * 60 * 1000) {
66
+ throw new ConflictException("Checkout attempt is already in progress");
67
+ }
68
+ }
69
+ const attempt = existingAttempt ??
70
+ this.attemptRepository.create({
71
+ idempotencyKey: key,
72
+ organisationUuid: request.organisationUuid,
73
+ userUuid: user.uuid,
74
+ productKey: product.key,
75
+ priceId: product.priceId,
76
+ mode: product.mode,
77
+ status: "creating",
78
+ });
79
+ attempt.status = "creating";
80
+ attempt.errorMessage = undefined;
81
+ try {
82
+ await this.attemptRepository.save(attempt);
83
+ }
84
+ catch (error) {
85
+ if (!this.isUniqueViolation(error)) {
86
+ throw error;
87
+ }
88
+ const concurrentAttempt = await this.attemptRepository.findOne({
89
+ where: { idempotencyKey: key },
90
+ });
91
+ if (!concurrentAttempt) {
92
+ throw error;
93
+ }
94
+ this.assertMatchingAttempt(concurrentAttempt, request, user, product.priceId);
95
+ if (concurrentAttempt.status === "created" &&
96
+ concurrentAttempt.stripeSessionId &&
97
+ concurrentAttempt.stripeSessionUrl) {
98
+ return {
99
+ stripeSessionId: concurrentAttempt.stripeSessionId,
100
+ stripeSessionUrl: concurrentAttempt.stripeSessionUrl,
101
+ };
102
+ }
103
+ throw new ConflictException("Checkout attempt is already in progress");
104
+ }
105
+ try {
106
+ const metadata = {
107
+ organisationUuid: request.organisationUuid,
108
+ userUuid: user.uuid,
109
+ productKey: product.key,
110
+ internalSku: product.internalSku,
111
+ checkoutAttemptId: String(attempt.id),
112
+ };
113
+ const session = await this.stripe.checkout.sessions.create({
114
+ mode: product.mode,
115
+ client_reference_id: String(attempt.id),
116
+ line_items: [{ price: product.priceId, quantity: 1 }],
117
+ success_url: successUrl,
118
+ cancel_url: cancelUrl,
119
+ metadata,
120
+ ...(product.mode === "subscription"
121
+ ? { subscription_data: { metadata } }
122
+ : { payment_intent_data: { metadata } }),
123
+ }, { idempotencyKey: key });
124
+ if (!session.url) {
125
+ throw new Error("Stripe did not return a Checkout URL");
126
+ }
127
+ attempt.status = "created";
128
+ attempt.stripeSessionId = session.id;
129
+ attempt.stripeSessionUrl = session.url;
130
+ await this.attemptRepository.save(attempt);
131
+ await this.telemetry.record({
132
+ name: "checkout.created",
133
+ attemptId: attempt.id,
134
+ organisationUuid: attempt.organisationUuid,
135
+ productKey: attempt.productKey,
136
+ stripeSessionId: session.id,
137
+ });
138
+ return {
139
+ stripeSessionId: session.id,
140
+ stripeSessionUrl: session.url,
141
+ };
142
+ }
143
+ catch (error) {
144
+ attempt.status = "failed";
145
+ const errorMessage = error instanceof Error
146
+ ? error.message.slice(0, 500)
147
+ : "Stripe error";
148
+ attempt.errorMessage = errorMessage;
149
+ await this.attemptRepository.save(attempt);
150
+ await this.telemetry.record({
151
+ name: "checkout.failed",
152
+ attemptId: attempt.id,
153
+ organisationUuid: attempt.organisationUuid,
154
+ productKey: attempt.productKey,
155
+ error: errorMessage,
156
+ });
157
+ this.logger.error("Stripe Checkout session creation failed", {
158
+ attemptId: attempt.id,
159
+ productKey: product.key,
160
+ error: error instanceof Error ? error.message : String(error),
161
+ });
162
+ throw new ServiceUnavailableException("Unable to start checkout right now. Please try again.");
163
+ }
164
+ }
165
+ async createCustomerPortalSession(request, user, idempotencyKey) {
166
+ const key = this.requireIdempotencyKey(idempotencyKey);
167
+ const subscription = await this.entitlementStore.findByUuid(request.subscriptionRecordUuid);
168
+ await this.accessPolicy.assertCanManageOrganisation({
169
+ organisationUuid: subscription.organisationUuid,
170
+ user,
171
+ operation: "customer-portal",
172
+ });
173
+ if (!/^cus_[A-Za-z0-9]+$/.test(subscription.paymentSystemCustomerId)) {
174
+ throw new ConflictException("This subscription has no valid Stripe customer");
175
+ }
176
+ const session = await this.stripe.billingPortal.sessions.create({
177
+ customer: subscription.paymentSystemCustomerId,
178
+ return_url: this.productService.buildRedirectUrl(request.returnUrl),
179
+ }, { idempotencyKey: key });
180
+ return { sessionUrl: session.url };
181
+ }
182
+ requireIdempotencyKey(idempotencyKey) {
183
+ if (!idempotencyKey || !idempotencyKeyPattern.test(idempotencyKey)) {
184
+ throw new ConflictException("An Idempotency-Key header is required for payment requests");
185
+ }
186
+ return idempotencyKey;
187
+ }
188
+ assertMatchingAttempt(attempt, request, user, priceId) {
189
+ if (attempt.organisationUuid !== request.organisationUuid ||
190
+ attempt.userUuid !== user.uuid ||
191
+ attempt.productKey !== request.productKey ||
192
+ attempt.priceId !== priceId) {
193
+ throw new ConflictException("The Idempotency-Key was already used for another checkout");
194
+ }
195
+ }
196
+ isUniqueViolation(error) {
197
+ return (error instanceof QueryFailedError &&
198
+ error
199
+ .driverError.code === "23505");
200
+ }
201
+ };
202
+ SecureStripeCheckoutService = SecureStripeCheckoutService_1 = __decorate([
203
+ Injectable(),
204
+ __param(0, Inject("SecureStripeClient")),
205
+ __param(1, InjectRepository(StripeCheckoutAttempt)),
206
+ __param(3, Inject(STRIPE_PAYMENTS_ENTITLEMENT_STORE)),
207
+ __param(4, Inject(STRIPE_PAYMENTS_ACCESS_POLICY)),
208
+ __metadata("design:paramtypes", [Stripe,
209
+ Repository,
210
+ SecureStripeProductService, Object, Object, StripePaymentsTelemetryService])
211
+ ], SecureStripeCheckoutService);
212
+ export { SecureStripeCheckoutService };
213
+ //# sourceMappingURL=secure-stripe-checkout.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-stripe-checkout.service.js","sourceRoot":"","sources":["../../../src/stripe-payments/services/secure-stripe-checkout.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Provider } from "@nestjs/common";
2
+ export declare const secureStripeClientProvider: Provider;
@@ -0,0 +1,15 @@
1
+ import Stripe from "stripe";
2
+ import { SECURE_STRIPE_CONFIGURATION, } from "../stripe-payment.config.js";
3
+ export const secureStripeClientProvider = {
4
+ provide: "SecureStripeClient",
5
+ useFactory: (configuration) => new Stripe(configuration.accessToken, {
6
+ apiVersion: Stripe.API_VERSION,
7
+ appInfo: {
8
+ name: "Miller Secure Payments",
9
+ url: "https://usemiller.dev",
10
+ },
11
+ maxNetworkRetries: 2,
12
+ }),
13
+ inject: [SECURE_STRIPE_CONFIGURATION],
14
+ };
15
+ //# sourceMappingURL=secure-stripe-client.provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-stripe-client.provider.js","sourceRoot":"","sources":["../../../src/stripe-payments/services/secure-stripe-client.provider.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EACH,2BAA2B,GAE9B,MAAM,6BAA6B,CAAC;AAErC,MAAM,CAAC,MAAM,0BAA0B,GAAa;IAChD,OAAO,EAAE,oBAAoB;IAC7B,UAAU,EAAE,CAAC,aAAwC,EAAU,EAAE,CAC7D,IAAI,MAAM,CAAC,aAAa,CAAC,WAAW,EAAE;QAClC,UAAU,EAAE,MAAM,CAAC,WAAW;QAC9B,OAAO,EAAE;YACL,IAAI,EAAE,wBAAwB;YAC9B,GAAG,EAAE,uBAAuB;SAC/B;QACD,iBAAiB,EAAE,CAAC;KACvB,CAAC;IACN,MAAM,EAAE,CAAC,2BAA2B,CAAC;CACxC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { SecureStripeConfiguration, SecureStripeProduct } from "../stripe-payment.config.js";
2
+ export declare class SecureStripeProductService {
3
+ private readonly configuration;
4
+ constructor(configuration: SecureStripeConfiguration);
5
+ getByKey(productKey: string): SecureStripeProduct;
6
+ getByPriceId(priceId: string): SecureStripeProduct;
7
+ getConfiguredProducts(): readonly SecureStripeProduct[];
8
+ buildRedirectUrl(path: string): string;
9
+ }
@@ -0,0 +1,54 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { BadRequestException, Inject, Injectable } from "@nestjs/common";
14
+ import { SECURE_STRIPE_CONFIGURATION, } from "../stripe-payment.config.js";
15
+ let SecureStripeProductService = class SecureStripeProductService {
16
+ configuration;
17
+ constructor(configuration) {
18
+ this.configuration = configuration;
19
+ }
20
+ getByKey(productKey) {
21
+ const product = this.configuration.products.get(productKey);
22
+ if (!product) {
23
+ throw new BadRequestException("Unknown payment product");
24
+ }
25
+ return product;
26
+ }
27
+ getByPriceId(priceId) {
28
+ const product = [...this.configuration.products.values()].find((candidate) => candidate.priceId === priceId);
29
+ if (!product) {
30
+ throw new Error("Stripe returned a price outside the configured catalog");
31
+ }
32
+ return product;
33
+ }
34
+ getConfiguredProducts() {
35
+ return [...this.configuration.products.values()];
36
+ }
37
+ buildRedirectUrl(path) {
38
+ const url = new URL(path, `${this.configuration.redirectsBaseUrl}/`);
39
+ const baseUrl = new URL(`${this.configuration.redirectsBaseUrl}/`);
40
+ if (url.origin !== baseUrl.origin ||
41
+ !path.startsWith("/") ||
42
+ path.startsWith("//")) {
43
+ throw new BadRequestException("Redirect path must remain on the configured application origin");
44
+ }
45
+ return url.toString();
46
+ }
47
+ };
48
+ SecureStripeProductService = __decorate([
49
+ Injectable(),
50
+ __param(0, Inject(SECURE_STRIPE_CONFIGURATION)),
51
+ __metadata("design:paramtypes", [Object])
52
+ ], SecureStripeProductService);
53
+ export { SecureStripeProductService };
54
+ //# sourceMappingURL=secure-stripe-product.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-stripe-product.service.js","sourceRoot":"","sources":["../../../src/stripe-payments/services/secure-stripe-product.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAC,MAAM,gBAAgB,CAAC;AACvE,OAAO,EACH,2BAA2B,GAG9B,MAAM,6BAA6B,CAAC;AAG9B,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IAGd;IAFrB,YAEqB,aAAwC;QAAxC,kBAAa,GAAb,aAAa,CAA2B;IAC1D,CAAC;IAEJ,QAAQ,CAAC,UAAkB;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,MAAM,IAAI,mBAAmB,CAAC,yBAAyB,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,YAAY,CAAC,OAAe;QACxB,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC1D,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,KAAK,OAAO,CAC/C,CAAC;QACF,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACX,wDAAwD,CAC3D,CAAC;QACN,CAAC;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,qBAAqB;QACjB,OAAO,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,gBAAgB,CAAC,IAAY;QACzB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,gBAAgB,GAAG,CAAC,CAAC;QACrE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,gBAAgB,GAAG,CAAC,CAAC;QACnE,IACI,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM;YAC7B,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YACrB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EACvB,CAAC;YACC,MAAM,IAAI,mBAAmB,CACzB,gEAAgE,CACnE,CAAC;QACN,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;CACJ,CAAA;AA5CY,0BAA0B;IADtC,UAAU,EAAE;IAGJ,WAAA,MAAM,CAAC,2BAA2B,CAAC,CAAA;;GAF/B,0BAA0B,CA4CtC"}
@@ -0,0 +1,14 @@
1
+ import Stripe from "stripe";
2
+ import { SecureStripeConfiguration } from "../stripe-payment.config.js";
3
+ import { StripePaymentEventService } from "./stripe-payment-event.service.js";
4
+ export interface RawStripeWebhookRequest {
5
+ headers: Record<string, string | string[] | undefined>;
6
+ rawBody?: Buffer;
7
+ }
8
+ export declare class SecureStripeWebhookService {
9
+ private readonly stripe;
10
+ private readonly configuration;
11
+ private readonly paymentEventService;
12
+ constructor(stripe: Stripe, configuration: SecureStripeConfiguration, paymentEventService: StripePaymentEventService);
13
+ handleWebhook(request: RawStripeWebhookRequest): Promise<void>;
14
+ }
@@ -0,0 +1,58 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { BadRequestException, Inject, Injectable, ServiceUnavailableException, } from "@nestjs/common";
14
+ import Stripe from "stripe";
15
+ import { SECURE_STRIPE_CONFIGURATION, } from "../stripe-payment.config.js";
16
+ import { StripePaymentEventService } from "./stripe-payment-event.service.js";
17
+ let SecureStripeWebhookService = class SecureStripeWebhookService {
18
+ stripe;
19
+ configuration;
20
+ paymentEventService;
21
+ constructor(stripe, configuration, paymentEventService) {
22
+ this.stripe = stripe;
23
+ this.configuration = configuration;
24
+ this.paymentEventService = paymentEventService;
25
+ }
26
+ async handleWebhook(request) {
27
+ if (!request.rawBody) {
28
+ throw new BadRequestException("Webhook body was not received as raw bytes");
29
+ }
30
+ const signatureHeader = request.headers["stripe-signature"];
31
+ if (typeof signatureHeader !== "string") {
32
+ throw new BadRequestException("Missing Stripe webhook signature");
33
+ }
34
+ let event;
35
+ try {
36
+ event = this.stripe.webhooks.constructEvent(request.rawBody, signatureHeader, this.configuration.webhookVerificationKey);
37
+ }
38
+ catch {
39
+ throw new BadRequestException("Invalid Stripe webhook signature");
40
+ }
41
+ try {
42
+ await this.paymentEventService.receive(event);
43
+ }
44
+ catch {
45
+ // A non-2xx response tells Stripe to retry delivery. The event row is
46
+ // deliberately durable so a later delivery can enqueue it again.
47
+ throw new ServiceUnavailableException("Payment event could not be queued");
48
+ }
49
+ }
50
+ };
51
+ SecureStripeWebhookService = __decorate([
52
+ Injectable(),
53
+ __param(0, Inject("SecureStripeClient")),
54
+ __param(1, Inject(SECURE_STRIPE_CONFIGURATION)),
55
+ __metadata("design:paramtypes", [Stripe, Object, StripePaymentEventService])
56
+ ], SecureStripeWebhookService);
57
+ export { SecureStripeWebhookService };
58
+ //# sourceMappingURL=secure-stripe-webhook.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-stripe-webhook.service.js","sourceRoot":"","sources":["../../../src/stripe-payments/services/secure-stripe-webhook.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EACH,mBAAmB,EACnB,MAAM,EACN,UAAU,EACV,2BAA2B,GAC9B,MAAM,gBAAgB,CAAC;AACxB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EACH,2BAA2B,GAE9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAC,yBAAyB,EAAC,MAAM,mCAAmC,CAAC;AAQrE,IAAM,0BAA0B,GAAhC,MAAM,0BAA0B;IAGd;IAEA;IACA;IALrB,YAEqB,MAAc,EAEd,aAAwC,EACxC,mBAA8C;QAH9C,WAAM,GAAN,MAAM,CAAQ;QAEd,kBAAa,GAAb,aAAa,CAA2B;QACxC,wBAAmB,GAAnB,mBAAmB,CAA2B;IAChE,CAAC;IAEJ,KAAK,CAAC,aAAa,CAAC,OAAgC;QAChD,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,IAAI,mBAAmB,CACzB,4CAA4C,CAC/C,CAAC;QACN,CAAC;QACD,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC5D,IAAI,OAAO,eAAe,KAAK,QAAQ,EAAE,CAAC;YACtC,MAAM,IAAI,mBAAmB,CAAC,kCAAkC,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,KAAmB,CAAC;QACxB,IAAI,CAAC;YACD,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CACvC,OAAO,CAAC,OAAO,EACf,eAAe,EACf,IAAI,CAAC,aAAa,CAAC,sBAAsB,CAC5C,CAAC;QACN,CAAC;QAAC,MAAM,CAAC;YACL,MAAM,IAAI,mBAAmB,CAAC,kCAAkC,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACL,sEAAsE;YACtE,iEAAiE;YACjE,MAAM,IAAI,2BAA2B,CACjC,mCAAmC,CACtC,CAAC;QACN,CAAC;IACL,CAAC;CACJ,CAAA;AAzCY,0BAA0B;IADtC,UAAU,EAAE;IAGJ,WAAA,MAAM,CAAC,oBAAoB,CAAC,CAAA;IAE5B,WAAA,MAAM,CAAC,2BAA2B,CAAC,CAAA;qCADX,MAAM,UAGO,yBAAyB;GAN1D,0BAA0B,CAyCtC"}
@@ -0,0 +1,18 @@
1
+ import { OrganisationSubscriptionRecord } from "../../organisation-subscriptions/entities/organisation-subscription.entity.js";
2
+ import { Queue } from "bullmq";
3
+ import { Repository } from "typeorm";
4
+ import type { StripePaymentsEntitlementInput } from "../stripe-payments.extensions.js";
5
+ export type SecureSubscriptionInput = StripePaymentsEntitlementInput;
6
+ export declare class SecureSubscriptionService {
7
+ private readonly subscriptionRepository;
8
+ private readonly activationQueue;
9
+ private readonly logger;
10
+ constructor(subscriptionRepository: Repository<OrganisationSubscriptionRecord>, activationQueue: Queue);
11
+ findByUuid(uuid: string): Promise<OrganisationSubscriptionRecord>;
12
+ findByTransactionId(paymentSystemTransactionId: string): Promise<OrganisationSubscriptionRecord | null>;
13
+ findByCustomerId(paymentSystemCustomerId: string, mode?: string): Promise<OrganisationSubscriptionRecord[]>;
14
+ save(input: SecureSubscriptionInput): Promise<OrganisationSubscriptionRecord>;
15
+ revokeCustomerPayments(customerId: string, input: Pick<SecureSubscriptionInput, "stripeEventId" | "stripeEventCreatedAt" | "stripeStatus">): Promise<void>;
16
+ revokePayment(paymentSystemTransactionId: string, input: Pick<SecureSubscriptionInput, "stripeEventId" | "stripeEventCreatedAt" | "stripeStatus">): Promise<boolean>;
17
+ private saveRecord;
18
+ }
@@ -0,0 +1,217 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ var SecureSubscriptionService_1;
14
+ import { Injectable, Logger } from "@nestjs/common";
15
+ import { InjectQueue } from "@nestjs/bullmq";
16
+ import { Organisation } from "../../organisation/entities/organisation.entity.js";
17
+ import { OrganisationSubscriptionRecord } from "../../organisation-subscriptions/entities/organisation-subscription.entity.js";
18
+ import { Queue } from "bullmq";
19
+ import { InjectRepository } from "@nestjs/typeorm";
20
+ import { QueryFailedError, Repository } from "typeorm";
21
+ import { StripePaymentState } from "../entities/stripe-payment-state.entity.js";
22
+ let SecureSubscriptionService = SecureSubscriptionService_1 = class SecureSubscriptionService {
23
+ subscriptionRepository;
24
+ activationQueue;
25
+ logger = new Logger(SecureSubscriptionService_1.name);
26
+ constructor(subscriptionRepository, activationQueue) {
27
+ this.subscriptionRepository = subscriptionRepository;
28
+ this.activationQueue = activationQueue;
29
+ }
30
+ async findByUuid(uuid) {
31
+ const record = await this.subscriptionRepository.findOne({
32
+ where: { uuid },
33
+ relations: { organisation: true },
34
+ });
35
+ if (!record) {
36
+ throw new Error("Subscription record not found");
37
+ }
38
+ return record;
39
+ }
40
+ async findByTransactionId(paymentSystemTransactionId) {
41
+ return this.subscriptionRepository.findOne({
42
+ where: { paymentSystemTransactionId },
43
+ relations: { organisation: true },
44
+ });
45
+ }
46
+ async findByCustomerId(paymentSystemCustomerId, mode) {
47
+ return this.subscriptionRepository.find({
48
+ where: {
49
+ paymentSystemCustomerId,
50
+ ...(mode ? { paymentSystemMode: mode } : {}),
51
+ },
52
+ relations: { organisation: true },
53
+ });
54
+ }
55
+ async save(input) {
56
+ const { record, shouldActivate } = await this.saveRecord(input);
57
+ if (shouldActivate) {
58
+ await this.activationQueue.add("activation-changed", {
59
+ organisationUuid: record.organisation.uuid,
60
+ subscriptionUuid: record.uuid,
61
+ productKey: record.internalSku,
62
+ active: record.validUntil > new Date(),
63
+ }, {
64
+ jobId: `subscription-activation:${record.uuid}:${String(record.validUntil.getTime())}`,
65
+ attempts: 5,
66
+ backoff: { type: "exponential", delay: 2_000 },
67
+ removeOnComplete: { age: 86_400, count: 10_000 },
68
+ removeOnFail: { age: 604_800, count: 10_000 },
69
+ });
70
+ }
71
+ return record;
72
+ }
73
+ async revokeCustomerPayments(customerId, input) {
74
+ const records = await this.findByCustomerId(customerId, "payment");
75
+ for (const record of records) {
76
+ await this.save({
77
+ paymentSystemTransactionId: record.paymentSystemTransactionId,
78
+ paymentSystemProductId: record.paymentSystemProductId,
79
+ paymentSystemCustomerId: record.paymentSystemCustomerId,
80
+ paymentSystemCustomerEmail: record.paymentSystemCustomerEmail,
81
+ paymentSystemMode: "payment",
82
+ paymentSystemName: "stripe",
83
+ validUntil: new Date(),
84
+ internalSku: record.internalSku,
85
+ productDisplayName: record.productDisplayName,
86
+ organisationUuid: record.organisation.uuid,
87
+ stripeEventId: input.stripeEventId,
88
+ stripeEventCreatedAt: input.stripeEventCreatedAt,
89
+ stripeStatus: input.stripeStatus,
90
+ });
91
+ }
92
+ }
93
+ async revokePayment(paymentSystemTransactionId, input) {
94
+ const record = await this.findByTransactionId(paymentSystemTransactionId);
95
+ if (record?.paymentSystemMode !== "payment") {
96
+ return false;
97
+ }
98
+ await this.save({
99
+ paymentSystemTransactionId: record.paymentSystemTransactionId,
100
+ paymentSystemProductId: record.paymentSystemProductId,
101
+ paymentSystemCustomerId: record.paymentSystemCustomerId,
102
+ paymentSystemCustomerEmail: record.paymentSystemCustomerEmail,
103
+ paymentSystemMode: "payment",
104
+ paymentSystemName: "stripe",
105
+ validUntil: new Date(),
106
+ internalSku: record.internalSku,
107
+ productDisplayName: record.productDisplayName,
108
+ organisationUuid: record.organisation.uuid,
109
+ stripeEventId: input.stripeEventId,
110
+ stripeEventCreatedAt: input.stripeEventCreatedAt,
111
+ stripeStatus: input.stripeStatus,
112
+ });
113
+ return true;
114
+ }
115
+ async saveRecord(input) {
116
+ try {
117
+ return await this.subscriptionRepository.manager.transaction(async (manager) => {
118
+ await manager.query("SELECT pg_advisory_xact_lock(hashtext($1))", [input.paymentSystemTransactionId]);
119
+ const subscriptionRepository = manager.getRepository(OrganisationSubscriptionRecord);
120
+ const stateRepository = manager.getRepository(StripePaymentState);
121
+ const state = await stateRepository.findOne({
122
+ where: {
123
+ paymentSystemTransactionId: input.paymentSystemTransactionId,
124
+ },
125
+ });
126
+ let record = await subscriptionRepository.findOne({
127
+ where: {
128
+ paymentSystemTransactionId: input.paymentSystemTransactionId,
129
+ },
130
+ relations: { organisation: true },
131
+ });
132
+ const isDuplicateEvent = state?.lastStripeEventCreatedAt.getTime() ===
133
+ input.stripeEventCreatedAt.getTime() &&
134
+ state.lastStripeEventId === input.stripeEventId;
135
+ if (record &&
136
+ state &&
137
+ (state.lastStripeEventCreatedAt >
138
+ input.stripeEventCreatedAt ||
139
+ isDuplicateEvent)) {
140
+ return {
141
+ record,
142
+ // Retry the durable activation notification if the
143
+ // previous attempt failed after the DB commit.
144
+ shouldActivate: isDuplicateEvent,
145
+ };
146
+ }
147
+ const isNewRecord = !record;
148
+ if (!record) {
149
+ const organisation = input.organisationUuid
150
+ ? await manager
151
+ .getRepository(Organisation)
152
+ .findOne({
153
+ where: { uuid: input.organisationUuid },
154
+ })
155
+ : null;
156
+ if (!organisation) {
157
+ throw new Error("Payment could not be matched to an organisation");
158
+ }
159
+ record = subscriptionRepository.create({
160
+ organisation,
161
+ });
162
+ }
163
+ const previousValidUntil = isNewRecord
164
+ ? undefined
165
+ : record.validUntil.getTime();
166
+ record.paymentSystemTransactionId =
167
+ input.paymentSystemTransactionId;
168
+ record.paymentSystemProductId =
169
+ input.paymentSystemProductId;
170
+ record.paymentSystemCustomerId =
171
+ input.paymentSystemCustomerId;
172
+ record.paymentSystemCustomerEmail =
173
+ input.paymentSystemCustomerEmail;
174
+ record.paymentSystemMode = input.paymentSystemMode;
175
+ record.paymentSystemName = input.paymentSystemName;
176
+ record.validUntil = input.validUntil;
177
+ record.internalSku = input.internalSku;
178
+ record.productDisplayName = input.productDisplayName;
179
+ const savedRecord = await subscriptionRepository.save(record);
180
+ const shouldActivate = isNewRecord ||
181
+ state?.lastStripeEventId !== input.stripeEventId ||
182
+ previousValidUntil !== input.validUntil.getTime();
183
+ const paymentState = state ??
184
+ stateRepository.create({
185
+ paymentSystemTransactionId: input.paymentSystemTransactionId,
186
+ });
187
+ paymentState.lastStripeEventId = input.stripeEventId;
188
+ paymentState.lastStripeEventCreatedAt =
189
+ input.stripeEventCreatedAt;
190
+ paymentState.status = input.stripeStatus;
191
+ await stateRepository.save(paymentState);
192
+ return { record: savedRecord, shouldActivate };
193
+ });
194
+ }
195
+ catch (error) {
196
+ if (error instanceof QueryFailedError &&
197
+ error
198
+ .driverError.code === "23505") {
199
+ const existing = await this.findByTransactionId(input.paymentSystemTransactionId);
200
+ if (existing) {
201
+ this.logger.warn(`Ignored concurrent duplicate payment ${input.paymentSystemTransactionId}`);
202
+ return { record: existing, shouldActivate: false };
203
+ }
204
+ }
205
+ throw error;
206
+ }
207
+ }
208
+ };
209
+ SecureSubscriptionService = SecureSubscriptionService_1 = __decorate([
210
+ Injectable(),
211
+ __param(0, InjectRepository(OrganisationSubscriptionRecord)),
212
+ __param(1, InjectQueue("subscription-activation-changed")),
213
+ __metadata("design:paramtypes", [Repository,
214
+ Queue])
215
+ ], SecureSubscriptionService);
216
+ export { SecureSubscriptionService };
217
+ //# sourceMappingURL=secure-subscription.service.js.map