diff --git a/Brocfile.js b/Brocfile.js index 436c8b025..4090733db 100644 --- a/Brocfile.js +++ b/Brocfile.js @@ -35,7 +35,7 @@ var fonts = pickFiles('bower_components/strapped/static/fonts', { var images = pickFiles('bower_components/strapped/static/images', { srcDir: '/', - files: ['**/*.png', '**/*.gif'], + files: ['**/*.png'], destDir: '/images' }); diff --git a/CHANGELOG.md b/CHANGELOG.md index 493d9dde5..acaa6d5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Balanced Dashboard Changelog - ### master +* Refactoring how model sidebars are displayed +* Turning off marketplace application process temporarily * Fixing restart verification button not showing after the wait period is over ### 1.2.1 diff --git a/Gruntfile.js b/Gruntfile.js index 820b59c0d..12c7041d6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -201,7 +201,7 @@ module.exports = function(grunt) { /* grunt task commands */ - grunt.registerTask('default', ['clean', 'bower', 'copy', 'exec:ember_server']); + grunt.registerTask('default', ['clean', 'copy', 'exec:ember_server']); grunt.registerTask('test', ['bower:install', 'exec:ember_test']); grunt.registerTask('build', ['bower:install', 'exec:ember_build_production']); grunt.registerTask('deploy', ['admin:uninstall', 'build', 's3:productionCached', 's3:productionUncached']); diff --git a/app/controllers/marketplace.js b/app/controllers/marketplace.js index 840eab7de..5d719cbec 100644 --- a/app/controllers/marketplace.js +++ b/app/controllers/marketplace.js @@ -29,18 +29,15 @@ var MarketplaceController = Ember.ObjectController.extend({ return this.get("auth.signedIn") && this.get("model"); }.property("auth.signedIn", "model"), - transactionSelected: isSelected('marketplace.transactions', 'credits', 'debits', 'holds', 'refunds', 'reversals'), - orderSelected: isSelected('marketplace.orders', 'orders'), - customerSelected: isSelected('marketplace.customers', 'customer'), - fundingInstrumentSelected: isSelected('marketplace.funding_instruments', 'bank_accounts', 'cards'), + orderSelected: isSelected('marketplace.orders', 'orders', 'credits', 'debits', 'holds', 'refunds', 'reversals'), + settlementSelected: isSelected('marketplace.settlements', 'settlement'), disputeSelected: isSelected('marketplace.disputes', 'dispute'), + customerSelected: isSelected('marketplace.customers', 'customer'), + fundingInstrumentSelected: isSelected('marketplace.funding_instruments', 'bank_accounts', 'cards', 'account'), logSelected: isSelected('marketplace.logs', 'log'), invoiceSelected: isSelected('marketplace.invoices', 'invoice'), settingSelected: isSelected('marketplace.settings'), - // Note: need this since bind-attr only works for a single property - paymentSelected: Ember.computed.or('transactionSelected', 'orderSelected'), - disputesResultsLoader: function() { if (this.get("model")) { return this.get('model').getDisputesLoader({ diff --git a/app/controllers/marketplace/import-payouts.js b/app/controllers/marketplace/import-payouts.js index d210298bc..2025c26ff 100644 --- a/app/controllers/marketplace/import-payouts.js +++ b/app/controllers/marketplace/import-payouts.js @@ -51,7 +51,7 @@ var MarketplaceImportPayoutsController = Ember.Controller.extend(Ember.Evented, callback(); } var count = collection.filterBy('isSaved').get('length'); - self.transitionToRoute('marketplace.transactions'); + self.transitionToRoute('marketplace.orders'); self.refresh(''); var message = '%@ payouts were successfully submitted. Payouts might take a couple seconds to appear in the transactions list.'.fmt(count); diff --git a/app/controllers/marketplace/settings.js b/app/controllers/marketplace/settings.js index c96137c50..e518d9f65 100644 --- a/app/controllers/marketplace/settings.js +++ b/app/controllers/marketplace/settings.js @@ -10,6 +10,16 @@ var MarketplaceSettingsController = Ember.ObjectController.extend(actionsMixin, ownerCustomer: Ember.computed.oneWay("marketplace.owner_customer"), + accountsResultsLoader: function() { + if (this.get("owner_customer")) { + return this.get("owner_customer").getAccountsLoader({ + limit: 10 + }); + } else { + return this.get("container").lookup("results-loader:base"); + } + }.property("owner_customer"), + fundingInstrumentsResultsLoader: function() { if (this.get("owner_customer")) { return this.get("owner_customer").getFundingInstrumentsLoader({ diff --git a/app/controllers/marketplace/settlements.js b/app/controllers/marketplace/settlements.js new file mode 100644 index 000000000..0e82d77ef --- /dev/null +++ b/app/controllers/marketplace/settlements.js @@ -0,0 +1,16 @@ +import Ember from "ember"; + +var MarketplaceSettlementsController = Ember.ObjectController.extend({ + needs: ['marketplace'], + resultsLoader: Ember.computed.oneWay("model"), + actions: { + changeDateFilter: function(startTime, endTime) { + this.get("resultsLoader").setProperties({ + endTime: endTime, + startTime: startTime + }); + }, + } +}); + +export default MarketplaceSettlementsController; diff --git a/app/initializers/legacy-routes-initializer.coffee b/app/initializers/legacy-routes-initializer.coffee index 8db7d59e2..02195e5af 100644 --- a/app/initializers/legacy-routes-initializer.coffee +++ b/app/initializers/legacy-routes-initializer.coffee @@ -7,18 +7,15 @@ LegacyRoutesInitializer = klass = createRedirectRoute(createArgs...) container.register("route:#{name}", klass) - defineRoute("account", "customer", "account") - defineRoute("accounts", "marketplace.customer") - defineRoute("accounts.index", 'marketplace.customers', "accounts") - defineRoute("bank-account.index", 'activity.funding_instruments') defineRoute("cards.index", "activity.funding_instruments") - defineRoute("marketplace-redirect-activity-transactions", "marketplace.transactions") - defineRoute("marketplace-redirect-activity-orders", "activity.orders") - defineRoute("marketplace-redirect-activity-customers", "marketplace.customers") - defineRoute("marketplace-redirect-activity-funding-instruments", "marketplace.funding-instruments") - defineRoute("marketplace-redirect-activity-disputes", "marketplace.disputes") - defineRoute("marketplace-redirect-invoices", "marketplace.invoices") + defineRoute("marketplace.redirect-activity-transactions", "marketplace.orders") + defineRoute("marketplace.redirect-transactions", "marketplace.orders") + defineRoute("marketplace.redirect-activity-orders", "marketplace.orders") + defineRoute("marketplace.redirect-activity-customers", "marketplace.customers") + defineRoute("marketplace.redirect-activity-funding-instruments", "marketplace.funding-instruments") + defineRoute("marketplace.redirect-activity-disputes", "marketplace.disputes") + defineRoute("marketplace.redirect-invoices", "marketplace.invoices") `export default LegacyRoutesInitializer` diff --git a/app/initializers/result-loaders-initializer.coffee b/app/initializers/result-loaders-initializer.coffee index ab341f2f7..322547c53 100644 --- a/app/initializers/result-loaders-initializer.coffee +++ b/app/initializers/result-loaders-initializer.coffee @@ -12,6 +12,7 @@ LOADER_NAMES = [ "marketplace-search" "orders" "transactions" + "unsettled-transactions" ] ResultLoadersInitializer = diff --git a/app/initializers/type-mappings-initializer.coffee b/app/initializers/type-mappings-initializer.coffee index 150d55671..342ccae2f 100644 --- a/app/initializers/type-mappings-initializer.coffee +++ b/app/initializers/type-mappings-initializer.coffee @@ -9,6 +9,7 @@ TypeMappingsInitializer = TypeMappings.addTypeMapping(key, klass) registerMapping "api_key", "api-key" + registerMapping "account" registerMapping "bank_account", "bank-account" registerMapping "bank-account", "bank-account" registerMapping "bank_account_verification", "verification" diff --git a/app/lib/utils.js b/app/lib/utils.js index bcb651e6b..84ff7b1ba 100644 --- a/app/lib/utils.js +++ b/app/lib/utils.js @@ -239,53 +239,6 @@ var Utils = Ember.Namespace.create({ } }, - applyUriFilters: function(uri, params) { - if (!uri) { - return uri; - } - - var transformedParams = ['limit', 'offset', 'sortField', 'sortOrder', 'minDate', 'maxDate', 'type', 'query']; - - var filteringParams = { - limit: params.limit || 10, - offset: params.offset || 0 - }; - - if (params.sortField && params.sortOrder && params.sortOrder !== 'none') { - filteringParams.sort = params.sortField + ',' + params.sortOrder; - } - - if (params.minDate) { - filteringParams['created_at[>]'] = params.minDate.toISOString(); - } - if (params.maxDate) { - filteringParams['created_at[<]'] = params.maxDate.toISOString(); - } - if (params.type) { - switch (params.type) { - case 'search': - filteringParams['type[in]'] = Constants.SEARCH.SEARCH_TYPES.join(','); - break; - case 'transaction': - filteringParams['type[in]'] = Constants.SEARCH.TRANSACTION_TYPES.join(','); - break; - case 'funding_instrument': - filteringParams['type[in]'] = Constants.SEARCH.FUNDING_INSTRUMENT_TYPES.join(','); - break; - default: - filteringParams.type = params.type; - } - } - filteringParams.q = ''; - if (params.query && params.query !== '%') { - filteringParams.q = params.query; - } - - filteringParams = _.extend(filteringParams, _.omit(params, transformedParams)); - filteringParams = Utils.sortDict(filteringParams); - return this.buildUri(uri, filteringParams); - }, - buildUri: function(path, queryStringObject) { var queryString = _.isString(queryStringObject) ? queryStringObject : diff --git a/app/models/account.coffee b/app/models/account.coffee new file mode 100644 index 000000000..1ad979004 --- /dev/null +++ b/app/models/account.coffee @@ -0,0 +1,24 @@ +`import Ember from "ember";` +`import Model from "./core/model";` +`import Computed from "balanced-dashboard/utils/computed";` +`import Constants from "balanced-dashboard/utils/constants";` + +Account = Model.extend( + routeName: "account" + route_name: "account" + isPayableAccount: Ember.computed.equal("type", "payable") + appears_on_statement_max_length: Constants.MAXLENGTH.APPEARS_ON_STATEMENT_BANK_ACCOUNT, + + type_name: Ember.computed "type", -> + type = @get("type") + + if type + "#{type.capitalize()} account" + else + "account" + + description_with_type: Ember.computed "id", -> + "Payable account: #{@get("id")}" +) + +`export default Account;` diff --git a/app/models/bk/account.coffee b/app/models/bk/account.coffee new file mode 100644 index 000000000..910cc2025 --- /dev/null +++ b/app/models/bk/account.coffee @@ -0,0 +1,20 @@ +`import Ember from "ember";` +`import BkAccount from "balanced-addon-models/models/account";` +`import Computed from "balanced-dashboard/utils/computed";` +`import BkUtils from "balanced-dashboard/utils/bk-utils";` + +Account = BkAccount.extend( + routeName: "account", + route_name: "account", + isPayableAccount: Ember.computed.equal("type", "payable"), + type_name: (-> + type = @get("type").capitalize() + "%@ account".fmt(type) + ).property("type"), + description_with_type: Ember.computed("id", -> + "Payable account: #{@get("id")}" + ), + toLegacyModel: BkUtils.generateToLegacyModelMethod("account") +) + +`export default Account;` diff --git a/app/models/bk/bank-account.coffee b/app/models/bk/bank-account.coffee index 19188a3c6..2cc02dbb6 100644 --- a/app/models/bk/bank-account.coffee +++ b/app/models/bk/bank-account.coffee @@ -1,7 +1,10 @@ `import Ember from "ember";` -`import BankAccount from "balanced-addon-models/models/bank-account";` +`import BkBankAccount from "balanced-addon-models/models/bank-account";` +`import BkUtils from "balanced-dashboard/utils/bk-utils";` -BkBankAccount = BankAccount.extend( +BankAccount = BkBankAccount.extend( + routeName: "bank_accounts" + toLegacyModel: BkUtils.generateToLegacyModelMethod("bank_account") ) -`export default BkBankAccount;` +`export default BankAccount;` diff --git a/app/models/bk/customer.coffee b/app/models/bk/customer.coffee index 355b44f48..0bdffe6d7 100644 --- a/app/models/bk/customer.coffee +++ b/app/models/bk/customer.coffee @@ -2,7 +2,7 @@ `import BkCustomer from "balanced-addon-models/models/customer";` Customer = BkCustomer.extend( - routeName: "customer" + routeName: "customer", ) `export default Customer;` diff --git a/app/models/bk/settlement.coffee b/app/models/bk/settlement.coffee new file mode 100644 index 000000000..233e978fa --- /dev/null +++ b/app/models/bk/settlement.coffee @@ -0,0 +1,13 @@ +`import Ember from "ember";` +`import Utils from "balanced-dashboard/lib/utils";` +`import BkSettlement from "balanced-addon-models/models/settlement";` + +Settlement = BkSettlement.extend( + routeName: "settlement", + type_name: "Settlement", + amountInDollars: (-> + "$%@".fmt(Utils.centsToDollars(@get("amount"))) + ).property("amount") +) + +`export default Settlement;` diff --git a/app/models/card-validatable.coffee b/app/models/card-validatable.coffee deleted file mode 100644 index 136fce6eb..000000000 --- a/app/models/card-validatable.coffee +++ /dev/null @@ -1,18 +0,0 @@ -`import Card from "./card";` -`import FundingInstrumentValidatable from "./mixins/funding-instrument-validatable";` - -CardValidatable = Card.extend(Ember.Validations, FundingInstrumentValidatable, - getTokenizingResponseHref: (response) -> - response.cards[0].href - getTokenizingObject: -> - balanced.card - getTokenizingData: -> - number: @get('number'), - expiration_month: @get('expiration_month'), - expiration_year: @get('expiration_year'), - cvv: @get('cvv'), - name: @get('name'), - address: @get('address') || {} -) - -`export default CardValidatable;` diff --git a/app/models/card.js b/app/models/card.js index f4b9aad97..fb1cac200 100644 --- a/app/models/card.js +++ b/app/models/card.js @@ -23,6 +23,21 @@ var Card = FundingInstrument.extend(Ember.Validations, { expiration_year: { presence: true }, + expiration_date: { + presence: true, + format: Constants.EXPIRATION_DATE_FORMAT, + expired: { + validator: function(object, attrName, value) { + var date = object.getExpirationDate(); + if (Ember.isBlank(date)) { + object.get("validationErrors").add(attrName, "expired", null, "" + value + " is not a valid card expiration date"); + } + else if (date < new Date()) { + object.get("validationErrors").add(attrName, "expired", null, "is expired"); + } + } + } + }, cvv: { presence: true, numericality: true, @@ -85,24 +100,48 @@ var Card = FundingInstrument.extend(Ember.Validations, { ); }.property('name', 'last_four', 'brand'), - human_readable_expiration: Computed.fmt('expiration_month', 'expiration_year', '%@/%@'), + human_readable_expiration: Ember.computed.reads('expiration_date'), - tokenizeAndCreate: function(customerId) { - var self = this; - var promise = this.resolveOn('didCreate'); - - function errorCreatingCard(err) { - Ember.run.next(function() { - self.setProperties({ - displayErrorDescription: true, - isSaving: false, - errorDescription: 'There was an error processing your card. ' + (Ember.get(err, 'errorDescription') || ''), - validationErrors: Ember.get(err, 'validationErrors') || {} + expiration_date: function(attrName, value) { + if (arguments.length) { + var match = this.getExpirationDateMatch(value); + if (match) { + this.setProperties({ + expiration_month: match[1], + expiration_year: match[2] }); - }); + } + } + return "%@ / %@".fmt(this.get("expiration_month"), this.get("expiration_year")); + }.property("expiration_month", "expiration_year"), + - promise.reject(); + getExpirationDate: function() { + var match = this.getExpirationDateMatch(this.get("expiration_date")); + if (match) { + var month = parseInt(match[0]); + if (0 < month && month <= 12) { + return moment(match[0], "MM / YYYY").endOf("month").toDate(); + } } + }, + + getExpirationDateMatch: function(expirationDate) { + if (!Ember.isBlank(expirationDate)) { + return expirationDate.match(Constants.EXPIRATION_DATE_FORMAT); + } + }, + + tokenizeAndCreate: function(customerId) { + var self = this; + + var deferred = Ember.RSVP.defer(); + + var getErrorMessage = function(error) { + return Ember.isBlank(error.additional) ? + error.description : + error.additional; + }; this.set('isSaving', true); var cardData = { @@ -118,46 +157,51 @@ var Card = FundingInstrument.extend(Ember.Validations, { cardData.customer = customerId; } - // Tokenize the card using the balanced.js library balanced.card.create(cardData, function(response) { if (response.errors) { - var validationErrors = Utils.extractValidationErrorHash(response); - self.setProperties({ - validationErrors: validationErrors, - isSaving: false + response.errors.forEach(function(error) { + if (Ember.isBlank(error.extras)) { + self.get("validationErrors").add(undefined, "server", null, getErrorMessage(error)); + } + else { + _.each(error.extras, function(value, key) { + self.get("validationErrors").add(key, "server", null, value); + }); + } }); - - if (!validationErrors) { - self.set('displayErrorDescription', true); - var errorSuffix = (response.errors && response.errors.length > 0 && response.errors[0].description) ? (': ' + response.errors[0].description) : '.'; - self.setProperties({ - displayErrorDescription: true, - errorDescription: 'Sorry, there was an error tokenizing this card' + errorSuffix - }); - } - - promise.reject(validationErrors); + self.set("isSaving", false); + deferred.reject(response); } else { - Card.find(response.cards[0].href) - - // Now that it's been tokenized, we just need to associate it with the customer's account - .then(function(card) { - card.set('links.customer', customerId); - - card.save().then(function() { + Card.findCreatedCard(response.cards[0].href) + .then(function(card) { + card.set('links.customer', customerId); + return card.save(); + }) + .then(function(card) { self.setProperties({ isSaving: false, isNew: false, isLoaded: true }); - - self.trigger('didCreate', card); - }, errorCreatingCard); - }, errorCreatingCard); + deferred.resolve(card); + }, function (response) { + response.errors.forEach(function(error) { + if (Ember.isBlank(error.extras)) { + self.get("validationErrors").add(undefined, "server", null, getErrorMessage(error)); + } + else { + _.each(error.extras, function(value, key) { + self.get("validationErrors").add(key, "server", null, value); + }); + } + }); + self.set("isSaving", false); + deferred.reject(response); + }); } }); - return promise; + return deferred.promise; } }); diff --git a/app/models/core/search-model-array.js b/app/models/core/search-model-array.js index faef7b838..0215940a4 100644 --- a/app/models/core/search-model-array.js +++ b/app/models/core/search-model-array.js @@ -19,7 +19,8 @@ var SearchModelArray = ModelArray.extend(Ember.SortableMixin, { total_reversals: readOnly("reversal"), total_transactions: Computed.sumAll('total_credits', 'total_debits', 'total_card_holds', 'total_refunds', "total_reversals"), total_funding_instruments: Computed.sumAll('total_bank_accounts', 'total_cards'), - total_results: Computed.sumAll('total_orders', 'total_transactions', 'total_funding_instruments', 'total_customers') + total_settlements: readOnly('settlement'), + total_results: Computed.sumAll('total_orders', 'total_transactions', 'total_funding_instruments', 'total_customers', 'total_settlements') }); export default SearchModelArray; diff --git a/app/models/credit.js b/app/models/credit.js index 5a0c2b3a1..e0293fb0b 100644 --- a/app/models/credit.js +++ b/app/models/credit.js @@ -1,7 +1,7 @@ import Ember from "ember"; import Computed from "balanced-dashboard/utils/computed"; import Transaction from "./transaction"; -import Rev1Serializer from "../serializers/rev1"; +import TransactionSerializer from "../serializers/transaction"; import Model from "./core/model"; import Utils from "balanced-dashboard/lib/utils"; @@ -14,6 +14,7 @@ var Credit = Transaction.extend({ destination: Model.belongsTo('destination', 'funding-instrument'), reversals: Model.hasMany('reversals', 'reversal'), order: Model.belongsTo('order', 'order'), + settlement: Model.belongsTo('settlement', 'settlement'), funding_instrument_description: Ember.computed.alias('destination.description'), last_four: Ember.computed.alias('destination.last_four'), @@ -70,7 +71,7 @@ var Credit = Transaction.extend({ }); Credit.reopenClass({ - serializer: Rev1Serializer.extend({ + serializer: TransactionSerializer.extend({ serialize: function(record) { var json = this._super(record); @@ -83,11 +84,6 @@ Credit.reopenClass({ } } - if (!Ember.isBlank(json.order_uri)) { - json.order = json.order_uri; - delete json.order_uri; - } - return json; } }).create(), diff --git a/app/models/customer.js b/app/models/customer.js index 2a347ed63..6dbec8fd5 100644 --- a/app/models/customer.js +++ b/app/models/customer.js @@ -1,8 +1,12 @@ import { CountryCodesToNames } from "balanced-dashboard/lib/country-codes"; import Model from "./core/model"; +import Order from "./order"; import Computed from "balanced-dashboard/utils/computed"; import FundingInstrumentsResultsLoader from "./results-loaders/funding-instruments"; import TransactionsResultsLoader from "./results-loaders/transactions"; +import BuyerTransactionsResultsLoader from "./results-loaders/buyer-transactions"; +import MerchantTransactionsResultsLoader from "./results-loaders/merchant-transactions"; +import OrdersResultsLoader from "./results-loaders/orders"; var CUSTOMER_TYPES = { BUSINESS: 'Business', @@ -19,6 +23,8 @@ var Customer = Model.extend({ refunds: Model.hasMany('refunds', 'refund'), orders: Model.hasMany('orders', 'order'), disputes: Model.hasMany('disputes', 'dispute'), + accounts: Model.hasMany('accounts', 'account'), + account: Ember.computed.reads("accounts.firstObject"), uri: '/customers', route_name: 'customer', @@ -26,18 +32,6 @@ var Customer = Model.extend({ has_bank_account: Ember.computed.and('bank_accounts.isLoaded', 'bank_accounts.length'), - orders_list: function() { - var customer_uri = this.get('href'); - var orders = this.get('orders') || Ember.A(); - - if (customer_uri) { - orders = orders.filter(function(order) { - return order.get('merchant_uri') === customer_uri; - }); - } - return orders; - }.property('orders', 'orders.@each.merchant_uri'), - debitable_bank_accounts: function() { return this.get('bank_accounts').filterBy('can_debit'); }.property('bank_accounts.@each.can_debit'), @@ -52,7 +46,7 @@ var Customer = Model.extend({ funding_instruments: Ember.computed.union('bank_accounts', 'cards'), debitable_funding_instruments: Ember.computed.union('debitable_bank_accounts', 'cards'), - creditable_funding_instruments: Ember.computed.union('bank_accounts', 'creditable_cards'), + creditable_funding_instruments: Ember.computed.union('bank_accounts', 'creditable_cards', 'accounts'), getFundingInstrumentsLoader: function(attributes) { attributes = _.extend({ @@ -67,12 +61,54 @@ var Customer = Model.extend({ }, attributes); return DisputesResultsLoader.create(attributes); }, + + hasCreditableOrders: Ember.computed.reads("creditableOrders.length"), + + creditableOrders: function () { + return this.getOrdersLoader().get("results"); + }.property("uri"), + + getOrdersLoader: function(attributes) { + // Note: Replace this back to "order_uri" when the issue balanced-api #739 is fixed + attributes = _.extend({ + path: this.get("uri") + "/search", + typeFilters: "order" + }, attributes); + return OrdersResultsLoader.create(attributes); + }, + getBuyerTransactionsLoader: function(attributes) { + attributes = _.extend({ + path: this.get("transactions_uri"), + }, attributes); + return BuyerTransactionsResultsLoader.create(attributes); + }, + getMerchantTransactionsLoader: function(attributes) { + attributes = _.extend({ + path: this.get("transactions_uri"), + }, attributes); + return MerchantTransactionsResultsLoader.create(attributes); + }, getTransactionsLoader: function(attributes) { attributes = _.extend({ path: this.get("transactions_uri"), }, attributes); return TransactionsResultsLoader.create(attributes); }, + getAccountsLoader: function(attributes) { + var AccountsResultsLoader = require("balanced-dashboard/models/results-loaders/accounts")["default"]; + attributes = _.extend({ + path: this.get("accounts_uri"), + }, attributes); + return AccountsResultsLoader.create(attributes); + }, + + createOrder: function(description) { + var order = Order.create({ + description: description + }); + order.set("uri", this.get("orders_uri")); + return order.save(); + }, type: function() { return (this.get('ein') || this.get('business_name')) ? CUSTOMER_TYPES.BUSINESS : CUSTOMER_TYPES.PERSON; diff --git a/app/models/debit.js b/app/models/debit.js index 5b267940f..6a71abd5d 100644 --- a/app/models/debit.js +++ b/app/models/debit.js @@ -2,6 +2,7 @@ import Computed from "balanced-dashboard/utils/computed"; import Transaction from "./transaction"; import Model from "./core/model"; import Utils from "balanced-dashboard/lib/utils"; +import TransactionSerializer from "../serializers/transaction"; var Debit = Transaction.extend({ @@ -59,4 +60,8 @@ var Debit = Transaction.extend({ }.property('amount', 'refund_amount', 'is_succeeded', 'dispute') }); +Debit.reopenClass({ + serializer: TransactionSerializer.create() +}); + export default Debit; diff --git a/app/models/factories/credit-bank-account-transaction-factory.js b/app/models/factories/credit-bank-account-transaction-factory.js index d542b68f0..2ce3a47c0 100644 --- a/app/models/factories/credit-bank-account-transaction-factory.js +++ b/app/models/factories/credit-bank-account-transaction-factory.js @@ -1,25 +1,21 @@ import Ember from "ember"; import ValidationHelpers from "balanced-dashboard/utils/validation-helpers"; -import TransactionFactory from "./transaction-factory"; +import CreditOrderFactory from "./credit-order-factory"; +import BankAccount from "../bank-account"; /* * This factory uses the api feature of creating a Credit without creating a * BankAccount object. */ -var CreditBankAccountTransactionFactory = TransactionFactory.extend({ +var CreditBankAccountTransactionFactory = CreditOrderFactory.extend({ getDestinationAttributes: function() { return this.getProperties("account_number", "name", "routing_number", "account_type"); }, - getAttributes: function() { - var attributes = this.getProperties("amount", "appears_on_statement_as", "description"); - attributes.destination = this.getDestinationAttributes(); - return attributes; - }, - - save: function() { - var Credit = BalancedApp.__container__.lookupFactory("model:credit"); - return Credit.create(this.getAttributes()).save(); + getDestination: function(seller) { + return BankAccount + .create(this.getDestinationAttributes()) + .tokenizeAndCreate(seller.get("uri")); }, validations: { @@ -29,7 +25,7 @@ var CreditBankAccountTransactionFactory = TransactionFactory.extend({ name: ValidationHelpers.bankAccountName, routing_number: ValidationHelpers.bankAccountRoutingNumber, account_number: ValidationHelpers.bankAccountNumber, - account_type: ValidationHelpers.bankAccountType, + account_type: ValidationHelpers.bankAccountType } }); diff --git a/app/models/factories/credit-existing-funding-instrument-transaction-factory.js b/app/models/factories/credit-existing-funding-instrument-transaction-factory.js index cf1544be0..f57996b74 100644 --- a/app/models/factories/credit-existing-funding-instrument-transaction-factory.js +++ b/app/models/factories/credit-existing-funding-instrument-transaction-factory.js @@ -1,38 +1,55 @@ import Ember from "ember"; import ValidationHelpers from "balanced-dashboard/utils/validation-helpers"; -import TransactionFactory from "./transaction-factory"; +import Utils from "balanced-dashboard/lib/utils"; +import CreditOrderFactory from "./credit-order-factory"; -var CreditExistingFundingInstrumentTransactionFactory = TransactionFactory.extend({ +var CreditExistingFundingInstrumentTransactionFactory = CreditOrderFactory.extend({ appears_on_statement_max_length: Ember.computed.oneWay("destination.appears_on_statement_max_length"), - destination_uri: Ember.computed.readOnly("destination.uri"), - getCreditAttributes: function() { - var properties = this.getProperties("amount", "appears_on_statement_as", "description", "destination_uri"); - properties.uri = this.get("destination.credits_uri"); - - if (this.get("order.href")) { - properties.order_uri = this.get("order.href"); - } - return properties; + getDestination: function() { + return Ember.RSVP.resolve(this.get("destination")); }, - save: function() { - var Credit = BalancedApp.__container__.lookupFactory("model:credit"); - - this.validate(); - if (this.get("isValid")) { - return Credit.create(this.getCreditAttributes()).save(); - } else { - return Ember.RSVP.reject(); + isAmountOverMaximum: function() { + if (this.get("order")) { + return this.get("amount") > this.get("order.amount_escrowed"); } + return false; }, validations: { - destination_uri: { - presence: true + dollar_amount: { + format: { + validator: function(object, attribute, value) { + var message = function(message) { + object.get("validationErrors").add(attribute, "format", null, message); + }; + + value = (value || "").toString().trim(); + if (Ember.isBlank(value)) { + message("is required"); + } else if (object.isAmountOverMaximum()) { + var maxAmount = object.get("order.amount_escrowed"); + message("cannot be more than %@".fmt(Utils.formatCurrency(maxAmount))); + } else if (!object.isAmountPositive()) { + message("must be a positive number"); + } else { + try { + var v = Utils.dollarsToCents(value); + if (isNaN(v) || v <= 0) { + message("must be a positive number"); + } + } catch (e) { + message(e.message.replace("Error: ", "")); + } + } + } + } }, - dollar_amount: ValidationHelpers.positiveDollarAmount, - appears_on_statement_as: ValidationHelpers.bankTransactionAppearsOnStatementAs + appears_on_statement_as: ValidationHelpers.bankTransactionAppearsOnStatementAs, + destination: { + presence: true + } } }); diff --git a/app/models/factories/credit-order-factory.js b/app/models/factories/credit-order-factory.js new file mode 100644 index 000000000..e92236873 --- /dev/null +++ b/app/models/factories/credit-order-factory.js @@ -0,0 +1,101 @@ +import TransactionFactory from "./transaction-factory"; +import Credit from "../credit"; +import Customer from "../customer"; + +var CreditOrderFactory = TransactionFactory.extend({ + save: function() { + var self = this; + var order = this.get("order"); + + var deferred = Ember.RSVP.defer(); + this.validate(); + + var getErrorMessage = function(error) { + return Ember.isBlank(error.additional) ? + error.description : + error.additional; + }; + + if (this.get("isValid")) { + self.getSeller() + .then(function(seller) { + return self.getDestination(seller); + }) + .then(function(destination) { + if (order) { + return self.createCredit(destination, order); + } else { + return self.createOneOffCredit(destination); + } + }) + .then(function(credit) { + deferred.resolve(credit); + }) + .catch(function(response) { + response.errors.forEach(function(error) { + if (error.extras) { + _.each(error.extras, function(value, key) { + self.get("validationErrors").add(key, "server", null, value); + }); + } + self.get("validationErrors").add(undefined, "server", null, getErrorMessage(error)); + }); + deferred.reject(self); + }); + } else { + deferred.reject(); + } + + return deferred.promise; + }, + + getDestination: function(/* seller */) { + Ember.assert("Implement #getDestination and make it return a promise with the destination", false); + }, + + getSeller: function() { + var seller = this.get("order.seller"); + + if (seller) { + return Ember.RSVP.resolve(seller); + } else { + return Customer.create({ + name: this.get("name") + }).save(); + } + }, + + getCreditAttributes: function() { + var properties = this.getProperties("amount", "appears_on_statement_as"); + properties.description = this.get("credit_description"); + + return properties; + }, + + createOneOffCredit: function(destination) { + var destinationUri = destination.get("uri"); + var creditsUri = destination.get("credits_uri"); + + var creditAttributes = _.extend({}, this.getCreditAttributes(), { + uri: creditsUri, + destination_uri: destinationUri, + }); + + return Credit.create(creditAttributes).save(); + }, + + createCredit: function(destination, order) { + var destinationUri = destination.get("uri"); + var creditsUri = destination.get("credits_uri"); + + var creditAttributes = _.extend({}, this.getCreditAttributes(), { + uri: creditsUri, + destination_uri: destinationUri, + order_uri: order.get("uri") + }); + + return Credit.create(creditAttributes).save(); + } +}); + +export default CreditOrderFactory; diff --git a/app/models/factories/debit-card-transaction-factory.js b/app/models/factories/debit-card-transaction-factory.js index f2601772a..a6809e3df 100644 --- a/app/models/factories/debit-card-transaction-factory.js +++ b/app/models/factories/debit-card-transaction-factory.js @@ -1,12 +1,11 @@ -import Debit from "../debit"; -import Card from "../card"; -import TransactionFactory from "./transaction-factory"; +import DebitOrderFactory from "./debit-order-factory"; import ValidationHelpers from "balanced-dashboard/utils/validation-helpers"; +import Customer from "../customer"; +import Card from "../card"; +import Constants from "balanced-dashboard/utils/constants"; -var EXPIRATION_DATE_FORMAT = /^(\d\d) [\/-] (\d\d\d\d)$/; - -var DebitCardTransactionFactory = TransactionFactory.extend({ - getDestinationAttributes: function() { +var DebitCardTransactionFactory = DebitOrderFactory.extend({ + getSourceAttributes: function() { var attributes = this.getProperties("name", "number", "cvv", "expiration_month", "expiration_year"); attributes.address = { postal_code: this.get("postal_code") @@ -14,10 +13,6 @@ var DebitCardTransactionFactory = TransactionFactory.extend({ return attributes; }, - getDebitAttributes: function() { - return this.getProperties("amount", "appears_on_statement_as", "description"); - }, - validations: { dollar_amount: ValidationHelpers.positiveDollarAmount, appears_on_statement_as: ValidationHelpers.cardTransactionAppearsOnStatementAs, @@ -27,11 +22,14 @@ var DebitCardTransactionFactory = TransactionFactory.extend({ cvv: ValidationHelpers.cardCvv, expiration_date: { presence: true, - format: EXPIRATION_DATE_FORMAT, + format: Constants.EXPIRATION_DATE_FORMAT, expired: { validator: function(object, attrName, value) { var date = object.getExpirationDate(); - if (date < new Date()) { + if (Ember.isBlank(date)) { + object.get("validationErrors").add(attrName, "expired", null, "" + value + " is not a valid card expiration date"); + } + else if (date < new Date()) { object.get("validationErrors").add(attrName, "expired", null, "is expired"); } } @@ -39,17 +37,47 @@ var DebitCardTransactionFactory = TransactionFactory.extend({ } }, + getSource: function(buyer) { + return Card + .create(this.getSourceAttributes()) + .tokenizeAndCreate(buyer.get("uri")); + }, + + getBuyerCustomerAttributes: function() { + var email = this.get("buyer_email_address"); + if (Ember.isBlank(email)) { + email = undefined; + } + return { + name: this.get("buyer_name"), + email: email + }; + }, + + getBuyer: function() { + var customer = this.get("customer"); + + if (customer) { + return customer; + } else { + return Customer.create(this.getBuyerCustomerAttributes()).save(); + } + }, + getExpirationDate: function() { var match = this.getExpirationDateMatch(); if (match) { - return moment(match[0], "MM / YYYY").endOf("month").toDate(); + var month = parseInt(match[0]); + if (0 < month && month <= 12) { + return moment(match[0], "MM / YYYY").endOf("month").toDate(); + } } }, getExpirationDateMatch: function() { var expirationDate = this.get("expiration_date"); if (!Ember.isBlank(expirationDate)) { - return expirationDate.match(EXPIRATION_DATE_FORMAT); + return expirationDate.match(Constants.EXPIRATION_DATE_FORMAT); } }, @@ -65,72 +93,7 @@ var DebitCardTransactionFactory = TransactionFactory.extend({ if (match) { return match[2]; } - }.property("expiration_date"), - - saveCard: function() { - var attributes = this.getDestinationAttributes(); - var deferred = Ember.RSVP.defer(); - - function resolve(r) { - deferred.resolve(r); - } - function reject(r) { - deferred.reject(r); - } - window.balanced.card.create(attributes, function(response) { - if (response.status_code === 201) { - Card.findCreatedCard(response.cards[0].href).then(resolve, reject); - } - else { - reject(response); - } - }); - return deferred.promise; - }, - - save: function() { - var deferred = Ember.RSVP.defer(); - - var baseDebitAttributes = this.getDebitAttributes(); - var self = this; - this.validate(); - - var getErrorMessage = function(error) { - return Ember.isBlank(error.additional) ? - error.description : - error.additional; - }; - - if (this.get("isValid")) { - this.saveCard() - .then(function(card) { - var debitAttributes = _.extend({}, baseDebitAttributes, { - uri: card.get('debits_uri'), - source_uri: card.get('uri') - }); - return Debit.create(debitAttributes).save(); - }) - .then(function(model) { - deferred.resolve(model); - }, function(response) { - response.errors.forEach(function(error) { - if (Ember.isBlank(error.extras)) { - self.get("validationErrors").add(undefined, "server", null, getErrorMessage(error)); - } - else { - _.each(error.extras, function(value, key) { - self.get("validationErrors").add(key, "server", null, value); - }); - } - }); - deferred.reject(self); - }); - } else { - deferred.reject(); - } - - return deferred.promise; - } + }.property("expiration_date") }); export default DebitCardTransactionFactory; diff --git a/app/models/factories/debit-existing-bank-account-transaction-factory.js b/app/models/factories/debit-existing-bank-account-transaction-factory.js deleted file mode 100644 index 18c6d515e..000000000 --- a/app/models/factories/debit-existing-bank-account-transaction-factory.js +++ /dev/null @@ -1,15 +0,0 @@ -import DebitExistingFundingInstrumentTransactionFactory from "./debit-existing-funding-instrument-transaction-factory"; -import ValidationHelpers from "balanced-dashboard/utils/validation-helpers"; - -var DebitExistingBankAccountTransactionFactory = DebitExistingFundingInstrumentTransactionFactory.extend({ - validations: { - dollar_amount: ValidationHelpers.positiveDollarAmount, - appears_on_statement_as: ValidationHelpers.bankTransactionAppearsOnStatementAs, - - source_uri: { - presence: true - } - } -}); - -export default DebitExistingBankAccountTransactionFactory; diff --git a/app/models/factories/debit-existing-card-transaction-factory.js b/app/models/factories/debit-existing-card-transaction-factory.js deleted file mode 100644 index 856433b05..000000000 --- a/app/models/factories/debit-existing-card-transaction-factory.js +++ /dev/null @@ -1,17 +0,0 @@ -import DebitExistingFundingInstrumentTransactionFactory from "./debit-existing-funding-instrument-transaction-factory"; -import ValidationHelpers from "balanced-dashboard/utils/validation-helpers"; -import Card from "../card"; -import Debit from "../debit"; - -var DebitExistingCardTransactionFactory = DebitExistingFundingInstrumentTransactionFactory.extend({ - validations: { - dollar_amount: ValidationHelpers.positiveDollarAmount, - appears_on_statement_as: ValidationHelpers.cardTransactionAppearsOnStatementAs, - - source_uri: { - presence: true - } - } -}); - -export default DebitExistingCardTransactionFactory; diff --git a/app/models/factories/debit-existing-funding-instrument-transaction-factory.js b/app/models/factories/debit-existing-funding-instrument-transaction-factory.js index 49c236b04..a5fb39cdd 100644 --- a/app/models/factories/debit-existing-funding-instrument-transaction-factory.js +++ b/app/models/factories/debit-existing-funding-instrument-transaction-factory.js @@ -1,38 +1,32 @@ -import TransactionFactory from "./transaction-factory"; +import DebitOrderFactory from "./debit-order-factory"; import ValidationHelpers from "balanced-dashboard/utils/validation-helpers"; -var DebitExistingFundingInstrumentTransactionFactory = TransactionFactory.extend({ +var DebitExistingFundingInstrumentTransactionFactory = DebitOrderFactory.extend({ appears_on_statement_max_length: Ember.computed.oneWay("source.appears_on_statement_max_length"), - source_uri: Ember.computed.readOnly("source.uri"), - getDebitAttributes: function() { - var properties = this.getProperties("amount", "appears_on_statement_as", "description", "source_uri"); - properties.uri = this.get("source.debits_uri"); - return properties; - }, - validations: { - dollar_amount: ValidationHelpers.positiveDollarAmount, - appears_on_statement_as: ValidationHelpers.cardTransactionAppearsOnStatementAs, + getBuyer: function() { + return Ember.RSVP.resolve(this.get("source.customer")); }, - save: function() { - var Debit = BalancedApp.__container__.lookupFactory("model:debit"); - var deferred = Ember.RSVP.defer(); + getSource: function() { + return Ember.RSVP.resolve(this.get("source")); + }, - this.validate(); - if (this.get("isValid")) { - Debit.create(this.getDebitAttributes()) - .save() - .then(function(model) { - deferred.resolve(model); - }, function() { - deferred.reject(); - }); - } else { - deferred.reject(); + validations: { + dollar_amount: ValidationHelpers.positiveDollarAmount, + appears_on_statement_as: { + presence: true, + format: { + validator: function(object, attribute) { + var validationErrors = object.get("validationErrors"); + var maxLength = object.get("appears_on_statement_max_length"); + ValidationHelpers.fundingInstrumentAppearsOnStatementAsValidator("appears_on_statement_as", object, maxLength); + } + } + }, + source: { + presence: true } - - return deferred.promise; } }); diff --git a/app/models/factories/debit-order-factory.js b/app/models/factories/debit-order-factory.js new file mode 100644 index 000000000..c2c1f1a69 --- /dev/null +++ b/app/models/factories/debit-order-factory.js @@ -0,0 +1,110 @@ +import TransactionFactory from "./transaction-factory"; +import Debit from "../debit"; +import Customer from "../customer"; + +var DebitOrderFactory = TransactionFactory.extend({ + save: function() { + var self = this; + var order; + var deferred = Ember.RSVP.defer(); + this.validate(); + + var getErrorMessage = function(error) { + return Ember.isBlank(error.additional) ? + error.description : + error.additional; + }; + + if (this.get("isValid")) { + this.getOrder() + .then(function(o) { + order = o; + return self.getBuyer(); + }) + .then(function(buyer) { + return self.getSource(buyer); + }) + .then(function(source) { + return self.createDebit(source, order); + }) + .then(function(debit) { + deferred.resolve(debit); + }) + .catch(function(response) { + response.errors.forEach(function(error) { + if (error.extras) { + _.each(error.extras, function(value, key) { + self.get("validationErrors").add(key, "server", null, value); + }); + } + self.get("validationErrors").add(undefined, "server", null, getErrorMessage(error)); + }); + deferred.reject(self); + }); + } else { + deferred.reject(); + } + + return deferred.promise; + }, + + getSource: function(/* buyer */) { + Ember.assert("Implement #getSource and make it return a promise with the source", false); + }, + + getBuyer: function() { + Ember.assert("Implement #getbuyer and make it return a promise with the buyer", false); + }, + + getDebitAttributes: function() { + var properties = this.getProperties("amount", "appears_on_statement_as"); + properties.description = this.get("debit_description"); + + return properties; + }, + + createDebit: function(source, order) { + var sourceUri = source.get("uri"); + var debitsUri = source.get("debits_uri"); + var buyerUri = source.get("customer_uri"); + + var debitAttributes = _.extend({}, this.getDebitAttributes(), { + customer_uri: buyerUri, + uri: debitsUri, + source_uri: sourceUri, + order_uri: order.get("uri") + }); + + return Debit.create(debitAttributes).save(); + }, + + getSellerCustomerAttributes: function() { + var email = this.get("seller_email_address"); + if (Ember.isBlank(email)) { + email = undefined; + } + return { + name: this.get("seller_name"), + email: email + }; + }, + + getOrder: function() { + var order = this.get("order"); + + if (order) { + return Ember.RSVP.resolve(order); + } else { + var orderDescription = this.get("order_description"); + var seller = Customer.create(this.getSellerCustomerAttributes()); + + return seller.save() + .then(function(seller) { + var description = orderDescription; + return seller.createOrder(description); + }); + } + }, +}); + +export default DebitOrderFactory; diff --git a/app/models/factories/hold-existing-funding-instrument-transaction-factory.js b/app/models/factories/hold-existing-funding-instrument-transaction-factory.js index aa9b5ad08..5a1b09f8d 100644 --- a/app/models/factories/hold-existing-funding-instrument-transaction-factory.js +++ b/app/models/factories/hold-existing-funding-instrument-transaction-factory.js @@ -1,38 +1,120 @@ import TransactionFactory from "./transaction-factory"; import ValidationHelpers from "balanced-dashboard/utils/validation-helpers"; +import Hold from "../hold"; +import Customer from "../customer"; var HoldExistingFundingInstrumentTransactionFactory = TransactionFactory.extend({ - source_uri: Ember.computed.readOnly("source.uri"), - getHoldAttributes: function() { - var properties = this.getProperties("amount", "description", "source_uri"); - properties.uri = this.get("source.card_holds_uri"); - return properties; - }, - validations: { dollar_amount: ValidationHelpers.positiveDollarAmount }, save: function() { - var Hold = BalancedApp.__container__.lookupFactory("model:hold"); + var self = this; + var order; var deferred = Ember.RSVP.defer(); - - var baseHoldAttributes = this.getHoldAttributes(); this.validate(); + + var getErrorMessage = function(error) { + return Ember.isBlank(error.additional) ? + error.description : + error.additional; + }; + if (this.get("isValid")) { - Hold.create(this.getHoldAttributes()) - .save() - .then(function(model) { - deferred.resolve(model); - }, function() { - deferred.reject(); + this.createOrderWithSeller() + .then(function(o) { + order = o; + var source = self.get("source"); + return self.attachCustomerToSource(source); + }) + .then(function(source) { + return self.createHold(source, order); + }) + .then(function(debit) { + deferred.resolve(debit); + }) + .catch(function(response) { + response.errors.forEach(function(error) { + if (error.extras) { + _.each(error.extras, function(value, key) { + self.get("validationErrors").add(key, "server", null, value); + }); + } + self.get("validationErrors").add(undefined, "server", null, getErrorMessage(error)); + }); + deferred.reject(self); }); } else { deferred.reject(); } return deferred.promise; - } + }, + + getBuyerCustomerAttributes: function() { + var email = this.get("buyer_email_address"); + if (Ember.isBlank(email)) { + email = undefined; + } + return { + name: this.get("buyer_name"), + email: email + }; + }, + + attachCustomerToSource: function(source) { + var customer = this.get("customer"); + + if (customer) { + return Ember.RSVP.resolve(source); + } else { + customer = Customer.create(this.getBuyerCustomerAttributes()).save(); + source.set('links.customer', customer.get("id")); + return source.save(); + } + }, + + getHoldAttributes: function() { + var properties = this.getProperties("amount", "description"); + return properties; + }, + + createHold: function(source, order) { + var sourceUri = source.get("uri"); + var holdsUri = source.get("card_holds_uri"); + var buyerUri = source.get("customer_uri"); + + var holdAttributes = _.extend({}, this.getHoldAttributes(), { + customer_uri: buyerUri, + uri: holdsUri, + source_uri: sourceUri, + order_uri: order.get("uri") + }); + + return Hold.create(holdAttributes).save(); + }, + + getSellerCustomerAttributes: function() { + var email = this.get("seller_email_address"); + if (Ember.isBlank(email)) { + email = undefined; + } + return { + name: this.get("seller_name"), + email: email + }; + }, + + createOrderWithSeller: function() { + var orderDescription = this.get("order_description"); + var seller = Customer.create(this.getSellerCustomerAttributes()); + + return seller.save() + .then(function(seller) { + var description = orderDescription; + return seller.createOrder(description); + }); + }, }); export default HoldExistingFundingInstrumentTransactionFactory; diff --git a/app/models/hold.js b/app/models/hold.js index 4d9bbba59..aa72f4357 100644 --- a/app/models/hold.js +++ b/app/models/hold.js @@ -2,11 +2,13 @@ import Ember from "ember"; import Model from "./core/model"; import Computed from "balanced-dashboard/utils/computed"; import Transaction from "./transaction"; +import TransactionSerializer from "../serializers/transaction"; var Hold = Transaction.extend({ card: Model.belongsTo('card', 'funding-instrument'), source: Ember.computed.alias('card'), debit: Model.belongsTo('debit', 'debit'), + order: Model.belongsTo('order', 'order'), status: function() { var apiStatus = this.get("__json.status"); @@ -45,4 +47,8 @@ var Hold = Transaction.extend({ funding_instrument_type: Ember.computed.alias('card.type_name') }); +Hold.reopenClass({ + serializer: TransactionSerializer.create() +}); + export default Hold; diff --git a/app/models/marketplace.js b/app/models/marketplace.js index ea6c6b6eb..fc075a727 100644 --- a/app/models/marketplace.js +++ b/app/models/marketplace.js @@ -85,19 +85,6 @@ var Marketplace = UserMarketplace.extend({ }); }, - search: function(query, resultsType, params) { - var baseUri = this.get("uri") + "/search"; - var searchParams = _.extend({ - sortField: "created_at", - sortOrder: "desc", - limit: 10, - query: query - }, params); - - var resultsUri = Utils.applyUriFilters(baseUri, searchParams); - return SearchModelArray.newArrayLoadedFromUri(resultsUri, resultsType); - }, - has_debitable_bank_account: Ember.computed.readOnly('owner_customer.has_debitable_bank_account'), has_bank_account: Ember.computed.readOnly('owner_customer.has_bank_account'), diff --git a/app/models/order.js b/app/models/order.js index 30e4f9160..ea6b6b242 100644 --- a/app/models/order.js +++ b/app/models/order.js @@ -54,9 +54,12 @@ var Order = Model.extend({ status: function() { if (this.get("isOverdue")) { return "overdue"; + } else if (this.get('amount_escrowed') === 0 && this.get("credits_list.length") > 0) { + return "completed"; } - return null; - }.property("isOverdue"), + + return "active"; + }.property("isOverdue", "amount_escrowed", "credits_list.length"), isOverdue: function() { if (this.get('amount_escrowed') === 0) { diff --git a/app/models/refund.js b/app/models/refund.js index 87df9bf2d..1cddb8e41 100644 --- a/app/models/refund.js +++ b/app/models/refund.js @@ -3,6 +3,7 @@ import Model from "./core/model"; import Transaction from "./transaction"; var Refund = Transaction.extend({ + order: Model.belongsTo('order', 'order'), debit: Model.belongsTo('debit', 'debit'), dispute: Model.belongsTo('dispute', 'dispute'), diff --git a/app/models/results-loaders/accounts.js b/app/models/results-loaders/accounts.js new file mode 100644 index 000000000..04f9d0341 --- /dev/null +++ b/app/models/results-loaders/accounts.js @@ -0,0 +1,8 @@ +import BaseResultsLoader from "./base"; +import Account from "../account"; + +var AccountsResultsLoader = BaseResultsLoader.extend({ + resultsType: Account, +}); + +export default AccountsResultsLoader; diff --git a/app/models/results-loaders/buyer-transactions.js b/app/models/results-loaders/buyer-transactions.js new file mode 100644 index 000000000..a141206e7 --- /dev/null +++ b/app/models/results-loaders/buyer-transactions.js @@ -0,0 +1,23 @@ +import FilterableTransactionsResultsLoader from "./filterable-transactions"; +import ModelArray from "../core/model-array"; + +var BuyerTransactionsResultsLoader = FilterableTransactionsResultsLoader.extend({ + results: function() { + var self = this; + + var results = this.get("unfilteredResults").filter(function(transaction) { + return !self.get("merchantOrderUris").contains(transaction.get("order_uri")); + }); + return ModelArray.create({ + isLoaded: true, + content: results + }); + }.property("unfilteredResults.@each.order_uri", "merchantOrderUris"), + + merchantOrderUris: function() { + return this.get("ordersResultsLoader.results").mapBy("uri"); + }.property("ordersResultsLoader.results.@each.uri"), + +}); + +export default BuyerTransactionsResultsLoader; diff --git a/app/models/results-loaders/filterable-transactions.js b/app/models/results-loaders/filterable-transactions.js new file mode 100644 index 000000000..17a65dcbb --- /dev/null +++ b/app/models/results-loaders/filterable-transactions.js @@ -0,0 +1,31 @@ +import TransactionsResultsLoader from "./transactions"; +import ModelArray from "../core/model-array"; +import SearchModelArray from "../core/search-model-array"; + +var FilterableTransactionsResultsLoader = TransactionsResultsLoader.extend({ + unfilteredResults: function() { + var uri = this.get('resultsUri'); + var type = this.get('resultsType'); + + if (Ember.isBlank(uri)) { + return ModelArray.create({ + isLoaded: true + }); + } else { + var searchArray = SearchModelArray.newArrayLoadedFromUri(uri, type); + searchArray.setProperties({ + sortProperties: [this.get('sortField') || 'created_at'], + sortAscending: this.get('sortDirection') === 'asc', + isLoaded: true + }); + + return searchArray; + } + }.property("resultsUri", "resultsType", "sortField", "sortDirection"), + + results: function() { + Ember.assert("Implement #results and make it return a ModelArray", false); + }.property() +}); + +export default FilterableTransactionsResultsLoader; diff --git a/app/models/results-loaders/marketplace-search.js b/app/models/results-loaders/marketplace-search.js index 7382ed10b..48bbf51be 100644 --- a/app/models/results-loaders/marketplace-search.js +++ b/app/models/results-loaders/marketplace-search.js @@ -4,32 +4,35 @@ import Transaction from "../transaction"; import FundingInstrument from "../funding-instrument"; import Customer from "../customer"; import Order from "../order"; +import Account from "../account"; +import Settlement from "../settlement"; import SearchModelArray from "../core/search-model-array"; - -var TRANSACTION_TYPES = ["credit", "debit", "card_hold", "refund", "reversal"]; -var FUNDING_INSTRUMENT_TYPES = ["card", "bank_account"]; -var CUSTOMER_TYPES = ["customer"]; -var ORDER_TYPES = ["order"]; +import Constants from "balanced-dashboard/utils/constants"; var MarketplaceSearchResultsLoader = BaseResultsLoader.extend({ - searchType: "transaction", + searchType: "order_transaction", limit: null, type: function() { var mapping = { - "funding_instrument": FUNDING_INSTRUMENT_TYPES, - "customer": CUSTOMER_TYPES, - "order": ORDER_TYPES + "order_transaction": Constants.SEARCH.ORDER_TRANSACTION_TYPES, + "funding_instrument": Constants.SEARCH.FUNDING_INSTRUMENT_TYPES, + "customer": Constants.SEARCH.CUSTOMER_TYPES, + "order": Constants.SEARCH.ORDER_TYPES, + "account": Constants.SEARCH.ACCOUNT_TYPES, + "settlement": Constants.SEARCH.SETTLEMENT_TYPES, }; - return mapping[this.get("searchType")] || TRANSACTION_TYPES; + return mapping[this.get("searchType")] || Constants.SEARCH.SEARCH_TYPES; }.property("searchType"), resultsType: function() { var mapping = { "funding_instrument": FundingInstrument, "customer": Customer, - "order": Order + "order": Order, + "account": Account, + "settlement": Settlement }; return mapping[this.get("searchType")] || Transaction; }.property("searchType"), diff --git a/app/models/results-loaders/merchant-transactions.js b/app/models/results-loaders/merchant-transactions.js new file mode 100644 index 000000000..841f38a75 --- /dev/null +++ b/app/models/results-loaders/merchant-transactions.js @@ -0,0 +1,23 @@ +import FilterableTransactionsResultsLoader from "./filterable-transactions"; +import ModelArray from "../core/model-array"; + +var MerchantTransactionsResultsLoader = FilterableTransactionsResultsLoader.extend({ + results: function() { + var self = this; + + var results = this.get("unfilteredResults").filter(function(transaction) { + return self.get("merchantOrderUris").contains(transaction.get("order_uri")); + }); + return ModelArray.create({ + isLoaded: true, + content: results + }); + }.property("unfilteredResults.@each.order_uri", "merchantOrderUris"), + + merchantOrderUris: function() { + return this.get("ordersResultsLoader.results").mapBy("uri"); + }.property("ordersResultsLoader.results.@each.uri"), + +}); + +export default MerchantTransactionsResultsLoader; diff --git a/app/models/results-loaders/orders.js b/app/models/results-loaders/orders.js index bb0b30b11..beb4fd11a 100644 --- a/app/models/results-loaders/orders.js +++ b/app/models/results-loaders/orders.js @@ -11,13 +11,13 @@ var OrdersResultsLoader = BaseResultsLoader.extend({ queryStringBuilder.addValues({ limit: this.get("limit"), sort: this.get("sort"), - + type: this.get("typeFilters"), "created_at[>]": this.get("startTime"), "created_at[<]": this.get("endTime"), }); return queryStringBuilder.getQueryStringAttributes(); - }.property("sort", "startTime", "endTime", "limit") + }.property("sort", "startTime", "endTime", "limit", "typeFilters") }); export default OrdersResultsLoader; diff --git a/app/models/results-loaders/unsettled-transactions.js b/app/models/results-loaders/unsettled-transactions.js new file mode 100644 index 000000000..887f5dce2 --- /dev/null +++ b/app/models/results-loaders/unsettled-transactions.js @@ -0,0 +1,57 @@ +import FilterableTransactionsResultsLoader from "./filterable-transactions"; +import ModelArray from "../core/model-array"; +import SearchModelArray from "../core/search-model-array"; + +var UnsettledTransactionsResultsLoader = FilterableTransactionsResultsLoader.extend({ + unfilteredResults: function() { + var uri = this.get('resultsUri'); + var type = this.get('resultsType'); + + if (Ember.isBlank(uri)) { + return ModelArray.create({ + isLoaded: true + }); + } else { + var searchArray = SearchModelArray.newArrayLoadedFromUri(uri, type); + searchArray.setProperties({ + sortProperties: [this.get('sortField') || 'created_at'], + sortAscending: this.get('sortDirection') === 'asc', + isLoaded: true + }); + + return searchArray; + } + }.property("resultsUri", "resultsType", "sortField", "sortDirection"), + + results: function() { + var self = this; + + var results = this.get("unfilteredResults").filter(function(credit) { + return !self.get("settledTransactionIds").contains(credit.get("id")); + }); + return ModelArray.create({ + isLoaded: true, + content: results + }); + }.property("unfilteredResults.@each.id", "settledTransactionIds"), + + settledTransactionIds: function() { + return this.get("settledTransactions").mapBy("id"); + }.property("settledTransactions.@each.id"), + + settledTransactions: function() { + var self = this; + var settlements = this.get("settlementsResultsLoader.results"); + var settledTransactions = []; + + settlements.forEach(function(settlement) { + var promise = SearchModelArray.newArrayLoadedFromUri(settlement.get("credits_uri"), "credit"); + promise.then(function(credits) { + settledTransactions.pushObjects(credits.content); + }); + }); + return settledTransactions; + }.property("settlementsResultsLoader.results.length") +}); + +export default UnsettledTransactionsResultsLoader; diff --git a/app/models/reversal.js b/app/models/reversal.js index 271011162..1b4ab8371 100644 --- a/app/models/reversal.js +++ b/app/models/reversal.js @@ -3,6 +3,7 @@ import Transaction from "./transaction"; import Model from "./core/model"; var Reversal = Transaction.extend({ + order: Model.belongsTo('order', 'order'), credit: Model.belongsTo('credit', 'credit'), type_name: 'Reversal', diff --git a/app/models/settlement.coffee b/app/models/settlement.coffee new file mode 100644 index 000000000..ade299289 --- /dev/null +++ b/app/models/settlement.coffee @@ -0,0 +1,14 @@ +`import Ember from "ember";` +`import Utils from "balanced-dashboard/lib/utils";` +`import Model from "./core/model";` + +Settlement = Model.extend( + routeName: "settlement", + route_name: "settlement", + type_name: "Settlement", + amountInDollars: (-> + "$%@".fmt(Utils.centsToDollars(@get("amount"))) + ).property("amount") +) + +`export default Settlement;` diff --git a/app/models/settlement.js b/app/models/settlement.js deleted file mode 100644 index ce8e49a63..000000000 --- a/app/models/settlement.js +++ /dev/null @@ -1,10 +0,0 @@ -import Transaction from "./transaction"; -import Model from "./core/model"; - -var Settlement = Transaction.extend({ - debit: Model.belongsTo('debit', 'debit'), - type_name: 'settlement', - route_name: 'Settlement' -}); - -export default Settlement; diff --git a/app/router.coffee b/app/router.coffee index bf4cb65e8..5d26594ab 100644 --- a/app/router.coffee +++ b/app/router.coffee @@ -48,20 +48,31 @@ Router.map -> this.route('import_payouts') # exists to handle old URIs - this.resource('accounts', path: '/accounts/:item_id') this.route("redirect_activity_transactions", path: '/activity/transactions') + this.route("redirect_transactions", path: '/transactions') this.route("redirect_activity_orders", path: '/activity/orders') this.route("redirect_activity_customers", path: 'activity/customers') this.route("redirect_activity_funding_instruments", path: 'activity/funding_instruments') this.route("redirect_activity_disputes", path: 'activity/disputes') this.route("redirect_invoices", path: 'invoices') + this.resource('account', path: '/accounts/:item_id') + this.route("orders") this.resource('orders', path: '/orders/:item_id') + this.resource('credits', path: '/credits/:item_id') + this.resource('reversals', path: '/reversals/:item_id') + this.resource('debits', path: '/debits/:item_id') + this.resource('holds', path: '/holds/:item_id') + this.resource('refunds', path: '/refunds/:item_id') + this.resource('events', path: '/events/:item_id') this.route("customers") this.resource('customer', path: '/customers/:item_id') + this.route("settlements") + this.resource('settlement', path: '/settlements/:item_id') + this.route("disputes") this.resource('dispute', path: '/disputes/:item_id') @@ -69,14 +80,6 @@ Router.map -> this.resource('bank_accounts', path: '/bank_accounts/:item_id') this.resource('cards', path: '/cards/:item_id') - this.route("transactions") - this.resource('credits', path: '/credits/:item_id') - this.resource('reversals', path: '/reversals/:item_id') - this.resource('debits', path: '/debits/:item_id') - this.resource('holds', path: '/holds/:item_id') - this.resource('refunds', path: '/refunds/:item_id') - this.resource('events', path: '/events/:item_id') - this.route("logs") this.resource("log", path: "/logs/:item_id") diff --git a/app/routes/account.coffee b/app/routes/account.coffee new file mode 100644 index 000000000..de90b81f0 --- /dev/null +++ b/app/routes/account.coffee @@ -0,0 +1,27 @@ +`import ModelRoute from "./model"` + +AccountRoute = ModelRoute.extend( + model: (params) -> + store = @getStore() + store.fetchItem("account", "/accounts/#{params.item_id}") + + setupController: (controller, model) -> + @_super(controller, model) + + this.get("container").lookupFactory("model:customer").find(model.get("customer_uri")).then (customer) -> + model.set("customer", customer) + + settlementsResultsLoader = this.get("container").lookupFactory("results-loader:transactions").create({ + path: model.get("settlements_uri") + }); + + unsettledCreditsResultsLoader = this.get("container").lookupFactory("results-loader:unsettled_transactions").create({ + path: model.get("credits_uri"), + settlementsResultsLoader: settlementsResultsLoader + }); + + controller.set("creditsResultsLoader", unsettledCreditsResultsLoader); + controller.set("settlementsResultsLoader", settlementsResultsLoader); +) + +`export default AccountRoute` diff --git a/app/routes/customer.js b/app/routes/customer.js index 38f77a52f..b0223027a 100644 --- a/app/routes/customer.js +++ b/app/routes/customer.js @@ -1,5 +1,6 @@ import ModelRoute from "./model"; import Customer from "../models/customer"; +import LegacyResultsLoaderWrapper from "balanced-dashboard/utils/legacy-results-loader-wrapper"; var CustomerRoute = ModelRoute.extend({ title: 'Customer', @@ -8,6 +9,18 @@ var CustomerRoute = ModelRoute.extend({ setupController: function(controller, customer) { this._super(controller, customer); + var store = this.getStore(); + store.fetchCollection("account", customer.get("accounts_uri"), { limit: 10 }).then(function(collection) { + var wrapper = LegacyResultsLoaderWrapper.create({collection: collection}); + controller.set("accountsResultsLoader", wrapper); + }); + + var ordersResultsLoader = customer.getOrdersLoader({ + limit: 10 + }); + + controller.set("ordersResultsLoader", ordersResultsLoader); + controller.setProperties({ fundingInstrumentsResultsLoader: customer.getFundingInstrumentsLoader({ limit: 10 @@ -18,6 +31,16 @@ var CustomerRoute = ModelRoute.extend({ transactionsResultsLoader: customer.getTransactionsLoader({ limit: 10, status: ['pending', 'succeeded', 'failed'] + }), + buyerTransactionsResultsLoader: customer.getBuyerTransactionsLoader({ + limit: 10, + status: ['pending', 'succeeded', 'failed'], + ordersResultsLoader: ordersResultsLoader + }), + merchantTransactionsResultsLoader: customer.getMerchantTransactionsLoader({ + limit: 10, + status: ['pending', 'succeeded', 'failed'], + ordersResultsLoader: ordersResultsLoader }) }); } diff --git a/app/routes/marketplace/import-payouts.js b/app/routes/marketplace/import-payouts.js index f9dac529e..6d9006117 100644 --- a/app/routes/marketplace/import-payouts.js +++ b/app/routes/marketplace/import-payouts.js @@ -1,7 +1,7 @@ import AuthRoute from "../auth"; var MarketplaceImportPayoutsRoute = AuthRoute.extend({ - pageTitle: 'Import payouts', + pageTitle: 'Import one-off credits', setupController: function(controller, model) { controller.refresh(); diff --git a/app/routes/marketplace/index.js b/app/routes/marketplace/index.js index 48100c297..1bbbc1d12 100644 --- a/app/routes/marketplace/index.js +++ b/app/routes/marketplace/index.js @@ -2,13 +2,7 @@ import AuthRoute from "balanced-dashboard/routes/auth"; var MarketplaceIndexRoute = AuthRoute.extend({ beforeModel: function() { - var mp = this.modelFor("marketplace"); - if (mp.get("isOrdersRequired")) { - this.transitionTo('marketplace.orders'); - } - else { - this.transitionTo('marketplace.transactions'); - } + this.transitionTo('marketplace.orders'); } }); diff --git a/app/routes/marketplace/orders.js b/app/routes/marketplace/orders.js index 332543ef8..4ad519563 100644 --- a/app/routes/marketplace/orders.js +++ b/app/routes/marketplace/orders.js @@ -4,7 +4,7 @@ var MarketplaceOrdersRoute = AuthRoute.extend({ pageTitle: 'Orders', model: function() { var marketplace = this.modelFor("marketplace"); - return marketplace.getOrdersLoader(); + return marketplace.getTransactionsLoader(); }, }); diff --git a/app/routes/marketplace/settlements.js b/app/routes/marketplace/settlements.js new file mode 100644 index 000000000..e6f3282b4 --- /dev/null +++ b/app/routes/marketplace/settlements.js @@ -0,0 +1,15 @@ +import AuthRoute from "../auth"; +import LegacyResultsLoaderWrapper from "balanced-dashboard/utils/legacy-results-loader-wrapper"; + +var MarketplaceSettlementsRoute = AuthRoute.extend({ + pageTitle: 'Settlements', + model: function() { + var store = this.container.lookup("controller:marketplace").get("store"); + return store.fetchCollection("settlement", "/settlements", { limit: 50 }).then(function(collection) { + var wrapper = LegacyResultsLoaderWrapper.create({collection: collection}); + return wrapper; + }); + }, +}); + +export default MarketplaceSettlementsRoute; diff --git a/app/routes/marketplace/transactions.js b/app/routes/marketplace/transactions.js deleted file mode 100644 index 770e87255..000000000 --- a/app/routes/marketplace/transactions.js +++ /dev/null @@ -1,11 +0,0 @@ -import AuthRoute from "../auth"; - -var MarketplaceTransactionsRoute = AuthRoute.extend({ - pageTitle: 'Transactions', - model: function() { - var marketplace = this.modelFor("marketplace"); - return marketplace.getTransactionsLoader(); - }, -}); - -export default MarketplaceTransactionsRoute; diff --git a/app/routes/model.js b/app/routes/model.js index a98b1a410..8652636cf 100644 --- a/app/routes/model.js +++ b/app/routes/model.js @@ -2,6 +2,10 @@ import TitleRoute from "./title"; import Utils from "balanced-dashboard/lib/utils"; var ModelRoute = TitleRoute.extend({ + getStore: function() { + return this.get("container").lookup("controller:marketplace").get("store"); + }, + model: function(params) { var marketplace = this.modelFor('marketplace'); var modelObject = this.get('modelObject'); diff --git a/app/routes/settlement.coffee b/app/routes/settlement.coffee new file mode 100644 index 000000000..fafe8e95f --- /dev/null +++ b/app/routes/settlement.coffee @@ -0,0 +1,26 @@ +`import ModelRoute from "./model";` +`import Settlement from "../models/bk/settlement";` + +SettlementRoute = ModelRoute.extend( + model: (params) -> + store = @getStore() + store.fetchItem("settlement", "/settlements/#{params.item_id}") + + setupController: (controller, model) -> + @_super(controller, model) + + store = @getStore() + store.fetchItem("account", model.get("source_uri")).then (source) -> + model.set("source", source.toLegacyModel()) + + store.fetchItem("account", model.get("destination_uri")).then (destination) -> + model.set("destination", destination.toLegacyModel()) + + creditsResultsLoader = this.get("container").lookupFactory("results-loader:transactions").create({ + path: model.get("credits_uri") + }); + + controller.set("creditsResultsLoader", creditsResultsLoader); +) + +`export default SettlementRoute;` diff --git a/app/serializers/transaction.js b/app/serializers/transaction.js new file mode 100644 index 000000000..4c27728e0 --- /dev/null +++ b/app/serializers/transaction.js @@ -0,0 +1,17 @@ +import Rev1Serializer from "./rev1"; + +var TransactionSerializer = Rev1Serializer.extend({ + _propertiesMap: function(record) { + var json = this._super(record); + + if (!Ember.isBlank(json.order_uri)) { + json.order = json.order_uri; + delete json.order_uri; + } + + return json; + } +}); + + +export default TransactionSerializer; diff --git a/app/stores/balanced.coffee b/app/stores/balanced.coffee index 0b9d7486f..5ab873a8e 100644 --- a/app/stores/balanced.coffee +++ b/app/stores/balanced.coffee @@ -4,6 +4,8 @@ BalancedStore = Store.extend( modelMaps: bank_account: "model:bk/bank-account" customer: "model:bk/customer" + account: "model:bk/account" + settlement: "model:bk/settlement" api_key_production: "model:bk/api-key-production" marketplace: "model:bk/marketplace" ) diff --git a/app/styles/components.less b/app/styles/components.less index 081edf01a..c71057a13 100644 --- a/app/styles/components.less +++ b/app/styles/components.less @@ -47,3 +47,15 @@ } } } + +.dropdown-toggle.ellipsis { + min-width: 0; + margin-right: 0; + padding: 4px 8px; + + span { + .sl-16-b; + color: @gray4; + line-height: 20px; + } +} diff --git a/app/styles/form_group.less b/app/styles/form_group.less index 89864d0fb..3c9584657 100644 --- a/app/styles/form_group.less +++ b/app/styles/form_group.less @@ -30,6 +30,10 @@ p { .sl-sm; + + a { + font-size: 12px; + } } .key-value-field-group { diff --git a/app/styles/login.less b/app/styles/login.less index 798859cec..a420cc591 100644 --- a/app/styles/login.less +++ b/app/styles/login.less @@ -37,6 +37,33 @@ } } +.intro-page { + padding: 100px 200px; + text-align: center; + + i.non-interactive { + color: @gray2; + font-size: 60px; + } + + h2 { + .sl-lg-sb; + padding: 20px 0; + margin: 20px 0 0; + } + + p.description { + .sl-20; + color: @gray6; + width: 650px; + margin: 0 auto 10px; + } + + div.center { + margin: 30px 0 25px; + } +} + .page-form { background-color: @gray0; border: 1px solid @gray2; diff --git a/app/styles/marketplace.less b/app/styles/marketplace.less index 9db8ce92a..d26231d57 100644 --- a/app/styles/marketplace.less +++ b/app/styles/marketplace.less @@ -112,8 +112,15 @@ form { li { margin: 5px 0; position: relative; - .delete { - float: right; + + .icon-table-x { + color: @gray1; + margin-top: 25px; + margin-right: 10px; + + &:hover { + color: @white; + } } .info { height: 65px; diff --git a/app/styles/pages.less b/app/styles/pages.less index c1132b0d1..c4c69a94f 100644 --- a/app/styles/pages.less +++ b/app/styles/pages.less @@ -173,6 +173,16 @@ } } + &.completed { + span { + color: @gray4; + } + + &:before { + background-color: @gray3; + } + } + &.overdue { &:before { font-family: 'Balanced-Icon'; @@ -287,6 +297,10 @@ section[class$="-info"] { color: @gray8; } +.drawer-open { + display: block; +} + .dispute-alert { margin: -45px -45px 45px -45px; diff --git a/app/styles/popovers.less b/app/styles/popovers.less index 6b2a0dd0f..ab95cdcc6 100644 --- a/app/styles/popovers.less +++ b/app/styles/popovers.less @@ -1,3 +1,7 @@ +[data-toggle="popover"] { + display: inline-block; +} + .icon-tooltip { font-size: 16px; margin-left: 3px; diff --git a/app/styles/results.less b/app/styles/results.less index 06499d479..422a25d9e 100644 --- a/app/styles/results.less +++ b/app/styles/results.less @@ -116,6 +116,11 @@ header.results-label h3 { } } + i.non-interactive { + color: @black; + margin-right: 5px; + } + td.unlinked-status { i.icon-unlinked { color: @imperialRed60; @@ -140,13 +145,78 @@ header.results-label h3 { } } + td.ungrouped-transactions-container { + padding-left: 0; + padding-right: 0; + + table.grouped-transactions { + border-bottom: none; + + thead { + border: none; + margin-top: -1px; + + th { + border-top: none; + border-bottom: none; + } + } + + tr { + &:last-of-type { + border-bottom: none; + } + + &:hover { + background-color: @persianBlue10; + } + } + } + } + td.grouped-transactions-container { padding-left: 0; padding-right: 0; + &.nested { + table.grouped-transactions:first-of-type { + border-bottom: none; + + td:first-of-type { + padding-left: 25px; + } + } + + table.grouped-transactions { + border-bottom: none; + + td:first-of-type { + padding-left: 50px; + } + } + } + + &.divided { + table.grouped-transactions { + td:first-of-type { + padding-left: 10px; + } + } + } + table.grouped-transactions { border-bottom: 1px solid @gray2; + thead { + border: none; + margin-top: -1px; + + th { + border-top: none; + border-bottom: none; + } + } + &:last-of-type { border-bottom: none; } @@ -168,6 +238,7 @@ header.results-label h3 { } } } + td { a.active { pointer-events: none; @@ -178,70 +249,31 @@ header.results-label h3 { } } + &:first-of-type { + padding-left: 25px; + } + &.status { text-transform: none; padding-left: 10px; - &:first-of-type { - padding-left: 25px; - } - - a > span > .secondary { - margin-left: 14px; - } - - &.orders .order { + & > a > span { &:before { - font-family: 'Balanced-Icon'; - background-color: transparent; - &:extend(.icon-orders:before); - font-size: 10px; - color: @gray4; - margin-top: -14px; - } - > .primary { - margin-left: 14px; - } - } - - &.dispute > a > span { - &:before { - font-family: 'Balanced-Icon'; - background-color: transparent; - &:extend(.icon-disputes:before); - font-size: 12px; - color: @curryYellow80; - margin-top: -12px; - } - - &.new:before { - color: @curryYellow80; - } - - &.under_review:before { - color: @byzantiumPurple80; - } - - &.won:before { - color: @pineGreen80; - } - - &.lost:before { - color: @imperialRed80; + margin-left: 2px; } - > .primary { - margin-left: 14px; + & > .primary, & > .secondary { + margin-left: 16px; } } - a > span:after { + &.connected a > span:after { position: absolute; content: ''; border-left: 2px solid @gray3; height: 30px; margin-top: -19px; - margin-left: 3px; + margin-left: 5px; } } } @@ -249,6 +281,17 @@ header.results-label h3 { } } + td.payment-method { + [class^="icon-"] { + font-family: 'Balanced-Icon'; + color: @gray4; + } + + & > a > span > .secondary { + margin-left: 19px; + } + } + td.status { a > span:before { content: ''; @@ -263,11 +306,34 @@ header.results-label h3 { a > span:before { float: left; margin-top: 7px; + margin-bottom: 10px; } + } - a > span.failed:before { - float: left; - margin-top: 14px; + &.dispute > a > span { + &:before { + font-family: 'Balanced-Icon'; + background-color: transparent; + &:extend(.icon-disputes:before); + font-size: 12px; + color: @curryYellow80; + margin-top: -12px; + } + + &.new:before { + color: @curryYellow80; + } + + &.under_review:before { + color: @byzantiumPurple80; + } + + &.won:before { + color: @pineGreen80; + } + + &.lost:before { + color: @imperialRed80; } } @@ -283,7 +349,7 @@ header.results-label h3 { } } - .failed, .expired, .lost, .arbitration, .canceled, .voided, .invalidated, .removed, .bad { + .failed, .expired, .lost, .arbitration, .canceled, .voided, .invalidated, .removed, .overdue, .bad { &:before { background-color: @imperialRed80; } @@ -292,6 +358,10 @@ header.results-label h3 { .under_review:before { background-color: @byzantiumPurple80; } + + .completed:before { + background-color: @gray3; + } } .num, .align-right { diff --git a/app/styles/scaffolding.less b/app/styles/scaffolding.less index 30f983f68..dcc999f62 100644 --- a/app/styles/scaffolding.less +++ b/app/styles/scaffolding.less @@ -121,12 +121,6 @@ min-width: 90px; } -#order-sort-menu { - .caret { - margin-top: 11px; - } -} - .dropdown-menu.align-right { right: 0; left: auto; @@ -168,6 +162,12 @@ hr { padding: 1px 8px 0; margin-top: 3px; .border-radius(15px); + + &.new { + background-color: @curryYellow60; + font-size: 10px; + text-transform: uppercase; + } } .payments-navbar { diff --git a/app/templates/account.hbs b/app/templates/account.hbs new file mode 100644 index 000000000..df665cbf9 --- /dev/null +++ b/app/templates/account.hbs @@ -0,0 +1,23 @@ +{{view "page-navigations/page-navigation" pageType=model.type_name title=model.id}} + +{{#view "detail-views/body-panel"}} + {{#view "detail-views/api-model-panel" model=model}} + + {{view detail-views/summary-sections/account-summary-section model=model}} + {{view detail-views/description-lists/account-titled-key-values-section model=model}} + {{view "meta-list" type=model}} + {{/view}} + + {{#view "detail-views/main-panel"}} +

Unsettled transactions

+
+ {{view "results/transactions-table-grouped-by-order" loader=creditsResultsLoader hideCustomerColumn=true}} +
+

Settled transactions

+
+ {{view "results/transactions-table-grouped-by-settlement" loader=settlementsResultsLoader hideCustomerColumn=true}} +
+ {{/view}} +{{/view}} diff --git a/app/templates/accounts.hbs b/app/templates/accounts.hbs deleted file mode 100644 index c24cd6895..000000000 --- a/app/templates/accounts.hbs +++ /dev/null @@ -1 +0,0 @@ -{{outlet}} diff --git a/app/templates/bank-accounts.hbs b/app/templates/bank-accounts.hbs index 364e3c254..393fa3781 100644 --- a/app/templates/bank-accounts.hbs +++ b/app/templates/bank-accounts.hbs @@ -1,11 +1,37 @@ {{#view "page-navigations/page-navigation" pageType=model.type_name title=model.title_description}} {{#unless model.isRemoved}}
- {{#if can_debit}} + {{#if model.can_debit}} Debit + {{else}} + {{#view "popover" class="sl-none" data-trigger="hover" data-placement="bottom" data-original-title="Can't debit this bank account" data-content="You must verify this bank account to debit from it."}} + Debit + {{/view}} {{/if}} - {{#if can_credit}} - Credit + + {{#if model.can_credit}} + {{#if model.customer}} + {{#if model.customer.hasCreditableOrders}} + Credit + {{else}} + {{#view "popover" class="sl-none" data-trigger="hover" data-placement="bottom" data-original-title="Can't credit this bank account" data-content="The customer of this bank account does not have any active orders as a merchant."}} + Credit + {{/view}} + {{/if}} + {{/if}} + + {{/if}}
{{/unless}} @@ -21,7 +47,7 @@ {{#view "detail-views/main-panel"}}

Payments

- {{view "results/embedded-transactions-table" loader=transactionsResultsLoader}} + {{view "results/transactions-table-grouped-by-order" loader=transactionsResultsLoader hideCustomerColumn=true}}

Logs

diff --git a/app/templates/cards.hbs b/app/templates/cards.hbs index 2403dbe00..b77698739 100644 --- a/app/templates/cards.hbs +++ b/app/templates/cards.hbs @@ -3,9 +3,32 @@
{{#if model.can_debit}} Hold + Debit + {{#if model.can_credit}} - Credit + {{#if model.customer}} + {{#if model.customer.hasCreditableOrders}} + Credit + {{else}} + {{#view "popover" class="sl-none" data-trigger="hover" data-placement="bottom" data-original-title="Can't credit this card" data-content="The customer of this card does not have any active orders as a merchant."}} + Credit + {{/view}} + {{/if}} + {{/if}} + + {{/if}} {{/if}}
@@ -22,7 +45,7 @@ {{#view "detail-views/main-panel"}}

Payments

- {{view "results/embedded-transactions-table" loader=transactionsResultsLoader}} + {{view "results/transactions-table-grouped-by-order" loader=transactionsResultsLoader hideCustomerColumn=true}}

Logs

diff --git a/app/templates/customer.hbs b/app/templates/customer.hbs index 68bb0408f..e541e47b7 100644 --- a/app/templates/customer.hbs +++ b/app/templates/customer.hbs @@ -1,7 +1,14 @@ {{#view "page-navigations/page-navigation" pageType="Customer" title=model.display_me}}
- Credit Debit + + {{#if model.hasCreditableOrders}} + Credit + {{else}} + {{#view "popover" class="sl-none" data-trigger="hover" data-placement="bottom" data-original-title="Can't credit this customer" data-content="This customer does not have any active orders as a merchant."}} + Credit + {{/view}} + {{/if}}
{{/view}} @@ -11,39 +18,57 @@ {{view "detail-views/description-lists/customer-titled-key-values-section" model=model}} {{view "meta-list" type=model}} {{/view}} - {{#view "detail-views/main-panel"}} -

Disputes

-
- {{view "results/embedded-transactions-table" loader=disputesResultsLoader}} -
- -

Payments

-
- {{view "results/embedded-transactions-table" loader=transactionsResultsLoader}} -
- - -

Payment methods

-
- {{view "results/embedded-funding-instruments-table" loader=fundingInstrumentsResultsLoader}} -
- -

Logs

-
- {{view "resource-logs" content=model}} -
- -

Events

-
- {{view "resource-events" content=model}} -
+ {{#view "detail-views/main-panel" model=model}} + {{view "detail-views/tabs/customer-tab" model=model}} + + {{#if view.isActivityTabSelected}} +

Merchant activity

+
+ {{view "results/transactions-table-grouped-by-order" loader=merchantTransactionsResultsLoader hideCustomerColumn=true}} +
+ +

Buyer activity

+
+ {{view "results/transactions-table-grouped-by-order" loader=buyerTransactionsResultsLoader hideCustomerColumn=true}} +
+ {{/if}} + {{#if view.isDisputesTabSelected}} +

Disputes

+
+ {{view "results/embedded-disputes-table" loader=disputesResultsLoader}} +
+ {{/if}} + {{#if view.isPaymentMethodsTabSelected}} + +

Cards & Bank accounts

+
+ {{view "results/embedded-funding-instruments-table" loader=fundingInstrumentsResultsLoader}} +
+ +

Internal accounts

+
+ {{view "results/embedded-internal-accounts-table" loader=accountsResultsLoader}} +
+ {{/if}} + + {{#if view.isLogsEventsTabSelected}} +

Logs

+
+ {{view "resource-logs" content=model}} +
+ +

Events

+
+ {{view "resource-events" content=model}} +
+ {{/if}} {{/view}} {{/view}} diff --git a/app/templates/debits.hbs b/app/templates/debits.hbs index 27cff88a3..e4c2f2ec7 100644 --- a/app/templates/debits.hbs +++ b/app/templates/debits.hbs @@ -8,7 +8,7 @@ {{#view "detail-views/body-panel"}} {{#view "detail-views/api-model-panel" model=model}} - {{view detail-views/summary-sections/debit-summary-section model=model}} + {{view "detail-views/summary-sections/debit-summary-section" model=model}} {{view "detail-views/description-lists/transaction-titled-key-values-section" model=model}} {{view "meta-list" type=model}} {{/view}} diff --git a/app/templates/detail-views/list-section.hbs b/app/templates/detail-views/list-section.hbs deleted file mode 100644 index 7fcdff26e..000000000 --- a/app/templates/detail-views/list-section.hbs +++ /dev/null @@ -1,19 +0,0 @@ -
-

{{view.title}}

- - {{#if view.linkedResources}} -
    - {{#each linkedResource in view.linkedResources}} -
  • - {{#link-to linkedResource.resource.route_name linkedResource.resource title=linkedResource.hoverValue}} - {{linkedResource.value}} - {{/link-to}} -
  • - {{/each}} -
- - {{#if view.resources.hasNextPage}} - Load more - {{/if}} - {{/if}} -
diff --git a/app/templates/detail-views/resource-summary.hbs b/app/templates/detail-views/resource-summary.hbs index 053e7c8ee..7361c72de 100644 --- a/app/templates/detail-views/resource-summary.hbs +++ b/app/templates/detail-views/resource-summary.hbs @@ -6,9 +6,17 @@ {{/if}} {{else}} {{#if view.model}} - {{#link-to view.model.route_name view.model title=view.hoverValue}} - {{view.value}} - {{/link-to}} + {{#if view.model.route_name}} + {{#link-to view.model.route_name view.model title=view.hoverValue}} + {{view.value}} + {{/link-to}} + {{else}} + {{#if view.model.routeName}} + {{#link-to view.model.routeName view.model title=view.hoverValue}} + {{view.value}} + {{/link-to}} + {{/if}} + {{/if}} {{else}} none {{/if}} diff --git a/app/templates/detail-views/summary-items/base.hbs b/app/templates/detail-views/summary-items/base.hbs new file mode 100644 index 000000000..359c782c6 --- /dev/null +++ b/app/templates/detail-views/summary-items/base.hbs @@ -0,0 +1,32 @@ +{{#if view.isLoading}} + Loading +{{else}} {{#if view.isBlank}} + none +{{else}} {{#if view.isLink}} + {{#link-to view.routeName view.model title=view.hoverValue}} + {{view.text}} + {{/link-to}} +{{else}} + {{view.text}} +{{/if}} {{/if}} {{/if}} + +{{#if view.isDescription}} +

+ {{view.description}} + {{#if view.isLearnMoreText}} + Learn more + {{/if}} +

+{{/if}} +{{#if view.isHasButton}} + + {{view.buttonModalText}} + +{{/if}} + + +{{#if view.isLearnMoreText}} +

+ {{view.learnMoreText}} +

+{{/if}} diff --git a/app/templates/detail-views/summary-items/label-base.hbs b/app/templates/detail-views/summary-items/label-base.hbs new file mode 100644 index 000000000..c84cf5809 --- /dev/null +++ b/app/templates/detail-views/summary-items/label-base.hbs @@ -0,0 +1,7 @@ + {{view.text}} + +{{#if view.isModalEditLink}} + + + +{{/if}} diff --git a/app/templates/detail-views/summary-section-base.hbs b/app/templates/detail-views/summary-section-base.hbs new file mode 100644 index 000000000..8984fefdf --- /dev/null +++ b/app/templates/detail-views/summary-section-base.hbs @@ -0,0 +1,5 @@ +
+ {{view "detail-views/summary-sections/summary-section-resource-list" + viewName="resourcesList" + }} +
diff --git a/app/templates/detail-views/summary-section.hbs b/app/templates/detail-views/summary-section.hbs deleted file mode 100644 index 992d370f7..000000000 --- a/app/templates/detail-views/summary-section.hbs +++ /dev/null @@ -1,51 +0,0 @@ -{{#if view.hasStatusOrLinkedResources}} -
-
- {{#if view.status}} -
- - Status -
-
- {{sentence-case view.status}} - - {{#if view.statusText}} -

- {{view.statusText}} - - {{#if view.learnMore}} - Learn more - {{/if}} -

- {{/if}} - - {{#if view.statusButtonModalView}} - - {{view.statusButtonText}} - - {{/if}} - - {{#if view.learnMore}} -

- {{view.learnMore.text}} -

- {{/if}} -
- {{/if}} - - {{#each linkedResource in view.linkedResources}} -
- - {{linkedResource.title}} - - {{#if linkedResource.editModelModalClass}} - - - - {{/if}} -
- {{view "detail-views/resource-summaries/resource-summary-base" model=linkedResource.resource isDescription=linkedResource.isDescription}} - {{/each}} -
-
-{{/if}} diff --git a/app/templates/events.hbs b/app/templates/events.hbs index c50934ead..c50e5a6f0 100644 --- a/app/templates/events.hbs +++ b/app/templates/events.hbs @@ -39,8 +39,8 @@
{{eventCallback.status}}
{{#if eventCallback.isSaving}} -
- +
+ Loading...
{{else}} Replay diff --git a/app/templates/marketplace/orders.hbs b/app/templates/marketplace/orders.hbs index 6e8f3a547..9113baecf 100644 --- a/app/templates/marketplace/orders.hbs +++ b/app/templates/marketplace/orders.hbs @@ -1,5 +1,20 @@ -
+ + +{{#if resultsLoader.results}}
- {{view "results/orders-table" loader=resultsLoader}} + {{view "results/transactions-table-grouped-by-order" loader=resultsLoader}}
-
+{{else}} + {{#if resultsLoader.results.isLoaded}} + {{#view "results/intro-page" iconName="orders" title="Create your first order" description="Each transaction within the order is linked together so you can easily reconcile incoming funds from buyers with outgoing funds to merchants."}} + + Learn more about orders + {{/view}} + {{else}} +
+ Loading... +
+ {{/if}} +{{/if}} diff --git a/app/templates/marketplace/payments-layout.hbs b/app/templates/marketplace/payments-layout.hbs index c50b97b79..032c0699a 100644 --- a/app/templates/marketplace/payments-layout.hbs +++ b/app/templates/marketplace/payments-layout.hbs @@ -1,44 +1,37 @@ -{{#view "page-navigations/page-navigation" title="Payments"}} - {{#if view.parentView.showActionButtons}} -
- Debit a card - Credit a bank account +{{#view "page-navigations/page-navigation" title="Orders"}} +
+ Create an order + - {{/if}} +
{{/view}} - +{{/if}} {{yield}} diff --git a/app/templates/marketplace/settings.hbs b/app/templates/marketplace/settings.hbs index 8a57138ce..3efe0f230 100644 --- a/app/templates/marketplace/settings.hbs +++ b/app/templates/marketplace/settings.hbs @@ -16,6 +16,11 @@
{{view "results/embedded-funding-instruments-table" loader=fundingInstrumentsResultsLoader}}
+ +

Internal accounts

+
+ {{view "results/embedded-internal-accounts-table" loader=accountsResultsLoader}} +
diff --git a/app/templates/marketplace/settlements.hbs b/app/templates/marketplace/settlements.hbs new file mode 100644 index 000000000..c03b89ed0 --- /dev/null +++ b/app/templates/marketplace/settlements.hbs @@ -0,0 +1,21 @@ +{{view "page-navigations/page-navigation" title="Settlements"}} + + + +{{#if resultsLoader.results}} +
+
+ {{view "results/transactions-table-grouped-by-settlement" loader=resultsLoader}} +
+
+{{else}} + {{#if resultsLoader.results.isLoaded}} + {{#view "results/intro-page" iconName="settlements" title="Batch payouts with settlements" description="Each customer has a payable account which can carry a stored balance. Credit the funds from multiple orders into your merchant's payable account. Then issue one settlement to your merchant's bank account."}} + Learn more about settlements + {{/view}} + {{else}} +
+ Loading... +
+ {{/if}} +{{/if}} diff --git a/app/templates/marketplaces/add-existing-marketplace.hbs b/app/templates/marketplaces/add-existing-marketplace.hbs index 11fdfe45b..f826df9b2 100644 --- a/app/templates/marketplaces/add-existing-marketplace.hbs +++ b/app/templates/marketplaces/add-existing-marketplace.hbs @@ -1,6 +1,8 @@
{{#if view.isSubmitting}} - + + Adding... + {{else}} {{/if}} diff --git a/app/templates/marketplaces/add-test-marketplace.hbs b/app/templates/marketplaces/add-test-marketplace.hbs index 69812388e..e4e8abc29 100644 --- a/app/templates/marketplaces/add-test-marketplace.hbs +++ b/app/templates/marketplaces/add-test-marketplace.hbs @@ -1,6 +1,8 @@
{{#if view.isSubmitting}} - + + Adding... + {{else}} {{/if}} diff --git a/app/templates/marketplaces/marketplace-link-bar.hbs b/app/templates/marketplaces/marketplace-link-bar.hbs index 246bf8cf5..2d4069396 100644 --- a/app/templates/marketplaces/marketplace-link-bar.hbs +++ b/app/templates/marketplaces/marketplace-link-bar.hbs @@ -1,5 +1,7 @@ {{#if view.isTest}} - × + + + {{/if}}
diff --git a/app/templates/modals/add-funds-modal.hbs b/app/templates/modals/add-funds-modal.hbs index 63c90d23c..c7608dd13 100644 --- a/app/templates/modals/add-funds-modal.hbs +++ b/app/templates/modals/add-funds-modal.hbs @@ -1,4 +1,4 @@ -{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText appearsOnStatementAsMaxLength=view.appearsOnStatementAsMaxLength debitableBankAccounts=view.debitableBankAccounts model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" debitableBankAccounts=view.debitableBankAccounts model=view.model sectionTitle="Payment information" sectionDescription="Funds will appear in your balance in 3-5 business days."}} {{#if view.debitableBankAccounts}} {{view "form-fields/select-form-field" model=view.model @@ -32,12 +32,10 @@ field="dollar_amount" }} - {{view "form-fields/text-form-field" + {{view "form-fields/statement-descriptor-text-form-field" model=view.model - labelText=view.appearsOnStatementAsLabelText field="appears_on_statement_as" - inputClassNames="full" - maxlength=view.appearsOnStatementAsMaxLength + maxlength=view.parentView.appearsOnStatementAsMaxLength }} {{view "form-fields/text-form-field" @@ -47,9 +45,3 @@ field="description" }} {{/view}} - -{{#view "form-fields/form-section" sectionTitle="Note"}} -
-

Funds will appear in your balance in 3-5 business days.

-
-{{/view}} diff --git a/app/templates/modals/bank-account-credit-create-modal.hbs b/app/templates/modals/bank-account-credit-create-modal.hbs index 93862ba37..f338cca7f 100644 --- a/app/templates/modals/bank-account-credit-create-modal.hbs +++ b/app/templates/modals/bank-account-credit-create-modal.hbs @@ -1,4 +1,4 @@ -{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText bankAccountTypes=view.bankAccountTypes model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" bankAccountTypes=view.bankAccountTypes model=view.model sectionTitle="Credit information" sectionDescription="Funds will appear in the bank account by the next business day. First time credits to new accounts may take 3-5 business days."}} {{view "form-fields/text-form-field" model=view.model field="name" labelText="Name on account" inputClassNames="full"}} {{view "form-fields/text-form-field" model=view.model field="routing_number" labelText="Routing number" inputClassNames="full" tooltipTitle="Where is the routing number?" tooltipContent='Bank Account Instructions'}} {{view "form-fields/text-form-field" model=view.model field="account_number" labelText="Account number" inputClassNames="full" tooltipTitle="Where is the account number?" tooltipContent='Bank Account Instructions'}} @@ -16,13 +16,11 @@ {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - {{view "form-fields/text-form-field" model=view.model field="appears_on_statement_as" labelText=view.appearsOnStatementAsLabelText maxlength=view.appearsOnStatementAsMaxLength inputClassNames="full"}} + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength + }} {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" inputClassNames="full"}} {{/view}} - -{{#view "form-fields/form-section" sectionTitle="Note"}} -
-

Funds will appear in the bank account by the next business day. First time credits to new accounts may take 3-5 business days.

-
-{{/view}} diff --git a/app/templates/modals/capture-hold-modal.hbs b/app/templates/modals/capture-hold-modal.hbs index a83077b13..4220831ad 100644 --- a/app/templates/modals/capture-hold-modal.hbs +++ b/app/templates/modals/capture-hold-modal.hbs @@ -1,4 +1,4 @@ -{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText model=view.model sectionTitle="Hold information"}} {{view "form-fields/static-text-form-field" value=view.model.hold.source.customer.display_me labelText="Customer" inputClassNames="full"}} diff --git a/app/templates/modals/card-debit-create-modal.hbs b/app/templates/modals/card-debit-create-modal.hbs index 24f11c0f6..c26e93faf 100644 --- a/app/templates/modals/card-debit-create-modal.hbs +++ b/app/templates/modals/card-debit-create-modal.hbs @@ -1,22 +1,33 @@ -{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText appearsOnStatementAsMaxLength=view.appearsOnStatementAsMaxLength model=view.model sectionTitle="Payment information"}} - {{view "form-fields/text-form-field" model=view.model field="name" labelText="Card holder's name"}} - {{view "form-fields/credit-card-number-form-field" model=view.model field="number" labelText="Card number"}} +{{#view "form-fields/form-section" sectionTitle="Order information" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText appearsOnStatementAsMaxLength=view.appearsOnStatementAsMaxLength model=view.model}} + {{view "form-fields/text-form-field" model=view.model field="order_description" labelText="Internal description" inputClassNames="full"}} - {{view "form-fields/text-form-field" model=view.model field="cvv" labelText="Security code" tooltipTitle="Where is the security code?" tooltipContent='Credit Card Instructions' - }} + {{view "form-fields/text-form-field" model=view.model field="seller_name" labelText="Merchant's name" inputClassNames="full"}} + {{view "form-fields/email-form-field" model=view.model field="seller_email_address" labelText="Merchant's email address" inputClassNames="full"}} +{{/view}} +{{#view "form-fields/form-section" sectionTitle="Buyer information" model=view.model}} + {{view "form-fields/text-form-field" model=view.model field="buyer_name" labelText="Name" inputClassNames="full"}} + {{view "form-fields/email-form-field" model=view.model field="buyer_email_address" labelText="Email address" inputClassNames="full"}} + {{view "form-fields/text-form-field" model=view.model field="name" labelText="Card holder's name" inputClassNames="full"}} + {{view "form-fields/credit-card-number-form-field" model=view.model field="number" labelText="Card number"}} + {{view "form-fields/cvv-form-field" model=view.model field="cvv" labelText="Security code" tooltipTitle="Where is the security code?" tooltipContent='Credit Card Instructions'}} {{view "form-fields/credit-card-expiration-date-form-field" model=view.model field="expiration_date" labelText="Expiration date" name="expiration_date" }} - {{view "form-fields/text-form-field" model=view.model field="postal_code" labelText="Billing zip code" explanationText="Required for American Express cards"}} +{{/view}} +{{#view "form-fields/form-section" sectionTitle="Debit information" model=view.model}} {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - {{view "form-fields/text-form-field" model=view.model field="appears_on_statement_as" labelText=view.appearsOnStatementAsLabelText maxlength=view.appearsOnStatementAsMaxLength inputClassNames="full"}} + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength + }} - {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" inputClassNames="full"}} + {{view "form-fields/text-form-field" model=view.model field="debit_description" labelText="Internal description" inputClassNames="full"}} {{/view}} diff --git a/app/templates/modals/credit-customer-modal.hbs b/app/templates/modals/credit-customer-modal.hbs index 5b8c5d4f0..0c5503ef1 100644 --- a/app/templates/modals/credit-customer-modal.hbs +++ b/app/templates/modals/credit-customer-modal.hbs @@ -1,39 +1,55 @@ -{{#view "form-fields/form-section" appearsOnStatementAsMaxLength=view.appearsOnStatementAsMaxLength appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText fundingInstruments=view.fundingInstruments customer=view.customer model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" sectionTitle="Order information" model=view.model creditableOrders=view.creditableOrders}} + {{view "form-fields/select-form-field" + model=view.model + content=view.creditableOrders + value=view.model.order + labelText="Select one" + optionValuePath="content" + optionLabelPath="content.page_title" + name="order" + field="order" + prompt="Select an order" + }} +{{/view}} - {{view "form-fields/static-text-form-field" labelText="Customer" value=view.customer.display_me_with_email}} +{{#view "form-fields/form-section" fundingInstruments=view.fundingInstruments model=view.model sectionTitle="Payment method"}} - {{#if view.fundingInstruments}} + {{#if view.parentView.isDisplayExistingFundingInstruments}} {{view "form-fields/select-form-field" model=view.model content=view.fundingInstruments value=view.model.destination - labelText="Payment method" + labelText="Select one" optionValuePath="content" optionLabelPath="content.description_with_type" name="destination" field="destination" + explanationText=view.parentView.nameOnAccountText }} {{else}} -
-

This customer doesn't have any payment method to credit.

-
+ {{view "form-fields/text-form-field" model=view.model field="name" labelText="Card holder's name" inputClassNames="full"}} + {{view "form-fields/credit-card-number-form-field" model=view.model field="number" labelText="Card number"}} + {{view "form-fields/cvv-form-field" model=view.model field="cvv" labelText="Security code" tooltipTitle="Where is the security code?" tooltipContent='Credit Card Instructions'}} + {{view "form-fields/credit-card-expiration-date-form-field" + model=view.model + field="expiration_date" + labelText="Expiration date" + name="expiration_date" + }} + {{view "form-fields/text-form-field" model=view.model field="postal_code" labelText="Billing zip code" explanationText="Required for American Express cards"}} {{/if}} +{{/view}} - {{view "form-fields/static-text-form-field" - labelText="Name on account" - value=view.model.destination.name - }} +{{#view "form-fields/form-section" model=view.model sectionTitle="Credit information"}} + {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount" explanationText=view.parentView.orderBalanceText}} - {{view "form-fields/static-text-form-field" - labelText="Bank" - value=view.model.destination.formatted_bank_name + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength }} - {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - - {{view "form-fields/text-form-field" model=view.model field="appears_on_statement_as" labelText=view.appearsOnStatementAsLabelText maxLength=view.appearsOnStatementAsMaxLength inputClassNames="full" maxlength=view.appearsOnStatementAsMaxLength}} - - {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" maxlength=Constants.MAXLENGTH.DESCRIPTION inputClassNames="full"}} + {{view "form-fields/text-form-field" model=view.model field="credit_description" labelText="Internal description" maxlength=Constants.MAXLENGTH.DESCRIPTION inputClassNames="full"}} {{/view}} {{#view "form-fields/form-section" sectionTitle="Note"}} diff --git a/app/templates/modals/credit-funding-instrument.hbs b/app/templates/modals/credit-funding-instrument.hbs index fa44c9058..2eff57f31 100644 --- a/app/templates/modals/credit-funding-instrument.hbs +++ b/app/templates/modals/credit-funding-instrument.hbs @@ -1,19 +1,16 @@ -{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText model=view.model sectionTitle="Payment information"}} - {{view "form-fields/static-text-form-field" value=view.model.destination.title_description field="name" labelText=view.model.destination.type_name inputClassNames="full"}} - - {{view "form-fields/static-text-form-field" value=view.model.destination.name labelText="Name on account" inputClassNames="full"}} - - {{view "form-fields/static-text-form-field" value=view.model.destination.formatted_bank_name emptyText="unknown" labelText="Bank" inputClassNames="full"}} - +{{#view "form-fields/form-section" sectionTitle="Note"}} +
+

One-off credits are reserved for unusual situations. Use orders as the default method to debit your buyers and credit your merchants. Learn more

+
+{{/view}} +{{#view "form-fields/form-section" model=view.model sectionTitle="Credit information" sectionDescription=view.expectedDateText}} {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - {{view "form-fields/text-form-field" model=view.model field="appears_on_statement_as" labelText=view.appearsOnStatementAsLabelText maxlength=view.model.destination.appears_on_statement_max_length inputClassNames="full"}} - - {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} -{{/view}} + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength + }} -{{#view "form-fields/form-section" sectionTitle="Note" model=view.model}} -
-

This credit is expected to appear on {{human-readable-date view.model.destination.expected_credit_date}}.

-
+ {{view "form-fields/text-form-field" model=view.model field="credit_description" labelText="Internal description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} {{/view}} diff --git a/app/templates/modals/credit-order-modal.hbs b/app/templates/modals/credit-order-modal.hbs index ffa810f26..341fd60a1 100644 --- a/app/templates/modals/credit-order-modal.hbs +++ b/app/templates/modals/credit-order-modal.hbs @@ -1,4 +1,4 @@ -{{#view "form-fields/form-section" appearsOnStatementAsMaxLength=view.appearsOnStatementAsMaxLength appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText fundingInstruments=view.fundingInstruments model=view.model sectionTitle="Payment information" sectionDescription="Funds will appear in the bank account by the next business day. First time credits to new accounts may take 3-5 business days."}} +{{#view "form-fields/form-section" fundingInstruments=view.fundingInstruments model=view.model sectionTitle="Payment method"}} {{view "form-fields/static-text-form-field" labelText="From" value=view.parentView.fromText @@ -17,16 +17,22 @@ name="destination" field="destination" explanationText=view.parentView.nameOnAccount + prompt="Select one" }} {{else}}

This customer doesn't have any payment method to credit.

{{/if}} - +{{/view}} +{{#view "form-fields/form-section" model=view.model sectionTitle="Credit information" sectionDescription="Funds will appear in the bank account by the next business day. First time credits to new accounts may take 3-5 business days."}} {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - {{view "form-fields/text-form-field" model=view.model field="appears_on_statement_as" labelText=view.appearsOnStatementAsLabelText maxLength=view.appearsOnStatementAsMaxLength inputClassNames="full" maxlength=view.appearsOnStatementAsMaxLength}} + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength + }} {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" maxlength=Constants.MAXLENGTH.DESCRIPTION inputClassNames="full"}} {{/view}} diff --git a/app/templates/modals/customer-card-create-modal.hbs b/app/templates/modals/customer-card-create-modal.hbs index 36fe9b2f5..042e2304a 100644 --- a/app/templates/modals/customer-card-create-modal.hbs +++ b/app/templates/modals/customer-card-create-modal.hbs @@ -6,14 +6,9 @@ inputClassNames="full" }} - {{view "form-fields/text-form-field" - model=view.model - labelText="Card number" - field="number" - inputClassNames="full" - }} + {{view "form-fields/credit-card-number-form-field" model=view.model field="number" labelText="Card number"}} - {{view "form-fields/text-form-field" + {{view "form-fields/cvv-form-field" model=view.model labelText="Security code" field="cvv" @@ -21,10 +16,9 @@ tooltipContent='Credit Card Instructions' }} - {{view "form-fields/month-year-select-form-field" + {{view "form-fields/credit-card-expiration-date-form-field" model=view.model - monthValue=view.model.expiration_month - yearValue=view.model.expiration_year + field="expiration_date" labelText="Expiration date" name="expiration_date" }} diff --git a/app/templates/modals/debit-customer-modal.hbs b/app/templates/modals/debit-customer-modal.hbs index 10606e8ae..9acb06238 100644 --- a/app/templates/modals/debit-customer-modal.hbs +++ b/app/templates/modals/debit-customer-modal.hbs @@ -1,8 +1,13 @@ -{{#view "form-fields/form-section" appearsOnStatementAsMaxLength=view.appearsOnStatementAsMaxLength appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText fundingInstruments=view.fundingInstruments customer=view.customer model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" sectionTitle="Order information" model=view.model}} + {{view "form-fields/text-form-field" model=view.model field="order_description" labelText="Internal description" inputClassNames="full"}} - {{view "form-fields/static-text-form-field" labelText="Customer" value=view.customer.display_me_with_email}} + {{view "form-fields/text-form-field" model=view.model field="seller_name" labelText="Merchant's name" inputClassNames="full"}} - {{#if view.fundingInstruments}} + {{view "form-fields/email-form-field" model=view.model field="seller_email_address" labelText="Merchant's email address" inputClassNames="full"}} +{{/view}} + +{{#view "form-fields/form-section" fundingInstruments=view.fundingInstruments model=view.model sectionTitle="Payment method"}} + {{#if view.parentView.isDisplayExistingFundingInstruments}} {{view "form-fields/select-form-field" model=view.model content=view.fundingInstruments @@ -13,25 +18,36 @@ name="source" field="source" }} + {{view "form-fields/static-text-form-field" + labelText="Name on account" + value=view.model.source.name + }} + {{view "form-fields/static-text-form-field" + labelText="Bank" + value=view.model.source.formatted_bank_name + }} {{else}} -
-

This customer doesn't have any payment method to debit.

-
+ {{view "form-fields/text-form-field" model=view.model field="name" labelText="Card holder's name" inputClassNames="full"}} + {{view "form-fields/credit-card-number-form-field" model=view.model field="number" labelText="Card number"}} + {{view "form-fields/cvv-form-field" model=view.model field="cvv" labelText="Security code" tooltipTitle="Where is the security code?" tooltipContent='Credit Card Instructions'}} + {{view "form-fields/credit-card-expiration-date-form-field" + model=view.model + field="expiration_date" + labelText="Expiration date" + name="expiration_date" + }} + {{view "form-fields/text-form-field" model=view.model field="postal_code" labelText="Billing zip code" explanationText="Required for American Express cards"}} {{/if}} +{{/view}} - {{view "form-fields/static-text-form-field" - labelText="Name on account" - value=view.model.source.name - }} - - {{view "form-fields/static-text-form-field" - labelText="Bank" - value=view.model.source.formatted_bank_name - }} - +{{#view "form-fields/form-section" sectionTitle="Debit information" model=view.model}} {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - {{view "form-fields/text-form-field" model=view.model field="appears_on_statement_as" labelText=view.appearsOnStatementAsLabelText maxLength=view.appearsOnStatementAsMaxLength inputClassNames="full" maxlength=view.appearsOnStatementAsMaxLength}} + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength + }} - {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} + {{view "form-fields/text-form-field" model=view.model field="debit_description" labelText="Internal description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} {{/view}} diff --git a/app/templates/modals/debit-funding-instrument.hbs b/app/templates/modals/debit-funding-instrument.hbs index 4bd7d0f5f..436879484 100644 --- a/app/templates/modals/debit-funding-instrument.hbs +++ b/app/templates/modals/debit-funding-instrument.hbs @@ -1,13 +1,19 @@ -{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText model=view.model sectionTitle="Payment information"}} - {{view "form-fields/static-text-form-field" value=view.model.source.title_description field="name" labelText=view.model.source.type_name inputClassNames="full"}} +{{#view "form-fields/form-section" sectionTitle="Order information" model=view.model}} + {{view "form-fields/text-form-field" model=view.model field="order_description" labelText="Internal description" inputClassNames="full"}} - {{view "form-fields/static-text-form-field" value=view.model.source.name labelText="Name on account" inputClassNames="full"}} + {{view "form-fields/text-form-field" model=view.model field="seller_name" labelText="Merchant's name" inputClassNames="full"}} - {{view "form-fields/static-text-form-field" value=view.model.source.formatted_bank_name emptyText="unknown" labelText="Bank" inputClassNames="full"}} + {{view "form-fields/email-form-field" model=view.model field="seller_email_address" labelText="Merchant's email address" inputClassNames="full"}} +{{/view}} +{{#view "form-fields/form-section" model=view.model sectionTitle="Debit information"}} {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - {{view "form-fields/text-form-field" model=view.model field="appears_on_statement_as" labelText=view.appearsOnStatementAsLabelText maxlength=view.model.source.appears_on_statement_max_length inputClassNames="full"}} + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength + }} - {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} + {{view "form-fields/text-form-field" model=view.model field="debit_description" labelText="Internal description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} {{/view}} diff --git a/app/templates/modals/debit-order-modal.hbs b/app/templates/modals/debit-order-modal.hbs new file mode 100644 index 000000000..b30a4ef71 --- /dev/null +++ b/app/templates/modals/debit-order-modal.hbs @@ -0,0 +1,26 @@ +{{#view "form-fields/form-section" sectionTitle="Buyer information" model=view.model}} + {{view "form-fields/text-form-field" model=view.model field="buyer_name" labelText="Name" inputClassNames="full"}} + {{view "form-fields/email-form-field" model=view.model field="buyer_email_address" labelText="Email address" inputClassNames="full"}} + {{view "form-fields/text-form-field" model=view.model field="name" labelText="Card holder's name" inputClassNames="full"}} + {{view "form-fields/credit-card-number-form-field" model=view.model field="number" labelText="Card number"}} + {{view "form-fields/cvv-form-field" model=view.model field="cvv" labelText="Security code" tooltipTitle="Where is the security code?" tooltipContent='Credit Card Instructions'}} + {{view "form-fields/credit-card-expiration-date-form-field" + model=view.model + field="expiration_date" + labelText="Expiration date" + name="expiration_date" + }} + {{view "form-fields/text-form-field" model=view.model field="postal_code" labelText="Billing zip code" explanationText="Required for American Express cards"}} +{{/view}} + +{{#view "form-fields/form-section" sectionTitle="Debit information" model=view.model}} + {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} + + {{view "form-fields/statement-descriptor-text-form-field" + model=view.model + field="appears_on_statement_as" + maxlength=view.parentView.appearsOnStatementAsMaxLength + }} + + {{view "form-fields/text-form-field" model=view.model field="debit_description" labelText="Internal description" inputClassNames="full"}} +{{/view}} diff --git a/app/templates/modals/evidence-portal-modal.hbs b/app/templates/modals/evidence-portal-modal.hbs index 80861712a..e4fda356c 100644 --- a/app/templates/modals/evidence-portal-modal.hbs +++ b/app/templates/modals/evidence-portal-modal.hbs @@ -23,8 +23,8 @@ {{format-file-size doc.file_size}} {{#if doc.isUploading}} -
- +
+ Uploading...
{{else}} diff --git a/app/templates/modals/hold-card-modal.hbs b/app/templates/modals/hold-card-modal.hbs index 5b697fbb5..75ac3264e 100644 --- a/app/templates/modals/hold-card-modal.hbs +++ b/app/templates/modals/hold-card-modal.hbs @@ -1,12 +1,14 @@ -{{#view "form-fields/form-section" appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText model=view.model sectionTitle="Payment information"}} - {{view "form-fields/static-text-form-field" value=view.model.source.title_description labelText=view.model.source.type_name inputClassNames="full"}} +{{#view "form-fields/form-section" sectionTitle="Order information" model=view.model}} + {{view "form-fields/text-form-field" model=view.model field="order_description" labelText="Internal description" inputClassNames="full"}} - {{view "form-fields/static-text-form-field" value=view.model.source.name labelText="Name on account" inputClassNames="full"}} + {{view "form-fields/text-form-field" model=view.model field="seller_name" labelText="Merchant's name" inputClassNames="full"}} - {{view "form-fields/static-text-form-field" value=view.model.source.formatted_bank_name emptyText="unknown" labelText="Bank" inputClassNames="full"}} + {{view "form-fields/email-form-field" model=view.model field="seller_email_address" labelText="Merchant's email address" inputClassNames="full"}} +{{/view}} +{{#view "form-fields/form-section" model=view.model sectionTitle="Hold information"}} {{view "form-fields/amount-form-field" model=view.model field="dollar_amount" labelText="Amount"}} - {{view "form-fields/text-form-field" model=view.model field="description" labelText="Internal description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} + {{view "form-fields/text-form-field" model=view.model field="description" labelText="Hold description" inputClassNames="full" maxlength=Constants.MAXLENGTH.DESCRIPTION}} {{/view}} diff --git a/app/templates/modals/refund-debit-modal.hbs b/app/templates/modals/refund-debit-modal.hbs index 43227fc2c..01051363f 100644 --- a/app/templates/modals/refund-debit-modal.hbs +++ b/app/templates/modals/refund-debit-modal.hbs @@ -1,4 +1,4 @@ -{{#view "form-fields/form-section" recipientLabel=view.recipientLabel recipientDisplay=view.recipientDisplay model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" recipientLabel=view.recipientLabel recipientDisplay=view.recipientDisplay model=view.model sectionTitle="Refund information"}} {{view "form-fields/static-text-form-field" labelText=view.recipientLabel value=view.recipientDisplay diff --git a/app/templates/modals/reverse-credit-modal.hbs b/app/templates/modals/reverse-credit-modal.hbs index e52129125..6ae5b8e32 100644 --- a/app/templates/modals/reverse-credit-modal.hbs +++ b/app/templates/modals/reverse-credit-modal.hbs @@ -1,4 +1,4 @@ -{{#view "form-fields/form-section" model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" model=view.model sectionTitle="Reversal information"}} {{view "form-fields/static-text-form-field" labelText="Customer" value=view.parentView.customerDisplay diff --git a/app/templates/modals/search-modal.hbs b/app/templates/modals/search-modal.hbs index f67a560cc..584b3fd2f 100644 --- a/app/templates/modals/search-modal.hbs +++ b/app/templates/modals/search-modal.hbs @@ -1,11 +1,7 @@
diff --git a/app/templates/modals/withdraw-funds-modal.hbs b/app/templates/modals/withdraw-funds-modal.hbs index 6123ea011..aaddfc96d 100644 --- a/app/templates/modals/withdraw-funds-modal.hbs +++ b/app/templates/modals/withdraw-funds-modal.hbs @@ -1,4 +1,4 @@ -{{#view "form-fields/form-section" availableBalance=view.availableBalance appearsOnStatementAsLabelText=view.appearsOnStatementAsLabelText appearsOnStatementAsMaxLength=view.appearsOnStatementAsMaxLength bankAccounts=view.bankAccounts model=view.model sectionTitle="Payment information"}} +{{#view "form-fields/form-section" availableBalance=view.availableBalance bankAccounts=view.bankAccounts model=view.model sectionTitle="Payment information"}} {{#if view.bankAccounts}} {{view "form-fields/select-form-field" model=view.model @@ -35,11 +35,10 @@ explanationText=view.availableBalance }} - {{view "form-fields/text-form-field" + {{view "form-fields/statement-descriptor-text-form-field" model=view.model - labelText=view.appearsOnStatementAsLabelText field="appears_on_statement_as" - maxlength=view.appearsOnStatementAsMaxLength + maxlength=view.parentView.appearsOnStatementAsMaxLength }} {{view "form-fields/text-form-field" diff --git a/app/templates/orders.hbs b/app/templates/orders.hbs index 226841faa..b8d6522df 100644 --- a/app/templates/orders.hbs +++ b/app/templates/orders.hbs @@ -1,5 +1,6 @@ {{#view "page-navigations/page-navigation" pageType=model.type_name title=model.page_title}} {{/view}} @@ -13,18 +14,18 @@ {{view "meta-list" type=model}} {{/view}} - {{#view "detail-views/order-main-panel" model=model}} + {{#view "detail-views/main-panel" model=model}} {{view "detail-views/tabs/order-tab" model=model}} {{#if view.isActivityTabSelected}}

Charges

- {{view "results/order-transactions-table" loader=debitsResultsLoader customers=model.buyers}} + {{view "results/transactions-table-grouped-by-customer" loader=debitsResultsLoader paymentMethodText="From" customers=model.buyers}}

Payouts

- {{view "results/order-transactions-table" loader=creditsResultsLoader customers=model.seller}} + {{view "results/transactions-table-grouped-by-customer" loader=creditsResultsLoader paymentMethodText="To" customers=model.seller}}
{{/if}} diff --git a/app/templates/results/accounts-table.hbs b/app/templates/results/accounts-table.hbs new file mode 100644 index 000000000..a7a49d40e --- /dev/null +++ b/app/templates/results/accounts-table.hbs @@ -0,0 +1,70 @@ + + + + {{#if view.isSearch}} + {{#view "results/search-results-loader-sort-column-header" resultsLoader=view.loader field="created_at" actionName="changeSortOrder"}} + Date + {{/view}} + {{else}} + {{#view "results/results-loader-sort-column-header" resultsLoader=view.loader field="created_at" actionName="changeSortOrder"}} + Date + {{/view}} + {{/if}} + + Payment method + Customer + Balance + + + +{{#if view.loader.results.hasNextPage}} + {{view "results/results-load-more" results=view.loader.results columns=6}} +{{/if}} + + + {{#each funding_instrument in view.loader.results}} + + + {{#link-to funding_instrument.routeName funding_instrument}} + + {{/link-to}} + + + {{#link-to funding_instrument.routeName funding_instrument}} + {{funding_instrument.id}} + {{sentence-case funding_instrument.type}} account + {{/link-to}} + + + {{#link-to funding_instrument.routeName funding_instrument}} + {{#if funding_instrument.customer.display_me}} + {{funding_instrument.customer.display_me}} + {{#if funding_instrument.customer.email}} + {{funding_instrument.customer.email}} + {{/if}} + {{else}} + none + {{/if}} + {{/link-to}} + + + {{#link-to funding_instrument.routeName funding_instrument}} + {{format-currency funding_instrument.balance}} + {{/link-to}} + + + {{else}} + + + {{#if view.loader.results.isLoaded}} + No internal accounts + {{else}} + Loading... + {{/if}} + + + {{/each}} + diff --git a/app/templates/results/associated-transactions-table.hbs b/app/templates/results/associated-transactions-table.hbs index 3ea93f753..44a1f4b3f 100644 --- a/app/templates/results/associated-transactions-table.hbs +++ b/app/templates/results/associated-transactions-table.hbs @@ -1,12 +1,15 @@ - + Transaction - - Payment method + + From - + + To + + Amount @@ -15,51 +18,59 @@ {{#if view.transaction}} - - {{#view "results/grouped-transactions-table"}} + + {{#view "results/grouped-transactions-table" hideCustomerColumn=true}} + {{#if view.parentView.transaction.order}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.order hideCustomerColumn=true}} + {{/if}} + {{#if view.parentView.transaction.dispute}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction.dispute}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.dispute hideCustomerColumn=true}} {{/if}} {{#if view.parentView.transaction.refunds}} {{#each refund in view.parentView.transaction.refunds}} - {{view "results/grouped-transaction-row" item=refund}} + {{view "results/grouped-transaction-row" item=refund hideCustomerColumn=true}} {{/each}} {{/if}} {{#if view.parentView.transaction.reversals}} {{#each reversal in view.parentView.transaction.reversals}} - {{view "results/grouped-transaction-row" item=reversal}} + {{view "results/grouped-transaction-row" item=reversal hideCustomerColumn=true}} {{/each}} {{/if}} + {{#if view.parentView.transaction.settlement}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.settlement hideCustomerColumn=true}} + {{/if}} + {{#if view.parentView.transaction.debit}} {{#if view.parentView.transaction.debit.dispute}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction.debit.dispute}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.debit.dispute hideCustomerColumn=true}} {{/if}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction.debit}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.debit hideCustomerColumn=true}} {{/if}} {{#if view.parentView.transaction.credit}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction.credit}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.credit hideCustomerColumn=true}} {{/if}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction class="current"}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction class="current" hideCustomerColumn=true}} {{#if view.parentView.transaction.transaction}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction.transaction}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction.transaction.hold}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.transaction hideCustomerColumn=true}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.transaction.hold hideCustomerColumn=true}} {{/if}} {{#if view.parentView.transaction.hold}} - {{view "results/grouped-transaction-row" item=view.parentView.transaction.hold}} + {{view "results/grouped-transaction-row" item=view.parentView.transaction.hold hideCustomerColumn=true}} {{/if}} {{/view}} {{else}} - + {{#if view.transaction.isLoaded}} No transactions {{else}} diff --git a/app/templates/results/embedded-internal-accounts-table.hbs b/app/templates/results/embedded-internal-accounts-table.hbs new file mode 100644 index 000000000..06a14814d --- /dev/null +++ b/app/templates/results/embedded-internal-accounts-table.hbs @@ -0,0 +1,42 @@ + + + + {{view "results/payment-methods-results-dropdown-filter" model=view.loader}} + + Balance + + + +{{#if view.loader.results.hasNextPage}} + {{view "results/results-load-more" results=view.loader.results columns=5}} +{{/if}} + + + {{#each account in view.loader.results}} + + + {{#link-to account.routeName account}} + + + {{sentence-case account.type}} account + + {{/link-to}} + + + {{#link-to account.routeName account}} + {{format-currency account.balance}} + {{/link-to}} + + + {{else}} + + + {{#if view.loader.results.isLoaded}} + No internal accounts + {{else}} + Loading... + {{/if}} + + + {{/each}} + diff --git a/app/templates/results/embedded-transactions-table.hbs b/app/templates/results/embedded-transactions-table.hbs deleted file mode 100644 index 9dafbe8be..000000000 --- a/app/templates/results/embedded-transactions-table.hbs +++ /dev/null @@ -1,66 +0,0 @@ - - - - Transaction - - - Payment method - - - Amount - - - -{{#if view.loader.results.hasNextPage}} - {{view "results/results-load-more" results=view.loader.results columns=3}} -{{/if}} - - - {{#each transaction in view.filteredResults}} - - - {{#view "results/grouped-transactions-table"}} - {{#if transaction.order}} - {{view "results/grouped-order-row" item=transaction.order}} - {{/if}} - - {{#if transaction.dispute}} - {{view "results/grouped-transaction-row" item=transaction.dispute}} - {{/if}} - - {{#if transaction.refunds}} - {{#each refund in transaction.refunds}} - {{view "results/grouped-transaction-row" item=refund}} - {{/each}} - {{/if}} - - {{#if transaction.reversals}} - {{#each reversal in transaction.reversals}} - {{view "results/grouped-transaction-row" item=reversal}} - {{/each}} - {{/if}} - - {{view "results/grouped-transaction-row" item=transaction}} - - {{#if transaction.transaction}} - {{view "results/grouped-transaction-row" item=transaction.transaction}} - {{/if}} - - {{#if transaction.hold}} - {{view "results/grouped-transaction-row" item=transaction.hold}} - {{/if}} - {{/view}} - - - {{else}} - - - {{#if view.loader.results.isLoaded}} - No transactions - {{else}} - Loading... - {{/if}} - - - {{/each}} - diff --git a/app/templates/results/grouped-results-dropdown-filter.hbs b/app/templates/results/grouped-results-dropdown-filter.hbs new file mode 100644 index 000000000..8a372d923 --- /dev/null +++ b/app/templates/results/grouped-results-dropdown-filter.hbs @@ -0,0 +1,16 @@ + + {{view.toggleText}} + + + diff --git a/app/templates/results/grouped-transaction-row.hbs b/app/templates/results/grouped-transaction-row.hbs index 90b64d887..aca0e078a 100644 --- a/app/templates/results/grouped-transaction-row.hbs +++ b/app/templates/results/grouped-transaction-row.hbs @@ -1,18 +1,38 @@ - + {{#link-to view.routeName view.item}} {{view.displayValue}} {{/link-to}} - - {{#link-to view.routeName view.item}} - - {{view.paymentMethodText}} - - {{/link-to}} - - +{{#unless view.hideCustomerColumn}} + + {{#link-to view.routeName view.item}} + + {{view.customerText}} + + {{/link-to}} + +{{/unless}} +{{#unless view.hideFromColumn}} + + {{#link-to view.routeName view.item}} + + {{view.paymentMethodFromText}} + + {{/link-to}} + +{{/unless}} +{{#unless view.hideToColumn}} + + {{#link-to view.routeName view.item}} + + {{view.paymentMethodToText}} + + {{/link-to}} + +{{/unless}} + {{#link-to view.routeName view.item}} {{view.amountText}} diff --git a/app/templates/results/grouped-transactions-inner-table-layout.hbs b/app/templates/results/grouped-transactions-inner-table-layout.hbs new file mode 100644 index 000000000..e0e7dae85 --- /dev/null +++ b/app/templates/results/grouped-transactions-inner-table-layout.hbs @@ -0,0 +1,69 @@ + + + + {{#unless view.hideCustomerColumn}} + + {{/unless}} + {{#unless view.hideFromColumn}} + + {{/unless}} + {{#unless view.hideToColumn}} + + {{/unless}} + + + + + {{yield}} + + {{#if view.transactions}} + {{#each transaction in view.transactions}} + {{#if transaction.dispute}} + {{view "results/grouped-transaction-row" + item=transaction.dispute + hideCustomerColumn=view.hideCustomerColumn + hideFromColumn=view.hideFromColumn + hideToColumn=view.hideToColumn + }} + {{/if}} + + {{#if transaction.refunds}} + {{#each refund in transaction.refunds}} + {{view "results/grouped-transaction-row" + item=refund + hideCustomerColumn=view.hideCustomerColumn + hideFromColumn=view.hideFromColumn + hideToColumn=view.hideToColumn + }} + {{/each}} + {{/if}} + + {{#if transaction.reversals}} + {{#each reversal in transaction.reversals}} + {{view "results/grouped-transaction-row" + item=reversal + hideCustomerColumn=view.hideCustomerColumn + hideFromColumn=view.hideFromColumn + hideToColumn=view.hideToColumn + }} + {{/each}} + {{/if}} + + {{view "results/grouped-transaction-row" + item=transaction + hideCustomerColumn=view.hideCustomerColumn + hideFromColumn=view.hideFromColumn + hideToColumn=view.hideToColumn + }} + + {{#if transaction.hold}} + {{view "results/grouped-transaction-row" + item=transaction.hold + hideCustomerColumn=view.hideCustomerColumn + hideFromColumn=view.hideFromColumn + hideToColumn=view.hideToColumn + }} + {{/if}} + {{/each}} + {{/if}} + diff --git a/app/templates/results/grouped-transactions-table-layout.hbs b/app/templates/results/grouped-transactions-table-layout.hbs index f5788cc73..9b39422d5 100644 --- a/app/templates/results/grouped-transactions-table-layout.hbs +++ b/app/templates/results/grouped-transactions-table-layout.hbs @@ -1,3 +1,20 @@ + + + + {{view "results/orders-results-dropdown-filter" isSearch=view.isSearch}} + + {{#unless view.hideCustomerColumn}} + Customer + {{/unless}} + From + To + Amount + + +{{#if view.loader.results.hasNextPage}} + {{view "results/results-load-more" results=view.loader.results columns=view.colspan}} +{{/if}} + {{yield}} diff --git a/app/templates/results/intro-page.hbs b/app/templates/results/intro-page.hbs new file mode 100644 index 000000000..1f434b8d1 --- /dev/null +++ b/app/templates/results/intro-page.hbs @@ -0,0 +1,4 @@ + +

{{view.title}}

+

{{view.description}}

+{{yield}} diff --git a/app/templates/results/order-transactions-table.hbs b/app/templates/results/order-transactions-table.hbs deleted file mode 100644 index e4c2653c4..000000000 --- a/app/templates/results/order-transactions-table.hbs +++ /dev/null @@ -1,66 +0,0 @@ - - - Customer - Transaction - - Payment method - - - Amount - - - -{{#if view.loader.results.hasNextPage}} - {{view "results/results-load-more" results=view.loader.results columns=4}} -{{/if}} - - - {{#each customer_group in view.groupedResults}} - - {{view "tables/cells/linked-two-lines-cell" - item=customer_group.customer - routeName=customer_group.customer.route_name - primaryLabelText=customer_group.customer.display_me - secondaryLabelText=customer_group.customer.email - title=customer_group.customer.display_me_with_email - class="customer-group two-lines" - }} - - {{#each transaction in customer_group.transactions}} - {{#view "results/grouped-transactions-table"}} - {{#if transaction.dispute}} - {{view "results/grouped-transaction-row" item=transaction.dispute}} - {{/if}} - - {{view "results/grouped-transaction-row" item=transaction}} - - {{#if transaction.hold}} - {{view "results/grouped-transaction-row" item=transaction.hold}} - {{/if}} - - {{#if transaction.refunds}} - {{#each refund in transaction.refunds}} - {{view "results/grouped-transaction-row" item=refund}} - {{/each}} - {{/if}} - - {{#if transaction.reversals}} - {{#each reversal in transaction.reversals}} - {{view "results/grouped-transaction-row" item=reversal}} - {{/each}} - {{/if}} - {{/view}} - {{/each}} - - {{else}} - - - {{#if view.loader.results.isLoaded}} - No transactions - {{else}} - Loading... - {{/if}} - - - {{/each}} - diff --git a/app/templates/results/orders-table.hbs b/app/templates/results/orders-table.hbs deleted file mode 100644 index 45bb7b763..000000000 --- a/app/templates/results/orders-table.hbs +++ /dev/null @@ -1,34 +0,0 @@ -
- {{#each order in view.loader.results}} - {{view "tables/cells/linked-card" - routeName=order.route_name - dateLabel="Created at" - date=order.created_at - title=order.page_title - amountLabel="Balance" - amount=order.escrow_balance - item=order - }} - {{else}} - {{#if view.loader.results.isLoaded}} -
- - No orders -
- {{else}} -
- Loading... -
- {{/if}} - {{/each}} -
- -{{#if view.loader.results.loadingNextPage}} -
- -
-{{else}} {{#if view.loader.results.hasNextPage}} - - {{view "results/results-load-more" results=view.loader.results}} -
-{{/if}} {{/if}} diff --git a/app/templates/results/results-filters-header.hbs b/app/templates/results/results-filters-header.hbs deleted file mode 100644 index 3b9a13596..000000000 --- a/app/templates/results/results-filters-header.hbs +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/app/templates/results/results-load-more.hbs b/app/templates/results/results-load-more.hbs index b3dd77fc2..ef131ecb6 100644 --- a/app/templates/results/results-load-more.hbs +++ b/app/templates/results/results-load-more.hbs @@ -1,8 +1,8 @@ {{#if view.results.loadingNextPage}} -
- +
+ Loading...
{{else}} {{#if view.results.hasNextPage}} Load more diff --git a/app/templates/results/transactions-table-grouped-by-customer.hbs b/app/templates/results/transactions-table-grouped-by-customer.hbs new file mode 100644 index 000000000..28316e2a7 --- /dev/null +++ b/app/templates/results/transactions-table-grouped-by-customer.hbs @@ -0,0 +1,75 @@ + + + Customer + Transaction + + {{view.paymentMethodText}} + + + Amount + + + +{{#if view.loader.results.hasNextPage}} + {{view "results/results-load-more" results=view.loader.results columns=view.colspan}} +{{/if}} + + + {{#each customer_group in view.groupedResults}} + + {{view "tables/cells/linked-two-lines-cell" + item=customer_group.customer + routeName=customer_group.customer.route_name + primaryLabelText=customer_group.customer.display_me + secondaryLabelText=customer_group.customer.email + title=customer_group.customer.display_me_with_email + class="customer-group two-lines" + }} + + {{#each transaction in customer_group.transactions}} + {{#view "results/grouped-transactions-table" hideCustomerColumn=view.hideCustomerColumn hideFromColumn=view.hideFromColumn hideToColumn=view.hideToColumn}} + {{#if transaction.dispute}} + {{view "results/grouped-transaction-row" item=transaction.dispute hideCustomerColumn=view.hideCustomerColumn hideFromColumn=view.hideFromColumn hideToColumn=view.hideToColumn}} + {{/if}} + + {{#if transaction.refunds}} + {{#each refund in transaction.refunds}} + {{view "results/grouped-transaction-row" item=refund hideCustomerColumn=view.hideCustomerColumn hideFromColumn=view.hideFromColumn hideToColumn=view.hideToColumn}} + {{/each}} + {{/if}} + + {{#if transaction.reversals}} + {{#each reversal in transaction.reversals}} + {{view "results/grouped-transaction-row" item=reversal hideCustomerColumn=view.hideCustomerColumn hideFromColumn=view.hideFromColumn hideToColumn=view.hideToColumn}} + {{/each}} + {{/if}} + + {{#if transaction.settlement}} + {{view "results/grouped-transaction-row" + item=transaction.settlement + hideCustomerColumn=view.hideCustomerColumn + hideFromColumn=view.hideFromColumn + hideToColumn=view.hideToColumn + }} + {{/if}} + + {{view "results/grouped-transaction-row" item=transaction hideCustomerColumn=view.hideCustomerColumn hideFromColumn=view.hideFromColumn hideToColumn=view.hideToColumn}} + + {{#if transaction.hold}} + {{view "results/grouped-transaction-row" item=transaction.hold hideCustomerColumn=view.hideCustomerColumn hideFromColumn=view.hideFromColumn hideToColumn=view.hideToColumn}} + {{/if}} + {{/view}} + {{/each}} + + {{else}} + + + {{#if view.loader.results.isLoaded}} + No transactions + {{else}} + Loading... + {{/if}} + + + {{/each}} + diff --git a/app/templates/results/transactions-table-grouped-by-order.hbs b/app/templates/results/transactions-table-grouped-by-order.hbs new file mode 100644 index 000000000..fa25eda47 --- /dev/null +++ b/app/templates/results/transactions-table-grouped-by-order.hbs @@ -0,0 +1,27 @@ +{{#each orderGroup in view.groupedResults}} + + {{#if orderGroup.order}} + + {{#view "results/grouped-transactions-table" hideCustomerColumn=view.hideCustomerColumn}} + {{view "results/grouped-transaction-row" item=orderGroup.order hideCustomerColumn=view.hideCustomerColumn}} + {{/view}} + + {{view "results/grouped-transactions-table" transactions=orderGroup.transactions hideCustomerColumn=view.hideCustomerColumn}} + + {{else}} + + {{view "results/grouped-transactions-table" transactions=orderGroup.transactions hideCustomerColumn=view.hideCustomerColumn}} + + {{/if}} + +{{else}} + + + {{#if view.loader.results.isLoaded}} + No transactions + {{else}} + Loading... + {{/if}} + + +{{/each}} diff --git a/app/templates/results/transactions-table-grouped-by-settlement.hbs b/app/templates/results/transactions-table-grouped-by-settlement.hbs new file mode 100644 index 000000000..c4cb4fb86 --- /dev/null +++ b/app/templates/results/transactions-table-grouped-by-settlement.hbs @@ -0,0 +1,23 @@ +{{#each settlementGroup in view.groupedResults}} + + + {{#view "results/grouped-transactions-table" hideCustomerColumn=view.hideCustomerColumn}} + {{view "results/grouped-transaction-row" item=settlementGroup.settlement hideCustomerColumn=view.hideCustomerColumn}} + {{/view}} + {{#each orderGroup in settlementGroup.orderGroups}} + {{#view "results/grouped-transactions-table" transactions=orderGroup.transactions hideCustomerColumn=view.hideCustomerColumn}} + {{view "results/grouped-transaction-row" item=orderGroup.order hideCustomerColumn=view.hideCustomerColumn}} + {{/view}} + {{/each}} + +{{else}} + + + {{#if view.loader.results.isLoaded}} + No transactions + {{else}} + Loading... + {{/if}} + + +{{/each}} diff --git a/app/templates/settlement.hbs b/app/templates/settlement.hbs new file mode 100644 index 000000000..6382472fb --- /dev/null +++ b/app/templates/settlement.hbs @@ -0,0 +1,25 @@ +{{view "page-navigations/page-navigation" pageType=model.type_name title=model.amountInDollars}} + +{{#view "detail-views/body-panel"}} + {{#view "detail-views/api-model-panel" model=model}} + {{view detail-views/summary-sections/settlement-summary-section model=model}} + {{view detail-views/description-lists/transaction-titled-key-values-section model=model}} + {{view "meta-list" type=model}} + {{/view}} + + {{#view "detail-views/main-panel"}} +

Settled transactions

+
+ {{view "results/transactions-table-grouped-by-order" loader=creditsResultsLoader hideCustomerColumn=true}} +
+

Logs

+
+ {{view "resource-logs" content=model}} +
+ +

Events

+
+ {{view "resource-events" content=model}} +
+ {{/view}} +{{/view}} diff --git a/app/templates/sidebar/basic-link-sidebar-item.hbs b/app/templates/sidebar/basic-link-sidebar-item.hbs index 4d0bcc203..9270a40ed 100644 --- a/app/templates/sidebar/basic-link-sidebar-item.hbs +++ b/app/templates/sidebar/basic-link-sidebar-item.hbs @@ -5,7 +5,7 @@ {{view.linkText}} {{#if view.alertCount}} -
{{view.alertCount}}
+
{{view.alertCount}}
{{/if}} {{/link-to}} diff --git a/app/utils/bk-utils.coffee b/app/utils/bk-utils.coffee new file mode 100644 index 000000000..1e4db70d4 --- /dev/null +++ b/app/utils/bk-utils.coffee @@ -0,0 +1,5 @@ +BkUtils = + generateToLegacyModelMethod: (klassName) -> + return -> + BalancedApp.__container__.lookupFactory("model:#{klassName}").find(@get("href")) +`export default BkUtils;` diff --git a/app/utils/constants.js b/app/utils/constants.js index f340fec3f..eb557665b 100644 --- a/app/utils/constants.js +++ b/app/utils/constants.js @@ -12,6 +12,8 @@ Constants.KEYS = { ESCAPE: 27 }; +Constants.EXPIRATION_DATE_FORMAT = /^(\d\d) [\/-] (\d\d\d\d)$/; + Constants.BANK_ACCOUNT_TYPES = [{ label: 'Checking', value: 'checking' @@ -23,9 +25,13 @@ Constants.BANK_ACCOUNT_TYPES = [{ Constants.SEARCH = { CATEGORIES: ['order', 'transaction', 'search', 'customer', 'funding_instrument', 'dispute'], SEARCH_TYPES: ['debit', 'credit', 'card_hold', 'refund', "reversal"], - TRANSACTION_TYPES: ['debit', 'credit', 'hold', 'refund'], + ORDER_TRANSACTION_TYPES: ['debit', 'credit', 'card_hold', 'refund', "reversal", 'order'], FUNDING_INSTRUMENT_TYPES: ['bank_account', 'card'], - DISPUTE_TYPES: ['pending', 'won', 'lost', 'arbitration'] + DISPUTE_TYPES: ['pending', 'won', 'lost', 'arbitration'], + CUSTOMER_TYPES: ["customer"], + ORDER_TYPES: ["order"], + SETTLEMENT_TYPES: ['settlement'], + ACCOUNT_TYPES: ['account'], }; // time in ms to throttle between key presses for search diff --git a/app/utils/legacy-results-loader-wrapper.coffee b/app/utils/legacy-results-loader-wrapper.coffee new file mode 100644 index 000000000..5e6bb0e23 --- /dev/null +++ b/app/utils/legacy-results-loader-wrapper.coffee @@ -0,0 +1,28 @@ +`import Ember from "ember";` + +LegacyResultsLoaderWrapper = Ember.Object.extend( + results: Ember.computed.reads("collection") + isLoading: Ember.computed.reads("collection.isLoading") + + loadNextPage: -> + @get("collection").loadNextPage() +) + +LegacyResultsLoaderWrapper.reopenClass( + generateMethod: (methodName) -> + return (attributes) -> + LegacyResultsLoaderWrapper.createForCollection(=> + return @[methodName](attributes || {}) + ) + + createForCollection: (initializer) -> + loader = @create(collection: []) + if Ember.isArray(initializer) + loader.set("collection", initializer) + else + initializer().then (collection) -> + loader.set("collection", collection) + loader +) + +`export default LegacyResultsLoaderWrapper;` diff --git a/app/utils/validation-helpers.js b/app/utils/validation-helpers.js index 8d96d0c1e..60228dc6b 100644 --- a/app/utils/validation-helpers.js +++ b/app/utils/validation-helpers.js @@ -45,6 +45,23 @@ var generateTransactionAppearsOnStatementAsValidation = function(maxLength) { }); }; +var validateAppearsOnStatementAs = function(propertyName, factory, maxLength) { + var validationErrors = factory.get("validationErrors"); + var value = factory.get(propertyName) || ""; + + if (maxLength < value.length) { + validationErrors.add(propertyName, "format", null, "must be under %@ characters".fmt(maxLength + 1)); + } + + var invalidCharacters = Transaction.findAppearsOnStatementAsInvalidCharacters(value); + + if (invalidCharacters.length === 1) { + validationErrors.add(propertyName, "format", null, '"%@" is an invalid character'.fmt(invalidCharacters)); + } else if (invalidCharacters.length > 1) { + validationErrors.add(propertyName, "format", null, '"%@" are invalid characters'.fmt(invalidCharacters)); + } +}; + var ValidationHelpers = Ember.Namespace.create({ phoneNumberValidator: function(object, attribute, value) { var stripped = $.trim(value).replace(/[\d- ()+]/g, ""); @@ -67,7 +84,7 @@ var ValidationHelpers = Ember.Namespace.create({ }) }, - generateTransactionAppearsOnStatementFormatValidation: generateTransactionAppearsOnStatementAsValidation, + fundingInstrumentAppearsOnStatementAsValidator: validateAppearsOnStatementAs, bankTransactionAppearsOnStatementAs: { presence: true, diff --git a/app/views/detail-views/description-lists/account-titled-key-values-section.js b/app/views/detail-views/description-lists/account-titled-key-values-section.js new file mode 100644 index 000000000..7d8a8d8e9 --- /dev/null +++ b/app/views/detail-views/description-lists/account-titled-key-values-section.js @@ -0,0 +1,13 @@ +import TitledKeyValuesSectionView from "./titled-key-values-section"; +import ListValueGenerator from "./list-value-generator"; + +var AccountTitledKeyValuesSectionView = TitledKeyValuesSectionView.extend({ + title: "Account information", + + keyValueListViews: ListValueGenerator.create() + .add("Created at", "created_at") + .add("Account ID", "id") + .toProperty() +}); + +export default AccountTitledKeyValuesSectionView; diff --git a/app/views/detail-views/list-sections/list-section.js b/app/views/detail-views/list-sections/list-section.js deleted file mode 100644 index d7dc5cd47..000000000 --- a/app/views/detail-views/list-sections/list-section.js +++ /dev/null @@ -1,7 +0,0 @@ -import SummarySectionView from "../summary-sections/summary-section"; - -var ListSectionView = SummarySectionView.extend({ - templateName: "detail-views/list-section", -}); - -export default ListSectionView; diff --git a/app/views/detail-views/main-panel.js b/app/views/detail-views/main-panel.js index 0fc1e9e26..f47e6f8d8 100644 --- a/app/views/detail-views/main-panel.js +++ b/app/views/detail-views/main-panel.js @@ -1,7 +1,8 @@ import Ember from "ember"; var MainPanelView = Ember.View.extend({ - classNameBindings: [":main-panel", ":span"] + classNameBindings: [":main-panel", ":span"], + isActivityTabSelected: true }); export default MainPanelView; diff --git a/app/views/detail-views/order-main-panel.js b/app/views/detail-views/order-main-panel.js deleted file mode 100644 index 34dcaa3ed..000000000 --- a/app/views/detail-views/order-main-panel.js +++ /dev/null @@ -1,8 +0,0 @@ -import Ember from "ember"; -import MainPanelView from "./main-panel"; - -var OrderMainPanelView = MainPanelView.extend({ - isActivityTabSelected: true -}); - -export default OrderMainPanelView; diff --git a/app/views/detail-views/resource-summaries/resource-summary-base.coffee b/app/views/detail-views/resource-summaries/resource-summary-base.coffee index 6f89cf991..13eeb113b 100644 --- a/app/views/detail-views/resource-summaries/resource-summary-base.coffee +++ b/app/views/detail-views/resource-summaries/resource-summary-base.coffee @@ -17,11 +17,17 @@ ResourceSummaryBase = Ember.View.extend( model.get("display_me") else if @isType("card") "#{model.get('last_four')} #{model.get('brand')}" - else if @isType("bank-account") + else if @isType("bank-account") || @isType("bk/bank-account") "#{model.get('last_four')} #{model.get('formatted_bank_name')}" + else if @isType("bk/account") || @isType("account") + currentRoute = @container.lookup("controller:application").get('currentRouteName') + if currentRoute == "settlement" + "Payable account" + else + "Balance: $%@".fmt(Utils.centsToDollars(model.get('balance'))) else null - ).property("model.amount_escrowed", "model.amount", "model.display_me", "model.last_four", "model.brand", "model.formatted_bank_name") + ).property("model.amount_escrowed", "model.amount", "model.display_me", "model.last_four", "model.brand", "model.formatted_bank_name", "model.balance") hoverValue: (-> model = @get("model") diff --git a/app/views/detail-views/summary-items/account.coffee b/app/views/detail-views/summary-items/account.coffee new file mode 100644 index 000000000..3c0910197 --- /dev/null +++ b/app/views/detail-views/summary-items/account.coffee @@ -0,0 +1,13 @@ +`import FundingInstrument from "./funding-instrument";` +`import Util from "balanced-dashboard/lib/utils";` + +Account = FundingInstrument.extend( + text: Ember.computed "model.balance", -> + Util.capitalize(@get("model.type_name")) + + hoverValue: Ember.computed("text", "fundingInstrumentType", -> + "#{@get("text")} (Balance: #{Util.formatCurrency(@get("model.balance"))})" + ) +) + +`export default Account;` diff --git a/app/views/detail-views/summary-items/bank-account-status.coffee b/app/views/detail-views/summary-items/bank-account-status.coffee new file mode 100644 index 000000000..18ec15c0e --- /dev/null +++ b/app/views/detail-views/summary-items/bank-account-status.coffee @@ -0,0 +1,54 @@ +`import BaseStatus from "./base-status";` +`import Utils from "balanced-dashboard/lib/utils";` + +BankAccountStatus = BaseStatus.extend( + + verificationRestartDate: Ember.computed("model.verification.updated_at", -> + updatedAtDate = @get("model.verification.updated_at") + return moment(new Date(updatedAtDate)).add(3, "days").toDate() + ) + + buttonModal: Ember.computed("isCanStartVerification", "isCanConfirmVerification", -> + container = this.get("container"); + if (this.get("isCanStartVerification")) + return container.lookupFactory("view:modals/verify-bank-account-modal") + else if (this.get("isCanConfirmVerification")) + return container.lookupFactory("view:modals/bank-account-verification-confirm-modal") + + undefined + ), + + isCanStartVerification: Ember.computed("model.status", "verificationRestartDate", -> + status = this.get("model.status"); + (status == "unverified") || (status == "failed" && this.get("verificationRestartDate") < new Date()) + ), + + isCanConfirmVerification: Ember.computed.equal("model.status", "pending"), + + buttonModalText: Ember.computed "isCanStartVerification", "isCanConfirmVerification", -> + if (this.get("isCanStartVerification") || this.get("isCanConfirmVerification")) + return "Verify" + undefined + + description: Ember.computed("model.status", "formattedVerificationDate", -> + switch @get("model.status") + when "pending" + 'Two deposits have been made to your bank account. Confirm verification by entering the amounts.' + when "unverified" + 'You may only credit to this bank account. You must verify this bank account to debit from it.' + when "unverifiable" + 'You may only credit to this bank account. This bank account is unverifiable because it\'s not associated with a customer.' + when "failed" + "We could not verify your bank account. You may restart the verification process after #{@get("formattedVerificationDate")}" + else + null + ) + + formattedVerificationDate: Ember.computed("verificationRestartDate", -> + Utils.humanReadableDate @get("verificationRestartDate") + ) + + buttonModalArgument: Ember.computed.reads("model") +) + +`export default BankAccountStatus;` diff --git a/app/views/detail-views/summary-items/base-status.coffee b/app/views/detail-views/summary-items/base-status.coffee new file mode 100644 index 000000000..f1bf27107 --- /dev/null +++ b/app/views/detail-views/summary-items/base-status.coffee @@ -0,0 +1,46 @@ +`import Ember from "ember";` +`import Base from "./base";` + +computedContains = (arrayName, valueName) -> + return Ember.computed(arrayName, valueName, -> + @get(arrayName).contains @get(valueName) + ) + +BaseStatus = Base.extend( + isLink: false + classNameBindings: [":status", "isSuccess:succeeded", "isError:failed", "isOverdue:overdue", "isWarning:pending", "isUnderReview:under_review", "isCompleted:completed"] + + isDescription: Ember.computed("description", -> + !Ember.isBlank(@get("description")) + ) + + isLoading: Ember.computed.reads("model.isLoading") + isBlank: Ember.computed.empty("model.status") + + text: Ember.computed("status", -> + @get("status").replace(/_/g, " ").capitalize() + ) + status: Ember.computed.reads("model.status") + description: Ember.computed.reads("model.status_description") + + successValues: ["active", "succeeded", "captured", "verified", "paid"] + isSuccess: computedContains("successValues", "status") + + errorValues: ["failed", "error"] + isError: computedContains("errorValues", "status") + + overdueValues: ["overdue"] + isOverdue: computedContains("overdueValues", "status") + + warningValues: ["unverified", "pending", "needs_attention"] + isWarning: computedContains("warningValues", "status") + + underReviewValues: ["under_review"] + isUnderReview: computedContains("underReviewValues", "status") + + completedValues: ["completed"] + isCompleted: computedContains("completedValues", "status") + +) + +`export default BaseStatus;` diff --git a/app/views/detail-views/summary-items/base.coffee b/app/views/detail-views/summary-items/base.coffee new file mode 100644 index 000000000..eb701e0dd --- /dev/null +++ b/app/views/detail-views/summary-items/base.coffee @@ -0,0 +1,34 @@ +`import Ember from "ember";` + +BaseSummaryItemView = Ember.View.extend( + tagName: "dd" + templateName: "detail-views/summary-items/base" + classNameBindings: [] + + isHasButton: Ember.computed("buttonModal", -> + !Ember.isBlank(@get("buttonModal")) + ) + + isLoading: Ember.computed.reads("model.isLoading") + isBlank: Ember.computed("text", -> + Ember.isBlank @get("text") + ) + + isLink: Ember.computed "routeName", -> + !Ember.isBlank(@get("routeName")) + + routeName: Ember.computed.reads("model.route_name") + hoverValue: null + + learnMoreText: null + isLearnMoreText: Ember.computed "learnMoreText", -> + !Ember.isBlank(@get("learnMoreText")) + + isShowLearnMoreSection: false + + actions: + toggleDrawer: -> + @toggleProperty("isShowLearnMoreSection") +) + +`export default BaseSummaryItemView;` diff --git a/app/views/detail-views/summary-items/card-status.coffee b/app/views/detail-views/summary-items/card-status.coffee new file mode 100644 index 000000000..faefc6888 --- /dev/null +++ b/app/views/detail-views/summary-items/card-status.coffee @@ -0,0 +1,5 @@ +`import BaseStatus from "./base-status";` + +CardStatus = BaseStatus.extend() + +`export default CardStatus;` diff --git a/app/views/detail-views/summary-items/customer-status.coffee b/app/views/detail-views/summary-items/customer-status.coffee new file mode 100644 index 000000000..4e4d783bf --- /dev/null +++ b/app/views/detail-views/summary-items/customer-status.coffee @@ -0,0 +1,15 @@ +`import Base from "./base-status";` + +CustomerStatus = Base.extend( + isUnverified: Ember.computed.equal("status", "unverified") + + description: Ember.computed "isUnverified", -> + if @get("isUnverified") + 'You may credit this customer, but we recommend collecting more information from this customer for underwriting purposes.' + + learnMoreText: Ember.computed "isUnverified", -> + if @get("isUnverified") + "For an individual, you may collect full legal name, email, permanent street address, and last four digits of SSN. For a business, we also recommend collecting the full business name and EIN number." +) + +`export default CustomerStatus;` diff --git a/app/views/detail-views/summary-items/customer.coffee b/app/views/detail-views/summary-items/customer.coffee new file mode 100644 index 000000000..ea75f36ca --- /dev/null +++ b/app/views/detail-views/summary-items/customer.coffee @@ -0,0 +1,11 @@ +`import Base from "./base";` + +Customer = Base.extend( + isLoading: Ember.computed.reads("model.isLoading") + isLink: true + + text: Ember.computed.reads("model.display_me") + hoverValue: Ember.computed.reads("model.display_me_with_email") +) + +`export default Customer;` diff --git a/app/views/detail-views/summary-items/dispute-status.coffee b/app/views/detail-views/summary-items/dispute-status.coffee new file mode 100644 index 000000000..d4a230349 --- /dev/null +++ b/app/views/detail-views/summary-items/dispute-status.coffee @@ -0,0 +1,12 @@ +`import BaseStatus from "./base-status";` + +DisputeStatus = BaseStatus.extend( + description: Ember.computed "model.status", -> + switch @get('model.status') + when "needs_attention" + 'Provide documentation to fight this dispute' + when "under_review" + 'This dispute is under review. Once the card reviewer issues a decision, the status will update to won or lost.' +) + +`export default DisputeStatus;` diff --git a/app/views/detail-views/summary-items/funding-instrument.coffee b/app/views/detail-views/summary-items/funding-instrument.coffee new file mode 100644 index 000000000..276812911 --- /dev/null +++ b/app/views/detail-views/summary-items/funding-instrument.coffee @@ -0,0 +1,40 @@ +`import Base from "./base";` +`import Util from "balanced-dashboard/lib/utils";` + +FundingInstrument = Base.extend( + isCard: Ember.computed.reads("model.isCard") + isBankAccount: Ember.computed.reads("model.isBankAccount") + isPayableAccount: Ember.computed.reads("model.isPayableAccount") + + fundingInstrumentType: Ember.computed "isCard", "isBankAccount", "isPayableAccount", -> + if @get("isCard") + "card" + else if @get("isBankAccount") + "bank-account" + else if @get("isPayableAccount") + "payable-account" + + isLoading: Ember.computed.reads("model.isLoading") + isLink: true + isBlank: Ember.computed.empty("model") + + text: Ember.computed "fundingInstrumentType", "model.balance", "model.description", "model.id", -> + switch @get("fundingInstrumentType") + when "payable-account" + "Balance: #{Util.formatCurrency(@get("model.balance"))}" + else + @get("model.description") + + hoverValue: Ember.computed("text", "fundingInstrumentType", -> + "#{@get("model.type_name")} (#{@get("text")})" + ) + + lastFour: Ember.computed.reads("model.last_four") + company: Ember.computed "isCard", "model.brand", "model.formatted_bank_name", -> + if @get("isCard") + return @get("model.brand") + else + return @get("model.formatted_bank_name") +) + +`export default FundingInstrument;` diff --git a/app/views/detail-views/summary-items/invoice-status.coffee b/app/views/detail-views/summary-items/invoice-status.coffee new file mode 100644 index 000000000..b7beeb400 --- /dev/null +++ b/app/views/detail-views/summary-items/invoice-status.coffee @@ -0,0 +1,10 @@ +`import BaseStatus from "./base-status";` +`import Utils from "balanced-dashboard/lib/utils";` + +InvoiceStatus = BaseStatus.extend( + status: Ember.computed.reads("model.state") + description: Ember.computed "model.settle_at", -> + return "on #{Utils.humanReadableDateLong(@get('model.settle_at'))}" +) + +`export default InvoiceStatus;` diff --git a/app/views/detail-views/summary-items/log-status.coffee b/app/views/detail-views/summary-items/log-status.coffee new file mode 100644 index 000000000..0fe531506 --- /dev/null +++ b/app/views/detail-views/summary-items/log-status.coffee @@ -0,0 +1,9 @@ +`import Base from "./base-status";` + +LogStatus = Base.extend( + text: Ember.computed.reads("model.status_code"), + description: Ember.computed.reads("model.additional"), + status: Ember.computed.reads("model.status"), +) + +`export default LogStatus;` diff --git a/app/views/detail-views/summary-items/model-description.coffee b/app/views/detail-views/summary-items/model-description.coffee new file mode 100644 index 000000000..d51758d60 --- /dev/null +++ b/app/views/detail-views/summary-items/model-description.coffee @@ -0,0 +1,10 @@ +`import Base from "./base";` + +ModelDescription = Base.extend( + isLink: false + isLoading: Ember.computed.reads("model.isLoading") + isBlank: Ember.computed.empty("model.description") + text: Ember.computed.reads("model.description") +) + +`export default ModelDescription;` diff --git a/app/views/detail-views/summary-items/order-status.coffee b/app/views/detail-views/summary-items/order-status.coffee new file mode 100644 index 000000000..1929ab878 --- /dev/null +++ b/app/views/detail-views/summary-items/order-status.coffee @@ -0,0 +1,12 @@ +`import BaseStatus from "./base-status";` + +OrderStatus = BaseStatus.extend( + description: Ember.computed("model.isOverdue", "model.status", -> + if @get("model.status") == "overdue" + "Funds in this order are older than 30 days. Pay out your outstanding balance now." + else if @get("model.status") == "completed" + "All funds in this order are paid out. You may still have unsettled credits for your merchant." + ) +) + +`export default OrderStatus;` diff --git a/app/views/detail-views/summary-items/order.coffee b/app/views/detail-views/summary-items/order.coffee new file mode 100644 index 000000000..efd125f59 --- /dev/null +++ b/app/views/detail-views/summary-items/order.coffee @@ -0,0 +1,17 @@ +`import Base from "./base";` +`import Utils from "balanced-dashboard/lib/utils";` + +Order = Base.extend( + isLoading: Ember.computed.reads("model.isLoading") + isLink: true + + text: Ember.computed "model.amount_escrowed", -> + amount = @get("model.amount_escrowed") + "$#{Utils.centsToDollars(amount)}" + + hoverValue: Ember.computed "model.created_at", -> + date = @get("model.created_at") + "Created at #{Utils.humanReadableDateShort(date)}" +) + +`export default Order;` diff --git a/app/views/detail-views/summary-items/refund-status.coffee b/app/views/detail-views/summary-items/refund-status.coffee new file mode 100644 index 000000000..e2e320f97 --- /dev/null +++ b/app/views/detail-views/summary-items/refund-status.coffee @@ -0,0 +1,5 @@ +`import Base from "./transaction-status";` + +RefundStatus = Base.extend() + +`export default RefundStatus;` diff --git a/app/views/detail-views/summary-items/settlement-status.coffee b/app/views/detail-views/summary-items/settlement-status.coffee new file mode 100644 index 000000000..4ed0ee89d --- /dev/null +++ b/app/views/detail-views/summary-items/settlement-status.coffee @@ -0,0 +1,6 @@ +`import BaseStatus from "./base-status";` +`import Utils from "balanced-dashboard/lib/utils";` + +Status = BaseStatus.extend() + +`export default Status;` diff --git a/app/views/detail-views/summary-items/summary-item-view-generator.coffee b/app/views/detail-views/summary-items/summary-item-view-generator.coffee new file mode 100644 index 000000000..d4b6b3ab9 --- /dev/null +++ b/app/views/detail-views/summary-items/summary-item-view-generator.coffee @@ -0,0 +1,14 @@ +class SummaryItemViewGenerator + @view: (template, model) -> + new @(template, model) + + constructor: (@template, @model) -> + + addTo: (summarySectionView) -> + summarySectionView.addItem("detail-views/resource-summary", + model: @model + valueBinding: "model.source" + hoverValueBinding: "model" + ) + +`export default SummaryItemViewGenerator;` diff --git a/app/views/detail-views/summary-items/transaction-status.coffee b/app/views/detail-views/summary-items/transaction-status.coffee new file mode 100644 index 000000000..3e3516bc8 --- /dev/null +++ b/app/views/detail-views/summary-items/transaction-status.coffee @@ -0,0 +1,5 @@ +`import Base from "./base-status";` + +TransactionStatus = Base.extend() + +`export default TransactionStatus;` diff --git a/app/views/detail-views/summary-items/transaction.coffee b/app/views/detail-views/summary-items/transaction.coffee new file mode 100644 index 000000000..e5e600de7 --- /dev/null +++ b/app/views/detail-views/summary-items/transaction.coffee @@ -0,0 +1,17 @@ +`import Base from "./base";` +`import Utils from "balanced-dashboard/lib/utils";` + +Transaction = Base.extend( + isLoading: Ember.computed.reads("model.isLoading") + isLink: true + + text: Ember.computed "model.amount", -> + amount = @get("model.amount") + "$#{Utils.centsToDollars(amount)}" + + hoverValue: Ember.computed "model.created_at", -> + date = @get("model.created_at") + "Created at #{Utils.humanReadableDateShort(date)}" +) + +`export default Transaction;` diff --git a/app/views/detail-views/summary-labels/funding-instrument-label.coffee b/app/views/detail-views/summary-labels/funding-instrument-label.coffee new file mode 100644 index 000000000..7aab28c04 --- /dev/null +++ b/app/views/detail-views/summary-labels/funding-instrument-label.coffee @@ -0,0 +1,34 @@ +`import LabelBase from "./label-base";` + +View = LabelBase.extend( + isCard: Ember.computed.reads("model.isCard") + isBankAccount: Ember.computed.reads("model.isBankAccount") + isPayableAccount: Ember.computed.reads("model.isPayableAccount") + readableCardType: Ember.computed.reads("model.type_name") + + fundingInstrumentType: Ember.computed "isCard", "isBankAccount", "isPayableAccount", -> + if @get("isCard") + "card" + else if @get("isBankAccount") + "bank-account" + else if @get("isPayableAccount") + "payable-account" + + icon: Ember.computed("fundingInstrumentType", -> + @get("fundingInstrumentType") || "bank-account" + ) + + text: Ember.computed "model", "fundingInstrumentType", "prefixText", -> + prefixText = @get("prefixText") || "" + switch @get("fundingInstrumentType") + when "card" + "#{prefixText}: Card" + when "bank-account" + "#{prefixText}: Bank account" + when "payable-account" + "#{prefixText}: Payable account" + else + "#{prefixText}: Funding instrument" +) + +`export default View;` diff --git a/app/views/detail-views/summary-labels/label-base.coffee b/app/views/detail-views/summary-labels/label-base.coffee new file mode 100644 index 000000000..47703736d --- /dev/null +++ b/app/views/detail-views/summary-labels/label-base.coffee @@ -0,0 +1,15 @@ +`import Ember from "ember";` + +LabelBaseView = Ember.View.extend( + tagName: "dt" + templateName: "detail-views/summary-items/label-base" + + isModalEditLink: Ember.computed "modalEditClass", -> + !Ember.isBlank(@get("modalEditClass")) + + iconClasses: Ember.computed "icon", -> + icons = ["icon-#{@get("icon")}"] + icons.join(" ") +) + +`export default LabelBaseView;` diff --git a/app/views/detail-views/summary-sections/account-summary-section.js b/app/views/detail-views/summary-sections/account-summary-section.js new file mode 100644 index 000000000..c7b352b0e --- /dev/null +++ b/app/views/detail-views/summary-sections/account-summary-section.js @@ -0,0 +1,14 @@ +import SummarySectionView from "./summary-section-base"; + +var AccountSummarySectionView = SummarySectionView.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Customer", "customers"); + this.addSummaryItem("customer", { + modelBinding: "fundingInstrument.customer", + fundingInstrument: model + }); + }, +}); + +export default AccountSummarySectionView; diff --git a/app/views/detail-views/summary-sections/bank-account-summary-section.js b/app/views/detail-views/summary-sections/bank-account-summary-section.js index 0bb5dec17..50952f916 100644 --- a/app/views/detail-views/summary-sections/bank-account-summary-section.js +++ b/app/views/detail-views/summary-sections/bank-account-summary-section.js @@ -1,58 +1,20 @@ -import SummarySectionView from "./summary-section"; +import BaseSummarySection from "./summary-section-base"; import Utils from "balanced-dashboard/lib/utils"; -var BankAccountSummarySectionView = SummarySectionView.extend({ - verificationRestartDate: Ember.computed("model.verification.updated_at", function() { - var updatedAtDate = this.get("model.verification.updated_at"); - return moment(new Date(updatedAtDate)).add(3, "days").toDate(); - }), - - statusText: function() { - var status = this.get('model.status'); - - if (status === 'pending') { - return 'Two deposits have been made to your bank account. Confirm verification by entering the amounts.'; - } - else if (status === 'unverified') { - return 'You may only credit to this bank account. You must verify this bank account to debit from it.'; - } - else if (status === 'unverifiable') { - return 'You may only credit to this bank account. This bank account is unverifiable because it\'s not associated with a customer.'; - } - else if (status === 'failed') { - return 'We could not verify your bank account. You may restart the verification process after %@.'.fmt(Utils.humanReadableDate(this.get('verificationRestartDate'))); - } - - return undefined; - }.property('model.status', 'verificationRestartDate'), - - statusButtonModalView: function() { - var container = this.get("container"); - if (this.get("isCanStartVerification")) { - return container.lookupFactory("view:modals/verify-bank-account-modal"); - } else if (this.get("isCanConfirmVerification")) { - return container.lookupFactory("view:modals/bank-account-verification-confirm-modal"); - } - return undefined; - }.property('isCanStartVerification', "isCanConfirmVerification"), - - isCanStartVerification: Ember.computed("model.status", "verificationRestartDate", function() { - var status = this.get("model.status"); - return status === "unverified" || (status === "failed" && this.get("verificationRestartDate") < new Date()); - }), - - isCanConfirmVerification: Ember.computed.equal("model.status", "pending"), - - statusButtonText: function() { - if (this.get("isCanStartVerification") || this.get("isCanConfirmVerification")) { - return "Verify"; - } - return undefined; - }.property("isCanStartVerification", "isCanConfirmVerification"), - - linkedResources: function() { - return this.resourceLinks("model.customer"); - }.property('model.customer'), +var BankAccountSummarySectionView = BaseSummarySection.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Status", "status"); + this.addSummaryItem("bank-account-status", { + model: model + }); + + this.addLabel("Customer", "customers"); + this.addSummaryItem("customer", { + modelBinding: "fundingInstrument.customer", + fundingInstrument: model + }); + } }); export default BankAccountSummarySectionView; diff --git a/app/views/detail-views/summary-sections/card-summary-section.js b/app/views/detail-views/summary-sections/card-summary-section.js index 7585f7234..f4f90719d 100644 --- a/app/views/detail-views/summary-sections/card-summary-section.js +++ b/app/views/detail-views/summary-sections/card-summary-section.js @@ -1,9 +1,19 @@ -import SummarySectionView from "./summary-section"; +import BaseSummarySection from "./summary-section-base"; -var CardSummarySectionView = SummarySectionView.extend({ - linkedResources: function() { - return this.resourceLinks("model.customer"); - }.property("model.customer") +var CardSummarySectionView = BaseSummarySection.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Status", "status"); + this.addSummaryItem("card-status", { + model: model + }); + + this.addLabel("Customer", "customers"); + this.addSummaryItem("customer", { + modelBinding: "fundingInstrument.customer", + fundingInstrument: model + }); + }, }); -export default CardSummarySectionView; \ No newline at end of file +export default CardSummarySectionView; diff --git a/app/views/detail-views/summary-sections/credit-summary-section.js b/app/views/detail-views/summary-sections/credit-summary-section.js index 217050cdb..c096436a9 100644 --- a/app/views/detail-views/summary-sections/credit-summary-section.js +++ b/app/views/detail-views/summary-sections/credit-summary-section.js @@ -1,11 +1,5 @@ -import SummarySectionView from "./summary-section"; +import TransactionBaseSummarySection from "./transaction-base-summary-section"; -var CreditSummarySectionView = SummarySectionView.extend({ - statusText: Ember.computed.alias('model.status_description'), - - linkedResources: function() { - return this.resourceLinks("model.description", "model.customer", "model.destination"); - }.property("model.customer", "model.destination", "model.description") -}); +var CreditSummarySectionView = TransactionBaseSummarySection.extend(); export default CreditSummarySectionView; diff --git a/app/views/detail-views/summary-sections/customer-summary-section.js b/app/views/detail-views/summary-sections/customer-summary-section.js index 91c912464..cced68040 100644 --- a/app/views/detail-views/summary-sections/customer-summary-section.js +++ b/app/views/detail-views/summary-sections/customer-summary-section.js @@ -1,19 +1,19 @@ -import SummarySectionView from "./summary-section"; +import SummarySectionBase from "./summary-section-base"; -var CustomerSummarySectionView = SummarySectionView.extend({ - statusText: function() { - if (this.get('model.status') === 'unverified') { - return 'You may credit this customer, but we recommend collecting more information from this customer for underwriting purposes.'; - } - return null; - }.property('model.status'), +var CustomerSummarySectionView = SummarySectionBase.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Status", "status"); + this.addSummaryItem("customer-status", { + model: model, + }); - learnMore: function() { - return { - className: 'learn-more-unverified', - text: "For an individual, you may collect full legal name, email, permanent street address, and last four digits of SSN. For a business, we also recommend collecting the full business name and EIN number." - }; - }.property(), + this.addLabel("Payable account", "payable-account"); + this.addSummaryItem("funding-instrument", { + modelBinding: "summaryView.model.account", + summaryView: this + }); + }, }); export default CustomerSummarySectionView; diff --git a/app/views/detail-views/summary-sections/debit-summary-section.coffee b/app/views/detail-views/summary-sections/debit-summary-section.coffee new file mode 100644 index 000000000..b3cb64b5c --- /dev/null +++ b/app/views/detail-views/summary-sections/debit-summary-section.coffee @@ -0,0 +1,9 @@ +`import Ember from "ember";` +`import TransactionBaseSummarySection from "./transaction-base-summary-section";` + +DebitSummarySectionView = TransactionBaseSummarySection.extend( + fundingInstrument: Ember.computed.reads("model.source") + isSource: true +) + +`export default DebitSummarySectionView;` diff --git a/app/views/detail-views/summary-sections/debit-summary-section.js b/app/views/detail-views/summary-sections/debit-summary-section.js deleted file mode 100644 index c5e820352..000000000 --- a/app/views/detail-views/summary-sections/debit-summary-section.js +++ /dev/null @@ -1,11 +0,0 @@ -import SummarySectionView from "./summary-section"; - -var DebitSummarySectionView = SummarySectionView.extend({ - statusText: Ember.computed.alias('model.status_description'), - - linkedResources: function() { - return this.resourceLinks("model.description", "model.customer", "model.source"); - }.property("model.description", "model.customer", "model.source") -}); - -export default DebitSummarySectionView; diff --git a/app/views/detail-views/summary-sections/dispute-summary-section.js b/app/views/detail-views/summary-sections/dispute-summary-section.js index 8df7209bb..289e3a920 100644 --- a/app/views/detail-views/summary-sections/dispute-summary-section.js +++ b/app/views/detail-views/summary-sections/dispute-summary-section.js @@ -1,20 +1,49 @@ -import SummarySectionView from "./summary-section"; +import BaseSummarySection from "./summary-section-base"; -var DisputeSummarySectionView = SummarySectionView.extend({ - statusText: function() { - var status = this.get('model.status'); +var DisputeSummarySectionView = BaseSummarySection.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Status", "status"); + this.addSummaryItem("dispute-status", { + model: model + }); - if (status === 'needs_attention') { - return 'Provide documentation to fight this dispute'; - } else if (status === 'under_review') { - return 'This dispute is under review. Once the card reviewer issues a decision, the status will update to won or lost.'; + this.addLabel("Customer", "customers"); + this.addSummaryItem("customer", { + modelBinding: "summaryView.customer", + summaryView: this + }); + + this.addLabel("Funding instrument label", { + textBinding: "summaryView.fundingInstrumentLabelText", + iconBinding: "summaryView.fundingInstrumentLabelIcon", + summaryView: this + }); + this.addSummaryItem("funding-instrument", { + modelBinding: "summaryView.fundingInstrument", + summaryView: this + }); + }, + + customer: Ember.computed.reads("model.transaction.customer"), + + fundingInstrument: Ember.computed.reads("model.transaction.source"), + fundingInstrumentLabelText: Ember.computed("fundingInstrument.isCard", function() { + var isCard = this.get("fundingInstrument.isCard"); + if (Ember.isBlank(isCard)) { + return "Source"; } - return null; - }.property('model.status'), + else { + return isCard ? "Source card" : "Source bank account"; + } + }), - linkedResources: function() { - return this.resourceLinks("model.transaction.customer", "model.transaction.source"); - }.property("model.transaction.customer", "model.transaction.source") + fundingInstrumentLabelIcon: Ember.computed("fundingInstrument.isCard", function() { + var isCard = this.get("fundingInstrument.isCard"); + if (!Ember.isBlank(isCard)) { + return isCard ? "card" : "bank-account"; + } + }), }); export default DisputeSummarySectionView; diff --git a/app/views/detail-views/summary-sections/hold-summary-section.coffee b/app/views/detail-views/summary-sections/hold-summary-section.coffee new file mode 100644 index 000000000..d700a1b68 --- /dev/null +++ b/app/views/detail-views/summary-sections/hold-summary-section.coffee @@ -0,0 +1,9 @@ +`import Ember from "ember";` +`import TransactionBaseSummarySection from "./transaction-base-summary-section";` + +HoldSummarySectionView = TransactionBaseSummarySection.extend( + fundingInstrument: Ember.computed.reads("model.source") + isSource: true +) + +`export default HoldSummarySectionView;` diff --git a/app/views/detail-views/summary-sections/hold-summary-section.js b/app/views/detail-views/summary-sections/hold-summary-section.js deleted file mode 100644 index 623131a14..000000000 --- a/app/views/detail-views/summary-sections/hold-summary-section.js +++ /dev/null @@ -1,10 +0,0 @@ -import SummarySectionView from "./summary-section"; - -var HoldSummarySectionView = SummarySectionView.extend({ - statusText: Ember.computed.reads('model.status_description'), - linkedResources: function() { - return this.resourceLinks("model.description", "model.customer", "model.source"); - }.property("model.customer", "model.source", "model.description") -}); - -export default HoldSummarySectionView; diff --git a/app/views/detail-views/summary-sections/invoice-summary-section.js b/app/views/detail-views/summary-sections/invoice-summary-section.js index 937ca06a8..4d70c1956 100644 --- a/app/views/detail-views/summary-sections/invoice-summary-section.js +++ b/app/views/detail-views/summary-sections/invoice-summary-section.js @@ -1,11 +1,28 @@ -import SummarySectionView from "./summary-section"; -import Utils from "balanced-dashboard/lib/utils"; - -var InvoiceSummarySectionView = SummarySectionView.extend({ - statusText: function() { - var createdAt = Utils.humanReadableDateLong(this.get('model.settle_at')); - return 'on %@'.fmt(createdAt); - }.property('model.settle_at') +import SummarySectionBase from "./summary-section-base"; + +var InvoiceSummarySectionView = SummarySectionBase.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Status", "status"); + this.addSummaryItem("invoice-status", { + model: model, + }); + + this.addLabel("Funding instrument label", { + textBinding: "summaryView.fundingInstrumentLabelText", + iconBinding: "summaryView.fundingInstrumentLabelIcon", + summaryView: this + }); + this.addSummaryItem("funding-instrument", { + summaryView: this, + modelBinding: "summaryView.fundingInstrument" + }); + }, + + isSource: true, + fundingInstrument: Ember.computed.reads("model.source"), + fundingInstrumentLabelText: "Source", + fundingInstrumentLabelIcon: "bank-account" }); export default InvoiceSummarySectionView; diff --git a/app/views/detail-views/summary-sections/log-summary-section.coffee b/app/views/detail-views/summary-sections/log-summary-section.coffee new file mode 100644 index 000000000..771cbe0be --- /dev/null +++ b/app/views/detail-views/summary-sections/log-summary-section.coffee @@ -0,0 +1,41 @@ +`import SummarySectionBase from "./summary-section-base";` + +LogSummarySectionView = SummarySectionBase.extend({ + generateItems: -> + model = @get("model") + @addLabel("Status", "status") + @addSummaryItem("log-status", model: model) + + @addModelItem "Order", "order", "order" + @addTransactionItem "Hold", "hold" + @addTransactionItem "Debit", "debit" + @addTransactionItem "Credit", "Credit" + @addTransactionItem "Refund", "refund" + @addTransactionItem "Reversal", "reversal" + @addTransactionItem "Dispute", "dispute" + + if @get("model.customer") + @addLabel("Customer", "customers") + @addSummaryItem("customer", modelBinding: "log.customer", log: @get("model")) + + if @get("model.card") + @addLabel("Card", "card") + @addSummaryItem("funding-instrument", modelBinding: "log.card", log: @get("model")) + + if @get("model.bank_account") + @addLabel("Bank account", "bank-account") + @addSummaryItem("funding-instrument", modelBinding: "log.bank_account", log: @get("model")) + + addModelItem: (label, binding, view) -> + if @get("model.#{binding}") + @addLabel label, "single-transaction" + @addSummaryItem view, modelBinding: "log.#{binding}", log: @get("model") + + addTransactionItem: (label, binding) -> + @addModelItem(label, binding, "transaction") + + linkedResources: -> + return this.resourceLinks("model.order", "model.debit", "model.credit", "model.refund", "model.reversal", "model.hold", "model.customer", "model.card", "model.bank_account") +}) + +`export default LogSummarySectionView;` diff --git a/app/views/detail-views/summary-sections/log-summary-section.js b/app/views/detail-views/summary-sections/log-summary-section.js deleted file mode 100644 index 535ed6f99..000000000 --- a/app/views/detail-views/summary-sections/log-summary-section.js +++ /dev/null @@ -1,13 +0,0 @@ -import SummarySectionView from "./summary-section"; - -var LogSummarySectionView = SummarySectionView.extend({ - status: Ember.computed.reads("model.status_code"), - statusText: Ember.computed.reads("model.additional"), - statusClassName: Ember.computed.reads("model.status"), - - linkedResources: function() { - return this.resourceLinks("model.order", "model.debit", "model.credit", "model.refund", "model.reversal", "model.hold", "model.customer", "model.card", "model.bank_account"); - }.property("model.order", "model.debit", "model.credit", "model.refund", "model.reversal", "model.hold", "model.customer", "model.card", "model.bank_account") -}); - -export default LogSummarySectionView; diff --git a/app/views/detail-views/summary-sections/order-summary-section.js b/app/views/detail-views/summary-sections/order-summary-section.js index aad10b716..ad3c7ce75 100644 --- a/app/views/detail-views/summary-sections/order-summary-section.js +++ b/app/views/detail-views/summary-sections/order-summary-section.js @@ -1,16 +1,32 @@ -import SummarySectionView from "./summary-section"; +import SummarySectionBase from "./summary-section-base"; -var OrderSummarySectionView = SummarySectionView.extend({ - statusText: function() { - if (this.get("model.status") === "overdue") { - return "Funds in this order are older than 30 days. Pay out your outstanding balance now."; - } - return null; - }.property("model.status"), +var OrderSummarySectionView = SummarySectionBase.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Status", "status"); + this.addSummaryItem("order-status", { + model: model, + }); - linkedResources: function() { - return this.resourceLinks("model.description", "model.seller"); - }.property("model.description", "model.seller") + this.addInternalDescriptionLabel(); + this.addSummaryItem("model-description", { + model: model + }); + + this.addLabel("Merchant", "customers"); + this.addSummaryItem("customer", { + sectionView: this, + modelBinding: "sectionView.merchant" + }); + this.addLabel("Payable account", "payable-account"); + this.addSummaryItem("funding-instrument", { + summaryView: this, + modelBinding: "summaryView.account" + }); + }, + + merchant: Ember.computed.reads("model.seller"), + account: Ember.computed.reads("merchant.account") }); export default OrderSummarySectionView; diff --git a/app/views/detail-views/summary-sections/refund-summary-section.js b/app/views/detail-views/summary-sections/refund-summary-section.js index 70fbf20e7..7d82e0665 100644 --- a/app/views/detail-views/summary-sections/refund-summary-section.js +++ b/app/views/detail-views/summary-sections/refund-summary-section.js @@ -1,11 +1,30 @@ -import SummarySectionView from "./summary-section"; +import SummarySectionBase from "./transaction-base-summary-section"; -var RefundSummarySectionView = SummarySectionView.extend({ - statusText: Ember.computed.alias('model.status_description'), +var RefundSummarySectionView = SummarySectionBase.extend({ + generateItems: function() { + var model = this.get("model"); + this.addLabel("Status", "status"); + this.addSummaryItem("refund-status", { + model: model, + }); - linkedResources: function() { - return this.resourceLinks("model.description", "model.debit.customer", "model.debit.source"); - }.property("model.description", "model.debit.refunds.length", "model.debit.customer", "model.debit.source") + this.addInternalDescriptionLabel(); + this.addSummaryItem("model-description", { + model: model + }); + + this.addLabel("Customer", "customers"); + this.addSummaryItem("customer", { + sectionView: this, + modelBinding: "sectionView.debit.customer" + }); + + this.addFundingInstrumentLabel("Source", "model.debit.source"); + this.addSummaryItem("funding-instrument", { + summaryView: this, + modelBinding: "summaryView.model.debit.source" + }); + }, }); export default RefundSummarySectionView; diff --git a/app/views/detail-views/summary-sections/reversal-summary-section.js b/app/views/detail-views/summary-sections/reversal-summary-section.js index 24c8601d1..6b7804304 100644 --- a/app/views/detail-views/summary-sections/reversal-summary-section.js +++ b/app/views/detail-views/summary-sections/reversal-summary-section.js @@ -1,11 +1,8 @@ -import SummarySectionView from "./summary-section"; +import TransactionBaseSummarySection from "./transaction-base-summary-section"; -var ReversalSummarySectionView = SummarySectionView.extend({ - statusText: Ember.computed.alias('model.status_description'), - - linkedResources: function() { - return this.resourceLinks("model.description", "model.credit.customer", "model.credit.destination"); - }.property("model.description", "model.credit.customer", "model.credit.destination") +var ReversalSummarySectionView = TransactionBaseSummarySection.extend({ + fundingInstrument: Ember.computed.reads("model.credit.destination") }); export default ReversalSummarySectionView; + diff --git a/app/views/detail-views/summary-sections/settlement-summary-section.js b/app/views/detail-views/summary-sections/settlement-summary-section.js new file mode 100644 index 000000000..2378a665c --- /dev/null +++ b/app/views/detail-views/summary-sections/settlement-summary-section.js @@ -0,0 +1,38 @@ +import BaseSummarySection from "./summary-section-base"; +import Utils from "balanced-dashboard/lib/utils"; + +var SettlementSummarySectionView = BaseSummarySection.extend({ + generateItems: function() { + var model = this.get("model"); + + this.addLabel("Status", "status"); + this.addSummaryItem("settlement-status", { + model: model + }); + + this.addInternalDescriptionLabel(); + this.addSummaryItem("model-description", { + model: model + }); + + this.addLabel("Merchant", "customers"); + this.addSummaryItem("customer", { + sectionView: this, + modelBinding: "sectionView.model.destination.customer" + }); + + this.addFundingInstrumentLabel("From", "model.source"); + this.addSummaryItem("funding-instrument", { + modelBinding: "summaryView.model.source", + summaryView: this, + }); + + this.addFundingInstrumentLabel("To", "model.destination"); + this.addSummaryItem("funding-instrument", { + modelBinding: "summaryView.model.destination", + summaryView: this, + }); + }, +}); + +export default SettlementSummarySectionView; diff --git a/app/views/detail-views/summary-sections/summary-section-base.coffee b/app/views/detail-views/summary-sections/summary-section-base.coffee new file mode 100644 index 000000000..1e0f83876 --- /dev/null +++ b/app/views/detail-views/summary-sections/summary-section-base.coffee @@ -0,0 +1,30 @@ +`import Ember from "ember";` + +SummarySectionBase = Ember.View.extend( + templateName: "detail-views/summary-section-base" + + didInsertElement: -> + @_super() + @generateItems() + + addLabel: (text, icon) -> + @get("resourcesList").addLabel(text, icon) + + addItem: (viewClassName, viewAttributes) -> + @get("resourcesList").addItem(viewClassName, viewAttributes) + + addSummaryItem: (name, attrs) -> + @addItem("detail-views/summary-items/#{name}", attrs) + + addInternalDescriptionLabel: -> + @addLabel("Internal description", + icon: "description", + model: @get("model"), + modalEditClass: @container.lookupFactory("view:modals/edit-description-modal") + ) + + addFundingInstrumentLabel: (prefix, modelBinding) -> + @get("resourcesList").addFundingInstrumentLabel(prefix, modelBinding, @) +) + +`export default SummarySectionBase;` diff --git a/app/views/detail-views/summary-sections/summary-section-resource-list.coffee b/app/views/detail-views/summary-sections/summary-section-resource-list.coffee new file mode 100644 index 000000000..ccc9f91fd --- /dev/null +++ b/app/views/detail-views/summary-sections/summary-section-resource-list.coffee @@ -0,0 +1,29 @@ +`import Ember from "ember";` + +SummarySectionResourceList = Ember.ContainerView.extend( + tagName: "dl" + classNames: ["linked-resources"] + + addItem: (viewName, attributes) -> + view = @createChildView(viewName, attributes) + @pushObject view + view + + addFundingInstrumentLabel: (labelPrefix, modelBinding, summaryView) -> + @addItem("detail-views/summary-labels/funding-instrument-label", + prefixText: labelPrefix + summaryView: summaryView + modelBinding: "summaryView.#{modelBinding}" + ) + + addLabel: (labelText, labelIcon) -> + if Ember.typeOf(labelIcon) == "string" + attributes = + icon: labelIcon + else + attributes = labelIcon + attributes.text = labelText + @addItem("detail-views/summary-labels/label-base", attributes) +) + +`export default SummarySectionResourceList;` diff --git a/app/views/detail-views/summary-sections/summary-section.js b/app/views/detail-views/summary-sections/summary-section.js deleted file mode 100644 index 0ae80ca47..000000000 --- a/app/views/detail-views/summary-sections/summary-section.js +++ /dev/null @@ -1,161 +0,0 @@ -import Ember from "ember"; -import Utils from "balanced-dashboard/lib/utils"; -import Computed from "balanced-dashboard/utils/computed"; - -var SummarySectionView = Ember.View.extend({ - templateName: "detail-views/summary-section", - status: Ember.computed.oneWay("model.status"), - hasStatusOrLinkedResources: Computed.orProperties("status", "linkedResources"), - resourceLinks: function() { - var self = this; - var args = _.toArray(arguments); - - var result = args.map(function(resourceName) { - var resource = self.get(resourceName); - - if (resourceName === 'model.description') { - return self.generateDescriptionResource(self.get("model")); - } - - if (!resource) { - return undefined; - } - - if (_.isArray(resource.content)) { - resource = resource.content; - } - - if (resource.length > 0) { - var resources = []; - _.each(resource, function(content) { - resources.push(self.generateResourceLink(self.model, content)); - }); - return resources; - } else { - return self.generateResourceLink(self.model, resource); - } - }); - - return _.flatten(result).compact(); - }, - - generateDescriptionResource: function(model) { - return { - className: 'icon-description', - title: 'Internal description', - resource: model, - isDescription: true, - editModelModalClass: this.get("container").lookupFactory("view:modals/edit-description-modal") - }; - }, - - generateResourceLink: function(parentModel, model) { - var BankAccount = this.get("container").lookupFactory("model:bank-account"); - var Card = this.get("container").lookupFactory("model:card"); - var Credit = this.get("container").lookupFactory("model:credit"); - var Customer = this.get("container").lookupFactory("model:customer"); - var Debit = this.get("container").lookupFactory("model:debit"); - var Dispute = this.get("container").lookupFactory("model:dispute"); - var Hold = this.get("container").lookupFactory("model:hold"); - var Order = this.get("container").lookupFactory("model:order"); - var Refund = this.get("container").lookupFactory("model:refund"); - var Reversal = this.get("container").lookupFactory("model:reversal"); - - var title; - if (Ember.isBlank(model) || parentModel.uri === model.uri) { - return; - } - - if (model.constructor === Order) { - return { - className: 'icon-single-transaction', - title: 'Order', - resource: model - }; - } - - if (model.constructor === Hold) { - return { - className: 'icon-single-transaction', - title: 'Hold', - resource: model - }; - } - - if (model.constructor === Debit) { - return { - className: 'icon-single-transaction', - title: 'Debit', - resource: model - }; - } - - if (model.constructor === Credit) { - return { - className: 'icon-single-transaction', - title: 'Credit', - resource: model, - }; - } - - if (model.constructor === Refund) { - title = (parentModel.constructor === Refund) ? 'Other refund' : 'Refund'; - - return { - className: 'icon-single-transaction', - title: title, - resource: model, - }; - } - - if (model.constructor === Reversal) { - title = (parentModel.constructor === Reversal) ? 'Other reversal' : 'Reversal'; - - return { - className: 'icon-single-transaction', - title: title, - resource: model, - }; - } - - if (model.constructor === Dispute) { - return { - className: 'icon-single-transaction', - title: 'Dispute', - resource: model, - }; - } - - if (model.constructor === Customer) { - title = 'Customer'; - - if (parentModel.constructor === Order) { - title = (model.get('id') === parentModel.get('seller.id')) ? 'Merchant' : 'Buyer'; - } - - return { - className: 'icon-customers', - title: title, - resource: model, - }; - } - - if (model.constructor === Card) { - return { - className: 'icon-card', - title: model.get('type_name'), - resource: model, - }; - } - - if (model.constructor === BankAccount) { - return { - className: 'icon-bank-account', - title: model.get('type_name').split(' ')[0], - resource: model, - }; - } - } -}); - -export default SummarySectionView; diff --git a/app/views/detail-views/summary-sections/transaction-base-summary-section.coffee b/app/views/detail-views/summary-sections/transaction-base-summary-section.coffee new file mode 100644 index 000000000..204f3573f --- /dev/null +++ b/app/views/detail-views/summary-sections/transaction-base-summary-section.coffee @@ -0,0 +1,30 @@ +`import Ember from "ember";` +`import SummarySectionBaseView from "./summary-section-base";` + +TransactionBaseSummarySection = SummarySectionBaseView.extend( + generateItems: -> + model = @get("model") + @addLabel("Status", "status") + @addSummaryItem("transaction-status", model: model) + + @addInternalDescriptionLabel() + @addSummaryItem("model-description", model: model) + @addLabel "Customer", "customers" + @addSummaryItem("customer", modelBinding: "transaction.customer", transaction: model) + + if @get("isSource") + labelPrefix = "From" + else + labelPrefix = "To" + @addFundingInstrumentLabel(labelPrefix, "fundingInstrument") + + @addSummaryItem("funding-instrument", + summaryView: @ + modelBinding: "summaryView.fundingInstrument" + ) + + isSource: false + fundingInstrument: Ember.computed.reads("model.destination") +) + +`export default TransactionBaseSummarySection;` diff --git a/app/views/detail-views/tabs/customer-tab.js b/app/views/detail-views/tabs/customer-tab.js new file mode 100644 index 000000000..aa7e05a30 --- /dev/null +++ b/app/views/detail-views/tabs/customer-tab.js @@ -0,0 +1,15 @@ +import { defineFilter } from "balanced-dashboard/views/results/results-dropdown-filter"; +import TabView from "./tab"; + +var CustomerTabView = TabView.extend({ + tabs: function(){ + return [ + defineFilter("Activity", "activity", true), + defineFilter("Disputes", "disputes"), + defineFilter("Payment methods", "paymentMethods"), + defineFilter("Logs & Events", "logsEvents"), + ]; + }.property(), +}); + +export default CustomerTabView; diff --git a/app/views/detail-views/tabs/order-tab.js b/app/views/detail-views/tabs/order-tab.js index 76fc936fe..44c0281e5 100644 --- a/app/views/detail-views/tabs/order-tab.js +++ b/app/views/detail-views/tabs/order-tab.js @@ -8,24 +8,6 @@ var OrderTabView = TabView.extend({ defineFilter("Logs & Events", "logsEvents"), ]; }.property(), - - actions: { - setTab: function(tabLink) { - if (tabLink.value === "activity") { - this.get("parentView").setProperties({ - "isActivityTabSelected": true, - "isLogsEventsTabSelected": false - }); - } else if (tabLink.value === "logsEvents") { - this.get("parentView").setProperties({ - "isActivityTabSelected": false, - "isLogsEventsTabSelected": true - }); - } - - this.toggleSelected(tabLink); - } - } }); export default OrderTabView; diff --git a/app/views/detail-views/tabs/tab.js b/app/views/detail-views/tabs/tab.js index e8a2838f3..b413b2223 100644 --- a/app/views/detail-views/tabs/tab.js +++ b/app/views/detail-views/tabs/tab.js @@ -1,15 +1,27 @@ +import Utils from 'balanced-dashboard/lib/utils'; + var TabView = Ember.View.extend({ templateName: "detail-views/tab", isSelected: false, - toggleSelected: function(tabLink) { - var tabs = this.get("tabs"); - tabs.map(function(tab) { - tab.set("isSelected", false); - }); + actions: { + setTab: function(tabLink) { + var self = this; + var tabs = this.get("tabs"); + var isTabSelected = ""; + + tabs.map(function(tab) { + isTabSelected = "is%@TabSelected".fmt(Utils.capitalize(tab.value)); + self.get("parentView").set(isTabSelected, false); + tab.set("isSelected", false); + }); - tabLink.set("isSelected", true); + isTabSelected = "is%@TabSelected".fmt(Utils.capitalize(tabLink.value)); + this.get("parentView").set(isTabSelected, true); + tabLink.set("isSelected", true); + } } + }); export default TabView; diff --git a/app/views/form-fields/cvv-form-field.js b/app/views/form-fields/cvv-form-field.js new file mode 100644 index 000000000..9ffafd3f8 --- /dev/null +++ b/app/views/form-fields/cvv-form-field.js @@ -0,0 +1,8 @@ +import BaseFormFieldView from "./base-form-field"; + +var CvvFormFieldView = BaseFormFieldView.extend({ + inputType: "text", + maxlength: 4 +}); + +export default CvvFormFieldView; diff --git a/app/views/form-fields/email-form-field.js b/app/views/form-fields/email-form-field.js new file mode 100644 index 000000000..fb532950c --- /dev/null +++ b/app/views/form-fields/email-form-field.js @@ -0,0 +1,8 @@ +import BaseFormFieldView from "./base-form-field"; + +var EmailFormFieldView = BaseFormFieldView.extend({ + inputType: "email", + inputClassNames: "full" +}); + +export default EmailFormFieldView; diff --git a/app/views/form-fields/statement-descriptor-text-form-field.js b/app/views/form-fields/statement-descriptor-text-form-field.js new file mode 100644 index 000000000..e46dfc9c8 --- /dev/null +++ b/app/views/form-fields/statement-descriptor-text-form-field.js @@ -0,0 +1,19 @@ +import TextFormFieldView from "./text-form-field"; + +var StatementDescriptorTextFormFieldView = TextFormFieldView.extend({ + maxlength: 0, + labelText: "On statement as", + explanationText: function() { + var maxLength = this.get('maxlength'); + + if (maxLength > 0) { + var noteLength = this.get('value') ? this.get('value.length') : 0; + var remaining = maxLength - noteLength; + var unit = (remaining === 1) ? "character" : "characters"; + + return "%@ %@ remaining".fmt(remaining, unit); + } + }.property('value.length'), +}); + +export default StatementDescriptorTextFormFieldView; diff --git a/app/views/form-fields/textarea-form-field.js b/app/views/form-fields/textarea-form-field.js index 7c2636cec..80284842d 100644 --- a/app/views/form-fields/textarea-form-field.js +++ b/app/views/form-fields/textarea-form-field.js @@ -9,10 +9,11 @@ var TextAreaFormFieldView = BaseFormFieldView.extend({ if (maxLength > 0) { var noteLength = this.get('value') ? this.get('value.length') : 0; var remaining = maxLength - noteLength; - return "%@ characters remaining".fmt(remaining); + var unit = (remaining === 1) ? "character" : "characters"; + + return "%@ %@ remaining".fmt(remaining, unit); } }.property('value.length'), - }); export default TextAreaFormFieldView; diff --git a/app/views/marketplace/transactions.js b/app/views/marketplace/transactions.js deleted file mode 100644 index 20a9da160..000000000 --- a/app/views/marketplace/transactions.js +++ /dev/null @@ -1,7 +0,0 @@ -var MarketplaceTransactionsView = Ember.View.extend({ - layoutName: "marketplace/payments-layout", - templateName: "marketplace/transactions", - showActionButtons: true -}); - -export default MarketplaceTransactionsView; diff --git a/app/views/menus/order-sort-menu.coffee b/app/views/menus/order-sort-menu.coffee deleted file mode 100644 index 501d427b8..000000000 --- a/app/views/menus/order-sort-menu.coffee +++ /dev/null @@ -1,18 +0,0 @@ -`import BaseMenuView from "./base-menu";` -`import Utils from "balanced-dashboard/lib/utils";` - -OrderSortMenuView = BaseMenuView.extend( - templateName: "menus/order-sort-menu" - elementId: "order-sort-menu" - descMenuText: "Date created: newest" - ascMenuText: "Date created: oldest" - - menuTitle: (-> - if @get("controller.resultsLoader.sortDirection") == "desc" - return @get("descMenuText") - else - return @get("ascMenuText") - ).property("controller.resultsLoader.sortDirection", "ascMenuText", "descMenuText") -) - -`export default OrderSortMenuView;` diff --git a/app/views/modals/add-funds-modal.js b/app/views/modals/add-funds-modal.js index e4a097f1b..e2f8a6f5d 100644 --- a/app/views/modals/add-funds-modal.js +++ b/app/views/modals/add-funds-modal.js @@ -2,10 +2,11 @@ import Ember from "ember"; import ModalBaseView from "./modal-base"; import Form from "balanced-dashboard/views/modals/mixins/form-modal-mixin"; import Full from "balanced-dashboard/views/modals/mixins/full-modal-mixin"; +import Action from "balanced-dashboard/views/modals/mixins/object-action-mixin"; import Constants from "balanced-dashboard/utils/constants"; -import DebitExistingBankAccountTransactionFactory from "balanced-dashboard/models/factories/debit-existing-bank-account-transaction-factory"; +import DebitExistingFundingInstrumentTransactionFactory from "balanced-dashboard/models/factories/debit-existing-funding-instrument-transaction-factory"; -var AddFundsModalView = ModalBaseView.extend(Full, Form, { +var AddFundsModalView = ModalBaseView.extend(Full, Form, Action, { templateName: "modals/add-funds-modal", elementId: "add-funds", title: "Add funds", @@ -13,9 +14,6 @@ var AddFundsModalView = ModalBaseView.extend(Full, Form, { submitButtonText: "Add", appearsOnStatementAsMaxLength: Constants.MAXLENGTH.APPEARS_ON_STATEMENT_BANK_ACCOUNT, - appearsOnStatementAsLabelText: function() { - return "Appears on statement as (%@ characters max)".fmt(this.get("appearsOnStatementAsMaxLength")); - }.property("appearsOnStatementAsMaxLength"), debitableBankAccounts: Ember.computed.readOnly("marketplace.owner_customer.debitable_bank_accounts"), debitableBankAccountsForSelect: Ember.computed.map("debitableBankAccounts", function(bankAccount) { @@ -26,24 +24,18 @@ var AddFundsModalView = ModalBaseView.extend(Full, Form, { }), model: function() { - return DebitExistingBankAccountTransactionFactory.create(); + return DebitExistingFundingInstrumentTransactionFactory.create(); }.property(), + onModelSaved: function(credit) { + this.get('controller').transitionToRoute(credit.get('route_name'), credit); + this.close(); + }, + actions: { save: function() { - var self = this; var model = this.get("model"); - model.validate(); - if (model.get("isValid")) { - self.set("isSaving", true); - model.save().then(function(credit) { - self.get('controller').transitionToRoute(credit.get('route_name'), credit); - self.close(); - }). - finally(function() { - self.set("isSaving", false); - }); - } + this.save(model); } } }); diff --git a/app/views/modals/bank-account-credit-create-modal.js b/app/views/modals/bank-account-credit-create-modal.js index d330171d5..5a7da5709 100644 --- a/app/views/modals/bank-account-credit-create-modal.js +++ b/app/views/modals/bank-account-credit-create-modal.js @@ -8,9 +8,9 @@ import CreditBankAccountTransactionFactory from "balanced-dashboard/models/facto var BankAccountCreditCreateModalView = ModalBaseView.extend(Save, Full, Form, { templateName: "modals/bank-account-credit-create-modal", elementId: "pay-seller", - title: "Credit a bank account", + title: "Create a one-off credit", cancelButtonText: "Cancel", - submitButtonText: "Credit", + submitButtonText: "Create", model: function() { return this.container.lookup("model:factories/credit-bank-account-transaction-factory"); @@ -23,11 +23,6 @@ var BankAccountCreditCreateModalView = ModalBaseView.extend(Save, Full, Form, { }; }), - appearsOnStatementAsLabelText: function() { - var length = this.get("appearsOnStatementAsMaxLength"); - return "Appears on statement as (%@ characters max)".fmt(length); - }.property("appearsOnStatementAsMaxLength"), - appearsOnStatementAsMaxLength: Constants.MAXLENGTH.APPEARS_ON_STATEMENT_BANK_ACCOUNT, actions: { diff --git a/app/views/modals/card-debit-create-modal.js b/app/views/modals/card-debit-create-modal.js index 774af1aa2..74f529798 100644 --- a/app/views/modals/card-debit-create-modal.js +++ b/app/views/modals/card-debit-create-modal.js @@ -7,9 +7,9 @@ import Save from "balanced-dashboard/views/modals/mixins/object-action-mixin"; var CardDebitCreateModalView = ModalBaseView.extend(Save, Full, Form, { templateName: "modals/card-debit-create-modal", elementId: "charge-card", - title: "Debit a card", + title: "Create an order", cancelButtonText: "Cancel", - submitButtonText: "Debit", + submitButtonText: "Create", model: function() { var DebitCardTransactionFactory = require("balanced-dashboard/models/factories/debit-card-transaction-factory")['default']; @@ -18,11 +18,6 @@ var CardDebitCreateModalView = ModalBaseView.extend(Save, Full, Form, { appearsOnStatementAsMaxLength: Constants.MAXLENGTH.APPEARS_ON_STATEMENT_CARD, - appearsOnStatementAsLabelText: function() { - var length = this.get("appearsOnStatementAsMaxLength"); - return "Appears on statement as (%@ characters max)".fmt(length); - }.property("appearsOnStatementAsMaxLength"), - actions: { save: function() { var controller = this.get("controller"); diff --git a/app/views/modals/credit-customer-modal.js b/app/views/modals/credit-customer-modal.js index 6633cc76e..434a79b76 100644 --- a/app/views/modals/credit-customer-modal.js +++ b/app/views/modals/credit-customer-modal.js @@ -1,4 +1,5 @@ import Ember from "ember"; +import Utils from "balanced-dashboard/lib/utils"; import CreditExistingFundingInstrumentTransactionFactory from "balanced-dashboard/models/factories/credit-existing-funding-instrument-transaction-factory"; import ModalBaseView from "./modal-base"; import Form from "balanced-dashboard/views/modals/mixins/form-modal-mixin"; @@ -18,13 +19,26 @@ var CreditCustomerModalView = ModalBaseView.extend(Full, Form, Save, { }); }.property("customer"), + nameOnAccountText: function() { + if (this.get("model.destination.name")) { + return "Name on account: %@".fmt(this.get("model.destination.name")); + } + }.property("model.destination.name"), + + orderBalanceText: function() { + if (this.get("model.order")) { + return "Order balance: %@".fmt(Utils.formatCurrency(this.get("model.order.amount_escrowed"))); + } + }.property("model.order.amount_escrowed"), + appearsOnStatementAsMaxLength: Ember.computed.oneWay("model.appears_on_statement_max_length"), - appearsOnStatementAsLabelText: function() { - var length = this.get("appearsOnStatementAsMaxLength"); - return "Appears on statement as (%@ characters max)".fmt(length); - }.property("appearsOnStatementAsMaxLength"), + + creditableOrders: function() { + return this.get("customer").getOrdersLoader().get("results"); + }.property("customer"), fundingInstruments: Ember.computed.oneWay('customer.creditable_funding_instruments'), + isDisplayExistingFundingInstruments: Ember.computed.gt("fundingInstruments.length", 0), actions: { save: function() { @@ -39,10 +53,9 @@ var CreditCustomerModalView = ModalBaseView.extend(Full, Form, Save, { }); CreditCustomerModalView.reopenClass({ - open: function(customer, order) { + open: function(customer) { return this.create({ - customer: customer, - order: order + customer: customer }); }, }); diff --git a/app/views/modals/credit-funding-instrument-modal.js b/app/views/modals/credit-funding-instrument-modal.js index d23e2e61e..bfd3d0351 100644 --- a/app/views/modals/credit-funding-instrument-modal.js +++ b/app/views/modals/credit-funding-instrument-modal.js @@ -1,4 +1,5 @@ import BaseFundingInstrumentModalView from "./base-funding-instrument-modal"; +import Utils from "balanced-dashboard/lib/utils"; import CreditExistingFundingInstrumentTransactionFactory from "balanced-dashboard/models/factories/credit-existing-funding-instrument-transaction-factory"; var CreditFundingInstrumentModalView = BaseFundingInstrumentModalView.extend({ @@ -10,11 +11,12 @@ var CreditFundingInstrumentModalView = BaseFundingInstrumentModalView.extend({ cancelButtonText: "Cancel", submitButtonText: "Credit", - appearsOnStatementAsLabelText: function() { - var length = this.get("model.destination.appears_on_statement_max_length"); - return "Appears on statement as (%@ characters max)".fmt(length); - }.property("model.destination.appears_on_statement_max_length"), + expectedDateText: function() { + var creditDate = this.get("model.destination.expected_credit_date"); + return "This credit is expected to appear on %@.".fmt(Utils.humanReadableDate(creditDate)); + }.property("model.destination.expected_credit_date"), + appearsOnStatementAsMaxLength: Ember.computed.reads("model.destination.appears_on_statement_max_length"), }); CreditFundingInstrumentModalView.reopenClass({ diff --git a/app/views/modals/credit-order-modal.js b/app/views/modals/credit-order-modal.js index f419e1a73..6a300dfbd 100644 --- a/app/views/modals/credit-order-modal.js +++ b/app/views/modals/credit-order-modal.js @@ -5,7 +5,9 @@ import CreditCustomerModalView from "./credit-customer-modal"; var CreditOrderModalView = CreditCustomerModalView.extend({ templateName: "modals/credit-order-modal", elementId: "credit-order", - title: "Credit this order", + title: "Credit from this order", + + appearsOnStatementAsMaxLength: Ember.computed.reads("model.appears_on_statement_max_length"), model: function() { return CreditExistingFundingInstrumentTransactionFactory.create({ diff --git a/app/views/modals/customer-card-create-modal.js b/app/views/modals/customer-card-create-modal.js index 19493f81f..ce4fcdf60 100644 --- a/app/views/modals/customer-card-create-modal.js +++ b/app/views/modals/customer-card-create-modal.js @@ -3,7 +3,7 @@ import ModalBaseView from "./modal-base"; import Form from "balanced-dashboard/views/modals/mixins/form-modal-mixin"; import Full from "balanced-dashboard/views/modals/mixins/full-modal-mixin"; import Save from "balanced-dashboard/views/modals/mixins/object-action-mixin"; -import CardValidatable from "balanced-dashboard/models/card-validatable"; +import Card from "balanced-dashboard/models/card"; var CustomerCardCreateModalView = ModalBaseView.extend(Full, Form, Save, { templateName: 'modals/customer-card-create-modal', @@ -13,22 +13,21 @@ var CustomerCardCreateModalView = ModalBaseView.extend(Full, Form, Save, { submitButtonText: "Add", model: function() { - return CardValidatable.create({ - name: '', - number: '', + return Card.create({ + name: 'Carlos', + number: '4111 1111 1111 1111', cvv: '', - expiration_month: '', - expiration_year: '', + expiration_month: "12", + expiration_year: "2000", address: this.get('customer.address') || {} }); - }.property(), + }.property("customer.address"), expiration_error: Computed.orProperties('model.validationErrors.expiration_month', 'model.validationErrors.expiration_year'), isSaving: Ember.computed.oneWay("model.isSaving"), save: function(fundingInstrument) { var self = this; - fundingInstrument.get("validationErrors").clear(); return fundingInstrument .tokenizeAndCreate(this.get('customer.id')) .then(function(model) { @@ -40,10 +39,16 @@ var CustomerCardCreateModalView = ModalBaseView.extend(Full, Form, Save, { actions: { save: function() { var controller = this.get("controller"); - this.save(this.get("model")) - .then(function(model) { - controller.transitionToRoute(model.get("route_name"), model); - }); + var model = this.get("model"); + + model.get("validationErrors").clear(); + model.validate(); + if (model.get("isValid")) { + this.save(this.get("model")) + .then(function(model) { + controller.transitionToRoute(model.get("route_name"), model); + }); + } } } }); diff --git a/app/views/modals/debit-customer-modal.js b/app/views/modals/debit-customer-modal.js index f5f8196f4..27ea54b93 100644 --- a/app/views/modals/debit-customer-modal.js +++ b/app/views/modals/debit-customer-modal.js @@ -4,6 +4,7 @@ import Full from "balanced-dashboard/views/modals/mixins/full-modal-mixin"; import Save from "balanced-dashboard/views/modals/mixins/object-action-mixin"; import ModalBaseView from "./modal-base"; import DebitExistingFundingInstrumentTransactionFactory from "balanced-dashboard/models/factories/debit-existing-funding-instrument-transaction-factory"; +import DebitCardTransactionFactory from "balanced-dashboard/models/factories/debit-card-transaction-factory"; var DebitCustomerModalView = ModalBaseView.extend(Full, Form, Save, { templateName: "modals/debit-customer-modal", @@ -13,18 +14,25 @@ var DebitCustomerModalView = ModalBaseView.extend(Full, Form, Save, { submitButtonText: "Debit", appearsOnStatementAsMaxLength: Ember.computed.oneWay("model.appears_on_statement_max_length"), - appearsOnStatementAsLabelText: function() { - var length = this.get("appearsOnStatementAsMaxLength"); - return "Appears on statement as (%@ characters max)".fmt(length); - }.property("appearsOnStatementAsMaxLength"), + + transactionFactoryClass: function() { + if (this.get("isDisplayExistingFundingInstruments")) { + return DebitExistingFundingInstrumentTransactionFactory; + } else { + return DebitCardTransactionFactory; + } + }.property("isDisplayExistingFundingInstruments"), model: function() { - return DebitExistingFundingInstrumentTransactionFactory.create({ + return this.get("transactionFactoryClass").create({ customer: this.get("customer") }); - }.property("customer"), + }.property("customer", "transactionFactoryClass"), + fundingInstruments: Ember.computed.oneWay('customer.debitable_funding_instruments'), + isDisplayExistingFundingInstruments: Ember.computed.gt("fundingInstruments.length", 0), + actions: { save: function() { var controller = this.get("controller"); @@ -38,10 +46,9 @@ var DebitCustomerModalView = ModalBaseView.extend(Full, Form, Save, { }); DebitCustomerModalView.reopenClass({ - open: function(customer, order) { + open: function(customer) { return this.create({ - customer: customer, - order: order + customer: customer }); }, }); diff --git a/app/views/modals/debit-funding-instrument-modal.js b/app/views/modals/debit-funding-instrument-modal.js index 0c4632344..70349474d 100644 --- a/app/views/modals/debit-funding-instrument-modal.js +++ b/app/views/modals/debit-funding-instrument-modal.js @@ -10,16 +10,14 @@ var DebitFundingInstrumentModalView = BaseFundingInstrumentModalView.extend({ cancelButtonText: "Cancel", submitButtonText: "Debit", - appearsOnStatementAsLabelText: function() { - var length = this.get("model.source.appears_on_statement_max_length"); - return "Appears on statement as (%@ characters max)".fmt(length); - }.property("model.source.appears_on_statement_max_length"), + appearsOnStatementAsMaxLength: Ember.computed.reads("model.source.appears_on_statement_max_length"), }); DebitFundingInstrumentModalView.reopenClass({ open: function(fundingInstrument) { var debit = DebitExistingFundingInstrumentTransactionFactory.create({ - source: fundingInstrument + source: fundingInstrument, + customer: fundingInstrument.get("customer") }); return this.create({ model: debit diff --git a/app/views/modals/debit-order-modal.js b/app/views/modals/debit-order-modal.js new file mode 100644 index 000000000..c42c2795a --- /dev/null +++ b/app/views/modals/debit-order-modal.js @@ -0,0 +1,42 @@ +import ModalBaseView from "./modal-base"; +import Form from "balanced-dashboard/views/modals/mixins/form-modal-mixin"; +import Full from "balanced-dashboard/views/modals/mixins/full-modal-mixin"; +import Constants from "balanced-dashboard/utils/constants"; +import Save from "balanced-dashboard/views/modals/mixins/object-action-mixin"; + +var DebitOrderModalView = ModalBaseView.extend(Save, Full, Form, { + templateName: "modals/debit-order-modal", + elementId: "debit-order", + title: "Debit into this order", + cancelButtonText: "Cancel", + submitButtonText: "Debit", + + model: function() { + var DebitCardTransactionFactory = require("balanced-dashboard/models/factories/debit-card-transaction-factory")['default']; + return DebitCardTransactionFactory.create({ + order:this.get("order") + }); + }.property(), + + appearsOnStatementAsMaxLength: Constants.MAXLENGTH.APPEARS_ON_STATEMENT_CARD, + + actions: { + save: function() { + var controller = this.get("controller"); + this.save(this.get("model")) + .then(function(model) { + controller.transitionToRoute(model.get("route_name"), model); + }); + }, + } +}); + +DebitOrderModalView.reopenClass({ + open: function(order) { + return this.create({ + order: order + }); + }, +}); + +export default DebitOrderModalView; diff --git a/app/views/modals/hold-card-modal.js b/app/views/modals/hold-card-modal.js index 8f92f64b9..abb399a10 100644 --- a/app/views/modals/hold-card-modal.js +++ b/app/views/modals/hold-card-modal.js @@ -12,7 +12,8 @@ var HoldCardModalView = BaseFundingInstrumentModalView.extend({ HoldCardModalView.reopenClass({ open: function(card) { var hold = HoldExistingFundingInstrumentTransactionFactory.create({ - source: card + source: card, + customer: card.get("customer") }); return this.create({ model: hold diff --git a/app/views/modals/mixins/object-action-mixin.coffee b/app/views/modals/mixins/object-action-mixin.coffee index 573059b34..feb2cfe2e 100644 --- a/app/views/modals/mixins/object-action-mixin.coffee +++ b/app/views/modals/mixins/object-action-mixin.coffee @@ -22,7 +22,8 @@ ObjectActionMixin = Ember.Mixin.create( @getNotificationController().alertSuccess successAlertText @onModelSaved(model) - errorHandler = (model) -> + errorHandler = (model) => + @element.scrollTop = 0 if !Ember.isBlank(model) getRootErrorMessages(model).forEach (message) -> notificationsController.alertError(message) diff --git a/app/views/modals/search-modal.coffee b/app/views/modals/search-modal.coffee index 0df5924e1..02c31f09f 100644 --- a/app/views/modals/search-modal.coffee +++ b/app/views/modals/search-modal.coffee @@ -10,19 +10,20 @@ SearchModalView = ModalBaseView.extend(Search, templateName: 'modals/search-modal' elementId: 'search-modal' - selectedTabType: "transaction" + selectedTabType: "order_transaction" - isOrdersTabSelected: isTabSelected("order") - isTransactionsTabSelected: isTabSelected("transaction") + isOrdersTabSelected: isTabSelected("order_transaction") isCustomersTabSelected: isTabSelected("customer") isFundingInstrumentsTabSelected: isTabSelected("funding_instrument") + isAccountsTabSelected: isTabSelected("account") + isSettlementsTabSelected: isTabSelected("settlement") isLogsTabSelected: isTabSelected("log") totalResults: Computed.sumAll("resultsLoader.results.total_results", "totalLogs") - totalOrders: Ember.computed.oneWay("resultsLoader.results.total_orders") - totalTransactions: Ember.computed.oneWay("resultsLoader.results.total_transactions") totalCustomers: Ember.computed.oneWay("resultsLoader.results.counts.customer") + totalAccounts: Ember.computed.oneWay("totalCustomers") totalFundingInstruments: Ember.computed.oneWay("resultsLoader.results.total_funding_instruments") + totalSettlements: Ember.computed.oneWay("resultsLoader.results.total_settlements") totalLogs: Ember.computed.oneWay("logsResultsLoader.results.length") isLoaded: Ember.computed.oneWay("resultsLoader.results.isLoaded") diff --git a/app/views/modals/withdraw-funds-modal.js b/app/views/modals/withdraw-funds-modal.js index 61ab94412..21c2c2ff4 100644 --- a/app/views/modals/withdraw-funds-modal.js +++ b/app/views/modals/withdraw-funds-modal.js @@ -14,10 +14,6 @@ var WithdrawFundsModalView = ModalBaseView.extend(Full, Form, { submitButtonText: "Withdraw", appearsOnStatementAsMaxLength: Constants.MAXLENGTH.APPEARS_ON_STATEMENT_BANK_ACCOUNT, - appearsOnStatementAsLabelText: function() { - var length = this.get("appearsOnStatementAsMaxLength"); - return "Appears on statement as (%@ characters max)".fmt(length); - }.property("appearsOnStatementAsMaxLength"), bankAccounts: Ember.computed.readOnly('marketplace.owner_customer.bank_accounts'), availableBalance: function() { diff --git a/app/views/results/accounts-table.js b/app/views/results/accounts-table.js new file mode 100644 index 000000000..2510de3dc --- /dev/null +++ b/app/views/results/accounts-table.js @@ -0,0 +1,8 @@ +import ResultsTableView from "./results-table"; + +var AccountsTableView = ResultsTableView.extend({ + classNames: 'accounts', + templateName: 'results/accounts-table' +}); + +export default AccountsTableView; diff --git a/app/views/results/associated-transactions-table.js b/app/views/results/associated-transactions-table.js index 11af90dca..a5dd7831a 100644 --- a/app/views/results/associated-transactions-table.js +++ b/app/views/results/associated-transactions-table.js @@ -2,7 +2,9 @@ import TransactionsTableView from "./transactions-table"; var AssociatedTransactionsTableView = TransactionsTableView.extend({ templateName: "results/associated-transactions-table", - classNames: ["non-interactive"] + classNames: ["non-interactive"], + colspan: 4, + hideCustomerColumn: true }); export default AssociatedTransactionsTableView; diff --git a/app/views/results/customer-transactions-table-grouped-by-order.js b/app/views/results/customer-transactions-table-grouped-by-order.js new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/results/embedded-internal-accounts-table.js b/app/views/results/embedded-internal-accounts-table.js new file mode 100644 index 000000000..b19fa5c1c --- /dev/null +++ b/app/views/results/embedded-internal-accounts-table.js @@ -0,0 +1,7 @@ +import ResultsTableView from "./results-table"; + +var EmbeddedInternalAccountsTable = ResultsTableView.extend({ + templateName: 'results/embedded-internal-accounts-table' +}); + +export default EmbeddedInternalAccountsTable; diff --git a/app/views/results/embedded-transactions-table.js b/app/views/results/embedded-transactions-table.js deleted file mode 100644 index 3bf85d789..000000000 --- a/app/views/results/embedded-transactions-table.js +++ /dev/null @@ -1,29 +0,0 @@ -import TransactionsTableView from "./transactions-table"; - -var EmbeddedTransactionsTableView = TransactionsTableView.extend({ - templateName: "results/embedded-transactions-table", - classNames: ["non-interactive"], - filteredResults: function() { - var results = this.get("loader.results"); - var filteredResults = []; - - results.forEach(function(transaction) { - if (!_.contains(["Hold", "Refund", "Reversal"], transaction.get("type_name"))) { - filteredResults.pushObject(transaction); - } - - // TODO: Figure out a better way to include manually created holds - if (transaction.get("type_name") === "Hold" && Ember.keys(transaction.meta).length !== 0) { - filteredResults.pushObject(transaction); - } - }); - - if (filteredResults.length === 0 && results.total > 0) { - results.loadNextPage(); - } - - return filteredResults; - }.property("loader.results.length"), -}); - -export default EmbeddedTransactionsTableView; diff --git a/app/views/results/grouped-order-row.js b/app/views/results/grouped-order-row.js index be15b74f7..f93279ba8 100644 --- a/app/views/results/grouped-order-row.js +++ b/app/views/results/grouped-order-row.js @@ -11,7 +11,7 @@ var GroupedOrderRowView = GroupedTransactionRowView.extend({ primaryLabelText: function() { var description = this.get('item.description') || this.get('item.id'); - return '%@: %@'.fmt(this.get('item.type_name'), description); + return '%@ (%@)'.fmt(this.get('item.type_name'), description); }.property('item.type_name', 'item.description', 'item.id'), secondaryLabelText: function () { diff --git a/app/views/results/grouped-transaction-row.js b/app/views/results/grouped-transaction-row.js index ae0bf4162..c72d07f9c 100644 --- a/app/views/results/grouped-transaction-row.js +++ b/app/views/results/grouped-transaction-row.js @@ -6,62 +6,205 @@ import Utils from "balanced-dashboard/lib/utils"; var GroupedTransactionRowView = LinkedTwoLinesCellView.extend({ tagName: 'tr', templateName: 'results/grouped-transaction-row', - routeName: Ember.computed.oneWay("item.route_name"), - spanClassNames: Ember.computed.oneWay("item.status"), + routeName: Computed.orProperties("item.route_name", "item.routeName"), + spanClassNames: Ember.computed.reads("item.status"), + typeName: Ember.computed.reads("item.type_name"), + connected: true, title: function() { - var description = this.get("item.description"); - - if (description) { - return description; - } if (_.contains(this.get("classNames"), "current")) { return 'You are currently viewing this transaction.'; } - return '(Created at %@)'.fmt(this.get("secondaryLabelText")); - }.property("item.description", "primaryLabelText", "secondaryLabelText"), + + return this.get("primaryLabelText"); + }.property("primaryLabelText"), primaryLabelText: function() { if (_.contains(this.get("classNames"), "current")) { - return '%@ (currently viewing)'.fmt(this.get('item.type_name')); + return '%@ (currently viewing)'.fmt(this.get('typeName')); } var transactionText; var description = this.get('item.description'); var status = Utils.capitalize(this.get('item.status')); - if (status) { - status = status.toLowerCase(); - } - if (description) { - transactionText = '%@ (%@) %@'.fmt(this.get('item.type_name'), description, status); + transactionText = '%@ (%@)'.fmt(this.get('typeName'), description); } else { - transactionText = '%@ %@'.fmt(this.get('item.type_name'), status); + transactionText = this.get('typeName'); } - if (this.get('item.type_name') === 'Dispute') { - transactionText = '%@ %@'.fmt(this.get('item.type_name'), status); + if (status) { + transactionText = '%@ %@'.fmt(transactionText, status.toLowerCase()); } - return Utils.safeFormat(transactionText).htmlSafe(); - }.property('classNames', 'item.type_name', 'item.status', 'item.description'), + return transactionText; + }.property('classNames', 'item.status', 'item.description'), secondaryLabelText: function () { return Utils.humanReadableDateTime(this.get('item.created_at')); }.property('item.created_at'), - paymentMethodText: function() { - var label = '%@%@'; - var secondaryLabel = this.get('paymentMethodSecondaryLabelText') || ''; - return Utils.safeFormat(label, this.get('paymentMethodPrimaryLabelText'), secondaryLabel).htmlSafe(); - }.property('paymentMethodPrimaryLabelText', 'paymentMethodSecondaryLabelText'), + paymentMethodFromText: function() { + var primaryLabel = ""; + var secondaryLabel = ""; + var iconName = ""; + var typeName = this.get('typeName'); + + if (typeName === "Debit" || typeName === "Hold") { + primaryLabel = this.get("item.source.last_four"); + secondaryLabel = this.get("item.source.type_name"); + + if (this.get("item.source")) { + iconName = Ember.String.dasherize(this.get("item.source.type_name")); + } + } + if (typeName === "Credit" || typeName === "Refund") { + if (this.get("item.order_uri")) { + primaryLabel = "Balance"; + secondaryLabel = "Order"; + iconName = "orders"; + } else if (this.get("item.isLoaded")) { + primaryLabel = "Balance"; + secondaryLabel = "Marketplace"; + iconName = "escrow"; + } + } + if (typeName === "Reversal") { + if (this.get("item.credit.destination.last_four")) { + primaryLabel = this.get("item.credit.destination.last_four"); + secondaryLabel = this.get("item.credit.destination.type_name"); + + if (this.get("item.credit.destination")) { + iconName = Ember.String.dasherize(this.get("item.credit.destination.type_name")); + } + } else { + primaryLabel = "Balance"; + secondaryLabel = "Payable account"; + iconName = "payable-account"; + } + } + if (typeName === "Settlement") { + if (this.get("settlementSource.last_four")) { + primaryLabel = this.get("settlementSource.last_four"); + secondaryLabel = this.get("settlementSource.type_name"); + + if (this.get("settlementSource")) { + iconName = Ember.String.dasherize(this.get("settlementSource.type_name")); + } + } else if (this.get("settlementSource.type") === "payable") { + primaryLabel = "Balance"; + secondaryLabel = "Payable account"; + iconName = "payable-account"; + } + } + return Utils.safeFormat('%@%@', iconName, primaryLabel, secondaryLabel).htmlSafe(); + }.property("typeName", "item.source.last_four", "item.source.type_name", "item.credit.destination.last_four", "item.credit.destination.type_name", "settlementSource.last_four", "settlementSource.type_name", "settlementSource.type", "item.isLoaded", "item.order_uri"), + + paymentMethodToText: function() { + var primaryLabel = ""; + var secondaryLabel = ""; + var iconName = ""; + var typeName = this.get('typeName'); + + if (typeName === "Debit" || typeName === "Hold") { + if (this.get("item.order_uri")) { + primaryLabel = "Balance"; + secondaryLabel = "Order"; + iconName = "orders"; + } else if (this.get("item.isLoaded")) { + primaryLabel = "Balance"; + secondaryLabel = "Marketplace"; + iconName = "escrow"; + } + } + if (typeName === "Credit") { + if (this.get("item.destination.last_four")) { + primaryLabel = this.get("item.destination.last_four"); + if (this.get("item.destination")) { + secondaryLabel = this.get("item.destination.type_name"); + iconName = Ember.String.dasherize(this.get("item.destination.type_name")); + } + } else { + primaryLabel = "Balance"; + secondaryLabel = "Payable account"; + iconName = "payable-account"; + } + } + if (typeName === "Refund") { + primaryLabel = this.get("item.debit.source.last_four"); + if (this.get("item.debit.source")) { + secondaryLabel = this.get("item.debit.source.type_name"); + iconName = Ember.String.dasherize(this.get("item.debit.source.type_name")); + } + } + if (typeName === "Reversal") { + if (this.get("item.order_uri")) { + primaryLabel = "Balance"; + secondaryLabel = "Order"; + iconName = "orders"; + } else if (this.get("item.isLoaded")) { + primaryLabel = "Balance"; + secondaryLabel = "Marketplace"; + iconName = "escrow"; + } + } + if (typeName === "Settlement") { + if (this.get("settlementDestination.last_four")) { + primaryLabel = this.get("settlementDestination.last_four"); + secondaryLabel = this.get("settlementDestination.type_name"); + iconName = Ember.String.dasherize(this.get("settlementDestination.type_name")); + } else if (this.get("settlementDestination.type") === "payable") { + primaryLabel = "Balance"; + secondaryLabel = "Payable acccount"; + iconName = "payable-account"; + } + } + return Utils.safeFormat('%@%@', iconName, primaryLabel, secondaryLabel).htmlSafe(); + }.property("typeName", "item.destination.last_four", "item.destination.type_name", "item.debit.source.last_four", "item.debit.source.type_name", "settlementDestination.last_four", "settlementDestination.type_name", "settlementDestination.type", "item.isLoaded", "item.order_uri"), + + settlementSource: function(attr) { + var self = this; + var store = this.container.lookup("controller:marketplace").get("store"); + store.fetchItem("account", this.get("item.source_uri")).then(function(source) { + self.set(attr, source.toLegacyModel()); + }); + return null; + }.property("item.source_uri"), - paymentMethodPrimaryLabelText: Computed.fmt('item.last_four', 'item.brand', '%@ %@'), - paymentMethodSecondaryLabelText: Ember.computed.reads('item.funding_instrument_type'), + settlementDestination: function(attr) { + var self = this; + var store = this.container.lookup("controller:marketplace").get("store"); + store.fetchItem("account", this.get("item.destination_uri")).then(function(destination) { + self.set(attr, destination.toLegacyModel()); + }); + }.property("item.destination_uri"), + + customerText: function() { + var typeName = this.get('typeName'); + + if (typeName === "Order") { + var label = '%@Merchant'; + var primaryLabel = this.get("item.seller.display_me"); + + return Utils.safeFormat(label, primaryLabel).htmlSafe(); + } else { + return this.get("item.customer.display_me"); + } + }.property("item.seller.display_me", "item.customer.display_me"), amountText: function() { - return Utils.formatCurrency(this.get("item.amount")); - }.property("item.amount") + if (this.get("typeName") === "Order") { + if (["account", "settlement", "marketplace.settlements"].contains(this.container.lookup("controller:application").get('currentRouteName'))) { + return null; + } + + var label = '%@Order balance'; + var primaryLabel = Utils.formatCurrency(this.get("item.amount_escrowed")); + return Utils.safeFormat(label, primaryLabel).htmlSafe(); + } else { + return Utils.formatCurrency(this.get("item.amount")); + } + }.property("item.amount", "typeName", "item.amount_escrowed") }); export default GroupedTransactionRowView; diff --git a/app/views/results/grouped-transactions-table.js b/app/views/results/grouped-transactions-table.js index 5fdbb5fe0..420d7801a 100644 --- a/app/views/results/grouped-transactions-table.js +++ b/app/views/results/grouped-transactions-table.js @@ -1,7 +1,7 @@ import Ember from "ember"; var GroupedTransactionsTableView = Ember.View.extend({ - layoutName: 'results/grouped-transactions-table-layout', + layoutName: 'results/grouped-transactions-inner-table-layout', tagName: 'table', classNames: ["items", "grouped-transactions"], }); diff --git a/app/views/results/intro-page.coffee b/app/views/results/intro-page.coffee new file mode 100644 index 000000000..3bbf8380d --- /dev/null +++ b/app/views/results/intro-page.coffee @@ -0,0 +1,10 @@ +`import Ember from "ember";` +`import Computed from "balanced-dashboard/utils/computed";` + +IntroPageView = Ember.View.extend( + layoutName: "results/intro-page" + classNames: ["intro-page"] + iconClassName: Computed.fmt('iconName', 'icon-%@ non-interactive'), +) + +`export default IntroPageView;` diff --git a/app/views/results/orders-results-dropdown-filter.js b/app/views/results/orders-results-dropdown-filter.js new file mode 100644 index 000000000..c4bbfa3d1 --- /dev/null +++ b/app/views/results/orders-results-dropdown-filter.js @@ -0,0 +1,62 @@ +import ResultsDropdownFilterView from "./results-dropdown-filter"; +import { defineFilter } from "./results-dropdown-filter"; + +var OrdersResultsDropdownFilterView = ResultsDropdownFilterView.extend({ + templateName: "results/grouped-results-dropdown-filter", + toggleText: "Order", + + toggleSelected: function(filterLink, groupLabelText) { + var filterGroup = this.get("filterGroups").findBy("groupLabelText", groupLabelText); + + filterGroup.filters.map(function(filter) { + filter.set("isSelected", false); + }); + + filterLink.set("isSelected", true); + + if (filterLink.get('text') === "All") { + this.set("isSelected", false); + } else { + this.set("isSelected", true); + } + }.observes("filterGroups"), + + filterGroups: function() { + return [{ + groupLabelText: "Type", + actionName: "changeTypeFilter", + filters: [ + defineFilter("All", "transaction", true), + defineFilter("Credits", "credit"), + defineFilter("Debits", "debit"), + defineFilter("Holds", "card_hold"), + defineFilter("Refunds", "refund"), + defineFilter("Reversals", "reversal") + ] + }, { + groupLabelText: "Status", + actionName: "changeStatusFilter", + filters: [ + defineFilter("All", undefined, true), + defineFilter("Pending", "pending"), + defineFilter("Succeeded", "succeeded"), + defineFilter("Failed", "failed") + ] + }]; + }.property(), + + actions: { + setFilter: function(actionName, filterLink, groupLabelText) { + var target = this.get("parentView"); + + if (this.get("isSearch")) { + target = this.container.lookup("controller:marketplace/search"); + } + + target.send(actionName, filterLink.value); + this.toggleSelected(filterLink, groupLabelText); + } + } +}); + +export default OrdersResultsDropdownFilterView; diff --git a/app/views/results/orders-table.js b/app/views/results/orders-table.js deleted file mode 100644 index 7361a4155..000000000 --- a/app/views/results/orders-table.js +++ /dev/null @@ -1,9 +0,0 @@ -import ResultsTableView from "./results-table"; - -var OrdersTableView = ResultsTableView.extend({ - tagName: 'div', - classNames: 'orders', - templateName: "results/orders-table" -}); - -export default OrdersTableView; diff --git a/app/views/results/settlements-transaction-results-dropdown-filter.js b/app/views/results/settlements-transaction-results-dropdown-filter.js new file mode 100644 index 000000000..d040c54d2 --- /dev/null +++ b/app/views/results/settlements-transaction-results-dropdown-filter.js @@ -0,0 +1,24 @@ +import ResultsDropdownFilterView from "./results-dropdown-filter"; +import { defineFilter } from "./results-dropdown-filter"; + +var SettlementsTransactionResultsDropdownFilterView = ResultsDropdownFilterView.extend({ + toggleText: "Status", + filters: function() { + return [ + defineFilter("All", null, true), + defineFilter("Needs attention & Under review", ["pending"]), + defineFilter("Won", ["won"]), + defineFilter("Lost", ["lost"]) + ]; + }.property(), + + actions: { + setFilter: function(filterLink) { + var controller = this.get('controller'); + controller.send('changeDisputeStatusFilter', filterLink.value); + this.toggleSelected(filterLink); + } + } +}); + +export default SettlementsTransactionResultsDropdownFilterView; diff --git a/app/views/results/order-transactions-table.js b/app/views/results/transactions-table-grouped-by-customer.js similarity index 54% rename from app/views/results/order-transactions-table.js rename to app/views/results/transactions-table-grouped-by-customer.js index 422cd1e4d..c0f178566 100644 --- a/app/views/results/order-transactions-table.js +++ b/app/views/results/transactions-table-grouped-by-customer.js @@ -1,9 +1,14 @@ import TransactionsTableView from "./transactions-table"; -var OrderTransactionsTableView = TransactionsTableView.extend({ - templateName: 'results/order-transactions-table', +var TransactionsTableGroupedByCustomerView = TransactionsTableView.extend({ + templateName: 'results/transactions-table-grouped-by-customer', classNames: 'non-interactive', + colspan: 4, + hideCustomerColumn: true, + hideFromColumn: Ember.computed.equal("paymentMethodText", "To"), + hideToColumn: Ember.computed.equal("paymentMethodText", "From"), + customersArray: function() { var customers = this.get("customers"); if (!Ember.isArray(customers)) { @@ -17,24 +22,24 @@ var OrderTransactionsTableView = TransactionsTableView.extend({ var results = this.get("loader.results"); var groupedTransactions = []; - results.forEach(function(transactions) { - var buyer = customers.findBy("uri", transactions.get('customer_uri')); - var customer = groupedTransactions.findBy('customer_uri', transactions.get('customer_uri')); + results.forEach(function(transaction) { + var buyer = customers.findBy("uri", transaction.get('customer_uri')); + var customer = groupedTransactions.findBy('customer_uri', transaction.get('customer_uri')); if(!customer) { customer = Ember.Object.create({ - customer_uri: transactions.get('customer_uri'), + customer_uri: transaction.get('customer_uri'), customer: buyer, transactions: [] }); groupedTransactions.pushObject(customer); } - customer.get('transactions').pushObject(transactions); + customer.get('transactions').pushObject(transaction); }); return groupedTransactions; }.property("customersArray", "loader.results.length"), }); -export default OrderTransactionsTableView; +export default TransactionsTableGroupedByCustomerView; diff --git a/app/views/results/transactions-table-grouped-by-order.js b/app/views/results/transactions-table-grouped-by-order.js new file mode 100644 index 000000000..7c08768a6 --- /dev/null +++ b/app/views/results/transactions-table-grouped-by-order.js @@ -0,0 +1,59 @@ +import TransactionsTableView from "./transactions-table"; + +var TransactionsTableGroupedByOrderView = TransactionsTableView.extend({ + layoutName: 'results/grouped-transactions-table-layout', + templateName: 'results/transactions-table-grouped-by-order', + classNames: 'non-interactive', + + colspan: function() { + return (this.get("hideCustomerColumn")) ? "4": "5"; + }.property("hideCustomerColumn"), + + groupedResults: function() { + var results = this.get("loader.results"); + + var groupedTransactions = []; + var Order = this.container.lookupFactory("model:order"); + + results.forEach(function(transaction) { + if (!_.contains(["Hold", "Refund", "Reversal"], transaction.get("type_name"))) { + // TODO: Figure out a way to include manually created holds + var orderUri = transaction.get('order_uri'); + var order = orderUri ? Order.find(orderUri) : null; + var orderGroup = groupedTransactions.findBy('order_uri', transaction.get('order_uri')); + + if (!orderGroup) { + orderGroup = Ember.Object.create({ + order_uri: orderUri, + order: order, + transactions: [] + }); + groupedTransactions.pushObject(orderGroup); + } + orderGroup.get('transactions').pushObject(transaction); + } + }); + + if (groupedTransactions.length === 0 && results.total > 0) { + results.loadNextPage(); + } + + this.set("parentView.totalOrders", groupedTransactions.length); + + return groupedTransactions; + }.property("loader.results.length"), + + actions: { + changeTypeFilter: function(type) { + if (type === "transaction") { + type = null; + } + this.set("loader.type", type); + }, + changeStatusFilter: function(status) { + this.get("loader").setStatusFilter(status); + }, + } +}); + +export default TransactionsTableGroupedByOrderView; diff --git a/app/views/results/transactions-table-grouped-by-settlement.js b/app/views/results/transactions-table-grouped-by-settlement.js new file mode 100644 index 000000000..bbeb15202 --- /dev/null +++ b/app/views/results/transactions-table-grouped-by-settlement.js @@ -0,0 +1,56 @@ +import ResultsTableView from "./results-table"; +import SearchModelArray from "balanced-dashboard/models/core/search-model-array"; + +var TransactionsTableGroupedBySettlement = ResultsTableView.extend({ + layoutName: 'results/grouped-transactions-table-layout', + templateName: 'results/transactions-table-grouped-by-settlement', + classNames: ['settlements', 'non-interactive'], + + colspan: function() { + return (this.get("hideCustomerColumn")) ? "4": "5"; + }.property("hideCustomerColumn"), + + groupedResults: function() { + var self = this; + var settlements = this.get("loader.results"); + var groupedTransactions = []; + + settlements.forEach(function(settlement) { + var promise = SearchModelArray.newArrayLoadedFromUri(settlement.get("credits_uri"), "credit"); + promise.then(function(credits) { + var settlementGroup = Ember.Object.create({ + settlement_uri: settlement.get('uri'), + settlement: settlement, + orderGroups: self.groupCreditsByOrder(credits) + }); + groupedTransactions.pushObject(settlementGroup); + }); + }); + + return groupedTransactions; + }.property("loader.results.length"), + + groupCreditsByOrder: function(credits) { + var groupedCredits = []; + var Order = this.container.lookupFactory("model:order"); + + credits.forEach(function(transaction) { + var order = Order.find(transaction.get('order_uri')); + var orderGroup = groupedCredits.findBy('order_uri', transaction.get('order_uri')); + + if (!orderGroup) { + orderGroup = Ember.Object.create({ + order_uri: transaction.get('order_uri'), + order: order, + transactions: [] + }); + groupedCredits.pushObject(orderGroup); + } + orderGroup.get('transactions').pushObject(transaction); + }); + + return groupedCredits; + } +}); + +export default TransactionsTableGroupedBySettlement; diff --git a/app/views/sidebar/marketplace-sidebar.js b/app/views/sidebar/marketplace-sidebar.js index 0d78e061f..feeadab78 100644 --- a/app/views/sidebar/marketplace-sidebar.js +++ b/app/views/sidebar/marketplace-sidebar.js @@ -1,10 +1,22 @@ import SidebarView from "./sidebar"; var SIDEBAR_ITEMS = [{ - linkText: "Payments", - linkIcon: "icon-payments", + linkText: "Orders", + linkIcon: "icon-orders", routeName: "marketplace.orders", - isSelectedBinding: "controller.controllers.marketplace.paymentSelected", + isSelectedBinding: "controller.controllers.marketplace.orderSelected", +}, { + linkText: "Settlements", + linkIcon: "icon-settlements", + routeName: "marketplace.settlements", + isSelectedBinding: "controller.controllers.marketplace.settlementSelected", + alertCount: "new", +}, { + linkText: "Disputes", + linkIcon: "icon-disputes", + routeName: "marketplace.disputes", + isSelectedBinding: "controller.controllers.marketplace.disputeSelected", + alertCountBinding: "controller.controllers.marketplace.disputeAlertCount", }, { linkText: "Customers", linkIcon: "icon-customers", @@ -15,12 +27,6 @@ var SIDEBAR_ITEMS = [{ linkIcon: "icon-card", routeName: "marketplace.funding_instruments", isSelectedBinding: "controller.controllers.marketplace.fundingInstrumentSelected" -}, { - linkText: "Disputes", - linkIcon: "icon-disputes", - routeName: "marketplace.disputes", - isSelectedBinding: "controller.controllers.marketplace.disputeSelected", - alertCountBinding: "controller.controllers.marketplace.disputeAlertCount", }, { linkText: "Logs", linkIcon: "icon-logs", diff --git a/bower.json b/bower.json index b87635460..4a722c187 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "ember-validations": "lcoq/ember-validations", "google-code-prettify": "1.0.0", "handlebars": "1.3.0", - "strapped": "0.3.14", + "strapped": "0.3.18", "jquery-csv": "", "jquery-hotkeys": "jeresig/jquery.hotkeys", "jquery.cookie": "1.3.1", diff --git a/package.json b/package.json index efcab6b72..b15a582e9 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "repository": "git@github.com:balanced/balanced-dashboard.git", "author": "Balanced ", "devDependencies": { - "balanced-addon-models": "balanced/balanced-addon-models", + "balanced-addon-models": "git://github.com/balanced/balanced-addon-models", "body-parser": "1.2.0", "bower": "1.3.12", "broccoli-asset-rev": "0.3.1", diff --git a/tests/helpers/testing.js b/tests/helpers/testing.js index aa863add0..0240715f7 100644 --- a/tests/helpers/testing.js +++ b/tests/helpers/testing.js @@ -25,7 +25,7 @@ var Testing = { // constant routes MARKETPLACES_ROUTE: '/marketplaces', - TRANSACTIONS_ROUTE: null, + MARKETPLACE_ROUTE: null, ADD_CUSTOMER_ROUTE: null, BANK_ACCOUNT_ROUTE: null, CARD_ROUTE: null, diff --git a/tests/integration/bank-account-test.js b/tests/integration/bank-account-test.js index 999dff5b7..4b71059e0 100644 --- a/tests/integration/bank-account-test.js +++ b/tests/integration/bank-account-test.js @@ -38,30 +38,25 @@ test('can view bank account page', function() { }); test('credit bank account', function() { - var stub = sinon.stub(Adapter, "create"); + var spy = sinon.spy(Adapter, "create"); visit(Testing.BANK_ACCOUNT_ROUTE) - .click(".page-navigation a:contains(Credit)") - .checkText('#credit-funding-instrument label:contains(characters max)', 'Appears on statement as (14 characters max)') + .click(".page-navigation .dropdown a:contains(Create a one-off credit)") + .checkText('#credit-funding-instrument .alert-info', '14 characters remaining') .then(function() { var attribute = $('#credit-funding-instrument input[name=appears_on_statement_as]').prop("maxLength"); equal(attribute, 14); }) .fillForm("#credit-funding-instrument", { dollar_amount: '1000', - description: 'Test credit', + credit_description: 'Test credit', appears_on_statement_as: "Test credit" }) .click('#credit-funding-instrument .modal-footer button[name=modal-submit]') - .click('#credit-funding-instrument .modal-footer button[name=modal-submit]') - .click('#credit-funding-instrument .modal-footer button[name=modal-submit]') - .click('#credit-funding-instrument .modal-footer button[name=modal-submit]') - .click('#credit-funding-instrument .modal-footer button[name=modal-submit]') .then(function() { - ok(stub.calledOnce); - deepEqual(stub.firstCall.args[0], Models.lookupFactory("credit")); - deepEqual(stub.firstCall.args[1], '/bank_accounts/' + Testing.BANK_ACCOUNT_ID + '/credits'); - matchesProperties(stub.firstCall.args[2], { + deepEqual(spy.args[0][1], '/customers', "Create customer call"); + deepEqual(spy.args[1][1], '/bank_accounts/' + Testing.BANK_ACCOUNT_ID + '/credits', "Credit bank account call"); + matchesProperties(spy.args[1][2], { amount: 100000, description: "Test credit", appears_on_statement_as: "Test credit" @@ -73,8 +68,8 @@ test('displays error message if properties are invalid', function() { var stub = sinon.stub(Adapter, "create"); visit(Testing.BANK_ACCOUNT_ROUTE) - .click(".page-navigation a:contains(Credit)") - .checkText('#credit-funding-instrument label:contains(characters max)', 'Appears on statement as (14 characters max)') + .click(".page-navigation .dropdown a:contains(Create a one-off credit)") + .checkText('#credit-funding-instrument .alert-info', '14 characters remaining') .then(function() { var attribute = $('#credit-funding-instrument input[name=appears_on_statement_as]').prop("maxLength"); equal(attribute, 14); @@ -92,7 +87,7 @@ test('displays error message if properties are invalid', function() { }); test('debit bank account', function() { - var stub = sinon.stub(Adapter, "create"); + var spy = sinon.spy(Adapter, "create"); visit(Testing.BANK_ACCOUNT_ROUTE) .then(function() { @@ -101,25 +96,24 @@ test('debit bank account', function() { }); }) .click(".page-navigation a:contains(Debit)") - .checkText('#debit-funding-instrument label:contains(characters max)', 'Appears on statement as (14 characters max)') + .checkText('#debit-funding-instrument .alert-info', '14 characters remaining') .then(function() { var attr = $('#debit-funding-instrument input[name=appears_on_statement_as]').prop('maxLength'); equal(attr, 14); }) .fillForm("#debit-funding-instrument", { dollar_amount: '1000', - description: 'Test debit', + seller_name: 'Test seller', + order_description: 'Test order', + debit_description: 'Test debit', appears_on_statement_as: "Test debit" }) .click('#debit-funding-instrument .modal-footer button[name=modal-submit]') - .click('#debit-funding-instrument .modal-footer button[name=modal-submit]') - .click('#debit-funding-instrument .modal-footer button[name=modal-submit]') - .click('#debit-funding-instrument .modal-footer button[name=modal-submit]') .then(function() { - ok(stub.calledOnce); - deepEqual(stub.firstCall.args[0], Models.lookupFactory("debit")); - deepEqual(stub.firstCall.args[1], '/bank_accounts/' + Testing.BANK_ACCOUNT_ID + '/debits'); - matchesProperties(stub.firstCall.args[2], { + deepEqual(spy.args[0][1], "/customers", "Create customer call"); + deepEqual(spy.args[1][1].replace(/CU[\w\d]+/g, "CUxxxxxx"), "/customers/CUxxxxxx/orders", "Create order call"); + deepEqual(spy.args[2][1], '/bank_accounts/' + Testing.BANK_ACCOUNT_ID + '/debits', "create debit"); + matchesProperties(spy.args[2][2], { amount: 100000, description: "Test debit", appears_on_statement_as: "Test debit" diff --git a/tests/integration/card-test.js b/tests/integration/card-test.js index 20b69a4ed..7dfb3793e 100644 --- a/tests/integration/card-test.js +++ b/tests/integration/card-test.js @@ -48,7 +48,7 @@ test('debit card', function() { }); }) .click(".page-actions a:contains(Debit)") - .check('#debit-funding-instrument label:contains(characters max)', "Appears on statement as (18 characters max)") + .check('#debit-funding-instrument .alert-info', "18 characters remaining") .then(function() { var l = $('#debit-funding-instrument input[name=appears_on_statement_as]').prop('maxlength'); equal(l, 18); diff --git a/tests/integration/charge-card-test.js b/tests/integration/create-order-test.js similarity index 57% rename from tests/integration/charge-card-test.js rename to tests/integration/create-order-test.js index e1018de04..d8ee75426 100644 --- a/tests/integration/charge-card-test.js +++ b/tests/integration/create-order-test.js @@ -9,7 +9,7 @@ import Models from "../helpers/models"; var App, Adapter; -module('Integration - Charge Card', { +module('Integration - Create Order', { setup: function() { App = startApp(); Adapter = App.__container__.lookup("adapter:main"); @@ -18,7 +18,6 @@ module('Integration - Charge Card', { }, teardown: function() { Testing.restoreMethods( - balanced.card.create, Adapter.create ); Ember.run(App, "destroy"); @@ -26,26 +25,20 @@ module('Integration - Charge Card', { }); test('form validation', 2, function() { - visit(Testing.TRANSACTIONS_ROUTE) - .click('.page-navigation a:contains(Debit a card)') + visit(Testing.MARKETPLACE_ROUTE) + .click('.page-navigation a:contains(Create an order)') .checkElements({ - "#charge-card button:contains(Debit)": 1 + "#charge-card button:contains(Create)": 1 }) - .click('button:contains(Debit)') + .click('button:contains(Create)') .check("#charge-card .form-group.has-error", 6); }); test('can charge a card', function() { var spy = sinon.spy(Adapter, 'create'); - var tokenizingStub = sinon.stub(balanced.card, 'create'); - tokenizingStub.callsArgWith(1, { - status: 201, - cards: [{ - href: '/cards/' + Testing.CARD_ID - }] - }); - visit(Testing.TRANSACTIONS_ROUTE) - .click('.page-navigation a:contains(Debit a card)') + + visit(Testing.MARKETPLACE_ROUTE) + .click('.page-navigation a:contains(Create an order)') .fillForm('#charge-card', { name: 'Tarun Chaudhry', number: '4111111111111111', @@ -53,24 +46,19 @@ test('can charge a card', function() { expiration_date: '12 / 2020', postal_code: '95014', appears_on_statement_as: 'My Charge', - description: 'Internal', + debit_description: 'Internal', dollar_amount: '12.00' }) + .click('button:contains(Create)') .then(function() { - var model = App.__container__.lookup("controller:modals-container").get("currentModal.model"); - model.saveCard = function() { - return App.__container__.lookupFactory("model:card").find("/cards/" + Testing.CARD_ID); - }; - }) - .click('button:contains(Debit)') - .then(function() { - var args = spy.firstCall.args; - ok(spy.calledOnce); + var args = spy.args; + deepEqual(spy.args[0][1], '/customers', "Create customer call"); + deepEqual(spy.args[1][1].replace(/CU[\w\d]+/g, "CUxxxxxx"), "/customers/CUxxxxxx/orders", "Create order call"); deepEqual(args.slice(0, 2), [Models.Debit, "/cards/%@/debits".fmt(Testing.CARD_ID)]); - matchesProperties(args[2], { + matchesProperties(args[2][2], { amount: "1200", appears_on_statement_as: 'My Charge', - description: 'Internal', + debit_description: 'Internal', source_uri: '/cards/' + Testing.CARD_ID }); }); diff --git a/tests/integration/customer-page-add-test.js b/tests/integration/customer-page-add-test.js index 3fd89ef47..6d3a8187d 100644 --- a/tests/integration/customer-page-add-test.js +++ b/tests/integration/customer-page-add-test.js @@ -31,6 +31,7 @@ test('can add bank account', function() { var tokenizingSpy = sinon.stub(balanced.bankAccount, "create"); visit(Testing.CUSTOMER_ROUTE) + .click('.main-panel .nav-tabs li a:contains(Payment methods)') .click('.main-panel a:contains(Add a bank account)') .fillForm('#add-bank-account', { name: "TEST", @@ -65,11 +66,11 @@ test('can add card', function() { }); visit(Testing.CUSTOMER_ROUTE) + .click('.main-panel .nav-tabs li a:contains(Payment methods)') .click('.main-panel a:contains(Add a card)') .fillForm('#add-card', { number: '1234123412341234', - expiration_date_month: 1, - expiration_date_year: 2020, + expiration_date: '1 / 2020', cvv: '123', name: 'TEST' }) @@ -107,11 +108,11 @@ test('can add card with postal code', function() { }); visit(Testing.CUSTOMER_ROUTE) + .click('.main-panel .nav-tabs li a:contains(Payment methods)') .click('.main-panel a:contains(Add a card)') .fillForm('#add-card', { number: '1234123412341234', - expiration_date_month: 1, - expiration_date_year: 2020, + expiration_date: '1 / 2020', cvv: '123', name: 'TEST', address_postal_code: '94612' @@ -149,11 +150,11 @@ test('can add card with address', function() { }); visit(Testing.CUSTOMER_ROUTE) + .click('.main-panel .nav-tabs li a:contains(Payment methods)') .click('.main-panel a:contains(Add a card)') .fillForm('#add-card', { number: '1234123412341234', - expiration_date_month: 1, - expiration_date_year: 2020, + expiration_date: '1 / 2020', cvv: '123', name: 'TEST', address_postal_code: '94612', @@ -187,5 +188,5 @@ test('can add card with address', function() { test('verification renders properly against rev1', function() { visit(Testing.CUSTOMER_ROUTE) - .check(".status.verified span", "Verified"); + .check(".status.succeeded span", "Verified"); }); diff --git a/tests/integration/customer-page-credit-test.js b/tests/integration/customer-page-credit-test.js index bae8e50fe..57a4535af 100644 --- a/tests/integration/customer-page-credit-test.js +++ b/tests/integration/customer-page-credit-test.js @@ -24,7 +24,7 @@ module('Integration - Customer Page: Credit', { }); test('can credit to a debit card', function() { - var spy = sinon.stub(Adapter, "create"); + var spy = sinon.spy(Adapter, "create"); visit(Testing.CUSTOMER_ROUTE) .then(function() { @@ -71,7 +71,7 @@ test('can credit to a debit card', function() { "can_debit": false, "id": "BAxxxxxxxxxxxxxxxxxxxxxxx" }]); - + m.set("hasCreditableOrders", true); m.set("cards", cards); m.set("bank_accounts", bankAccounts); }); @@ -88,27 +88,31 @@ test('can credit to a debit card', function() { .fillForm('#credit-customer', { dollar_amount: '1', appears_on_statement_as: "DEBIT", - description: 'Test credit to a debit card' + credit_description: 'Test credit to a debit card' }, { click: '.modal-footer button[name=modal-submit]' }) .then(function() { var card = BalancedApp.__container__.lookup("controller:customer").get("model.creditable_cards").objectAt(0); - ok(spy.calledOnce, "Create was called once"); - equal(spy.firstCall.args[0], Models.lookupFactory("credit")); - equal(spy.firstCall.args[1], '/cards/CCxxxxxxxxxxxxxxxxxxx/credits'); - - deepEqual(spy.firstCall.args[2].amount, '100'); - deepEqual(spy.firstCall.args[2].description, "Test credit to a debit card"); + equal(spy.args[0][1], '/customers', "Create customer call"); + equal(spy.args[1][1], '/cards/CCxxxxxxxxxxxxxxxxxxx/credits'); + equal(spy.args[1][2].amount, '100'); + equal(spy.args[1][2].description, "Test credit to a debit card"); }); }); test('when crediting customer triggers an error, the error is displayed to the user', function() { visit(Testing.CUSTOMER_ROUTE) + .then(function() { + var m = BalancedApp.__container__.lookup("controller:customer").get("model"); + Ember.run(function() { + m.set("hasCreditableOrders", true); + }); + }) .click(".page-navigation a:contains(Credit)") .fillForm('#credit-customer', { dollar_amount: '10', - description: 'Test credit' + credit_description: 'Test credit' }, { click: '.modal-footer button[name=modal-submit]' }) @@ -121,11 +125,17 @@ test("can't credit customer multiple times using the same modal", function() { var stub = sinon.stub(Adapter, "create"); visit(Testing.CUSTOMER_ROUTE) + .then(function() { + var m = BalancedApp.__container__.lookup("controller:customer").get("model"); + Ember.run(function() { + m.set("hasCreditableOrders", true); + }); + }) .click(".page-navigation a:contains(Credit)") .fillForm('#credit-customer', { dollar_amount: '1000', appears_on_statement_as: "SODA", - description: 'Test credit' + credit_description: 'Test credit' }) .click('#credit-customer .modal-footer button[name=modal-submit]') .click('#credit-customer .modal-footer button[name=modal-submit]') diff --git a/tests/integration/customer-page-delete-test.js b/tests/integration/customer-page-delete-test.js index e4f2f3b9e..a63aee60a 100644 --- a/tests/integration/customer-page-delete-test.js +++ b/tests/integration/customer-page-delete-test.js @@ -25,19 +25,6 @@ module("Integration - Customer Page: Delete", { } }); - - - - - - - - - - - - - test('can delete bank account', function() { var spy = sinon.stub(Adapter, "delete"); var ModelClass = BalancedApp.__container__.lookupFactory("model:bank-account"); @@ -69,6 +56,7 @@ test('can delete bank account', function() { })); }); }) + .click(".main-panel .nav-tabs a:contains(Payment methods)") .click("table.items.funding-instruments tr.type-bank-account .funding-instrument-delete:last") .click('#delete-funding-instrument button[name=modal-submit]') .then(function() { @@ -107,6 +95,7 @@ test('can delete cards', function() { })); }); }) + .click(".main-panel .nav-tabs a:contains(Payment methods)") .click("table.items.funding-instruments tr.type-card .funding-instrument-delete:last") .click('#delete-funding-instrument button[name=modal-submit]') .then(function() { diff --git a/tests/integration/customer-test.js b/tests/integration/customer-test.js index 29a3e53f2..089203f4f 100644 --- a/tests/integration/customer-test.js +++ b/tests/integration/customer-test.js @@ -158,7 +158,7 @@ test('can debit customer using card', function() { var spy, fundingInstrumentUri; andThen(function() { - spy = sinon.stub(Adapter, "create"); + spy = sinon.spy(Adapter, "create"); }); visit(Testing.CUSTOMER_ROUTE) @@ -173,17 +173,17 @@ test('can debit customer using card', function() { }) .fillForm("#debit-customer", { dollar_amount: "1000", - description: "Card debit", + debit_description: "Card debit", appears_on_statement_as: "Cool" }) .click('#debit-customer .modal-footer button[name=modal-submit]') .then(function() { - ok(spy.calledOnce, "Called once"); - var args = spy.firstCall.args; + console.log(spy.args) + var args = spy.args; deepEqual(args.slice(0, 2), [Models.lookupFactory("debit"), "/cards/%@/debits".fmt(Testing.CARD_ID)]); - matchesProperties(args[2], { + matchesProperties(args[1][2], { amount: "100000", - description: "Card debit", + debit_description: "Card debit", appears_on_statement_as: "Cool", source_uri: "/cards/%@".fmt(Testing.CARD_ID) }); @@ -210,7 +210,7 @@ test('can debit customer using bank account', function() { }) .fillForm('#debit-customer', { dollar_amount: '1000', - description: 'Test debit', + debit_description: 'Test debit', appears_on_statement_as: "Cool", }, { click: '.modal-footer button[name=modal-submit]' @@ -221,7 +221,7 @@ test('can debit customer using bank account', function() { deepEqual(args.slice(0, 2), [Models.lookupFactory('debit'), "/bank_accounts/%@/debits".fmt(Testing.BANK_ACCOUNT_ID)]); matchesProperties(args[2], { amount: "100000", - description: "Test debit", + debit_description: "Test debit", appears_on_statement_as: "Cool", source_uri: "/bank_accounts/%@".fmt(Testing.BANK_ACCOUNT_ID) }); @@ -244,7 +244,7 @@ test("can't debit customer multiple times using the same modal", function() { }) .fillForm('#debit-customer', { dollar_amount: '1000', - description: 'Test debit', + debit_description: 'Test debit', appears_on_statement_as: "Cool", }) .click('#debit-customer .modal-footer button[name=modal-submit]') @@ -257,7 +257,7 @@ test("can't debit customer multiple times using the same modal", function() { matchesProperties(spy.firstCall.args[2], { amount: "100000", appears_on_statement_as: "Cool", - description: "Test debit", + debit_description: "Test debit", source_uri: "/bank_accounts/%@".fmt(Testing.BANK_ACCOUNT_ID) }); }); @@ -270,7 +270,7 @@ test("debit customer triggers reload of transactions", function() { .click(".page-navigation a:contains(Debit)") .fillForm('#debit-customer', { dollar_amount: '1000', - description: 'Test debit' + debit_description: 'Test debit' }, { click: '.modal-footer button[name=modal-submit]' }) diff --git a/tests/integration/marketplace-settings-guest-test.js b/tests/integration/marketplace-settings-guest-test.js index 03ff5b701..8d70c0eca 100644 --- a/tests/integration/marketplace-settings-guest-test.js +++ b/tests/integration/marketplace-settings-guest-test.js @@ -225,8 +225,7 @@ test('can create cards', function() { name: "TEST", number: "1234123412341234", cvv: "123", - expiration_date_year: "2020", - expiration_date_month: "1" + expiration_date: "1 / 2020" }) .click('#add-card .modal-footer button[name=modal-submit]') .then(function() { diff --git a/tests/integration/transactions-test.js b/tests/integration/marketplace-test.js similarity index 67% rename from tests/integration/transactions-test.js rename to tests/integration/marketplace-test.js index bdca1be09..e8a865c32 100644 --- a/tests/integration/transactions-test.js +++ b/tests/integration/marketplace-test.js @@ -12,7 +12,7 @@ var App, Adapter, ADD_FUNDS_SELECTOR = "#marketplace-escrow-menu a:contains(Add funds)", WITHDRAW_FUNDS_SELECTOR = "#marketplace-escrow-menu a:contains(Withdraw funds)"; -module('Integration - Transactions', { +module('Integration - Marketplace', { setup: function() { App = startApp(); Adapter = App.__container__.lookup("adapter:main"); @@ -38,40 +38,12 @@ var setupMarketplaceController = function(bankAccounts) { })); }); }; -var assertQueryString = function(string, expected) { - var qsParameters = Utils.queryStringToObject(string); - _.each(expected, function(value, key) { - deepEqual(qsParameters[key], value, "Query string parameter %@".fmt(key)); - }); -}; - -var getResultsUri = function() { - var controller = BalancedApp.__container__.lookup("controller:marketplace/transactions"); - return controller.get("resultsLoader.resultsUri"); -}; - -test('can visit page', function() { - visit(Testing.TRANSACTIONS_ROUTE) - .click(".nav-pills a:contains(Transactions)") - .checkPageTitle("Payments") - .checkElements({ - '.payments-navbar a:contains(Export)': 1 - }) - .then(function() { - var resultsUri = getResultsUri(); - deepEqual(resultsUri.split("?")[0], '/transactions', 'Transactions URI is correct'); - assertQueryString(resultsUri, { - limit: "50", - sort: "created_at,desc" - }); - }); -}); test('add funds', function() { var spy = sinon.spy(Adapter, "create"); var bankAccounts = Models.BankAccount.findAll(); - visit(Testing.TRANSACTIONS_ROUTE) + visit(Testing.MARKETPLACE_ROUTE) .then(function() { setupMarketplaceController(bankAccounts); }) @@ -79,7 +51,7 @@ test('add funds', function() { .checkElements({ "#add-funds:visible": 1, '#add-funds select option': 1, - '#add-funds label:contains(characters max)': 'Appears on statement as (14 characters max)', + '#add-funds .alert-info': '14 characters remaining', '#add-funds input[name=appears_on_statement_as][maxlength=14]': 1 }) .fillForm("#add-funds form", { @@ -107,7 +79,7 @@ test('add funds only adds once despite multiple clicks', function() { var stub = sinon.stub(Adapter, "create"); var bankAccounts = Models.BankAccount.findAll(); - visit(Testing.TRANSACTIONS_ROUTE) + visit(Testing.MARKETPLACE_ROUTE) .then(function() { setupMarketplaceController(bankAccounts); }) @@ -127,7 +99,7 @@ test('withdraw funds', function() { var spy = sinon.spy(Adapter, "create"); var bankAccounts = Models.BankAccount.findAll(); - visit(Testing.TRANSACTIONS_ROUTE) + visit(Testing.MARKETPLACE_ROUTE) .then(function() { setupMarketplaceController(bankAccounts); }) @@ -141,7 +113,7 @@ test('withdraw funds', function() { .checkElements({ '#withdraw-funds:visible': 1, '#withdraw-funds select option': 1, - '#withdraw-funds label.control-label:contains(characters max)': 'Appears on statement as (14 characters max)' + '#withdraw-funds .alert-info': 'Available balance: $014 characters remaining' }) .fillForm("#withdraw-funds form", { "dollar_amount": "55.55", @@ -167,7 +139,7 @@ test('withdraw funds only withdraws once despite multiple clicks', function() { var stub = sinon.stub(Adapter, "create"); var bankAccounts = Models.BankAccount.findAll(); - visit(Testing.TRANSACTIONS_ROUTE) + visit(Testing.MARKETPLACE_ROUTE) .then(function() { setupMarketplaceController(bankAccounts); }) @@ -182,10 +154,10 @@ test('withdraw funds only withdraws once despite multiple clicks', function() { }); }); -test('download activity', function() { +test('download transactions', function() { var stub; - visit(Testing.TRANSACTIONS_ROUTE) + visit(Testing.MARKETPLACE_ROUTE) .then(function() { var controller = BalancedApp.__container__.lookup("controller:marketplace/transactions"); var loader = controller.get("resultsLoader"); @@ -211,43 +183,3 @@ test('download activity', function() { "#header .notification-center-message:last": "We're processing your request. We will email you once the exported data is ready to view." }); }); - -test('transactions date sort has different states', function() { - var objectPath = ".results th.date .sortable"; - var states = []; - var count = 0; - var testAmount = 5; - - visit(Testing.TRANSACTIONS_ROUTE) - .then(function() { - ok($(objectPath).is(".descending"), "Search defaults to descending"); - }) - .click(objectPath) - .then(function() { - ok($(objectPath).is(".ascending"), "Search is set to ascending"); - }); -}); - -test('Filter Activity transactions table by type & status', function() { - visit(Testing.TRANSACTIONS_ROUTE) - .click('#content .results table.transactions th.type .type-filter li a:contains(Holds)') - .then(function() { - var resultsUri = getResultsUri(); - deepEqual(resultsUri.split("?")[0], '/transactions', 'Activity Transactions URI is correct'); - assertQueryString(resultsUri, { - type: "hold", - 'status[in]': 'failed,succeeded,pending', - limit: "50", - sort: "created_at,desc" - }); - }) - .click('#content table.transactions th.type a:contains(All)') - .click("#content table.transactions th.status a:contains(Succeeded)") - .then(function() { - assertQueryString(getResultsUri(), { - status: "succeeded", - limit: "50", - sort: "created_at,desc" - }); - }); -}); diff --git a/tests/integration/marketplaces-test.js b/tests/integration/marketplaces-test.js index 93bbdb401..d8e58cd0e 100644 --- a/tests/integration/marketplaces-test.js +++ b/tests/integration/marketplaces-test.js @@ -100,7 +100,7 @@ test('delete marketplace', function() { var uri; visit(Testing.MARKETPLACES_ROUTE) - .click(".marketplace-list.test li:first-of-type .icon-delete") + .click(".marketplace-list.test li:first-of-type .icon-table-x") .then(function() { stub = sinon.stub(jQuery, "ajax").returns(Ember.RSVP.resolve()); var mp = BalancedApp.__container__.lookup("controller:modals-container").get("currentModal.marketplace"); diff --git a/tests/integration/orders-test.js b/tests/integration/orders-test.js index 4802299d6..62bcd1215 100644 --- a/tests/integration/orders-test.js +++ b/tests/integration/orders-test.js @@ -39,27 +39,37 @@ var assertQueryString = function(string, expected) { test("can visit orders page", function() { visit(Testing.MARKETPLACE_ROUTE) - .click(".sidebar a:contains(Payments)") - .click(".nav-pills a:contains(Orders)") - .checkPageTitle("Payments") + .checkPageTitle("Orders") .then(function() { var resultsUri = BalancedApp.__container__.lookup('controller:marketplace/orders').get("resultsLoader.resultsUri"); - deepEqual(resultsUri.split("?")[0], "/orders"); + deepEqual(resultsUri.split("?")[0], "/transactions"); assertQueryString(resultsUri, { - limit: "20", + limit: "50", }); }); }); -test('can sort orders by date', function() { +test('Filter orders table by type & status', function() { visit(Testing.MARKETPLACE_ROUTE) - .click("#order-sort-menu") - .click(".dropdown-menu a:contains(Date created: oldest)") + .click('#content .results table.transactions th:first-of-type li a:contains(Holds)') .then(function() { var resultsUri = getResultsUri(); + deepEqual(resultsUri.split("?")[0], '/transactions', 'Activity Transactions URI is correct'); assertQueryString(resultsUri, { - sort: "created_at,asc" + type: "card_hold", + 'status[in]': 'failed,succeeded,pending', + limit: "50", + sort: "created_at,desc" + }); + }) + .click('#content table.transactions th:first-of-type a:contains(All)') + .click("#content table.transactions th.status a:contains(Succeeded)") + .then(function() { + assertQueryString(getResultsUri(), { + status: "succeeded", + limit: "50", + sort: "created_at,desc" }); }); }); @@ -84,6 +94,6 @@ test('displays correct number of charges and payouts per customer', function() { ".customer-group": 2, ".grouped-transactions-container": 2, ".grouped-transactions-container .grouped-transactions": 3, - ".grouped-transactions-container .grouped-transactions tr": 5 + ".grouped-transactions-container .grouped-transactions tr": 8 }); }); diff --git a/tests/integration/pay-seller-test.js b/tests/integration/pay-seller-test.js index 60c6796df..81e3b6fad 100644 --- a/tests/integration/pay-seller-test.js +++ b/tests/integration/pay-seller-test.js @@ -26,8 +26,8 @@ module('Integration - Pay Seller', { test('can pay a seller', function() { var stub = sinon.stub(Adapter, "create"); - visit(Testing.TRANSACTIONS_ROUTE) - .click(".page-navigation a:contains(Credit a bank account)") + visit(Testing.MARKETPLACE_ROUTE) + .click(".page-navigation .dropdown a:contains(Create a one-off credit)") .fillForm('#pay-seller', { 'name': 'TEST', 'routing_number': '123123123', @@ -37,9 +37,10 @@ test('can pay a seller', function() { 'appears_on_statement_as': 'Transaction', 'description': "Cool" }) - .click('#pay-seller .modal-footer button:contains(Credit)') + .click('#pay-seller .modal-footer button:contains(Create)') .then(function() { var args = stub.firstCall.args; + console.log(args); deepEqual(args.slice(0, 2), [Models.lookupFactory("credit"), "/credits"]); matchesProperties(args[2], { amount: "9800", @@ -58,8 +59,8 @@ test('can pay a seller', function() { test('pay a seller only submits once despite multiple button clicks', function() { var stub = sinon.stub(Adapter, "create"); - visit(Testing.TRANSACTIONS_ROUTE) - .click(".page-navigation a:contains(Credit a bank account)") + visit(Testing.MARKETPLACE_ROUTE) + .click(".page-navigation a:contains(Create a one-off credit)") .fillForm('#pay-seller', { 'name': 'TEST', 'routing_number': '123123123', @@ -68,7 +69,7 @@ test('pay a seller only submits once despite multiple button clicks', function() 'dollar_amount': '98', 'appears_on_statement_as': 'Transaction' }, { - clickMultiple: '.modal-footer button:contains(Credit)' + clickMultiple: '.modal-footer button:contains(Create)' }) .then(function() { ok(stub.calledOnce); diff --git a/tests/integration/search-test.js b/tests/integration/search-test.js index 1860b1c5d..43b277f9d 100644 --- a/tests/integration/search-test.js +++ b/tests/integration/search-test.js @@ -112,26 +112,7 @@ test('search date range pick', function() { "created_at[<]": "2013-08-01T23:59:00.000Z", "created_at[>]": "2013-08-01T00:00:00.000Z", sort: "created_at,desc", - "type[in]": "credit,debit,card_hold,refund,reversal" + "type[in]": "debit,credit,card_hold,refund,reversal,order" }); }); }); - -test('search date sort has two states', function() { - var objectPath = "#search-modal .results th.date .sortable"; - - visit(Testing.MARKETPLACE_ROUTE) - .then(function() { - Testing.runSearch(''); - stubResults(); - }) - .checkElements({ - "#search-modal .results th.date .sortable.descending": 1 - }) - .then(function() { - $(objectPath).click(); - }) - .checkElements({ - "#search-modal .results th.date .sortable.ascending": 1 - }); -}); diff --git a/tests/unit/models/hold-test.coffee b/tests/unit/models/hold-test.coffee index 9c5c5402b..35332c053 100644 --- a/tests/unit/models/hold-test.coffee +++ b/tests/unit/models/hold-test.coffee @@ -37,8 +37,6 @@ test "#is_expired", -> s.set("expires_at_date", date) deepEqual(s.get("is_expired"), true) - - test "#can_void_or_capture", -> s = Hold.create() t = (expected) -> diff --git a/tests/unit/models/results-loaders/marketplace-search-results-loader-test.js b/tests/unit/models/results-loaders/marketplace-search-results-loader-test.js index 95fc587fd..00dcca9bd 100644 --- a/tests/unit/models/results-loaders/marketplace-search-results-loader-test.js +++ b/tests/unit/models/results-loaders/marketplace-search-results-loader-test.js @@ -30,7 +30,7 @@ test("#results", function() { subject.set("query", "Kermit"); subject.get("results"); deepEqual(searchStub.firstCall.args, [ - "/search?sort=created_at%2Cdesc&type%5Bin%5D=credit%2Cdebit%2Ccard_hold%2Crefund%2Creversal&q=Kermit", + "/search?sort=created_at%2Cdesc&type%5Bin%5D=debit%2Ccredit%2Ccard_hold%2Crefund%2Creversal&q=Kermit", Customer ]); }); diff --git a/tests/unit/views/modals/search-modal-test.coffee b/tests/unit/views/modals/search-modal-test.coffee index 277db6b7c..aea335fdc 100644 --- a/tests/unit/views/modals/search-modal-test.coffee +++ b/tests/unit/views/modals/search-modal-test.coffee @@ -10,10 +10,11 @@ test "selected tab", -> subject.set "selectedTabType", tabValue deepEqual subject.get(selectionTest), true, "#{selectionTest} is true when value is '#{tabValue}'" - t("order", "isOrdersTabSelected") - t("transaction", "isTransactionsTabSelected") + t("order_transaction", "isOrdersTabSelected") t("customer", "isCustomersTabSelected") t("funding_instrument", "isFundingInstrumentsTabSelected") + t("account", "isAccountsTabSelected") + t("settlement", "isSettlementsTabSelected") t("log", "isLogsTabSelected") test "#totalResults", -> diff --git a/tests/unit/views/results/order-transactions-table-test.js b/tests/unit/views/results/order-transactions-table-test.js index d71fb2bb6..f219262e4 100644 --- a/tests/unit/views/results/order-transactions-table-test.js +++ b/tests/unit/views/results/order-transactions-table-test.js @@ -1,4 +1,4 @@ -import OrderTransactionsTable from "balanced-dashboard/views/results/order-transactions-table"; +import OrderTransactionsTable from "balanced-dashboard/views/results/transactions-table-grouped-by-customer"; import Testing from "balanced-dashboard/tests/helpers/testing"; module("View - OrderTransactionsTable", { diff --git a/tests/unit/views/sidebar/marketplace-sidebar-test.js b/tests/unit/views/sidebar/marketplace-sidebar-test.js index 4f321400f..579d85939 100644 --- a/tests/unit/views/sidebar/marketplace-sidebar-test.js +++ b/tests/unit/views/sidebar/marketplace-sidebar-test.js @@ -10,7 +10,7 @@ test("#sidebarItemsDefinition", function() { } }); - deepEqual(view.get("items.length"), 7); + deepEqual(view.get("items.length"), 8); view.set("marketplace", null); deepEqual(view.get("items.length"), 0); });