mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-23 03:42:27 +03:00
Updated offers setup to allow trial values
refs https://github.com/TryGhost/Team/issues/1726 - updates offer setup to allow new `trial` as discount type, was prev only `fixed` and `percent` - updates offer setup to allow `amount` as free trial days value - updates offer setup to allow `trial` as discount duration value for trial offers, was prev only `once`/`forever`/`repeating`
This commit is contained in:
parent
27a89d4b0e
commit
66970e5002
@ -11,7 +11,7 @@
|
||||
* @prop {string} display_title
|
||||
* @prop {string} display_description
|
||||
*
|
||||
* @prop {'percent'|'fixed'} type
|
||||
* @prop {'percent'|'fixed'|'trial'} type
|
||||
*
|
||||
* @prop {'month'|'year'} cadence
|
||||
* @prop {number} amount
|
||||
@ -19,7 +19,7 @@
|
||||
* @prop {boolean} currency_restriction
|
||||
* @prop {string} currency
|
||||
*
|
||||
* @prop {'once'|'repeating'|'forever'} duration
|
||||
* @prop {'once'|'repeating'|'forever'|'trial'} duration
|
||||
* @prop {null|number} duration_in_months
|
||||
*
|
||||
* @prop {'active'|'archived'} status
|
||||
|
@ -108,7 +108,7 @@ class OfferRepository {
|
||||
code: json.code,
|
||||
display_title: json.portal_title,
|
||||
display_description: json.portal_description,
|
||||
type: json.discount_type === 'amount' ? 'fixed' : 'percent',
|
||||
type: json.discount_type === 'amount' ? 'fixed' : json.discount_type,
|
||||
amount: json.discount_amount,
|
||||
cadence: json.interval,
|
||||
currency: json.currency,
|
||||
@ -192,7 +192,7 @@ class OfferRepository {
|
||||
code: offer.code.value,
|
||||
portal_title: offer.displayTitle.value || null,
|
||||
portal_description: offer.displayDescription.value || null,
|
||||
discount_type: offer.type.value === 'fixed' ? 'amount' : 'percent',
|
||||
discount_type: offer.type.value === 'fixed' ? 'amount' : offer.type.value,
|
||||
discount_amount: offer.amount.value,
|
||||
interval: offer.cadence.value,
|
||||
product_id: offer.tier.id,
|
||||
|
@ -276,10 +276,19 @@ class Offer {
|
||||
});
|
||||
}
|
||||
|
||||
//CASE: For offer type trial, the duration can only be `trial`
|
||||
if (type.value === 'trial' && duration.value.type !== 'trial') {
|
||||
throw new errors.InvalidOfferDuration({
|
||||
message: 'Offer `duration` must be "trial" for offer type "trial".'
|
||||
});
|
||||
}
|
||||
|
||||
let currency = null;
|
||||
let amount;
|
||||
if (type.equals(OfferType.Percentage)) {
|
||||
amount = OfferAmount.OfferPercentageAmount.create(data.amount);
|
||||
} else if (type.equals(OfferType.Trial)) {
|
||||
amount = OfferAmount.OfferTrialAmount.create(data.amount);
|
||||
} else if (type.equals(OfferType.Fixed)) {
|
||||
amount = OfferAmount.OfferFixedAmount.create(data.amount);
|
||||
currency = OfferCurrency.create(data.currency);
|
||||
|
@ -52,6 +52,31 @@ class OfferFixedAmount extends OfferAmount {
|
||||
static InvalidOfferAmount = InvalidOfferAmount;
|
||||
}
|
||||
|
||||
class OfferTrialAmount extends OfferAmount {
|
||||
/** @param {unknown} amount */
|
||||
static create(amount) {
|
||||
if (typeof amount !== 'number') {
|
||||
throw new InvalidOfferAmount({
|
||||
message: 'Offer `amount` must be an integer greater than 0.'
|
||||
});
|
||||
}
|
||||
if (!Number.isInteger(amount)) {
|
||||
throw new InvalidOfferAmount({
|
||||
message: 'Offer `amount` must be a integer greater than 0.'
|
||||
});
|
||||
}
|
||||
if (amount < 0) {
|
||||
throw new InvalidOfferAmount({
|
||||
message: 'Offer `amount` must be a integer greater than 0.'
|
||||
});
|
||||
}
|
||||
return new OfferTrialAmount(amount);
|
||||
}
|
||||
|
||||
static InvalidOfferAmount = InvalidOfferAmount;
|
||||
}
|
||||
|
||||
module.exports = OfferAmount;
|
||||
module.exports.OfferPercentageAmount = OfferPercentageAmount;
|
||||
module.exports.OfferFixedAmount = OfferFixedAmount;
|
||||
module.exports.OfferTrialAmount = OfferTrialAmount;
|
||||
|
@ -3,7 +3,7 @@ const InvalidOfferDuration = require('../errors').InvalidOfferDuration;
|
||||
|
||||
/**
|
||||
* @typedef {object} BasicDuration
|
||||
* @prop {'once'|'forever'} type
|
||||
* @prop {'once'|'forever'|'trial'} type
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -26,9 +26,9 @@ class OfferDuration extends ValueObject {
|
||||
message: 'Offer `duration` must be a string.'
|
||||
});
|
||||
}
|
||||
if (type !== 'once' && type !== 'repeating' && type !== 'forever') {
|
||||
if (type !== 'once' && type !== 'repeating' && type !== 'forever' && type !== 'trial') {
|
||||
throw new InvalidOfferDuration({
|
||||
message: 'Offer `duration` must be one of "once", "repeating" or "forever".'
|
||||
message: 'Offer `duration` must be one of "once", "repeating", "forever" or "trial.'
|
||||
});
|
||||
}
|
||||
if (type !== 'repeating') {
|
||||
|
@ -1,7 +1,7 @@
|
||||
const ValueObject = require('./shared/ValueObject');
|
||||
const InvalidOfferType = require('../errors').InvalidOfferType;
|
||||
|
||||
/** @extends ValueObject<'fixed'|'percent'> */
|
||||
/** @extends ValueObject<'fixed'|'percent'|'trial'> */
|
||||
class OfferType extends ValueObject {
|
||||
/** @param {unknown} type */
|
||||
static create(type) {
|
||||
@ -10,9 +10,9 @@ class OfferType extends ValueObject {
|
||||
message: 'Offer `type` must be a string.'
|
||||
});
|
||||
}
|
||||
if (type !== 'percent' && type !== 'fixed') {
|
||||
if (type !== 'percent' && type !== 'fixed' && type !== 'trial') {
|
||||
throw new InvalidOfferType({
|
||||
message: 'Offer `type` must be one of "percent" or "fixed".'
|
||||
message: 'Offer `type` must be one of "percent", "fixed" or "trial".'
|
||||
});
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ class OfferType extends ValueObject {
|
||||
static Percentage = new OfferType('percent');
|
||||
|
||||
static Fixed = new OfferType('fixed');
|
||||
|
||||
static Trial = new OfferType('trial');
|
||||
}
|
||||
|
||||
module.exports = OfferType;
|
||||
|
@ -41,6 +41,48 @@ describe('Offer', function () {
|
||||
);
|
||||
});
|
||||
|
||||
it('Creates a valid instance of a trial Offer', async function () {
|
||||
const offer = await Offer.create({
|
||||
name: 'My Trial Offer',
|
||||
code: 'offer-code-trial',
|
||||
display_title: 'My Offer Title',
|
||||
display_description: 'My Offer Description',
|
||||
cadence: 'month',
|
||||
type: 'trial',
|
||||
amount: 10,
|
||||
duration: 'trial',
|
||||
currency: 'USD',
|
||||
tier: {
|
||||
id: ObjectID()
|
||||
}
|
||||
}, mockUniqueChecker);
|
||||
should.ok(
|
||||
offer instanceof Offer,
|
||||
'Offer.create should return an instance of Offer'
|
||||
);
|
||||
});
|
||||
|
||||
it('Throws an error if the duration for trial offer is not right', async function () {
|
||||
await Offer.create({
|
||||
name: 'My Trial Offer',
|
||||
code: 'trial-test',
|
||||
display_title: 'My Offer Title',
|
||||
display_description: 'My Offer Description',
|
||||
cadence: 'month',
|
||||
type: 'trial',
|
||||
amount: 10,
|
||||
duration: 'forever',
|
||||
currency: 'USD',
|
||||
tier: {
|
||||
id: ObjectID()
|
||||
}
|
||||
}, mockUniqueChecker).then(() => {
|
||||
should.fail('Expected an error');
|
||||
}, (err) => {
|
||||
should.ok(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('Throws an error if the code is not unique', async function () {
|
||||
await Offer.create({
|
||||
name: 'My Offer',
|
||||
@ -150,6 +192,27 @@ describe('Offer', function () {
|
||||
should.equal(offer.currency, null);
|
||||
});
|
||||
|
||||
it('Has a currency of null if the type is trial', async function () {
|
||||
const data = {
|
||||
name: 'My Trial Offer',
|
||||
code: 'offer-code-trial',
|
||||
display_title: 'My Offer Title',
|
||||
display_description: 'My Offer Description',
|
||||
cadence: 'year',
|
||||
type: 'trial',
|
||||
amount: 20,
|
||||
duration: 'trial',
|
||||
currency: 'USD',
|
||||
tier: {
|
||||
id: ObjectID()
|
||||
}
|
||||
};
|
||||
|
||||
const offer = await Offer.create(data, mockUniqueChecker);
|
||||
|
||||
should.equal(offer.currency, null);
|
||||
});
|
||||
|
||||
it('Can handle ObjectID, string and no id', async function () {
|
||||
const data = {
|
||||
name: 'My Offer',
|
||||
|
@ -1,4 +1,4 @@
|
||||
const {OfferPercentageAmount, OfferFixedAmount} = require('../../../../lib/domain/models/OfferAmount');
|
||||
const {OfferPercentageAmount, OfferFixedAmount, OfferTrialAmount} = require('../../../../lib/domain/models/OfferAmount');
|
||||
|
||||
describe('OfferAmount', function () {
|
||||
describe('OfferPercentageAmount', function () {
|
||||
@ -118,4 +118,58 @@ describe('OfferAmount', function () {
|
||||
should.ok(typeof cadence.value === 'number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('OfferTrialAmount', function () {
|
||||
describe('OfferTrialAmount.create factory', function () {
|
||||
it('Will only create an OfferTrialAmount containing an integer greater than 0', function () {
|
||||
try {
|
||||
OfferTrialAmount.create();
|
||||
should.fail();
|
||||
} catch (err) {
|
||||
should.ok(
|
||||
err instanceof OfferTrialAmount.InvalidOfferAmount,
|
||||
'expected an InvalidOfferAmount error'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
OfferTrialAmount.create('1');
|
||||
should.fail();
|
||||
} catch (err) {
|
||||
should.ok(
|
||||
err instanceof OfferTrialAmount.InvalidOfferAmount,
|
||||
'expected an InvalidOfferAmount error'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
OfferTrialAmount.create(-1);
|
||||
should.fail();
|
||||
} catch (err) {
|
||||
should.ok(
|
||||
err instanceof OfferTrialAmount.InvalidOfferAmount,
|
||||
'expected an InvalidOfferAmount error'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
OfferTrialAmount.create(3.14);
|
||||
should.fail();
|
||||
} catch (err) {
|
||||
should.ok(
|
||||
err instanceof OfferTrialAmount.InvalidOfferAmount,
|
||||
'expected an InvalidOfferAmount error'
|
||||
);
|
||||
}
|
||||
|
||||
OfferTrialAmount.create(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('Exposes a number on the value property', function () {
|
||||
const cadence = OfferTrialAmount.create(42);
|
||||
|
||||
should.ok(typeof cadence.value === 'number');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ describe('OfferDuration', function () {
|
||||
it('Will only allow creating a once, repeating or forever duration', function () {
|
||||
OfferDuration.create('once');
|
||||
OfferDuration.create('forever');
|
||||
OfferDuration.create('trial');
|
||||
OfferDuration.create('repeating', 2);
|
||||
|
||||
try {
|
||||
|
@ -5,6 +5,7 @@ describe('OfferType', function () {
|
||||
it('Creates an Offer type containing either "fixed" or "percent"', function () {
|
||||
OfferType.create('fixed');
|
||||
OfferType.create('percent');
|
||||
OfferType.create('trial');
|
||||
|
||||
try {
|
||||
OfferType.create('other');
|
||||
@ -41,4 +42,11 @@ describe('OfferType', function () {
|
||||
should.ok(OfferType.Fixed.equals(OfferType.create('fixed')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('OfferType.Trial', function () {
|
||||
it('Is an OfferType with a value of "trial"', function () {
|
||||
should.equal(OfferType.Trial.value, 'trial');
|
||||
should.ok(OfferType.Trial.equals(OfferType.create('trial')));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user