mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Added support for "once" and "forever" Offer duration
refs https://github.com/TryGhost/Team/issues/1083 Instead of Offers being hardcoded to the "once" duration this will allow Admins to start creating offers of variable durations.
This commit is contained in:
parent
ea0282c80e
commit
6d383c2d0e
5 changed files with 84 additions and 40 deletions
|
@ -19,6 +19,8 @@
|
||||||
* @prop {boolean} currency_restriction
|
* @prop {boolean} currency_restriction
|
||||||
* @prop {string} currency
|
* @prop {string} currency
|
||||||
*
|
*
|
||||||
|
* @prop {'once'|'repeating'|'forever'} duration
|
||||||
|
*
|
||||||
* @prop {object} tier
|
* @prop {object} tier
|
||||||
* @prop {string} tier.id
|
* @prop {string} tier.id
|
||||||
* @prop {string} tier.name
|
* @prop {string} tier.name
|
||||||
|
@ -39,8 +41,9 @@ class OfferMapper {
|
||||||
type: offer.type.value,
|
type: offer.type.value,
|
||||||
cadence: offer.cadence.value,
|
cadence: offer.cadence.value,
|
||||||
amount: offer.amount.value,
|
amount: offer.amount.value,
|
||||||
currency_restriction: offer.type === 'amount',
|
duration: offer.duration.value,
|
||||||
currency: offer.type === 'amount' ? offer.currency : null,
|
currency_restriction: offer.type.value === 'amount',
|
||||||
|
currency: offer.type.value === 'amount' ? offer.currency : null,
|
||||||
tier: {
|
tier: {
|
||||||
id: offer.tier.id,
|
id: offer.tier.id,
|
||||||
name: offer.tier.name
|
name: offer.tier.name
|
||||||
|
|
|
@ -22,6 +22,7 @@ function toDomain(json) {
|
||||||
amount: json.discount_amount,
|
amount: json.discount_amount,
|
||||||
cadence: json.interval,
|
cadence: json.interval,
|
||||||
currency: json.currency,
|
currency: json.currency,
|
||||||
|
duration: json.duration,
|
||||||
stripe_coupon_id: json.stripe_coupon_id,
|
stripe_coupon_id: json.stripe_coupon_id,
|
||||||
tier: {
|
tier: {
|
||||||
id: json.product.id,
|
id: json.product.id,
|
||||||
|
@ -121,7 +122,7 @@ class OfferRepository {
|
||||||
discount_amount: offer.amount.value,
|
discount_amount: offer.amount.value,
|
||||||
interval: offer.cadence.value,
|
interval: offer.cadence.value,
|
||||||
product_id: offer.tier.id,
|
product_id: offer.tier.id,
|
||||||
duration: 'once'
|
duration: offer.duration.value
|
||||||
});
|
});
|
||||||
|
|
||||||
if (offer.codeChanged || offer.isNew) {
|
if (offer.codeChanged || offer.isNew) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ const OfferTitle = require('./OfferTitle');
|
||||||
const OfferDescription = require('./OfferDescription');
|
const OfferDescription = require('./OfferDescription');
|
||||||
const OfferCadence = require('./OfferCadence');
|
const OfferCadence = require('./OfferCadence');
|
||||||
const OfferType = require('./OfferType');
|
const OfferType = require('./OfferType');
|
||||||
|
const OfferDuration = require('./OfferDuration');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} OfferProps
|
* @typedef {object} OfferProps
|
||||||
|
@ -19,6 +20,7 @@ const OfferType = require('./OfferType');
|
||||||
* @prop {OfferCadence} cadence
|
* @prop {OfferCadence} cadence
|
||||||
* @prop {OfferType} type
|
* @prop {OfferType} type
|
||||||
* @prop {OfferAmount} amount
|
* @prop {OfferAmount} amount
|
||||||
|
* @prop {OfferDuration} duration
|
||||||
* @prop {string} currency
|
* @prop {string} currency
|
||||||
* @prop {string} [stripe_coupon_id]
|
* @prop {string} [stripe_coupon_id]
|
||||||
* @prop {OfferTier} tier
|
* @prop {OfferTier} tier
|
||||||
|
@ -93,40 +95,8 @@ class Offer {
|
||||||
return this.props.currency;
|
return this.props.currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
get duration() {
|
||||||
* @param {OfferCode} code
|
return this.props.duration;
|
||||||
* @param {UniqueChecker} uniqueChecker
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async updateCode(code, uniqueChecker) {
|
|
||||||
if (code.equals(this.props.code)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!await uniqueChecker.isUniqueCode(code)) {
|
|
||||||
throw new errors.InvalidOfferCode({
|
|
||||||
message: 'Offer `code` must be unique.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.changed.code.push(this.props.code);
|
|
||||||
this.props.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {OfferName} name
|
|
||||||
* @param {UniqueChecker} uniqueChecker
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async updateName(name, uniqueChecker) {
|
|
||||||
if (name.equals(this.props.name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!await uniqueChecker.isUniqueName(name)) {
|
|
||||||
throw new errors.InvalidOfferNameError({
|
|
||||||
message: 'Offer `name` must be unique.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.changed.name.push(this.props.name);
|
|
||||||
this.props.name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get oldCodes() {
|
get oldCodes() {
|
||||||
|
@ -177,6 +147,42 @@ class Offer {
|
||||||
return this.props.stripe_coupon_id;
|
return this.props.stripe_coupon_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {OfferCode} code
|
||||||
|
* @param {UniqueChecker} uniqueChecker
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async updateCode(code, uniqueChecker) {
|
||||||
|
if (code.equals(this.props.code)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!await uniqueChecker.isUniqueCode(code)) {
|
||||||
|
throw new errors.InvalidOfferCode({
|
||||||
|
message: 'Offer `code` must be unique.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.changed.code.push(this.props.code);
|
||||||
|
this.props.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {OfferName} name
|
||||||
|
* @param {UniqueChecker} uniqueChecker
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async updateName(name, uniqueChecker) {
|
||||||
|
if (name.equals(this.props.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!await uniqueChecker.isUniqueName(name)) {
|
||||||
|
throw new errors.InvalidOfferName({
|
||||||
|
message: 'Offer `name` must be unique.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.changed.name.push(this.props.name);
|
||||||
|
this.props.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {OfferProps} props
|
* @param {OfferProps} props
|
||||||
|
@ -219,6 +225,7 @@ class Offer {
|
||||||
const description = OfferDescription.create(data.display_description);
|
const description = OfferDescription.create(data.display_description);
|
||||||
const type = OfferType.create(data.type);
|
const type = OfferType.create(data.type);
|
||||||
const cadence = OfferCadence.create(data.cadence);
|
const cadence = OfferCadence.create(data.cadence);
|
||||||
|
const duration = OfferDuration.create(data.duration);
|
||||||
let amount;
|
let amount;
|
||||||
if (type.equals(OfferType.Percent)) {
|
if (type.equals(OfferType.Percent)) {
|
||||||
amount = OfferAmount.OfferPercentageAmount.create(data.amount);
|
amount = OfferAmount.OfferPercentageAmount.create(data.amount);
|
||||||
|
@ -242,6 +249,12 @@ class Offer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (duration.equals(OfferDuration.create('repeating'))) {
|
||||||
|
throw new errors.InvalidOfferDuration({
|
||||||
|
message: 'Offer `duration` must be either "once" or "forever".'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const currency = data.currency;
|
const currency = data.currency;
|
||||||
|
|
||||||
if (isNew && data.stripe_coupon_id) {
|
if (isNew && data.stripe_coupon_id) {
|
||||||
|
@ -267,6 +280,7 @@ class Offer {
|
||||||
type,
|
type,
|
||||||
amount,
|
amount,
|
||||||
cadence,
|
cadence,
|
||||||
|
duration,
|
||||||
currency,
|
currency,
|
||||||
tier,
|
tier,
|
||||||
stripe_coupon_id: couponId
|
stripe_coupon_id: couponId
|
||||||
|
|
25
ghost/offers/lib/domain/models/OfferDuration.js
Normal file
25
ghost/offers/lib/domain/models/OfferDuration.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
const ValueObject = require('../../shared/ValueObject');
|
||||||
|
const InvalidOfferDuration = require('../../errors').InvalidOfferDuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ValueObject<'once'|'repeating'|'forever'>
|
||||||
|
*/
|
||||||
|
class OfferDuration extends ValueObject {
|
||||||
|
/** @param {unknown} duration */
|
||||||
|
static create(duration) {
|
||||||
|
if (!duration || typeof duration !== 'string') {
|
||||||
|
throw new InvalidOfferDuration({
|
||||||
|
message: 'Offer `duration` must be a string.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (duration !== 'once' && duration !== 'repeating' && duration !== 'forever') {
|
||||||
|
throw new InvalidOfferDuration({
|
||||||
|
message: 'Offer `duration` must be one of "once", "repeating" or "forever".'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new OfferDuration(duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = OfferDuration;
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
const {GhostError} = require('@tryghost/errors');
|
const {GhostError} = require('@tryghost/errors');
|
||||||
|
|
||||||
class InvalidPropError extends GhostError {
|
class InvalidPropError extends GhostError {
|
||||||
static message = 'Invalid Offer property';
|
|
||||||
/** @param {any} options */
|
/** @param {any} options */
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super({
|
super({
|
||||||
statusCode: 400
|
statusCode: 400,
|
||||||
|
...options
|
||||||
});
|
});
|
||||||
this.errorType = this.constructor.name;
|
this.errorType = this.constructor.name;
|
||||||
this.message = options.message || this.constructor.message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +20,7 @@ class InvalidOfferAmount extends InvalidPropError {}
|
||||||
class InvalidOfferCurrency extends InvalidPropError {}
|
class InvalidOfferCurrency extends InvalidPropError {}
|
||||||
class InvalidOfferTierName extends InvalidPropError {}
|
class InvalidOfferTierName extends InvalidPropError {}
|
||||||
class InvalidOfferCadence extends InvalidPropError {}
|
class InvalidOfferCadence extends InvalidPropError {}
|
||||||
|
class InvalidOfferDuration extends InvalidPropError {}
|
||||||
class InvalidOfferCoupon extends InvalidPropError {}
|
class InvalidOfferCoupon extends InvalidPropError {}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -32,6 +32,7 @@ module.exports = {
|
||||||
InvalidOfferAmount,
|
InvalidOfferAmount,
|
||||||
InvalidOfferCurrency,
|
InvalidOfferCurrency,
|
||||||
InvalidOfferCadence,
|
InvalidOfferCadence,
|
||||||
|
InvalidOfferDuration,
|
||||||
InvalidOfferTierName,
|
InvalidOfferTierName,
|
||||||
InvalidOfferCoupon
|
InvalidOfferCoupon
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue