M Gemfile => Gemfile +1 -1
@@ 20,7 20,7 @@ gem "multihashes"
gem "ougai"
gem "relative_time"
gem "roda"
-gem "ruby-bandwidth-iris", git: "https://github.com/singpolyma/ruby-bandwidth-iris", branch: "sip_credential"
+gem "ruby-bandwidth-iris", git: "https://github.com/singpolyma/ruby-bandwidth-iris", branch: "tn_move"
gem "sentry-ruby", "<= 4.3.1"
gem "slim"
gem "statsd-instrument", git: "https://github.com/singpolyma/statsd-instrument.git", branch: "graphite"
M config-schema.dhall => config-schema.dhall +2 -0
@@ 19,6 19,8 @@
, electrum_notify_url :
forall (address : Text) -> forall (customer_id : Text) -> Text
, interac : Text
+, keep_area_codes : List Text
+, keep_area_codes_in : { account : Text, site : Text }
, notify_admin : Text
, notify_from : Text
, ogm_path : Text
M config.dhall.sample => config.dhall.sample +2 -0
@@ 77,6 77,8 @@ in
notify_from = "+15551234567@example.net",
admins = ["test\\40example.com@example.net"],
unbilled_targets = ["+14169938000"],
+ keep_area_codes = ["555"],
+ keep_area_codes_in = { account = "", site = "" },
upstream_domain = "example.net",
approved_domains = toMap { `example.com` = Some "customer_id" }
}
M => +2 -1
@@ 10,6 10,7 @@ field(
options: [
{ value: "info", label: "Customer Info" },
{ value: "financial", label: "Customer Billing Information" },
{ value: "bill_plan", label: "Bill Customer" }
{ value: "bill_plan", label: "Bill Customer" },
{ value: "cancel_account", label: "Cancel Customer" }
]
)
M lib/admin_command.rb => lib/admin_command.rb +13 -0
@@ 75,6 75,19 @@ class AdminCommand
BillPlanCommand.for(@target_customer).call
end
+ def action_cancel_account
+ m = Blather::Stanza::Message.new
+ m.from = CONFIG[:notify_from]
+ m.body = "Your JMP account has been cancelled."
+ @target_customer.stanza_to(m).then {
+ EMPromise.all([
+ @target_customer.stanza_to(IBR.new(:set).tap(&:remove!)),
+ @target_customer.deregister!,
+ @customer_repo.disconnect_tel(@target_customer)
+ ])
+ }
+ end
+
def pay_methods(financial_info)
reply(FormTemplate.render(
"admin_payment_methods",
M lib/backend_sgx.rb => lib/backend_sgx.rb +7 -0
@@ 27,6 27,13 @@ class BackendSgx
IQ_MANAGER.write(ibr)
end
+ def deregister!
+ ibr = IBR.new(:set, @jid)
+ ibr.from = from_jid
+ ibr.remove!
+ IQ_MANAGER.write(ibr)
+ end
+
def stanza(s)
s.dup.tap do |stanza|
stanza.to = stanza.to.with(domain: jid.domain)
M lib/bandwidth_tn_repo.rb => lib/bandwidth_tn_repo.rb +22 -0
@@ 3,6 3,15 @@
require "ruby-bandwidth-iris"
class BandwidthTnRepo
+ def initialize
+ @move_client =
+ BandwidthIris::Client.new(
+ account_id: CONFIG[:keep_area_codes_in][:account],
+ username: CONFIG[:creds][:username],
+ password: CONFIG[:creds][:password]
+ )
+ end
+
def find(tel)
BandwidthIris::Tn.new(telephone_number: tel).get_details
end
@@ 18,4 27,17 @@ class BandwidthTnRepo
rescue BandwidthIris::Errors::GenericError
raise "Could not set CNAM, please contact support"
end
+
+ def disconnect(tel, order_name)
+ tn = tel.sub(/\A\+1/, "")
+ if CONFIG[:keep_area_codes].find { |area| tn.start_with?(area) }
+ BandwidthIris::Tn.new({ telephone_number: tn }, @move_client).move(
+ site_id: CONFIG[:keep_area_codes_in][:site],
+ customer_order_id: order_name,
+ source_account_id: CONFIG[:creds][:account]
+ )
+ else
+ BandwidthIris::Disconnect.create(order_name, tn)
+ end
+ end
end
M lib/customer.rb => lib/customer.rb +2 -1
@@ 26,7 26,7 @@ class Customer
:currency, :merchant_account, :plan_name, :minute_limit,
:message_limit, :auto_top_up_amount, :monthly_overage_limit,
:monthly_price, :save_plan!
- def_delegators :@sgx, :register!, :registered?, :set_ogm_url,
+ def_delegators :@sgx, :deregister!, :register!, :registered?, :set_ogm_url,
:fwd, :transcription_enabled
def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage
def_delegators :@financials, :payment_methods, :btc_addresses,
@@ 85,6 85,7 @@ class Customer
def stanza_to(stanza)
stanza = stanza.dup
stanza.to = jid.with(resource: stanza.to&.resource)
+ stanza.from ||= Blather::JID.new("")
stanza.from = stanza.from.with(domain: CONFIG[:component][:jid])
block_given? ? yield(stanza) : (BLATHER << stanza)
end
M lib/customer_repo.rb => lib/customer_repo.rb +5 -0
@@ 107,6 107,11 @@ class CustomerRepo
end
end
+ def disconnect_tel(customer)
+ tel = customer.registered?.phone
+ @bandwidth_tn_repo.disconnect(tel, customer.customer_id)
+ end
+
def put_lidb_name(customer, lidb_name)
@bandwidth_tn_repo.put_lidb_name(customer.registered?.phone, lidb_name)
end
M lib/ibr.rb => lib/ibr.rb +11 -0
@@ 16,6 16,17 @@ class IBR < Blather::Stanza::Iq::Query
!!query.at_xpath("./ns:registered", ns: self.class.registered_ns)
end
+ def remove!
+ query.children.remove
+ node = Nokogiri::XML::Node.new("remove", document)
+ node.default_namespace = self.class.registered_ns
+ query << node
+ end
+
+ def remove?
+ !!query.at_xpath("./ns:remove", ns: self.class.registered_ns)
+ end
+
[
"instructions",
"username",
A test/test_admin_command.rb => test/test_admin_command.rb +115 -0
@@ 0,0 1,115 @@
+# frozen_string_literal: true
+
+require "admin_command"
+
+BackendSgx::IQ_MANAGER = Minitest::Mock.new
+Customer::BLATHER = Minitest::Mock.new
+
+class AdminCommandTest < Minitest::Test
+ def admin_command(tel="+15556667777")
+ sgx = Minitest::Mock.new(OpenStruct.new(
+ registered?: OpenStruct.new(phone: tel)
+ ))
+ [sgx, AdminCommand.new(customer(sgx: sgx), CustomerRepo.new)]
+ end
+
+ def test_action_cancel_account
+ sgx, admin = admin_command
+
+ Customer::BLATHER.expect(
+ :<<,
+ EMPromise.resolve(nil),
+ [
+ Matching.new do |m|
+ assert_equal "Your JMP account has been cancelled.", m.body
+ assert_equal "test@example.net", m.to.to_s
+ assert_equal "notify_from@component", m.from.to_s
+ end
+ ]
+ )
+
+ Customer::BLATHER.expect(
+ :<<,
+ EMPromise.resolve(nil),
+ [
+ Matching.new do |iq|
+ assert iq.remove?
+ assert_equal "test@example.net", iq.to.to_s
+ assert_equal "component", iq.from.to_s
+ end
+ ]
+ )
+
+ sgx.expect(:deregister!, EMPromise.resolve(nil))
+
+ stub_request(
+ :post,
+ "https://dashboard.bandwidth.com/v1.0/accounts//disconnects"
+ ).with(
+ body: {
+ name: "test",
+ DisconnectTelephoneNumberOrderType: {
+ TelephoneNumberList: {
+ TelephoneNumber: "5556667777"
+ }
+ }
+ }.to_xml(indent: 0, root: "DisconnectTelephoneNumberOrder")
+ ).to_return(status: 200, body: "")
+
+ admin.action_cancel_account.sync
+
+ assert_mock sgx
+ assert_mock BackendSgx::IQ_MANAGER
+ assert_mock Customer::BLATHER
+ end
+ em :test_action_cancel_account
+
+ def test_action_cancel_account_keep_number
+ sgx, admin = admin_command("+15566667777")
+
+ Customer::BLATHER.expect(
+ :<<,
+ EMPromise.resolve(nil),
+ [
+ Matching.new do |m|
+ assert_equal "Your JMP account has been cancelled.", m.body
+ assert_equal "test@example.net", m.to.to_s
+ assert_equal "notify_from@component", m.from.to_s
+ end
+ ]
+ )
+
+ Customer::BLATHER.expect(
+ :<<,
+ EMPromise.resolve(nil),
+ [
+ Matching.new do |iq|
+ assert iq.remove?
+ assert_equal "test@example.net", iq.to.to_s
+ assert_equal "component", iq.from.to_s
+ end
+ ]
+ )
+
+ sgx.expect(:deregister!, EMPromise.resolve(nil))
+
+ stub_request(
+ :post,
+ "https://dashboard.bandwidth.com/v1.0/accounts/moveto/moveTns"
+ ).with(
+ body: {
+ SiteId: "movetosite",
+ CustomerOrderId: "test",
+ SourceAccountId: "test_bw_account",
+ TelephoneNumbers: { TelephoneNumber: "5566667777" }
+ }.to_xml(indent: 0, root: "MoveTnsOrder")
+ ).to_return(status: 200, body: "")
+
+ admin.action_cancel_account.sync
+
+ assert_mock sgx
+ assert_mock BackendSgx::IQ_MANAGER
+ assert_mock Customer::BLATHER
+ end
+ em :test_action_cancel_account_keep_number
+end
M test/test_helper.rb => test/test_helper.rb +2 -0
@@ 98,6 98,8 @@ CONFIG = {
},
credit_card_url: ->(*) { "http://creditcard.example.com" },
electrum_notify_url: ->(*) { "http://notify.example.com" },
+ keep_area_codes: ["556"],
+ keep_area_codes_in: { account: "moveto", site: "movetosite" },
upstream_domain: "example.net",
approved_domains: {
"approved.example.com": nil,