diff --git a/ghost/admin/app/components/gh-member-settings-form.hbs b/ghost/admin/app/components/gh-member-settings-form.hbs
index f16e5f9289..dfcdd73ae1 100644
--- a/ghost/admin/app/components/gh-member-settings-form.hbs
+++ b/ghost/admin/app/components/gh-member-settings-form.hbs
@@ -170,16 +170,24 @@
Cancellation reason: {{sub.cancellationReason}}
{{/if}}
{{#if sub.offer}}
-
- {{sub.offer.name}}
- offer
- {{#if (eq sub.offer.type 'fixed')}}
- ({{currency-symbol sub.offer.currency}}{{gh-price-amount sub.offer.amount}} off)
- {{else}}
- ({{sub.offer.amount}}% off)
- {{/if}}
- applied to subscription
-
+ {{#if (eq sub.offer.type "trial")}}
+
+ {{sub.offer.name}}
+ offer
+ ({{sub.offer.amount}} days free)
+
+ {{else}}
+
+ {{sub.offer.name}}
+ offer
+ {{#if (eq sub.offer.type 'fixed')}}
+ ({{currency-symbol sub.offer.currency}}{{gh-price-amount sub.offer.amount}} off)
+ {{else}}
+ ({{sub.offer.amount}}% off)
+ {{/if}}
+ applied to subscription
+
+ {{/if}}
{{/if}}
Created on {{sub.startDate}}
diff --git a/ghost/members-api/lib/repositories/member.js b/ghost/members-api/lib/repositories/member.js
index 80c9f8a4a9..ebc9a4bb01 100644
--- a/ghost/members-api/lib/repositories/member.js
+++ b/ghost/members-api/lib/repositories/member.js
@@ -327,7 +327,7 @@ module.exports = class MemberRepository {
if (memberData.bio) {
memberData.bio = memberData.bio.trim();
}
-
+
// Determine if we need to fetch the initial member with relations
const needsProducts = this._stripeAPIService.configured && data.products;
const needsNewsletters = memberData.newsletters || typeof memberData.subscribed === 'boolean';
@@ -698,6 +698,7 @@ module.exports = class MemberRepository {
* @param {Object} data
* @param {String} data.id - member ID
* @param {Object} data.subscription
+ * @param {String} data.offerId
* @param {*} options
* @returns
*/
@@ -785,7 +786,9 @@ module.exports = class MemberRepository {
}
let stripeCouponId = subscription.discount && subscription.discount.coupon ? subscription.discount.coupon.id : null;
- let offerId = null;
+
+ // For trial offers, offer id is passed from metadata as there is no stripe coupon
+ let offerId = data.offerId || null;
if (stripeCouponId) {
// Get the offer from our database
@@ -831,6 +834,11 @@ module.exports = class MemberRepository {
let eventData = {};
if (model) {
+ // CASE: Offer is already mapped against sub, don't overwrite it with NULL
+ // Needed for trial offers, which don't have a stripe coupon/discount attached to sub
+ if (!subscriptionData.offer_id) {
+ delete subscriptionData.offer_id;
+ }
const updated = await this._StripeCustomerSubscription.edit(subscriptionData, {
...options,
id: model.id
diff --git a/ghost/stripe/lib/WebhookController.js b/ghost/stripe/lib/WebhookController.js
index c9ada71f53..a71582107d 100644
--- a/ghost/stripe/lib/WebhookController.js
+++ b/ghost/stripe/lib/WebhookController.js
@@ -253,9 +253,12 @@ module.exports = class WebhookController {
for (const subscription of customer.subscriptions.data) {
try {
+ const offerId = session.metadata?.offer;
+
await this.deps.memberRepository.linkSubscription({
id: member.id,
- subscription
+ subscription,
+ offerId
});
} catch (err) {
if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {