Skip to content

Commit

Permalink
feat: Add support for tokenizing the CVV standalone
Browse files Browse the repository at this point in the history
Introduces the `CvvElement` which can be used to tokenize the CVV by
itself for use in CIT where the merchant wants the customer to confirm
their CVV before checking out:

```js
const cvvElement = recurly.elements.CvvElement({});
cvvElement.attach(document.querySelector('#recurly-elements'));
```
  • Loading branch information
cbarton committed Sep 30, 2024
1 parent 168095b commit 38bcabc
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 31 deletions.
5 changes: 4 additions & 1 deletion lib/recurly.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const DEFAULTS = {
threeDSecure: { preflightDeviceDataCollector: true }
},
api: DEFAULT_API_URL,
required: ['number', 'expiry', 'first_name', 'last_name'],
fields: {
all: {
style: {}
Expand Down Expand Up @@ -280,7 +281,9 @@ export class Recurly extends Emitter {
deepAssign(this.config.fields, options.fields);
}

this.config.required = options.required || this.config.required || [];
if (typeof options.required === 'object') {
this.config.required = [...this.config.required, ...options.required];
}

// Begin parent role configuration and setup
if (this.config.parent) {
Expand Down
11 changes: 11 additions & 0 deletions lib/recurly/element/cvv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Element from './element';

export function factory (options) {
return new CvvElement({ ...options, elements: this });
}

export class CvvElement extends Element {
static type = 'cvv';
static elementClassName = 'CvvElement';
static supportsTokenization = true;
}
5 changes: 4 additions & 1 deletion lib/recurly/elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { factory as cardNumberElementFactory, CardNumberElement } from './elemen
import { factory as cardMonthElementFactory, CardMonthElement } from './element/card-month';
import { factory as cardYearElementFactory, CardYearElement } from './element/card-year';
import { factory as cardCvvElementFactory, CardCvvElement } from './element/card-cvv';
import { factory as cvvElementFactory, CvvElement } from './element/cvv';
import uid from '../util/uid';

const debug = require('debug')('recurly:elements');
Expand All @@ -29,10 +30,12 @@ export default class Elements extends Emitter {
CardMonthElement = cardMonthElementFactory;
CardYearElement = cardYearElementFactory;
CardCvvElement = cardCvvElementFactory;
CvvElement = cvvElementFactory;

static VALID_SETS = [
[ CardElement ],
[ CardNumberElement, CardMonthElement, CardYearElement, CardCvvElement ]
[ CvvElement ],
[ CardNumberElement, CardMonthElement, CardYearElement, CardCvvElement ],
];

constructor ({ recurly }) {
Expand Down
12 changes: 8 additions & 4 deletions lib/recurly/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,14 @@ function token (customerData, bus, done) {
}

const { number, month, year } = inputs;
Risk.preflight({ recurly: this, number, month, year })
.then(results => inputs.risk = results)
.then(() => this.request.post({ route: '/token', data: inputs, done: complete }))
.done();
if (number && month && year) {
Risk.preflight({ recurly: this, number, month, year })
.then(results => inputs.risk = results)
.then(() => this.request.post({ route: '/token', data: inputs, done: complete }))
.done();
} else {
this.request.post({ route: '/token', data: inputs, done: complete.bind(this) });
}
}

function complete (err, res) {
Expand Down
32 changes: 9 additions & 23 deletions lib/recurly/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,30 +197,16 @@ export function validateCardInputs (recurly, inputs) {
const format = formatFieldValidationError;
let errors = [];

if (!cardNumber(inputs.number)) {
errors.push(format('number', INVALID));
}

if (!expiry(inputs.month, inputs.year)) {
errors.push(format('month', INVALID), format('year', INVALID));
}

if (!inputs.first_name) {
errors.push(format('first_name', BLANK));
}

if (!inputs.last_name) {
errors.push(format('last_name', BLANK));
}

if (~recurly.config.required.indexOf('cvv') && !inputs.cvv) {
errors.push(format('cvv', BLANK));
} else if ((~recurly.config.required.indexOf('cvv') || inputs.cvv) && !cvv(inputs.cvv)) {
errors.push(format('cvv', INVALID));
}

each(recurly.config.required, function (field) {
if (!inputs[field] && ~CARD_FIELDS.indexOf(field)) {
if (field === 'number' && !cardNumber(inputs.number)) {
errors.push(format(field, BLANK));
} else if (field === 'expiry' && !expiry(inputs.month, inputs.year)) {
errors.push(format('month', INVALID), format('year', INVALID));
} else if (field === 'cvv' && !inputs.cvv) {
errors.push(format(field, BLANK));
} else if (field === 'cvv' && !cvv(inputs.cvv)) {
errors.push(format(field, INVALID));
} else if (!inputs[field] && ~CARD_FIELDS.indexOf(field)) {
errors.push(format(field, BLANK));
}
});
Expand Down
6 changes: 4 additions & 2 deletions test/unit/elements.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import assert from 'assert';
import Element from '../../lib/recurly/element';
import Elements from '../../lib/recurly/elements';
import { initRecurly } from './support/helpers';
import { Recurly } from '../../lib/recurly';

const noop = () => {};

Expand All @@ -26,7 +25,8 @@ describe('Elements', function () {
'CardNumberElement',
'CardMonthElement',
'CardYearElement',
'CardCvvElement'
'CardCvvElement',
'CvvElement',
].forEach(elementName => {
const elements = new Elements({ recurly: this.recurly });
const element = elements[elementName]();
Expand Down Expand Up @@ -90,6 +90,8 @@ describe('Elements', function () {
const invalidSets = [
['CardElement', 'CardNumberElement'],
['CardElement', 'CardCvvElement'],
['CardElement', 'CvvElement'],
['CardNumberElement', 'CvvElement'],
['CardNumberElement', 'CardElement'],
['CardNumberElement', 'CardMonthElement', 'CardYearElement', 'CardCvvElement', 'CardElement']
];
Expand Down

0 comments on commit 38bcabc

Please sign in to comment.