M .rubocop.yml => .rubocop.yml +1 -1
@@ 122,7 122,7 @@ Style/FormatStringToken:
Style/FrozenStringLiteralComment:
Exclude:
- - forms/*
+ - forms/**/*.rb
Naming/AccessorMethodName:
Enabled: false
M Gemfile => Gemfile +1 -1
@@ 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"
M config-schema.dhall => config-schema.dhall +1 -0
@@ 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 :
M config.dhall.sample => config.dhall.sample +2 -1
@@ 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" }
}
A forms/registration/activate.rb => forms/registration/activate.rb +36 -0
@@ 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")
A forms/registration/allow.rb => forms/registration/allow.rb +10 -0
@@ 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")
A forms/registration/plan_name.rb => forms/registration/plan_name.rb +16 -0
@@ 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"
+ }
+ ]
+)
M lib/registration.rb => lib/registration.rb +56 -73
@@ 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
M test/test_helper.rb => test/test_helper.rb +6 -2
@@ 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)
M test/test_registration.rb => test/test_registration.rb +138 -2
@@ 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)
+ <TelephoneNumberResponse>
+ <TelephoneNumber>5555550000</TelephoneNumber>
+ </TelephoneNumberResponse>
+ RESPONSE
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/tns/5555550000/ratecenter"
+ ).to_return(status: 201, body: <<~RESPONSE)
+ <TelephoneNumberResponse>
+ <TelephoneNumberDetails>
+ <State>KE</State>
+ <RateCenter>FA</RateCenter>
+ </TelephoneNumberDetails>
+ </TelephoneNumberResponse>
+ 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)
+ <TelephoneNumberResponse>
+ <TelephoneNumber>5555550000</TelephoneNumber>
+ </TelephoneNumberResponse>
+ RESPONSE
+ stub_request(
+ :get,
+ "https://dashboard.bandwidth.com/v1.0/tns/5555550000/ratecenter"
+ ).to_return(status: 201, body: <<~RESPONSE)
+ <TelephoneNumberResponse>
+ <TelephoneNumberDetails>
+ <State>KE</State>
+ <RateCenter>FA</RateCenter>
+ </TelephoneNumberDetails>
+ </TelephoneNumberResponse>
+ 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