From ebb465e3bab8c1f2c9636a47f15306f35f12a850 Mon Sep 17 00:00:00 2001 From: David Chin Date: Tue, 18 Apr 2017 15:39:14 +1000 Subject: [PATCH] refactor(payment): Extract classes from initializeOffsitePayment function --- src/client/client.js | 17 ++-- src/payment/index.js | 2 - src/payment/initialize-offsite-payment.js | 19 ---- src/payment/offsite-mappers/index.js | 5 - src/payment/offsite-mappers/map-to-address.js | 27 ------ .../offsite-mappers/map-to-billing-address.js | 10 -- .../offsite-mappers/map-to-customer.js | 22 ----- src/payment/offsite-mappers/map-to-meta.js | 16 ---- src/payment/offsite-mappers/map-to-payload.js | 39 -------- .../map-to-shipping-address.js | 10 -- src/payment/offsite-mappers/map-to-store.js | 15 --- src/payment/offsite-payment-initializer.js | 63 +++++++++++++ .../offsite-payment-mappers/address-mapper.js | 52 +++++++++++ .../customer-mapper.js | 30 ++++++ .../offsite-payment-mappers/meta-mapper.js | 24 +++++ .../offsite-payment-mappers/payload-mapper.js | 92 +++++++++++++++++++ .../offsite-payment-mappers/store-mapper.js | 23 +++++ src/payment/payment-method-ids.js | 2 + .../payment-method-id-mapper.js | 22 +++++ src/payment/url-helper.js | 35 +++++++ test/client/client.spec.js | 12 ++- .../initialilze-offsite-payment.spec.js | 41 --------- .../offsite-mappers/map-to-address.spec.js | 29 ------ .../offsite-mappers/map-to-payload.spec.js | 61 ------------ .../offsite-payment-initializer.spec.js | 58 ++++++++++++ .../address-mapper.spec.js | 50 ++++++++++ .../customer-mapper.spec.js} | 10 +- .../meta-mapper.spec.js} | 10 +- .../payload-mapper.spec.js | 83 +++++++++++++++++ .../store-mapper.spec.js} | 10 +- .../payment-method-id-mapper.spec.js | 21 +++++ test/payment/url-helper.spec.js | 19 ++++ 32 files changed, 606 insertions(+), 323 deletions(-) delete mode 100644 src/payment/initialize-offsite-payment.js delete mode 100644 src/payment/offsite-mappers/index.js delete mode 100644 src/payment/offsite-mappers/map-to-address.js delete mode 100644 src/payment/offsite-mappers/map-to-billing-address.js delete mode 100644 src/payment/offsite-mappers/map-to-customer.js delete mode 100644 src/payment/offsite-mappers/map-to-meta.js delete mode 100644 src/payment/offsite-mappers/map-to-payload.js delete mode 100644 src/payment/offsite-mappers/map-to-shipping-address.js delete mode 100644 src/payment/offsite-mappers/map-to-store.js create mode 100644 src/payment/offsite-payment-initializer.js create mode 100644 src/payment/offsite-payment-mappers/address-mapper.js create mode 100644 src/payment/offsite-payment-mappers/customer-mapper.js create mode 100644 src/payment/offsite-payment-mappers/meta-mapper.js create mode 100644 src/payment/offsite-payment-mappers/payload-mapper.js create mode 100644 src/payment/offsite-payment-mappers/store-mapper.js create mode 100644 src/payment/payment-method-ids.js create mode 100644 src/payment/payment-method-mappers/payment-method-id-mapper.js create mode 100644 src/payment/url-helper.js delete mode 100644 test/payment/initialilze-offsite-payment.spec.js delete mode 100644 test/payment/offsite-mappers/map-to-address.spec.js delete mode 100644 test/payment/offsite-mappers/map-to-payload.spec.js create mode 100644 test/payment/offsite-payment-initializer.spec.js create mode 100644 test/payment/offsite-payment-mappers/address-mapper.spec.js rename test/payment/{offsite-mappers/map-to-customer.spec.js => offsite-payment-mappers/customer-mapper.spec.js} (69%) rename test/payment/{offsite-mappers/map-to-meta.spec.js => offsite-payment-mappers/meta-mapper.spec.js} (55%) create mode 100644 test/payment/offsite-payment-mappers/payload-mapper.spec.js rename test/payment/{offsite-mappers/map-to-store.spec.js => offsite-payment-mappers/store-mapper.spec.js} (51%) create mode 100644 test/payment/payment-method-mappers/payment-method-id-mapper.spec.js create mode 100644 test/payment/url-helper.spec.js diff --git a/src/client/client.js b/src/client/client.js index a03fc59c..27238a0f 100644 --- a/src/client/client.js +++ b/src/client/client.js @@ -1,4 +1,5 @@ -import { PAYMENT_TYPES, initializeOffsitePayment, submitPayment } from '../payment'; +import { PAYMENT_TYPES, submitPayment } from '../payment'; +import OffsitePaymentInitializer from '../payment/offsite-payment-initializer'; export default class Client { /** @@ -6,8 +7,9 @@ export default class Client { * @param {Object} config * @param {string} config.host */ - constructor({ host }) { - this.host = host; + constructor(config) { + this.host = config.host; + this.offsitePaymentInitializer = OffsitePaymentInitializer.create(config); } /** @@ -17,14 +19,7 @@ export default class Client { * @returns {void} */ initializeOffsitePayment(data, callback) { - const { paymentMethod = {} } = data; - const options = { host: this.host }; - - if (paymentMethod.type !== PAYMENT_TYPES.HOSTED) { - throw new Error(`${paymentMethod.type} is not supported.`); - } - - initializeOffsitePayment(data, options, callback); + this.offsitePaymentInitializer.initializeOffsitePayment(data, callback); } /** diff --git a/src/payment/index.js b/src/payment/index.js index 7758b741..f6d31202 100644 --- a/src/payment/index.js +++ b/src/payment/index.js @@ -1,9 +1,7 @@ import * as PAYMENT_TYPES from './payment-types'; -import initializeOffsitePayment from './initialize-offsite-payment'; import submitPayment from './submit-payment'; export { PAYMENT_TYPES, - initializeOffsitePayment, submitPayment, }; diff --git a/src/payment/initialize-offsite-payment.js b/src/payment/initialize-offsite-payment.js deleted file mode 100644 index 9a4e28d5..00000000 --- a/src/payment/initialize-offsite-payment.js +++ /dev/null @@ -1,19 +0,0 @@ -import { createFormPoster } from 'form-poster'; -import { getOffsitePaymentUrl } from './urls'; -import { mapToPayload } from './offsite-mappers'; - -/** - * Initialize offsite payment - * @param {PaymentRequestData} data - * @param {Object} [options = {}] - * @param {string} [options.host] - * @param {Function} [callback] - * @returns {void} - */ -export default function initializeOffsitePayment(data, { host } = {}, callback) { - const payload = mapToPayload(data); - const url = getOffsitePaymentUrl(host); - const formPoster = createFormPoster(); - - formPoster.postForm(url, payload, callback); -} diff --git a/src/payment/offsite-mappers/index.js b/src/payment/offsite-mappers/index.js deleted file mode 100644 index ba1edea8..00000000 --- a/src/payment/offsite-mappers/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import mapToPayload from './map-to-payload'; - -export { - mapToPayload, -}; diff --git a/src/payment/offsite-mappers/map-to-address.js b/src/payment/offsite-mappers/map-to-address.js deleted file mode 100644 index a38c4d88..00000000 --- a/src/payment/offsite-mappers/map-to-address.js +++ /dev/null @@ -1,27 +0,0 @@ -import { omitNil, toSnakeCase } from '../../common/utils'; - -/** - * Map to address - * @param {PaymentRequestData} data - * @param {string} addressKey - * @returns {Object} - */ -export default function mapToAddress(data, addressKey) { - const address = data[addressKey] || {}; - const formattedAddressKey = toSnakeCase(addressKey); - - return omitNil({ - [`${formattedAddressKey}_city`]: address.city, - [`${formattedAddressKey}_company`]: address.company, - [`${formattedAddressKey}_country_code`]: address.countryCode, - [`${formattedAddressKey}_country`]: address.country, - [`${formattedAddressKey}_first_name`]: address.firstName, - [`${formattedAddressKey}_last_name`]: address.lastName, - [`${formattedAddressKey}_phone`]: address.phone, - [`${formattedAddressKey}_state_code`]: address.provinceCode, - [`${formattedAddressKey}_state`]: address.province, - [`${formattedAddressKey}_street_1`]: address.addressLine1, - [`${formattedAddressKey}_street_2`]: address.addressLine2, - [`${formattedAddressKey}_zip`]: address.postCode, - }); -} diff --git a/src/payment/offsite-mappers/map-to-billing-address.js b/src/payment/offsite-mappers/map-to-billing-address.js deleted file mode 100644 index 4c393c49..00000000 --- a/src/payment/offsite-mappers/map-to-billing-address.js +++ /dev/null @@ -1,10 +0,0 @@ -import mapToAddress from './map-to-address'; - -/** - * Map to billing address - * @param {PaymentRequestData} data - * @returns {Object} - */ -export default function mapToBillingAddress(data) { - return mapToAddress(data, 'billingAddress'); -} diff --git a/src/payment/offsite-mappers/map-to-customer.js b/src/payment/offsite-mappers/map-to-customer.js deleted file mode 100644 index 62e980fa..00000000 --- a/src/payment/offsite-mappers/map-to-customer.js +++ /dev/null @@ -1,22 +0,0 @@ -import { omitNil } from '../../common/utils'; - -/** - * Map to customer - * @param {PaymentRequestData} data - * @returns {Object} - */ -export default function mapToCustomer(data) { - const { customer = {}, quoteMeta = {}, store = {} } = data; - - return omitNil({ - customer_browser_info: navigator.userAgent, - customer_email: customer.email, - customer_first_name: customer.firstName, - customer_geo_ip_country_code: quoteMeta.request ? quoteMeta.request.geoCountryCode : null, - customer_last_name: customer.lastName, - customer_locale: store.storeLanguage, - customer_name: customer.name, - customer_phone: customer.phoneNumber, - customer_reference: customer.email, - }); -} diff --git a/src/payment/offsite-mappers/map-to-meta.js b/src/payment/offsite-mappers/map-to-meta.js deleted file mode 100644 index fcc6036e..00000000 --- a/src/payment/offsite-mappers/map-to-meta.js +++ /dev/null @@ -1,16 +0,0 @@ -import { omitNil } from '../../common/utils'; - -/** - * Map to meta - * @param {PaymentRequestData} data - * @returns {Object} - */ -export default function mapToMeta(data) { - const { source } = data; - - return omitNil({ - meta_referrer: document.referrer, - meta_source: source, - meta_user_agent: navigator.userAgent, - }); -} diff --git a/src/payment/offsite-mappers/map-to-payload.js b/src/payment/offsite-mappers/map-to-payload.js deleted file mode 100644 index a693f786..00000000 --- a/src/payment/offsite-mappers/map-to-payload.js +++ /dev/null @@ -1,39 +0,0 @@ -import objectAssign from 'object-assign'; -import { omitNil, toString } from '../../common/utils'; -import { mapToId } from '../../payment-method'; -import mapToBillingAddress from './map-to-billing-address'; -import mapToCustomer from './map-to-customer'; -import mapToMeta from './map-to-meta'; -import mapToShippingAddress from './map-to-shipping-address'; -import mapToStore from './map-to-store'; - -/** - * Map to payload - * @param {PaymentRequestData} data - * @returns {Object} - */ -export default function mapToPayload(data) { - const { authToken, order = {}, paymentMethod = {} } = data; - - const payload = objectAssign( - { - amount: order.grandTotal ? order.grandTotal.integerAmount : null, - bc_auth_token: authToken, - currency: order.currency, - gateway: paymentMethod.gateway, - notify_url: order.callbackUrl, - order_id: toString(order.orderId), - page_title: document.title, - payment_method_id: mapToId(paymentMethod), - reference_id: toString(order.orderId), - return_url: paymentMethod.returnUrl || (order.payment ? order.payment.returnUrl : null), - }, - mapToBillingAddress(data), - mapToCustomer(data), - mapToMeta(data), - mapToShippingAddress(data), - mapToStore(data) - ); - - return omitNil(payload); -} diff --git a/src/payment/offsite-mappers/map-to-shipping-address.js b/src/payment/offsite-mappers/map-to-shipping-address.js deleted file mode 100644 index c9198ba8..00000000 --- a/src/payment/offsite-mappers/map-to-shipping-address.js +++ /dev/null @@ -1,10 +0,0 @@ -import mapToAddress from './map-to-address'; - -/** - * Map to shipping address - * @param {PaymentRequestData} data - * @returns {Object} - */ -export default function mapToShippingAddress(data) { - return mapToAddress(data, 'shippingAddress'); -} diff --git a/src/payment/offsite-mappers/map-to-store.js b/src/payment/offsite-mappers/map-to-store.js deleted file mode 100644 index 76a2cfb7..00000000 --- a/src/payment/offsite-mappers/map-to-store.js +++ /dev/null @@ -1,15 +0,0 @@ -import { omitNil } from '../../common/utils'; - -/** - * Map to store - * @param {PaymentRequestData} data - * @returns {Object} - */ -export default function mapToStore(data) { - const { store = {} } = data; - - return omitNil({ - store_hash: store.storeHash, - store_id: `${store.storeId}`, - }); -} diff --git a/src/payment/offsite-payment-initializer.js b/src/payment/offsite-payment-initializer.js new file mode 100644 index 00000000..aa233aff --- /dev/null +++ b/src/payment/offsite-payment-initializer.js @@ -0,0 +1,63 @@ +import { createFormPoster } from 'form-poster'; +import { HOSTED } from './payment-types'; +import PayloadMapper from './offsite-payment-mappers/payload-mapper'; +import UrlHelper from './url-helper'; + +export default class OffsitePaymentInitializer { + /** + * @param {Object} config + * @returns {OffsitePaymentInitializer} + */ + static create(config) { + const urlHelper = UrlHelper.create(config); + const formPoster = createFormPoster(); + const payloadMapper = PayloadMapper.create(); + + return new OffsitePaymentInitializer(urlHelper, formPoster, payloadMapper); + } + + /** + * @param {UrlHelper} urlHelper + * @param {FormPoster} formPoster + * @param {PayloadMapper} payloadMapper + * @returns {void} + */ + constructor(urlHelper, formPoster, payloadMapper) { + /** + * @private + * @type {UrlHelper} + */ + this.urlHelper = urlHelper; + + /** + * @private + * @type {FormPoster} + */ + this.formPoster = formPoster; + + /** + * @private + * @type {PayloadMapper} + */ + this.payloadMapper = payloadMapper; + } + + /** + * @param {PaymentRequestData} data + * @param {Function} [callback] + * @returns {void} + * @throws {Error} + */ + initializeOffsitePayment(data, callback) { + const { paymentMethod = {} } = data; + + if (paymentMethod.type !== HOSTED) { + throw new Error(`${paymentMethod.type} is not supported.`); + } + + const payload = this.payloadMapper.mapToPayload(data); + const url = this.urlHelper.getOffsitePaymentUrl(); + + this.formPoster.postForm(url, payload, callback); + } +} diff --git a/src/payment/offsite-payment-mappers/address-mapper.js b/src/payment/offsite-payment-mappers/address-mapper.js new file mode 100644 index 00000000..c4dd0e38 --- /dev/null +++ b/src/payment/offsite-payment-mappers/address-mapper.js @@ -0,0 +1,52 @@ +import { omitNil, toSnakeCase } from '../../common/utils'; + +export default class AddressMapper { + /** + * @returns {AddressMapper} + */ + static create() { + return new AddressMapper(); + } + + /** + * @param {PaymentRequestData} data + * @returns {Object} + */ + mapToBillingAddress(data) { + return this.mapToAddress(data, 'billingAddress'); + } + + /** + * @param {PaymentRequestData} data + * @returns {Object} + */ + mapToShippingAddress(data) { + return this.mapToAddress(data, 'shippingAddress'); + } + + /** + * @private + * @param {PaymentRequestData} data + * @param {string} addressKey + * @returns {Object} + */ + mapToAddress(data, addressKey) { + const address = data[addressKey] || {}; + const formattedAddressKey = toSnakeCase(addressKey); + + return omitNil({ + [`${formattedAddressKey}_city`]: address.city, + [`${formattedAddressKey}_company`]: address.company, + [`${formattedAddressKey}_country_code`]: address.countryCode, + [`${formattedAddressKey}_country`]: address.country, + [`${formattedAddressKey}_first_name`]: address.firstName, + [`${formattedAddressKey}_last_name`]: address.lastName, + [`${formattedAddressKey}_phone`]: address.phone, + [`${formattedAddressKey}_state_code`]: address.provinceCode, + [`${formattedAddressKey}_state`]: address.province, + [`${formattedAddressKey}_street_1`]: address.addressLine1, + [`${formattedAddressKey}_street_2`]: address.addressLine2, + [`${formattedAddressKey}_zip`]: address.postCode, + }); + } +} diff --git a/src/payment/offsite-payment-mappers/customer-mapper.js b/src/payment/offsite-payment-mappers/customer-mapper.js new file mode 100644 index 00000000..7b7f099c --- /dev/null +++ b/src/payment/offsite-payment-mappers/customer-mapper.js @@ -0,0 +1,30 @@ +import { omitNil } from '../../common/utils'; + +export default class CustomerMapper { + /** + * @returns {CustomerMapper} + */ + static create() { + return new CustomerMapper(); + } + + /** + * @param {PaymentRequestData} data + * @returns {Object} + */ + mapToCustomer(data) { + const { customer = {}, quoteMeta = {}, store = {} } = data; + + return omitNil({ + customer_browser_info: navigator.userAgent, + customer_email: customer.email, + customer_first_name: customer.firstName, + customer_geo_ip_country_code: quoteMeta.request ? quoteMeta.request.geoCountryCode : null, + customer_last_name: customer.lastName, + customer_locale: store.storeLanguage, + customer_name: customer.name, + customer_phone: customer.phoneNumber, + customer_reference: customer.email, + }); + } +} diff --git a/src/payment/offsite-payment-mappers/meta-mapper.js b/src/payment/offsite-payment-mappers/meta-mapper.js new file mode 100644 index 00000000..f94295c1 --- /dev/null +++ b/src/payment/offsite-payment-mappers/meta-mapper.js @@ -0,0 +1,24 @@ +import { omitNil } from '../../common/utils'; + +export default class MetaMapper { + /** + * @returns {MetaMapper} + */ + static create() { + return new MetaMapper(); + } + + /** + * @param {PaymentRequestData} data + * @returns {Object} + */ + mapToMeta(data) { + const { source } = data; + + return omitNil({ + meta_referrer: document.referrer, + meta_source: source, + meta_user_agent: navigator.userAgent, + }); + } +} diff --git a/src/payment/offsite-payment-mappers/payload-mapper.js b/src/payment/offsite-payment-mappers/payload-mapper.js new file mode 100644 index 00000000..47e1a4a0 --- /dev/null +++ b/src/payment/offsite-payment-mappers/payload-mapper.js @@ -0,0 +1,92 @@ +import objectAssign from 'object-assign'; +import { omitNil, toString } from '../../common/utils'; +import AddressMapper from './address-mapper'; +import CustomerMapper from './customer-mapper'; +import MetaMapper from './meta-mapper'; +import PaymentMethodIdMapper from '../payment-method-mappers/payment-method-id-mapper'; +import StoreMapper from './store-mapper'; + +export default class PayloadMapper { + /** + * @returns {PayloadMapper} + */ + static create() { + const addressMapper = AddressMapper.create(); + const customerMapper = CustomerMapper.create(); + const metaMapper = MetaMapper.create(); + const paymentMethodIdMapper = PaymentMethodIdMapper.create(); + const storeMapper = StoreMapper.create(); + + return new PayloadMapper(addressMapper, customerMapper, metaMapper, paymentMethodIdMapper, storeMapper); + } + + /** + * @param {AddressMapper} addressMapper + * @param {CustomerMapper} customerMapper + * @param {MetaMapper} metaMapper + * @param {PaymentMethodIdMapper} paymentMethodIdMapper + * @param {StoreMapper} storeMapper + * @returns {Object} + */ + constructor(addressMapper, customerMapper, metaMapper, paymentMethodIdMapper, storeMapper) { + /** + * @private + * @type {AddressMapper} + */ + this.addressMapper = addressMapper; + + /** + * @private + * @type {CustomerMapper} + */ + this.customerMapper = customerMapper; + + /** + * @private + * @type {MetaMapper} + */ + this.metaMapper = metaMapper; + + /** + * @private + * @type {PaymentMethodIdMapper} + */ + this.paymentMethodIdMapper = paymentMethodIdMapper; + + /** + * @private + * @type {StoreMapper} + */ + this.storeMapper = storeMapper; + } + + /** + * @param {PaymentRequestData} data + * @returns {Object} + */ + mapToPayload(data) { + const { authToken, order = {}, paymentMethod = {} } = data; + + const payload = objectAssign( + { + amount: order.grandTotal ? order.grandTotal.integerAmount : null, + bc_auth_token: authToken, + currency: order.currency, + gateway: paymentMethod.gateway, + notify_url: order.callbackUrl, + order_id: order.orderId ? toString(order.orderId) : null, + page_title: document.title ? document.title : null, + payment_method_id: this.paymentMethodIdMapper.mapToId(paymentMethod), + reference_id: order.orderId ? toString(order.orderId) : null, + return_url: paymentMethod.returnUrl || (order.payment ? order.payment.returnUrl : null), + }, + this.addressMapper.mapToBillingAddress(data), + this.customerMapper.mapToCustomer(data), + this.metaMapper.mapToMeta(data), + this.addressMapper.mapToShippingAddress(data), + this.storeMapper.mapToStore(data) + ); + + return omitNil(payload); + } +} diff --git a/src/payment/offsite-payment-mappers/store-mapper.js b/src/payment/offsite-payment-mappers/store-mapper.js new file mode 100644 index 00000000..847f67b0 --- /dev/null +++ b/src/payment/offsite-payment-mappers/store-mapper.js @@ -0,0 +1,23 @@ +import { omitNil, toString } from '../../common/utils'; + +export default class StoreMapper { + /** + * @returns {StoreMapper} + */ + static create() { + return new StoreMapper(); + } + + /** + * @param {PaymentRequestData} data + * @returns {Object} + */ + mapToStore(data) { + const { store = {} } = data; + + return omitNil({ + store_hash: store.storeHash, + store_id: store.storeId ? toString(store.storeId) : null, + }); + } +} diff --git a/src/payment/payment-method-ids.js b/src/payment/payment-method-ids.js new file mode 100644 index 00000000..53b92c84 --- /dev/null +++ b/src/payment/payment-method-ids.js @@ -0,0 +1,2 @@ +export const BRAINTREE = 'braintree'; +export const BRAINTREE_PAYPAL = 'braintreepaypal'; diff --git a/src/payment/payment-method-mappers/payment-method-id-mapper.js b/src/payment/payment-method-mappers/payment-method-id-mapper.js new file mode 100644 index 00000000..245e7846 --- /dev/null +++ b/src/payment/payment-method-mappers/payment-method-id-mapper.js @@ -0,0 +1,22 @@ +import { BRAINTREE, BRAINTREE_PAYPAL } from '../payment-method-ids'; + +export default class PaymentMethodIdMapper { + /** + * @returns {PaymentMethodIdMapper} + */ + static create() { + return new PaymentMethodIdMapper(); + } + + /** + * @param {PaymentMethod} paymentMethod + * @returns {string} + */ + mapToId(paymentMethod) { + if (paymentMethod.id === BRAINTREE_PAYPAL) { + return BRAINTREE; + } + + return paymentMethod.id; + } +} diff --git a/src/payment/url-helper.js b/src/payment/url-helper.js new file mode 100644 index 00000000..b51ddc13 --- /dev/null +++ b/src/payment/url-helper.js @@ -0,0 +1,35 @@ +export default class UrlHelper { + /** + * @param {string} host + * @returns {CustomerMapper} + */ + static create(host) { + return new UrlHelper(host); + } + + /** + * @param {string} host + * @returns {void} + */ + constructor(host) { + /** + * @private + * @type {string} + */ + this.host = host; + } + + /** + * @returns {string} + */ + getOffsitePaymentUrl() { + return `${this.host}/pay/initialize`; + } + + /** + * @returns {string} + */ + getPaymentUrl() { + return `${this.host}/api/public/v1/orders/payments`; + } +} diff --git a/test/client/client.spec.js b/test/client/client.spec.js index a0dfbdef..c6dc09e0 100644 --- a/test/client/client.spec.js +++ b/test/client/client.spec.js @@ -2,18 +2,24 @@ import cloneDeep from 'lodash/cloneDeep'; import { HOSTED } from '../../src/payment/payment-types'; import * as paymentModule from '../../src/payment'; import Client from '../../src/client/client'; +import OffsitePaymentInitializer from '../../src/payment/offsite-payment-initializer'; import paymentRequestDataMock from '../mocks/payment-request-data'; describe('Client', () => { let callback; let client; let config; + let offsitePaymentInitializer; beforeEach(() => { callback = () => {}; config = { host: 'https://bcapp.dev' }; - spyOn(paymentModule, 'initializeOffsitePayment'); + offsitePaymentInitializer = { + initializeOffsitePayment: jasmine.createSpy('initializeOffsitePayment'), + }; + + spyOn(OffsitePaymentInitializer, 'create').and.returnValue(offsitePaymentInitializer); spyOn(paymentModule, 'submitPayment'); }); @@ -35,11 +41,9 @@ describe('Client', () => { }); it('should initialize offsite payment', () => { - const { initializeOffsitePayment } = paymentModule; - client.initializeOffsitePayment(data, callback); - expect(initializeOffsitePayment).toHaveBeenCalledWith(data, { host: config.host }, callback); + expect(offsitePaymentInitializer.initializeOffsitePayment).toHaveBeenCalledWith(data, callback); }); }); diff --git a/test/payment/initialilze-offsite-payment.spec.js b/test/payment/initialilze-offsite-payment.spec.js deleted file mode 100644 index 5d279643..00000000 --- a/test/payment/initialilze-offsite-payment.spec.js +++ /dev/null @@ -1,41 +0,0 @@ -import * as formPosterModule from 'form-poster'; -import * as urlsModule from '../../src/payment/urls'; -import * as offsiteMappersModule from '../../src/payment/offsite-mappers'; -import paymentRequestDataMock from '../mocks/payment-request-data'; -import initializeOffsitePayment from '../../src/payment/initialize-offsite-payment'; - -describe('initializeOffsitePayment', () => { - let callback; - let data; - let formPoster; - let options; - let transformedData; - - beforeEach(() => { - callback = () => {}; - data = paymentRequestDataMock; - formPoster = { postForm: jasmine.createSpy('postForm') }; - transformedData = { body: 'hello world' }; - options = { host: 'https://bcapp.dev' }; - - spyOn(formPosterModule, 'createFormPoster').and.returnValue(formPoster); - spyOn(offsiteMappersModule, 'mapToPayload').and.returnValue(transformedData); - spyOn(urlsModule, 'getOffsitePaymentUrl').and.returnValue(`${options.host}/api/pay/initialize`); - }); - - it('should transform input data', () => { - const { mapToPayload } = offsiteMappersModule; - - initializeOffsitePayment(data, options, callback); - - expect(mapToPayload).toHaveBeenCalled(); - }); - - it('should post request data to server', () => { - const url = urlsModule.getOffsitePaymentUrl(); - - initializeOffsitePayment(data, options, callback); - - expect(formPoster.postForm).toHaveBeenCalledWith(url, transformedData, callback); - }); -}); diff --git a/test/payment/offsite-mappers/map-to-address.spec.js b/test/payment/offsite-mappers/map-to-address.spec.js deleted file mode 100644 index 60c74962..00000000 --- a/test/payment/offsite-mappers/map-to-address.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import mapToAddress from '../../../src/payment/offsite-mappers/map-to-address'; -import paymentRequestDataMock from '../../mocks/payment-request-data'; - -describe('mapToAddress', () => { - let data; - - beforeEach(() => { - data = paymentRequestDataMock; - }); - - it('should map to billing address', () => { - const output = mapToAddress(data, 'billingAddress'); - - expect(output).toEqual({ - billing_address_city: data.billingAddress.city, - billing_address_company: data.billingAddress.company, - billing_address_country_code: data.billingAddress.countryCode, - billing_address_country: data.billingAddress.country, - billing_address_first_name: data.billingAddress.firstName, - billing_address_last_name: data.billingAddress.lastName, - billing_address_phone: data.billingAddress.phone, - billing_address_state_code: data.billingAddress.provinceCode, - billing_address_state: data.billingAddress.province, - billing_address_street_1: data.billingAddress.addressLine1, - billing_address_street_2: data.billingAddress.addressLine2, - billing_address_zip: data.billingAddress.postCode, - }); - }); -}); diff --git a/test/payment/offsite-mappers/map-to-payload.spec.js b/test/payment/offsite-mappers/map-to-payload.spec.js deleted file mode 100644 index c69ecbfd..00000000 --- a/test/payment/offsite-mappers/map-to-payload.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import omit from 'lodash/omit'; -import merge from 'lodash/merge'; -import * as mapToBillingAddressModule from '../../../src/payment/offsite-mappers/map-to-billing-address'; -import * as mapToCustomerModule from '../../../src/payment/offsite-mappers/map-to-customer'; -import * as mapToMetaModule from '../../../src/payment/offsite-mappers/map-to-meta'; -import * as mapToShippingAddressModule from '../../../src/payment/offsite-mappers/map-to-shipping-address'; -import * as mapToStoreModule from '../../../src/payment/offsite-mappers/map-to-store'; -import mapToPayload from '../../../src/payment/offsite-mappers/map-to-payload'; -import paymentRequestDataMock from '../../mocks/payment-request-data'; - -describe('mapToPayload', () => { - let data; - - beforeEach(() => { - data = paymentRequestDataMock; - - spyOn(mapToBillingAddressModule, 'default').and.returnValue({ billingAddress: 'billingAddress' }); - spyOn(mapToCustomerModule, 'default').and.returnValue({ customer: 'customer' }); - spyOn(mapToMetaModule, 'default').and.returnValue({ meta: 'meta' }); - spyOn(mapToShippingAddressModule, 'default').and.returnValue({ shippingAddress: 'shippingAddress' }); - spyOn(mapToStoreModule, 'default').and.returnValue({ store: 'store' }); - }); - - it('should map to payload', () => { - data = merge({}, data, { - paymentMethod: { - gateway: 'Adyen', - }, - }); - - const output = mapToPayload(data); - - expect(output).toEqual({ - amount: data.order.grandTotal.integerAmount, - bc_auth_token: data.authToken, - billingAddress: 'billingAddress', - currency: data.order.currency, - customer: 'customer', - gateway: data.paymentMethod.gateway, - meta: 'meta', - notify_url: data.order.callbackUrl, - order_id: data.order.orderId, - page_title: document.title, - payment_method_id: data.paymentMethod.id, - reference_id: data.order.orderId, - return_url: data.paymentMethod.returnUrl, - shippingAddress: 'shippingAddress', - store: 'store', - }); - }); - - it('should use the return URL contained in the order object as a fallback', () => { - data = merge({}, data, { - paymentMethod: omit(data.paymentMethod, 'returnUrl'), - }); - - const output = mapToPayload(data); - - expect(output.return_url).toEqual(data.paymentMethod.returnUrl); - }); -}); diff --git a/test/payment/offsite-payment-initializer.spec.js b/test/payment/offsite-payment-initializer.spec.js new file mode 100644 index 00000000..19dc43be --- /dev/null +++ b/test/payment/offsite-payment-initializer.spec.js @@ -0,0 +1,58 @@ +import merge from 'lodash/merge'; +import { API, HOSTED } from '../../src/payment/payment-types'; +import OffsitePaymentInitializer from '../../src/payment/offsite-payment-initializer'; +import paymentRequestDataMock from '../mocks/payment-request-data'; + +describe('OffsitePaymentInitializer', () => { + let data; + let formPoster; + let offsitePaymentInitializer; + let payloadMapper; + let transformedData; + let urlHelper; + + beforeEach(() => { + transformedData = { body: 'hello world' }; + + data = merge({}, paymentRequestDataMock, { + paymentMethod: { + type: HOSTED, + }, + }); + + urlHelper = { + getOffsitePaymentUrl: jasmine.createSpy('getOffsitePaymentUrl').and.returnValue('/api/pay/initialize'), + }; + + formPoster = { + postForm: jasmine.createSpy('postForm'), + }; + + payloadMapper = { + mapToPayload: jasmine.createSpy('mapToPayload').and.returnValue(transformedData), + }; + + offsitePaymentInitializer = new OffsitePaymentInitializer(urlHelper, formPoster, payloadMapper); + }); + + it('creates an instance of OffsitePaymentInitializer', () => { + const instance = OffsitePaymentInitializer.create(); + + expect(instance instanceof OffsitePaymentInitializer).toBeTruthy(); + }); + + it('maps the input data into a payload object required for submitting an offsite payment', () => { + offsitePaymentInitializer.initializeOffsitePayment(data, () => {}); + + expect(payloadMapper.mapToPayload).toHaveBeenCalled(); + }); + + it('posts the request payload containing payment information to the server using a hidden HTML form', () => { + const callback = () => {}; + const url = urlHelper.getOffsitePaymentUrl(); + + offsitePaymentInitializer.initializeOffsitePayment(data, callback); + + expect(formPoster.postForm).toHaveBeenCalledWith(url, transformedData, callback); + }); +}); diff --git a/test/payment/offsite-payment-mappers/address-mapper.spec.js b/test/payment/offsite-payment-mappers/address-mapper.spec.js new file mode 100644 index 00000000..48aee2ea --- /dev/null +++ b/test/payment/offsite-payment-mappers/address-mapper.spec.js @@ -0,0 +1,50 @@ +import AddressMapper from '../../../src/payment/offsite-payment-mappers/address-mapper'; +import paymentRequestDataMock from '../../mocks/payment-request-data'; + +describe('AddressMapper', () => { + let addressMapper; + let data; + + beforeEach(() => { + data = paymentRequestDataMock; + addressMapper = new AddressMapper(); + }); + + it('maps the input data into a billing address object', () => { + const output = addressMapper.mapToBillingAddress(data); + + expect(output).toEqual({ + billing_address_city: data.billingAddress.city, + billing_address_company: data.billingAddress.company, + billing_address_country_code: data.billingAddress.countryCode, + billing_address_country: data.billingAddress.country, + billing_address_first_name: data.billingAddress.firstName, + billing_address_last_name: data.billingAddress.lastName, + billing_address_phone: data.billingAddress.phone, + billing_address_state_code: data.billingAddress.provinceCode, + billing_address_state: data.billingAddress.province, + billing_address_street_1: data.billingAddress.addressLine1, + billing_address_street_2: data.billingAddress.addressLine2, + billing_address_zip: data.billingAddress.postCode, + }); + }); + + it('maps the input data into a shipping address object', () => { + const output = addressMapper.mapToShippingAddress(data); + + expect(output).toEqual({ + shipping_address_city: data.shippingAddress.city, + shipping_address_company: data.shippingAddress.company, + shipping_address_country_code: data.shippingAddress.countryCode, + shipping_address_country: data.shippingAddress.country, + shipping_address_first_name: data.shippingAddress.firstName, + shipping_address_last_name: data.shippingAddress.lastName, + shipping_address_phone: data.shippingAddress.phone, + shipping_address_state_code: data.shippingAddress.provinceCode, + shipping_address_state: data.shippingAddress.province, + shipping_address_street_1: data.shippingAddress.addressLine1, + shipping_address_street_2: data.shippingAddress.addressLine2, + shipping_address_zip: data.shippingAddress.postCode, + }); + }); +}); diff --git a/test/payment/offsite-mappers/map-to-customer.spec.js b/test/payment/offsite-payment-mappers/customer-mapper.spec.js similarity index 69% rename from test/payment/offsite-mappers/map-to-customer.spec.js rename to test/payment/offsite-payment-mappers/customer-mapper.spec.js index e384d848..2c919dbb 100644 --- a/test/payment/offsite-mappers/map-to-customer.spec.js +++ b/test/payment/offsite-payment-mappers/customer-mapper.spec.js @@ -1,15 +1,17 @@ -import mapToCustomer from '../../../src/payment/offsite-mappers/map-to-customer'; +import CustomerMapper from '../../../src/payment/offsite-payment-mappers/customer-mapper'; import paymentRequestDataMock from '../../mocks/payment-request-data'; -describe('mapToCustomer', () => { +describe('CustomerMapper', () => { + let customerMapper; let data; beforeEach(() => { + customerMapper = new CustomerMapper(); data = paymentRequestDataMock; }); - it('should map to customer', () => { - const output = mapToCustomer(data); + it('maps the input data into a customer object', () => { + const output = customerMapper.mapToCustomer(data); expect(output).toEqual({ customer_browser_info: navigator.userAgent, diff --git a/test/payment/offsite-mappers/map-to-meta.spec.js b/test/payment/offsite-payment-mappers/meta-mapper.spec.js similarity index 55% rename from test/payment/offsite-mappers/map-to-meta.spec.js rename to test/payment/offsite-payment-mappers/meta-mapper.spec.js index 963310f6..44db2f90 100644 --- a/test/payment/offsite-mappers/map-to-meta.spec.js +++ b/test/payment/offsite-payment-mappers/meta-mapper.spec.js @@ -1,15 +1,17 @@ -import mapToMeta from '../../../src/payment/offsite-mappers/map-to-meta'; +import MetaMapper from '../../../src/payment/offsite-payment-mappers/meta-mapper'; import paymentRequestDataMock from '../../mocks/payment-request-data'; -describe('mapToMeta', () => { +describe('MetaMapper', () => { let data; + let metaMapper; beforeEach(() => { data = paymentRequestDataMock; + metaMapper = new MetaMapper(); }); - it('should map to meta', () => { - const output = mapToMeta(data); + it('maps the input data into a meta object', () => { + const output = metaMapper.mapToMeta(data); expect(output).toEqual({ meta_referrer: document.referrer, diff --git a/test/payment/offsite-payment-mappers/payload-mapper.spec.js b/test/payment/offsite-payment-mappers/payload-mapper.spec.js new file mode 100644 index 00000000..7434a447 --- /dev/null +++ b/test/payment/offsite-payment-mappers/payload-mapper.spec.js @@ -0,0 +1,83 @@ +import omit from 'lodash/omit'; +import merge from 'lodash/merge'; +import PayloadMapper from '../../../src/payment/offsite-payment-mappers/payload-mapper'; +import paymentRequestDataMock from '../../mocks/payment-request-data'; + +describe('PayloadMapper', () => { + let addressMapper; + let customerMapper; + let data; + let metaMapper; + let payloadMapper; + let paymentMethodIdMapper; + let storeMapper; + + beforeEach(() => { + data = paymentRequestDataMock; + + addressMapper = { + mapToBillingAddress: jasmine.createSpy('mapToBillingAddress').and.returnValue({ billingAddress: 'billingAddress' }), + mapToShippingAddress: jasmine.createSpy('mapToShippingAddress').and.returnValue({ shippingAddress: 'shippingAddress' }), + }; + + customerMapper = { + mapToCustomer: jasmine.createSpy('mapToCustomer').and.returnValue({ customer: 'customer' }), + }; + + metaMapper = { + mapToMeta: jasmine.createSpy('mapToMeta').and.returnValue({ meta: 'meta' }), + }; + + paymentMethodIdMapper = { + mapToId: jasmine.createSpy('mapToId').and.returnValue(data.paymentMethod.id), + }; + + storeMapper = { + mapToStore: jasmine.createSpy('mapToStore').and.returnValue({ store: 'store' }), + }; + + payloadMapper = new PayloadMapper(addressMapper, customerMapper, metaMapper, paymentMethodIdMapper, storeMapper); + }); + + it('maps the input data into a payload for submitting a payment to an offsite provider', () => { + data = merge({}, data, { + paymentMethod: { + gateway: 'Adyen', + }, + }); + + document.title = 'Hello world'; + + const output = payloadMapper.mapToPayload(data); + + expect(output).toEqual({ + amount: data.order.grandTotal.integerAmount, + bc_auth_token: data.authToken, + billingAddress: 'billingAddress', + currency: data.order.currency, + customer: 'customer', + gateway: data.paymentMethod.gateway, + meta: 'meta', + notify_url: data.order.callbackUrl, + order_id: data.order.orderId, + page_title: document.title, + payment_method_id: data.paymentMethod.id, + reference_id: data.order.orderId, + return_url: data.paymentMethod.returnUrl, + shippingAddress: 'shippingAddress', + store: 'store', + }); + + document.title = ''; + }); + + it('uses the return URL contained in the order object as a fallback', () => { + data = merge({}, data, { + paymentMethod: omit(data.paymentMethod, 'returnUrl'), + }); + + const output = payloadMapper.mapToPayload(data); + + expect(output.return_url).toEqual(data.paymentMethod.returnUrl); + }); +}); diff --git a/test/payment/offsite-mappers/map-to-store.spec.js b/test/payment/offsite-payment-mappers/store-mapper.spec.js similarity index 51% rename from test/payment/offsite-mappers/map-to-store.spec.js rename to test/payment/offsite-payment-mappers/store-mapper.spec.js index 0a430969..54685c8c 100644 --- a/test/payment/offsite-mappers/map-to-store.spec.js +++ b/test/payment/offsite-payment-mappers/store-mapper.spec.js @@ -1,15 +1,17 @@ -import mapToStore from '../../../src/payment/offsite-mappers/map-to-store'; import paymentRequestDataMock from '../../mocks/payment-request-data'; +import StoreMapper from '../../../src/payment/offsite-payment-mappers/store-mapper'; -describe('mapToStore', () => { +describe('StoreMapper', () => { let data; + let storeMapper; beforeEach(() => { data = paymentRequestDataMock; + storeMapper = new StoreMapper(); }); - it('should map to store', () => { - const output = mapToStore(data); + it('maps the input object into a store object', () => { + const output = storeMapper.mapToStore(data); expect(output).toEqual({ store_hash: data.store.storeHash, diff --git a/test/payment/payment-method-mappers/payment-method-id-mapper.spec.js b/test/payment/payment-method-mappers/payment-method-id-mapper.spec.js new file mode 100644 index 00000000..8b435080 --- /dev/null +++ b/test/payment/payment-method-mappers/payment-method-id-mapper.spec.js @@ -0,0 +1,21 @@ +import PaymentMethodIdMapper from '../../../src/payment/payment-method-mappers/payment-method-id-mapper'; +import * as PAYMENT_METHODS from '../../../src/payment/payment-method-ids'; + +describe('PaymentMethodIdMapper', () => { + let paymentMethod; + let paymentMethodIdMapper; + + beforeEach(() => { + paymentMethodIdMapper = new PaymentMethodIdMapper(); + }); + + it('returns "braintree" if the payment method is "braintreepaypal"', () => { + paymentMethod = { id: PAYMENT_METHODS.BRAINTREE_PAYPAL }; + expect(paymentMethodIdMapper.mapToId(paymentMethod)).toEqual(PAYMENT_METHODS.BRAINTREE); + }); + + it('does not perform any mapping for other payment methods', () => { + paymentMethod = { id: PAYMENT_METHODS.BRAINTREE }; + expect(paymentMethodIdMapper.mapToId(paymentMethod)).toEqual(PAYMENT_METHODS.BRAINTREE); + }); +}); diff --git a/test/payment/url-helper.spec.js b/test/payment/url-helper.spec.js new file mode 100644 index 00000000..aab508dc --- /dev/null +++ b/test/payment/url-helper.spec.js @@ -0,0 +1,19 @@ +import UrlHelper from '../../src/payment/url-helper'; + +describe('UrlHelper', () => { + let host; + let urlHelper; + + beforeEach(() => { + host = 'https://bigpay.com'; + urlHelper = new UrlHelper(host); + }); + + it('returns a URL for submitting payments to an API provider', () => { + expect(urlHelper.getPaymentUrl()).toEqual(`${host}/api/public/v1/orders/payments`); + }); + + it('returns a URL for submitting payments to an offsite provider', () => { + expect(urlHelper.getOffsitePaymentUrl()).toEqual(`${host}/pay/initialize`); + }); +});