bitcoin_payable 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +158 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +125 -0
  6. data/Rakefile +3 -0
  7. data/bitcoin_payable.gemspec +35 -0
  8. data/features/bitcoin_payments.feature +20 -0
  9. data/features/default.feature +5 -0
  10. data/features/pricing_processor.feature +4 -0
  11. data/features/step_definitions/bitcoin_payment_steps.rb +16 -0
  12. data/features/step_definitions/currency_conversion_steps.rb +4 -0
  13. data/features/step_definitions/model_step.rb +11 -0
  14. data/features/step_definitions/widget_steps.rb +3 -0
  15. data/features/support/env.rb +33 -0
  16. data/lib/bitcoin_payable.rb +23 -0
  17. data/lib/bitcoin_payable/address.rb +24 -0
  18. data/lib/bitcoin_payable/bitcoin_calculator.rb +15 -0
  19. data/lib/bitcoin_payable/bitcoin_payment.rb +64 -0
  20. data/lib/bitcoin_payable/bitcoin_payment_transaction.rb +7 -0
  21. data/lib/bitcoin_payable/commands/payment_processor.rb +38 -0
  22. data/lib/bitcoin_payable/commands/pricing_processor.rb +46 -0
  23. data/lib/bitcoin_payable/config.rb +13 -0
  24. data/lib/bitcoin_payable/currency_conversion.rb +4 -0
  25. data/lib/bitcoin_payable/has_bitcoin_payment.rb +16 -0
  26. data/lib/bitcoin_payable/tasks.rb +17 -0
  27. data/lib/bitcoin_payable/version.rb +3 -0
  28. data/lib/generators/bitcoin_payable/install_generator.rb +22 -0
  29. data/lib/generators/bitcoin_payable/templates/create_bitcoin_payment_transactions.rb +15 -0
  30. data/lib/generators/bitcoin_payable/templates/create_bitcoin_payments.rb +17 -0
  31. data/lib/generators/bitcoin_payable/templates/create_currency_conversions.rb +10 -0
  32. data/spec/dummy/README.rdoc +28 -0
  33. data/spec/dummy/Rakefile +6 -0
  34. data/spec/dummy/app/assets/images/.keep +0 -0
  35. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  36. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  37. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  38. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  39. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  40. data/spec/dummy/app/mailers/.keep +0 -0
  41. data/spec/dummy/app/models/.keep +0 -0
  42. data/spec/dummy/app/models/concerns/.keep +0 -0
  43. data/spec/dummy/app/models/widget.rb +3 -0
  44. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  45. data/spec/dummy/bin/bundle +3 -0
  46. data/spec/dummy/bin/rails +4 -0
  47. data/spec/dummy/bin/rake +4 -0
  48. data/spec/dummy/config.ru +4 -0
  49. data/spec/dummy/config/application.rb +23 -0
  50. data/spec/dummy/config/boot.rb +5 -0
  51. data/spec/dummy/config/database.yml +25 -0
  52. data/spec/dummy/config/environment.rb +5 -0
  53. data/spec/dummy/config/environments/development.rb +29 -0
  54. data/spec/dummy/config/environments/production.rb +80 -0
  55. data/spec/dummy/config/environments/test.rb +36 -0
  56. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/spec/dummy/config/initializers/bitcoin_payable.rb +1 -0
  58. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  59. data/spec/dummy/config/initializers/inflections.rb +16 -0
  60. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  61. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  62. data/spec/dummy/config/initializers/session_store.rb +3 -0
  63. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  64. data/spec/dummy/config/locales/en.yml +23 -0
  65. data/spec/dummy/config/routes.rb +56 -0
  66. data/spec/dummy/db/development.sqlite3 +0 -0
  67. data/spec/dummy/db/migrate/20140510023211_create_widgets.rb +8 -0
  68. data/spec/dummy/db/migrate/20140510025739_create_bitcoin_payments.rb +17 -0
  69. data/spec/dummy/db/migrate/20140510025740_create_bitcoin_payment_transactions.rb +10 -0
  70. data/spec/dummy/db/migrate/20140511021326_create_currency_conversions.rb +10 -0
  71. data/spec/dummy/db/schema.rb +50 -0
  72. data/spec/dummy/db/test.sqlite3 +0 -0
  73. data/spec/dummy/lib/assets/.keep +0 -0
  74. data/spec/dummy/log/.keep +0 -0
  75. data/spec/dummy/log/development.log +85 -0
  76. data/spec/dummy/log/test.log +384 -0
  77. data/spec/dummy/public/404.html +58 -0
  78. data/spec/dummy/public/422.html +58 -0
  79. data/spec/dummy/public/500.html +57 -0
  80. data/spec/dummy/public/favicon.ico +0 -0
  81. data/spec/dummy/test/fixtures/widgets.yml +11 -0
  82. data/spec/dummy/test/models/widget_test.rb +7 -0
  83. metadata +325 -0
@@ -0,0 +1,23 @@
1
+ require 'net/http'
2
+ require 'bitcoin_payable/config'
3
+ require 'bitcoin_payable/version'
4
+ require 'bitcoin_payable/has_bitcoin_payment'
5
+ require 'bitcoin_payable/tasks'
6
+ require 'bitcoin_payable/bitcoin_calculator'
7
+ require 'helloblock'
8
+
9
+ module BitcoinPayable
10
+ def self.config
11
+ @@config ||= BitcoinPayable::Config.instance
12
+ end
13
+ end
14
+
15
+ require 'bitcoin_payable/bitcoin_payment_transaction'
16
+ require "bitcoin_payable/address"
17
+ require 'bitcoin_payable/bitcoin_payment'
18
+
19
+ require 'bitcoin_payable/currency_conversion'
20
+
21
+ ActiveSupport.on_load(:active_record) do
22
+ include BitcoinPayable::Model
23
+ end
@@ -0,0 +1,24 @@
1
+ module BitcoinPayable
2
+ class Address
3
+
4
+ def self.create(id)
5
+ if BitcoinPayable.config.master_seed
6
+ config = {
7
+ seed_hex: BitcoinPayable.config.master_seed
8
+ }
9
+ config.merge!(network: :bitcoin_testnet) if BitcoinPayable.config.testnet
10
+ master = MoneyTree::Master.new config
11
+ node = master.node_for_path "m/0/#{id}"
12
+
13
+ elsif BitcoinPayable.config.master_public_key
14
+ master = MoneyTree::Node.from_serialized_address BitcoinPayable.config.master_public_key
15
+ node = master.node_for_path id.to_s
16
+ else
17
+ raise "MASTER_SEED or MASTER_PUBLIC_KEY is required"
18
+ end
19
+
20
+ node.to_address
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module BitcoinPayable
2
+ class BitcoinCalculator
3
+ def self.convert_satoshis_to_bitcoin(satoshis)
4
+ satoshis * 0.00000001
5
+ end
6
+
7
+ def self.convert_bitcoins_to_satoshis(bitcoins)
8
+ bitcoins / 0.00000001
9
+ end
10
+
11
+ def self.exchange_price(price, exchange_rate)
12
+ (price.to_f / exchange_rate.to_f).round(8)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,64 @@
1
+ #require 'bitcoin-addrgen'
2
+ require 'money-tree'
3
+ require 'state_machine'
4
+
5
+ module BitcoinPayable
6
+ class BitcoinPayment < ::ActiveRecord::Base
7
+
8
+ belongs_to :payable, polymorphic: true
9
+ has_many :transactions, class_name: BitcoinPayable::BitcoinPaymentTransaction
10
+
11
+ validates :reason, presence: true
12
+ validates :price, presence: true
13
+
14
+ before_create :populate_currency_and_amount_due
15
+ after_create :populate_address
16
+
17
+ state_machine :state, initial: :pending do
18
+ state :pending
19
+ state :partial_payment
20
+ state :paid_in_full
21
+
22
+ event :paid do
23
+ transition [:pending, :partial_payment] => :paid_in_full
24
+ end
25
+
26
+ after_transition :on => :paid, :do => :notify_payable
27
+
28
+ event :partially_paid do
29
+ transition :pending => :partial_payment
30
+ end
31
+ end
32
+
33
+ def currency_amount_paid
34
+ self.transactions.inject(0) { |sum, tx| sum + (BitcoinPayable::BitcoinCalculator.convert_satoshis_to_bitcoin(tx.estimated_value) * tx.btc_conversion) }.round(2)
35
+ end
36
+
37
+ def currency_amount_due
38
+ self.price - currency_amount_paid
39
+ end
40
+
41
+ def calculate_btc_amount_due
42
+ btc_rate = BitcoinPayable::CurrencyConversion.last.btc
43
+ BitcoinPayable::BitcoinCalculator.exchange_price currency_amount_due, btc_rate
44
+ end
45
+
46
+ private
47
+
48
+ def populate_currency_and_amount_due
49
+ self.currency ||= BitcoinPayable.config.currency
50
+ self.btc_amount_due = calculate_btc_amount_due
51
+ end
52
+
53
+ def populate_address
54
+ self.update(address: Address.create(self.id))
55
+ end
56
+
57
+ def notify_payable
58
+ if self.payable.respond_to?(:bitcoin_payment_paid)
59
+ self.payable.bitcoin_payment_paid
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ module BitcoinPayable
2
+ class BitcoinPaymentTransaction < ::ActiveRecord::Base
3
+
4
+ belongs_to :bitcoin_payment
5
+
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module BitcoinPayable
2
+ class PaymentProcessor
3
+ def self.perform
4
+ new.perform
5
+ end
6
+
7
+ def initialize
8
+ end
9
+
10
+ def perform
11
+ BitcoinPayable::BitcoinPayment.where(state: [:pending, :partial_payment]).each do |payment|
12
+ transactions = HelloBlock::Transaction.where(addresses: [payment.address]).to_hash
13
+ if transactions["data"]
14
+ transactions["data"]["transactions"].each do |tx|
15
+ unless payment.transactions.find_by_transaction_hash(tx["txHash"])
16
+ payment.transactions.create!(
17
+ estimated_value: tx["estimatedTxValue"],
18
+ transaction_hash: tx["txHash"],
19
+ block_hash: tx["blockHash"],
20
+ block_time: (Time.at(tx["blockTime"]) if tx["blockTime"]),
21
+ estimated_time: (Time.at(tx["estimatedTxTime"]) if tx["estimatedTxTime"]),
22
+ btc_conversion: BitcoinPayable::CurrencyConversion.last.btc
23
+ )
24
+
25
+ payment.update(btc_amount_due: payment.calculate_btc_amount_due)
26
+ end
27
+ end
28
+ end
29
+
30
+ if payment.currency_amount_paid >= payment.price
31
+ payment.paid
32
+ elsif payment.currency_amount_paid > 0
33
+ payment.partially_paid
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ module BitcoinPayable
2
+ class PricingProcessor
3
+
4
+ def self.perform
5
+ new.perform
6
+ end
7
+
8
+ def initialize
9
+ end
10
+
11
+ def perform
12
+ # => Store three previous price ranges
13
+ # => get_currency TODO: enable this again
14
+ # => Defaulting to 1.00 for now
15
+ rate = CurrencyConversion.create!(currency: 1.00, btc: get_btc)
16
+
17
+ # => Loop through all unpaid payments and update them with the new price
18
+ # => If it has been 20 mins since they have been updated
19
+ BitcoinPayable::BitcoinPayment.where(state: ['pending', 'partial_payment']).where("updated_at < ? OR btc_amount_due = 0", 30.minutes.ago).each do |bp|
20
+ bp.update(btc_amount_due: bp.calculate_btc_amount_due)
21
+ end
22
+ end
23
+
24
+ def get_btc
25
+ uri = URI.parse("https://api.bitcoinaverage.com/all")
26
+ http = Net::HTTP.new(uri.host, uri.port)
27
+ http.use_ssl = true
28
+
29
+ request = Net::HTTP::Get.new(uri.request_uri)
30
+
31
+ response = http.request(request)
32
+ hash = JSON.parse(response.body)
33
+
34
+ hash[BitcoinPayable.config.currency.to_s.upcase]["averages"]["24h_avg"].to_f * 100.00
35
+ end
36
+
37
+ def get_currency
38
+ #uri = URI("http://rate-exchange.appspot.com/currency?from=#{BitcoinPayable.config.currency}&to=USD")
39
+ #rate = JSON.parse(Net::HTTP.get(uri))["rate"]
40
+
41
+ uri = URI("http://openexchangerates.org/api/latest.json?app_id=#{BitcoinPayable.config.open_exchange_key}")
42
+ response = JSON.parse(Net::HTTP.get(uri))
43
+ response["rates"][BitcoinPayable.config.currency.to_s.upcase]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,13 @@
1
+ require 'singleton'
2
+
3
+ module BitcoinPayable
4
+ class Config
5
+ include Singleton
6
+ attr_accessor :master_seed, :master_public_key, :currency, :testnet, :open_exchange_key
7
+
8
+ def initialize
9
+ @currency ||= :cad
10
+ @testnet ||= false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module BitcoinPayable
2
+ class CurrencyConversion < ::ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ module BitcoinPayable
2
+ module Model
3
+
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def has_bitcoin_payments(options = {})
11
+ has_many :bitcoin_payments, -> {order(:id)}, class_name: BitcoinPayable::BitcoinPayment, as: 'payable'
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'rake'
2
+ require 'bitcoin_payable/commands/pricing_processor'
3
+ require 'bitcoin_payable/commands/payment_processor'
4
+
5
+ namespace :bitcoin_payable do
6
+
7
+ desc "Process the prices and update the payments"
8
+ task :process_prices => :environment do
9
+ BitcoinPayable::PricingProcessor.perform
10
+ end
11
+
12
+ desc "Connect to HelloBlock.io and process payments"
13
+ task :process_payments => :environment do
14
+ BitcoinPayable::PaymentProcessor.perform
15
+ end
16
+
17
+ end
@@ -0,0 +1,3 @@
1
+ module BitcoinPayable
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/active_record'
3
+
4
+ module BitcoinPayable
5
+ class InstallGenerator < ::Rails::Generators::Base
6
+ include ::Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ desc 'Generates (but does not run) a migration to add a bitcoin payment tables.'
11
+
12
+ def create_migration_file
13
+ migration_template 'create_bitcoin_payments.rb', 'db/migrate/create_bitcoin_payments.rb'
14
+ migration_template 'create_bitcoin_payment_transactions.rb', 'db/migrate/create_bitcoin_payment_transactions.rb'
15
+ migration_template 'create_currency_conversions.rb', 'db/migrate/create_currency_conversions.rb'
16
+ end
17
+
18
+ def self.next_migration_number(dirname)
19
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ class CreateBitcoinPaymentTransactions < ActiveRecord::Migration
2
+ def change
3
+ create_table :bitcoin_payment_transactions do |t|
4
+ t.integer :estimated_value
5
+ t.string :transaction_hash
6
+ t.string :block_hash
7
+ t.datetime :block_time
8
+ t.datetime :estimated_time
9
+ t.integer :bitcoin_payment_id
10
+ t.integer :btc_conversion
11
+ end
12
+
13
+ add_index :bitcoin_payment_transactions, :bitcoin_payment_id
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ class CreateBitcoinPayments < ActiveRecord::Migration
2
+ def change
3
+ create_table :bitcoin_payments do |t|
4
+ t.string :payable_type
5
+ t.integer :payable_id
6
+ t.string :currency
7
+ t.string :reason
8
+ t.integer :price
9
+ t.float :btc_amount_due, default: 0
10
+ t.string :address
11
+ t.string :state
12
+ t.datetime :created_at
13
+ t.datetime :updated_at
14
+ end
15
+ add_index :bitcoin_payments, [:payable_type, :payable_id]
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ class CreateCurrencyConversions < ActiveRecord::Migration
2
+ def change
3
+ create_table :currency_conversions do |t|
4
+ t.float "currency"
5
+ t.integer "btc"
6
+ t.datetime "created_at"
7
+ t.datetime "updated_at"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Dummy::Application.load_tasks
File without changes
@@ -0,0 +1,13 @@
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 vendor/assets/javascripts of plugins, if any, 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/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,13 @@
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 vendor/assets/stylesheets of plugins, if any, 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 top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
File without changes
File without changes