@mason-api/javascript-sdk
Version:
Mason component rendering library
138 lines (123 loc) • 3.93 kB
JavaScript
import _ from 'lodash';
import update from 'immutability-helper';
import { CONFIG, TREE } from '@mason-api/utils';
const SECRET_KEY = 'secretKey';
const PUBLISHABLE_KEY = 'publishableKey';
const stripeElementsLookUpTable = {
'card': 'card',
'card-cvc': 'cardCvc',
'card-expiry': 'cardExpiry',
'card-number': 'cardNumber',
};
// inputs with these names will be sent to stripe along with card data
const stripeCardKeys = [
'address_city',
'address_country',
'address_line1',
'address_line2',
'address_state',
'address_zip',
'country',
'name',
];
export const schema = {
id: 'stripe',
name: 'Stripe',
keys: {
PUBLISHABLE_KEY,
SECRET_KEY,
},
requiredKeys: [PUBLISHABLE_KEY],
spec: [
{ key: PUBLISHABLE_KEY, value: '' },
{ key: SECRET_KEY, private: true, value: '' },
],
};
let stripe;
function mountElements(e) {
const forms = e.target.querySelectorAll('form[name="stripe"]');
_.forEach(forms, (form) => {
const elements = stripe.elements();
_.forEach(stripeElementsLookUpTable, (name) => {
const input = form.querySelector(`div[data-name="${name}"]`);
if (input) {
const element = elements.create(name);
element.mount(input);
}
});
});
}
export const has = config => !_.isEmpty(CONFIG.getValueForIntegration(config, schema.id, PUBLISHABLE_KEY));
export const init = getContext => next => (config) => {
if (!stripe) {
if (!_.isFunction(Stripe)) {
throw new Error('Stripe not found. Please include Stripe.js script, visit https://stripe.com/docs/stripe-js/reference#including-stripejs for more details');
} else {
stripe = Stripe(CONFIG.getValueForIntegration(config, schema.id, PUBLISHABLE_KEY));
}
}
let nextConfig = { ...config };
_.forEach(_.keys(nextConfig.data), (configKey) => {
const { data: { [configKey]: { tree } } } = config;
let nextTree = { ...tree };
const forms = TREE.search(nextTree, { tag: 'form', p: { name: 'stripe' } });
// first we replace any stripe inputs with their react-elements container
_.forEach(forms, (form) => {
_.forEach(stripeElementsLookUpTable, (type, id) => {
const input = _.first(TREE.search(form.c, { p: { name: _.camelCase(id) } }, form.path));
if (input) {
nextTree = TREE.replaceNode(nextTree, update(input, {
tag: { $set: 'div' },
p: { $merge: { 'data-name': input.p.name }, $unset: ['name'] },
}));
}
});
});
const buttons = TREE.search(nextTree, { tag: 'button', p: { _function: 'stripe:createToken' } });
_.forEach(buttons, (button) => {
nextTree = TREE.setNodeProp(nextTree, button.path, '_function', 'submitForm');
});
nextConfig = update(nextConfig, {
data: {
[configKey]: {
tree: { $set: nextTree },
},
},
});
});
return next(nextConfig);
};
export const render = getContext => next => (config, configSubpath, target, props) => {
target.addEventListener('render', mountElements);
return next(config, configSubpath, target, {
...props,
willSendData: (form, name) => {
if (name === 'stripe') {
// this integration will only process forms named stripe
return stripe.createToken(_.pick(form.data, stripeCardKeys)).then(({ token }) => {
const nextForm = update(form, {
data: {
$merge: {
stripeToken: token.id,
},
},
});
if (_.isFunction(props.willSendData)) {
return props.willSendData(nextForm, name);
}
return nextForm;
});
}
// if it's not named stripe, still must call ownProps.willSendData
if (_.isFunction(props.willSendData)) {
return props.willSendData(form, name);
}
return form;
}
});
};
export default {
has,
init,
render,
};