// @ts-nocheck import { expect } from 'chai'; import * as sinon from 'sinon'; import { mockRavelinDeviceId, mockDeliveryAddress, mockPaymentDetails, deliveryAddressForSubscribe, optionDataForSubscribe, optionDataForSubscribeSingleTermSubscriptionTrue, } from '../fixtures/subscription'; import { userDataForSubscribe, userProfile } from '../fixtures/user-data'; import { TestConfiguration } from '../../src/configuration'; import { Subscription as SubscriptionSdk } from '../../src/subscription'; import { Subscription } from '../../src/models/subscription'; const sandbox = sinon.createSandbox(); describe('Subscription SDK', () => { let instance; let fetchStub; let config; const mockAppId = 'appId'; beforeEach(() => { fetchStub = sandbox.stub(); config = new TestConfiguration({ originSystemId: 'n-membership-sdk test', subscriptionApiKey: 'secret', subscriptionApiAppId: mockAppId, subscriptionHistoryApiKey: 'historySecret' }); instance = new SubscriptionSdk(config, { fetchOverride: fetchStub }); sandbox.stub(instance, 'requestPut').resolves({}); sandbox.stub(instance, 'requestPost').resolves({}); sandbox.stub(instance, 'requestGet').resolves({ authData: {} }); }); afterEach(() => { sandbox.restore(); }); it('requires host and key configuration properties', () => { try { new SubscriptionSdk({}); expect.fail('It should throw'); } catch (error) { } }); describe('createSubscription', () => { const mockResponse = { subscriptionId: 'test-id', subscriptionNumber: 'test-number', invoiceId: 'test-inv-id', invoiceNumber: 'test-inv-number' }; it('should pass correct parameters to requestPost singleTermSubscription equal to false', async () => { const subscribeUrl = `${instance.membershipApi}/subscriptions/subscribe`; await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDeliveryAddress); const postParams = instance.requestPost.getCall(0).args[0]; // Path checks expect(postParams).to.have.property('url'); expect(postParams.url).to.equal(subscribeUrl); // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe).to.have.property('user'); expect(postParams.body.subscribe.user).to.deep.equal(userDataForSubscribe); expect(postParams.body.subscribe.options).to.deep.equal(optionDataForSubscribe); }); it('should pass correct parameters to requestPost singleTermSubscription equal to true', async () => { const subscribeUrl = `${instance.membershipApi}/subscriptions/subscribe`; mockPaymentDetails.singleTermSubscription=true; await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDeliveryAddress); const postParams = instance.requestPost.getCall(0).args[0]; // Path checks expect(postParams).to.have.property('url'); expect(postParams.url).to.equal(subscribeUrl); // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe).to.have.property('user'); expect(postParams.body.subscribe.user).to.deep.equal(userDataForSubscribe); expect(postParams.body.subscribe.options).to.deep.equal(optionDataForSubscribeSingleTermSubscriptionTrue); }); it('should pass ravelin property', async () => { await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDeliveryAddress); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body.subscribe).to.have.property('ravelinData'); expect(postParams.body.subscribe.ravelinData.deviceId).to.equal(mockRavelinDeviceId); }); it('should handle deliveryOption', async () => { const mockDelivery = { ...mockDeliveryAddress }; mockDelivery.company = 'Company Name'; const mockDeliveryAddressForSubscribe = { ...deliveryAddressForSubscribe, company: mockDelivery.company, poBox: false, state: undefined, }; await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDelivery); const postParams = instance.requestPost.getCall(0).args[0]; // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe.deliveryAddress).to.deep.equal(mockDeliveryAddressForSubscribe); }); it('should handle deliveryOption with poBox', async () => { const mockDelivery = { ...mockDeliveryAddress }; mockDelivery.poBox = '123'; mockDelivery.addressType = 'pobox'; mockDelivery.company = 'Company Name'; const mockDeliveryAddressForSubscribe = { ...deliveryAddressForSubscribe, address1: mockDelivery.poBox, poBox: true, company: mockDelivery.company, state: undefined, }; await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDelivery); const postParams = instance.requestPost.getCall(0).args[0]; // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe.deliveryAddress).to.deep.equal(mockDeliveryAddressForSubscribe); }); it('when billing country code is not set it throws a ValidationError', async () => { const mock = Object.assign({}, mockPaymentDetails); mock.billingCountry = undefined; try { await instance.createSubscription(userProfile, mock, mockRavelinDeviceId, mockDeliveryAddress); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Billing country code is not defined'); } }); it('should contain state and postCode in request body when country is USA', async () => { const mockPayment = { ...mockPaymentDetails, billingCountry: 'USA', option: 'PV', billingState: 'CA', billingPostcode: '90210', }; const mockDelivery = { ...mockDeliveryAddress, province: mockPayment.billingState, }; await instance.createSubscription(userProfile, mockPayment, mockRavelinDeviceId, mockDelivery); const postParams = instance.requestPost.getCall(0).args[0]; // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe).to.have.property('user'); expect(postParams.body.subscribe.user.state).to.equal('CA'); expect(postParams.body.subscribe.user.postCode).to.equal('90210'); expect(postParams.body.subscribe).to.have.property('deliveryAddress'); expect(postParams.body.subscribe.deliveryAddress.state).to.equal(mockDelivery.province); }); it('should contain state from province and postCode in request body when country is Canada', async () => { const mockPayment = { ...mockPaymentDetails, billingCountry: 'CAN', option: 'PV', billingState: 'AB', billingPostcode: '1234', }; const mockDelivery = { ...mockDeliveryAddress, countryCode: mockPayment.billingCountry, state: mockPayment.billingState, }; await instance.createSubscription(userProfile, mockPayment, mockRavelinDeviceId, mockDelivery); const postParams = instance.requestPost.getCall(0).args[0]; // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe).to.have.property('user'); expect(postParams.body.subscribe.user.state).to.equal('AB'); expect(postParams.body.subscribe.user.postCode).to.equal('1234'); expect(postParams.body.subscribe).to.have.property('deliveryAddress'); expect(postParams.body.subscribe.deliveryAddress.state).to.equal(mockDelivery.state); }); it('should contain city and postCode in request body when offer is ePaper', async () => { const mock = Object.assign({}, mockPaymentDetails); mock.billingCountry = 'CAN'; mock.option = 'PV'; mock.billingState = 'AB'; mock.billingPostcode = '1234'; mock.billingCity= 'city-test'; mock.offer.productType = 'PRINT'; mock.offer.productCode = 'EP'; await instance.createSubscription(userProfile, mock); const postParams = instance.requestPost.getCall(0).args[0]; // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe).to.have.property('user'); expect(postParams.body.subscribe.user.city).to.equal(mock.billingCity); expect(postParams.body.subscribe.user.postCode).to.equal(mock.billingPostcode); }); it('should not contain city in request body when offer is not ePaper', async () => { const mock = Object.assign({}, mockPaymentDetails); mock.billingCountry = 'CAN'; mock.option = 'PV'; mock.billingState = 'AB'; mock.billingPostcode = '1234'; mock.offer.productType = 'DIGITAL'; await instance.createSubscription(userProfile, mock); const postParams = instance.requestPost.getCall(0).args[0]; // Body checks expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('subscribe'); expect(postParams.body.subscribe).to.have.property('user'); expect(postParams.body.subscribe.user).not.to.have.property('city'); }); context('when no errors are returned', () => { beforeEach(() => { instance.requestPost.resolves(mockResponse); }); it('returns the result', async () => { const data = await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDeliveryAddress); expect(data).to.deep.equal(mockResponse); }); }); context('when an error object is return with http 200', () => { const mockError = { errors: ['some', 'error', 'happened'] }; beforeEach(() => { instance.requestPost.resolves(mockError); }); it('should throw an error', async () => { try { await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDeliveryAddress); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('createSubscriptionError'); expect(error.data).to.equal(mockError); } }); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPost.rejects(errorResponse); }); it('it throws the same error', async () => { try { await instance.createSubscription(userProfile, mockPaymentDetails, mockRavelinDeviceId, mockDeliveryAddress); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('fetchGatewayConfig', () => { const paymentType = 'testPaymentType'; const countryCode = 'testCountryCode'; const clientIPAddress = '10.10.10.10'; const authorizationAmount = 1; const userId = 'userId'; const ftSession = 'ft-session-s-token'; const mockResponse = { pageId: 'test-id', '3DS': 'false', authData: { token: 'test-token', key: 'test-key', tenantId: 'test-tenant-id', signature: 'test-signature' }, paymentGateway: 'test-gateway', currencyCode: 'GBP', hostedPaymentPageUrl: 'test-page-id', gatewayData:{ 'param_gwOptions_CurrencyCode':'840', 'param_gwOptions_purchaseTotals_currency':'GBP', 'is3DS':'true', 'useZeroAuthorizationAmount':'true', } }; const mappedResponse = { id: mockResponse.pageId, key: mockResponse.authData.key, style: 'inline', token: mockResponse.authData.token, tenantId: mockResponse.authData.tenantId, signature: mockResponse.authData.signature, submitEnabled: 'false', paymentGateway: mockResponse.paymentGateway, field_currency: mockResponse.gatewayData.param_gwOptions_purchaseTotals_currency, field_passthrough1: mockResponse.pageId, field_passthrough2: userId, field_passthrough3: mockAppId, field_passthrough4: mockResponse.paymentGateway, field_passthrough5: ftSession, field_passthrough6: countryCode, field_passthrough7: mockResponse.currencyCode, authorizationAmount: authorizationAmount, url: mockResponse.hostedPaymentPageUrl, is3DS: mockResponse.gatewayData.is3DS, useZeroAuthorizationAmount: mockResponse.gatewayData.useZeroAuthorizationAmount, param_gwOptions_CurrencyCode: mockResponse.gatewayData.param_gwOptions_CurrencyCode, param_gwOptions_purchaseTotals_currency: mockResponse.gatewayData.param_gwOptions_purchaseTotals_currency, }; it('should pass correct parameters to requestGet', async () => { instance.requestGet.resolves(mockResponse); await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); const getParams = instance.requestGet.getCall(0).args[0]; expect(getParams).to.have.property('url'); expect(getParams.url.toString()).to.contain(paymentType); expect(getParams.url.toString()).to.contain(countryCode); expect(getParams.url.toString()).to.contain(config.get('subscriptionApiAppId')); }); it('should use overrideAppId as appId if provided', async () => { instance.requestGet.resolves(mockResponse); const overrideAppId = 'someOtherAppId'; const gatewayConfig = await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, appIdOverride: overrideAppId, ftSession }); const getParams = instance.requestGet.getCall(0).args[0]; expect(getParams).to.have.property('url'); expect(getParams.url.toString()).to.contain(overrideAppId); expect(gatewayConfig['field_passthrough3']).to.contain(overrideAppId); }); it('should use userId if provided as an additional parameter', async () => { instance.requestGet.resolves(mockResponse); await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); const getParams = instance.requestGet.getCall(0).args[0]; expect(getParams).to.have.property('url'); expect(getParams.url.toString()).to.contain('userId='+userId); }); it('should pass paymentTern as `billingFrequency` query param if provided', async () => { const paymentTerm = 'P1M'; instance.requestGet.resolves(mockResponse); await instance.fetchGatewayConfig({ paymentType, paymentTerm, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); const getParams = instance.requestGet.getCall(0).args[0]; expect(getParams).to.have.property('url'); expect(getParams.url.toString()).to.contain('billingFrequency=P1M'); }); it('should not pass paymentTern as `billingFrequency` query param if not provided', async () => { instance.requestGet.resolves(mockResponse); await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); const getParams = instance.requestGet.getCall(0).args[0]; expect(getParams).to.have.property('url'); expect(getParams.url.toString()).to.not.contain('billingFrequency'); }); it('should set ftSession in passthrough fields', async () => { instance.requestGet.resolves(mockResponse); const response = await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); expect(response).to.have.property('field_passthrough5'); expect(response.field_passthrough5).to.equal(ftSession); }); it('should default ftSession if not provided', async () => { instance.requestGet.resolves(mockResponse); const response = await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount }); expect(response).to.have.property('field_passthrough5'); expect(response.field_passthrough5).to.equal(''); }); context('when no errors are returned', () => { it('returns the result', async () => { instance.requestGet.resolves(mockResponse); const data = await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); expect(data).to.deep.equal(mappedResponse); }); it('updates the IP address if the gateway config response contains a key for gatewayData.param_gwOptions_customer_ip', async () => { const mockResponseWithIp = { ...mockResponse }; mockResponseWithIp.gatewayData.param_gwOptions_customer_ip = ''; instance.requestGet.resolves(mockResponseWithIp); const gatewayConfig = await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); expect(gatewayConfig.param_gwOptions_customer_ip).to.equal(clientIPAddress); }); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestGet.rejects(errorResponse); }); it('throws the same error', async () => { try { await instance.fetchGatewayConfig({ paymentType, countryCode, userId, clientIPAddress, authorizationAmount, ftSession }); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('fetchGatewayName', () => { const mockResponse = { gateway: 'some gateway' }; beforeEach(() => { instance.requestGet.resolves(mockResponse); }); it('should request a path with the given payment type', () => { const paymentType = 'TESTPAYMENTTYPE'; const countryCode = 'GBR'; instance.fetchGatewayName(paymentType, countryCode); expect(instance.requestGet.getCall(0).args[0].url).to.contain(paymentType); }); it('should request a path with the given country code', () => { const paymentType = 'TESTPAYMENTTYPE'; const countryCode = 'GBR'; instance.fetchGatewayName(paymentType, countryCode); expect(instance.requestGet.getCall(0).args[0].url).to.contain(countryCode); }); it('should throw an error if response has no gateway parameter', done => { instance.requestGet.returns(Promise.resolve({})); instance.fetchGatewayName('testPaymentType', 'GBR').catch(() => done()); }); it('should return the gateway parameter', async () => { const gateway = 'TEST GATEWAY STRING'; instance.requestGet.returns(Promise.resolve({ gateway })); expect(await instance.fetchGatewayName('testPaymentType', 'GBR')).to.equal(gateway); }); }); describe('validatePaymentSignature', () => { const mockPaymentSignature = { token: 'token', pageId: 'pageId', tenantId: 'tenantId', signature: 'signature', paymentGateway: 'Chase', userId: 'some-user-id', }; it('should pass correct parameters to requestPost', async () => { const url = `${instance.membershipApi}/paymentpage2/validation/signature`; fetchStub.returns({ ok: true, status: 200 }); await instance.validatePaymentSignature(mockPaymentSignature); const getParams = fetchStub.getCall(0).args; expect(getParams[0]).to.equal(url); expect(getParams[1].method).to.equal('POST'); expect(getParams[1].body).to.deep.equal(JSON.stringify(mockPaymentSignature)); }); context('when no errors are returned', () => { beforeEach(() => { fetchStub.returns({ ok: true, status: 200 }); }); it('returns the result', async () => { const response = await instance.validatePaymentSignature(mockPaymentSignature); expect(response.status).to.equal(200); expect(response.ok).to.equal(true); }); }); context('when an error is returned', () => { beforeEach(() => { fetchStub.returns({ ok: false, status: 500 }); }); it('it returns the expected response', async () => { const response = await instance.validatePaymentSignature(mockPaymentSignature); expect(response.status).to.equal(500); expect(response.ok).to.equal(false); }); }); }); describe('getSubscriptionHistoryByUserId', () => { const mockUserId = 'test-user-id'; beforeEach(() => { instance.requestGet.resolves([ { event_type: 'item-1' }, { event_type: 'item-2' } ]); }); it('calls the correct URL', async () => { await instance.getSubscriptionHistoryByUserId(mockUserId); const getParams = instance.requestGet.getCall(0).args[0]; // Path checks expect(getParams).to.have.property('url'); expect(getParams.url).to.equal(`${instance.membershipApi}/membership/subs-history/subscriptions/history?userId=${mockUserId}`); }); }); describe('getSubscriptionHistoryById', async () => { const mockSubscriptionId = 'A-S00000000'; beforeEach(() => { instance.requestGet.resolves([ { event_type: 'item-1' }, { event_type: 'item-2' } ]); }); it('calls the correct URL', async () => { await instance.getSubscriptionHistoryById(mockSubscriptionId); const getParams = instance.requestGet.getCall(0).args[0]; // Path checks expect(getParams).to.have.property('url'); expect(getParams.url).to.equal(`${instance.membershipApi}/membership/subs-history/subscriptions/history/${mockSubscriptionId}`); }); }); describe('updateDefaultPaymentMethod', async () => { const mockUserId = 'some-kind-of-user-id'; const mockPaymentMethodId = 'some-kind-of-payment-method-id'; beforeEach(() => { instance.requestPut.resolves(); }); it('calls the correct URL', async () => { await instance.updateDefaultPaymentMethod(mockUserId, mockPaymentMethodId); const putParams = instance.requestPut.getCall(0).args[0]; // Path checks expect(putParams).to.have.property('url'); expect(putParams.url).to.equal(`${instance.membershipApi}/set-default-payment/${mockPaymentMethodId}?userId=${mockUserId}`); }); it('calls the correct URL when paymentGateway is set', async () => { const mockGateway = 'my-gateway-name'; await instance.updateDefaultPaymentMethod(mockUserId, mockPaymentMethodId, mockGateway); const putParams = instance.requestPut.getCall(0).args[0]; // Path checks expect(putParams).to.have.property('url'); expect(putParams.url).to.equal(`${instance.membershipApi}/set-default-payment/${mockPaymentMethodId}?userId=${mockUserId}&paymentGateway=${mockGateway}`); }); it('calls the correct URL when isPayNow is set', async () => { const isPayNow = true; await instance.updateDefaultPaymentMethod(mockUserId, mockPaymentMethodId,undefined, isPayNow); const putParams = instance.requestPut.getCall(0).args[0]; // Path checks expect(putParams).to.have.property('url'); expect(putParams.url).to.equal(`${instance.membershipApi}/set-default-payment/${mockPaymentMethodId}?userId=${mockUserId}&isPayNow=${isPayNow}`); }); }); describe('createPaymentMethod', async () => { const mockUserDetails = { id: 'mock-id', email: 'mock-email', countryCode: 'mock-country' }; const mockPaymentDetails = { currencyCode: 'GBP', paypalBAID: 'mock-baid', paypalEmail: 'mock-paypal-email' }; beforeEach(() => { instance.requestPost.resolves(); }); it('calls the endpoint properly', async () => { await instance.createPaymentMethod(mockUserDetails, mockPaymentDetails); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams).to.have.property('url'); expect(postParams.url).to.equal(`${instance.membershipApi}/payment-methods`); expect(postParams.key).to.equal(instance.subscriptionApiKey); expect(postParams.body).to.have.property('paymentMethod'); expect(postParams.body.paymentMethod).to.have.property('user'); expect(postParams.body.paymentMethod).to.have.property('deliveryAddress'); expect(postParams.body.paymentMethod).to.have.property('paymentDetails'); // User mappings expect(postParams.body.paymentMethod.user).to.have.property('userId', mockUserDetails.id); expect(postParams.body.paymentMethod.user).to.have.property('email', mockUserDetails.email); expect(postParams.body.paymentMethod.user).to.have.property('countryCode', mockUserDetails.countryCode); // Delivery Address expect(postParams.body.paymentMethod.deliveryAddress).to.have.property('countryCode', mockUserDetails.countryCode); // Payment details expect(postParams.body.paymentMethod.paymentDetails).to.have.property('paymentType', 'PAYPAL'); expect(postParams.body.paymentMethod.paymentDetails).to.have.property('paypalBAID', mockPaymentDetails.paypalBAID); expect(postParams.body.paymentMethod.paymentDetails).to.have.property('paypalEmail', mockPaymentDetails.paypalEmail); expect(postParams.body.paymentMethod.paymentDetails).to.have.property('currencyCode', mockPaymentDetails.currencyCode); }); }); });