Skip to content

Commit

Permalink
Merge pull request #701 from recurly/ideal
Browse files Browse the repository at this point in the history
IDeal and Sofort
  • Loading branch information
brittany-wood authored Feb 24, 2022
2 parents 87dbe6c + 01279dc commit f1c11e1
Show file tree
Hide file tree
Showing 18 changed files with 693 additions and 5 deletions.
2 changes: 2 additions & 0 deletions lib/recurly.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import version from './recurly/version';
import { tokenDispatcher as token } from './recurly/token';
import { factory as Adyen } from './recurly/adyen';
import { factory as ApplePay } from './recurly/apple-pay';
import { factory as BankRedirect } from './recurly/bank-redirect';
import { factory as Elements } from './recurly/elements';
import { factory as Frame } from './recurly/frame';
import { factory as PayPal } from './recurly/paypal';
Expand Down Expand Up @@ -106,6 +107,7 @@ const DEFAULTS = {
export class Recurly extends Emitter {
Adyen = Adyen;
ApplePay = ApplePay;
BankRedirect = BankRedirect;
coupon = coupon;
Elements = Elements;
Frame = Frame;
Expand Down
204 changes: 204 additions & 0 deletions lib/recurly/bank-redirect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import Emitter from 'component-emitter';
import errors from './errors';

const debug = require('debug')('recurly:bank-redirect');

/**
* Instantiation factory.
*
* @param {Object} options
* @return {BankRedirect}
*/
export function factory (options) {
return new BankRedirect(Object.assign({}, options, { recurly: this }));
}

/**
* Initializes a BankRedirect session.
*
* @param {Object} options
* @param {Recurly} options.recurly
* @constructor
* @public
*/
class BankRedirect extends Emitter {
constructor (options) {
debug('Creating new BankRedirect session');

super();
this.recurly = options.recurly;
}

/**
* Fetch the banks.
* @param {Object} data
* @param {string} attachTo
*/
loadBanks (data, attachTo) {
debug('Load banks');

const errors = validateBankPayload(data);
if (errors.length > 0) {
return this.error('validation', { fields: errors });
}

this.recurly.request.get({
route: '/bank_redirect/banks',
data,
done: (error, response) => {
if (error) {
return this.error('banks-error', { cause: error });
}

if (attachTo) {
attachListToSelect(attachTo, response.banks, 'issuer_id');
}
this.emit('banks', response.banks);
}
});
}

/**
* Fetch the countries.
* @param {Object} data
* @param {string} attachTo
*/
loadCountries (data, attachTo) {
debug('Load countries');

const errors = validateCountriesPayload(data);
if (errors.length > 0) {
return this.error('validation', { fields: errors });
}

const countries = [
{ id: 'AT', name: 'Austria' },
{ id: 'BE', name: 'Belgium' },
{ id: 'DE', name: 'Germany' },
{ id: 'IT', name: 'Italy' },
{ id: 'ES', name: 'Spain' },
{ id: 'NL', name: 'The Netherlands' }
];
if (attachTo) {
attachListToSelect(attachTo, countries, 'country_code');
}
this.emit('countries', countries);
}

/**
* Start the BankRedirect Payment Modal.
* @param {Object} data
*/
start (data) {
debug('Start BankRedirect Payment Modal');

const errors = validatePayload(data);
if (errors.length > 0) {
return this.error('validation', { fields: errors });
}

const frame = this.recurly.Frame({
height: 600,
path: '/bank_redirect/start',
payload: data
});
frame.once('error', cause => this.error('bank-redirect-error', { cause }));
frame.once('done', token => this.emit('token', token));
return frame;
}

error (...params) {
const err = params[0] instanceof Error ? params[0] : errors(...params);
this.emit('error', err);

return err;
}
}

function validatePayload (data) {
const errors = validatePaymentMethodType(data);

const validation = {
ideal: validateIdealPayload,
sofort: validateSofortPayload,
}[data && data.payment_method_type];
if (validation) {
errors.push(...validation(data));
}

return errors;
}

function validateBankPayload (data) {
return validatePaymentMethodType(data);
}

function validateCountriesPayload (data) {
return validatePaymentMethodType(data);
}

function validatePaymentMethodType (data) {
if (!data || !data.payment_method_type) {
return ['payment_method_type cannot be blank'];
}

if (data.payment_method_type !== 'ideal' && data.payment_method_type !== 'sofort') {
return ['invalid payment_method_type'];
}

return [];
}

function validateIdealPayload (data) {
const errors = [];

if (!data || !data.issuer_id) {
errors.push('issuer_id cannot be blank');
}

if (!data || !data.invoice_uuid) {
errors.push('invoice_uuid cannot be blank');
}

return errors;
}

function validateSofortPayload (data) {
const errors = [];

if (!data || !data.country_code) {
errors.push('country_code cannot be blank');
}

if (!data || !data.invoice_uuid) {
errors.push('invoice_uuid cannot be blank');
}

return errors;
}

function attachListToSelect (selector, list, selectorId) {
let $select = document.querySelector(selector);
if (!$select) {
return;
}

if ($select.tagName != 'SELECT') {
const $container = $select;
$select = document.createElement('select');
$select.id = selectorId;
$select.setAttribute('name', selectorId);
$container.appendChild($select);
}

while ($select.options.length > 0) {
$select.remove(0);
}

for (const { id, name } of list) {
const $option = document.createElement('option');
$option.appendChild(document.createTextNode(name));
$option.setAttribute('value', id);
$select.appendChild($option);
}
}
12 changes: 12 additions & 0 deletions lib/recurly/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,18 @@ const ERRORS = [
cause property for more detail.`,
classification: 'internal'
},
{
code: 'bank-redirect-error',
message: `An error occurred within your BankRedirect payment process. Please refer to the
cause property for more detail.`,
classification: 'internal'
},
{
code: 'banks-error',
message: `An error occurred while loading the available banks. Please refer to the
cause property for more detail.`,
classification: 'api'
},
{
code: '3ds-vendor-load-error',
message: c => `An error occurred while loading a dependency from ${c.vendor}. Please refer
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions test/server/fixtures/bank_redirect/banks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = function banks () {
const payload = this.query;

if (payload.payment_method_type === 'ideal') {
if (payload.error) {
return {
error: {
code: 'api-error',
message: 'Api error',
}
};
}

return {
banks: [
{ id: 'bank1', name: 'Bank 1' },
{ id: 'bank2', name: 'Bank 2' },
]
};
}

return {
error: {
code: 'invalid-payment-method-type',
message: 'Invalid payment method type'
}
};
};
1 change: 1 addition & 0 deletions test/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ app.use(route.get('/apple_pay/token', json));
app.use(route.post('/apple_pay/start', json));
app.use(route.post('/apple_pay/token', json));
app.use(route.get('/bank', json));
app.use(route.get('/bank_redirect/banks', json));
app.use(route.get('/coupons/:id', json));
app.use(route.get('/events', ok));
app.use(route.post('/events', ok));
Expand Down
Loading

0 comments on commit f1c11e1

Please sign in to comment.