diff --git a/app/components/payments/funds-recipient/details-form.js b/app/components/payments/funds-recipient/details-form.js
index b002ff5c2..97720add5 100644
--- a/app/components/payments/funds-recipient/details-form.js
+++ b/app/components/payments/funds-recipient/details-form.js
@@ -27,7 +27,7 @@ export default Component.extend({
isIndividual: equal('recipientType', 'individual'),
init() {
- let recipientType = get(this, 'account.recipientType') || 'individual';
+ let recipientType = get(this, 'stripeConnnectAccount.recipientType') || 'individual';
set(this, 'recipientType', recipientType);
this._super(...arguments);
},
diff --git a/app/components/payments/funds-recipient/identity-document-file-upload.js b/app/components/payments/funds-recipient/identity-document-file-upload.js
new file mode 100644
index 000000000..f16ace39f
--- /dev/null
+++ b/app/components/payments/funds-recipient/identity-document-file-upload.js
@@ -0,0 +1,106 @@
+import Ember from 'ember';
+import EmberUploader from 'ember-uploader';
+import ENV from 'code-corps-ember/config/environment';
+
+const {
+ computed,
+ get,
+ getProperties,
+ isEmpty
+} = Ember;
+
+const { Uploader } = EmberUploader;
+
+/**
+ * `payments/funds-recipient/identity-document-file-upload` provides a file input
+ * for uploading an identity verification document to stripe.
+ *
+ *
+ * @class identity-document-file-upload
+ * @module Component
+ * @extends EmberUploader.FileField
+ */
+export default EmberUploader.FileField.extend({
+ classNames: ['identity-document-file-upload'],
+
+ /**
+ * Object containing additional properties to be passed
+ * in as part of the form data being uploaded
+ * @type {Object}
+ */
+ additionalUploadData: {
+ // required to authorize the upload request
+ key: ENV.stripe.publishableKey,
+ purpose: 'identity_document'
+ },
+
+ maxFileSize: 1024 * 1024 * 8, // 8mb
+ multiple: false,
+ supportedFileTypes: ['image/jpeg', 'image/jpg', 'image/png'],
+ url: 'https://uploads.stripe.com/v1/files',
+
+ /**
+ * A computed property containing settings for the ajax request
+ * Used to set stripe account id in the request header
+ * @return {Object}
+ */
+ ajaxSettings: computed('stripeConnectAccount', function() {
+ let headers = {
+ 'Stripe-Account': get(this, 'stripeConnectAccount.idFromStripe')
+ };
+
+ return { headers };
+ }),
+
+ /**
+ * Triggers when the file selection for the rendered file input changes
+ * @param {[File]} files An array of files selected by the user.
+ * Since the `multiple` setting is set to false, only 1 file
+ * is in the array.
+ */
+ filesDidChange(files) {
+ if (!isEmpty(files) && this._validate(files[0])) {
+ this._performUpload(files[0]);
+ }
+ },
+
+ _validate({ size, type }) {
+ let { maxFileSize, supportedFileTypes } = getProperties(this, 'maxFileSize', 'supportedFileTypes');
+ let isValid = (size <= maxFileSize) && (supportedFileTypes.indexOf(type) >= 0);
+
+ if (!isValid) {
+ this.sendAction('validationError');
+ }
+
+ return isValid;
+ },
+
+ _performUpload(file) {
+ this.sendAction('uploadStarted');
+
+ let params = getProperties(this, 'url', 'ajaxSettings');
+ let uploader = Uploader.create(params);
+
+ uploader.on('progress', (event) => this._handleUploadProgress(event));
+
+ let additionalUploadData = get(this, 'additionalUploadData');
+
+ uploader.upload(file, additionalUploadData)
+ .then((event) => this._handleUploadDone(event))
+ .catch((reason) => this._handleUploadError(reason));
+ },
+
+ // error handlers
+
+ _handleUploadDone({ id }) {
+ this.sendAction('uploadDone', id);
+ },
+
+ _handleUploadError(reason) {
+ this.sendAction('uploadError', reason);
+ },
+
+ _handleUploadProgress(event) {
+ this.sendAction('uploadProgress', event.percent);
+ }
+});
diff --git a/app/components/payments/funds-recipient/verification-document.js b/app/components/payments/funds-recipient/verification-document.js
new file mode 100644
index 000000000..b8b0d19e8
--- /dev/null
+++ b/app/components/payments/funds-recipient/verification-document.js
@@ -0,0 +1,46 @@
+import Ember from 'ember';
+
+const {
+ Component,
+ computed,
+ get,
+ set
+} = Ember;
+
+const VALIDATION_ERROR = 'The file you selected is invalid. Only .jpg and .png images of up to 8mb in size are supported.';
+const UPLOAD_ERROR = 'There was a problem with uploading your file. Please try again.';
+
+export default Component.extend({
+ classNames: ['verification-document'],
+ status: computed.alias('stripeConnectAccount.verificationDocumentStatus'),
+
+ progressPercentage: 0,
+ progressMessage: computed('progressPercentage', function() {
+ let percentage = get(this, 'progressPercentage');
+ return `Uploading... ${percentage}`;
+ }),
+
+ onUploadStarted() {
+ set(this, 'isUploading', true);
+ set(this, 'error', null);
+ },
+
+ onUploadProgress(percentage) {
+ set(this, 'progressPercentage', percentage);
+ },
+
+ onUploadDone(stripeFileUploadId) {
+ set(this, 'isUploading', false);
+ let onVerificationDocumentSubmitted = get(this, 'onVerificationDocumentSubmitted');
+ onVerificationDocumentSubmitted(stripeFileUploadId);
+ },
+
+ onUploadError() {
+ set(this, 'isUploading', false);
+ set(this, 'error', UPLOAD_ERROR);
+ },
+
+ onValidationError() {
+ set(this, 'error', VALIDATION_ERROR);
+ }
+});
diff --git a/app/controllers/project/settings/donations/payments.js b/app/controllers/project/settings/donations/payments.js
index e076d13e2..02b7af57f 100644
--- a/app/controllers/project/settings/donations/payments.js
+++ b/app/controllers/project/settings/donations/payments.js
@@ -11,8 +11,9 @@ const {
} = Ember;
const ACCOUNT_ADDING_ERROR = 'There was a problem submitting your bank account information.';
-const ACCOUNT_TOKEN_CREATION_ERROR = 'There was a problem with your bank account information. Please check your input and try again.';
+const ACCOUNT_TOKEN_CREATION_ERROR = 'There was a problem in using your bank account information. Please check your input and try again.';
const STRIPE_ACCOUNT_CREATION_ERROR = 'There was a problem with your account information. Please check your input and try again.';
+const VERIFICATION_DOCUMENT_ERROR = 'There was a problem in attaching the verification document to your stripe account';
export default Controller.extend({
currentUser: service(),
@@ -20,63 +21,82 @@ export default Controller.extend({
stripe: service(),
actions: {
- onBankAccountInformationSubmitted({ accountNumber, routingNumber }) {
+ onRecipientDetailsSubmitted(recipientInformation) {
set(this, 'isBusy', true);
let promises = {
- tokenData: this._createAccountToken(accountNumber, routingNumber),
- stripeConnectAccount: get(this, 'stripeConnectAccount')
+ organization: get(this, 'project.organization'),
+ email: get(this, 'currentUser.user.email')
};
RSVP.hash(promises)
- .then(({ tokenData, stripeConnectAccount }) => this._addBankAccount(tokenData, stripeConnectAccount))
- .catch((response) => this._handleError(response))
+ .then(({ organization, email }) => this._createStripeAccount(recipientInformation, organization, email))
+ .catch((reason) => this._handleError(reason))
.finally(() => set(this, 'isBusy', false));
},
- onPersonalIdNumberSubmitted() {
- // TODO: FIX THIS
- return;
- },
-
- onRecipientDetailsSubmitted(recipientInformation) {
+ onBankAccountInformationSubmitted({ accountNumber, routingNumber }) {
set(this, 'isBusy', true);
let promises = {
- organization: get(this, 'project.organization'),
- email: get(this, 'currentUser.user.email')
+ tokenData: this._createAccountToken(accountNumber, routingNumber),
+ stripeConnectAccount: get(this, 'stripeConnectAccount')
};
RSVP.hash(promises)
- .then(({ organization, email }) => this._createStripeAccount(recipientInformation, organization, email))
- .catch((reason) => this._handleError(reason))
+ .then(({ tokenData, stripeConnectAccount }) => this._addBankAccount(tokenData, stripeConnectAccount))
+ .catch((response) => this._handleError(response))
.finally(() => set(this, 'isBusy', false));
},
- onVerificationDocumentSubmitted() {
- // TODO: FIX THIS
- return;
+ onVerificationDocumentSubmitted(stripeFileUploadId) {
+ set(this, 'isBusy', true);
+
+ get(this, 'stripeConnectAccount')
+ .then((account) => this._assignIdentityVerificationDocument(account, stripeFileUploadId))
+ .catch((response) => this._handleError(response))
+ .finally(() => set(this, 'isBusy', false));
+ },
+
+ onPersonalIdNumberSubmitted() {
+ console.log(arguments);
}
},
- _addBankAccount(tokenData, stripeConnectAccount) {
- set(stripeConnectAccount, 'externalAccount', tokenData.id);
+ // creating a stripe account
- return stripeConnectAccount.save()
- .then((stripeConnectAccount) => RSVP.resolve(stripeConnectAccount))
- .catch((reason) => this._handleAddBankAccountError(reason));
+ _createStripeAccount(recipientInformation, organization, email) {
+ let accountParams = merge(recipientInformation, { organization, email });
+
+ return get(this, 'store')
+ .createRecord('stripe-connect-account', accountParams)
+ .save()
+ .then((account) => RSVP.resolve(account))
+ .catch((reason) => this._handleStripeAccountCreationError(reason));
},
- _bankAccountTokenParams(accountNumber, routingNumber) {
- return {
- account_number: accountNumber,
- routing_number: routingNumber,
- object: 'bank_account',
- country: 'US',
- currency: 'USD'
- };
+ _handleStripeAccountCreationError() {
+ let friendlyError = new FriendlyError(STRIPE_ACCOUNT_CREATION_ERROR);
+ return RSVP.reject(friendlyError);
+ },
+
+ // uploading and assigning an id verification document
+
+ _assignIdentityVerificationDocument(account, stripeFileUploadId) {
+ set(account, 'identityDocumentId', stripeFileUploadId);
+
+ return account.save()
+ .then((account) => RSVP.resolve(account))
+ .catch((reason) => this._handleIdentityVerificationDocumentError(reason));
+ },
+
+ _handleIdentityVerificationDocumentError() {
+ let friendlyError = new FriendlyError(VERIFICATION_DOCUMENT_ERROR);
+ return RSVP.reject(friendlyError);
},
+ // bank account - token step
+
_createAccountToken(accountNumber, routingNumber) {
let stripe = get(this, 'stripe');
let params = this._bankAccountTokenParams(accountNumber, routingNumber);
@@ -86,14 +106,29 @@ export default Controller.extend({
.catch((reason) => this._handleBankAccountTokenError(reason));
},
- _createStripeAccount(recipientInformation, organization, email) {
- let accountParams = merge(recipientInformation, { organization, email });
+ _bankAccountTokenParams(accountNumber, routingNumber) {
+ return {
+ account_number: accountNumber,
+ country: 'US',
+ currency: 'USD',
+ object: 'bank_account',
+ routing_number: routingNumber
+ };
+ },
- return get(this, 'store')
- .createRecord('stripe-connect-account', accountParams)
- .save()
- .then((account) => RSVP.resolve(account))
- .catch((reason) => this._handleStripeAccountCreationError(reason));
+ _handleBankAccountTokenError() {
+ let friendlyError = new FriendlyError(ACCOUNT_TOKEN_CREATION_ERROR);
+ return RSVP.reject(friendlyError);
+ },
+
+ // bank account - updating connect account record step
+
+ _addBankAccount(tokenData, stripeConnectAccount) {
+ set(stripeConnectAccount, 'externalAccount', tokenData.id);
+
+ return stripeConnectAccount.save()
+ .then((stripeConnectAccount) => RSVP.resolve(stripeConnectAccount))
+ .catch((reason) => this._handleAddBankAccountError(reason));
},
_handleAddBankAccountError() {
@@ -101,17 +136,9 @@ export default Controller.extend({
return RSVP.reject(friendlyError);
},
- _handleBankAccountTokenError() {
- let friendlyError = new FriendlyError(ACCOUNT_TOKEN_CREATION_ERROR);
- return RSVP.reject(friendlyError);
- },
+ // general catch-all error handler
_handleError(error) {
set(this, 'error', error);
- },
-
- _handleStripeAccountCreationError() {
- let friendlyError = new FriendlyError(STRIPE_ACCOUNT_CREATION_ERROR);
- return RSVP.reject(friendlyError);
}
});
diff --git a/app/models/stripe-connect-account.js b/app/models/stripe-connect-account.js
index b25c9ec5f..1f2023936 100644
--- a/app/models/stripe-connect-account.js
+++ b/app/models/stripe-connect-account.js
@@ -18,6 +18,7 @@ export default Model.extend({
dobYear: attr(),
email: attr(),
firstName: attr(),
+ identityDocumentId: attr(),
idFromStripe: attr(),
insertedAt: attr(),
lastName: attr(),
@@ -26,6 +27,7 @@ export default Model.extend({
ssnLast4: attr(),
state: attr(),
updatedAt: attr(),
+ verificationDocumentStatus: attr(),
verificationFieldsNeeded: attr(),
zip: attr(),
diff --git a/app/templates/components/payments/funds-recipient.hbs b/app/templates/components/payments/funds-recipient.hbs
index 85eab9d73..06c8b915a 100644
--- a/app/templates/components/payments/funds-recipient.hbs
+++ b/app/templates/components/payments/funds-recipient.hbs
@@ -10,9 +10,10 @@
{{/if}}
{{#if (eq status 'verifying')}}
-