diff --git a/lib/customer.rb b/lib/customer.rb index 84baa8bfea84cc2d5ca8903d31d1881eb77d3e91..62d5c58e0f8d500a2d77fde60c74f34b9136f044 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -81,10 +81,10 @@ class Customer ) end - def with_plan(plan_name) + def with_plan(plan_name, **kwargs) self.class.new( @customer_id, @jid, - plan: @plan.with_plan_name(plan_name), + plan: @plan.with_plan_name(plan_name, **kwargs), balance: @balance, tndetails: @tndetails, sgx: @sgx ) end diff --git a/lib/customer_plan.rb b/lib/customer_plan.rb index 63f2700703ca867c54dc589211180305868adb6d..bacd26640dde3febd4475ccca29a9b00dbd272fa 100644 --- a/lib/customer_plan.rb +++ b/lib/customer_plan.rb @@ -70,15 +70,29 @@ class CustomerPlan :expired end - def with_plan_name(plan_name) + def with_plan_name(plan_name, **kwargs) self.class.new( @customer_id, plan: Plan.for(plan_name), - expires_at: @expires_at + expires_at: @expires_at, **kwargs ) end + def verify_parent! + return unless @parent_customer_id + + result = DB.query(<<~SQL, [@parent_customer_id]) + SELECT plan_name FROM customer_plans WHERE customer_id=$1 + SQL + + raise "Invalid parent account" if !result || !result.first + + plan = Plan.for(result.first["plan_name"]) + raise "Parent currency mismatch" unless plan.currency == currency + end + def save_plan! + verify_parent! DB.exec_defer(<<~SQL, [@customer_id, plan_name, @parent_customer_id]) INSERT INTO plan_log (customer_id, plan_name, parent_customer_id, date_range) @@ -107,6 +121,7 @@ class CustomerPlan end def activate_plan_starting_now + verify_parent! activated = DB.exec(<<~SQL, [@customer_id, plan_name, @parent_customer_id]) INSERT INTO plan_log (customer_id, plan_name, date_range, parent_customer_id) VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'), $3) diff --git a/lib/registration.rb b/lib/registration.rb index 81ae35868dde9158b0f441a41398b868845203de..b001261992cb4d4b2083ac4bde40880ea20f1eaa 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -11,6 +11,7 @@ require_relative "./command" require_relative "./em" require_relative "./invites_repo" require_relative "./oob" +require_relative "./parent_code_repo" require_relative "./proxied_jid" require_relative "./tel_selections" require_relative "./welcome_message" @@ -105,7 +106,7 @@ class Registration def next_step(iq) code = iq.form.field("code")&.value&.to_s - save_customer_plan(iq).then { + save_customer_plan(iq, code).then { finish_if_valid_invite(code) }.catch_only(InvitesRepo::Invalid) do @invites.stash_code(customer.customer_id, code).then do @@ -124,10 +125,12 @@ class Registration 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! + def save_customer_plan(iq, code) + ParentCodeRepo.new(REDIS).find(code).then do |parent| + plan_name = iq.form.field("plan_name").value.to_s + @customer = @customer.with_plan(plan_name, parent_customer_id: parent) + @customer.save_plan! + end end class GooglePlay @@ -136,6 +139,7 @@ class Registration @google_play_userid = google_play_userid @tel = tel @invites = InvitesRepo.new(DB, REDIS) + @parent_code_repo = ParentCodeRepo.new(REDIS) end def used @@ -163,17 +167,25 @@ class Registration end def activate(iq) - REDIS.sadd("google_play_userids", @google_play_userid).then { - plan_name = iq.form.field("plan_name").value.to_s - @customer = @customer.with_plan(plan_name) - @customer.activate_plan_starting_now + plan_name = iq.form.field("plan_name").value + code = iq.form.field("code")&.value + EMPromise.all([ + @parent_code_repo.find(code), + REDIS.sadd("google_play_userids", @google_play_userid) + ]).then { |(parent, _)| + save_active_plan(plan_name, parent) }.then do - use_referral_code(iq.form.field("code")&.value&.to_s) + use_referral_code(code) end end protected + def save_active_plan(plan_name, parent) + @customer = @customer.with_plan(plan_name, parent_customer_id: parent) + @customer.activate_plan_starting_now + end + def use_referral_code(code) EMPromise.resolve(nil).then { @invites.claim_code(@customer.customer_id, code) { @@ -406,7 +418,24 @@ class Registration end class InviteCode - Payment.kinds[:code] = method(:new) + Payment.kinds[:code] = ->(*args, **kw) { self.for(*args, **kw) } + + def self.for(in_customer, tel, finish: Finish, **) + reload_customer(in_customer).then do |customer| + if customer.balance >= CONFIG[:activation_amount_accept] + next BillPlan.new(customer, tel, finish: finish) + end + + msg = if customer.balance.positive? + "Account balance not enough to cover the activation" + end + new(customer, tel, error: msg) + end + end + + def self.reload_customer(customer) + Command.execution.customer_repo.find(customer.customer_id) + end FIELDS = [{ var: "code", diff --git a/test/test_customer.rb b/test/test_customer.rb index 0fb7b7c4db8914cc06ce0eafbd5c05814f01ec94..6da8192abf0942d38d4e193b38afcaeeb9ca3038 100644 --- a/test/test_customer.rb +++ b/test/test_customer.rb @@ -49,6 +49,11 @@ class CustomerTest < Minitest::Test em :test_bill_plan_activate def test_bill_plan_reactivate_child + CustomerPlan::DB.expect( + :query, + [{ "plan_name" => "test_usd" }], + [String, ["parent"]] + ) CustomerPlan::DB.expect(:transaction, nil) do |&block| block.call true diff --git a/test/test_customer_repo.rb b/test/test_customer_repo.rb index 1d84993728615f3647451ab7c5fb5c4cd99dec80..bf7b6d0befdda7cef72cbab5456737de2ef4fe66 100644 --- a/test/test_customer_repo.rb +++ b/test/test_customer_repo.rb @@ -194,6 +194,11 @@ class CustomerRepoTest < Minitest::Test EMPromise.resolve([]), ["jmp_customer_feature_flags-testp"] ) + CustomerPlan::DB.expect( + :query, + [{ "plan_name" => "test_usd" }], + [String, ["1234"]] + ) CustomerPlan::DB.expect( :exec_defer, EMPromise.resolve(nil), diff --git a/test/test_registration.rb b/test/test_registration.rb index 7be8c1de9bb6579713097477d7425d14a07959c8..1d89b139957b234d65418d18d1da79594d215171 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -112,7 +112,9 @@ class RegistrationTest < Minitest::Test class ActivationTest < Minitest::Test Registration::Activation::DB = Minitest::Mock.new - Registration::Activation::REDIS = FakeRedis.new + Registration::Activation::REDIS = FakeRedis.new( + "jmp_parent_codes" => { "PARENT_CODE" => 1 } + ) Registration::Activation::Payment = Minitest::Mock.new Registration::Activation::Finish = Minitest::Mock.new Command::COMMAND_MANAGER = Minitest::Mock.new @@ -136,7 +138,9 @@ class RegistrationTest < Minitest::Test ) end] ) - @customer.expect(:with_plan, @customer, ["test_usd"]) + @customer.expect(:with_plan, @customer) do |*args, **| + assert_equal ["test_usd"], args + end @customer.expect(:save_plan!, EMPromise.resolve(nil), []) Registration::Activation::Payment.expect( :for, @@ -170,7 +174,9 @@ class RegistrationTest < Minitest::Test ) end] ) - @customer.expect(:with_plan, @customer, ["test_usd"]) + @customer.expect(:with_plan, @customer) do |*args, **| + assert_equal ["test_usd"], args + end @customer.expect(:save_plan!, EMPromise.resolve(nil), []) @customer.expect(:activate_plan_starting_now, EMPromise.resolve(nil), []) Registration::Activation::DB.expect(:transaction, []) { |&blk| blk.call } @@ -212,7 +218,9 @@ class RegistrationTest < Minitest::Test ) end] ) - @customer.expect(:with_plan, @customer, ["test_usd"]) + @customer.expect(:with_plan, @customer) do |*args, **| + assert_equal ["test_usd"], args + end @customer.expect(:save_plan!, EMPromise.resolve(nil), []) Registration::Activation::DB.expect(:transaction, []) { |&blk| blk.call } Registration::Activation::DB.expect( @@ -241,6 +249,50 @@ class RegistrationTest < Minitest::Test assert_mock Registration::Activation::DB end em :test_write_with_group_code + + def test_write_with_parent_code + 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: "PARENT_CODE" } + ] + }), + [Matching.new do |iq| + assert_equal :form, iq.form.type + assert_equal( + "You've selected +15555550000 as your JMP number.", + iq.form.instructions.lines.first.chomp + ) + end] + ) + @customer.expect(:with_plan, @customer) do |*args, **kwargs| + assert_equal ["test_usd"], args + assert_equal({ parent_customer_id: 1 }, kwargs) + end + @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", "PARENT_CODE"]] + ) + 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 + assert_mock Registration::Activation::DB + end + em :test_write_with_parent_code end class AllowTest < Minitest::Test @@ -426,13 +478,18 @@ class RegistrationTest < Minitest::Test { var: "activation_method", value: "code" }, { var: "plan_name", value: "test_usd" } ] - result = Registration::Payment.for( - iq, - customer, - "+15555550000" - ) + cust = customer + result = execute_command do + Command.execution.customer_repo.expect(:find, cust, ["test"]) + Registration::Payment.for( + iq, + cust, + "+15555550000" + ) + end assert_kind_of Registration::Payment::InviteCode, result end + em :test_for_code class BitcoinTest < Minitest::Test Registration::Payment::Bitcoin::BTC_SELL_PRICES = Minitest::Mock.new