---
forms/registration/bch.rb | 19 ++++++++++
lib/btc_sell_prices.rb | 48 +++++++++++++++++++-----
lib/registration.rb | 71 ++++++++++++++++++++++++++++++------
sgx_jmp.rb | 1 +
test/test_bch_sell_prices.rb | 41 +++++++++++++++++++++
test/test_registration.rb | 61 ++++++++++++++++++++++++++++++-
6 files changed, 219 insertions(+), 22 deletions(-)
create mode 100644 forms/registration/bch.rb
create mode 100644 test/test_bch_sell_prices.rb
diff --git a/forms/registration/bch.rb b/forms/registration/bch.rb
new file mode 100644
index 0000000..3af32de
--- /dev/null
+++ b/forms/registration/bch.rb
@@ -0,0 +1,19 @@
+result!
+title "Activate using Bitcoin Cash"
+
+field(
+ label: "Minimual initial Bitcoin Cash deposit for activation",
+ var: "amount",
+ value: "%.6f" % @amount
+)
+
+field(
+ label: "Bitcoin Cash address",
+ var: "bch_addresses",
+ value: @addr
+)
+
+instructions(
+ "You will received a notification when your payment is complete." \
+ "#{@final_message}"
+)
diff --git a/lib/btc_sell_prices.rb b/lib/btc_sell_prices.rb
index f1783db..771f1a2 100644
--- a/lib/btc_sell_prices.rb
+++ b/lib/btc_sell_prices.rb
@@ -8,7 +8,7 @@ require "nokogiri"
require_relative "em"
-class BTCSellPrices
+class CryptoSellPrices
def initialize(redis, oxr_app_id)
@redis = redis
@oxr = Money::Bank::OpenExchangeRatesBank.new(
@@ -17,25 +17,33 @@ class BTCSellPrices
@oxr.app_id = oxr_app_id
end
+ def usd
+ EMPromise.all([cad, cad_to_usd]).then { |(a, b)| a * b }
+ end
+
+ def ticker_row_selector
+ raise NotImplementedError, "Subclass must implement"
+ end
+
+ def crypto_name
+ raise NotImplementedError, "Subclass must implement"
+ end
+
def cad
fetch_canadianbitcoins.then do |http|
- canadianbitcoins = Nokogiri::HTML.parse(http.response)
+ cb = Nokogiri::HTML.parse(http.response)
- bitcoin_row = canadianbitcoins.at("#ticker > table > tbody > tr")
- unless bitcoin_row.at("td").text == "Bitcoin"
- raise "Bitcoin row has moved"
+ row = cb.at(self.ticker_row_selector)
+ unless row.at("td").text == self.crypto_name
+ raise "#{crypto_name} row has moved"
end
BigDecimal(
- bitcoin_row.at("td:nth-of-type(4)").text.match(/^\$(\d+\.\d+)/)[1]
+ row.at("td:nth-of-type(4)").text.match(/^\$(\d+\.\d+)/)[1]
)
end
end
- def usd
- EMPromise.all([cad, cad_to_usd]).then { |(a, b)| a * b }
- end
-
protected
def fetch_canadianbitcoins
@@ -59,3 +67,23 @@ protected
end
end
end
+
+class BCHSellPrices < CryptoSellPrices
+ def crypto_name
+ "Bitcoin Cash"
+ end
+
+ def ticker_row_selector
+ "#ticker > table > tbody > tr:nth-of-type(2)"
+ end
+end
+
+class BTCSellPrices < CryptoSellPrices
+ def crypto_name
+ "Bitcoin"
+ end
+
+ def ticker_row_selector
+ "#ticker > table > tbody > tr"
+ end
+end
diff --git a/lib/registration.rb b/lib/registration.rb
index ce18987..671fc4f 100644
--- a/lib/registration.rb
+++ b/lib/registration.rb
@@ -276,10 +276,18 @@ class Registration
}.call(customer, tel, final_message: final_message, finish: finish)
end
- class Bitcoin
- Payment.kinds[:bitcoin] = method(:new)
+ class CryptoPaymentMethod
+ def crypto_addrs
+ raise NotImplementedError, "Subclass must implement"
+ end
- THIRTY_DAYS = 60 * 60 * 24 * 30
+ def reg_form_name
+ raise NotImplementedError, "Subclass must implement"
+ end
+
+ def sell_prices
+ raise NotImplementedError, "Subclass must implement"
+ end
def initialize(customer, tel, final_message: nil, **)
@customer = customer
@@ -288,17 +296,17 @@ class Registration
@final_message = final_message
end
- attr_reader :customer_id, :tel
-
def save
TEL_SELECTIONS.set(@customer.jid, @tel)
end
+ attr_reader :customer_id, :tel
+
def form(rate, addr)
amount = CONFIG[:activation_amount] / rate
FormTemplate.render(
- "registration/btc",
+ reg_form_name,
amount: amount,
addr: addr,
final_message: @final_message
@@ -314,9 +322,7 @@ class Registration
}.then(&method(:handle_possible_prev))
end
end
-
protected
-
def handle_possible_prev(iq)
raise "Action not allowed" unless iq.prev?
@@ -325,14 +331,57 @@ class Registration
def addr_and_rate
EMPromise.all([
- @customer.btc_addresses.then { |addrs|
- addrs.first || @customer.add_btc_address
+ self.crypto_addrs.then { |addrs|
+ addrs.first || self.add_crypto_addr
},
- BTC_SELL_PRICES.public_send(@customer.currency.to_s.downcase)
+
+ sell_prices.public_send(@customer.currency.to_s.downcase)
])
end
end
+ class Bitcoin < CryptoPaymentMethod
+ Payment.kinds[:bitcoin] = method(:new)
+
+ def reg_form_name
+ "registration/btc"
+ end
+
+ def sell_prices
+ BTC_SELL_PRICES
+ end
+
+ def crypto_addrs
+ @customer.btc_addresses
+ end
+
+ def add_crypto_addr
+ @customer.add_btc_address
+ end
+ end
+
+ ## Like Bitcoin
+ class BCH < CryptoPaymentMethod
+ Payment.kinds[:bch] = method(:new)
+
+ def reg_form_name
+ "registration/bch"
+ end
+
+ def sell_prices
+ BCH_SELL_PRICES
+ end
+
+ def crypto_addrs
+ @customer.bch_addresses
+ end
+
+ def add_crypto_addr
+ @customer.add_bch_address
+ end
+ end
+
+
class CreditCard
Payment.kinds[:credit_card] = ->(*args, **kw) { self.for(*args, **kw) }
diff --git a/sgx_jmp.rb b/sgx_jmp.rb
index cd2bb12..10176f7 100644
--- a/sgx_jmp.rb
+++ b/sgx_jmp.rb
@@ -220,6 +220,7 @@ when_ready do
REDIS = EM::Hiredis.connect
MEMCACHE = EM::P::Memcache.connect
BTC_SELL_PRICES = BTCSellPrices.new(REDIS, CONFIG[:oxr_app_id])
+ BCH_SELL_PRICES = BCHSellPrices.new(REDIS, CONFIG[:oxr_app_id])
DB = Postgres.connect(dbname: "jmp", size: 5)
TEL_SELECTIONS = TelSelections.new
diff --git a/test/test_bch_sell_prices.rb b/test/test_bch_sell_prices.rb
new file mode 100644
index 0000000..9b8da94
--- /dev/null
+++ b/test/test_bch_sell_prices.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require "em-hiredis"
+require "test_helper"
+require "btc_sell_prices"
+
+class BCHSellPricesTest < Minitest::Test
+ def setup
+ @redis = Minitest::Mock.new
+ @subject = BCHSellPrices.new(@redis, "")
+ end
+
+ def test_cad
+ stub_request(:get, "https://www.canadianbitcoins.com").to_return(
+ body: "<div id='ticker'><table><tbody>" \
+ "<tr>" \
+ "<td>Monopoly Money</td><td></td><td></td><td>10 trillion</td>" \
+ "</tr>" \
+ "<tr>" \
+ "<td>Bitcoin Cash</td><td></td><td></td><td>$123.00</td>" \
+ "</tr>"
+ )
+ assert_equal BigDecimal(123), @subject.cad.sync
+ end
+ em :test_cad
+
+ def test_usd
+ stub_request(:get, "https://www.canadianbitcoins.com").to_return(
+ body: "<div id='ticker'><table><tbody>" \
+ "<tr>" \
+ "<td>Monopoly Money</td><td></td><td></td><td>10 trillion</td>" \
+ "</tr>" \
+ "<tr>" \
+ "<td>Bitcoin Cash</td><td></td><td></td><td>$123.00</td>" \
+ "</tr>"
+ )
+ @redis.expect(:get, EMPromise.resolve("0.5"), ["cad_to_usd"])
+ assert_equal BigDecimal(123) / 2, @subject.usd.sync
+ end
+ em :test_usd
+end
diff --git a/test/test_registration.rb b/test/test_registration.rb
index e3bf4e4..9d7f0ff 100644
--- a/test/test_registration.rb
+++ b/test/test_registration.rb
@@ -574,6 +574,16 @@ class RegistrationTest < Minitest::Test
assert_kind_of Registration::Payment::Bitcoin, result
end
+ def test_for_bch
+ iq = Blather::Stanza::Iq::Command.new
+ iq.form.fields = [
+ { var: "activation_method", value: "bch" },
+ { var: "plan_name", value: "test_usd" }
+ ]
+ result = Registration::Payment.for(iq, customer, "+15555550000")
+ assert_kind_of Registration::Payment::BCH, result
+ end
+
def test_for_credit_card
braintree_customer = Minitest::Mock.new
CustomerFinancials::BRAINTREE.expect(
@@ -655,7 +665,7 @@ class RegistrationTest < Minitest::Test
[Matching.new do |reply|
assert_equal :canceled, reply.status
assert_equal "1.000000", reply.form.field("amount").value
- assert_equal "testaddr", reply.form.field("btc_addresses").value
+ assert_equal "testaddr", reply.form.field("bch_addresses").value
true
end]
)
@@ -673,6 +683,55 @@ class RegistrationTest < Minitest::Test
em :test_write
end
+ class BCHTest < Minitest::Test
+ Registration::Payment::BCH::BCH_SELL_PRICES = Minitest::Mock.new
+ CustomerFinancials::REDIS = Minitest::Mock.new
+
+ def setup
+ @customer = Minitest::Mock.new(
+ customer(plan_name: "test_usd")
+ )
+ @customer.expect(
+ :add_btc_address,
+ EMPromise.resolve("testaddr")
+ )
+ @bch = Registration::Payment::BCH.new(
+ @customer,
+ "+15555550000"
+ )
+ end
+
+ def test_write
+ CustomerFinancials::REDIS.expect(
+ :smembers,
+ EMPromise.resolve([]),
+ ["jmp_customer_bch_addresses-test"]
+ )
+ blather = Minitest::Mock.new
+ Command::COMMAND_MANAGER.expect(
+ :write,
+ EMPromise.reject(SessionManager::Timeout.new),
+ [Matching.new do |reply|
+ assert_equal :canceled, reply.status
+ assert_equal "1.000000", reply.form.field("amount").value
+ assert_equal "testaddr", reply.form.field("bch_addresses").value
+ true
+ end]
+ )
+ Registration::Payment::BCH::BCH_SELL_PRICES.expect(
+ :usd,
+ EMPromise.resolve(BigDecimal(1))
+ )
+ @bch.stub(:save, EMPromise.resolve(nil)) do
+ execute_command(blather: blather) do
+ @bch.write
+ end
+ end
+ assert_mock blather
+ end
+ em :test_write
+ end
+
class CreditCardTest < Minitest::Test
def setup
@credit_card = Registration::Payment::CreditCard.new(
--
2.34.1
>---
> forms/registration/bch.rb | 19 ++++++++++
> lib/btc_sell_prices.rb | 48 +++++++++++++++++++-----
> lib/registration.rb | 71 ++++++++++++++++++++++++++++++------
> sgx_jmp.rb | 1 +
> test/test_bch_sell_prices.rb | 41 +++++++++++++++++++++
> test/test_registration.rb | 61 ++++++++++++++++++++++++++++++-
> 6 files changed, 219 insertions(+), 22 deletions(-)
> create mode 100644 forms/registration/bch.rb
> create mode 100644 test/test_bch_sell_prices.rb
>
>diff --git a/test/test_registration.rb b/test/test_registration.rb
>index e3bf4e4..9d7f0ff 100644
>--- a/test/test_registration.rb
>+++ b/test/test_registration.rb
>@@ -574,6 +574,16 @@ class RegistrationTest < Minitest::Test
> assert_kind_of Registration::Payment::Bitcoin, result
> end
>
>+ def test_for_bch
>+ iq = Blather::Stanza::Iq::Command.new
>+ iq.form.fields = [
>+ { var: "activation_method", value: "bch" },
>+ { var: "plan_name", value: "test_usd" }
>+ ]
>+ result = Registration::Payment.for(iq, customer, "+15555550000")
>+ assert_kind_of Registration::Payment::BCH, result
>+ end
>+
> def test_for_credit_card
> braintree_customer = Minitest::Mock.new
> CustomerFinancials::BRAINTREE.expect(
>@@ -655,7 +665,7 @@ class RegistrationTest < Minitest::Test
> [Matching.new do |reply|
> assert_equal :canceled, reply.status
> assert_equal "1.000000", reply.form.field("amount").value
>- assert_equal "testaddr", reply.form.field("btc_addresses").value
>+ assert_equal "testaddr", reply.form.field("bch_addresses").value
Why did this change?
> true
> end]
> )
>@@ -673,6 +683,55 @@ class RegistrationTest < Minitest::Test
> em :test_write
> end
>
>+ class BCHTest < Minitest::Test
>+ Registration::Payment::BCH::BCH_SELL_PRICES = Minitest::Mock.new
>+ CustomerFinancials::REDIS = Minitest::Mock.new
>+
>+ def setup
>+ @customer = Minitest::Mock.new(
>+ customer(plan_name: "test_usd")
>+ )
>+ @customer.expect(
>+ :add_btc_address,
>+ EMPromise.resolve("testaddr")
>+ )
We don't expect this for bch, right?
>+ @bch = Registration::Payment::BCH.new(
>+ @customer,
>+ "+15555550000"
>+ )
>+ end
>+
>+ def test_write
>+ CustomerFinancials::REDIS.expect(
>+ :smembers,
>+ EMPromise.resolve([]),
>+ ["jmp_customer_bch_addresses-test"]
>+ )
>+ blather = Minitest::Mock.new
>+ Command::COMMAND_MANAGER.expect(
>+ :write,
>+ EMPromise.reject(SessionManager::Timeout.new),
>+ [Matching.new do |reply|
>+ assert_equal :canceled, reply.status
>+ assert_equal "1.000000", reply.form.field("amount").value
>+ assert_equal "testaddr", reply.form.field("bch_addresses").value
>+ true
>+ end]
>+ )
>+ Registration::Payment::BCH::BCH_SELL_PRICES.expect(
>+ :usd,
>+ EMPromise.resolve(BigDecimal(1))
>+ )
>+ @bch.stub(:save, EMPromise.resolve(nil)) do
>+ execute_command(blather: blather) do
>+ @bch.write
>+ end
>+ end
>+ assert_mock blather
>+ end
>+ em :test_write
>+ end
>+
> class CreditCardTest < Minitest::Test
> def setup
> @credit_card = Registration::Payment::CreditCard.new(
>--
>2.34.1
>