~singpolyma/sgx-jmp

6d7b1ee1c4b3df35385530fbc8c2f77515adcf92 — Stephen Paul Weber 1 year, 10 months ago cf84aef
Alt top up command

To show mailing address, eTransfer address, Bitcoin addresses, and allow
generating a new Bitcoin address.
M config.dhall.sample => config.dhall.sample +4 -1
@@ 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 = ""
}

A lib/add_bitcoin_address.rb => lib/add_bitcoin_address.rb +50 -0
@@ 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

A lib/alt_top_up_form.rb => lib/alt_top_up_form.rb +115 -0
@@ 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

M lib/command_list.rb => lib/command_list.rb +5 -0
@@ 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",

M sgx_jmp.rb => sgx_jmp.rb +24 -0
@@ 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|

A test/test_add_bitcoin_address.rb => test/test_add_bitcoin_address.rb +44 -0
@@ 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

A test/test_alt_top_up_form.rb => test/test_alt_top_up_form.rb +91 -0
@@ 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

M test/test_command_list.rb => test/test_command_list.rb +9 -8
@@ 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

M test/test_helper.rb => test/test_helper.rb +5 -0
@@ 62,6 62,11 @@ CONFIG = {
		{
			name: "test_bad_currency",
			currency: :BAD
		},
		{
			name: "test_cad",
			currency: :CAD,
			monthly_price: 1000
		}
	],
	braintree: {