From adf8b182a30d0525f7f53c09d75187df59674fa7 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Tue, 19 Dec 2017 12:59:44 +0800 Subject: [PATCH 1/5] Expresses ApplePay total line items more clearly Signed-off-by: Christopher Rogers --- lib/recurly/apple-pay.js | 20 ++++++++++++++------ test/apple-pay.test.js | 4 ++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/recurly/apple-pay.js b/lib/recurly/apple-pay.js index c3a8736df..dab0e3440 100644 --- a/lib/recurly/apple-pay.js +++ b/lib/recurly/apple-pay.js @@ -78,7 +78,7 @@ class ApplePay extends Emitter { supportedNetworks: this.config.supportedNetworks, merchantCapabilities: this.config.merchantCapabilities, requiredBillingContactFields: ['postalAddress'], - total: { label: this.config.label, amount: this.config.total } + total: this.totalLineItem }); session.onvalidatemerchant = this.onValidateMerchant.bind(this); @@ -103,8 +103,16 @@ class ApplePay extends Emitter { * @return {Object} total cost line item * @private */ - get total () { - return { type: 'final', label: this.config.label, amount: this.config.total }; + get totalLineItem () { + return { label: this.config.label, amount: this.config.total }; + } + + /** + * @return {Object} total cost line item indicating a finalized line item + * @private + */ + get finalTotalLineItem () { + return Object.assign({}, this.total, { type: 'final' }); } /** @@ -240,7 +248,7 @@ class ApplePay extends Emitter { */ onPaymentMethodSelected (event) { debug('Payment method selected', event); - this.session.completePaymentMethodSelection(this.total, this.lineItems); + this.session.completePaymentMethodSelection(this.finalTotalLineItem, this.lineItems); } /** @@ -252,7 +260,7 @@ class ApplePay extends Emitter { onShippingContactSelected (event) { const status = this.session.STATUS_SUCCESS; const newShippingMethods = []; - this.session.completeShippingContactSelection(status, newShippingMethods, this.total, this.lineItems); + this.session.completeShippingContactSelection(status, newShippingMethods, this.finalTotalLineItem, this.lineItems); } /** @@ -262,7 +270,7 @@ class ApplePay extends Emitter { * @private */ onShippingMethodSelected (event) { - this.session.completeShippingMethodSelection(this.total, this.lineItems); + this.session.completeShippingMethodSelection(this.finalTotalLineItem, this.lineItems); } /** diff --git a/test/apple-pay.test.js b/test/apple-pay.test.js index f7a731cea..dddda940c 100644 --- a/test/apple-pay.test.js +++ b/test/apple-pay.test.js @@ -216,9 +216,9 @@ apiTest(function (requestMethod) { it('updates the total to reflect Pricing changes', function (done) { let applePay = this.recurly.ApplePay(merge({}, validOpts, { pricing: this.pricing })); - let originalTotal = clone(applePay.total); + let originalTotal = clone(applePay.totalLineItem); this.pricing.on('change', () => { - assert.notDeepEqual(originalTotal, applePay.total); + assert.notDeepEqual(originalTotal, applePay.totalLineItem); done(); }); this.pricing.plan('basic', { quantity: 1 }).done(); From eefa9d7cac2a6821e1aef00ae5ea8282a1dbbea2 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 24 Jan 2018 02:04:23 -0800 Subject: [PATCH 2/5] Adds Priicng line item translation to ApplePay Signed-off-by: Christopher Rogers --- lib/recurly/apple-pay.js | 28 +++++++++++++++++++++--- package-lock.json | 46 ++++++++++++++++++++-------------------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/lib/recurly/apple-pay.js b/lib/recurly/apple-pay.js index dab0e3440..d134b9ad0 100644 --- a/lib/recurly/apple-pay.js +++ b/lib/recurly/apple-pay.js @@ -104,7 +104,7 @@ class ApplePay extends Emitter { * @private */ get totalLineItem () { - return { label: this.config.label, amount: this.config.total }; + return lineItem(this.config.label, this.config.total); } /** @@ -218,8 +218,20 @@ class ApplePay extends Emitter { * @private */ updatePriceFromPricing () { - this.config.lineItems = []; - this.config.total = this.config.pricing.totalNow; + const { pricing } = this.config; + + let lineItems = this.config.lineItems = []; + this.config.total = pricing.totalNow; + + if (!pricing.hasPrice) return; + let taxAmount = pricing.price.taxes || pricing.price.tax; + + if (pricing.price.discount) lineItems.push(lineItem('Discount', pricing.price.discount)); + lineItems.push(lineItem('Subtotal', pricing.price.subtotal)); + if (taxAmount) lineItems.push(lineItem('Tax', taxAmount)); + if (pricing.price.giftCard) lineItems.push(lineItem('Gift card', pricing.price.giftCard)); + + this.config.lineItems = lineItems; } /** @@ -332,3 +344,13 @@ class ApplePay extends Emitter { return err; } } + +/** + * Builds an ApplePayLineItem + * @param {String} label + * @param {Number} amount + * @return {object} + */ +function lineItem (label = '', amount = 0) { + return { label, amount }; +} diff --git a/package-lock.json b/package-lock.json index 8f1bc34dc..9cd8587f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3696,13 +3696,6 @@ } } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -3712,6 +3705,13 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "stringstream": { "version": "0.0.5", "bundled": true @@ -4341,7 +4341,7 @@ }, "iectrl": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/iectrl/-/iectrl-0.1.2.tgz", + "resolved": "http://npmrepo.log1.recurly.net/iectrl/-/iectrl-0.1.2.tgz", "integrity": "sha1-GskwDeZErqpZ7swMsMa/YIESMyo=", "dev": true, "requires": { @@ -4354,13 +4354,13 @@ "dependencies": { "colors": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", + "resolved": "http://npmrepo.log1.recurly.net/colors/-/colors-0.6.2.tgz", "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=", "dev": true }, "commander": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-1.2.0.tgz", + "resolved": "http://npmrepo.log1.recurly.net/commander/-/commander-1.2.0.tgz", "integrity": "sha1-/VcTv6FTx9bMWZN4patMRcU1Ap4=", "dev": true, "requires": { @@ -4369,13 +4369,13 @@ }, "debug": { "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "resolved": "http://npmrepo.log1.recurly.net/debug/-/debug-0.7.4.tgz", "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", "dev": true }, "q": { "version": "0.9.7", - "resolved": "https://registry.npmjs.org/q/-/q-0.9.7.tgz", + "resolved": "http://npmrepo.log1.recurly.net/q/-/q-0.9.7.tgz", "integrity": "sha1-TeLmyzspCIyeTLwDv51C+5bOL3U=", "dev": true } @@ -6518,7 +6518,7 @@ }, "keypress": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", + "resolved": "http://npmrepo.log1.recurly.net/keypress/-/keypress-0.1.0.tgz", "integrity": "sha1-SjGI1CkbZrT2XtuZ+AaqmuKTWSo=", "dev": true }, @@ -8069,7 +8069,7 @@ }, "moment": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.0.0.tgz", + "resolved": "http://npmrepo.log1.recurly.net/moment/-/moment-2.0.0.tgz", "integrity": "sha1-K7xbRMMhg3aTq278rb1G7ZRiEf4=", "dev": true }, @@ -10904,15 +10904,6 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -10924,6 +10915,15 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", From eb53346461081d9ebd64ec17ec3d7173f888fc21 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Mon, 5 Feb 2018 16:11:07 -0700 Subject: [PATCH 3/5] Adds authorization line item to Apple Pay - When an Apple Pay transaction total is zero, an authroization line item is displayed with temp label to circumvent Apple Pay zero amount transaction limitation Signed-off-by: Christopher Rogers --- lib/recurly/apple-pay.js | 119 ++++++++++++++++++++++++----------- lib/recurly/pricing/index.js | 5 ++ test/apple-pay.test.js | 43 +++++++++++++ 3 files changed, 129 insertions(+), 38 deletions(-) diff --git a/lib/recurly/apple-pay.js b/lib/recurly/apple-pay.js index d134b9ad0..3740470a7 100644 --- a/lib/recurly/apple-pay.js +++ b/lib/recurly/apple-pay.js @@ -16,7 +16,11 @@ const APPLE_PAY_ADDRESS_MAP = { state: 'administrativeArea', postal_code: 'postalCode', country: 'countryCode' -} +}; + +const I18N = { + authorizationLineItemLabel: 'Card Authorization (Temporary)' +}; /** * Instantiation factory @@ -48,7 +52,9 @@ class ApplePay extends Emitter { super(); this._ready = false; - this.config = {}; + this.config = { + i18n: I18N + }; this.once('ready', () => this._ready = true); // Detect whether Apple Pay is available @@ -72,6 +78,8 @@ class ApplePay extends Emitter { debug('Creating new Apple Pay session'); + this.addAuthorizationLineItem(); + let session = new global.ApplePaySession(APPLE_PAY_API_VERSION, { countryCode: this.config.country, currencyCode: this.config.currency, @@ -96,7 +104,8 @@ class ApplePay extends Emitter { */ get lineItems () { // Clone configured line items - return [].concat(this.config.lineItems); + const lineItems = [].concat(this.config.lineItems); + return lineItems; } /** @@ -112,7 +121,16 @@ class ApplePay extends Emitter { * @private */ get finalTotalLineItem () { - return Object.assign({}, this.total, { type: 'final' }); + return Object.assign({}, this.totalLineItem, { type: 'final' }); + } + + /** + * Used when the total price is zero, to circumvent Apple Pay + * zero amount authorization limitation + * @return {Object} card authorization line item, $1.00 + */ + get authorizationLineItem () { + return lineItem(this.config.i18n.authorizationLineItemLabel, 1.00); } /** @@ -146,8 +164,8 @@ class ApplePay extends Emitter { const { pricing } = options; if (pricing instanceof Pricing) { this.config.pricing = pricing; - pricing.on('change', price => this.updatePriceFromPricing()); - if (pricing.hasPrice) this.updatePriceFromPricing(); + pricing.on('change', price => this.onPricingChange()); + if (pricing.hasPrice) this.onPricingChange(); } else if ('total' in options) { this.config.total = options.total; } else { @@ -157,6 +175,8 @@ class ApplePay extends Emitter { if ('recurly' in options) this.recurly = options.recurly; else return this.initError = this.error('apple-pay-factory-only'); + if ('i18n' in options) Object.assign(this.config.i18n, options.i18n); + // Retrieve remote configuration this.recurly.request('get', '/apple_pay/info', (err, info) => { if (err) return this.initError = this.error(err); @@ -174,32 +194,6 @@ class ApplePay extends Emitter { }); } - /** - * Maps data from the Apple Pay token into the inputs - * object that is sent to RA for tokenization - * - * @public - */ - mapPaymentData (inputs, data) { - inputs.paymentData = data.token.paymentData; - inputs.paymentMethod = data.token.paymentMethod; - - if (!data.billingContact) return; - if (FIELDS.some(field => inputs[field])) return; - - FIELDS.forEach(field => { - if (!APPLE_PAY_ADDRESS_MAP[field]) return; - - let tokenData = data.billingContact[APPLE_PAY_ADDRESS_MAP[field]]; - - // address lines are an array from Apple Pay - if (field === 'address1') tokenData = tokenData[0]; - else if (field === 'address2') tokenData = tokenData[1]; - - inputs[field] = tokenData; - }); - } - /** * Begins Apple Pay transaction * @@ -211,25 +205,46 @@ class ApplePay extends Emitter { this.session.begin(); } + /** + * Conditionally adds an authorization line item in order to circumvent Apple Pay + * zero-amount authorization limitation + * + * @private + */ + addAuthorizationLineItem () { + if (parseFloat(this.config.total) > 0) return; + this.config.lineItems.push(this.authorizationLineItem); + this.config.total = this.authorizationLineItem.amount; + } + /** * Updates line items and total price on pricing module changes * * @param {Object} price Pricing.price * @private */ - updatePriceFromPricing () { + onPricingChange () { const { pricing } = this.config; let lineItems = this.config.lineItems = []; this.config.total = pricing.totalNow; if (!pricing.hasPrice) return; - let taxAmount = pricing.price.taxes || pricing.price.tax; + let taxAmount = pricing.price.now.taxes || pricing.price.now.tax; + + lineItems.push(lineItem('Subtotal', pricing.subtotalPreDiscountNow)); - if (pricing.price.discount) lineItems.push(lineItem('Discount', pricing.price.discount)); - lineItems.push(lineItem('Subtotal', pricing.price.subtotal)); - if (taxAmount) lineItems.push(lineItem('Tax', taxAmount)); - if (pricing.price.giftCard) lineItems.push(lineItem('Gift card', pricing.price.giftCard)); + if (+pricing.price.now.discount) { + lineItems.push(lineItem('Discount', -pricing.price.now.discount)); + } + + if (+taxAmount) { + lineItems.push(lineItem('Tax', taxAmount)); + } + + if (+pricing.price.now.giftCard) { + lineItems.push(lineItem('Gift card', -pricing.price.now.giftCard)); + } this.config.lineItems = lineItems; } @@ -260,6 +275,7 @@ class ApplePay extends Emitter { */ onPaymentMethodSelected (event) { debug('Payment method selected', event); + console.log(this.lineItems); this.session.completePaymentMethodSelection(this.finalTotalLineItem, this.lineItems); } @@ -343,6 +359,33 @@ class ApplePay extends Emitter { this.emit('error', err); return err; } + + + /** + * Maps data from the Apple Pay token into the inputs + * object that is sent to RA for tokenization + * + * @private + */ + mapPaymentData (inputs, data) { + inputs.paymentData = data.token.paymentData; + inputs.paymentMethod = data.token.paymentMethod; + + if (!data.billingContact) return; + if (FIELDS.some(field => inputs[field])) return; + + FIELDS.forEach(field => { + if (!APPLE_PAY_ADDRESS_MAP[field]) return; + + let tokenData = data.billingContact[APPLE_PAY_ADDRESS_MAP[field]]; + + // address lines are an array from Apple Pay + if (field === 'address1') tokenData = tokenData[0]; + else if (field === 'address2') tokenData = tokenData[1]; + + inputs[field] = tokenData; + }); + } } /** diff --git a/lib/recurly/pricing/index.js b/lib/recurly/pricing/index.js index a05de5e32..f90992bca 100644 --- a/lib/recurly/pricing/index.js +++ b/lib/recurly/pricing/index.js @@ -36,6 +36,11 @@ export class Pricing extends Emitter { return decimalize(this.hasPrice ? this.price.now.total : 0); } + get subtotalPreDiscountNow () { + let subtotalPreDiscountNow = parseFloat(this.price.now.subtotal) + parseFloat(this.price.now.discount); + return decimalize(this.hasPrice ? subtotalPreDiscountNow : 0) + } + get currencyCode () { return this.items.currency || ''; } diff --git a/test/apple-pay.test.js b/test/apple-pay.test.js index dddda940c..9252d0bb3 100644 --- a/test/apple-pay.test.js +++ b/test/apple-pay.test.js @@ -373,6 +373,49 @@ apiTest(function (requestMethod) { }); }); }); + + describe('Authorization line item', () => { + it('has a customizable label', function () { + const example = 'Test auth label'; + const applePay = this.recurly.ApplePay(Object.assign({}, validOpts, { + i18n: { authorizationLineItemLabel: example } + })); + applePay.authorizationLineItem.label === example; + }); + + describe('when the total price is greater than zero', function () { + beforeEach(function (done) { + this.applePay = this.recurly.ApplePay(validOpts); + this.applePay.ready(() => { + this.applePay.begin(); + done(); + }); + }); + + it('is not present', function () { + assert.equal(this.applePay.config.total, 3.49); + assert.equal(this.applePay.lineItems.length, 0); + }); + }); + + describe('when the total price is zero', function () { + beforeEach(function (done) { + this.applePay = this.recurly.ApplePay(Object.assign({}, validOpts, { + total: 0 + })); + this.applePay.ready(() => { + this.applePay.begin(); + done(); + }); + }); + + it('is present', function () { + assert.equal(this.applePay.config.total, 1); + assert.equal(this.applePay.lineItems.length, 1); + assert.deepEqual(this.applePay.lineItems[0], this.applePay.authorizationLineItem); + }); + }); + }); }); }); From 2e69bcd615912c9416c8e3a4d49e33bf4f098ca5 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Tue, 6 Feb 2018 11:58:28 -0700 Subject: [PATCH 4/5] Adds additional i18n for ApplePay line item labels Signed-off-by: Christopher Rogers --- karma.conf.js | 1 + lib/recurly/apple-pay.js | 27 +++++++++------ test/apple-pay.test.js | 73 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index 793f43b64..07aae5e91 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -20,6 +20,7 @@ var staticConfig = { autoWatch: true, browsers: [ 'PhantomJS' + // 'ChromeDebug' // 'IE11 - Win7' ], singleRun: true, diff --git a/lib/recurly/apple-pay.js b/lib/recurly/apple-pay.js index 3740470a7..06a785701 100644 --- a/lib/recurly/apple-pay.js +++ b/lib/recurly/apple-pay.js @@ -2,6 +2,7 @@ import Emitter from 'component-emitter'; import errors from '../errors'; import {Pricing} from './pricing'; import {normalize} from '../util/normalize'; +import decimalize from '../util/decimalize'; import {FIELDS} from './token'; const debug = require('debug')('recurly:apple-pay'); @@ -19,7 +20,11 @@ const APPLE_PAY_ADDRESS_MAP = { }; const I18N = { - authorizationLineItemLabel: 'Card Authorization (Temporary)' + authorizationLineItemLabel: 'Card Authorization (Temporary)', + subtotalLineItemLabel: 'Subtotal', + discountLineItemLabel: 'Discount', + taxLineItemLabel: 'Tax', + giftCardLineItemLabel: 'Gift card' }; /** @@ -160,6 +165,11 @@ class ApplePay extends Emitter { // Initialize with no line items this.config.lineItems = []; + if ('recurly' in options) this.recurly = options.recurly; + else return this.initError = this.error('apple-pay-factory-only'); + + if ('i18n' in options) Object.assign(this.config.i18n, options.i18n); + // Listen for pricing changes to update totals and currency const { pricing } = options; if (pricing instanceof Pricing) { @@ -172,11 +182,6 @@ class ApplePay extends Emitter { return this.initError = this.error('apple-pay-config-missing', { opt: 'total' }); } - if ('recurly' in options) this.recurly = options.recurly; - else return this.initError = this.error('apple-pay-factory-only'); - - if ('i18n' in options) Object.assign(this.config.i18n, options.i18n); - // Retrieve remote configuration this.recurly.request('get', '/apple_pay/info', (err, info) => { if (err) return this.initError = this.error(err); @@ -232,18 +237,18 @@ class ApplePay extends Emitter { if (!pricing.hasPrice) return; let taxAmount = pricing.price.now.taxes || pricing.price.now.tax; - lineItems.push(lineItem('Subtotal', pricing.subtotalPreDiscountNow)); + lineItems.push(lineItem(this.config.i18n.subtotalLineItemLabel, pricing.subtotalPreDiscountNow)); if (+pricing.price.now.discount) { - lineItems.push(lineItem('Discount', -pricing.price.now.discount)); + lineItems.push(lineItem(this.config.i18n.discountLineItemLabel, -pricing.price.now.discount)); } if (+taxAmount) { - lineItems.push(lineItem('Tax', taxAmount)); + lineItems.push(lineItem(this.config.i18n.taxLineItemLabel, taxAmount)); } if (+pricing.price.now.giftCard) { - lineItems.push(lineItem('Gift card', -pricing.price.now.giftCard)); + lineItems.push(lineItem(this.config.i18n.giftCardLineItemLabel, -pricing.price.now.giftCard)); } this.config.lineItems = lineItems; @@ -395,5 +400,5 @@ class ApplePay extends Emitter { * @return {object} */ function lineItem (label = '', amount = 0) { - return { label, amount }; + return { label, amount: decimalize(amount) }; } diff --git a/test/apple-pay.test.js b/test/apple-pay.test.js index 9252d0bb3..fd6373864 100644 --- a/test/apple-pay.test.js +++ b/test/apple-pay.test.js @@ -114,8 +114,8 @@ apiTest(function (requestMethod) { describe('when given options.pricing', function () { beforeEach(function () { - let pricing = this.pricing = this.recurly.Pricing(); - this.applePay = this.recurly.ApplePay(merge({}, validOpts, { pricing })) + let pricing = this.pricing = this.recurly.Pricing.Checkout(); + this.applePay = this.recurly.ApplePay(merge({}, validOpts, { pricing })); }); it('binds a pricing instance', function (done) { @@ -131,6 +131,75 @@ apiTest(function (requestMethod) { done(); }); }); + + describe('when the pricing instance includes several items', () => { + beforeEach(function (done) { + this.subscription = this.recurly.Pricing.Subscription() + .plan('basic') + .address({ country: 'US', postalCode: '94117' }) + .done(() => { + this.pricing + .subscription(this.subscription) + .adjustment({ amount: 100 }) + .coupon('coop') + .giftCard('super-gift-card') + .done(() => done()); + }); + }); + + it('includes relevant line items', function () { + const subtotal = this.applePay.lineItems[0]; + const discount = this.applePay.lineItems[1]; + const giftCard = this.applePay.lineItems[2]; + const total = this.applePay.totalLineItem; + assert.equal(this.applePay.lineItems.length, 3); + assert.strictEqual(subtotal.label, this.applePay.config.i18n.subtotalLineItemLabel); + assert.strictEqual(discount.label, this.applePay.config.i18n.discountLineItemLabel); + assert.strictEqual(giftCard.label, this.applePay.config.i18n.giftCardLineItemLabel); + assert.strictEqual(subtotal.amount, '121.99'); + assert.strictEqual(discount.amount, '-20.00'); + assert.strictEqual(giftCard.amount, '-20.00'); + assert.strictEqual(total.amount, '81.99'); + }); + + describe('when the line item labels are customized', () => { + beforeEach(function () { + const pricing = this.pricing; + const i18n = this.exampleI18n = { + authorizationLineItemLabel: 'Custom card authorization label', + subtotalLineItemLabel: 'Custom subtotal label', + discountLineItemLabel: 'Custom discount label', + taxLineItemLabel: 'Custom tax label', + giftCardLineItemLabel: 'Custom Gift card label' + }; + this.applePay = this.recurly.ApplePay(merge({}, validOpts, { pricing, i18n })); + }); + + it('displays those labels', function () { + const subtotal = this.applePay.lineItems[0]; + const discount = this.applePay.lineItems[1]; + const giftCard = this.applePay.lineItems[2]; + assert.equal(subtotal.label, this.exampleI18n.subtotalLineItemLabel); + assert.equal(discount.label, this.exampleI18n.discountLineItemLabel); + assert.equal(giftCard.label, this.exampleI18n.giftCardLineItemLabel); + }); + }); + + describe('when the total price is zero', () => { + beforeEach(function (done) { + this.pricing.coupon('coop-fixed-all-500').done(() => done()); + }); + + it('adds an authorization line item', function () { + assert.strictEqual(this.applePay.totalLineItem.amount, '0.00'); + this.applePay.begin(); + const authorization = this.applePay.lineItems[2]; + assert.strictEqual(authorization.label, this.applePay.config.i18n.authorizationLineItemLabel); + assert.strictEqual(authorization.amount, '1.00'); + assert.strictEqual(this.applePay.totalLineItem.amount, '1.00'); + }); + }); + }); }); it('requires a valid country', function (done) { From de615512cb8422efbb83895fb175fca8f3a1b850 Mon Sep 17 00:00:00 2001 From: Christopher Rogers Date: Wed, 14 Feb 2018 10:15:43 -0800 Subject: [PATCH 5/5] Cleans up ApplePay Signed-off-by: Christopher Rogers --- lib/recurly/apple-pay.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/recurly/apple-pay.js b/lib/recurly/apple-pay.js index 06a785701..6091475f4 100644 --- a/lib/recurly/apple-pay.js +++ b/lib/recurly/apple-pay.js @@ -109,8 +109,7 @@ class ApplePay extends Emitter { */ get lineItems () { // Clone configured line items - const lineItems = [].concat(this.config.lineItems); - return lineItems; + return [].concat(this.config.lineItems); } /** @@ -280,7 +279,6 @@ class ApplePay extends Emitter { */ onPaymentMethodSelected (event) { debug('Payment method selected', event); - console.log(this.lineItems); this.session.completePaymentMethodSelection(this.finalTotalLineItem, this.lineItems); }