diff --git a/core/server/data/importer/importers/data/stripe-products.js b/core/server/data/importer/importers/data/stripe-products.js index 7a23961cc5..403c5e657b 100644 --- a/core/server/data/importer/importers/data/stripe-products.js +++ b/core/server/data/importer/importers/data/stripe-products.js @@ -8,6 +8,7 @@ class StripeProductsImporter extends BaseImporter { super(allDataFromFile, { modelName: 'StripeProduct', dataKeyToImport: 'stripe_products', + requiredFromFile: ['products'], requiredImportedData: ['products'], requiredExistingData: ['products'] }); @@ -41,11 +42,32 @@ class StripeProductsImporter extends BaseImporter { objectInFile.product_id = importedObject.id; return; } - const existingObject = _.find(this.requiredExistingData.products, {id: objectInFile.product_id}); + + const existingObjectById = _.find(this.requiredExistingData.products, {id: objectInFile.product_id}); // CASE: the product exists in the db already - if (existingObject) { + if (existingObjectById) { return; } + + // CASE: we skipped product import because a product with the same name and slug exists in the DB + debug('lookup product by name and slug'); + const productFromFile = _.find( + this.requiredFromFile.products, + {id: objectInFile.product_id} + ); + if (productFromFile) { + // look for the existing product with the same name and slug + const existingObjectByNameAndSlug = _.find( + this.requiredExistingData.products, + {name: productFromFile.name, slug: productFromFile.slug} + ); + if (existingObjectByNameAndSlug) { + debug(`resolved ${objectInFile.product_id} to ${existingObjectByNameAndSlug.name}`); + objectInFile.product_id = existingObjectByNameAndSlug.id; + return; + } + } + // CASE: we don't know what product this is for debug(`ignoring stripe product ${objectInFile.stripe_product_id}`); invalidProducts.push(objectInFile.id); diff --git a/test/regression/api/admin/db.test.js b/test/regression/api/admin/db.test.js index 602ec3a085..7f0bf986a9 100644 --- a/test/regression/api/admin/db.test.js +++ b/test/regression/api/admin/db.test.js @@ -378,3 +378,101 @@ describe('DB API (canary)', function () { yearlyPrice.get('stripe_product_id').should.equal('prod_d2c1708c21'); }); }); + +// The following tests will create a new clean database for every test +describe('DB API (cleaned)', function () { + let backupKey; + let schedulerKey; + + beforeEach(async function () { + await testUtils.stopGhost(); + await localUtils.startGhost(); + request = supertest.agent(config.get('url')); + await localUtils.doAuth(request); + backupKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-backup'}}); + schedulerKey = _.find(testUtils.getExistingData().apiKeys, {integration: {slug: 'ghost-scheduler'}}); + }); + + afterEach(function () { + sinon.restore(); + }); + + it('Can import a JSON database with products for an existing product', async function () { + // Create a product with existing slug + const existingProduct = await models.Product.forge({ + slug: 'ghost-inc', + name: 'Ghost Inc.', + description: 'Our daily newsletter', + type: 'paid', + active: 1, + visibility: 'public' + }).save(); + + const res = await request.post(localUtils.API.getApiQuery('db/')) + .set('Origin', config.get('url')) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .attach('importfile', path.join(__dirname, '/../../../utils/fixtures/export/products_export.json')) + .expect(200); + + // Check if we ignored the import of the product + const productDuplicate = await models.Product.findOne({slug: 'ghost-inc-2'}); + should.not.exist(productDuplicate); + + // Check if we have a product + const product = await models.Product.findOne({slug: 'ghost-inc'}); + should.exist(product); + product.id.should.equal(existingProduct.id); + product.get('slug').should.equal('ghost-inc'); + product.get('name').should.equal('Ghost Inc.'); + product.get('description').should.equal('Our daily newsletter'); + + // Check settings + const portalProducts = await models.Settings.findOne({key: 'portal_products'}); + should.exist(portalProducts); + JSON.parse(portalProducts.get('value')).should.deepEqual([]); + + // Check stripe products + const stripeProduct = await models.StripeProduct.findOne({product_id: product.id}); + should.exist(stripeProduct); + stripeProduct.get('stripe_product_id').should.equal('prod_d2c1708c21'); + stripeProduct.id.should.not.equal('60be1fc9bd3af33564cfb337'); + + // Check newsletters + const newsletter = await models.Newsletter.findOne({slug: 'test'}); + should.exist(newsletter); + newsletter.get('name').should.equal('Ghost Inc.'); + // Make sure sender_email is not set + should(newsletter.get('sender_email')).equal(null); + + // Check posts + const post = await models.Post.findOne({slug: 'test-newsletter'}, {withRelated: ['tiers']}); + should.exist(post); + + post.get('newsletter_id').should.equal(newsletter.id); + post.get('visibility').should.equal('public'); + post.get('email_recipient_filter').should.equal('status:-free'); + + // Check this post is connected to the imported product + post.relations.tiers.models.map(m => m.id).should.match([product.id]); + + // Check stripe prices + const monthlyPrice = await models.StripePrice.findOne({stripe_price_id: 'price_a425520db0'}); + should.exist(monthlyPrice); + + const yearlyPrice = await models.StripePrice.findOne({stripe_price_id: 'price_d04baebb73'}); + should.exist(yearlyPrice); + + monthlyPrice.get('amount').should.equal(500); + monthlyPrice.get('currency').should.equal('usd'); + monthlyPrice.get('interval').should.equal('month'); + monthlyPrice.get('stripe_price_id').should.equal('price_a425520db0'); + monthlyPrice.get('stripe_product_id').should.equal('prod_d2c1708c21'); + + yearlyPrice.get('amount').should.equal(4800); + yearlyPrice.get('currency').should.equal('usd'); + yearlyPrice.get('interval').should.equal('year'); + yearlyPrice.get('stripe_price_id').should.equal('price_d04baebb73'); + yearlyPrice.get('stripe_product_id').should.equal('prod_d2c1708c21'); + }); +});