From 6d7b1ee1c4b3df35385530fbc8c2f77515adcf92 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Thu, 1 Jul 2021 19:50:41 -0500 Subject: [PATCH] Alt top up command To show mailing address, eTransfer address, Bitcoin addresses, and allow generating a new Bitcoin address. --- config.dhall.sample | 5 +- lib/add_bitcoin_address.rb | 50 ++++++++++++++ lib/alt_top_up_form.rb | 115 +++++++++++++++++++++++++++++++ lib/command_list.rb | 5 ++ sgx_jmp.rb | 24 +++++++ test/test_add_bitcoin_address.rb | 44 ++++++++++++ test/test_alt_top_up_form.rb | 91 ++++++++++++++++++++++++ test/test_command_list.rb | 17 ++--- test/test_helper.rb | 5 ++ 9 files changed, 347 insertions(+), 9 deletions(-) create mode 100644 lib/add_bitcoin_address.rb create mode 100644 lib/alt_top_up_form.rb create mode 100644 test/test_add_bitcoin_address.rb create mode 100644 test/test_alt_top_up_form.rb diff --git a/config.dhall.sample b/config.dhall.sample index 6d95044..40a1cc5 100644 --- a/config.dhall.sample +++ b/config.dhall.sample @@ -63,5 +63,8 @@ oxr_app_id = "", activation_amount = 15, credit_card_url = \(jid: Text) -> \(customer_id: Text) -> - "https://pay.jmp.chat/${jid}/credit_cards?customer_id=${customer_id}" + "https://pay.jmp.chat/${jid}/credit_cards?customer_id=${customer_id}", + adr = "", + interac = "", + payable = "" } diff --git a/lib/add_bitcoin_address.rb b/lib/add_bitcoin_address.rb new file mode 100644 index 0000000..f18bb40 --- /dev/null +++ b/lib/add_bitcoin_address.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class AddBitcoinAddress + def self.for(iq, alt_form, customer) + if alt_form.parse(iq.form)[:add_btc_address] + new(iq, customer) + else + DoNot.new(iq) + end + end + + def initialize(iq, customer) + @reply = iq.reply + @reply.status = :completed + @customer = customer + end + + def write + @customer.add_btc_address.then do |addr| + form.fields = [{ + var: "btc_address", + type: "fixed", + label: "Bitcoin Address", + value: addr + }] + BLATHER << @reply + end + end + +protected + + def form + form = @reply.form + form.type = :result + form.title = "New Bitcoin Address" + form.instructions = "Your new address has been created" + form + end + + class DoNot + def initialize(iq) + @reply = iq.reply + @reply.status = :completed + end + + def write + BLATHER << @reply + end + end +end diff --git a/lib/alt_top_up_form.rb b/lib/alt_top_up_form.rb new file mode 100644 index 0000000..743aff7 --- /dev/null +++ b/lib/alt_top_up_form.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +class AltTopUpForm + def self.for(customer) + customer.btc_addresses.then do |addrs| + AltTopUpForm.new(*[ + (IS_CAD if customer.currency == :CAD), + (HasBitcoinAddresses.new(addrs) unless addrs.empty?), + AddBtcAddressField.for(addrs) + ].compact) + end + end + + def initialize(*fields) + @fields = fields + end + + def form + form = Blather::Stanza::X.new(:result) + form.type = :form + form.title = "Buy Account Credit" + form.instructions = + "Besides credit cards, we support payment by Bitcoin, postal mail, " \ + "or in Canada by Interac eTransfer." + + form.fields = fields.to_a + form + end + + def parse(form) + { + add_btc_address: ["1", "true"].include?( + form.field("add_btc_address")&.value.to_s + ) + } + end + + MAILING_ADDRESS = { + var: "adr", + type: "fixed", + label: "Mailing Address", + description: + "Make payable to #{CONFIG[:payable]} and include your " \ + "Jabber ID in the mailing somewhere.", + value: CONFIG[:adr] + }.freeze + + def fields + Enumerator.new do |y| + y << MAILING_ADDRESS + @fields.each do |fs| + fs.each { |f| y << f } + end + end + end + + IS_CAD = [ + var: "adr", + type: "fixed", + label: "Interac eTransfer Address", + description: "Please include your Jabber ID in the note", + value: CONFIG[:interac] + ].freeze + + class AddBtcAddressField + def self.for(addrs) + if addrs.empty? + AddNewBtcAddressField.new + else + new + end + end + + def each + yield( + var: "add_btc_address", + label: label, + type: "boolean", + value: false + ) + end + + def label + "Or, create a new Bitcoin address?" + end + + class AddNewBtcAddressField < AddBtcAddressField + def label + "You have no Bitcoin addresses, would you like to create one?" + end + end + end + + class HasBitcoinAddresses + def initialize(addrs) + @addrs = addrs + end + + DESCRIPTION = + "You can make a Bitcoin payment of any amount to any " \ + "of these addresses and it will be credited to your " \ + "account at the Canadian Bitcoins exchange rate within 5 " \ + "minutes of your transaction reaching 3 confirmations." + + def each + yield( + var: "btc_address", + type: "fixed", + label: "Bitcoin Addresses", + description: DESCRIPTION, + value: @addrs + ) + end + end +end diff --git a/lib/command_list.rb b/lib/command_list.rb index fc75baa..4651253 100644 --- a/lib/command_list.rb +++ b/lib/command_list.rb @@ -24,6 +24,7 @@ class CommandList ]).then do |(fwd, payment_methods)| Registered.new(*[ (HAS_CREDIT_CARD unless payment_methods.empty?), + (HAS_CURRENCY if customer.currency), (HAS_FORWARDING if fwd) ].compact) end @@ -52,6 +53,10 @@ class CommandList end end + HAS_CURRENCY = [ + node: "alt top up", + name: "Buy Account Credit by Bitcoin, Mail, or Interac eTransfer" + ].freeze HAS_FORWARDING = [ node: "record-voicemail-greeting", diff --git a/sgx_jmp.rb b/sgx_jmp.rb index bc91715..4fd357d 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -24,6 +24,8 @@ singleton_class.class_eval do Blather::DSL.append_features(self) end +require_relative "lib/alt_top_up_form" +require_relative "lib/add_bitcoin_address" require_relative "lib/backend_sgx" require_relative "lib/bandwidth_tn_order" require_relative "lib/btc_sell_prices" @@ -370,6 +372,28 @@ command :execute?, node: "top up", sessionid: nil do |iq| }.catch { |e| panic(e, sentry_hub) } end +command :execute?, node: "alt top up", sessionid: nil do |iq| + sentry_hub = new_sentry_hub(iq, name: iq.node) + reply = iq.reply + reply.status = :executing + reply.allowed_actions = [:complete] + + Customer.for_jid(iq.from.stripped).then { |customer| + sentry_hub.current_scope.set_user( + id: customer.customer_id, + jid: iq.from.stripped.to_s + ) + + EMPromise.all([AltTopUpForm.for(customer), customer]) + }.then { |(alt_form, customer)| + reply.command << alt_form.form + + COMMAND_MANAGER.write(reply).then do |iq2| + AddBitcoinAddress.for(iq2, alt_form, customer).write + end + }.catch { |e| panic(e, sentry_hub) } +end + command :execute?, node: "reset sip account", sessionid: nil do |iq| sentry_hub = new_sentry_hub(iq, name: iq.node) Customer.for_jid(iq.from.stripped).then { |customer| diff --git a/test/test_add_bitcoin_address.rb b/test/test_add_bitcoin_address.rb new file mode 100644 index 0000000..8690d14 --- /dev/null +++ b/test/test_add_bitcoin_address.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "test_helper" +require "alt_top_up_form" +require "add_bitcoin_address" + +class AddBitcoinAddressTest < Minitest::Test + def test_for + iq = Blather::Stanza::Iq::Command.new + AddBitcoinAddress.for(iq, AltTopUpForm.new, Customer.new("test")) + end + + def test_for_add_bitcoin + iq = Blather::Stanza::Iq::Command.new + iq.form.fields = [{ var: "add_btc_address", value: "true" }] + AddBitcoinAddress.for(iq, AltTopUpForm.new, Customer.new("test")) + end + + def test_write + customer = Minitest::Mock.new + customer.expect(:add_btc_address, EMPromise.resolve("newaddress")) + iq = Blather::Stanza::Iq::Command.new + AddBitcoinAddress.new(iq, customer).write.sync + assert_mock customer + end + em :test_write + + class DoNotTest < Minitest::Test + AddBitcoinAddress::DoNot::BLATHER = Minitest::Mock.new + + def test_write + AddBitcoinAddress::DoNot::BLATHER.expect( + :<<, + EMPromise.resolve(nil) + ) do |stanza| + assert_equal :completed, stanza.status + end + iq = Blather::Stanza::Iq::Command.new + AddBitcoinAddress::DoNot.new(iq).write.sync + assert_mock AddBitcoinAddress::DoNot::BLATHER + end + em :test_write + end +end diff --git a/test/test_alt_top_up_form.rb b/test/test_alt_top_up_form.rb new file mode 100644 index 0000000..4a844af --- /dev/null +++ b/test/test_alt_top_up_form.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "test_helper" +require "alt_top_up_form" +require "customer" + +class AltTopUpFormTest < Minitest::Test + def test_for + Customer::REDIS.expect( + :smembers, + EMPromise.resolve([]), + ["jmp_customer_btc_addresses-test"] + ) + assert_kind_of( + AltTopUpForm, + AltTopUpForm.for(Customer.new("test")).sync + ) + end + em :test_for + + def test_for_addresses + Customer::REDIS.expect( + :smembers, + EMPromise.resolve(["testaddr"]), + ["jmp_customer_btc_addresses-test"] + ) + assert_kind_of( + AltTopUpForm, + AltTopUpForm.for(Customer.new("test")).sync + ) + end + em :test_for_addresses + + def test_for_cad + Customer::REDIS.expect( + :smembers, + EMPromise.resolve([]), + ["jmp_customer_btc_addresses-test"] + ) + assert_kind_of( + AltTopUpForm, + AltTopUpForm.for(Customer.new("test", plan_name: "test_cad")).sync + ) + end + em :test_for_cad + + def test_form_addrs + assert_kind_of( + Blather::Stanza::X, + AltTopUpForm.new(AltTopUpForm::AddBtcAddressField.new).form + ) + end + + def test_form_new_addrs + assert_kind_of( + Blather::Stanza::X, + AltTopUpForm.new( + AltTopUpForm::AddBtcAddressField::AddNewBtcAddressField.new + ).form + ) + end + + def test_parse_true + iq_form = Blather::Stanza::X.new + iq_form.fields = [ + { var: "add_btc_address", value: "true" } + ] + assert AltTopUpForm.new.parse(iq_form)[:add_btc_address] + end + + def test_parse_1 + iq_form = Blather::Stanza::X.new + iq_form.fields = [ + { var: "add_btc_address", value: "1" } + ] + assert AltTopUpForm.new.parse(iq_form)[:add_btc_address] + end + + def test_parse_false + iq_form = Blather::Stanza::X.new + iq_form.fields = [ + { var: "add_btc_address", value: "false" } + ] + refute AltTopUpForm.new.parse(iq_form)[:add_btc_address] + end + + def test_parse_not_presend + iq_form = Blather::Stanza::X.new + refute AltTopUpForm.new.parse(iq_form)[:add_btc_address] + end +end diff --git a/test/test_command_list.rb b/test/test_command_list.rb index 3281a15..ebefdaf 100644 --- a/test/test_command_list.rb +++ b/test/test_command_list.rb @@ -92,24 +92,25 @@ class CommandListTest < Minitest::Test end em :test_for_registered_with_credit_card - def test_for_registered_with_forwarding_and_billing + def test_for_registered_with_currency CommandList::REDIS.expect( :get, - EMPromise.resolve("tel:1"), + EMPromise.resolve(nil), ["catapult_fwd-1"] ) CommandList::Customer.expect( :for_jid, EMPromise.resolve(OpenStruct.new( registered?: OpenStruct.new(phone: "1"), - plan_name: "test", - payment_methods: EMPromise.resolve([:boop]) + currency: :USD )), ["registered"] ) - result = CommandList.for("registered").sync - assert_kind_of CommandList::HasForwarding, result - assert_kind_of CommandList::HasBilling, result + + assert_equal( + CommandList::HAS_CURRENCY, + CommandList::HAS_CURRENCY & CommandList.for("registered").sync.to_a + ) end - em :test_for_registered_with_forwarding_and_billing + em :test_for_registered_with_currency end diff --git a/test/test_helper.rb b/test/test_helper.rb index 98ed484..899b690 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -62,6 +62,11 @@ CONFIG = { { name: "test_bad_currency", currency: :BAD + }, + { + name: "test_cad", + currency: :CAD, + monthly_price: 1000 } ], braintree: { -- 2.34.5