Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CheckoutPricing: Coupons #385

Merged
merged 45 commits into from
Nov 13, 2017
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
95a861e
Adds CheckoutPricing#coupon
chrissrogers Oct 26, 2017
e4c5f81
Adds CheckoutPricing discount calculations
chrissrogers Oct 26, 2017
f3be292
Updates recurly.coupon for new endpoint
chrissrogers Oct 27, 2017
e26485d
Removes component-type
chrissrogers Oct 27, 2017
53b622b
Updates coupon module structure
chrissrogers Oct 27, 2017
75af47d
Updates CheckoutPricing for new coupon getter
chrissrogers Oct 27, 2017
0f84178
Updates coupon specs for new endpoint
chrissrogers Oct 27, 2017
c513752
Adds recurly.pipedRequest
chrissrogers Oct 28, 2017
2e73e2a
Adds multiple plan code support to coupon getter
chrissrogers Oct 28, 2017
b8df59c
Fixes argument syntax
chrissrogers Nov 1, 2017
8503c99
Updates CheckoutPricing coupon handler for new types
chrissrogers Nov 2, 2017
b75213f
Improves recurly.pipedRequest response handling
chrissrogers Nov 2, 2017
19f22f9
Adds spec plans
chrissrogers Nov 2, 2017
adb4523
Updates SubscriptionPricing.coupon to receive objects
chrissrogers Nov 4, 2017
bd08d09
Fixes CheckoutPricing plan currency handling
chrissrogers Nov 4, 2017
712f670
Fixes CheckoutPricing/Calculations sub calculation safety
chrissrogers Nov 4, 2017
4430bb4
Adds EmbeddedSubscriptionPricing
chrissrogers Nov 4, 2017
2a96fa2
Fixes SubscriptionPricing reprice safety
chrissrogers Nov 4, 2017
06b62b8
Fixes CheckoutPricing/Calculations trial detection
chrissrogers Nov 4, 2017
73b6e63
Ensures subscription safety in CheckoutPricing/Calculations
chrissrogers Nov 4, 2017
0fab807
Applies Promise pattern to CheckoutPricing/Calculations
chrissrogers Nov 5, 2017
3860f7f
Fixes checkout pricing free trial coupon application
chrissrogers Nov 6, 2017
fcb7da7
Adds adjustment code uniqueness validator
chrissrogers Nov 6, 2017
c1b7e07
Adds reprice silence option
chrissrogers Nov 6, 2017
18eca38
Narrows scope of reprice event silencer
chrissrogers Nov 6, 2017
576644b
Fixes itemization collector for checkouts with sub discounts
chrissrogers Nov 6, 2017
a04476b
Documentation
chrissrogers Nov 6, 2017
59008b8
Cleans up unused variables
chrissrogers Nov 6, 2017
580312e
Fixes CheckoutPricing/Calculations plan-specific discount filter
chrissrogers Nov 6, 2017
b2181c8
Fixes coupon specs
chrissrogers Nov 6, 2017
2fff200
Fixes SubscriptionPricing.reprice
chrissrogers Nov 6, 2017
70b3b98
Adds coupon validity detection for new endpoint
chrissrogers Nov 6, 2017
31a01ed
Fixes coupon fixtures
chrissrogers Nov 6, 2017
fbb652c
Fixes coupon validity method safety
chrissrogers Nov 6, 2017
dc8ecdf
Fixes pipedRequest for empty sets
chrissrogers Nov 7, 2017
eb0f52f
Fixes CheckoutPricing.coupon set and calculations
chrissrogers Nov 7, 2017
2aaa800
Adds CheckoutPricing.coupon specs
chrissrogers Nov 7, 2017
2c5c70d
Fixes CheckoutPricing fixed amount discount overrun
chrissrogers Nov 8, 2017
8011207
Fixes CheckoutPricing subscription coupon and gift card removal
chrissrogers Nov 8, 2017
193bb94
Adds decimalize pass to CheckoutPricing
chrissrogers Nov 8, 2017
2a15d81
Adds CheckoutPricing fixed amount discount soecs
chrissrogers Nov 8, 2017
1b8eb55
Fixes CheckoutPricing free trial application reprice
chrissrogers Nov 8, 2017
2852800
Fixes SubscriptionPricing fixed amount discounting of setup fees
chrissrogers Nov 8, 2017
1bbabe0
Adds CheckoutPricing discount specs
chrissrogers Nov 8, 2017
2362f17
Adds recurly.pipedRequest defaults
chrissrogers Nov 9, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions lib/recurly.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
/*jshint -W058 */

import type from 'component-type';
import clone from 'component-clone';
import chunk from 'lodash.chunk';
import merge from 'lodash.merge';
import jsonp from 'jsonp';
import qs from 'qs';
import Emitter from 'component-emitter';
import Promise from 'promise';
import errors from './errors';
import version from './version';
import bankAccount from './recurly/bank-account';
import coupon from './recurly/coupon';
import giftcard from './recurly/giftcard';
import {token} from './recurly/token';
import {factory as Frame} from './recurly/frame';
import {factory as ApplePay} from './recurly/apple-pay';
Expand All @@ -17,13 +20,10 @@ import {deprecated as deprecatedPaypal} from './recurly/paypal/strategy/direct';
import {Bus} from './recurly/bus';
import {Fraud} from './recurly/fraud';
import {HostedFields, FIELD_TYPES} from './recurly/hosted-fields';
import {Message} from './recurly/message';
import CheckoutPricing from './recurly/pricing/checkout';
import SubscriptionPricing from './recurly/pricing/subscription';
import uuid from './util/uuid';

import giftcard from './recurly/giftcard';

const debug = require('debug')('recurly');

/**
Expand Down Expand Up @@ -129,7 +129,7 @@ export class Recurly extends Emitter {

options = clone(options);

if (type(options) === 'string') options = { publicKey: options };
if (typeof options === 'string') options = { publicKey: options };

options = normalizeOptions(options)

Expand Down Expand Up @@ -159,7 +159,7 @@ export class Recurly extends Emitter {
this.config.parent = options.parent;
}

if (type(options.fields) === 'object') {
if (typeof options.fields === 'object') {
merge(this.config.fields, options.fields);
}

Expand Down Expand Up @@ -258,7 +258,7 @@ export class Recurly extends Emitter {
throw errors('not-configured');
}

if ('function' == type(data)) {
if (typeof data === 'function') {
done = data;
data = {};
}
Expand All @@ -278,6 +278,65 @@ export class Recurly extends Emitter {
}
}

/**
* Pipelines a request across several requests
*
* Chunks are made of `data[by]`` in `size`. If any request errors,
* the entire pipeline errors
*
* - Ignores 404s until done
* - Immediately rejects other errors
* - Collects and concats array responses
* - Immediately resolves non-array responses
*
* @param {String} method
* @param {String} route
* @param {Object} data
* @param {Array} data.by must be an Array
* @param {String} by
* @param {Number} size=100
* @param {Function} done
* @private
*/

pipedRequest ({ method, route, data, by, size }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about setting the default for size here like it appears in the doc block above. Since the only use of this function at this time using the default, we could remove the arg in that call. Is the method generally always going to be a GET? If so, we could set that default as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually fixed the default assignment in #393 when I added specs for this method. I'll add it here too.
+1 to setting the method default as well

let results = [];
let empties = [];

const chunks = chunk(data[by], size).map(piece => {
return Object.assign({}, data, { [by]: piece });
});

if (chunks.length === 0) {
return Promise.denodeify(this.request.bind(this))(method, route, data);
}

return new Promise((resolve, reject) => {
const part = (err, res) => {
// if we have any errors that are not 404, reject
if (err) {
if (err.code === 'not-found') empties.push(err);
else return reject(err);
}

// If the response is an array, collect it; otherwise, resolve with it
if (res) {
if (Array.isArray(res)) results.push(res);
else return resolve(res);
}

if ((results.length + empties.length) === chunks.length) {
// if we have some results and some 404s, resolve with results
// otherwise, reject with the first 404
if (results.length > 0) resolve(results.reduce((acc, cur) => acc.concat(cur)));
else reject(empties[0]);
}
};

chunks.forEach(dataChunk => this.request(method, route, dataChunk, part));
});
}

/**
* Issues an API request over xhr.
*
Expand Down Expand Up @@ -374,7 +433,7 @@ export class Recurly extends Emitter {
}

Recurly.prototype.Frame = Frame;
Recurly.prototype.coupon = require('./recurly/coupon');
Recurly.prototype.coupon = coupon;
Recurly.prototype.giftcard = giftcard;
Recurly.prototype.ApplePay = ApplePay;
Recurly.prototype.PayPal = PayPal;
Expand Down
2 changes: 1 addition & 1 deletion lib/recurly/apple-pay.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Emitter from 'component-emitter';
import errors from '../errors';
import {CheckoutPricing} from './pricing/checkout';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to be used in one of the followup PR's?

Copy link
Member Author

@chrissrogers chrissrogers Nov 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Prior to writing the CheckoutPricing/Attachment class, I'll adapt these modules to use the Pricing base class

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #394

import {SubscriptionPricing} from './pricing/subscription';
import {normalize} from '../util/normalize';
import {FIELDS} from './token';

const debug = require('debug')('recurly:apple-pay');

const SESSION_TIMEOUT = 300000; // 5m
const APPLE_PAY_API_VERSION = 2;
const APPLE_PAY_ADDRESS_MAP = {
first_name: 'givenName',
Expand Down
9 changes: 4 additions & 5 deletions lib/recurly/bank-account.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import each from 'component-each';
import type from 'component-type';
import {normalize} from '../util/normalize';
import errors from '../errors';

Expand Down Expand Up @@ -75,7 +74,7 @@ function token (options, done) {
var input = data.values;
var userErrors = validate(input);

if ('function' !== type(done)) {
if (typeof done !== 'function') {
throw errors('missing-callback');
}

Expand Down Expand Up @@ -109,12 +108,12 @@ function token (options, done) {
function bankInfo (options, done) {
debug('bankInfo');

if ('function' !== type(done)) {
if (typeof done !== 'function') {
throw errors('missing-callback');
}

var routingNumber = options && options.routingNumber;
if (!routingNumber || type(routingNumber) !== 'string') {
if (!routingNumber || typeof routingNumber !== 'string') {
return done(errors('validation', { fields: ['routingNumber'] }));
}

Expand All @@ -135,7 +134,7 @@ function validate (input) {
var errors = [];

each(requiredFields, function(field){
if (!input[field] || type(input[field]) !== 'string') {
if (!input[field] || typeof input[field] !== 'string') {
errors.push(field);
}
});
Expand Down
46 changes: 23 additions & 23 deletions lib/recurly/coupon.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
var type = require('component-type');
var debug = require('debug')('recurly:coupon');
var errors = require('../errors');
import errors from '../errors';

module.exports = coupon;
const debug = require('debug')('recurly:coupon');

const CHUNK_SIZE = 100;

/**
* Coupon mixin.
*
* Retrieves coupon information for the `plan`. The `callback` signature
* is `err, plan` where `err` may be a request or server error, and `plan`
* is a representation of the requested plan.
* Retrieves coupon information for the `coupon` and optional `plan`.
* The `callback` signature is `err, coupon` where `err` may be a request
* or server error, and `coupon` is a representation of the requested coupon.
*
* @param {Object} options
* @param {Function} callback
* @param {String} options.coupon coupon code
* @param {String} [options.plan] plan code
* @param {Array[String]} [options.plans] plan codes
* @param {Function} done
*/

function coupon (options, callback) {
export default function coupon (options = {}, done) {
debug('%j', options);

if ('function' !== type(callback)) {
throw errors('missing-callback');
}

if ('object' !== type(options)) {
throw errors('invalid-options');
}
let {coupon, plans, plan, currency} = options;

if (!('plan' in options)) {
throw errors('missing-plan');
}
if (!coupon) throw errors('missing-coupon');
if (typeof done !== 'function') throw errors('missing-callback');

if (!('coupon' in options)) {
throw errors('missing-coupon');
}
if (!plans && plan) plans = [plan];

this.request('get', '/plans/' + options.plan + '/coupons/' + options.coupon, options, callback);
this.pipedRequest({
method: 'get',
route: `/coupons/${coupon}`,
data: { plan_codes: plans, currency },
by: 'plan_codes',
size: CHUNK_SIZE,
}).nodeify(done);
}
5 changes: 2 additions & 3 deletions lib/recurly/giftcard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type from 'component-type';
import errors from '../errors';

const debug = require('debug')('recurly:giftcard');
Expand All @@ -17,11 +16,11 @@ const debug = require('debug')('recurly:giftcard');
export default function giftcard (options, callback) {
debug('%j', options);

if ('function' !== type(callback)) {
if (typeof callback !== 'function') {
throw errors('missing-callback');
}

if ('object' !== type(options)) {
if (typeof options !== 'object') {
throw errors('invalid-options');
}

Expand Down
1 change: 0 additions & 1 deletion lib/recurly/paypal/strategy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import errors from '../../../errors';

const debug = require('debug')('recurly:paypal:strategy');

const PAYPAL_CLIENT_URI = 'https://www.paypalobjects.com/api/checkout.js';
const DISPLAY_OPTIONS = [
'amount',
'currency',
Expand Down
5 changes: 2 additions & 3 deletions lib/recurly/plan.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
var type = require('component-type');
var debug = require('debug')('recurly:plan');

module.exports = plan;
Expand All @@ -17,11 +16,11 @@ module.exports = plan;
function plan (code, callback) {
debug('%s', code);

if ('function' != type(callback)) {
if (typeof callback !== 'function') {
throw new Error('Missing callback');
}

if ('undefined' == type(code)) {
if (typeof code === 'undefined') {
return callback(new Error('Missing plan code'));
}

Expand Down
Loading