diff --git a/app/components/payments/bank-account.js b/app/components/payments/bank-account.js
index 606b5d7c3..3c19424f1 100644
--- a/app/components/payments/bank-account.js
+++ b/app/components/payments/bank-account.js
@@ -1,9 +1,21 @@
import Ember from 'ember';
const {
- Component
+ Component,
+ computed,
+ get
} = Ember;
export default Component.extend({
- classNames: ['bank-account']
+ classNameBindings: ['statusClass'],
+ classNames: ['bank-account', 'account-setup__section'],
+
+ accountNumber: '000123456789',
+ routingNumber: '110000000',
+
+ status: computed.alias('account.bankAccountStatus'),
+
+ statusClass: computed('status', function() {
+ return `account-setup__section--${get(this, 'status')}`;
+ })
});
diff --git a/app/controllers/project/settings/donations/payments.js b/app/controllers/project/settings/donations/payments.js
index 1c2aa71bd..ee8fc1fae 100644
--- a/app/controllers/project/settings/donations/payments.js
+++ b/app/controllers/project/settings/donations/payments.js
@@ -1,17 +1,26 @@
import Ember from 'ember';
+import FriendlyError from 'code-corps-ember/utils/friendly-error';
+
const {
computed: { alias },
Controller,
get,
inject: { service },
- merge
+ merge,
+ RSVP,
+ set
} = Ember;
+const ACCOUNT_TOKEN_CREATION_ERROR = 'There was a problem in using your bank account information. Please check your input and try again.';
+const ACCOUNT_ADDING_ERROR = 'There was a problem in attaching the provided bank account information to your Stripe account.';
+
export default Controller.extend({
currentUser: service(),
store: service(),
+ stripe: service(),
user: alias('currentUser.user'),
+ stripeConnectAccount: alias('project.organization.stripeConnectAccount'),
actions: {
onRecipientInformationSubmitted(organization, email, recipientInformation) {
@@ -20,8 +29,63 @@ export default Controller.extend({
.save();
},
- onBankAccountInformationSubmitted(/* bankAccountInformation */) {
- // TODO: Handle receiving bank account information
+ onBankAccountInformationSubmitted({ accountNumber, routingNumber }) {
+ set(this, 'isBusy', true);
+
+ let promises = {
+ tokenData: this._createAccountToken(accountNumber, routingNumber),
+ stripeConnectAccount: get(this, 'stripeConnectAccount')
+ };
+
+ RSVP.hash(promises)
+ .then(({ tokenData, stripeConnectAccount }) => this._addBankAccount(tokenData, stripeConnectAccount))
+ .catch((response) => this._handleError(response))
+ .finally(() => set(this, 'isBusy', false));
}
+ },
+
+ // adding bank account information
+
+ _createAccountToken(accountNumber, routingNumber) {
+ let stripe = get(this, 'stripe');
+ let params = this._bankAccountTokenParams(accountNumber, routingNumber);
+
+ return stripe.bankAccount.createToken(params)
+ .then((stripeResponse) => RSVP.resolve(stripeResponse))
+ .catch((reason) => this._handleBankAccountTokenError(reason));
+ },
+
+ _handleBankAccountTokenError() {
+ let friendlyError = new FriendlyError(ACCOUNT_TOKEN_CREATION_ERROR);
+ return RSVP.reject(friendlyError);
+ },
+
+ _addBankAccount(tokenData, stripeConnectAccount) {
+ set(stripeConnectAccount, 'externalAccount', tokenData.id);
+
+ return stripeConnectAccount.save()
+ .then((stripeConnectAccount) => RSVP.resolve(stripeConnectAccount))
+ .catch((reason) => this._handleAddBankAccountError(reason));
+ },
+
+ _handleAddBankAccountError() {
+ let friendlyError = new FriendlyError(ACCOUNT_ADDING_ERROR);
+ return RSVP.reject(friendlyError);
+ },
+
+ _bankAccountTokenParams(accountNumber, routingNumber) {
+ return {
+ account_number: accountNumber,
+ routing_number: routingNumber,
+ object: 'bank_account',
+ country: 'US',
+ currency: 'USD'
+ };
+ },
+
+ // setting error property
+
+ _handleError(error) {
+ this.set('error', error);
}
});
diff --git a/app/styles/_colors.scss b/app/styles/_colors.scss
index 8afa7167b..5ebae6b24 100644
--- a/app/styles/_colors.scss
+++ b/app/styles/_colors.scss
@@ -71,10 +71,11 @@ $taskTypeTextOnBackgroundColors: (
$light-red-background: #FFE5E1;
$light-blue-background: #EDF9FF;
+$light-green-background: #EFFFEE;
$idea-background: #FFF9DE;
$issue-background: $light-red-background;
-$progress-background: #EFFFEE;
+$progress-background: $light-green-background;
$task-background: $light-blue-background;
$taskTypeBackgrounds: (
diff --git a/app/styles/_icons.scss b/app/styles/_icons.scss
index 95107757e..4413b2b28 100644
--- a/app/styles/_icons.scss
+++ b/app/styles/_icons.scss
@@ -15,7 +15,7 @@ $spritePath: '/assets/images/icons/sprite';
$spriteURL: $spritePath + '.png';
$spritex2URL: $spritePath + '@2x.png';
$sprite-bgiSizeW: 230px;
-$sprite-bgiSizeH: 304px;
+$sprite-bgiSizeH: 324px;
$logo: 230px 50px $spriteURL 0px 0px $spritex2URL;
$logo-small: 161px 35px $spriteURL 0px -50px $spritex2URL;
$location: 16px 16px $spriteURL 0px -85px $spritex2URL;
@@ -59,6 +59,7 @@ $calendar: 20px 20px $spriteURL -42px -268px $spritex2URL;
$task-small: 16px 16px $spriteURL 0px -288px $spritex2URL;
$issue-small: 16px 16px $spriteURL -16px -288px $spritex2URL;
$idea-small: 16px 16px $spriteURL -32px -288px $spritex2URL;
+$tick-green-large: 20px 20px $spriteURL 0px -304px $spritex2URL;
.box-icon {
@include sprite($box);
diff --git a/app/styles/app.scss b/app/styles/app.scss
index c0e790526..1a377fdab 100644
--- a/app/styles/app.scss
+++ b/app/styles/app.scss
@@ -50,6 +50,8 @@
@import "components/navigation-menu";
@import "components/organization-header";
@import "components/organization-members";
+@import "components/payments/account-setup";
+@import "components/payments/bank-account";
@import "components/pager-control";
@import "components/task-card";
@import "components/task-comment-list";
diff --git a/app/styles/components/payments/account-setup.scss b/app/styles/components/payments/account-setup.scss
new file mode 100644
index 000000000..1ce0db356
--- /dev/null
+++ b/app/styles/components/payments/account-setup.scss
@@ -0,0 +1,55 @@
+//
+// ACCOUNT SETUP SECTIONS
+//
+.account-setup__section {
+ align-items: center;
+ background-color: #FCFCFC;
+ border: 1px solid #DDD;
+ border-radius: 4px;
+ display: flex;
+ min-height: 4em;
+ padding: 1.5em;
+
+ aside {
+ @include span-columns(3);
+ align-self: baseline;
+ }
+
+ h1 {
+ font-size: 1.1em;
+ font-weight: normal;
+ margin: 0;
+ }
+
+ label {
+ display: block;
+ font-weight: 600;
+ margin-bottom: .3em;
+ }
+
+ section {
+ @include span-columns(9);
+ }
+}
+
+//
+// STATES
+//
+.account-setup__section--required {
+ @include outer-container;
+}
+
+.account-setup__section--verified {
+ background-color: $light-green-background;
+ border-color: $green;
+
+ aside h1:before {
+ content: "";
+ display: block;
+ float: left;
+ height: 20px;
+ margin: 0 10px 0 0;
+ width: 24px;
+ @include sprite($tick-green-large);
+ }
+}
diff --git a/app/styles/components/payments/bank-account.scss b/app/styles/components/payments/bank-account.scss
new file mode 100644
index 000000000..e19e6c30e
--- /dev/null
+++ b/app/styles/components/payments/bank-account.scss
@@ -0,0 +1,10 @@
+//
+// ACCOUNT INFORMATION
+//
+.bank-account__account {
+ @extend .input-group;
+}
+
+.bank-account__routing {
+ @extend .input-group;
+}
diff --git a/app/templates/components/payments/account-setup.hbs b/app/templates/components/payments/account-setup.hbs
index ca83e60e3..438c82af5 100644
--- a/app/templates/components/payments/account-setup.hbs
+++ b/app/templates/components/payments/account-setup.hbs
@@ -1,18 +1,9 @@
Create a Stripe account for {{organizationName}}
-{{!
- TODO:
- Account setup should decide if it should render the "form"
- for each section, or a "done" component for that particular data.
-
- Possibly, even, the payments controller should decide and then
- pass in flags to the account setup, but that might be overkill.
-}}
{{payments/contact-info email=email}}
{{payments/funds-recipient
fundsRecipient=fundsRecipient
recipientInformationSubmitted=(action onRecipientInformationSubmitted email)}}
-{{payments/bank-account
- bankAccountInformationSubmitted=(action onBankAccountInformationSubmitted)}}
+{{payments/bank-account account=account isBusy=isBusy submit=(action onBankAccountInformationSubmitted)}}
diff --git a/app/templates/components/payments/bank-account.hbs b/app/templates/components/payments/bank-account.hbs
index 8a57174ff..9816599e6 100644
--- a/app/templates/components/payments/bank-account.hbs
+++ b/app/templates/components/payments/bank-account.hbs
@@ -1,12 +1,30 @@
-
- Routing Number
- {{input type="text" name="routing-number" value=routingNumber}}
-
-
- Account Number
- {{input type="text" name="account-number" value=accountNumber}}
-
+
-
- Submit
-
\ No newline at end of file
+{{#if (eq status 'required')}}
+
+
+ Routing Number
+ {{input disabled=isBusy type="text" name="routing-number" value=routingNumber}}
+
+
+ Account Number
+ {{input disabled=isBusy type="text" name="account-number" value=accountNumber}}
+
+ Submit
+
+{{/if}}
+
+{{#if (eq status 'verified')}}
+
+
+
Routing Number
+
{{account.bankAccountRoutingNumber}}
+
+
+
Account Number
+
{{account.bankAccountLast4}}
+
+
+{{/if}}
diff --git a/app/templates/components/project-settings-menu.hbs b/app/templates/components/project-settings-menu.hbs
index ea89a999d..d7e2ec01d 100644
--- a/app/templates/components/project-settings-menu.hbs
+++ b/app/templates/components/project-settings-menu.hbs
@@ -5,10 +5,10 @@
{{link-to "Contributors" "project.settings.contributors" project.slug}}
- {{link-to "Donations" "project.settings.donations.goals" project.slug}}
+ {{link-to "Donation goals" "project.settings.donations.goals" project.slug}}
- {{link-to "Payments" "project.settings.donations.payments" project.slug}}
+ {{link-to "Payment settings" "project.settings.donations.payments" project.slug}}
{{link-to "Basic settings" "project.settings.profile" project.slug}}
diff --git a/app/templates/project/settings/donations/payments.hbs b/app/templates/project/settings/donations/payments.hbs
index 60e73e85b..f5b23fc1f 100644
--- a/app/templates/project/settings/donations/payments.hbs
+++ b/app/templates/project/settings/donations/payments.hbs
@@ -1,6 +1,10 @@
{{payments/account-setup
- email=user.email
+ account=project.organization.stripeConnectAccount
+ isBusy=isBusy
onBankAccountInformationSubmitted=(action 'onBankAccountInformationSubmitted')
onRecipientInformationSubmitted=(action 'onRecipientInformationSubmitted' project.organization)
organizationName=project.organization.name
}}
+{{#if error}}
+ {{error-formatter error=error}}
+{{/if}}
\ No newline at end of file
diff --git a/public/assets/images/icons/sprite.png b/public/assets/images/icons/sprite.png
index 925ab848b..f7f9eb408 100644
Binary files a/public/assets/images/icons/sprite.png and b/public/assets/images/icons/sprite.png differ
diff --git a/public/assets/images/icons/sprite@2x.png b/public/assets/images/icons/sprite@2x.png
index 837c6d091..1c8f429ff 100644
Binary files a/public/assets/images/icons/sprite@2x.png and b/public/assets/images/icons/sprite@2x.png differ
diff --git a/tests/integration/components/payments/bank-account-test.js b/tests/integration/components/payments/bank-account-test.js
index fe870aa16..5e868b30a 100644
--- a/tests/integration/components/payments/bank-account-test.js
+++ b/tests/integration/components/payments/bank-account-test.js
@@ -9,11 +9,8 @@ const { K } = Ember;
let page = PageObject.create(bankAccountComponent);
-function setHandler(context, bankAccountInformationSubmittedHandler = K) {
- context.set(
- 'bankAccountInformationSubmittedHandler',
- bankAccountInformationSubmittedHandler
- );
+function setHandler(context, submitHandler = K) {
+ context.set('submitHandler', submitHandler);
}
moduleForComponent('payments/bank-account', 'Integration | Component | payments/bank account', {
@@ -27,11 +24,71 @@ moduleForComponent('payments/bank-account', 'Integration | Component | payments/
}
});
-test('it renders', function(assert) {
- page.render(hbs`{{payments/bank-account bankAccountInformationSubmitted=bankAccountInformationSubmittedHandler}}`);
- assert.equal(this.$('.bank-account').length, 1, 'Component renders');
+test('it renders correctly for "pending" status', function(assert) {
+ assert.expect(1);
+
+ let account = { bankAccountStatus: 'pending_requirement' };
+ this.set('account', account);
+
+ page.render(hbs`{{payments/bank-account account=account submit=submitHandler}}`);
+ assert.ok(page.rendersPending, 'Component is rendered in pending status.');
+});
+
+test('it renders correctly for "required" status', function(assert) {
+ assert.expect(4);
+
+ let account = { bankAccountStatus: 'required' };
+ this.set('account', account);
+
+ page.render(hbs`{{payments/bank-account account=account submit=submitHandler}}`);
+ assert.ok(page.rendersRequired, 'Component is rendered in required mode.');
+ assert.ok(page.rendersAccountNumberField, 'Component renders the account number field.');
+ assert.ok(page.rendersRoutingNumberField, 'Component renders the routing number field.');
+ assert.ok(page.rendersSubmitButton, 'Component renders the submit button.');
+});
+
+test('it renders correctly for "verified" status', function(assert) {
+ assert.expect(3);
+
+ let account = { bankAccountLast4: 4321, bankAccountRoutingNumber: 123456, bankAccountStatus: 'verified' };
+ this.set('account', account);
+
+ page.render(hbs`{{payments/bank-account account=account submit=submitHandler}}`);
+ assert.ok(page.rendersVerified, 'Component is rendered in verified mode.');
+ assert.equal(page.accountLast4Text, 4321, 'Component renders last 4 digits of account number.');
+ assert.equal(page.routingNumberText, 123456, 'Component renders routing number.');
});
-// TODO: Write tests, remove 'it renders' test
+test('it sends properties with submit action', function(assert) {
+ assert.expect(1);
-// test('it sends out bank account parameters on submit')
+ let account = { bankAccountStatus: 'required' };
+ this.set('account', account);
+
+ let input = {
+ routingNumber: '123456',
+ accountNumber: '654321'
+ };
+
+ setHandler(this, (output) => {
+ assert.deepEqual(output, input, 'Correct parameters were sent out with action.');
+ });
+
+ page.render(hbs`{{payments/bank-account account=account submit=submitHandler}}`)
+ .accountNumber(input.accountNumber)
+ .routingNumber(input.routingNumber)
+ .clickSubmit();
+});
+
+test('it disables controls when busy', function(assert) {
+ assert.expect(3);
+
+ let account = { bankAccountStatus: 'required' };
+ this.set('account', account);
+
+ page.render(hbs`{{payments/bank-account account=account isBusy=true submit=submitHandler}}`);
+
+ assert.ok(page.accountNumberFieldIsDisabled, 'Account number field is disabled when busy.');
+ assert.ok(page.routingNumberFieldIsDisabled, 'Routing number field is disabled when buys.');
+ assert.ok(page.submitButtonIsDisabled, 'Submit button is disabled when busy.');
+});
diff --git a/tests/integration/components/project-settings-menu-test.js b/tests/integration/components/project-settings-menu-test.js
index 6852f9637..eae38bb9f 100644
--- a/tests/integration/components/project-settings-menu-test.js
+++ b/tests/integration/components/project-settings-menu-test.js
@@ -26,8 +26,8 @@ test('when authenticated and can manage organization, it renders properly', func
assert.equal(this.$('.project-settings-menu').length, 1, 'The component itself renders');
assert.equal(this.$('.project-settings-menu li').length, 4, 'The list items render');
assert.equal(this.$('li a:contains("Contributors")').length, 1, 'The contributors link renders');
- assert.equal(this.$('li a:contains("Donations")').length, 1, 'The donations link renders');
- assert.equal(this.$('li a:contains("Payments")').length, 1, 'The payments link renders');
+ assert.equal(this.$('li a:contains("Donation goals")').length, 1, 'The donation goals link renders');
+ assert.equal(this.$('li a:contains("Payment settings")').length, 1, 'The payment settings link renders');
assert.equal(this.$('li a:contains("Basic settings")').length, 1, 'The basic settings link renders');
});
diff --git a/tests/pages/components/payments/bank-account.js b/tests/pages/components/payments/bank-account.js
index 8794276b2..ecf0a56b4 100644
--- a/tests/pages/components/payments/bank-account.js
+++ b/tests/pages/components/payments/bank-account.js
@@ -1,3 +1,25 @@
+import { clickable, fillable, is, isVisible, hasClass, text } from 'ember-cli-page-object';
+
export default {
- scope: '.bank-account'
+ scope: '.bank-account',
+
+ rendersPending: hasClass('account-setup__section--pending_requirement'),
+ rendersRequired: hasClass('account-setup__section--required'),
+ rendersVerified: hasClass('account-setup__section--verified'),
+
+ rendersAccountNumberField: isVisible('[name=account-number]'),
+ rendersRoutingNumberField: isVisible('[name=routing-number]'),
+ rendersSubmitButton:isVisible('button'),
+
+ accountLast4Text: text('.bank-account__account p'),
+ routingNumberText: text('.bank-account__routing p'),
+
+ accountNumber: fillable('[name=account-number]'),
+ routingNumber: fillable('[name=routing-number]'),
+
+ clickSubmit: clickable('button'),
+
+ accountNumberFieldIsDisabled: is(':disabled', '[name=account-number]'),
+ routingNumberFieldIsDisabled: is(':disabled', '[name=routing-number]'),
+ submitButtonIsDisabled: is(':disabled', 'button')
};