diff --git a/forms/configure_calls.rb b/forms/configure_calls.rb new file mode 100644 index 0000000000000000000000000000000000000000..c69859b117482f9de788648c0fec6118780756fd --- /dev/null +++ b/forms/configure_calls.rb @@ -0,0 +1,48 @@ +form! +title "Configure Calls" + +field( + var: "fwd[timeout]", + type: "text-single", + datatype: "xs:integer", + label: "Seconds to ring before voicemail", + description: "One ring is ~5 seconds. Negative means ring forever.", + value: @customer.fwd.timeout.to_i.to_s +) + +field( + var: "voicemail_transcription", + type: "boolean", + label: "Voicemail transcription", + value: @customer.transcription_enabled.to_s +) + +field( + var: "fwd[uri]", + type: "list-single", + datatype: "xs:anyURI", + open: true, + label: "Forward calls to", + description: "List item or any custom xmpp:, sip:, or tel: URI.", + options: [ + { label: "Jabber ID", value: "xmpp:#{@customer.jid}" }, + { label: "SIP Account", value: @customer.sip_account.uri } + ], + value: @customer.fwd.uri +) + +if @customer.tndetails.dig(:features, :lidb) + field( + var: "lidb_name", + type: "fixed", + label: "CNAM", + value: "#{@lidb[:name]} (#{@lidb[:status]})" + ) +elsif @customer.tndetails[:on_net_vendor] + field( + var: "lidb_name", + type: "text-single", + label: "CNAM Name", + description: "or nothing/space to leave blank" + ) +end diff --git a/lib/backend_sgx.rb b/lib/backend_sgx.rb index 9a0ee42940c4f9184424a94651f72955572a1e5a..8b0a15a20fb018df23aacf5ef5a4aae843d9cdc2 100644 --- a/lib/backend_sgx.rb +++ b/lib/backend_sgx.rb @@ -36,14 +36,6 @@ class BackendSgx end end - def set_fwd(uri) - REDIS.set("catapult_fwd-#{registered?.phone}", uri) - end - - def set_fwd_timeout(timeout) - REDIS.set("catapult_fwd_timeout-#{from_jid}", timeout) - end - def set_ogm_url(url) REDIS.set("catapult_ogm_url-#{from_jid}", url) end diff --git a/lib/bwmsgsv2_repo.rb b/lib/bwmsgsv2_repo.rb index 312563122ea83d88ffa8274c5f319ba22dfea488..b295ff199a08b972648ff974d9e1b79cf4d23112 100644 --- a/lib/bwmsgsv2_repo.rb +++ b/lib/bwmsgsv2_repo.rb @@ -20,15 +20,43 @@ class Bwmsgsv2Repo fetch_raw(sgx.from_jid).then do |(((ogm_url, fwd_time, fwd), trans_d), reg)| sgx.with({ ogm_url: ogm_url, - fwd: CustomerFwd.for(fwd, fwd_time), + fwd: CustomerFwd.for(uri: fwd, timeout: fwd_time), transcription_enabled: !trans_d, registered?: reg }.compact) end end + def put_transcription_enabled(customer_id, enabled) + sgx = @trivial_repo.get(customer_id) + REDIS.setbit( + "catapult_settings_flags-#{sgx.from_jid}", + Bwmsgsv2Repo::VOICEMAIL_TRANSCRIPTION_DISABLED, + enabled ? 0 : 1 + ) + end + + def put_fwd(customer_id, tel, customer_fwd) + sgx = @trivial_repo.get(customer_id) + EMPromise.all([ + set_or_delete("catapult_fwd-#{tel}", customer_fwd.uri), + set_or_delete( + "catapult_fwd_timeout-#{sgx.from_jid}", + customer_fwd.timeout.to_i + ) + ]) + end + protected + def set_or_delete(k, v) + if v.nil? + REDIS.del(k) + else + REDIS.set(k, v) + end + end + def fetch_raw(from_jid) registration(from_jid).then do |r| EMPromise.all([from_redis(from_jid, r ? r.phone : nil), r]) diff --git a/lib/configure_calls_form.rb b/lib/configure_calls_form.rb new file mode 100644 index 0000000000000000000000000000000000000000..1d0690597846e68e0a507542a717755558e8b208 --- /dev/null +++ b/lib/configure_calls_form.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "form_to_h" + +class ConfigureCallsForm + using FormToH + + def initialize(customer) + @customer = customer + end + + def render + FormTemplate.render("configure_calls", customer: @customer) + end + + def parse(form) + params = form.to_h + {}.tap do |result| + result[:fwd] = parse_fwd(params["fwd"]) if params.key?("fwd") + if params.key?("voicemail_transcription") + result[:transcription_enabled] = + ["1", "true"].include?(params["voicemail_transcription"]) + end + result[:lidb_name] = params["lidb_name"] if lidb_guard(params["lidb_name"]) + end + end + +protected + + def lidb_guard(lidb_name) + !lidb_name.to_s.strip.empty? && + !@customer.tndetails.dig(:features, :lidb) + end + + def parse_fwd(fwd_from_form) + fwd_from_form.reduce(@customer.fwd) do |fwd, (var, val)| + fwd.with(var.to_sym => val) + end + end +end diff --git a/lib/customer.rb b/lib/customer.rb index 71aebc0b97ed12416b218d7bf15e3e110e0bf7c9..68e74fa01418fb3b76763730194e08d052ba456f 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -23,7 +23,7 @@ class Customer def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan, :currency, :merchant_account, :plan_name, :auto_top_up_amount def_delegators :@sgx, :register!, :registered?, :set_ogm_url, - :set_fwd, :fwd, :transcription_enabled + :fwd, :transcription_enabled def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage def initialize( @@ -83,6 +83,11 @@ class Customer stanza_to(iq, &IQ_MANAGER.method(:write)).then(&:vcard) end + def tndetails + @tndetails ||= + BandwidthIris::Tn.new(telephone_number: registered?.phone).get_details + end + def ogm(from_tel=nil) CustomerOGM.for(@sgx.ogm_url, -> { fetch_vcard_temp(from_tel) }) end diff --git a/lib/customer_fwd.rb b/lib/customer_fwd.rb index b43f79383749a09411068cf3ae20849fcde6e668..a1a2e8dc3f7c92b3a60a851f0fcb43c9f2117e15 100644 --- a/lib/customer_fwd.rb +++ b/lib/customer_fwd.rb @@ -1,17 +1,25 @@ # frozen_string_literal: true +require "value_semantics/monkey_patched" require "uri" class CustomerFwd - def self.for(uri, timeout) + def self.for(uri:, timeout:) timeout = Timeout.new(timeout) - return if !uri || timeout.zero? + return None.new(uri: uri, timeout: timeout) if !uri || timeout.zero? + if uri =~ /\Asip:(.*)@sip.cheogram.com\Z/ + uri = "xmpp:#{$1.gsub(/%([0-9A-F]{2})/i) { $1.to_i(16).chr }}" + end URIS.fetch(uri.split(":", 2).first.to_sym) { raise "Unknown forward URI: #{uri}" - }.new(uri, timeout) + }.new(uri: uri, timeout: timeout) end class Timeout + def self.new(s) + s.is_a?(self) ? s : super + end + def initialize(s) @timeout = s.nil? || s.to_i.negative? ? 300 : s.to_i end @@ -25,54 +33,51 @@ class CustomerFwd end end - def create_call_request + value_semantics do + uri Either(String, NilClass) + # rubocop:disable Style/RedundantSelf + self.timeout Timeout, coerce: Timeout.method(:new) + # rubocop:enable Style/RedundantSelf + end + + def with(new_attrs) + CustomerFwd.for(to_h.merge(new_attrs)) + end + + def create_call(account) request = Bandwidth::ApiCreateCallRequest.new.tap do |cc| cc.to = to cc.call_timeout = timeout.to_i + yield cc if block_given? end - yield request if block_given? - request + BANDWIDTH_VOICE.create_call(account, body: request).data.call_id end class Tel < CustomerFwd - attr_reader :timeout - - def initialize(uri, timeout) - @tel = uri.sub(/^tel:/, "") - @timeout = timeout - end - def to - @tel + uri.sub(/^tel:/, "") end end class SIP < CustomerFwd - attr_reader :timeout - - def initialize(uri, timeout) - @uri = uri - @timeout = timeout - end - def to - @uri + uri end end class XMPP < CustomerFwd - attr_reader :timeout - - def initialize(uri, timeout) - @jid = uri.sub(/^xmpp:/, "") - @timeout = timeout - end - def to - "sip:#{ERB::Util.url_encode(@jid)}@sip.cheogram.com" + jid = uri.sub(/^xmpp:/, "") + "sip:#{ERB::Util.url_encode(jid)}@sip.cheogram.com" end end + class None < CustomerFwd + def create_call; end + + def to; end + end + URIS = { tel: Tel, sip: SIP, diff --git a/lib/customer_repo.rb b/lib/customer_repo.rb index c45c8fc50f056b67d978ee224495535244561c14..109ac28cf82da9c3b5b5cfe62754b378ca9bd593 100644 --- a/lib/customer_repo.rb +++ b/lib/customer_repo.rb @@ -59,12 +59,36 @@ class CustomerRepo end end + def put_lidb_name(customer, lidb_name) + BandwidthIris::Lidb.create( + customer_order_id: customer.customer_id, + lidb_tn_groups: { lidb_tn_group: { + telephone_numbers: [customer.registered?.phone.sub(/\A\+1/, "")], + subscriber_information: lidb_name, + use_type: "RESIDENTIAL", + visibility: "PUBLIC" + } } + ) + end + + def put_transcription_enabled(customer, transcription_enabled) + @sgx_repo.put_transcription_enabled( + customer.customer_id, transcription_enabled + ) + end + + def put_fwd(customer, customer_fwd) + @sgx_repo.put_fwd( + customer.customer_id, + customer.registered?.phone, + customer_fwd + ) + end + protected def new_sgx(customer_id) - TrivialBackendSgxRepo.new.get(customer_id).with( - registered?: false - ) + TrivialBackendSgxRepo.new.get(customer_id).with(registered?: false) end def find_legacy_customer(jid) diff --git a/lib/form_template.rb b/lib/form_template.rb index ffe7ccdd968bdb4143740bd00b48d62bfa8114a0..422ec719f022203c993c81c0fc958eb7fd0fb97e 100644 --- a/lib/form_template.rb +++ b/lib/form_template.rb @@ -48,8 +48,30 @@ class FormTemplate @__form.instructions = s end - def field(**kwargs) - @__form.fields = @__form.fields + [kwargs] + def validate(f, datatype: nil, **kwargs) + Nokogiri::XML::Builder.with(f) do |x| + x.validate( + xmlns: "http://jabber.org/protocol/xdata-validate", + datatype: datatype || "xs:string" + ) do + x.basic unless validation_type(x, **kwargs) + end + end + end + + def validation_type(x, open: false, regex: nil, range: nil) + x.open if open + x.range(min: range.first, max: range.last) if range + x.regex(regex.source) if regex + open || regex || range + end + + def field(datatype: nil, open: false, regex: nil, range: nil, **kwargs) + f = Blather::Stanza::X::Field.new(kwargs) + if datatype || open || regex || range + validate(f, datatype: datatype, open: open, regex: regex, range: range) + end + @__form.fields += [f] end def xml diff --git a/lib/registration.rb b/lib/registration.rb index 7b12c51a4a35c5473ff0e594bd76e3c2704b6724..9de075707d379cfb09efa1f9f0a81b67290e94e6 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -445,10 +445,6 @@ class Registration }.then { |tel| Finish.new(@customer, tel).write } end - def cheogram_sip_addr - "sip:#{ERB::Util.url_encode(@customer.jid)}@sip.cheogram.com" - end - def raise_setup_error(e) Command.log.error "@customer.register! failed", e Command.finish( @@ -459,11 +455,12 @@ class Registration end def customer_active_tel_purchased - @customer.register!(@tel).catch(&method(:raise_setup_error)).then { |sgx| + @customer.register!(@tel).catch(&method(:raise_setup_error)).then { EMPromise.all([ REDIS.del("pending_tel_for-#{@customer.jid}"), - sgx.set_fwd(cheogram_sip_addr), - sgx.set_fwd_timeout(25) # ~5 seconds / ring, 5 rings + Bwmsgsv2Repo.new.put_fwd(@customer.customer_id, @tel, CustomerFwd.for( + uri: "xmpp:#{@customer.jid}", timeout: 25 # ~5 seconds / ring, 5 rings + )) ]) }.then do Command.finish("Your JMP account has been activated as #{@tel}") diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 15cfde0eeea3ed5d3725c6de6f4cbcc52c22f571..c1fd2695097c13e5d4013f0657240bf5f74e9ad3 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -73,6 +73,7 @@ require_relative "lib/bwmsgsv2_repo" require_relative "lib/bandwidth_tn_order" require_relative "lib/btc_sell_prices" require_relative "lib/buy_account_credit_form" +require_relative "lib/configure_calls_form" require_relative "lib/command" require_relative "lib/command_list" require_relative "lib/customer" @@ -453,16 +454,25 @@ Command.new( end }.register(self).then(&CommandList.method(:register)) -# Commands that just pass through to the SGX -{ - "configure-calls" => ["Configure Calls"] -}.each do |node, args| - Command.new(node, *args) { - Command.customer.then do |customer| - customer.stanza_from(Command.execution.iq) +Command.new( + "configure calls", + "Configure Calls", + customer_repo: CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new) +) { + Command.customer.then do |customer| + cc_form = ConfigureCallsForm.new(customer) + Command.reply { |reply| + reply.allowed_actions = [:next] + reply.command << cc_form.render + }.then { |iq| + EMPromise.all(cc_form.parse(iq.form).map { |k, v| + Command.execution.customer_repo.public_send("put_#{k}", customer, v) + }) + }.then do + Command.finish("Configuration saved!") end - }.register(self, guards: [node: node]).then(&CommandList.method(:register)) -end + end +}.register(self).then(&CommandList.method(:register)) Command.new( "ogm", @@ -617,12 +627,12 @@ Command.new( if ["1", "true"].include?(fwd.form.field("change_fwd")&.value.to_s) # Migrate location if needed BandwidthIris::SipPeer.new( - site_id: CONFIG[:bandwidth_site], - id: CONFIG[:bandwidth_peer] + site_id: CONFIG[:bandwidth_site], id: CONFIG[:bandwidth_peer] ).move_tns([customer.registered?.phone]) - customer.set_fwd(sip_account.uri).then do - Command.finish("Inbound calls will now forward to SIP.") - end + Command.execution.customer_repo.put_fwd( + customer, + customer.fwd.with(uri: sip_account.uri) + ).then { Command.finish("Inbound calls will now forward to SIP.") } else Command.finish end diff --git a/test/test_customer_fwd.rb b/test/test_customer_fwd.rb new file mode 100644 index 0000000000000000000000000000000000000000..de530302a86caf52e29656dec5852cda31f9e6d1 --- /dev/null +++ b/test/test_customer_fwd.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "test_helper" +require "customer_fwd" + +class Rantly + def jid + v = Blather::JID.new(Blather::JID.new(string, string).stripped.to_s) + guard !v.to_s.to_s.empty? + v + end +end + +class CustomerFwdTest < Minitest::Test + property(:for_xmpp) { jid } + def for_xmpp(jid) + sip = "sip:#{ERB::Util.url_encode(jid.to_s)}@sip.cheogram.com" + fwd = CustomerFwd.for(uri: "xmpp:#{jid}", timeout: 10) + assert_kind_of CustomerFwd::XMPP, fwd + assert_equal sip, fwd.to + end + + property(:for_xmpp_sip) { jid } + def for_xmpp_sip(jid) + sip = "sip:#{ERB::Util.url_encode(jid.to_s)}@sip.cheogram.com" + fwd = CustomerFwd.for(uri: sip, timeout: 10) + assert_kind_of CustomerFwd::XMPP, fwd + assert_equal sip, fwd.to + end + + property(:for_tel) { "+#{string(:digit)}" } + def for_tel(tel) + fwd = CustomerFwd.for(uri: "tel:#{tel}", timeout: 10) + assert_kind_of CustomerFwd::Tel, fwd + assert_equal tel, fwd.to + end + + property(:for_sip) { "#{string(:alnum)}@#{string(:alnum)}.example.com" } + def for_sip(sip) + fwd = CustomerFwd.for(uri: "sip:#{sip}", timeout: 10) + assert_kind_of CustomerFwd::SIP, fwd + assert_equal "sip:#{sip}", fwd.to + end + + property(:for_bogus) { string } + def for_bogus(bogus) + assert_raises(RuntimeError) do + CustomerFwd.for(uri: "bogus:#{bogus}", timeout: 10) + end + end +end diff --git a/test/test_customer_repo.rb b/test/test_customer_repo.rb index 255352dc78f557c2964a63c0ff8223cc780c5d91..d996491e4d0aeb26d5c85812e3f65b19d3e3006c 100644 --- a/test/test_customer_repo.rb +++ b/test/test_customer_repo.rb @@ -3,6 +3,10 @@ require "test_helper" require "customer_repo" +class CustomerRepo + attr_reader :sgx_repo +end + class CustomerRepoTest < Minitest::Test FAKE_REDIS = FakeRedis.new( # sgx-jmp customer @@ -51,7 +55,13 @@ class CustomerRepoTest < Minitest::Test db: FAKE_DB, braintree: Minitest::Mock.new ) - CustomerRepo.new(redis: redis, db: db, braintree: braintree) + sgx_repo = Minitest::Mock.new(TrivialBackendSgxRepo.new) + CustomerRepo.new( + redis: redis, + db: db, + braintree: braintree, + sgx_repo: sgx_repo + ) end def setup @@ -153,4 +163,73 @@ class CustomerRepoTest < Minitest::Test assert_mock redis end em :test_create + + def test_put_lidb_name + post = stub_request( + :post, + "https://dashboard.bandwidth.com/v1.0/accounts//lidbs" + ).with(body: { + CustomerOrderId: "test", + LidbTnGroups: { + LidbTnGroup: { + TelephoneNumbers: "5556667777", + SubscriberInformation: "Hank", + UseType: "RESIDENTIAL", + Visibility: "PUBLIC" + } + } + }.to_xml(root: "LidbOrder", indent: 0)).to_return( + status: 201, + headers: { location: "/boop/123" } + ) + + stub_request( + :get, + "https://dashboard.bandwidth.com/v1.0/accounts//lidbs/123" + ) + + @repo.put_lidb_name( + Customer.new( + "test", + "test@exmple.com", + sgx: OpenStruct.new(registered?: OpenStruct.new(phone: "+15556667777")) + ), + "Hank" + ) + + assert_requested post + end + em :test_put_lidb_name + + def test_put_transcription_enabled + @repo.sgx_repo.expect( + :put_transcription_enabled, + EMPromise.resolve(nil), + ["test", true] + ) + @repo.put_transcription_enabled( + Customer.new("test", "test@exmple.com"), + true + ) + assert_mock @repo.sgx_repo + end + em :test_put_transcription_enabled + + def test_put_fwd + @repo.sgx_repo.expect( + :put_fwd, + EMPromise.resolve(nil), + ["test", "+15556667777", :fwd] + ) + @repo.put_fwd( + Customer.new( + "test", + "test@exmple.com", + sgx: OpenStruct.new(registered?: OpenStruct.new(phone: "+15556667777")) + ), + :fwd + ) + assert_mock @repo.sgx_repo + end + em :test_put_fwd end diff --git a/test/test_form_template.rb b/test/test_form_template.rb index 78c4e0e0071fd2a8addd6eabf11578b010b349e1..8015d917a2ec1a2ad5183cf21855677bd2aa95f3 100644 --- a/test/test_form_template.rb +++ b/test/test_form_template.rb @@ -46,6 +46,74 @@ class FormTemplateTest < Minitest::Test assert_equal "INSTRUCTIONS", form.instructions end + def test_form_validate_basic + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", datatype: "xs:integer") + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal "xs:integer", validate[:datatype] + assert_equal "basic", validate.children.first.name + end + + def test_form_validate_open + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", open: true) + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal ["open"], validate.children.map(&:name) + end + + def test_form_validate_regex + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", regex: /[A-Z]/) + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal ["regex"], validate.children.map(&:name) + assert_equal "[A-Z]", validate.children.first.content + end + + def test_form_validate_range + template = FormTemplate.new(<<~TEMPLATE) + form! + field(var: "thevar", label: "thelabel", range: (10..22)) + TEMPLATE + form = template.render + assert_equal 1, form.fields.length + assert_equal "thevar", form.fields[0].var + assert_equal "thelabel", form.fields[0].label + validate = form.fields[0].find( + "ns:validate", + ns: "http://jabber.org/protocol/xdata-validate" + ).first + assert_equal ["range"], validate.children.map(&:name) + assert_equal "10", validate.children.first[:min] + assert_equal "22", validate.children.first[:max] + end + def test_no_type template = FormTemplate.new(<<~TEMPLATE) title "TITLE" diff --git a/test/test_registration.rb b/test/test_registration.rb index 52b5e6475fe5592a543b4aa4b20da63892dfe731..24b2e7c926e89d230bc0d4802feed387a60934d7 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -517,7 +517,7 @@ class RegistrationTest < Minitest::Test Command::COMMAND_MANAGER = Minitest::Mock.new Registration::Finish::TEL_SELECTIONS = FakeTelSelections.new Registration::Finish::REDIS = Minitest::Mock.new - BackendSgx::REDIS = Minitest::Mock.new + Bwmsgsv2Repo::REDIS = Minitest::Mock.new def setup @sgx = Minitest::Mock.new(TrivialBackendSgxRepo.new.get("test")) @@ -568,15 +568,15 @@ class RegistrationTest < Minitest::Test nil, ["pending_tel_for-test@example.net"] ) - BackendSgx::REDIS.expect( + Bwmsgsv2Repo::REDIS.expect( :set, nil, [ "catapult_fwd-+15555550000", - "sip:test%40example.net@sip.cheogram.com" + "xmpp:test@example.net" ] ) - BackendSgx::REDIS.expect( + Bwmsgsv2Repo::REDIS.expect( :set, nil, ["catapult_fwd_timeout-customer_test@component", 25] @@ -608,7 +608,7 @@ class RegistrationTest < Minitest::Test assert_requested create_order assert_mock @sgx assert_mock Registration::Finish::REDIS - assert_mock BackendSgx::REDIS + assert_mock Bwmsgsv2Repo::REDIS assert_mock blather end em :test_write diff --git a/web.rb b/web.rb index 75145a1590e79f3d45803ef2fb946f7b75693d69..530c6ec9eaf994387b4ef267b4d20037582d1673 100644 --- a/web.rb +++ b/web.rb @@ -257,17 +257,16 @@ class Web < Roda CustomerRepo.new( sgx_repo: Bwmsgsv2Repo.new ).find_by_tel(params["to"]).then(&:fwd).then do |fwd| - if fwd + call = fwd.create_call(CONFIG[:creds][:account]) do |cc| true_inbound_call[pseudo_call_id] = params["callId"] - outbound_transfers[pseudo_call_id] = BANDWIDTH_VOICE.create_call( - CONFIG[:creds][:account], - body: fwd.create_call_request do |cc| - cc.from = params["from"] - cc.application_id = params["applicationId"] - cc.answer_url = url inbound_calls_path(nil) - cc.disconnect_url = url inbound_calls_path(:transfer_complete) - end - ).data.call_id + cc.from = params["from"] + cc.application_id = params["applicationId"] + cc.answer_url = url inbound_calls_path(nil) + cc.disconnect_url = url inbound_calls_path(:transfer_complete) + end + + if call + outbound_transfers[pseudo_call_id] = call render :pause, locals: { duration: 300 } else render :redirect, locals: { to: inbound_calls_path(:voicemail) }