diff --git a/forms/registration/activate.rb b/forms/registration/activate.rb index 28b021de17fda7ac05ffe5040f5ea78a63cc956d..95886767e9ebc5ef6d5d57ce7a59a752fa43d813 100644 --- a/forms/registration/activate.rb +++ b/forms/registration/activate.rb @@ -3,7 +3,7 @@ title "Activate JMP" instructions <<~I You've selected #{@tel} as your JMP number. - To activate your account, you can either deposit $#{CONFIG[:activation_amount]} to your balance or enter your referral code if you have one. + To activate your account, you can either deposit $#{'%.2f' % (CONFIG[:activation_amount] + @tel.price)} to your balance or enter your referral code if you have one. (If you'd like to pay in another cryptocurrency, currently we recommend using a service like simpleswap.io, morphtoken.com, changenow.io, or godex.io.) I diff --git a/forms/registration/bch.rb b/forms/registration/bch.rb index 3af32deddbcfffc86ac5ca354ab3d2242f356bd5..e01390d7f07adce0869d8814d9055f23b917c76e 100644 --- a/forms/registration/bch.rb +++ b/forms/registration/bch.rb @@ -13,7 +13,4 @@ field( value: @addr ) -instructions( - "You will received a notification when your payment is complete." \ - "#{@final_message}" -) +instructions "You will received a notification when your payment is complete." diff --git a/forms/registration/btc.rb b/forms/registration/btc.rb index 9451e7681766b01b0f09266c326ecaa6d3fb1706..9228e0eb655805cd81629b47618322d45c9cb00c 100644 --- a/forms/registration/btc.rb +++ b/forms/registration/btc.rb @@ -13,7 +13,4 @@ field( value: @addr ) -instructions( - "You will received a notification when your payment is complete." \ - "#{@final_message}" -) +instructions "You will received a notification when your payment is complete." diff --git a/forms/registration/mail.rb b/forms/registration/mail.rb index f801b0fd76f8622f11759dfbcab596ab1655d695..c4fe3f76c73e95b0bf7993e803b8731c86c443fb 100644 --- a/forms/registration/mail.rb +++ b/forms/registration/mail.rb @@ -3,10 +3,9 @@ title "Activate by Mail or Interac e-Tranfer" instructions( "Activate your account by sending at least " \ - "$#{CONFIG[:activation_amount]}\nWe support payment by " \ + "$#{'%.2f' % @price}\nWe support payment by " \ "postal mail or, in Canada, by Interac e-Transfer.\n\n" \ - "You will receive a notification when your payment is complete." \ - "#{@final_message}" + "You will receive a notification when your payment is complete." ) if @customer_id diff --git a/lib/invites_repo.rb b/lib/invites_repo.rb index 023a06bb4aad43c5722f3dc63db0c198e99a3d09..a760988eb6143850356ef7f035f6ce5f4c17f1e1 100644 --- a/lib/invites_repo.rb +++ b/lib/invites_repo.rb @@ -133,7 +133,9 @@ protected end def invalid_code(customer_id, code) - @redis.incr("jmp_invite_tries-#{customer_id}").then { + stash_code(customer_id, code).then { + @redis.incr("jmp_invite_tries-#{customer_id}") + }.then { @redis.expire("jmp_invite_tries-#{customer_id}", 60 * 60) }.then { @redis.hexists("jmp_group_codes", code) diff --git a/lib/parent_code_repo.rb b/lib/parent_code_repo.rb index ed92d25103053df67548f787595e53d5ad9269a4..5631a913d8ff5aaed04bad79ed5fb58f1eba5202 100644 --- a/lib/parent_code_repo.rb +++ b/lib/parent_code_repo.rb @@ -7,6 +7,8 @@ require_relative "customer" require_relative "trust_level_repo" class ParentCodeRepo + class Invalid < StandardError; end + def initialize( redis: REDIS, db: DB, @@ -17,6 +19,21 @@ class ParentCodeRepo @trust_level_repo = trust_level_repo end + def claim_code(customer, code) + customer_domain = ProxiedJID.new(customer.jid).unproxied.domain + find(code).then do |parent| + raise Invalid, "Not a valid code" unless parent + + if parent && customer_domain == CONFIG[:onboarding_domain] + raise "Please create a new Jabber ID before creating a subaccount." + end + + plan_name = customer.plan_name + customer = customer.with_plan(plan_name, parent_customer_id: parent) + customer.save_plan!.then { block_given? ? yield(customer) : customer } + end + end + def find(code) @redis.hget("jmp_parent_codes", code).then do |parent_id| trust_level_guard(parent_id).then { parent_id } diff --git a/lib/registration.rb b/lib/registration.rb index 910db903fb32822ff35443893741c8802561bfa9..274b9ecc578c953ea7bc9066141cf6743651fd39 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -16,6 +16,12 @@ require_relative "./proxied_jid" require_relative "./tel_selections" require_relative "./welcome_message" +def reload_customer(customer) + EMPromise.resolve(nil).then do + Command.execution.customer_repo.find(customer.customer_id) + end +end + class Registration def self.for(customer, google_play_userid, tel_selections) if (reg = customer.registered?) @@ -41,13 +47,6 @@ class Registration end end - def self.guard_onboarding_subaccounts(customer) - customer_domain = ProxiedJID.new(customer.jid).domain - return unless customer_domain == CONFIG[:onboarding_domain] - - raise "Please create a new Jabber ID before creating a subaccount." - end - class Registered def self.for(customer, tel) jid = ProxiedJID.new(customer.jid).unproxied @@ -108,7 +107,6 @@ class Registration def initialize(customer, tel) @customer = customer @tel = tel - @invites = InvitesRepo.new(DB, REDIS) end attr_reader :customer, :tel @@ -125,33 +123,15 @@ class Registration end def next_step(iq) - code = iq.form.field("code")&.value&.to_s - 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 - 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, code) - Registration.guard_onboarding_subaccounts(@customer) - - ParentCodeRepo.new(redis: REDIS, db: DB).find(code).then do |parent| + EMPromise.resolve(nil).then do plan = Plan.for_registration(iq.form.field("plan_name").value.to_s) - @customer = @customer.with_plan(plan.name, parent_customer_id: parent) - @customer.save_plan! + @customer = @customer.with_plan(plan.name) + Registration::Payment::InviteCode.new( + @customer, @tel, finish: Finish, db: DB, redis: REDIS + ).parse(iq, force_save_plan: true) + .catch_only(InvitesRepo::Invalid) do + Payment.for(iq, @customer, @tel).then(&:write) + end end end @@ -279,10 +259,16 @@ class Registration @kinds ||= {} end - def self.for(iq, customer, tel, final_message: nil, finish: Finish) + def self.for( + iq, customer, tel, + finish: Finish, maybe_bill: MaybeBill, + price: CONFIG[:activation_amount] + tel.price + ) 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) + }.call( + customer, tel, finish: finish, maybe_bill: maybe_bill, price: price + ) end class CryptoPaymentMethod @@ -298,11 +284,14 @@ class Registration raise NotImplementedError, "Subclass must implement" end - def initialize(customer, tel, final_message: nil, **) + def initialize( + customer, tel, + price: CONFIG[:activation_amount] + tel.price, ** + ) @customer = customer @customer_id = customer.customer_id @tel = tel - @final_message = final_message + @price = price end def save @@ -312,13 +301,10 @@ class Registration attr_reader :customer_id, :tel def form(rate, addr) - amount = CONFIG[:activation_amount] / rate - FormTemplate.render( reg_form_name, - amount: amount, - addr: addr, - final_message: @final_message + amount: @price / rate, + addr: addr ) end @@ -392,34 +378,45 @@ class Registration end end - class CreditCard - Payment.kinds[:credit_card] = ->(*args, **kw) { self.for(*args, **kw) } + class MaybeBill + def initialize(customer, tel, finish: Finish) + @customer = customer + @tel = tel + @finish = finish + end - def self.for(in_customer, tel, finish: Finish, **) - reload_customer(in_customer).then do |(customer, payment_methods)| + def call + reload_customer(@customer).then do |customer| if customer.balance >= CONFIG[:activation_amount_accept] - next BillPlan.new(customer, tel, finish: finish) + next BillPlan.new(customer, @tel, finish: @finish) end - if (method = payment_methods.default_payment_method) - next Activate.new(customer, method, tel, finish: finish) - end - - new(customer, tel, finish: finish) + yield customer end end + end - def self.reload_customer(customer) - EMPromise.all([ - Command.execution.customer_repo.find(customer.customer_id), - customer.payment_methods - ]) + class JustCharge + def initialize(customer, *, **) + @customer = customer end - def initialize(customer, tel, finish: Finish) + def call; end + end + + class CreditCard + Payment.kinds[:credit_card] = method(:new) + + def initialize( + customer, tel, + finish: Finish, maybe_bill: MaybeBill, + price: CONFIG[:activation_amount] + tel.price + ) @customer = customer @tel = tel @finish = finish + @maybe_bill = maybe_bill.new(customer, tel, finish: finish) + @price = price end def oob(reply) @@ -427,8 +424,8 @@ class Registration oob.url = CONFIG[:credit_card_url].call( reply.to.stripped.to_s.gsub("\\", "%5C"), @customer.customer_id - ) + "&amount=#{CONFIG[:activation_amount]}" - oob.desc = "Add credit card, save, then next here to continue" + ) + "&amount=#{@price.ceil}" + oob.desc = "Pay by credit card, save, then next here to continue" oob end @@ -441,85 +438,13 @@ class Registration }.then do |iq| next Activation.for(@customer, nil, @tel).then(&:write) if iq.prev? - CreditCard.for(@customer, @tel, finish: @finish).then(&:write) - end - end - - class Activate - def initialize(customer, payment_method, tel, finish: Finish) - @customer = customer - @payment_method = payment_method - @tel = tel - @finish = finish - end - - def write - CreditCardSale.create( - @customer, - amount: CONFIG[:activation_amount], - payment_method: @payment_method - ).then( - ->(_) { sold }, - ->(_) { declined } - ) - end - - protected - - def sold - BillPlan.new(@customer, @tel, finish: @finish).write - end - - DECLINE_MESSAGE = - "Your bank declined the transaction. " \ - "Often this happens when a person's credit card " \ - "is a US card that does not support international " \ - "transactions, as JMP is not based in the USA, though " \ - "we do support transactions in USD.\n\n" \ - "You may add another card" - - def decline_oob(reply) - oob = OOB.find_or_create(reply.command) - oob.url = CONFIG[:credit_card_url].call( - reply.to.stripped.to_s.gsub("\\", "%5C"), - @customer.customer_id - ) + "&amount=#{CONFIG[:activation_amount]}" - oob.desc = DECLINE_MESSAGE - oob - end - - def declined - Command.reply { |reply| - reply_oob = decline_oob(reply) - reply.allowed_actions = [:next] - reply.note_type = :error - reply.note_text = "#{reply_oob.desc}: #{reply_oob.url}" - }.then do - CreditCard.for(@customer, @tel, finish: @finish).then(&:write) - end + @maybe_bill.call { self }&.then(&:write) end end end class InviteCode - 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, finish: Finish) - end - end - - def self.reload_customer(customer) - Command.execution.customer_repo.find(customer.customer_id) - end + Payment.kinds[:code] = method(:new) FIELDS = [{ var: "code", @@ -528,12 +453,16 @@ class Registration required: true }].freeze - def initialize(customer, tel, error: nil, finish: Finish, **) + def initialize( + customer, tel, + error: nil, finish: Finish, db: DB, redis: REDIS, ** + ) @customer = customer @tel = tel @error = error @finish = finish - @parent_code_repo = ParentCodeRepo.new(redis: REDIS, db: DB) + @invites_repo = InvitesRepo.new(db, redis) + @parent_code_repo = ParentCodeRepo.new(db: db, redis: redis) end def add_form(reply) @@ -548,48 +477,44 @@ class Registration Command.reply { |reply| reply.allowed_actions = [:next, :prev] add_form(reply) - }.then(&method(:parse)) + }.then(&method(:parse)).catch_only(InvitesRepo::Invalid) { |e| + invalid_code(e).write + } end - def parse(iq) + def parse(iq, force_save_plan: false) return Activation.for(@customer, nil, @tel).then(&:write) if iq.prev? - verify(iq.form.field("code")&.value&.to_s) - .catch_only(InvitesRepo::Invalid, &method(:invalid_code)) + verify(iq.form.field("code")&.value&.to_s, force_save_plan) .then(&:write) end protected def invalid_code(e) - InviteCode.new(@customer, @tel, error: e.message) + self.class.new(@customer, @tel, error: e.message, finish: @finish) end def customer_id @customer.customer_id end - def verify(code) - @parent_code_repo.find(code).then do |parent_customer_id| - if parent_customer_id - set_parent(parent_customer_id) - else - InvitesRepo.new(DB, REDIS).claim_code(customer_id, code) { + def verify(code, force_save_plan) + @parent_code_repo.claim_code(@customer, code) { + check_parent_balance + }.catch_only(ParentCodeRepo::Invalid) { + (@customer.save_plan! if force_save_plan).then do + @invites_repo.claim_code(customer_id, code) { @customer.activate_plan_starting_now - }.then { Finish.new(@customer, @tel) } + }.then { @finish.new(@customer, @tel) } end - end + } end - def set_parent(parent_customer_id) - Registration.guard_onboarding_subaccounts(@customer) - - @customer = @customer.with_plan( - @customer.plan_name, - parent_customer_id: parent_customer_id - ) - @customer.save_plan!.then do - self.class.for(@customer, @tel, finish: @finish) + def check_parent_balance + MaybeBill.new(@customer, @tel, finish: @finish).call do + msg = "Account balance not enough to cover the activation" + invalid_code(RuntimeError.new(msg)) end end end @@ -597,17 +522,20 @@ class Registration class Mail Payment.kinds[:mail] = method(:new) - def initialize(customer, tel, final_message: nil, **) + def initialize( + customer, tel, + price: CONFIG[:activation_amount] + tel.price, ** + ) @customer = customer @tel = tel - @final_message = final_message + @price = price end def form FormTemplate.render( "registration/mail", currency: @customer.currency, - final_message: @final_message, + price: @price, **onboarding_extras ) end @@ -645,11 +573,39 @@ class Registration def write @customer.bill_plan(note: "Bill #{@tel} for first month").then do - @finish.new(@customer, @tel).write + updated_customer = + @customer.with_balance(@customer.balance - @customer.monthly_price) + @finish.new(updated_customer, @tel).write end end end + class BuyNumber + def initialize(customer, tel) + @customer = customer + @tel = tel + end + + def write + Command.reply { |reply| + reply.command << FormTemplate.render( + "registration/buy_number", + tel: @tel + ) + }.then(&method(:parse)).then(&:write) + end + + protected + + def parse(iq) + Payment.for( + iq, @customer, @tel, + maybe_bill: ::Registration::Payment::JustCharge, + price: @tel.price + ) + end + end + class Finish def initialize(customer, tel) @customer = customer @@ -658,6 +614,8 @@ class Registration end def write + return buy_number if @customer.balance < @tel.price + @tel.order(DB, @customer).then( ->(_) { customer_active_tel_purchased }, method(:number_purchase_error) @@ -666,6 +624,14 @@ class Registration protected + def buy_number + BuyNumber.new(@customer, @tel).write.then do + reload_customer(@customer).then { |customer| + Finish.new(customer, @tel) + }.then(&:write) + end + end + def number_purchase_error(e) Command.log.error "number_purchase_error", e TEL_SELECTIONS.delete(@customer.jid).then { @@ -712,7 +678,8 @@ class Registration EMPromise.all([ TEL_SELECTIONS.delete(@customer.jid), put_default_fwd, - use_referral_code + use_referral_code, + @tel.charge(@customer) ]) }.then do FinishOnboarding.for(@customer, @tel).then(&:write) diff --git a/lib/statsd.rb b/lib/statsd.rb index 5fd77c3fb0c6ef89a7b16bee8dbe7b89bf3fc142..f54a225500aeca335a2366e7309ad69948218d27 100644 --- a/lib/statsd.rb +++ b/lib/statsd.rb @@ -17,10 +17,6 @@ Registration::Payment::Bitcoin.statsd_count :write, "registration.payment.bitcoi Registration::Payment::CreditCard.extend StatsD::Instrument Registration::Payment::CreditCard.statsd_count :write, "registration.payment.credit_card" -Registration::Payment::CreditCard::Activate.extend StatsD::Instrument -Registration::Payment::CreditCard::Activate.statsd_count :write, "registration.payment.credit_card.activate" -Registration::Payment::CreditCard::Activate.statsd_count :declined, "registration.payment.credit_card.activate_declined" - Registration::Payment::InviteCode.extend StatsD::Instrument Registration::Payment::InviteCode.statsd_count :write, "registration.payment.invite_code" diff --git a/lib/tel_selections.rb b/lib/tel_selections.rb index 0f6ce10e6dab0febee0ef0951bd9c4d9e7a278a9..7e1f3bb17506b5e39fbbd908e2a9396a01e132bd 100644 --- a/lib/tel_selections.rb +++ b/lib/tel_selections.rb @@ -155,7 +155,7 @@ class TelSelections full_number: row["tel"].sub(/\A\+1/, ""), city: row["locality"], state: row["region"] - ), row["bandwidth_account_id"]) + ), row["bandwidth_account_id"], price: row["premium_price"]) } } end @@ -215,13 +215,41 @@ class TelSelections def self.for_pending_value(value) if value.start_with?("LocalInventory/") - tel, account = value.sub(/\ALocalInventory\//, "").split("/", 2) - LocalInventory.new(Tn.new(tel), account) + tel, account, price = + value.sub(/\ALocalInventory\//, "").split("/", 3) + LocalInventory.new(Tn.new(tel), account, price: price.to_d) else Bandwidth.new(Tn.new(value)) end end + def price + 0 + end + + # Creates and inserts transaction charging the customer + # for the phone number. If price <= 0 this is a noop. + # This method never checks customer balance. + # + # @param customer [Customer] the customer to charge + def charge(customer) + return if price <= 0 + + transaction(customer).insert + end + + # @param customer [Customer] the customer to charge + def transaction(customer) + Transaction.new( + customer_id: customer.customer_id, + transaction_id: + "#{customer.customer_id}-bill-#{@tel}-at-#{Time.now.to_i}", + amount: -price, + note: "One-time charge for number: #{formatted_tel}", + ignore_duplicate: false + ) + end + def initialize(tel) @tel = tel end @@ -286,6 +314,8 @@ class TelSelections end class LocalInventory < SimpleDelegator + attr_reader :price + def self.fetch(tn, db: DB) db.query_defer("SELECT * FROM tel_inventory WHERE tel = $1", [tn]) .then { |rows| @@ -294,18 +324,19 @@ class TelSelections full_number: row["tel"].sub(/\A\+1/, ""), city: row["locality"], state: row["region"] - ), row["bandwidth_account_id"]) + ), row["bandwidth_account_id"], price: row["premium_price"]) } } end - def initialize(tn, bandwidth_account_id) + def initialize(tn, bandwidth_account_id, price: 0) super(tn) @bandwidth_account_id = bandwidth_account_id + @price = price end def pending_value - "LocalInventory/#{tel}/#{@bandwidth_account_id}" + "LocalInventory/#{tel}/#{@bandwidth_account_id}/#{price}" end def reserve(*) diff --git a/sgx_jmp.rb b/sgx_jmp.rb index ca1827c340cc25e6ce4ee09a72cb9a53ca185b91..de932d7c6c8470b49dda58a7078f4433c17644a0 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -621,7 +621,6 @@ Command.new( customer.save_plan!.then { Registration::Payment.for( iq, customer, customer.registered?.phone, - final_message: PaypalDone::MESSAGE, finish: PaypalDone ) }.then(&:write).catch_only(Command::Execution::FinalStanza) do |s| diff --git a/test/test_helper.rb b/test/test_helper.rb index 539f3e5ae84b5135bdc65c7f1cf3d9f9677a77ec..7729ff95c5fcb1373721cc7b1ced347bea51a889 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -304,7 +304,7 @@ class FakeRedis end def hget(key, field) - @values.dig(key, field) + EMPromise.resolve(@values.dig(key, field)) end def hexists(key, field) diff --git a/test/test_registration.rb b/test/test_registration.rb index d40fd78fc92ca17f79a840afc6306222430c3ded..79246f852eb013ce841276a2c68e2c4c4f54524b 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -155,7 +155,8 @@ class RegistrationTest < Minitest::Test def setup @customer = Minitest::Mock.new(customer) - @activation = Registration::Activation.new(@customer, "+15555550000") + @tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") + @activation = Registration::Activation.new(@customer, @tel) end def test_write @@ -167,7 +168,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] @@ -179,7 +180,7 @@ class RegistrationTest < Minitest::Test Registration::Activation::Payment.expect( :for, EMPromise.reject(:test_result), - [Blather::Stanza::Iq, @customer, "+15555550000"] + [Blather::Stanza::Iq, @customer, @tel] ) assert_equal( :test_result, @@ -191,6 +192,43 @@ class RegistrationTest < Minitest::Test end em :test_write + def test_write_with_onboarding_jid + 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 (555) 555-0000 as your JMP number.", + iq.form.instructions.lines.first.chomp + ) + end] + ) + @customer.expect( + :jid, + Blather::JID.new("test\\40onboarding.example.com@proxy") + ) + @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, + EMPromise.reject(:test_result), + [Blather::Stanza::Iq, @customer, @tel] + ) + 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_with_onboarding_jid + def test_write_bad_plan Command::COMMAND_MANAGER.expect( :write, @@ -200,7 +238,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] @@ -226,7 +264,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] @@ -245,7 +283,7 @@ class RegistrationTest < Minitest::Test Registration::Activation::Finish.expect( :new, OpenStruct.new(write: EMPromise.reject(:test_result)), - [@customer, "+15555550000"] + [@customer, @tel] ) assert_equal( :test_result, @@ -270,7 +308,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] @@ -288,7 +326,7 @@ class RegistrationTest < Minitest::Test Registration::Activation::Payment.expect( :for, EMPromise.reject(:test_result), - [Blather::Stanza::Iq, @customer, "+15555550000"] + [Blather::Stanza::Iq, @customer, @tel] ) assert_equal( :test_result, @@ -308,52 +346,57 @@ class RegistrationTest < Minitest::Test 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] - ) - Registration::Activation::DB.expect( - :query_one, {}, [String, "1"], default: {} - ) - Registration::Activation::DB.expect( - :query_one, { c: 0 }, [String, "1"], default: { c: 0 } - ) - @customer.expect(:with_plan, @customer) do |*args, **kwargs| - assert_equal ["test_usd"], args - assert_equal({ parent_customer_id: "1" }, kwargs) + execute_command do + 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 (555) 555-0000 as your JMP number.", + iq.form.instructions.lines.first.chomp + ) + end] + ) + Registration::Activation::DB.expect( + :query_one, {}, [String, "1"], default: {} + ) + Registration::Activation::DB.expect( + :query_one, { c: 0 }, [String, "1"], default: { c: 0 } + ) + @customer.expect(:with_plan, @customer) do |*args, **| + assert_equal ["test_usd"], args + end + @customer.expect(:with_plan, @customer) do |*, **kwargs| + assert_equal({ parent_customer_id: "1" }, kwargs) + end + @customer.expect(:save_plan!, EMPromise.resolve(nil), []) + @customer.expect(:balance, 100, []) + Command.execution.customer_repo.expect( + :find, + EMPromise.resolve(@customer), ["test"] + ) + Registration::Payment::MaybeBill::BillPlan.expect( + :new, + EMPromise.reject(:test_result) + ) do |*args, **| + assert_equal @tel, args[1] + end + assert_equal( + :test_result, + @activation.write.catch { |e| e }.sync + ) 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 + assert_mock Registration::Payment::MaybeBill::BillPlan end em :test_write_with_parent_code @@ -369,14 +412,24 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] ) - @customer.expect(:jid, Blather::JID.new("test@onboarding.example.com")) + Registration::Activation::DB.expect( + :query_one, {}, [String, "1"], default: {} + ) + Registration::Activation::DB.expect( + :query_one, { c: 0 }, [String, "1"], default: { c: 0 } + ) + @customer.expect(:with_plan, @customer, ["test_usd"]) + @customer.expect( + :jid, + Blather::JID.new("test\\40onboarding.example.com@proxy") + ) iq = Blather::Stanza::Iq::Command.new - iq.from = "test@onboarding.example.com" + iq.from = "test\\40onboarding.example.com@proxied" assert_equal( "Please create a new Jabber ID before creating a subaccount.", execute_command(iq) { @activation.write.catch(&:to_s) } @@ -400,7 +453,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] @@ -434,7 +487,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] @@ -462,8 +515,9 @@ class RegistrationTest < Minitest::Test Registration::Activation::Allow::DB = Minitest::Mock.new def test_write_credit_to_nil + tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") cust = Minitest::Mock.new(customer("test")) - allow = Registration::Activation::Allow.new(cust, "+15555550000", nil) + allow = Registration::Activation::Allow.new(cust, tel, nil) Command::COMMAND_MANAGER.expect( :write, @@ -473,7 +527,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) assert_equal 1, iq.form.fields.length @@ -498,8 +552,9 @@ class RegistrationTest < Minitest::Test def test_write_credit_to_refercust cust = Minitest::Mock.new(customer("test")) + tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") allow = Registration::Activation::Allow.new( - cust, "+15555550000", "refercust" + cust, tel, "refercust" ) Command::COMMAND_MANAGER.expect( @@ -510,7 +565,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) assert_equal 1, iq.form.fields.length @@ -545,10 +600,11 @@ class RegistrationTest < Minitest::Test def setup @customer = customer + @tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") @google_play = Registration::Activation::GooglePlay.new( @customer, "abcd", - "+15555550000" + @tel ) end @@ -561,7 +617,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( - "You've selected +15555550000 as your JMP number.", + "You've selected (555) 555-0000 as your JMP number.", iq.form.instructions.lines.first.chomp ) end] @@ -580,7 +636,7 @@ class RegistrationTest < Minitest::Test Registration::Activation::GooglePlay::Finish.expect( :new, OpenStruct.new(write: EMPromise.reject(:test_result)), - [Customer, "+15555550000"] + [Customer, @tel] ) result = execute_command { @google_play.write.catch { |e| e } } assert_equal :test_result, result @@ -596,37 +652,29 @@ class RegistrationTest < Minitest::Test CustomerFinancials::BRAINTREE = Minitest::Mock.new def test_for_bitcoin + tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") 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, customer, "+15555550000") + result = Registration::Payment.for(iq, customer, tel) assert_kind_of Registration::Payment::Bitcoin, result end def test_for_bch + tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") iq = Blather::Stanza::Iq::Command.new iq.form.fields = [ { var: "activation_method", value: "bch" }, { var: "plan_name", value: "test_usd" } ] - result = Registration::Payment.for(iq, customer, "+15555550000") + result = Registration::Payment.for(iq, customer, tel) assert_kind_of Registration::Payment::BCH, result end def test_for_credit_card - braintree_customer = Minitest::Mock.new - CustomerFinancials::BRAINTREE.expect( - :customer, - braintree_customer - ) - CustomerFinancials::REDIS.expect(:smembers, [], ["block_credit_cards"]) - braintree_customer.expect( - :find, - EMPromise.resolve(OpenStruct.new(payment_methods: [])), - ["test"] - ) + tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") iq = Blather::Stanza::Iq::Command.new iq.from = "test@example.com" iq.form.fields = [ @@ -639,8 +687,8 @@ class RegistrationTest < Minitest::Test Registration::Payment.for( iq, cust, - "" - ).sync + tel + ) end assert_kind_of Registration::Payment::CreditCard, result end @@ -658,7 +706,7 @@ class RegistrationTest < Minitest::Test Registration::Payment.for( iq, cust, - "+15555550000" + TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") ) end assert_kind_of Registration::Payment::InviteCode, result @@ -677,9 +725,10 @@ class RegistrationTest < Minitest::Test :add_btc_address, EMPromise.resolve("testaddr") ) + @tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") @bitcoin = Registration::Payment::Bitcoin.new( @customer, - "+15555550000" + @tel ) end @@ -726,9 +775,10 @@ class RegistrationTest < Minitest::Test :add_bch_address, EMPromise.resolve("testaddr") ) + @tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") @bch = Registration::Payment::BCH.new( @customer, - "+15555550000" + @tel ) end @@ -765,13 +815,15 @@ class RegistrationTest < Minitest::Test class CreditCardTest < Minitest::Test def setup + @tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") @credit_card = Registration::Payment::CreditCard.new( customer, - "+15555550000" + @tel ) end - def test_for + def test_new + tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") cust = Minitest::Mock.new(customer) cust.expect( :payment_methods, @@ -780,32 +832,12 @@ class RegistrationTest < Minitest::Test execute_command do Command.execution.customer_repo.expect(:find, cust, ["test"]) assert_kind_of( - Registration::Payment::CreditCard::Activate, - Registration::Payment::CreditCard.for( - cust, - "+15555550000" - ).sync + Registration::Payment::CreditCard, + Registration::Payment::CreditCard.new(cust, tel) ) end end - em :test_for - - def test_for_has_balance - cust = Minitest::Mock.new(customer) - cust.expect(:balance, 100) - cust.expect(:payment_methods, EMPromise.resolve(nil)) - execute_command do - Command.execution.customer_repo.expect(:find, cust, ["test"]) - assert_kind_of( - Registration::BillPlan, - Registration::Payment::CreditCard.for( - cust, - "+15555550000" - ).sync - ) - end - end - em :test_for_has_balance + em :test_new def test_write result = execute_command do @@ -815,7 +847,7 @@ class RegistrationTest < Minitest::Test [Matching.new do |reply| assert_equal [:execute, :next, :prev], reply.allowed_actions assert_equal( - "Add credit card, save, then next here to continue: " \ + "Pay by credit card, save, then next here to continue: " \ "http://creditcard.example.com?&amount=1", reply.note.content ) @@ -832,9 +864,10 @@ class RegistrationTest < Minitest::Test class MailTest < Minitest::Test def setup + @tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") @mail = Registration::Payment::Mail.new( customer(plan_name: "test_cad"), - "+15555550000" + @tel ) end @@ -865,88 +898,6 @@ class RegistrationTest < Minitest::Test em :test_write end - class ActivateTest < Minitest::Test - Registration::Payment::CreditCard::Activate::Finish = - Minitest::Mock.new - Registration::Payment::CreditCard::Activate::CreditCardSale = - Minitest::Mock.new - Command::COMMAND_MANAGER = Minitest::Mock.new - - def test_write - customer = Minitest::Mock.new( - customer(plan_name: "test_usd") - ) - Registration::Payment::CreditCard::Activate::CreditCardSale.expect( - :create, - EMPromise.resolve(nil) - ) do |acustomer, amount:, payment_method:| - assert_operator customer, :===, acustomer - assert_equal CONFIG[:activation_amount], amount - assert_equal :test_default_method, payment_method - end - customer.expect( - :bill_plan, - nil, - note: "Bill +15555550000 for first month" - ) - Registration::Payment::CreditCard::Activate::Finish.expect( - :new, - OpenStruct.new(write: nil), - [customer, "+15555550000"] - ) - execute_command do - Registration::Payment::CreditCard::Activate.new( - customer, - :test_default_method, - "+15555550000" - ).write - end - Registration::Payment::CreditCard::Activate::CreditCardSale.verify - customer.verify - Registration::Payment::CreditCard::Activate::Finish.verify - end - em :test_write - - def test_write_declines - customer = Minitest::Mock.new( - customer(plan_name: "test_usd") - ) - iq = Blather::Stanza::Iq::Command.new - iq.from = "test@example.com" - msg = Registration::Payment::CreditCard::Activate::DECLINE_MESSAGE - Command::COMMAND_MANAGER.expect( - :write, - EMPromise.reject(:test_result), - [Matching.new do |reply| - assert_equal :error, reply.note_type - assert_equal( - "#{msg}: http://creditcard.example.com?&amount=1", - reply.note.content - ) - end] - ) - result = execute_command do - Registration::Payment::CreditCard::Activate::CreditCardSale.expect( - :create, - EMPromise.reject("declined") - ) do |acustomer, amount:, payment_method:| - assert_operator customer, :===, acustomer - assert_equal CONFIG[:activation_amount], amount - assert_equal :test_default_method, payment_method - end - - Registration::Payment::CreditCard::Activate.new( - customer, - :test_default_method, - "+15555550000" - ).write.catch { |e| e } - end - assert_equal :test_result, result - Registration::Payment::CreditCard::Activate::CreditCardSale.verify - end - em :test_write_declines - end - class InviteCodeTest < Minitest::Test Registration::Payment::InviteCode::DB = Minitest::Mock.new @@ -955,9 +906,13 @@ class RegistrationTest < Minitest::Test Command::COMMAND_MANAGER = Minitest::Mock.new Registration::Payment::InviteCode::Finish = Minitest::Mock.new - Registration::Payment::InviteCode::BillPlan = + Registration::Payment::MaybeBill::BillPlan = Minitest::Mock.new + def setup + @tel = TelSelections::ChooseTel::Tn.for_pending_value("+15555550000") + end + def test_write customer = customer(plan_name: "test_usd") Registration::Payment::InviteCode::DB.expect(:transaction, true, []) @@ -966,7 +921,7 @@ class RegistrationTest < Minitest::Test OpenStruct.new(write: nil), [ customer, - "+15555550000" + @tel ] ) execute_command do @@ -995,7 +950,7 @@ class RegistrationTest < Minitest::Test Registration::Payment::InviteCode.new( customer, - "+15555550000" + @tel ).write end assert_mock Command::COMMAND_MANAGER @@ -1007,7 +962,7 @@ class RegistrationTest < Minitest::Test def test_write_parent_code customer = customer(plan_name: "test_usd") - Registration::Payment::InviteCode::BillPlan.expect( + Registration::Payment::MaybeBill::BillPlan.expect( :new, OpenStruct.new(write: nil) ) { |*| true } @@ -1058,19 +1013,24 @@ class RegistrationTest < Minitest::Test Registration::Payment::InviteCode.new( customer, - "+15555550000" + @tel ).write end assert_mock Command::COMMAND_MANAGER assert_mock Registration::Payment::InviteCode::DB assert_mock Registration::Payment::InviteCode::REDIS - assert_mock Registration::Payment::InviteCode::BillPlan + assert_mock Registration::Payment::MaybeBill::BillPlan end em :test_write_parent_code def test_write_bad_code result = execute_command do customer = customer(plan_name: "test_usd") + Registration::Payment::InviteCode::REDIS.expect( + :set, + EMPromise.resolve(nil), + ["jmp_customer_pending_invite-test", "abc"] + ) Registration::Payment::InviteCode::REDIS.expect( :get, EMPromise.resolve(0), @@ -1131,7 +1091,7 @@ class RegistrationTest < Minitest::Test Registration::Payment::InviteCode.new( customer, - "+15555550000" + @tel ).write.catch { |e| e } end assert_equal :test_result, result @@ -1144,6 +1104,11 @@ class RegistrationTest < Minitest::Test def test_write_group_code result = execute_command do customer = customer(plan_name: "test_usd") + Registration::Payment::InviteCode::REDIS.expect( + :set, + EMPromise.resolve(nil), + ["jmp_customer_pending_invite-test", "abc"] + ) Registration::Payment::InviteCode::REDIS.expect( :get, EMPromise.resolve(0), @@ -1204,7 +1169,7 @@ class RegistrationTest < Minitest::Test Registration::Payment::InviteCode.new( customer, - "+15555550000" + @tel ).write.catch { |e| e } end assert_equal :test_result, result @@ -1249,7 +1214,7 @@ class RegistrationTest < Minitest::Test ) Registration::Payment::InviteCode.new( customer, - "+15555550000" + @tel ).write.catch { |e| e } end assert_equal :test_result, result @@ -1697,6 +1662,136 @@ class RegistrationTest < Minitest::Test end em :test_write_local_inventory + def test_write_local_inventory_must_pay + low_cust = customer( + sgx: @sgx, + jid: Blather::JID.new("test\\40onboarding.example.com@proxy") + ).with_balance(5.0) + high_cust = customer( + sgx: @sgx, + jid: Blather::JID.new("test\\40onboarding.example.com@proxy") + ).with_balance(100.0) + + stub_request( + :post, + "https://dashboard.bandwidth.com/v1.0/accounts/moveto/moveTns" + ).with( + body: { + CustomerOrderId: "test", + SourceAccountId: "bandwidth_account_id", + SiteId: "test_site", + SipPeerId: "test_peer", + TelephoneNumbers: { TelephoneNumber: "5555550000" } + }.to_xml(indent: 0, root: "MoveTnsOrder") + ).to_return(status: 200, body: "", headers: {}) + + Registration::Finish::REDIS.expect( + :get, + nil, + ["jmp_customer_pending_invite-test"] + ) + Registration::Finish::REDIS.expect( + :del, + nil, + ["jmp_customer_pending_invite-test"] + ) + Registration::Finish::REDIS.expect( + :hget, + nil, + ["jmp_group_codes", nil] + ) + Registration::Finish::DB.expect( + :exec_defer, + EMPromise.resolve(OpenStruct.new(cmd_tuples: 1)), + [String, ["+15555550000"]] + ) + Bwmsgsv2Repo::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_customer_backend_sgx-test"] + ) + Bwmsgsv2Repo::REDIS.expect( + :set, + nil, + [ + "catapult_fwd-+15555550000", + "xmpp:test\\40onboarding.example.com@proxy" + ] + ) + Bwmsgsv2Repo::REDIS.expect( + :set, + nil, + ["catapult_fwd_timeout-customer_test@component", 25] + ) + + local_tel = Minitest::Mock.new( + TelSelections::ChooseTel::Tn::LocalInventory.new( + TelSelections::ChooseTel::Tn.new("+15555550000"), + "bandwidth_account_id", + price: 10.0 + ) + ) + + result = execute_command do + local_tel.expect(:charge, EMPromise.reject(:test_result), [Customer]) + @sgx.expect( + :register!, + EMPromise.resolve(@sgx.with( + registered?: Blather::Stanza::Iq::IBR.new.tap do |ibr| + ibr.phone = "+15555550000" + end + )), + ["+15555550000"] + ) + + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq| + iq.from = "customer@example.org" + iq.form.fields = [ + { var: "activation_method", value: "credit_card" } + ] + }), + [Matching.new do |iq| + assert_equal :form, iq.form.type + assert_equal "Purchase Number", iq.form.title + end] + ) + + Command.execution.customer_repo.expect( + :find, + EMPromise.resolve(high_cust), + ["test"] + ) + + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.resolve(Blather::Stanza::Iq::Command.new.tap { |iq| + iq.from = "customer@example.org" + }), + [Matching.new do |iq| + assert_equal( + "Pay by credit card, save, then next here to continue: " \ + "http://creditcard.example.com?&amount=10", + iq.note.text + ) + end] + ) + + Registration::Finish.new( + low_cust, + local_tel + ).write.catch { |e| e } + end + + assert_equal :test_result, result + assert_mock @sgx + assert_mock Registration::Finish::REDIS + assert_mock Bwmsgsv2Repo::REDIS + assert_mock Command::COMMAND_MANAGER + end + em :test_write_local_inventory_must_pay + def test_write_tn_fail create_order = stub_request( :post,