Skip to content

Commit

Permalink
Nexmo: handle some of the newer nexmo api error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
dhedlund committed May 3, 2013
1 parent 3eb4d84 commit aeeae0d
Show file tree
Hide file tree
Showing 7 changed files with 381 additions and 29 deletions.
4 changes: 4 additions & 0 deletions app/controllers/nexmo_controller.rb
Expand Up @@ -13,6 +13,10 @@ def confirm_delivery
:mo_tag => params[:'mo-tag'],
:status => params[:status].try(:upcase),
:scts => params[:scts],
:sender_id => params[:to],
:err_code => params[:'err-code'],
:price => params[:price],
:client_ref => params[:'client-ref'],
:params => params, # to include w/ error message if failure
})
unless success
Expand Down
86 changes: 70 additions & 16 deletions app/models/delivery/provider/nexmo.rb
Expand Up @@ -6,22 +6,28 @@ class Delivery::Provider::Nexmo
class ConfigurationError < StandardError; end

# error types (used for DeliveryAttempt's :error_type attribute)
INTERNAL_ERROR = 'INTERNAL_ERROR'
REMOTE_TIMEOUT = 'REMOTE_TIMEOUT'
REMOTE_ERROR = 'REMOTE_ERROR'
THROTTLED = 'THROTTLED'
MISSING_PARAMS = 'MISSING_PARAMS'
INVALID_PARAMS = 'INVALID_PARAMS'
INVALID_CREDS = 'INVALID_CREDS'
NEXMO_ERROR = 'NEXMO_ERROR'
INVALID_MESSAGE = 'INVALID_MESSAGE'
BLACKLISTED = 'BLACKLISTED'
ACCOUNT_BARRED = 'ACCOUNT_BARRED'
NO_CREDITS = 'NO_CREDITS'
CONNECTION_LIMIT = 'CONNECTION_LIMIT'
REST_DISABLED = 'REST_DISABLED'
MESSAGE_LENGTH = 'MESSAGE_LENGTH'
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
INTERNAL_ERROR = 'INTERNAL_ERROR'
REMOTE_TIMEOUT = 'REMOTE_TIMEOUT'
REMOTE_ERROR = 'REMOTE_ERROR'
THROTTLED = 'THROTTLED'
MISSING_PARAMS = 'MISSING_PARAMS'
INVALID_PARAMS = 'INVALID_PARAMS'
INVALID_CREDS = 'INVALID_CREDS'
NEXMO_ERROR = 'NEXMO_ERROR'
INVALID_MESSAGE = 'INVALID_MESSAGE'
BLACKLISTED = 'BLACKLISTED'
ACCOUNT_BARRED = 'ACCOUNT_BARRED'
NO_CREDITS = 'NO_CREDITS'
CONNECTION_LIMIT = 'CONNECTION_LIMIT'
REST_DISABLED = 'REST_DISABLED'
MESSAGE_LENGTH = 'MESSAGE_LENGTH'
COMMUNICATION_FAIL = 'COMMUNICATION_FAIL'
INVALID_SIGNATURE = 'INVALID_SIGNATURE'
INVALID_SENDER_ADDR = 'INVALID_SENDER_ADDR'
INVALID_TTL = 'INVALID_TTL'
FACILITY_NOT_ALLOWED = 'FACILITY_NOT_ALLOWED'
INVALID_MSG_CLASS = 'INVALID_MSG_CLASS'
UNKNOWN_ERROR = 'UNKNOWN_ERROR'

def initialize(config={})
@json_endpoint = config[:json_endpoint] || 'http://rest.nexmo.com/sms/json'
Expand Down Expand Up @@ -230,6 +236,54 @@ def handle_nexmo_response(attempt, res)
:error_msg => "message is too long: #{msg_res.inspect}",
}

when '13'
message.status = NexmoOutboundMessage::FAILED
attempt_statuses << {
:result => DeliveryAttempt::TEMP_FAIL,
:error_type => COMMUNICATION_FAIL,
:error_msg => "not submitted because there was a communication failure: #{msg_res.inspect}",
}

when '14'
message.status = NexmoOutboundMessage::FAILED
attempt_statuses << {
:result => DeliveryAttempt::PERM_FAIL,
:error_type => INVALID_SIGNATURE,
:error_msg => "verification failure in the submitted signature: #{msg_res.inspect}",
}

when '15'
message.status = NexmoOutboundMessage::FAILED
attempt_statuses << {
:result => DeliveryAttempt::PERM_FAIL,
:error_type => INVALID_SENDER_ADDR,
:error_msg => "sender address was not allowed for this message: #{msg_res.inspect}",
}

when '16'
message.status = NexmoOutboundMessage::FAILED
attempt_statuses << {
:result => DeliveryAttempt::PERM_FAIL,
:error_type => INVALID_TTL,
:error_msg => "the ttl parameter values is invalid: #{msg_res.inspect}",
}

when '19'
message.status = NexmoOutboundMessage::FAILED
attempt_statuses << {
:result => DeliveryAttempt::PERM_FAIL,
:error_type => FACILITY_NOT_ALLOWED,
:error_msg => "request makes use of a facility that is not enabled on your account: #{msg_res.inspect}",
}

when '20'
message.status = NexmoOutboundMessage::FAILED
attempt_statuses << {
:result => DeliveryAttempt::PERM_FAIL,
:error_type => INVALID_MSG_CLASS,
:error_msg => "message class value supplied was out of range (0 - 3): #{msg_res.inspect}",
}

else
message.status = NexmoOutboundMessage::FAILED
attempt_statuses << {
Expand Down
128 changes: 117 additions & 11 deletions app/models/nexmo_outbound_message.rb
Expand Up @@ -5,14 +5,29 @@ class NexmoOutboundMessage < ActiveRecord::Base

after_update :update_attempt

# status
DELIVERED = 'DELIVERED'
BUFFERED = 'BUFFERED'
EXPIRED = 'EXPIRED'
FAILED = 'FAILED'
EXPIRED = 'EXPIRED'
FAILED = 'FAILED'
ACCEPTED = 'ACCEPTED'
SUBMITTED = 'ACCEPTED'
BUFFERED = 'BUFFERED'
UNKNOWN = 'UNKNOWN'

# error types (used for DeliveryAttempt's :error_type attribute)
REMOTE_TIMEOUT = 'REMOTE_TIMEOUT'
REMOTE_ERROR = 'REMOTE_ERROR'
REMOTE_TIMEOUT = 'REMOTE_TIMEOUT'
REMOTE_ERROR = 'REMOTE_ERROR'
ABSENT_SUBSCRIBER = 'ABSENT_SUBSCRIBER'
CALL_BARRED = 'CALL_BARRED'
PORTABILITY_ERROR = 'PORTABILITY_ERROR'
ANTI_SPAM = 'ANTI_SPAM'
HANDSET_BUSY = 'HANDSET_BUSY'
NETWORK_ERROR = 'NETWORK_ERROR'
ILLEGAL_NUMBER = 'ILLEGAL_NUMBER'
INVALID_MSG = 'INVALID_MSG'
UNROUTABLE = 'UNROUTABLE'
GENERAL_ERROR = 'GENERAL_ERROR'
UNKNOWN_ERROR = 'UNKNOWN_ERROR'

validates :delivery_attempt_id, :presence => true
validates :ext_message_id, :presence => true, :uniqueness => true
Expand All @@ -27,19 +42,110 @@ def update_attempt
if noms.all? { |n| n.status == DELIVERED }
delivery_attempt.update_attributes({ :result => DeliveryAttempt::DELIVERED })

elsif status == ACCEPTED || status == BUFFERED
# assuming we'll get another status update later

elsif status == EXPIRED
delivery_attempt.update_attributes({
:result => DeliveryAttempt::TEMP_FAIL,
:error_type => REMOTE_TIMEOUT,
:error_msg => "expired trying to reach endpoint: #{params.try(:to_json)}",
})

elsif status == FAILED
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => REMOTE_ERROR,
:error_msg => "failed trying to reach endpoint: #{params.try(:to_json)}",
})
elsif status == FAILED || status == UNKNOWN
# nexmo added a new error code column that's not

# error codes from 'err-code' nexmo responses
case err_code
when '0' # delivered
# not really an error, uh...whatever

when '2' # absent subscriber - temporary
delivery_attempt.update_attributes({
:result => DeliveryAttempt::TEMP_FAIL,
:error_type => ABSENT_SUBSCRIBER,
:error_msg => "nexmo returned 'absent subscriber' (2): #{params.try(:to_json)}",
})

when '3' # absent subscriber - permenant
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => ABSENT_SUBSCRIBER,
:error_msg => "nexmo returned 'absent subscriber' (3): #{params.try(:to_json)}",
})

when '4' # call barred by user
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => CALL_BARRED,
:error_msg => "nexmo returned 'call barred by user' (4): #{params.try(:to_json)}",
})

when '5' # portability error
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => PORTABILITY_ERROR,
:error_msg => "nexmo returned 'portability error' (5): #{params.try(:to_json)}",
})

when '6' # anti-spam rejection
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => ANTI_SPAM,
:error_msg => "nexmo returned 'anti-spam rejection' (6): #{params.try(:to_json)}",
})

when '7' # handset busy
delivery_attempt.update_attributes({
:result => DeliveryAttempt::TEMP_FAIL,
:error_type => HANDSET_BUSY,
:error_msg => "nexmo returned 'handset busy' (7): #{params.try(:to_json)}",
})

when '8' # network error
delivery_attempt.update_attributes({
:result => DeliveryAttempt::TEMP_FAIL,
:error_type => NETWORK_ERROR,
:error_msg => "nexmo returned 'network error' (8): #{params.try(:to_json)}",
})

when '9' # illegal number
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => ILLEGAL_NUMBER,
:error_msg => "nexmo returned 'illegal number' (9): #{params.try(:to_json)}",
})

when '10' # invalid message
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => INVALID_MSG,
:error_msg => "nexmo returned 'invalid message' (10): #{params.try(:to_json)}",
})

when '11' # unroutable
# this might be a permanent error but we can't tell yet
delivery_attempt.update_attributes({
:result => DeliveryAttempt::TEMP_FAIL,
:error_type => UNROUTABLE,
:error_msg => "nexmo returned 'unroutable' (11): #{params.try(:to_json)}",
})

when '99' # general error
# this might be a permanent error but we can't tell yet
delivery_attempt.update_attributes({
:result => DeliveryAttempt::TEMP_FAIL,
:error_type => GENERAL_ERROR,
:error_msg => "nexmo returned 'general error' (99): #{params.try(:to_json)}",
})

else # unknown
delivery_attempt.update_attributes({
:result => DeliveryAttempt::PERM_FAIL,
:error_type => UNKNOWN_ERROR,
:error_msg => "unknown error trying to reach endpoint: #{params.try(:to_json)}",
})
end
end
end

Expand Down
16 changes: 16 additions & 0 deletions db/migrate/20130503075139_add_new_nexmo_fields.rb
@@ -0,0 +1,16 @@
class AddNewNexmoFields < ActiveRecord::Migration
def up
add_column :nexmo_outbound_messages, :sender_id, :string
add_column :nexmo_outbound_messages, :err_code, :string
add_column :nexmo_outbound_messages, :price, :string
add_column :nexmo_outbound_messages, :client_ref, :string
end

def down
remove_column :nexmo_outbound_messages, :sender_id
remove_column :nexmo_outbound_messages, :err_code
remove_column :nexmo_outbound_messages, :price
remove_column :nexmo_outbound_messages, :client_ref
end

end
6 changes: 5 additions & 1 deletion db/schema.rb
Expand Up @@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.

ActiveRecord::Schema.define(:version => 20130318165636) do
ActiveRecord::Schema.define(:version => 20130503075139) do

create_table "delayed_jobs", :force => true do |t|
t.integer "priority", :default => 0
Expand Down Expand Up @@ -108,6 +108,10 @@
t.string "scts"
t.datetime "created_at"
t.datetime "updated_at"
t.string "sender_id"
t.string "err_code"
t.string "price"
t.string "client_ref"
end

add_index "nexmo_outbound_messages", ["delivery_attempt_id"], :name => "index_nexmo_outbound_messages_on_delivery_attempt_id"
Expand Down
48 changes: 48 additions & 0 deletions test/unit/delivery/provider/nexmo_test.rb
Expand Up @@ -222,6 +222,54 @@ class AliensHaveLanded < StandardError; end
assert_equal NexmoOutboundMessage::FAILED, NexmoOutboundMessage.first.status
end

test "deliver supports nexmo response code 13 (communication fail)" do
@provider.stubs(:handle_request).returns(fake_response(['13']))
@provider.deliver(@attempt)
assert_equal DeliveryAttempt::TEMP_FAIL, @attempt.result
assert_equal Delivery::Provider::Nexmo::COMMUNICATION_FAIL, @attempt.error_type
assert_equal NexmoOutboundMessage::FAILED, NexmoOutboundMessage.first.status
end

test "deliver supports nexmo response code 14 (invalid signature)" do
@provider.stubs(:handle_request).returns(fake_response(['14']))
@provider.deliver(@attempt)
assert_equal DeliveryAttempt::PERM_FAIL, @attempt.result
assert_equal Delivery::Provider::Nexmo::INVALID_SIGNATURE, @attempt.error_type
assert_equal NexmoOutboundMessage::FAILED, NexmoOutboundMessage.first.status
end

test "deliver supports nexmo response code 15 (invalid sender addr)" do
@provider.stubs(:handle_request).returns(fake_response(['15']))
@provider.deliver(@attempt)
assert_equal DeliveryAttempt::PERM_FAIL, @attempt.result
assert_equal Delivery::Provider::Nexmo::INVALID_SENDER_ADDR, @attempt.error_type
assert_equal NexmoOutboundMessage::FAILED, NexmoOutboundMessage.first.status
end

test "deliver supports nexmo response code 16 (invalid TTL)" do
@provider.stubs(:handle_request).returns(fake_response(['16']))
@provider.deliver(@attempt)
assert_equal DeliveryAttempt::PERM_FAIL, @attempt.result
assert_equal Delivery::Provider::Nexmo::INVALID_TTL, @attempt.error_type
assert_equal NexmoOutboundMessage::FAILED, NexmoOutboundMessage.first.status
end

test "deliver supports nexmo response code 19 (facility not allowed)" do
@provider.stubs(:handle_request).returns(fake_response(['19']))
@provider.deliver(@attempt)
assert_equal DeliveryAttempt::PERM_FAIL, @attempt.result
assert_equal Delivery::Provider::Nexmo::FACILITY_NOT_ALLOWED, @attempt.error_type
assert_equal NexmoOutboundMessage::FAILED, NexmoOutboundMessage.first.status
end

test "deliver supports nexmo response code 20 (invalid message class)" do
@provider.stubs(:handle_request).returns(fake_response(['20']))
@provider.deliver(@attempt)
assert_equal DeliveryAttempt::PERM_FAIL, @attempt.result
assert_equal Delivery::Provider::Nexmo::INVALID_MSG_CLASS, @attempt.error_type
assert_equal NexmoOutboundMessage::FAILED, NexmoOutboundMessage.first.status
end

test "deliver supports unknown nexmo responses" do
@provider.stubs(:handle_request).returns(fake_response(['SOS']))
@provider.deliver(@attempt)
Expand Down

0 comments on commit aeeae0d

Please sign in to comment.