killbill-deposit 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +48 -0
  4. data/Rakefile +35 -0
  5. data/app/assets/javascripts/application.js +18 -0
  6. data/app/assets/javascripts/deposit/deposit.js +7 -0
  7. data/app/assets/stylesheets/application.css +19 -0
  8. data/app/assets/stylesheets/bootstrap_and_overrides.css +13 -0
  9. data/app/assets/stylesheets/deposit/deposit.css +16 -0
  10. data/app/controllers/deposit/collection_controller.rb +149 -0
  11. data/app/controllers/deposit/engine_controller.rb +32 -0
  12. data/app/helpers/deposit/application_helper.rb +6 -0
  13. data/app/views/deposit/collection/_invoices_table.html.erb +68 -0
  14. data/app/views/deposit/collection/_payment_form.html.erb +53 -0
  15. data/app/views/deposit/collection/index.html.erb +10 -0
  16. data/app/views/deposit/layouts/deposit_application.html.erb +31 -0
  17. data/config/routes.rb +12 -0
  18. data/lib/deposit.rb +30 -0
  19. data/lib/deposit/client.rb +47 -0
  20. data/lib/deposit/engine.rb +20 -0
  21. data/lib/deposit/version.rb +5 -0
  22. data/test/deposit_test.rb +9 -0
  23. data/test/dummy/README.rdoc +28 -0
  24. data/test/dummy/Rakefile +6 -0
  25. data/test/dummy/app/assets/config/manifest.js +1 -0
  26. data/test/dummy/app/controllers/application_controller.rb +5 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/config.ru +4 -0
  29. data/test/dummy/config/application.rb +26 -0
  30. data/test/dummy/config/boot.rb +3 -0
  31. data/test/dummy/config/environment.rb +5 -0
  32. data/test/dummy/config/environments/development.rb +54 -0
  33. data/test/dummy/config/environments/production.rb +91 -0
  34. data/test/dummy/config/environments/test.rb +42 -0
  35. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  36. data/test/dummy/config/initializers/assets.rb +14 -0
  37. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  39. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  40. data/test/dummy/config/initializers/inflections.rb +16 -0
  41. data/test/dummy/config/initializers/killbill_client.rb +3 -0
  42. data/test/dummy/config/initializers/mime_types.rb +4 -0
  43. data/test/dummy/config/initializers/new_framework_defaults_5_1.rb +14 -0
  44. data/test/dummy/config/initializers/session_store.rb +3 -0
  45. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  46. data/test/dummy/config/locales/en.yml +33 -0
  47. data/test/dummy/config/routes.rb +4 -0
  48. data/test/dummy/config/secrets.yml +32 -0
  49. data/test/integration/navigation_test.rb +12 -0
  50. data/test/test_helper.rb +21 -0
  51. metadata +317 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: '09496c07a7db3caa0dcf65fc18faf53d19a0cb17'
4
+ data.tar.gz: 76ce6e54f69e81e6159f1f5af4851d31f6ca2fd0
5
+ SHA512:
6
+ metadata.gz: 451564c625a229a0f661a2eb6c2df28ad60ba80d61e019bef72cc0029dc333ca6f52f044459e40c4275a5b5540d5665bc8788261bf41b4277c4d076a5e452c43
7
+ data.tar.gz: 2bd27f8b964de1455b9d6d1c8c9e04556b4b752d2157438c1e8212c2fa7c1a4ff0e15eb93deecdae4bba7725ee015817634ccae20a6a09a54ebd68860d823aab
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright 2021 Equinix, Inc
2
+ Copyright 2021 The Billing Project, LLC
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ Overview
2
+ ========
3
+
4
+ This Rails mountable engine allows you to manage your Deposit integration in Kill Bill.
5
+
6
+ * See [killbill-deposit-plugin](https://github.com/killbill/killbill-deposit-plugin) to get started with the Deposit plugin.
7
+ * See [killbill-admin-ui-standalone](https://github.com/killbill/killbill-admin-ui-standalone) to get started with the Kill Bill Admin UI.
8
+
9
+ Kill Bill compatibility
10
+ -----------------------
11
+
12
+ | Deposit UI version | Kill Bill version |
13
+ | ----------------: | ----------------: |
14
+ | 0.0.y | 0.22.z |
15
+
16
+ Functionality
17
+ -------------
18
+
19
+ This app lets you:
20
+
21
+ * Record and categorize external payments as Cash, Check, Bank Transfer, etc.
22
+ * Apply payments to one or multiple invoices.
23
+ * Identify invoice line items that are unpaid (e.g. if the customer is disputing a subset of the invoice).
24
+
25
+ Getting Started
26
+ ---------------
27
+
28
+ You can run this locally by using the test/dummy app provided.
29
+
30
+ To do so, specify your Kill Bill server url, api key and secret in ```test/dummy/config/initializers/killbill_client.rb```:
31
+
32
+ ```
33
+ KillBillClient.url = 'http://127.0.0.1:8080/'
34
+ KillBillClient.api_key = 'bob'
35
+ KillBillClient.api_secret = 'lazar'
36
+ ```
37
+
38
+ Then, simply run:
39
+
40
+ ```
41
+ rails s
42
+ ```
43
+
44
+ To run tests:
45
+
46
+ ```
47
+ rails t
48
+ ```
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Deposit'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.rdoc')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ Bundler::GemHelper.install_tasks
25
+
26
+ require 'rake/testtask'
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'lib'
30
+ t.libs << 'test'
31
+ t.pattern = 'test/**/*_test.rb'
32
+ t.verbose = false
33
+ end
34
+
35
+ task default: :test
@@ -0,0 +1,18 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require twitter/bootstrap
16
+ //= require dataTables/jquery.dataTables
17
+ //= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
18
+ //= require deposit/deposit
@@ -0,0 +1,7 @@
1
+ // This is a manifest file that'll be compiled into including all the files listed below.
2
+ // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3
+ // be included in the compiled file accessible from http://example.com/assets/application.js
4
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5
+ // the compiled file.
6
+ //
7
+ //= require_tree .
@@ -0,0 +1,19 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_self
14
+ *= require dataTables/jquery.dataTables
15
+ *= require dataTables/bootstrap/3/jquery.dataTables.bootstrap
16
+ *= require font-awesome
17
+ *= require bootstrap_and_overrides
18
+ *= require deposit/deposit
19
+ */
@@ -0,0 +1,13 @@
1
+ /*
2
+ *= require twitter-bootstrap-static/bootstrap
3
+ */
4
+
5
+ /* Override Bootstrap 3 font locations */
6
+ @font-face {
7
+ font-family: 'Glyphicons Halflings';
8
+ src: url('../assets/glyphicons-halflings-regular.eot');
9
+ src: url('../assets/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
10
+ url('../assets/glyphicons-halflings-regular.woff') format('woff'),
11
+ url('../assets/glyphicons-halflings-regular.ttf') format('truetype'),
12
+ url('../assets/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
13
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
3
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
5
+ *= require_tree .
6
+ */
7
+
8
+ .payment_amount_invoice {
9
+ width: 100%;
10
+ }
11
+
12
+ #deposit_amount_mismatch {
13
+ text-align: right;
14
+ padding-top: 15px;
15
+ visibility: hidden;
16
+ }
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deposit/client'
4
+
5
+ module Deposit
6
+ class CollectionController < EngineController
7
+ def index
8
+ @account_id = params[:account_id]
9
+ @currency = params[:currency] || 'USD'
10
+ end
11
+
12
+ def record_payments
13
+ cached_options_for_klient = options_for_klient
14
+
15
+ payments = {}
16
+ params.each do |k, amount|
17
+ next unless k.start_with?('payment_amount_')
18
+
19
+ _, invoice_nb = k.split('payment_amount_')
20
+ payments[invoice_nb] = amount
21
+ end
22
+
23
+ begin
24
+ Killbill::Deposit::DepositClient.record_payments(params[:account_id],
25
+ params[:effective_date],
26
+ params[:payment_reference_number],
27
+ params[:deposit_type],
28
+ payments,
29
+ cached_options_for_klient[:username],
30
+ params[:reason],
31
+ params[:comment],
32
+ cached_options_for_klient)
33
+
34
+ flash[:notice] = 'Deposit successfully collected'
35
+ redirect_to kaui_engine.account_path(params[:account_id])
36
+ rescue StandardError => e
37
+ flash.now[:error] = case e
38
+ when ::KillBillClient::API::NotFound
39
+ 'Unable to record payment: invoice not found'
40
+ when ::KillBillClient::API::BadRequest
41
+ 'Unable to record payment: deposit type, reference date, and reference number must be specified'
42
+ when ::KillBillClient::API::UnprocessableEntity
43
+ 'Unable to record payment: amount too small or payment in UNKNOWN state'
44
+ else
45
+ "Internal error: #{as_string(e)}"
46
+ end
47
+
48
+ @account_id = params[:account_id]
49
+ @currency = params[:currency] || 'USD'
50
+
51
+ render :index
52
+ end
53
+ end
54
+
55
+ def account_invoices
56
+ cached_options_for_klient = options_for_klient
57
+
58
+ searcher = lambda do |search_key, _offset, _limit|
59
+ account = begin
60
+ KillBillClient::Model::Account.find_by_id(search_key, false, false,
61
+ cached_options_for_klient)
62
+ rescue StandardError
63
+ nil
64
+ end
65
+ if account.nil?
66
+ []
67
+ else
68
+ account.invoices(cached_options_for_klient)
69
+ end
70
+ end
71
+
72
+ data_extractor = lambda do |invoice, column|
73
+ [
74
+ invoice.invoice_number.to_i,
75
+ invoice.invoice_date,
76
+ invoice.amount,
77
+ invoice.balance,
78
+ nil
79
+ ][column]
80
+ end
81
+
82
+ formatter = lambda do |invoice|
83
+ [
84
+ invoice.invoice_number,
85
+ invoice.invoice_date,
86
+ view_context.humanized_money_with_symbol(Money.from_amount(invoice.amount.to_f, invoice.currency)),
87
+ view_context.humanized_money_with_symbol(Money.from_amount(invoice.balance.to_f, invoice.currency)),
88
+ nil
89
+ ]
90
+ end
91
+
92
+ begin
93
+ search_key = (params[:search] || {})[:value].presence
94
+ offset = (params[:start] || 0).to_i
95
+ limit = (params[:length] || 50).to_i
96
+ pages = searcher.call(search_key, offset, limit)
97
+ rescue StandardError => e
98
+ error = e.to_s
99
+ end
100
+
101
+ json = {
102
+ draw: (params[:draw] || 0).to_i,
103
+ # We need to fill-in a number to make DataTables happy
104
+ recordsTotal: pages.nil? ? 0 : limit,
105
+ recordsFiltered: pages.nil? ? 0 : limit,
106
+ data: []
107
+ }
108
+ json[:error] = error unless error.nil?
109
+
110
+ pages ||= []
111
+
112
+ # Until we support server-side sorting
113
+ ordering = ((params[:order] || {})[:'0'] || {})
114
+ ordering_column = (ordering[:column] || 0).to_i
115
+ ordering_dir = ordering[:dir] || 'asc'
116
+ unless search_key.nil?
117
+ pages.sort! do |a, b|
118
+ a = data_extractor.call(a, ordering_column)
119
+ b = data_extractor.call(b, ordering_column)
120
+ sort = a <=> b
121
+ sort.nil? ? -1 : sort
122
+ end
123
+ end
124
+ pages.reverse! if ordering_dir == 'desc' && limit >= 0 || ordering_dir == 'asc' && limit.negative?
125
+
126
+ pages.each { |page| json[:data] << formatter.call(page) }
127
+
128
+ respond_to do |format|
129
+ format.json { render json: json }
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def options_for_klient
136
+ user = current_tenant_user
137
+ {
138
+ username: user[:username],
139
+ password: user[:password],
140
+ session_id: user[:session_id],
141
+ api_key: user[:api_key],
142
+ api_secret: user[:api_secret],
143
+ # Pass the X-Request-Id seen by Rails to Kill Bill
144
+ # Note that this means that subsequent requests issued by a single action will share the same X-Request-Id in Kill Bill
145
+ request_id: request.request_id
146
+ }
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deposit
4
+ class EngineController < ApplicationController
5
+ layout :get_layout
6
+
7
+ # Used to format flash error messages
8
+ def as_string(e)
9
+ if e.is_a?(KillBillClient::API::ResponseError)
10
+ "Error #{e.response.code}: #{as_string_from_response(e.response.body)}"
11
+ else
12
+ log_rescue_error(e)
13
+ e.message
14
+ end
15
+ end
16
+
17
+ def log_rescue_error(error)
18
+ Rails.logger.warn "#{error.class} #{error.to_s}. #{error.backtrace.join("\n")}"
19
+ end
20
+
21
+ def get_layout
22
+ layout ||= Deposit.config[:layout]
23
+ end
24
+
25
+ def current_tenant_user
26
+ # If the rails application on which that engine is mounted defines such method (Devise), we extract the current user,
27
+ # if not we default to nil, and serve our static mock configuration
28
+ user = current_user if respond_to?(:current_user)
29
+ Deposit.current_tenant_user.call(session, user)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deposit
4
+ module ApplicationHelper
5
+ end
6
+ end
@@ -0,0 +1,68 @@
1
+ <div class="search">
2
+
3
+ <div class="column-block">
4
+
5
+ <h1>Invoices</h1>
6
+
7
+ <table id="invoices-table" class="table table-condensed mobile-data">
8
+ <thead>
9
+ <tr>
10
+ <th>Number</th>
11
+ <th>Date</th>
12
+ <th>Amount</th>
13
+ <th>Balance</th>
14
+ <th>Payment</th>
15
+ </tr>
16
+ </thead>
17
+ <tbody>
18
+ <tr>
19
+ <td colspan="1" class="dataTables_empty">Loading data from server</td>
20
+ </tr>
21
+ </tbody>
22
+ </table>
23
+
24
+ <div id="deposit_amount_mismatch" class="text-danger">
25
+ Warning! Total amount doesn't match deposit.
26
+ </div>
27
+ </div>
28
+
29
+ </div>
30
+
31
+ <%= javascript_tag do %>
32
+ $(document).ready(function() {
33
+ var table = $('#invoices-table').DataTable({
34
+ "dom": "t",
35
+ "paging": false,
36
+ "ajax": "<%= account_invoices_path :format => :json %>",
37
+ "processing": true,
38
+ "serverSide": true,
39
+ "search": {"search": $('#account_id').val()},
40
+ "columnDefs": [
41
+ {
42
+ "targets": [ 4 ],
43
+ "orderable": false
44
+ }
45
+ ],
46
+ createdRow: function(row, data, dataIndex) {
47
+ $('td', row).eq(4).html('<input class="payment_amount_invoice" type="number" name="payment_amount_' + data[0] + '" id="payment_amount_' + dataIndex + '" step="any" class="form-control">')
48
+ },
49
+ drawCallback: function() {
50
+ $('input').change(function() {
51
+ var table = $('#invoices-table').DataTable();
52
+ var total = 0;
53
+ table.rows().every(function (rowIdx, tableLoop, rowLoop) {
54
+ var d = this.data();
55
+ total += parseFloat($('#payment_amount_' + rowIdx).val(), 10);
56
+ });
57
+ if (total != parseFloat($('#payment_amount').val(), 10)) {
58
+ $('#deposit_amount_mismatch').css('visibility', 'visible');
59
+ $('#submit-btn').prop('disabled', true);
60
+ } else {
61
+ $('#deposit_amount_mismatch').css('visibility', 'hidden');
62
+ $('#submit-btn').prop('disabled', false);
63
+ }
64
+ });
65
+ }
66
+ });
67
+ });
68
+ <% end %>