Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion app/components/payments/funds-recipient/details-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
});
46 changes: 46 additions & 0 deletions app/components/payments/funds-recipient/verification-document.js
Original file line number Diff line number Diff line change
@@ -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);
}
});
123 changes: 75 additions & 48 deletions app/controllers/project/settings/donations/payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,72 +11,92 @@ 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(),
store: service(),
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);
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would also alpha-order here.


// bank account - token step

_createAccountToken(accountNumber, routingNumber) {
let stripe = get(this, 'stripe');
let params = this._bankAccountTokenParams(accountNumber, routingNumber);
Expand All @@ -86,32 +106,39 @@ 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() {
let friendlyError = new FriendlyError(ACCOUNT_ADDING_ERROR);
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);
}
});
2 changes: 2 additions & 0 deletions app/models/stripe-connect-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default Model.extend({
dobYear: attr(),
email: attr(),
firstName: attr(),
identityDocumentId: attr(),
idFromStripe: attr(),
insertedAt: attr(),
lastName: attr(),
Expand All @@ -26,6 +27,7 @@ export default Model.extend({
ssnLast4: attr(),
state: attr(),
updatedAt: attr(),
verificationDocumentStatus: attr(),
verificationFieldsNeeded: attr(),
zip: attr(),

Expand Down
7 changes: 4 additions & 3 deletions app/templates/components/payments/funds-recipient.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
{{/if}}

{{#if (eq status 'verifying')}}
<section class="verification-document">
TODO: verification-document component goes here
</section>
{{payments/funds-recipient/verification-document
isBusy=isBusy
onVerificationDocumentSubmitted=(action onVerificationDocumentSubmitted)
stripeConnectAccount=stripeConnectAccount}}
<section class="personal-id-number">
TODO: personal-id-number component goes here
</section>
Expand Down
Loading