ghost 5.115.0 → 5.115.1

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 (197) hide show
  1. package/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
  2. package/components/tryghost-adapter-manager-5.115.1.tgz +0 -0
  3. package/components/{tryghost-announcement-bar-settings-5.115.0.tgz → tryghost-announcement-bar-settings-5.115.1.tgz} +0 -0
  4. package/components/{tryghost-api-framework-5.115.0.tgz → tryghost-api-framework-5.115.1.tgz} +0 -0
  5. package/components/tryghost-constants-5.115.1.tgz +0 -0
  6. package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
  7. package/components/{tryghost-custom-theme-settings-service-5.115.0.tgz → tryghost-custom-theme-settings-service-5.115.1.tgz} +0 -0
  8. package/components/{tryghost-data-generator-5.115.0.tgz → tryghost-data-generator-5.115.1.tgz} +0 -0
  9. package/components/tryghost-domain-events-5.115.1.tgz +0 -0
  10. package/components/tryghost-donations-5.115.1.tgz +0 -0
  11. package/components/{tryghost-email-addresses-5.115.0.tgz → tryghost-email-addresses-5.115.1.tgz} +0 -0
  12. package/components/{tryghost-email-content-generator-5.115.0.tgz → tryghost-email-content-generator-5.115.1.tgz} +0 -0
  13. package/components/{tryghost-email-events-5.115.0.tgz → tryghost-email-events-5.115.1.tgz} +0 -0
  14. package/components/tryghost-email-service-5.115.1.tgz +0 -0
  15. package/components/{tryghost-email-suppression-list-5.115.0.tgz → tryghost-email-suppression-list-5.115.1.tgz} +0 -0
  16. package/components/{tryghost-express-dynamic-redirects-5.115.0.tgz → tryghost-express-dynamic-redirects-5.115.1.tgz} +0 -0
  17. package/components/tryghost-ghost-5.115.1.tgz +0 -0
  18. package/components/tryghost-html-to-plaintext-5.115.1.tgz +0 -0
  19. package/components/tryghost-i18n-5.115.1.tgz +0 -0
  20. package/components/{tryghost-importer-handler-content-files-5.115.0.tgz → tryghost-importer-handler-content-files-5.115.1.tgz} +0 -0
  21. package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
  22. package/components/{tryghost-job-manager-5.115.0.tgz → tryghost-job-manager-5.115.1.tgz} +0 -0
  23. package/components/{tryghost-link-redirects-5.115.0.tgz → tryghost-link-redirects-5.115.1.tgz} +0 -0
  24. package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
  25. package/components/{tryghost-magic-link-5.115.0.tgz → tryghost-magic-link-5.115.1.tgz} +0 -0
  26. package/components/{tryghost-mailgun-client-5.115.0.tgz → tryghost-mailgun-client-5.115.1.tgz} +0 -0
  27. package/components/tryghost-member-attribution-5.115.1.tgz +0 -0
  28. package/components/{tryghost-member-events-5.115.0.tgz → tryghost-member-events-5.115.1.tgz} +0 -0
  29. package/components/{tryghost-members-api-5.115.0.tgz → tryghost-members-api-5.115.1.tgz} +0 -0
  30. package/components/{tryghost-members-csv-5.115.0.tgz → tryghost-members-csv-5.115.1.tgz} +0 -0
  31. package/components/{tryghost-members-offers-5.115.0.tgz → tryghost-members-offers-5.115.1.tgz} +0 -0
  32. package/components/{tryghost-members-payments-5.115.0.tgz → tryghost-members-payments-5.115.1.tgz} +0 -0
  33. package/components/{tryghost-milestones-5.115.0.tgz → tryghost-milestones-5.115.1.tgz} +0 -0
  34. package/components/{tryghost-minifier-5.115.0.tgz → tryghost-minifier-5.115.1.tgz} +0 -0
  35. package/components/{tryghost-mw-error-handler-5.115.0.tgz → tryghost-mw-error-handler-5.115.1.tgz} +0 -0
  36. package/components/{tryghost-mw-version-match-5.115.0.tgz → tryghost-mw-version-match-5.115.1.tgz} +0 -0
  37. package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
  38. package/components/tryghost-post-events-5.115.1.tgz +0 -0
  39. package/components/{tryghost-post-revisions-5.115.0.tgz → tryghost-post-revisions-5.115.1.tgz} +0 -0
  40. package/components/{tryghost-posts-service-5.115.0.tgz → tryghost-posts-service-5.115.1.tgz} +0 -0
  41. package/components/{tryghost-prometheus-metrics-5.115.0.tgz → tryghost-prometheus-metrics-5.115.1.tgz} +0 -0
  42. package/components/tryghost-recommendations-5.115.1.tgz +0 -0
  43. package/components/{tryghost-security-5.115.0.tgz → tryghost-security-5.115.1.tgz} +0 -0
  44. package/components/{tryghost-slack-notifications-5.115.0.tgz → tryghost-slack-notifications-5.115.1.tgz} +0 -0
  45. package/components/{tryghost-tiers-5.115.0.tgz → tryghost-tiers-5.115.1.tgz} +0 -0
  46. package/components/tryghost-webmentions-5.115.1.tgz +0 -0
  47. package/content/themes/casper/LICENSE +1 -1
  48. package/content/themes/casper/README.md +1 -1
  49. package/content/themes/source/LICENSE +1 -1
  50. package/content/themes/source/README.md +1 -1
  51. package/content/themes/source/assets/built/screen.css +1 -1
  52. package/content/themes/source/assets/built/screen.css.map +1 -1
  53. package/content/themes/source/assets/css/screen.css +11 -6
  54. package/content/themes/source/partials/feature-image.hbs +2 -2
  55. package/core/boot.js +3 -1
  56. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +23497 -23041
  57. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  58. package/core/built/admin/assets/admin-x-demo/{index-0040480a.mjs → index-15df2af5.mjs} +4 -3
  59. package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-8ca61d78.mjs} +67 -65
  60. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.mjs → CodeEditorView-d2e6872f.mjs} +2 -2
  61. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
  62. package/core/built/admin/assets/admin-x-settings/{index-376f847c.mjs → index-8e8821e5.mjs} +2 -2
  63. package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f5cb3db3.mjs} +3104 -3094
  64. package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-e8ae4d46.mjs} +3 -3
  65. package/core/built/admin/assets/{chunk.524.31419fdf6fb3859ecc1e.js → chunk.524.2439684964c164c598ab.js} +6 -6
  66. package/core/built/admin/assets/{chunk.582.08c816d5e4ab766486a7.js → chunk.582.bf5a2bbb2c4eb69ef1e7.js} +10 -10
  67. package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +1 -0
  68. package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +1 -0
  69. package/core/built/admin/assets/{ghost-938b3d9c29e3564a53a22f8c8f82d351.js → ghost-df7b9558260aa27d18b195ee895b487d.js} +181 -159
  70. package/core/built/admin/assets/stats/stats.js +11824 -0
  71. package/core/built/admin/index.html +4 -4
  72. package/core/frontend/helpers/ghost_head.js +3 -1
  73. package/core/frontend/src/cards/css/cta.css +1 -1
  74. package/core/server/api/endpoints/slugs.js +6 -2
  75. package/core/server/data/importer/import-manager.js +2 -2
  76. package/core/server/data/importer/importers/importer-revue.js +128 -0
  77. package/core/server/data/importer/importers/json-to-html.js +107 -0
  78. package/core/server/data/migrations/utils/tables.js +2 -4
  79. package/core/server/lib/bootstrap-socket.js +87 -0
  80. package/core/server/lib/package-json/index.js +1 -0
  81. package/core/server/lib/package-json/package-json.js +160 -0
  82. package/core/server/lib/package-json/parse.js +57 -0
  83. package/core/server/models/base/plugins/actions.js +44 -31
  84. package/core/server/models/base/plugins/generate-slug.js +6 -0
  85. package/core/server/notify.js +1 -1
  86. package/core/server/services/activitypub/ActivityPubService.ts +1 -1
  87. package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +99 -0
  88. package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +80 -0
  89. package/core/server/services/api-version-compatibility/extract-api-key.js +57 -0
  90. package/core/server/services/api-version-compatibility/index.js +2 -2
  91. package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +31 -0
  92. package/core/server/services/audience-feedback/AudienceFeedbackController.js +85 -0
  93. package/core/server/services/audience-feedback/AudienceFeedbackService.js +34 -0
  94. package/core/server/services/audience-feedback/Feedback.js +35 -0
  95. package/core/server/services/audience-feedback/index.js +4 -2
  96. package/core/server/services/auth/session/emails/signin.js +168 -0
  97. package/core/server/services/auth/session/index.js +2 -2
  98. package/core/server/services/auth/session/session-from-token.js +69 -0
  99. package/core/server/services/auth/session/session-service.js +364 -0
  100. package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +62 -0
  101. package/core/server/services/email-analytics/EmailAnalyticsService.js +552 -0
  102. package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +3 -3
  103. package/core/server/services/email-analytics/EventProcessingResult.js +66 -0
  104. package/core/server/services/explore-ping/ExplorePingService.js +106 -0
  105. package/core/server/services/explore-ping/index.js +31 -0
  106. package/core/server/services/identity-tokens/IdentityTokenService.js +30 -0
  107. package/core/server/services/identity-tokens/IdentityTokenService.ts +28 -0
  108. package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +1 -1
  109. package/core/server/services/invitations/accept.js +5 -2
  110. package/core/server/services/mail-events/BookshelfMailEventRepository.js +2 -2
  111. package/core/server/services/mail-events/InMemoryMailEventRepository.js +10 -0
  112. package/core/server/services/mail-events/InMemoryMailEventRepository.ts +8 -0
  113. package/core/server/services/mail-events/MailEvent.js +20 -0
  114. package/core/server/services/mail-events/MailEvent.ts +10 -0
  115. package/core/server/services/mail-events/MailEventRepository.js +2 -0
  116. package/core/server/services/mail-events/MailEventRepository.ts +5 -0
  117. package/core/server/services/mail-events/MailEventService.js +124 -0
  118. package/core/server/services/mail-events/MailEventService.ts +169 -0
  119. package/core/server/services/mail-events/index.js +1 -1
  120. package/core/server/services/mail-events/libraries.d.ts +2 -0
  121. package/core/server/services/members/CaptchaService.js +80 -0
  122. package/core/server/services/members/api.js +1 -1
  123. package/core/server/services/members/importer/MembersCSVImporter.js +464 -0
  124. package/core/server/services/members/importer/MembersCSVImporterStripeUtils.js +194 -0
  125. package/core/server/services/members/importer/email-template.js +182 -0
  126. package/core/server/services/members/importer/index.js +30 -0
  127. package/core/server/services/members/members-ssr.js +333 -0
  128. package/core/server/services/members/service.js +2 -2
  129. package/core/server/services/posts/stats/PostStats.js +13 -0
  130. package/core/server/services/route-settings/SettingsPathManager.js +47 -0
  131. package/core/server/services/route-settings/index.js +1 -1
  132. package/core/server/services/stripe/README.md +63 -0
  133. package/core/server/services/stripe/StripeAPI.js +931 -0
  134. package/core/server/services/stripe/StripeMigrations.js +613 -0
  135. package/core/server/services/stripe/StripeService.js +175 -0
  136. package/core/server/services/stripe/WebhookController.js +100 -0
  137. package/core/server/services/stripe/WebhookManager.js +175 -0
  138. package/core/server/services/stripe/events/StripeLiveDisabledEvent.js +23 -0
  139. package/core/server/services/stripe/events/StripeLiveEnabledEvent.js +23 -0
  140. package/core/server/services/stripe/events/index.js +4 -0
  141. package/core/server/services/stripe/service.js +1 -1
  142. package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +255 -0
  143. package/core/server/services/stripe/services/webhook/InvoiceEventService.js +70 -0
  144. package/core/server/services/stripe/services/webhook/SubscriptionEventService.js +54 -0
  145. package/core/server/services/themes/loader.js +1 -1
  146. package/core/server/services/themes/to-json.js +1 -1
  147. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  148. package/core/server/web/shared/middleware/cache-control.js +51 -0
  149. package/core/server/web/shared/middleware/index.js +1 -1
  150. package/core/server/web/well-known.js +1 -1
  151. package/core/shared/labs.js +3 -1
  152. package/core/shared/settings-cache/CacheManager.js +64 -6
  153. package/package.json +103 -134
  154. package/tsconfig.tsbuildinfo +1 -1
  155. package/yarn.lock +7 -93
  156. package/components/tryghost-adapter-cache-redis-5.115.0.tgz +0 -0
  157. package/components/tryghost-adapter-manager-5.115.0.tgz +0 -0
  158. package/components/tryghost-api-version-compatibility-service-5.115.0.tgz +0 -0
  159. package/components/tryghost-audience-feedback-5.115.0.tgz +0 -0
  160. package/components/tryghost-bookshelf-repository-5.115.0.tgz +0 -0
  161. package/components/tryghost-bootstrap-socket-5.115.0.tgz +0 -0
  162. package/components/tryghost-captcha-service-5.115.0.tgz +0 -0
  163. package/components/tryghost-constants-5.115.0.tgz +0 -0
  164. package/components/tryghost-custom-fonts-5.115.0.tgz +0 -0
  165. package/components/tryghost-domain-events-5.115.0.tgz +0 -0
  166. package/components/tryghost-donations-5.115.0.tgz +0 -0
  167. package/components/tryghost-email-analytics-provider-mailgun-5.115.0.tgz +0 -0
  168. package/components/tryghost-email-analytics-service-5.115.0.tgz +0 -0
  169. package/components/tryghost-email-service-5.115.0.tgz +0 -0
  170. package/components/tryghost-extract-api-key-5.115.0.tgz +0 -0
  171. package/components/tryghost-ghost-5.115.0.tgz +0 -0
  172. package/components/tryghost-html-to-plaintext-5.115.0.tgz +0 -0
  173. package/components/tryghost-i18n-5.115.0.tgz +0 -0
  174. package/components/tryghost-identity-token-service-5.115.0.tgz +0 -0
  175. package/components/tryghost-importer-revue-5.115.0.tgz +0 -0
  176. package/components/tryghost-in-memory-repository-5.115.0.tgz +0 -0
  177. package/components/tryghost-link-replacer-5.115.0.tgz +0 -0
  178. package/components/tryghost-mail-events-5.115.0.tgz +0 -0
  179. package/components/tryghost-member-attribution-5.115.0.tgz +0 -0
  180. package/components/tryghost-members-importer-5.115.0.tgz +0 -0
  181. package/components/tryghost-members-ssr-5.115.0.tgz +0 -0
  182. package/components/tryghost-members-stripe-service-5.115.0.tgz +0 -0
  183. package/components/tryghost-mw-api-version-mismatch-5.115.0.tgz +0 -0
  184. package/components/tryghost-mw-cache-control-5.115.0.tgz +0 -0
  185. package/components/tryghost-mw-session-from-token-5.115.0.tgz +0 -0
  186. package/components/tryghost-mw-update-user-last-seen-5.115.0.tgz +0 -0
  187. package/components/tryghost-mw-vhost-5.115.0.tgz +0 -0
  188. package/components/tryghost-package-json-5.115.0.tgz +0 -0
  189. package/components/tryghost-post-events-5.115.0.tgz +0 -0
  190. package/components/tryghost-recommendations-5.115.0.tgz +0 -0
  191. package/components/tryghost-referrers-5.115.0.tgz +0 -0
  192. package/components/tryghost-session-service-5.115.0.tgz +0 -0
  193. package/components/tryghost-settings-path-manager-5.115.0.tgz +0 -0
  194. package/components/tryghost-version-notifications-data-service-5.115.0.tgz +0 -0
  195. package/components/tryghost-webmentions-5.115.0.tgz +0 -0
  196. package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
  197. package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.css +0 -1
@@ -0,0 +1,175 @@
1
+ const WebhookManager = require('./WebhookManager');
2
+ const StripeAPI = require('./StripeAPI');
3
+ const StripeMigrations = require('./StripeMigrations');
4
+ const WebhookController = require('./WebhookController');
5
+ const DomainEvents = require('@tryghost/domain-events');
6
+ const {StripeLiveEnabledEvent, StripeLiveDisabledEvent} = require('./events');
7
+ const SubscriptionEventService = require('./services/webhook/SubscriptionEventService');
8
+ const InvoiceEventService = require('./services/webhook/InvoiceEventService');
9
+ const CheckoutSessionEventService = require('./services/webhook/CheckoutSessionEventService');
10
+
11
+ /**
12
+ * @typedef {object} IStripeServiceConfig
13
+ * @prop {string} secretKey The Stripe secret key
14
+ * @prop {string} publicKey The Stripe publishable key
15
+ * @prop {boolean} enablePromoCodes Whether to enable promo codes
16
+ * @prop {boolean} enableAutomaticTax Whether to enable automatic tax
17
+ * @prop {string} checkoutSessionSuccessUrl The URL to redirect to after successful checkout
18
+ * @prop {string} checkoutSessionCancelUrl The URL to redirect to if checkout is cancelled
19
+ * @prop {string} checkoutSetupSessionSuccessUrl The URL to redirect to after successful setup session
20
+ * @prop {string} checkoutSetupSessionCancelUrl The URL to redirect to if setup session is cancelled
21
+ * @prop {boolean} testEnv Whether this is a test environment
22
+ * @prop {string} webhookSecret The Stripe webhook secret
23
+ * @prop {string} webhookHandlerUrl The URL to handle Stripe webhooks
24
+ */
25
+
26
+ /**
27
+ * The `StripeService` contains the core logic for Ghost's Stripe integration.
28
+
29
+ */
30
+ module.exports = class StripeService {
31
+ /**
32
+ * @param {object} deps
33
+ * @param {*} deps.labs
34
+ * @param {*} deps.membersService
35
+ * @param {*} deps.donationService
36
+ * @param {*} deps.staffService
37
+ * @param {import('./WebhookManager').StripeWebhook} deps.StripeWebhook
38
+ * @param {object} deps.models
39
+ * @param {object} deps.models.Product
40
+ * @param {object} deps.models.StripePrice
41
+ * @param {object} deps.models.StripeCustomerSubscription
42
+ * @param {object} deps.models.StripeProduct
43
+ * @param {object} deps.models.MemberStripeCustomer
44
+ * @param {object} deps.models.Offer
45
+ * @param {object} deps.models.Settings
46
+ */
47
+ constructor({
48
+ labs,
49
+ membersService,
50
+ donationService,
51
+ staffService,
52
+ StripeWebhook,
53
+ models
54
+ }) {
55
+ const api = new StripeAPI({labs});
56
+ const migrations = new StripeMigrations({
57
+ models,
58
+ api
59
+ });
60
+
61
+ const webhookManager = new WebhookManager({
62
+ StripeWebhook,
63
+ api
64
+ });
65
+
66
+ const subscriptionEventService = new SubscriptionEventService({
67
+ get memberRepository(){
68
+ return membersService.api.members;
69
+ }
70
+ });
71
+
72
+ const invoiceEventService = new InvoiceEventService({
73
+ api,
74
+ get memberRepository(){
75
+ return membersService.api.members;
76
+ },
77
+ get eventRepository(){
78
+ return membersService.api.events;
79
+ },
80
+ get productRepository(){
81
+ return membersService.api.productRepository;
82
+ }
83
+ });
84
+
85
+ const checkoutSessionEventService = new CheckoutSessionEventService({
86
+ api,
87
+ get memberRepository(){
88
+ return membersService.api.members;
89
+ },
90
+ get productRepository(){
91
+ return membersService.api.productRepository;
92
+ },
93
+ get eventRepository(){
94
+ return membersService.api.events;
95
+ },
96
+ get donationRepository(){
97
+ return donationService.repository;
98
+ },
99
+ get staffServiceEmails(){
100
+ return staffService.api.emails;
101
+ },
102
+ sendSignupEmail(email){
103
+ return membersService.api.sendEmailWithMagicLink({
104
+ email,
105
+ requestedType: 'signup-paid',
106
+ options: {
107
+ forceEmailType: true
108
+ },
109
+ tokenData: {}
110
+ });
111
+ }
112
+ });
113
+
114
+ const webhookController = new WebhookController({
115
+ webhookManager,
116
+ subscriptionEventService,
117
+ invoiceEventService,
118
+ checkoutSessionEventService
119
+ });
120
+
121
+ this.models = models;
122
+ this.api = api;
123
+ this.webhookManager = webhookManager;
124
+ this.migrations = migrations;
125
+ this.webhookController = webhookController;
126
+ }
127
+
128
+ async connect() {
129
+ DomainEvents.dispatch(StripeLiveEnabledEvent.create({message: 'Stripe Live Mode Enabled'}));
130
+ }
131
+
132
+ async disconnect() {
133
+ await this.models.Product.forge().query().update({
134
+ monthly_price_id: null,
135
+ yearly_price_id: null
136
+ });
137
+ await this.models.StripePrice.forge().query().del();
138
+ await this.models.StripeProduct.forge().query().del();
139
+ await this.models.MemberStripeCustomer.forge().query().del();
140
+ await this.models.Offer.forge().query().update({
141
+ stripe_coupon_id: null
142
+ });
143
+ await this.webhookManager.stop();
144
+
145
+ this.api.configure(null);
146
+
147
+ DomainEvents.dispatch(StripeLiveDisabledEvent.create({message: 'Stripe Live Mode Disabled'}));
148
+ }
149
+
150
+ /**
151
+ * Configures the Stripe API and registers the webhook with Stripe
152
+ * @param {IStripeServiceConfig} config
153
+ */
154
+ async configure(config) {
155
+ this.api.configure({
156
+ secretKey: config.secretKey,
157
+ publicKey: config.publicKey,
158
+ enablePromoCodes: config.enablePromoCodes,
159
+ get enableAutomaticTax() {
160
+ return config.enableAutomaticTax;
161
+ },
162
+ checkoutSessionSuccessUrl: config.checkoutSessionSuccessUrl,
163
+ checkoutSessionCancelUrl: config.checkoutSessionCancelUrl,
164
+ checkoutSetupSessionSuccessUrl: config.checkoutSetupSessionSuccessUrl,
165
+ checkoutSetupSessionCancelUrl: config.checkoutSetupSessionCancelUrl,
166
+ testEnv: config.testEnv
167
+ });
168
+
169
+ await this.webhookManager.configure({
170
+ webhookSecret: config.webhookSecret,
171
+ webhookHandlerUrl: config.webhookHandlerUrl
172
+ });
173
+ await this.webhookManager.start();
174
+ }
175
+ };
@@ -0,0 +1,100 @@
1
+ const logging = require('@tryghost/logging');
2
+
3
+ module.exports = class WebhookController {
4
+ /**
5
+ * @param {object} deps
6
+ * @param {import('./WebhookManager')} deps.webhookManager
7
+ * @param {import('./services/webhook/CheckoutSessionEventService')} deps.checkoutSessionEventService
8
+ * @param {import('./services/webhook/SubscriptionEventService')} deps.subscriptionEventService
9
+ * @param {import('./services/webhook/InvoiceEventService')} deps.invoiceEventService
10
+ */
11
+ constructor(deps) {
12
+ this.checkoutSessionEventService = deps.checkoutSessionEventService;
13
+ this.subscriptionEventService = deps.subscriptionEventService;
14
+ this.invoiceEventService = deps.invoiceEventService;
15
+ this.webhookManager = deps.webhookManager;
16
+ this.handlers = {
17
+ 'customer.subscription.deleted': this.subscriptionEvent,
18
+ 'customer.subscription.updated': this.subscriptionEvent,
19
+ 'customer.subscription.created': this.subscriptionEvent,
20
+ 'invoice.payment_succeeded': this.invoiceEvent,
21
+ 'checkout.session.completed': this.checkoutSessionEvent
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Handles a Stripe webhook event.
27
+ * - Parses the webhook event
28
+ * - Delegates the event to the appropriate handler
29
+ * - Returns a 200 response to Stripe to confirm receipt of the event, or an error response if the event is not handled or if an error occurs
30
+ * @param {import('express').Request} req
31
+ * @param {import('express').Response} res
32
+ * @returns {Promise<void>}
33
+ */
34
+ async handle(req, res) {
35
+ if (!req.body || !req.headers['stripe-signature']) {
36
+ res.writeHead(400);
37
+ return res.end();
38
+ }
39
+ let event;
40
+ try {
41
+ event = this.webhookManager.parseWebhook(req.body, req.headers['stripe-signature']);
42
+ } catch (err) {
43
+ logging.error(err);
44
+ res.writeHead(401);
45
+ return res.end();
46
+ }
47
+
48
+ logging.info(`Handling webhook ${event.type}`);
49
+ try {
50
+ await this.handleEvent(event);
51
+ res.writeHead(200);
52
+ res.end();
53
+ } catch (err) {
54
+ logging.error(`Error handling webhook ${event.type}`, err);
55
+ res.writeHead(err.statusCode || 500);
56
+ res.end();
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Accepts a webhook's event payload and delegates it to the appropriate handler based on the event type
62
+ * @private
63
+ * @param {import('stripe').Stripe.Event} event
64
+ * @returns {Promise<void>}
65
+ */
66
+ async handleEvent(event) {
67
+ if (!this.handlers[event.type]) {
68
+ return;
69
+ }
70
+
71
+ await this.handlers[event.type].call(this, event.data.object);
72
+ }
73
+
74
+ /**
75
+ * Delegates any `customer.subscription.*` events to the `subscriptionEventService`
76
+ * @param {import('stripe').Stripe.Subscription} subscription
77
+ * @private
78
+ */
79
+ async subscriptionEvent(subscription) {
80
+ await this.subscriptionEventService.handleSubscriptionEvent(subscription);
81
+ }
82
+
83
+ /**
84
+ * Delegates any `invoice.*` events to the `invoiceEventService`
85
+ * @param {import('stripe').Stripe.Invoice} invoice
86
+ * @private
87
+ */
88
+ async invoiceEvent(invoice) {
89
+ await this.invoiceEventService.handleInvoiceEvent(invoice);
90
+ }
91
+
92
+ /**
93
+ * Delegates any `checkout.session.*` events to the `checkoutSessionEventService`
94
+ * @param {import('stripe').Stripe.Checkout.Session} session
95
+ * @private
96
+ */
97
+ async checkoutSessionEvent(session) {
98
+ await this.checkoutSessionEventService.handleEvent(session);
99
+ }
100
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @typedef {import('stripe').Stripe.WebhookEndpointCreateParams.EnabledEvent} WebhookEvent
3
+ * @typedef {import('./StripeAPI')} StripeAPI
4
+ */
5
+
6
+ /**
7
+ * @typedef {object} StripeWebhookModel
8
+ * @prop {string} webhook_id
9
+ * @prop {string} secret
10
+ */
11
+
12
+ /**
13
+ * @typedef {object} StripeWebhook
14
+ * @prop {(data: StripeWebhookModel) => Promise<void>} save
15
+ * @prop {() => Promise<StripeWebhookModel>} get
16
+ */
17
+
18
+ module.exports = class WebhookManager {
19
+ /**
20
+ * @param {object} deps
21
+ * @param {StripeWebhook} deps.StripeWebhook
22
+ * @param {StripeAPI} deps.api
23
+ */
24
+ constructor({
25
+ StripeWebhook,
26
+ api
27
+ }) {
28
+ /** @private */
29
+ this.StripeWebhook = StripeWebhook;
30
+ /** @private */
31
+ this.api = api;
32
+ /** @private */
33
+ this.config = null;
34
+ /** @private */
35
+ this.webhookSecret = null;
36
+ /**
37
+ * @private
38
+ * @type {'network'|'local'}
39
+ */
40
+ this.mode = 'network';
41
+ }
42
+
43
+ /** @type {WebhookEvent[]} */
44
+ static events = [
45
+ 'checkout.session.completed',
46
+ 'customer.subscription.deleted',
47
+ 'customer.subscription.updated',
48
+ 'customer.subscription.created',
49
+ 'invoice.payment_succeeded'
50
+ ];
51
+
52
+ /**
53
+ * Deletes the Stripe Webhook Endpoint and saves null values for the webhook ID and secret.
54
+ *
55
+ * @returns {Promise<boolean>}
56
+ */
57
+ async stop() {
58
+ if (this.mode !== 'network') {
59
+ return;
60
+ }
61
+
62
+ try {
63
+ const existingWebhook = await this.StripeWebhook.get();
64
+ if (existingWebhook.webhook_id) {
65
+ await this.api.deleteWebhookEndpoint(existingWebhook.webhook_id);
66
+ }
67
+ await this.StripeWebhook.save({
68
+ webhook_id: null,
69
+ secret: null
70
+ });
71
+ return true;
72
+ } catch (err) {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Starts the Stripe Webhook Endpoint and saves the webhook ID and secret.
79
+ *
80
+ * @returns {Promise<void>}
81
+ */
82
+ async start() {
83
+ if (this.mode !== 'network') {
84
+ return;
85
+ }
86
+ const existingWebhook = await this.StripeWebhook.get();
87
+
88
+ const webhook = await this.setupWebhook(existingWebhook.webhook_id, existingWebhook.secret);
89
+
90
+ await this.StripeWebhook.save({
91
+ webhook_id: webhook.id,
92
+ secret: webhook.secret
93
+ });
94
+
95
+ this.webhookSecret = webhook.secret;
96
+ }
97
+
98
+ /**
99
+ * Configures the Stripe Webhook Manager.
100
+ * @param {object} config
101
+ * @param {string} [config.webhookSecret] An optional webhook secret for use with stripe-cli, passing this will ensure a webhook is not created in Stripe
102
+ * @param {string} config.webhookHandlerUrl The URL which the Webhook should hit
103
+ *
104
+ * @returns {Promise<void>}
105
+ */
106
+ async configure(config) {
107
+ this.config = config;
108
+ if (config.webhookSecret) {
109
+ this.webhookSecret = config.webhookSecret;
110
+ this.mode = 'local';
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Setup a new Stripe Webhook Endpoint.
116
+ * - If the webhook exists, delete it and create a new one
117
+ * - If the webhook does not exist, create a new one
118
+ *
119
+ * @param {string} [id]
120
+ * @param {string} [secret]
121
+ * @param {object} [opts]
122
+ * @param {boolean} [opts.forceCreate]
123
+ * @param {boolean} [opts.skipDelete]
124
+ *
125
+ * @returns {Promise<{id: string, secret: string}>}
126
+ */
127
+ async setupWebhook(id, secret, opts = {}) {
128
+ if (!id || !secret || opts.forceCreate) {
129
+ if (id && !opts.skipDelete) {
130
+ try {
131
+ await this.api.deleteWebhookEndpoint(id);
132
+ } catch (err) {
133
+ // Continue
134
+ }
135
+ }
136
+ const webhook = await this.api.createWebhookEndpoint(
137
+ this.config.webhookHandlerUrl,
138
+ WebhookManager.events
139
+ );
140
+ return {
141
+ id: webhook.id,
142
+ secret: webhook.secret
143
+ };
144
+ } else {
145
+ try {
146
+ await this.api.updateWebhookEndpoint(
147
+ id,
148
+ this.config.webhookHandlerUrl,
149
+ WebhookManager.events
150
+ );
151
+
152
+ return {
153
+ id,
154
+ secret
155
+ };
156
+ } catch (err) {
157
+ if (err.code === 'resource_missing') {
158
+ return this.setupWebhook(id, secret, {skipDelete: true, forceCreate: true});
159
+ }
160
+ return this.setupWebhook(id, secret, {skipDelete: false, forceCreate: true});
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Parse a Stripe Webhook event.
167
+ *
168
+ * @param {string} body
169
+ * @param {string} signature
170
+ * @returns {import('stripe').Stripe.Event}
171
+ */
172
+ parseWebhook(body, signature) {
173
+ return this.api.parseWebhook(body, signature, this.webhookSecret);
174
+ }
175
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @typedef {object} StripeLiveDisabledEventData
3
+ * @prop {string?} message
4
+ */
5
+
6
+ module.exports = class StripeLiveDisabledEvent {
7
+ /**
8
+ * @param {StripeLiveDisabledEventData} data
9
+ * @param {Date} timestamp
10
+ */
11
+ constructor(data, timestamp) {
12
+ this.data = data;
13
+ this.timestamp = timestamp;
14
+ }
15
+
16
+ /**
17
+ * @param {StripeLiveDisabledEventData} data
18
+ * @param {Date} [timestamp]
19
+ */
20
+ static create(data, timestamp) {
21
+ return new StripeLiveDisabledEvent(data, timestamp || new Date);
22
+ }
23
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @typedef {object} StripeLiveEnabledEventData
3
+ * @prop {string} message
4
+ */
5
+
6
+ module.exports = class StripeLiveEnabledEvent {
7
+ /**
8
+ * @param {StripeLiveEnabledEventData} data
9
+ * @param {Date} timestamp
10
+ */
11
+ constructor(data, timestamp) {
12
+ this.data = data;
13
+ this.timestamp = timestamp;
14
+ }
15
+
16
+ /**
17
+ * @param {StripeLiveEnabledEventData} data
18
+ * @param {Date} [timestamp]
19
+ */
20
+ static create(data, timestamp) {
21
+ return new StripeLiveEnabledEvent(data, timestamp || new Date);
22
+ }
23
+ };
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ StripeLiveEnabledEvent: require('./StripeLiveEnabledEvent'),
3
+ StripeLiveDisabledEvent: require('./StripeLiveDisabledEvent')
4
+ };
@@ -1,5 +1,5 @@
1
1
  const _ = require('lodash');
2
- const StripeService = require('@tryghost/members-stripe-service');
2
+ const StripeService = require('./StripeService');
3
3
  const logging = require('@tryghost/logging');
4
4
  const membersService = require('../members');
5
5
  const config = require('../../../shared/config');