ghost 5.114.1 → 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.
- package/components/tryghost-adapter-cache-redis-5.115.1.tgz +0 -0
- package/components/{tryghost-adapter-manager-5.114.1.tgz → tryghost-adapter-manager-5.115.1.tgz} +0 -0
- package/components/{tryghost-announcement-bar-settings-5.114.1.tgz → tryghost-announcement-bar-settings-5.115.1.tgz} +0 -0
- package/components/{tryghost-api-framework-5.114.1.tgz → tryghost-api-framework-5.115.1.tgz} +0 -0
- package/components/tryghost-constants-5.115.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.115.1.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.114.1.tgz → tryghost-custom-theme-settings-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-data-generator-5.114.1.tgz → tryghost-data-generator-5.115.1.tgz} +0 -0
- package/components/{tryghost-domain-events-5.114.1.tgz → tryghost-domain-events-5.115.1.tgz} +0 -0
- package/components/tryghost-donations-5.115.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.115.1.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.114.1.tgz → tryghost-email-content-generator-5.115.1.tgz} +0 -0
- package/components/tryghost-email-events-5.115.1.tgz +0 -0
- package/components/tryghost-email-service-5.115.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.115.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.115.1.tgz +0 -0
- package/components/tryghost-ghost-5.115.1.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.114.1.tgz → tryghost-html-to-plaintext-5.115.1.tgz} +0 -0
- package/components/tryghost-i18n-5.115.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.115.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.115.1.tgz +0 -0
- package/components/{tryghost-job-manager-5.114.1.tgz → tryghost-job-manager-5.115.1.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.114.1.tgz → tryghost-link-redirects-5.115.1.tgz} +0 -0
- package/components/tryghost-link-replacer-5.115.1.tgz +0 -0
- package/components/{tryghost-magic-link-5.114.1.tgz → tryghost-magic-link-5.115.1.tgz} +0 -0
- package/components/{tryghost-mailgun-client-5.114.1.tgz → tryghost-mailgun-client-5.115.1.tgz} +0 -0
- package/components/tryghost-member-attribution-5.115.1.tgz +0 -0
- package/components/{tryghost-member-events-5.114.1.tgz → tryghost-member-events-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-api-5.114.1.tgz → tryghost-members-api-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-csv-5.114.1.tgz → tryghost-members-csv-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-offers-5.114.1.tgz → tryghost-members-offers-5.115.1.tgz} +0 -0
- package/components/{tryghost-members-payments-5.114.1.tgz → tryghost-members-payments-5.115.1.tgz} +0 -0
- package/components/{tryghost-milestones-5.114.1.tgz → tryghost-milestones-5.115.1.tgz} +0 -0
- package/components/tryghost-minifier-5.115.1.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.114.1.tgz → tryghost-mw-error-handler-5.115.1.tgz} +0 -0
- package/components/{tryghost-mw-version-match-5.114.1.tgz → tryghost-mw-version-match-5.115.1.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.115.1.tgz +0 -0
- package/components/{tryghost-post-events-5.114.1.tgz → tryghost-post-events-5.115.1.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.114.1.tgz → tryghost-post-revisions-5.115.1.tgz} +0 -0
- package/components/{tryghost-posts-service-5.114.1.tgz → tryghost-posts-service-5.115.1.tgz} +0 -0
- package/components/{tryghost-prometheus-metrics-5.114.1.tgz → tryghost-prometheus-metrics-5.115.1.tgz} +0 -0
- package/components/tryghost-recommendations-5.115.1.tgz +0 -0
- package/components/{tryghost-security-5.114.1.tgz → tryghost-security-5.115.1.tgz} +0 -0
- package/components/tryghost-slack-notifications-5.115.1.tgz +0 -0
- package/components/{tryghost-tiers-5.114.1.tgz → tryghost-tiers-5.115.1.tgz} +0 -0
- package/components/{tryghost-webmentions-5.114.1.tgz → tryghost-webmentions-5.115.1.tgz} +0 -0
- package/content/themes/casper/LICENSE +1 -1
- package/content/themes/casper/README.md +1 -1
- package/content/themes/source/LICENSE +1 -1
- package/content/themes/source/README.md +1 -1
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/built/screen.css.map +1 -1
- package/content/themes/source/assets/css/screen.css +11 -6
- package/content/themes/source/partials/feature-image.hbs +2 -2
- package/core/boot.js +3 -1
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +23497 -23041
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-demo/{index-0040480a.mjs → index-15df2af5.mjs} +4 -3
- package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-8ca61d78.mjs} +67 -65
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.mjs → CodeEditorView-d2e6872f.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{index-376f847c.mjs → index-8e8821e5.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f5cb3db3.mjs} +3104 -3094
- package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-e8ae4d46.mjs} +3 -3
- package/core/built/admin/assets/{chunk.524.85c5b32bd46b91c147b9.js → chunk.524.2439684964c164c598ab.js} +7 -7
- package/core/built/admin/assets/{chunk.582.449a129a8005f03574bd.js → chunk.582.bf5a2bbb2c4eb69ef1e7.js} +10 -10
- package/core/built/admin/assets/ghost-327b17ea23cb8c89bd7e6a51e18e8506.css +1 -0
- package/core/built/admin/assets/ghost-dark-f30a597ac19632a118939492591c531b.css +1 -0
- package/core/built/admin/assets/{ghost-c563138cc2c0767bf6eefc9a2587eaa4.js → ghost-df7b9558260aa27d18b195ee895b487d.js} +182 -160
- package/core/built/admin/assets/stats/stats.js +11824 -0
- package/core/built/admin/index.html +4 -4
- package/core/frontend/helpers/ghost_head.js +3 -1
- package/core/frontend/src/cards/css/cta.css +1 -1
- package/core/server/api/endpoints/slugs.js +6 -2
- package/core/server/data/importer/import-manager.js +2 -2
- package/core/server/data/importer/importers/importer-revue.js +128 -0
- package/core/server/data/importer/importers/json-to-html.js +107 -0
- package/core/server/data/migrations/utils/tables.js +2 -4
- package/core/server/data/migrations/versions/5.115/2025-03-24-07-19-27-add-identity-read-permission-to-administrators.js +6 -0
- package/core/server/data/schema/fixtures/fixtures.json +2 -1
- package/core/server/lib/bootstrap-socket.js +87 -0
- package/core/server/lib/package-json/index.js +1 -0
- package/core/server/lib/package-json/package-json.js +160 -0
- package/core/server/lib/package-json/parse.js +57 -0
- package/core/server/models/base/plugins/actions.js +44 -31
- package/core/server/models/base/plugins/generate-slug.js +6 -0
- package/core/server/notify.js +1 -1
- package/core/server/services/activitypub/ActivityPubService.ts +1 -1
- package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +99 -0
- package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +80 -0
- package/core/server/services/api-version-compatibility/extract-api-key.js +57 -0
- package/core/server/services/api-version-compatibility/index.js +2 -2
- package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +31 -0
- package/core/server/services/audience-feedback/AudienceFeedbackController.js +85 -0
- package/core/server/services/audience-feedback/AudienceFeedbackService.js +34 -0
- package/core/server/services/audience-feedback/Feedback.js +35 -0
- package/core/server/services/audience-feedback/index.js +4 -2
- package/core/server/services/auth/session/emails/signin.js +168 -0
- package/core/server/services/auth/session/index.js +2 -2
- package/core/server/services/auth/session/session-from-token.js +69 -0
- package/core/server/services/auth/session/session-service.js +364 -0
- package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +62 -0
- package/core/server/services/email-analytics/EmailAnalyticsService.js +552 -0
- package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +3 -3
- package/core/server/services/email-analytics/EventProcessingResult.js +66 -0
- package/core/server/services/explore-ping/ExplorePingService.js +106 -0
- package/core/server/services/explore-ping/index.js +31 -0
- package/core/server/services/identity-tokens/IdentityTokenService.js +30 -0
- package/core/server/services/identity-tokens/IdentityTokenService.ts +28 -0
- package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +1 -1
- package/core/server/services/invitations/accept.js +5 -2
- package/core/server/services/mail-events/BookshelfMailEventRepository.js +2 -2
- package/core/server/services/mail-events/InMemoryMailEventRepository.js +10 -0
- package/core/server/services/mail-events/InMemoryMailEventRepository.ts +8 -0
- package/core/server/services/mail-events/MailEvent.js +20 -0
- package/core/server/services/mail-events/MailEvent.ts +10 -0
- package/core/server/services/mail-events/MailEventRepository.js +2 -0
- package/core/server/services/mail-events/MailEventRepository.ts +5 -0
- package/core/server/services/mail-events/MailEventService.js +124 -0
- package/core/server/services/mail-events/MailEventService.ts +169 -0
- package/core/server/services/mail-events/index.js +1 -1
- package/core/server/services/mail-events/libraries.d.ts +2 -0
- package/core/server/services/members/CaptchaService.js +80 -0
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/importer/MembersCSVImporter.js +464 -0
- package/core/server/services/members/importer/MembersCSVImporterStripeUtils.js +194 -0
- package/core/server/services/members/importer/email-template.js +182 -0
- package/core/server/services/members/importer/index.js +30 -0
- package/core/server/services/members/members-ssr.js +333 -0
- package/core/server/services/members/service.js +2 -2
- package/core/server/services/posts/stats/PostStats.js +13 -0
- package/core/server/services/route-settings/SettingsPathManager.js +47 -0
- package/core/server/services/route-settings/index.js +1 -1
- package/core/server/services/stripe/README.md +63 -0
- package/core/server/services/stripe/StripeAPI.js +931 -0
- package/core/server/services/stripe/StripeMigrations.js +613 -0
- package/core/server/services/stripe/StripeService.js +175 -0
- package/core/server/services/stripe/WebhookController.js +100 -0
- package/core/server/services/stripe/WebhookManager.js +175 -0
- package/core/server/services/stripe/events/StripeLiveDisabledEvent.js +23 -0
- package/core/server/services/stripe/events/StripeLiveEnabledEvent.js +23 -0
- package/core/server/services/stripe/events/index.js +4 -0
- package/core/server/services/stripe/service.js +1 -1
- package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +255 -0
- package/core/server/services/stripe/services/webhook/InvoiceEventService.js +70 -0
- package/core/server/services/stripe/services/webhook/SubscriptionEventService.js +54 -0
- package/core/server/services/themes/loader.js +1 -1
- package/core/server/services/themes/to-json.js +1 -1
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- package/core/server/web/shared/middleware/cache-control.js +51 -0
- package/core/server/web/shared/middleware/index.js +1 -1
- package/core/server/web/well-known.js +1 -1
- package/core/shared/labs.js +3 -1
- package/core/shared/settings-cache/CacheManager.js +64 -6
- package/package.json +103 -134
- package/tsconfig.tsbuildinfo +1 -1
- package/yarn.lock +7 -93
- package/components/tryghost-adapter-cache-redis-5.114.1.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.114.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.114.1.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.114.1.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.114.1.tgz +0 -0
- package/components/tryghost-captcha-service-5.114.1.tgz +0 -0
- package/components/tryghost-constants-5.114.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.114.1.tgz +0 -0
- package/components/tryghost-donations-5.114.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.114.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.114.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.114.1.tgz +0 -0
- package/components/tryghost-email-events-5.114.1.tgz +0 -0
- package/components/tryghost-email-service-5.114.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.114.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.114.1.tgz +0 -0
- package/components/tryghost-extract-api-key-5.114.1.tgz +0 -0
- package/components/tryghost-ghost-5.114.1.tgz +0 -0
- package/components/tryghost-i18n-5.114.1.tgz +0 -0
- package/components/tryghost-identity-token-service-5.114.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.114.1.tgz +0 -0
- package/components/tryghost-importer-revue-5.114.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.114.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.114.1.tgz +0 -0
- package/components/tryghost-mail-events-5.114.1.tgz +0 -0
- package/components/tryghost-member-attribution-5.114.1.tgz +0 -0
- package/components/tryghost-members-importer-5.114.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.114.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.114.1.tgz +0 -0
- package/components/tryghost-minifier-5.114.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.114.1.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.114.1.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.114.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.114.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.114.1.tgz +0 -0
- package/components/tryghost-package-json-5.114.1.tgz +0 -0
- package/components/tryghost-recommendations-5.114.1.tgz +0 -0
- package/components/tryghost-referrers-5.114.1.tgz +0 -0
- package/components/tryghost-session-service-5.114.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.114.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.114.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.114.1.tgz +0 -0
- package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
- package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.css +0 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const {DataImportError} = require('@tryghost/errors');
|
|
2
|
+
const tpl = require('@tryghost/tpl');
|
|
3
|
+
|
|
4
|
+
const messages = {
|
|
5
|
+
productNotFound: 'Cannot find Product {id}',
|
|
6
|
+
noStripeConnection: 'Cannot {action} without a Stripe Connection',
|
|
7
|
+
forceNoCustomer: 'Cannot find Stripe customer to update subscription',
|
|
8
|
+
forceNoExistingSubscription: 'Cannot update subscription when customer does not have an existing subscription',
|
|
9
|
+
forceTooManySubscriptions: 'Cannot update subscription when customer has multiple subscriptions',
|
|
10
|
+
forceTooManySubscriptionItems: 'Cannot update subscription when existing subscription has multiple items',
|
|
11
|
+
forceExistingSubscriptionNotRecurring: 'Cannot update subscription when existing subscription is not recurring'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports = class MembersCSVImporterStripeUtils {
|
|
15
|
+
/**
|
|
16
|
+
* @param {Object} stripeAPIService
|
|
17
|
+
* @param {Object} productRepository
|
|
18
|
+
*/
|
|
19
|
+
constructor({
|
|
20
|
+
stripeAPIService,
|
|
21
|
+
productRepository
|
|
22
|
+
}) {
|
|
23
|
+
this._stripeAPIService = stripeAPIService;
|
|
24
|
+
this._productRepository = productRepository;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Force a Stripe customer to be subscribed to a specific Ghost product
|
|
29
|
+
*
|
|
30
|
+
* This will either:
|
|
31
|
+
*
|
|
32
|
+
* Create a new price on the Stripe product that is associated with the Ghost product, then update
|
|
33
|
+
* the customer's Stripe subscription to use the new price. The new price will be created with the details of the
|
|
34
|
+
* existing price of the item in customer's Stripe subscription
|
|
35
|
+
*
|
|
36
|
+
* or
|
|
37
|
+
*
|
|
38
|
+
* Update the customer's stripe subscription to use an existing price on the Stripe product that matches the
|
|
39
|
+
* details of the existing price of the item in customer's Stripe subscription
|
|
40
|
+
*
|
|
41
|
+
* If there is no Stripe product associated with the Ghost product, one will be created
|
|
42
|
+
*
|
|
43
|
+
* This method should be used in-conjunction with `MembersRepository.linkSubscription` to ensure
|
|
44
|
+
* that the changes made in Stripe are reflected in Ghost - This is not executed as part of this to allow for
|
|
45
|
+
* flexibility and reduce duplication
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} data
|
|
48
|
+
* @param {String} data.customer_id - Stripe customer ID
|
|
49
|
+
* @param {String} data.product_id - Ghost product ID
|
|
50
|
+
* @param {Object} options
|
|
51
|
+
* @returns {Promise<Object>}
|
|
52
|
+
*/
|
|
53
|
+
async forceStripeSubscriptionToProduct(data, options) {
|
|
54
|
+
if (!this._stripeAPIService.configured) {
|
|
55
|
+
throw new DataImportError({
|
|
56
|
+
message: tpl(messages.noStripeConnection, {action: 'force subscription to product'})
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Retrieve customer's existing subscription information
|
|
61
|
+
const stripeCustomer = await this._stripeAPIService.getCustomer(data.customer_id);
|
|
62
|
+
|
|
63
|
+
// Subscription can only be forced if the customer exists
|
|
64
|
+
if (!stripeCustomer) {
|
|
65
|
+
throw new DataImportError({message: tpl(messages.forceNoCustomer)});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Subscription can only be forced if the customer has an existing subscription
|
|
69
|
+
if (stripeCustomer.subscriptions.data.length === 0) {
|
|
70
|
+
throw new DataImportError({message: tpl(messages.forceNoExistingSubscription)});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Subscription can only be forced if the customer does not have multiple subscriptions
|
|
74
|
+
if (stripeCustomer.subscriptions.data.length > 1) {
|
|
75
|
+
throw new DataImportError({message: tpl(messages.forceTooManySubscriptions)});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const stripeSubscription = stripeCustomer.subscriptions.data[0];
|
|
79
|
+
|
|
80
|
+
// Subscription can only be forced if the existing subscription does not have multiple items
|
|
81
|
+
if (stripeSubscription.items.data.length > 1) {
|
|
82
|
+
throw new DataImportError({message: tpl(messages.forceTooManySubscriptionItems)});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const stripeSubscriptionItem = stripeSubscription.items.data[0];
|
|
86
|
+
const stripeSubscriptionItemPrice = stripeSubscriptionItem.price;
|
|
87
|
+
const stripeSubscriptionItemPriceCurrency = stripeSubscriptionItemPrice.currency;
|
|
88
|
+
const stripeSubscriptionItemPriceAmount = stripeSubscriptionItemPrice.unit_amount;
|
|
89
|
+
const stripeSubscriptionItemPriceType = stripeSubscriptionItemPrice.type;
|
|
90
|
+
const stripeSubscriptionItemPriceInterval = stripeSubscriptionItemPrice.recurring?.interval || null;
|
|
91
|
+
|
|
92
|
+
// Subscription can only be forced if the existing subscription has a recurring interval
|
|
93
|
+
if (!stripeSubscriptionItemPriceInterval) {
|
|
94
|
+
throw new DataImportError({message: tpl(messages.forceExistingSubscriptionNotRecurring)});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Retrieve Ghost product
|
|
98
|
+
let ghostProduct = await this._productRepository.get(
|
|
99
|
+
{id: data.product_id},
|
|
100
|
+
{...options, withRelated: ['stripePrices', 'stripeProducts']}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (!ghostProduct) {
|
|
104
|
+
throw new DataImportError({message: tpl(messages.productNotFound, {id: data.product_id})});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// If there is not a Stripe product associated with the Ghost product, ensure one is created before continuing
|
|
108
|
+
if (!ghostProduct.related('stripeProducts').first()) {
|
|
109
|
+
// Even though we are not updating any information on the product, calling `ProductRepository.update`
|
|
110
|
+
// will ensure that the product gets created in Stripe
|
|
111
|
+
ghostProduct = await this._productRepository.update({
|
|
112
|
+
id: data.product_id,
|
|
113
|
+
name: ghostProduct.get('name'),
|
|
114
|
+
// Providing the pricing details will ensure the relevant prices for the Ghost product are created
|
|
115
|
+
// on the Stripe product
|
|
116
|
+
monthly_price: {
|
|
117
|
+
amount: ghostProduct.get('monthly_price'),
|
|
118
|
+
currency: ghostProduct.get('currency')
|
|
119
|
+
},
|
|
120
|
+
yearly_price: {
|
|
121
|
+
amount: ghostProduct.get('yearly_price'),
|
|
122
|
+
currency: ghostProduct.get('currency')
|
|
123
|
+
}
|
|
124
|
+
}, options);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Find price on Ghost product matching stripe subscription item price details
|
|
128
|
+
const ghostProductPrice = ghostProduct.related('stripePrices').find((price) => {
|
|
129
|
+
return price.get('currency') === stripeSubscriptionItemPriceCurrency &&
|
|
130
|
+
price.get('amount') === stripeSubscriptionItemPriceAmount &&
|
|
131
|
+
price.get('type') === stripeSubscriptionItemPriceType &&
|
|
132
|
+
price.get('interval') === stripeSubscriptionItemPriceInterval;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
let stripePriceId;
|
|
136
|
+
let isNewStripePrice = false;
|
|
137
|
+
|
|
138
|
+
if (!ghostProductPrice) {
|
|
139
|
+
// If there is not a matching price, create one on the associated Stripe product using the existing
|
|
140
|
+
// subscription item price details and update the stripe subscription to use it
|
|
141
|
+
const stripeProduct = ghostProduct.related('stripeProducts').first();
|
|
142
|
+
|
|
143
|
+
const newStripePrice = await this._stripeAPIService.createPrice({
|
|
144
|
+
product: stripeProduct.get('stripe_product_id'),
|
|
145
|
+
active: true,
|
|
146
|
+
nickname: stripeSubscriptionItemPriceInterval === 'month' ? 'Monthly' : 'Yearly',
|
|
147
|
+
currency: stripeSubscriptionItemPriceCurrency,
|
|
148
|
+
amount: stripeSubscriptionItemPriceAmount,
|
|
149
|
+
type: stripeSubscriptionItemPriceType,
|
|
150
|
+
interval: stripeSubscriptionItemPriceInterval
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await this._stripeAPIService.updateSubscriptionItemPrice(
|
|
154
|
+
stripeSubscription.id,
|
|
155
|
+
stripeSubscriptionItem.id,
|
|
156
|
+
newStripePrice.id,
|
|
157
|
+
{prorationBehavior: 'none'}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
stripePriceId = newStripePrice.id;
|
|
161
|
+
isNewStripePrice = true;
|
|
162
|
+
} else {
|
|
163
|
+
// If there is a matching price, and the subscription is not already using it,
|
|
164
|
+
// update the subscription to use it
|
|
165
|
+
stripePriceId = ghostProductPrice.get('stripe_price_id');
|
|
166
|
+
|
|
167
|
+
if (stripeSubscriptionItem.price.id !== stripePriceId) {
|
|
168
|
+
await this._stripeAPIService.updateSubscriptionItemPrice(
|
|
169
|
+
stripeSubscription.id,
|
|
170
|
+
stripeSubscriptionItem.id,
|
|
171
|
+
stripePriceId,
|
|
172
|
+
{prorationBehavior: 'none'}
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// If there is a matching price, and the subscription is already using it, nothing else needs to be done
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
stripePriceId,
|
|
181
|
+
isNewStripePrice
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Archive a price in Stripe
|
|
187
|
+
*
|
|
188
|
+
* @param {Number} stripePriceId
|
|
189
|
+
* @returns {Promise<void>}
|
|
190
|
+
*/
|
|
191
|
+
async archivePrice(stripePriceId) {
|
|
192
|
+
await this._stripeAPIService.updatePrice(stripePriceId, {active: false});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
function formatNumber(number) {
|
|
2
|
+
return number.toLocaleString();
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const iff = (cond, yes, no) => (cond ? yes : no);
|
|
6
|
+
module.exports = ({result, siteUrl, membersUrl, emailRecipient}) => `
|
|
7
|
+
<!doctype html>
|
|
8
|
+
<html>
|
|
9
|
+
<head>
|
|
10
|
+
<meta name="viewport" content="width=device-width">
|
|
11
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
12
|
+
<title>Your member import is complete</title>
|
|
13
|
+
<style>
|
|
14
|
+
/* -------------------------------------
|
|
15
|
+
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
|
16
|
+
------------------------------------- */
|
|
17
|
+
@media only screen and (max-width: 620px) {
|
|
18
|
+
table[class=body] h1 {
|
|
19
|
+
font-size: 28px !important;
|
|
20
|
+
margin-bottom: 10px !important;
|
|
21
|
+
}
|
|
22
|
+
table[class=body] p,
|
|
23
|
+
table[class=body] ul,
|
|
24
|
+
table[class=body] ol,
|
|
25
|
+
table[class=body] td,
|
|
26
|
+
table[class=body] span,
|
|
27
|
+
table[class=body] a {
|
|
28
|
+
font-size: 16px !important;
|
|
29
|
+
}
|
|
30
|
+
table[class=body] .title {
|
|
31
|
+
font-size: 22px !important;
|
|
32
|
+
}
|
|
33
|
+
table[class=body] .wrapper,
|
|
34
|
+
table[class=body] .article {
|
|
35
|
+
padding: 10px !important;
|
|
36
|
+
}
|
|
37
|
+
table[class=body] .content {
|
|
38
|
+
padding: 0 !important;
|
|
39
|
+
}
|
|
40
|
+
table[class=body] .container {
|
|
41
|
+
padding: 0 !important;
|
|
42
|
+
width: 100% !important;
|
|
43
|
+
}
|
|
44
|
+
table[class=body] .main {
|
|
45
|
+
border-left-width: 0 !important;
|
|
46
|
+
border-radius: 0 !important;
|
|
47
|
+
border-right-width: 0 !important;
|
|
48
|
+
}
|
|
49
|
+
table[class=body] .btn table {
|
|
50
|
+
width: 100% !important;
|
|
51
|
+
}
|
|
52
|
+
table[class=body] .btn a {
|
|
53
|
+
width: 100% !important;
|
|
54
|
+
}
|
|
55
|
+
table[class=body] .img-responsive {
|
|
56
|
+
height: auto !important;
|
|
57
|
+
max-width: 100% !important;
|
|
58
|
+
width: auto !important;
|
|
59
|
+
}
|
|
60
|
+
table[class=body] p[class=small],
|
|
61
|
+
table[class=body] a[class=small] {
|
|
62
|
+
font-size: 12x !important;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/* -------------------------------------
|
|
66
|
+
PRESERVE THESE STYLES IN THE HEAD
|
|
67
|
+
------------------------------------- */
|
|
68
|
+
@media all {
|
|
69
|
+
.ExternalClass {
|
|
70
|
+
width: 100%;
|
|
71
|
+
}
|
|
72
|
+
.ExternalClass,
|
|
73
|
+
.ExternalClass p,
|
|
74
|
+
.ExternalClass span,
|
|
75
|
+
.ExternalClass font,
|
|
76
|
+
.ExternalClass td,
|
|
77
|
+
.ExternalClass div {
|
|
78
|
+
line-height: 100%;
|
|
79
|
+
}
|
|
80
|
+
.recipient-link a {
|
|
81
|
+
color: inherit !important;
|
|
82
|
+
font-family: inherit !important;
|
|
83
|
+
font-size: inherit !important;
|
|
84
|
+
font-weight: inherit !important;
|
|
85
|
+
line-height: inherit !important;
|
|
86
|
+
text-decoration: none !important;
|
|
87
|
+
}
|
|
88
|
+
#MessageViewBody a {
|
|
89
|
+
color: inherit;
|
|
90
|
+
text-decoration: none;
|
|
91
|
+
font-size: inherit;
|
|
92
|
+
font-family: inherit;
|
|
93
|
+
font-weight: inherit;
|
|
94
|
+
line-height: inherit;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
hr {
|
|
98
|
+
border-width: 0;
|
|
99
|
+
height: 0;
|
|
100
|
+
margin-top: 34px;
|
|
101
|
+
margin-bottom: 34px;
|
|
102
|
+
border-bottom-width: 1px;
|
|
103
|
+
border-bottom-color: #EEF5F8;
|
|
104
|
+
}
|
|
105
|
+
a {
|
|
106
|
+
color: #3A464C;
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body class="" style="background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.5em; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
|
|
111
|
+
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
|
112
|
+
<tr>
|
|
113
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td>
|
|
114
|
+
<td class="container" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto; max-width: 540px; padding: 10px; width: 540px;">
|
|
115
|
+
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 600px; padding: 30px 20px;">
|
|
116
|
+
|
|
117
|
+
<!-- START CENTERED CONTAINER -->
|
|
118
|
+
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Your member import is complete</span>
|
|
119
|
+
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 8px;">
|
|
120
|
+
|
|
121
|
+
<!-- START MAIN CONTENT AREA -->
|
|
122
|
+
<tr>
|
|
123
|
+
<td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box;">
|
|
124
|
+
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
|
125
|
+
<tr>
|
|
126
|
+
<td align="center" style="padding-top: 20px; padding-bottom: 12px;"><img src="https://static.ghost.org/v4.0.0/images/ghost-orb-4.png" width="60" height="60" style="width: 60px; height: 60px;" /></td>
|
|
127
|
+
</tr>
|
|
128
|
+
<tr>
|
|
129
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; vertical-align: top;">
|
|
130
|
+
<p class="title" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 21px; color: #3A464C; font-weight: normal; line-height: 25px; margin-bottom: 30px; margin-top: 50px; font-weight: 600; color: #15212A;">${iff(result.imported > 0, `Your member import is complete`, `Your member import was unsuccessful`)}</p>
|
|
131
|
+
</td>
|
|
132
|
+
</tr>
|
|
133
|
+
${iff(result.imported === 0 && result.errors.length === 0, `
|
|
134
|
+
<tr>
|
|
135
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-bottom: 16px;">
|
|
136
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 0px;">No members were added.</p>
|
|
137
|
+
</td>
|
|
138
|
+
</tr>
|
|
139
|
+
`, ``)}
|
|
140
|
+
${iff(result.imported > 0, `
|
|
141
|
+
<tr>
|
|
142
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-bottom: 16px;">
|
|
143
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 0px;">A total of <strong style="font-weight: 600;">${formatNumber(result.imported)}</strong> ${iff(result.imported === 1, 'person', 'people')} were successfully added or updated in your list of members, and now have access to your site.</p>
|
|
144
|
+
</td>
|
|
145
|
+
</tr>`, ``)}
|
|
146
|
+
${iff(result.errors.length > 0, `
|
|
147
|
+
<tr>
|
|
148
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-bottom: 16px;">
|
|
149
|
+
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; margin: 0; line-height: 25px; margin-bottom: 0px;">
|
|
150
|
+
${iff(result.imported === 0, `No members were added.`, `<strong style="font-weight: 600;">${formatNumber(result.errors.length)}</strong> ${iff(result.errors.length === 1, `member was`, `members were`)} skipped due to errors.`)} There's a validated CSV file attached to this email with the list of errors so that you can fix them and re-upload the CSV to complete the import.</p>
|
|
151
|
+
</td>
|
|
152
|
+
</tr>`, '')}
|
|
153
|
+
<tr>
|
|
154
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-bottom: 12px; padding-top: 16px;">
|
|
155
|
+
<a href="${membersUrl.href}" target="_blank" style="display: inline-block; color: #ffffff; background-color: #15212A; border: solid 1px #15212A; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 16px; font-weight: normal; margin: 0; padding: 9px 22px 10px; border-color: #15212A;">${iff(result.imported > 0, `View members`, `Try again`)}</a>
|
|
156
|
+
</td>
|
|
157
|
+
</tr>
|
|
158
|
+
</table>
|
|
159
|
+
</td>
|
|
160
|
+
</tr>
|
|
161
|
+
<tr>
|
|
162
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; vertical-align: top; padding-top: 80px; padding-bottom: 10px;">
|
|
163
|
+
<div class="footer">
|
|
164
|
+
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; color: #738A94; font-weight: normal; margin: 0; line-height: 18px; margin-bottom: 0px; font-size: 11px;">This email was sent from <a href="${siteUrl.href}" style="color: #738A94;">${siteUrl.host}</a> to <a href="mailto:${emailRecipient}" style="color: #738A94;">${emailRecipient}</a></p>
|
|
165
|
+
</div>
|
|
166
|
+
</td>
|
|
167
|
+
</tr>
|
|
168
|
+
|
|
169
|
+
<!-- END MAIN CONTENT AREA -->
|
|
170
|
+
</table>
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
<!-- END CENTERED CONTAINER -->
|
|
174
|
+
</div>
|
|
175
|
+
</td>
|
|
176
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td>
|
|
177
|
+
</tr>
|
|
178
|
+
</table>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
`;
|
|
182
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const MembersCSVImporter = require('./MembersCSVImporter');
|
|
2
|
+
const MembersCSVImporterStripeUtils = require('./MembersCSVImporterStripeUtils');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('./MembersCSVImporter').MembersCSVImporterOptions} MembersCSVImporterOptions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} MakeImporterDeps
|
|
10
|
+
* @property {Object} stripeAPIService - Instance of StripeAPIService
|
|
11
|
+
* @property {Object} productRepository - Instance of ProductRepository
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Make an instance of MembersCSVImporter
|
|
16
|
+
*
|
|
17
|
+
* @param {MakeImporterDeps & MembersCSVImporterOptions} deps
|
|
18
|
+
* @returns {MembersCSVImporter}
|
|
19
|
+
*/
|
|
20
|
+
module.exports = function makeImporter(deps) {
|
|
21
|
+
const stripeUtils = new MembersCSVImporterStripeUtils({
|
|
22
|
+
stripeAPIService: deps.stripeAPIService,
|
|
23
|
+
productRepository: deps.productRepository
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return new MembersCSVImporter({
|
|
27
|
+
...deps,
|
|
28
|
+
stripeUtils
|
|
29
|
+
});
|
|
30
|
+
};
|