/**
* Orders Recipients resources
*
* @module resources/order-recipients
* @copyright 2015–2020 RewardOps Inc.
*/
const axios = require('axios');
const { get } = require('lodash');
const config = require('../config');
const { storeOrderRecipientSchema } = require('../schemas/store-order-recipient');
const { getApiBaseUrl } = require('../urls');
const { setPiiToken } = require('../utils/axios-helpers');
const { SDKError } = require('../utils/error');
const { log } = require('../utils/logger');
const ordersClient = axios.create();
// TODO: add request object to callback when errors occur (for API module as well see https://rewardops.atlassian.net/browse/MX-1559)
/**
* Higher order function for creating an order in context. Same as {@link module:resources/orders~createOrder}
*
* TODO: Refactor this so we don't have 2 different V3/V4 create order functions (https://rewardops.atlassian.net/browse/MX-1063)
*
* NOTE: This is actually something we'd regularly show, but are hiding so as to note cause confusion with
* 2 create order functions in the SDK's public API.
*
* @param {module:resources/order-recipients~V5OrderContext} v5OrderContext Order context object
*
* @returns {module:resources/order-recipients~CreateOrderFunc} Create order function
*
* @private
*/
const createOrder = ({ contextId }) => async (params, callback) => {
try {
const url = `${getApiBaseUrl()}/programs/${contextId}/orders`;
log('Request: POST {url}\nPayload: {params}', { data: { url, params } });
const { data, request } = await ordersClient.post(url, params);
callback(null, data.result, data, request);
} catch (error) {
log('API Error: {error}', { level: 'error', data: { error } });
callback(get(error, 'response.data', error));
}
};
/**
* Create order in context function for V3/V4 API.
*
* @typedef module:resources/order-recipients~CreateOrderFunc
*
* @property {object} params [Request]{@link https://github.com/request/request} params to pass to the create order API call.
* @property {module:api~requestCallback} callback Callback that handles the response.
*
* @private
*/
/**
* Higher order function for creating an order recipient in context.
*
* NOTE: This is a v5 API endpoint, therefore ignores the `apiVersion` {@link module:config~DefaultConfig Config}
* property.
*
* @param {module:resources/order-recipients~V5OrderContext} v5OrderContext Order context object
*
* @returns {module:resources/order-recipients~StoreOrderRecipientFunc} `create` order function in context
*
* @see {@link module:resources/order-recipients~StoreOrderRecipientFunc} for examples.
*
* @protected
*/
const storeOrderRecipient = ({ programCode }) => async member => {
return storeOrderRecipientSchema.validate(member).then(async () => {
try {
await setPiiToken(ordersClient);
const url = `${config.get('piiServerUrl')}/api/v5/programs/${programCode}/order_recipients`;
log('Request: POST {url}\nPayload: {member}', { data: { url, member } });
const {
data: { result },
} = await ordersClient.post(url, member);
return result;
} catch (error) {
log('API Error: {error}', { level: 'error', data: { error } });
// eslint-disable-next-line no-throw-literal
throw { error: get(error, 'response', error) };
}
});
};
/**
* Store order recipient function that makes a call to store data for geographic-specific
* PII storage-enabled programs.
*
* @typedef module:resources/order-recipients~StoreOrderRecipientFunc
*
* @property {module:resources/order-recipients~MemberPiiRequestBody} member Member PII information
*
* @protected
*/
/**
* Higher order function for creating an order that first stores geographic-specific PII information, in context.
*
* @param {module:resources/order-recipients~V5OrderContext} orderContext Order context object
*
* @returns {module:resources/order-recipients~CreateOrderWithPiiStorageFunc} Create an order function that also
* stores geographic-specific PII information.
*
* @see {@link module:resources/order-recipients~CreateOrderWithPiiStorageFunc} for examples.
*/
const createOrderWithPiiStorage = orderContext => async ({ member, ...createOrderParams }, callback) => {
try {
const orderRecipientData = await storeOrderRecipient(orderContext)(member);
await createOrder(orderContext)({ ...createOrderParams, ...orderRecipientData }, callback);
} catch (exception) {
callback(exception.errors || exception);
}
};
/**
* Create order function that makes a call to store geographic-specific PII information, then
* creates a new order along with the order recipient code generated from the first call.
*
* @typedef module:resources/order-recipients~CreateOrderWithPiiStorageFunc
*
* @param {object} orderParams Order params
* @param {module:resources/order-recipients~MemberPiiRequestBody} orderParams.member Member PII information
* @param {module:api~requestCallback} callback Callback that handles the response.
*
* @example
* // Create new order for a program that has geographic-specific PII storage enabled.
* // NOTE: Notice the second `'example_program_code'` argument on the `RO.program` method.
* const program = RO.program(12, 'example_program_code');
* program.order.create(
* {
* reward_id: 45231,
* member: {
* id: 'jb0987',
* full_name: 'Jolanta Banicki',
* email: 'jolanta.b@example.com',
* },
* },
* (error, responseBody, response) => {
* if (error) {
* console.log(error);
* } else {
* console.log(result);
* }
* }
* );
*/
/**
* Higher order function for fetching an order recipient in context.
*
* NOTE: This is a v5 API endpoint, therefore ignores the `apiVersion` config prop
*
* @param {module:resources/order-recipients~V5OrderContext} v5OrderContext Order context object
*
* @returns {module:resources/order-recipients~GetOrderRecipientFunc} `create` order function in context
*
* @see {@link module:resources/order-recipients~GetOrderRecipientFunc} for examples.
*/
const getOrderRecipient = ({ programCode }) => async (orderRecipientCode, callback) => {
try {
await setPiiToken(ordersClient);
const { data, request } = await ordersClient.get(
`${config.get('piiServerUrl')}/api/v5/programs/${programCode}/order_recipients/${orderRecipientCode}`
);
callback(null, data.result, data, request);
} catch (error) {
log('API Error: {error}', { level: 'error', data: { error } });
callback(error);
}
};
/**
* Fetch order recipient function.
*
* @typedef module:resources/order-recipients~GetOrderRecipientFunc
*
* @property {string} orderRecipientCode Order recipient code (e.g., from a
* {@link module:resources/orders~GetSummaryFunc GetSummaryFunc} payload).
* @property {module:api~requestCallback} callback Callback that handles the response.
*
* @example
* // Used in the context of a program
* const orderRecipient = ro.program(12, 'example_program_code').getOrderRecipient(
* 'poiuytr123456',
* (undefined, responseBody, response) => {
* console.log(result);
* });
*/
/**
* Factory for creating `orderRecipients` methods.
*
* @param {string} contextTypeName The type of the parent context ('programs')
* @param {number} contextId The ID of the order's parent program
* @param {string} programCode The Code of the order's parent program
*
* @returns {module:resources/orders~OrderRecipients} Orders object
*
* @protected
*/
const orderRecipientFactory = (contextTypeName, contextId, programCode) => {
if (contextTypeName !== 'programs') {
throw new SDKError('Can only create an order recipient object for programs');
}
if (!config.get('piiServerUrl')) {
throw new SDKError('`piiServerUrl` is not configured');
}
if (!config.get('supportedLocales')) {
throw new SDKError('`supportedLocales` is not configured');
}
const orderContext = {
contextTypeName,
contextId,
programCode,
};
return {
getOrderRecipient: getOrderRecipient(orderContext),
create: createOrderWithPiiStorage(orderContext),
};
};
/**
* Order recipients methods object
*
* @typedef module:resources/orders~OrderRecipients
* @property {module:resources/order-recipients~GetOrderRecipientFunc} getOrderRecipient Get order recipient function
* @property {module:resources/order-recipients~CreateFunc} create Create order recipients function
*/
module.exports = { orderRecipientFactory, createOrder, storeOrderRecipient, ordersClient };
/**
* Request body payload for Member PII information.
*
* @typedef module:resources/order-recipients~MemberPiiRequestBody
*
* @property {string} id Member ID
* @property {string} accept_language The member's preferred locale, in the format of Accept-Language
* as per RFC2616; e.g. `en-CA`, `en-US`
* @property {string} [gift] Is the order a gift?
* @property {string} [full_name] Full name
* @property {string} [ip_address] IP address
* @property {string} [email] Email address
* @property {string} [phone] Phone number
* @property {module:resources/order-recipients~AddressModelSchema} [address] Address
*/
/**
* RewardOps Address model schema
*
* @typedef module:resources/order-recipients~AddressModelSchema
*
* @property {string} address Street address
* @property {string} address_2 Street address - 2
* @property {string} city City
* @property {string} country_code Country code - ISO 3166-1 alpha-2
* @property {string} country_subregion_code Country subdivision code - ISO 3166-2 alpha-2 (State/Province)
* @property {string} postal_code Postal code (ZIP code)
*/
/**
* Order options object
*
* @typedef module:resources/order-recipients~V5OrderContext
*
* @property {string} contextTypeName The type of the parent context ('programs')
* @property {number} contextId The ID of the order's parent program
* @property {string} programCode The Code of the order's parent program
*/
Source