import 'mocha'; import * as chai from 'chai'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import cashierPayments from 'cashier-payments-sdk/dist/src'; import CashierPaymentsDriver from './CashierPaymentsWidget.driver'; import {eventually} from '../../../test/helpers'; const should = chai.should(); chai.use(sinonChai); describe('CashierPaymentsWidget component', () => { let driver: CashierPaymentsDriver = null; beforeEach(() => driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.payOffline() .given.settingsResponse() .given.paymentDetails() .given.charge() .when.render({}) ); afterEach(() => driver.cleanup()); it(`should be rendered`, () => eventually(() => chai.expect(driver.is.rendered()).to.be.true) ); it(`should have correct maxWidth`, () => { chai.expect(driver.when.render({maxWidth: 200}).get.maxWidth(200)).to.be.true; }); it(`should have correct maxWidth`, () => { chai.expect(driver.when.render({}).get.maxWidth(0)).to.be.true; }); it(`should present the payment methods`, () => eventually(() => driver.get.paymentMethodItems().should.have.lengthOf(10)) ); it(`should mark the first payment method as selected by default`, () => eventually(() => driver.get.creditCardPaymentMethodItem().props().checked.should.be.true) ); it(`should present the first payment method's payment component`, () => eventually(() => chai.expect(driver.get.payPalPaymentComponent().exists()).to.be.true) ); it(`should present the submit button by default`, () => eventually(() => { chai.expect(driver.get.submitButton().length).to.be.equal(1); }) ); it(`should present the submit button`, () => { driver.when.updateProps({externalSubmitButton: true}); return chai.expect(driver.get.submitButton().length).to.be.equal(0); }); it(`should not present the terms input by default`, () => chai.expect(driver.get.termsInput().length).to.be.equal(0) ); it(`should present the terms input`, () => { setTimeout(() => { driver.when.updateProps({useTermsAndConditions: true}); }); return eventually(() => { chai.expect(driver.get.termsInput().length).to.be.equal(1); }); }); describe('render by steps', () => { beforeEach(() => { driver.when.render({ showOnlyCurrentStep: true, }); }); it(`should render only payment methods on paymentMethod step`, () => { return eventually(() => { driver.get.paymentMethodItems().should.have.lengthOf(10); chai.expect(driver.get.creditCardPaymentComponent().length).to.be.equal(0); chai.expect(driver.get.submitButton().length).to.be.equal(0); }); }); it(`should render only payment detail and submit button on paymentDetail step`, async () => { await eventually(() => driver.when.creditCardPaymentMethodItemSelected()); return eventually(() => { chai.expect(driver.get.creditCardPaymentComponent().length).to.be.equal(1); chai.expect(driver.get.submitButton().length).to.be.equal(1); driver.get.paymentMethodItems().should.have.lengthOf(0); }); }); it(`should show step title`, async () => { await eventually(() => chai.expect(driver.is.rendered()).to.be.true); chai.expect(driver.get.stepTitle()).to.be.equal('01. Payment Method'); await eventually(() => { driver.when.creditCardPaymentMethodItemSelected(); chai.expect(driver.get.stepTitle()).to.be.equal('02. Payment Details'); }); }); it(`should show back button`, async () => { await eventually(() => chai.expect(driver.is.rendered()).to.be.true); chai.expect(driver.is.renderedBackButton()).to.be.false; await eventually(() => { driver.when.creditCardPaymentMethodItemSelected(); chai.expect(driver.get.stepTitle()).to.be.equal('02. Payment Details'); driver.when.goBack(); chai.expect(driver.get.stepTitle()).to.be.equal('01. Payment Method'); }); }); }); describe('Select Credit Card payment method', () => { beforeEach(() => driver.when.creditCardPaymentMethodItemSelected()); it(`should mark the Credit Card as selected`, () => eventually(() => driver.get.creditCardPaymentMethodItem().props().checked.should.be.true) ); it(`should present the Credit Card payment component`, () => eventually(() => chai.expect(driver.get.creditCardPaymentComponent().exists()).to.be.true) ); it('should notify once PaymentMethodChanged', () => eventually(() => driver.get.paymentMethodChangedHandler().should.have.been.calledTwice) ); it('should notify PaymentMethodChanged with Credit Card', () => eventually(() => driver.get.paymentMethodChangedHandler().should.have.been.calledWith('creditCard', { id: 'creditCard', popupSupported: false, })) ); }); describe('API', () => { describe('Tokenize', () => { it('should tokenize credit card', (done) => { const eToken = Math.floor(Math.random() * 1000); driver.when.creditCardPaymentMethodItemSelected(); driver.when.emulatePostMessageToIframe({ token: eToken, creditCard: { lastFourDigits: '4580', expiration: { year: 2020, month: 12 }, additionalInformation: {} } }); driver.when.tokenize().then((data) => { chai.expect(data).to.be.eql({ token: eToken, creditCard: { lastFourDigits: '4580', expiration: { year: 2020, month: 12 }, additionalInformation: {} } }); done(); }); }); it('should reject promise when tokenize not credit card', async () => { driver.when.offlinePaymentMethodItemSelected(); try { const token = await driver.when.tokenize(); } catch (e) { chai.expect(e).to.be.eql({code: 404}); } }); }); describe('initializePayment', () => { it('should initialize payment for credit card', (done) => { const eToken = Math.floor(Math.random() * 1000); driver.when.creditCardPaymentMethodItemSelected(); driver.when.emulatePostMessageToIframe({ token: eToken, creditCard: { lastFourDigits: '4580', expiration: { year: 2020, month: 12 }, additionalInformation: {} } }); driver.when.initializePayment().then((data) => { chai.expect(data).to.be.eql({detailsId: '12345'}); done(); }); }); }); describe('Pay', () => { it('should pay credit card', () => { driver.given.charge(); driver.when.creditCardPaymentMethodItemSelected(); driver.when.emulatePostMessageToIframe({ token: { token: 'ab89ed4e-9fba-4a42-9d31-bb6d3bab2b9d' } }); setTimeout(() => { driver.when.pay(); }); return eventually(() => { chai.expect(driver.is.onPaymentCompleteCalled('creditCard', { status: 'OK', paymentMethod: 'creditCard', errorCode: '', translatedError: '', chargeId: '123', isPending: false, })).to.be.true; }); }); it('should reject promise when pay not credit card', async () => { driver.when.offlinePaymentMethodItemSelected(); try { const token = await driver.when.pay(); } catch (e) { chai.expect(e).to.be.eql({code: 404}); } }); }); describe('ContinuePayment', () => { it('should continue payment with payPal', (done) => { driver.when.payPalPaymentMethodItemSelected(); driver.when.continuePayment({redirectUrl: 'http//:localhost:1111', transactionStatus: 'IN_PROCESS'}).then((obj) => { chai.expect(obj).to.be.eql({ status: 'APPROVED', errorCode: '', translatedError: '', paymentMethod: 'payPal' }); done(); }); setTimeout(() => { (window).onWalletPaymentCompleted({status: 'APPROVED'}); }); }); it('should resolve promise when tokenize not credit card', async () => { driver.when.offlinePaymentMethodItemSelected(); const obj = await driver.when.continuePayment({transactionStatus: 'APPROVED'}); chai.expect(obj).to.be.eql({ errorCode: '', translatedError: '', status: 'APPROVED', paymentMethod: 'offline' }); }); }); describe('LocalizeError', () => { it('should localize error', () => { return eventually(async () => { const errorTranslated = await driver.when.localizeError('1000', 'en'); chai.expect(errorTranslated).to.be.equal('This payment provider is not available at the moment. Please try again in a few minutes.'); const errorTranslated2 = await driver.when.localizeError('000', 'en'); chai.expect(errorTranslated2).to.be.equal(''); const errorTranslated3 = await driver.when.localizeError('1000'); chai.expect(errorTranslated3).to.be.equal('This payment provider is not available at the moment. Please try again in a few minutes.'); }); }); }); describe('Submit', () => { describe('Offline', () => { beforeEach(() => driver.when.offlinePaymentMethodItemSelected()); it('all payments should be visible', async () => { sinon.stub(cashierPayments, 'submit'); driver.get.isMethodCollapsed().should.be.false; const submitResult = await driver.when.submit(); chai.expect(cashierPayments.submit.calledOnce).to.be.true; cashierPayments.submit.restore(); }); }); describe('Paypal', () => { beforeEach(() => driver.when.payPalPaymentMethodItemSelected()); it('all payments should be not visible', async () => { sinon.stub(cashierPayments, 'submit'); const submitResult = await driver.when.submit(); chai.expect(cashierPayments.submit.calledOnce).to.be.true; cashierPayments.submit.restore(); }); }); describe('Credit card', () => { beforeEach(() => driver.when.creditCardPaymentMethodItemSelected()); it('all payments should be not visible', async () => { sinon.stub(cashierPayments, 'submit'); const submitResult = await driver.when.submit(); chai.expect(cashierPayments.submit.calledOnce).to.be.true; cashierPayments.submit.restore(); }); }); }); describe('Validate', () => { describe('Offline', () => { beforeEach(() => driver.when.offlinePaymentMethodItemSelected()); it('should (always) return true', async () => { const validationResult = await driver.when.validate(); chai.expect(validationResult).to.be.eql({isValid: true}); }); }); describe('Wallet', () => { beforeEach(() => driver.when.payPalPaymentMethodItemSelected()); it('should (always) return true', (done) => { driver.when.validate().then(result => { chai.expect(result).to.be.eql({isValid: true}); done(); }); }); }); }); describe('Collapse', () => { describe('Offline', () => { beforeEach(() => driver.when.offlinePaymentMethodItemSelected()); it('all payments should be not visible', async () => { driver.get.isMethodCollapsed().should.be.false; chai.expect(driver.is.collapsed()).to.be.false; const collapseResult = await driver.when.collapse(); delete collapseResult.message; collapseResult.should.eql({ method: 'offline', status: 'collapsed', type: 'COLLAPSE', }); driver.get.isMethodCollapsed().should.be.true; chai.expect(driver.is.collapsed()).to.be.true; }); }); describe('Paypal', () => { beforeEach(() => driver.when.payPalPaymentMethodItemSelected()); it('all payments should be not visible', async () => { driver.get.isMethodCollapsed().should.be.false; const collapseResult = await driver.when.collapse(); delete collapseResult.message; chai.expect(collapseResult).to.be.eql({ method: 'payPal', status: 'collapsed', type: 'COLLAPSE', }); driver.get.isMethodCollapsed().should.be.true; }); }); }); describe('Expand', () => { describe('Offline', () => { beforeEach(() => driver.when.offlinePaymentMethodItemSelected()); it('all payments should be visible', async () => { driver.get.isMethodCollapsed().should.be.false; const collapseResult = await driver.when.collapse(); delete collapseResult.message; collapseResult.should.eql({ method: 'offline', type: 'COLLAPSE', status: 'collapsed' }); driver.get.isMethodCollapsed().should.be.true; const expandResult = await driver.when.expand(); expandResult.should.eql({ method: 'offline', type: 'EXPAND', status: 'expanded' }); driver.get.isMethodCollapsed().should.be.false; }); }); describe('Paypal', () => { beforeEach(() => driver.when.payPalPaymentMethodItemSelected()); it('all payments should be not visible', async () => { driver.get.isMethodCollapsed().should.be.false; const collapseResult = await driver.when.collapse(); delete collapseResult.message; collapseResult.should.eql({ method: 'payPal', type: 'COLLAPSE', status: 'collapsed' }); driver.get.isMethodCollapsed().should.be.true; const expandResult = await driver.when.expand(); expandResult.should.eql({ method: 'payPal', type: 'EXPAND', status: 'expanded' }); driver.get.isMethodCollapsed().should.be.false; }); }); describe('Credit card', () => { beforeEach(() => driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .when.render({})); it('all payments should be not visible', async () => { return eventually(async () => { driver.when.creditCardPaymentMethodItemSelected(); driver.get.isMethodCollapsed().should.be.false; driver.when.collapse(); const expandResult = await driver.when.expand(); expandResult.should.eql({ method: 'creditCard', type: 'EXPAND', status: 'expanded' }); driver.get.isMethodCollapsed().should.be.false; }); }); }); }); }); describe('General tests', () => { beforeEach(() => driver.when.creditCardPaymentMethodItemSelected()); it('Error message should be rendered', async () => { driver.when.errorSetted('Error message').get.errorMessage().should.equal('Error message'); driver.get.isMethodCollapsed().should.be.false; }); it('Should set in progress state', () => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .given.charge() .when.render({host: ''}); setTimeout(() => { driver.when.creditCardPaymentMethodItemSelected(); driver.when.emulatePostMessageToIframe({ token: { token: 'ab89ed4e-9fba-4a42-9d31-bb6d3bab2b9d' } }); chai.expect(driver.is.onPaymentStartCalled('creditCard')).to.be.false; driver.when.submit(); }); return eventually(() => { chai.expect(driver.is.onPaymentStartCalled('creditCard')).to.be.true; }); }); it('Should set not in progress state without mocked reply from iframe', () => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .given.charge() .when.render({host: ''}); setTimeout(() => { driver.when.creditCardPaymentMethodItemSelected(); chai.expect(driver.is.paymentInProgress()).to.be.false; driver.when.submit(); }); return eventually(() => { chai.expect(driver.is.paymentInProgress()).to.be.false; }); }); it('Should call onPaymentStart', () => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .given.charge() .when.render({host: ''}); setTimeout(() => { driver.when.creditCardPaymentMethodItemSelected(); driver.when.emulatePostMessageToIframe({ token: { token: 'ab89ed4e-9fba-4a42-9d31-bb6d3bab2b9d' } }); chai.expect(driver.is.onPaymentStartCalled('creditCard')).to.be.false; driver.when.submit(); }); return eventually(() => { chai.expect(driver.is.onPaymentStartCalled('creditCard'), 'driver.is.onPaymentStartCalled').to.be.true; chai.expect(driver.is.appLoaded(), 'driver.is.appLoaded').to.be.true; chai.expect(driver.is.interactionStarted('pay-with-creditCard'), 'driver.is.interactionStarted').to.be.true; chai.expect(driver.is.interactionEnded('pay-with-creditCard'), 'driver.is.interactionEnded').to.be.true; }); }); it('Should handle message from CC', async () => { sinon.stub(cashierPayments, 'init'); const paymentResult = sinon.spy(); driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .given.charge() .when.render({host: '', paymentResult}); const onMessage = cashierPayments.init.args[0][0].onMessage; onMessage('creditCard', {type: 'CLIENT_FORM_SUBMIT', status: 'failure'}); chai.expect(paymentResult.calledOnce).to.be.true; cashierPayments.init.restore(); }); it('Submit button should be disabled when payed', () => { const paymentResult = sinon.spy(); driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .given.charge() .when.render({host: '', paymentResult}); chai.expect(driver.get.successPayedState()).to.be.false; setTimeout(() => { driver.when.offlinePaymentMethodItemSelected(); driver.when.submit(); }); return eventually(() => { chai.expect(driver.is.onPaymentStartCalled('offline')).to.be.true; chai.expect(paymentResult.called).to.be.true; chai.expect(driver.get.successPayedState()).to.be.true; }); }); it('Should log success payment for payPal of open in new tab', () => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.charge() .when.render({walletPaymentInNewTab: false}); setTimeout(() => { driver.when.payPalPaymentMethodItemSelected(); driver.when.submit(); }); return eventually(() => { chai.expect(driver.is.onPaymentStartCalled('payPal')).to.be.true; chai.expect(driver.is.appLoaded()).to.be.true; chai.expect(driver.is.interactionStarted('pay-with-payPal')).to.be.true; chai.expect(driver.is.interactionEnded('pay-with-payPal')).to.be.true; }); }); it('Should call onPaymentComplete for offline', () => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .given.charge() .when.render({host: ''}); setTimeout(() => { driver.when.offlinePaymentMethodItemSelected(); driver.when.submit(); }); return eventually(() => { chai.expect(driver.is.onPaymentStartCalled('offline')).to.be.true; chai.expect(driver.is.appLoaded()).to.be.true; chai.expect(driver.is.interactionStarted('pay-with-offline')).to.be.true; chai.expect(driver.is.interactionEnded('pay-with-offline')).to.be.true; }); }); it('Should call onPaymentComplete for offline and call paymentResult', () => { const paymentResult = sinon.spy(); driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.settingsResponse() .given.paymentDetails() .given.payOffline() .given.charge() .when.render({host: '', paymentResult}); setTimeout(() => { driver.when.offlinePaymentMethodItemSelected(); driver.when.submit(); }); return eventually(() => { chai.expect(driver.is.onPaymentStartCalled('offline')).to.be.true; chai.expect(driver.is.appLoaded()).to.be.true; chai.expect(driver.is.interactionStarted('pay-with-offline')).to.be.true; chai.expect(driver.is.interactionEnded('pay-with-offline')).to.be.true; chai.expect(paymentResult.called).to.be.true; }); }); }); it(`should present rtl className`, () => { chai.expect(driver.when.render({configuration: {locale: 'he'}}).is.rtlClassName()).to.be.true; }); it(`should not present rtl className`, () => { chai.expect(driver.when.render({}).is.rtlClassName()).to.be.false; }); }); describe('CashierPaymentsWidget component - single payment method', () => { let driver: CashierPaymentsDriver = null; beforeEach(() => driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.payOffline() .given.singlePaymentMethodSettingsResponse() .given.paymentDetails() .when.render({}) ); afterEach(() => driver.cleanup()); it(`should not present the method list`, () => { eventually(() => { chai.expect(driver.get.paymentMethodItems()).to.have.lengthOf(0); }); }); }); // TODO: Remove `skip` after BE part will be ready describe.skip(`CashierPaymentsWidget component`, () => { let driver: CashierPaymentsDriver = null; afterEach(() => driver.cleanup()); it(`Should render mandatory fields if no were passed`, () => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .when.mockRequests() .when.render({}); return eventually(() => { chai.expect(driver.get.mandatoryFieldsItems()).to.have.lengthOf(3); }); }); it(`Should skip to render mandatory field if was passed`, () => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .given.mandatoryFields({firstName: 'John'}) .when.mockRequests() .when.render({}); return eventually(() => { chai.expect(driver.get.mandatoryFieldsItems()).to.have.lengthOf(2); }); }); }); describe(`CashierPaymentsWidget with ref`, () => { let driver: CashierPaymentsDriver = null; beforeEach(() => { driver = new CashierPaymentsDriver() .given.configuration() .given.cashierPaymentsApi() .when.mockRequests() .when.render({}); }); afterEach(() => driver.cleanup()); it(`Should fetch mandatory fields from payment methods`, async () => { const mandatoryFields = await driver.fetch.mandatoryFields(); chai.expect(mandatoryFields).to.deep.eq([ 'firstName', 'lastName', 'phoneNumber', ]); }); // Unskip when provide data for credit card iframe it['skip'](`Should fetch mandatory fields and pay`, async () => { driver.when.emulatePostMessageToIframe({ token: { token: 'ab89ed4e-9fba-4a42-9d31-bb6d3bab2b9d' } }); const creditCardActivatingPromise = new Promise(resolve => { setTimeout(() => { driver.when.creditCardPaymentMethodItemSelected(); resolve(); }); }); await creditCardActivatingPromise; const mandatoryFields = await driver.fetch.mandatoryFields(); chai.expect(mandatoryFields).to.deep.eq([ 'firstName', 'lastName', 'phoneNumber', ]); await driver.when.pay(); return eventually(() => { chai.expect(driver.is.onPaymentCompleteCalled('creditCard', {})).to.equal(true); }); }); });