forms/registration/activate.rb 🔗
@@ -34,3 +34,9 @@ field(
)
instance_eval File.read("#{__dir__}/plan_name.rb")
+
+field(
+ var: "code",
+ type: "text-single",
+ label: "Optional referral code"
+)
Stephen Paul Weber created
If it's not a one-use code, save it as a pending code for later use maybe.
forms/registration/activate.rb | 6 +
lib/invites_repo.rb | 18 +++
lib/registration.rb | 47 ++++++---
test/test_registration.rb | 165 ++++++++++++++++++++++++++++++++---
4 files changed, 201 insertions(+), 35 deletions(-)
@@ -34,3 +34,9 @@ field(
)
instance_eval File.read("#{__dir__}/plan_name.rb")
+
+field(
+ var: "code",
+ type: "text-single",
+ label: "Optional referral code"
+)
@@ -15,13 +15,23 @@ class InvitesRepo
promise.then { |result| result.map { |row| row["code"] } }
end
+ def stash_code(customer_id, code)
+ return EMPromise.resolve(nil) if code.to_s.strip == ""
+
+ @redis.set("jmp_customer_pending_invite-#{customer_id}", code)
+ end
+
+ CLAIM_SQL = <<~SQL
+ UPDATE invites SET used_by_id=$1, used_at=LOCALTIMESTAMP
+ WHERE code=$2 AND used_by_id IS NULL
+ SQL
+
def claim_code(customer_id, code, &blk)
+ raise Invalid, "No code provided" if code.to_s.strip == ""
+
guard_too_many_tries(customer_id).then do
@db.transaction do
- valid = @db.exec(<<~SQL, [customer_id, code]).cmd_tuples.positive?
- UPDATE invites SET used_by_id=$1, used_at=LOCALTIMESTAMP
- WHERE code=$2 AND used_by_id IS NULL
- SQL
+ valid = @db.exec(CLAIM_SQL, [customer_id, code]).cmd_tuples.positive?
invalid_code(customer_id, code).sync unless valid
blk.call
@@ -87,6 +87,7 @@ class Registration
def initialize(customer, tel)
@customer = customer
@tel = tel
+ @invites = InvitesRepo.new(DB, REDIS)
end
attr_reader :customer, :tel
@@ -109,13 +110,32 @@ class Registration
end
def next_step(iq)
- EMPromise.resolve(nil).then {
- Payment.for(iq, customer, tel)
- }.then(&:write)
+ code = iq.form.field("code")&.value&.to_s
+ save_customer_plan(iq).then {
+ finish_if_valid_invite(code)
+ }.catch_only(InvitesRepo::Invalid) do
+ @invites.stash_code(customer.customer_id, code).then do
+ Payment.for(iq, @customer, @tel).then(&:write)
+ end
+ end
end
protected
+ def finish_if_valid_invite(code)
+ @invites.claim_code(@customer.customer_id, code) {
+ @customer.activate_plan_starting_now
+ }.then do
+ Finish.new(@customer, @tel).write
+ end
+ end
+
+ def save_customer_plan(iq)
+ plan_name = iq.form.field("plan_name").value.to_s
+ @customer = @customer.with_plan(plan_name)
+ @customer.save_plan!
+ end
+
def rate_center
EM.promise_fiber {
center = BandwidthIris::Tn.get(tel).get_rate_center
@@ -128,6 +148,7 @@ class Registration
@customer = customer
@google_play_userid = google_play_userid
@tel = tel
+ @invites = InvitesRepo.new(DB, REDIS)
end
def used
@@ -160,18 +181,18 @@ class Registration
@customer = @customer.with_plan(plan_name)
@customer.activate_plan_starting_now
}.then do
- if iq.form.field("code")
- use_referral_code(iq.form.field("code").value.to_s)
- end
+ use_referral_code(iq.form.field("code")&.value&.to_s)
end
end
protected
def use_referral_code(code)
- InvitesRepo.new.claim_code(@customer.customer_id, code) {
+ @invites.claim_code(@customer.customer_id, code) {
@customer.extend_plan
- }.catch_only(InvitesRepo::Invalid) { nil }
+ }.catch_only(InvitesRepo::Invalid) do
+ @invites.stash_code(customer.customer_id, code)
+ end
end
end
@@ -225,13 +246,9 @@ class Registration
end
def self.for(iq, customer, tel, final_message: nil, finish: Finish)
- plan_name = iq.form.field("plan_name").value.to_s
- customer = customer.with_plan(plan_name)
- customer.save_plan!.then do
- kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) {
- raise "Invalid activation method"
- }.call(customer, tel, final_message: final_message, finish: finish)
- end
+ kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) {
+ raise "Invalid activation method"
+ }.call(customer, tel, final_message: final_message, finish: finish)
end
class Bitcoin
@@ -103,9 +103,15 @@ class RegistrationTest < Minitest::Test
em :test_for_not_activated_with_customer_id
class ActivationTest < Minitest::Test
+ Registration::Activation::DB = Minitest::Mock.new
+ Registration::Activation::REDIS = FakeRedis.new
+ Registration::Activation::Payment = Minitest::Mock.new
+ Registration::Activation::Finish = Minitest::Mock.new
Command::COMMAND_MANAGER = Minitest::Mock.new
+
def setup
- @activation = Registration::Activation.new("test", "+15555550000")
+ @customer = Minitest::Mock.new(customer)
+ @activation = Registration::Activation.new(@customer, "+15555550000")
end
def test_write
@@ -130,7 +136,9 @@ class RegistrationTest < Minitest::Test
RESPONSE
Command::COMMAND_MANAGER.expect(
:write,
- EMPromise.reject(:test_result),
+ 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(
@@ -139,13 +147,149 @@ class RegistrationTest < Minitest::Test
)
end]
)
+ @customer.expect(:with_plan, @customer, ["test_usd"])
+ @customer.expect(:save_plan!, EMPromise.resolve(nil), [])
+ Registration::Activation::Payment.expect(
+ :for,
+ EMPromise.reject(:test_result),
+ [Blather::Stanza::Iq, @customer, "+15555550000"]
+ )
assert_equal(
:test_result,
execute_command { @activation.write.catch { |e| e } }
)
assert_mock Command::COMMAND_MANAGER
+ assert_mock @customer
+ assert_mock Registration::Activation::Payment
end
em :test_write
+
+ def test_write_with_code
+ 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" },
+ { var: "code", value: "123" }
+ ]
+ }),
+ [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
+ )
+ end]
+ )
+ @customer.expect(:with_plan, @customer, ["test_usd"])
+ @customer.expect(:save_plan!, EMPromise.resolve(nil), [])
+ @customer.expect(:activate_plan_starting_now, EMPromise.resolve(nil), [])
+ Registration::Activation::DB.expect(:transaction, []) { |&blk| blk.call }
+ Registration::Activation::DB.expect(
+ :exec,
+ OpenStruct.new(cmd_tuples: 1),
+ [String, ["test", "123"]]
+ )
+ Registration::Activation::Finish.expect(
+ :new,
+ OpenStruct.new(write: EMPromise.reject(:test_result)),
+ [@customer, "+15555550000"]
+ )
+ assert_equal(
+ :test_result,
+ execute_command { @activation.write.catch { |e| e } }
+ )
+ assert_mock Command::COMMAND_MANAGER
+ assert_mock @customer
+ assert_mock Registration::Activation::Payment
+ assert_mock Registration::Activation::DB
+ end
+ em :test_write_with_code
+
+ def test_write_with_group_code
+ 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" },
+ { var: "code", value: "123" }
+ ]
+ }),
+ [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
+ )
+ end]
+ )
+ @customer.expect(:with_plan, @customer, ["test_usd"])
+ @customer.expect(:save_plan!, EMPromise.resolve(nil), [])
+ Registration::Activation::DB.expect(:transaction, []) { |&blk| blk.call }
+ Registration::Activation::DB.expect(
+ :exec,
+ OpenStruct.new(cmd_tuples: 0),
+ [String, ["test", "123"]]
+ )
+ Registration::Activation::Payment.expect(
+ :for,
+ EMPromise.reject(:test_result),
+ [Blather::Stanza::Iq, @customer, "+15555550000"]
+ )
+ assert_equal(
+ :test_result,
+ execute_command { @activation.write.catch { |e| e } }
+ )
+ assert_equal(
+ "123",
+ Registration::Activation::REDIS.get(
+ "jmp_customer_pending_invite-test"
+ ).sync
+ )
+ assert_mock Command::COMMAND_MANAGER
+ assert_mock @customer
+ assert_mock Registration::Activation::Payment
+ assert_mock Registration::Activation::DB
+ end
+ em :test_write_with_group_code
end
class AllowTest < Minitest::Test
@@ -272,23 +416,16 @@ class RegistrationTest < Minitest::Test
CustomerFinancials::BRAINTREE = Minitest::Mock.new
def test_for_bitcoin
- cust = Minitest::Mock.new(customer)
- cust.expect(:with_plan, cust, ["test_usd"])
- cust.expect(:save_plan!, nil)
iq = Blather::Stanza::Iq::Command.new
iq.form.fields = [
{ var: "activation_method", value: "bitcoin" },
{ var: "plan_name", value: "test_usd" }
]
- result = Registration::Payment.for(iq, cust, "+15555550000")
+ result = Registration::Payment.for(iq, customer, "+15555550000")
assert_kind_of Registration::Payment::Bitcoin, result
- assert_mock cust
end
def test_for_credit_card
- cust = Minitest::Mock.new(customer)
- cust.expect(:with_plan, cust, ["test_usd"])
- cust.expect(:save_plan!, nil)
braintree_customer = Minitest::Mock.new
CustomerFinancials::BRAINTREE.expect(
:customer,
@@ -306,6 +443,7 @@ class RegistrationTest < Minitest::Test
{ var: "activation_method", value: "credit_card" },
{ var: "plan_name", value: "test_usd" }
]
+ cust = customer
result = execute_command do
Command.execution.customer_repo.expect(:find, cust, ["test"])
Registration::Payment.for(
@@ -315,14 +453,10 @@ class RegistrationTest < Minitest::Test
).sync
end
assert_kind_of Registration::Payment::CreditCard, result
- assert_mock cust
end
em :test_for_credit_card
def test_for_code
- cust = Minitest::Mock.new(customer)
- cust.expect(:with_plan, cust, ["test_usd"])
- cust.expect(:save_plan!, nil)
iq = Blather::Stanza::Iq::Command.new
iq.form.fields = [
{ var: "activation_method", value: "code" },
@@ -330,11 +464,10 @@ class RegistrationTest < Minitest::Test
]
result = Registration::Payment.for(
iq,
- cust,
+ customer,
"+15555550000"
)
assert_kind_of Registration::Payment::InviteCode, result
- assert_mock cust
end
class BitcoinTest < Minitest::Test