diff --git a/.rubocop.yml b/.rubocop.yml index f201ef3fd6f3b5dca69b945c28d3e73dd1ad8142..53c881fd4adcc41b9775b5162ea5cccb4b7cc117 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -122,7 +122,7 @@ Style/FormatStringToken: Style/FrozenStringLiteralComment: Exclude: - - forms/* + - forms/**/*.rb Naming/AccessorMethodName: Enabled: false diff --git a/Gemfile b/Gemfile index ae0f83313d32ec104998847a5fbdda567e528911..9b935edf9fa16021430d448efce824f0e5353703 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ gem "amazing_print" gem "bandwidth-sdk", "<= 6.1.0" gem "blather", git: "https://github.com/singpolyma/blather.git", branch: "ergonomics" gem "braintree" -gem "dhall" +gem "dhall", ">= 0.5.3.fixed" gem "em-hiredis" gem "em-http-request", git: "https://github.com/singpolyma/em-http-request", branch: "fix-letsencrypt" gem "em-pg-client", git: "https://github.com/royaltm/ruby-em-pg-client" diff --git a/config-schema.dhall b/config-schema.dhall index eaee7f7bf30818c77971d0ad87957264fda40232..553d672962c716fa2c995a23a19f22bb7ea7d518 100644 --- a/config-schema.dhall +++ b/config-schema.dhall @@ -1,6 +1,7 @@ { activation_amount : Natural , admins : List Text , adr : Text +, approved_domains : List { mapKey : Text, mapValue : Optional Text } , bandwidth_peer : Text , bandwidth_site : Text , braintree : diff --git a/config.dhall.sample b/config.dhall.sample index 0f261f466aef3ac80ba4e2c0815f3b736f87ac24..f07da0763a3d073b22ecda39dd17992445d8aff9 100644 --- a/config.dhall.sample +++ b/config.dhall.sample @@ -83,5 +83,6 @@ in payable = "", notify_from = "+15551234567@example.net", admins = ["test\\40example.com@example.net"], - upstream_domain = "example.net" + upstream_domain = "example.net", + approved_domains = toMap { `example.com` = Some "customer_id" } } diff --git a/forms/registration/activate.rb b/forms/registration/activate.rb new file mode 100644 index 0000000000000000000000000000000000000000..20e6705862b7e9fd486f7aa5738771e39643798b --- /dev/null +++ b/forms/registration/activate.rb @@ -0,0 +1,36 @@ +form! +title "Activate JMP" + +center = " (#{@rate_center})" if @rate_center +instructions <<~I + You've selected #{@tel}#{center} as your JMP number. + To activate your account, you can either deposit $#{CONFIG[:activation_amount]} to your balance or enter your invite code if you have one. + (If you'd like to pay in a cryptocurrency other than Bitcoin, currently we recommend using a service like simpleswap.io, morphtoken.com, changenow.io, or godex.io. Manual payment via Bitcoin Cash is also available if you contact support.) +I + +field( + var: "activation_method", + type: "list-single", + label: "Activate using", + required: true, + options: [ + { + value: "credit_card", + label: "Credit Card" + }, + { + value: "bitcoin", + label: "Bitcoin" + }, + { + value: "code", + label: "Invite Code" + }, + { + value: "mail", + label: "Mail or eTransfer" + } + ] +) + +instance_eval File.read("#{__dir__}/plan_name.rb") diff --git a/forms/registration/allow.rb b/forms/registration/allow.rb new file mode 100644 index 0000000000000000000000000000000000000000..ef4ece6c54f01374de2781a87c31bf5a5b2cddb4 --- /dev/null +++ b/forms/registration/allow.rb @@ -0,0 +1,10 @@ +form! +title "Activate JMP" + +center = " (#{@rate_center})" if @rate_center +instructions <<~I + You've selected #{@tel}#{center} as your JMP number. + As a user of #{@domain} you will start out with a free trial for one month, after which you will need to top up your balance to keep the account. +I + +instance_eval File.read("#{__dir__}/plan_name.rb") diff --git a/forms/registration/plan_name.rb b/forms/registration/plan_name.rb new file mode 100644 index 0000000000000000000000000000000000000000..a5c72affdfdf4d18e297b8ee1ccb1fb1c76c9a70 --- /dev/null +++ b/forms/registration/plan_name.rb @@ -0,0 +1,16 @@ +field( + var: "plan_name", + type: "list-single", + label: "What currency should your account balance be in?", + required: true, + options: [ + { + value: "cad_beta_unlimited-v20210223", + label: "Canadian Dollars" + }, + { + value: "usd_beta_unlimited-v20210223", + label: "United States Dollars" + } + ] +) diff --git a/lib/registration.rb b/lib/registration.rb index 8b64c13470885ef4642410c0118935ca7a1ebeb1..7597ba33f49b8c83613b1c3a65d5ba5b09e34b19 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -36,8 +36,11 @@ class Registration def self.for(customer, tel) if customer.active? Finish.new(customer, tel) + elsif CONFIG[:approved_domains].key?(customer.jid.domain.to_sym) + credit_to = CONFIG[:approved_domains][customer.jid.domain.to_sym] + Allow.new(customer, tel, credit_to) else - EMPromise.resolve(new(customer, tel)) + new(customer, tel) end end @@ -48,85 +51,27 @@ class Registration attr_reader :customer, :tel - FORM_FIELDS = [ - { - var: "activation_method", - type: "list-single", - label: "Activate using", - required: true, - options: [ - { - value: "credit_card", - label: "Credit Card" - }, - { - value: "bitcoin", - label: "Bitcoin" - }, - { - value: "code", - label: "Invite Code" - }, - { - value: "mail", - label: "Mail or eTransfer" - } - ] - }, - { - var: "plan_name", - type: "list-single", - label: "What currency should your account balance be in?", - required: true, - options: [ - { - value: "cad_beta_unlimited-v20210223", - label: "Canadian Dollars" - }, - { - value: "usd_beta_unlimited-v20210223", - label: "United States Dollars" - } - ] - } - ].freeze - - ACTIVATE_INSTRUCTION = - "To activate your account, you can either deposit " \ - "$#{CONFIG[:activation_amount]} to your balance or enter " \ - "your invite code if you have one." - - CRYPTOCURRENCY_INSTRUCTION = - "(If you'd like to pay in a cryptocurrency other than " \ - "Bitcoin, currently we recommend using a service like " \ - "simpleswap.io, morphtoken.com, changenow.io, or godex.io. " \ - "Manual payment via Bitcoin Cash is also available if you " \ - "contact support.)" - - def add_instructions(form, center) - center = " (#{center})" if center - [ - "You've selected #{tel}#{center} as your JMP number", - ACTIVATE_INSTRUCTION, - CRYPTOCURRENCY_INSTRUCTION - ].each do |txt| - form << Blather::XMPPNode.new(:instructions, form.document).tap { |i| - i << txt - } - end + def form(center) + FormTemplate.render( + "registration/activate", + tel: tel, + rate_center: center + ) end def write rate_center.then { |center| Command.reply do |reply| reply.allowed_actions = [:next] - form = reply.form - form.type = :form - form.title = "Activate JMP" - add_instructions(form, center) - form.fields = FORM_FIELDS + reply.command << form(center) end - }.then { |iq| Payment.for(iq, customer, tel) }.then(&:write) + }.then(&method(:next_step)) + end + + def next_step(iq) + EMPromise.resolve(nil).then { + Payment.for(iq, customer, tel) + }.then(&:write) end protected @@ -137,6 +82,44 @@ class Registration "#{center[:rate_center]}, #{center[:state]}" }.catch { nil } end + + class Allow < Activation + def initialize(customer, tel, credit_to) + super(customer, tel) + @credit_to = credit_to + end + + def form(center) + FormTemplate.render( + "registration/allow", + tel: tel, + rate_center: center, + domain: customer.jid.domain + ) + end + + def next_step(iq) + plan_name = iq.form.field("plan_name").value.to_s + @customer = customer.with_plan(plan_name) + EMPromise.resolve(nil).then { activate }.then do + Finish.new(customer, tel).write + end + end + + protected + + def activate + DB.transaction do + if @credit_to + DB.exec(<<~SQL, [@credit_to, customer.customer_id]) + INSERT INTO invites (creator_id, used_by_id, used_at) + VALUES ($1, $2, LOCALTIMESTAMP) + SQL + end + @customer.activate_plan_starting_now + end + end + end end module Payment diff --git a/test/test_helper.rb b/test/test_helper.rb index e0eb597b3cceaad5af273bad76a225dc5ffaa0ee..2a5ebd45aa55a1b583d10c6503d24fbd490040f3 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -40,7 +40,7 @@ $VERBOSE = nil Sentry.init def customer(customer_id="test", plan_name: nil, **kwargs) - jid = Blather::JID.new("#{customer_id}@example.net") + jid = kwargs.delete(:jid) || Blather::JID.new("#{customer_id}@example.net") if plan_name expires_at = kwargs.delete(:expires_at) || Time.now plan = CustomerPlan.new( @@ -100,7 +100,11 @@ CONFIG = { }, credit_card_url: ->(*) { "http://creditcard.example.com" }, electrum_notify_url: ->(*) { "http://notify.example.com" }, - upstream_domain: "example.net" + upstream_domain: "example.net", + approved_domains: { + "approved.example.com": nil, + "refer.example.com": "refer_to" + } }.freeze def panic(e) diff --git a/test/test_registration.rb b/test/test_registration.rb index eba4a4ae4cc00512df4a7ea75762a749d66e278f..8c7a5090c425ebc769b22832b2ee61710837c196 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -52,6 +52,22 @@ class RegistrationTest < Minitest::Test end em :test_for_activated + def test_for_not_activated_approved + sgx = OpenStruct.new(registered?: false) + web_manager = TelSelections.new(redis: FakeRedis.new) + web_manager.set("test@approved.example.com", "+15555550000") + iq = Blather::Stanza::Iq::Command.new + iq.from = "test@approved.example.com" + result = execute_command(iq) do + Registration.for( + customer(sgx: sgx, jid: Blather::JID.new("test@approved.example.com")), + web_manager + ) + end + assert_kind_of Registration::Activation::Allow, result + end + em :test_for_not_activated_approved + def test_for_not_activated_with_customer_id sgx = OpenStruct.new(registered?: false) web_manager = TelSelections.new(redis: FakeRedis.new) @@ -100,8 +116,8 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 (FA, KE) as your JMP number", - iq.form.instructions + "You've selected +15555550000 (FA, KE) as your JMP number.", + iq.form.instructions.lines.first.chomp ) end] ) @@ -114,6 +130,126 @@ class RegistrationTest < Minitest::Test em :test_write end + class AllowTest < Minitest::Test + Command::COMMAND_MANAGER = Minitest::Mock.new + Registration::Activation::Allow::DB = Minitest::Mock.new + + def test_write_credit_to_nil + cust = Minitest::Mock.new(customer("test")) + allow = Registration::Activation::Allow.new(cust, "+15555550000", nil) + + stub_request( + :get, + "https://dashboard.bandwidth.com/v1.0/tns/+15555550000" + ).to_return(status: 201, body: <<~RESPONSE) + + 5555550000 + + RESPONSE + stub_request( + :get, + "https://dashboard.bandwidth.com/v1.0/tns/5555550000/ratecenter" + ).to_return(status: 201, body: <<~RESPONSE) + + + KE + FA + + + RESPONSE + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq| + iq.form.fields = [{ var: "plan_name", value: "test_usd" }] + }), + [Matching.new do |iq| + assert_equal :form, iq.form.type + assert_equal( + "You've selected +15555550000 (FA, KE) as your JMP number.", + iq.form.instructions.lines.first.chomp + ) + assert_equal 1, iq.form.fields.length + end] + ) + Registration::Activation::Allow::DB.expect( + :transaction, + EMPromise.reject(:test_result) + ) do |&blk| + blk.call + true + end + cust.expect(:with_plan, cust, ["test_usd"]) + cust.expect(:activate_plan_starting_now, nil) + assert_equal( + :test_result, + execute_command { allow.write.catch { |e| e } } + ) + assert_mock Command::COMMAND_MANAGER + end + em :test_write_credit_to_nil + + def test_write_credit_to_refercust + cust = Minitest::Mock.new(customer("test")) + allow = Registration::Activation::Allow.new( + cust, "+15555550000", "refercust" + ) + + stub_request( + :get, + "https://dashboard.bandwidth.com/v1.0/tns/+15555550000" + ).to_return(status: 201, body: <<~RESPONSE) + + 5555550000 + + RESPONSE + stub_request( + :get, + "https://dashboard.bandwidth.com/v1.0/tns/5555550000/ratecenter" + ).to_return(status: 201, body: <<~RESPONSE) + + + KE + FA + + + RESPONSE + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq| + iq.form.fields = [{ var: "plan_name", value: "test_usd" }] + }), + [Matching.new do |iq| + assert_equal :form, iq.form.type + assert_equal( + "You've selected +15555550000 (FA, KE) as your JMP number.", + iq.form.instructions.lines.first.chomp + ) + assert_equal 1, iq.form.fields.length + end] + ) + Registration::Activation::Allow::DB.expect( + :transaction, + EMPromise.reject(:test_result) + ) do |&blk| + blk.call + true + end + Registration::Activation::Allow::DB.expect( + :exec, + nil, + [String, ["refercust", "test"]] + ) + cust.expect(:with_plan, cust, ["test_usd"]) + cust.expect(:activate_plan_starting_now, nil) + assert_equal( + :test_result, + execute_command { allow.write.catch { |e| e } } + ) + assert_mock Command::COMMAND_MANAGER + end + em :test_write_credit_to_refercust + end + class PaymentTest < Minitest::Test Customer::BRAINTREE = Minitest::Mock.new