paycall-sms 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YWEzOWVhNDhhNDRlMmFiYWRlMzU4MDg3NzY5NjUyYTJlZDNkZDRmMA==
5
+ data.tar.gz: !binary |-
6
+ NzMwYmQyN2QxNGExMTI5ZmJhZTdhZDVmMjcyYjEwYzc0ZTdjYWVkMA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MTA3NTgxMzI1YzM0MDhhZGZlYzE3NTI2YjM5OGEwOGY3MTViNTUwZDI0MmRh
10
+ NWVlZDQ0YzU1ZGIzYzRiYjY4NTA1MDA1MWFhNTM4NmMwNjc5ZGJmYmUzYTA2
11
+ ZmRiM2IyNjQ1ZTdmNmU4OWMwMGU2MTI2Y2YxY2JmNjBlZTgzYmQ=
12
+ data.tar.gz: !binary |-
13
+ Y2JlM2MxM2ZlODE3M2E5YTI1NDE5NTQ0MTM4Y2IyYzY5MmUwOGM3YmY1MjRl
14
+ ZDlkY2RlNTc3YTM5MjIwMTVmMDRhYzhlYWY2MmVjMTNhZDJjMDYzNDczMjc4
15
+ ODEyNDRlNzU1MGZkNGE1ODI1NTZjM2UxMjEzNzdmZTZkYjhlMzU=
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'httparty', '~> 0.10.2'
4
+ gem 'builder', '~> 2.1'
5
+ gem 'nokogiri', '~> 1.5.10'
6
+ gem 'uuidtools', '~> 2.1'
7
+ gem 'logging', '~> 1.7'
8
+ gem 'activesupport', '~> 3.0.9'
9
+ gem 'tzinfo', '~> 0.3' # for timezones support
10
+ gem 'i18n', '~> 0.5'
11
+
12
+ # Add dependencies to develop your gem here.
13
+ # Include everything needed to run rake, tests, features, etc.
14
+ group :development do
15
+ gem "rspec", "2.7.0"
16
+ gem "webmock"
17
+ gem "rdoc", "~> 3.12"
18
+ gem "bundler"
19
+ gem "jeweler", "~> 1.8.8"
20
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,84 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.0.9)
5
+ addressable (2.3.2)
6
+ builder (2.1.2)
7
+ crack (0.3.1)
8
+ diff-lcs (1.1.3)
9
+ faraday (0.8.9)
10
+ multipart-post (~> 1.2.0)
11
+ git (1.2.9.1)
12
+ github_api (0.10.1)
13
+ addressable
14
+ faraday (~> 0.8.1)
15
+ hashie (>= 1.2)
16
+ multi_json (~> 1.4)
17
+ nokogiri (~> 1.5.2)
18
+ oauth2
19
+ hashie (3.4.2)
20
+ highline (1.6.20)
21
+ httparty (0.10.2)
22
+ multi_json (~> 1.0)
23
+ multi_xml (>= 0.5.2)
24
+ i18n (0.6.1)
25
+ jeweler (1.8.8)
26
+ builder
27
+ bundler (~> 1.0)
28
+ git (>= 1.2.5)
29
+ github_api (= 0.10.1)
30
+ highline (>= 1.6.15)
31
+ nokogiri (= 1.5.10)
32
+ rake
33
+ rdoc
34
+ json (1.7.5)
35
+ jwt (1.5.0)
36
+ little-plugger (1.1.3)
37
+ logging (1.8.1)
38
+ little-plugger (>= 1.1.3)
39
+ multi_json (>= 1.3.6)
40
+ multi_json (1.12.1)
41
+ multi_xml (0.6.0)
42
+ multipart-post (1.2.0)
43
+ nokogiri (1.5.10)
44
+ oauth2 (1.0.0)
45
+ faraday (>= 0.8, < 0.10)
46
+ jwt (~> 1.0)
47
+ multi_json (~> 1.3)
48
+ multi_xml (~> 0.5)
49
+ rack (~> 1.2)
50
+ rack (1.4.1)
51
+ rake (10.4.2)
52
+ rdoc (3.12)
53
+ json (~> 1.4)
54
+ rspec (2.7.0)
55
+ rspec-core (~> 2.7.0)
56
+ rspec-expectations (~> 2.7.0)
57
+ rspec-mocks (~> 2.7.0)
58
+ rspec-core (2.7.1)
59
+ rspec-expectations (2.7.0)
60
+ diff-lcs (~> 1.1.2)
61
+ rspec-mocks (2.7.0)
62
+ tzinfo (0.3.39)
63
+ uuidtools (2.1.3)
64
+ webmock (1.8.10)
65
+ addressable (>= 2.2.7)
66
+ crack (>= 0.1.7)
67
+
68
+ PLATFORMS
69
+ ruby
70
+
71
+ DEPENDENCIES
72
+ activesupport (~> 3.0.9)
73
+ builder (~> 2.1)
74
+ bundler
75
+ httparty (~> 0.10.2)
76
+ i18n (~> 0.5)
77
+ jeweler (~> 1.8.8)
78
+ logging (~> 1.7)
79
+ nokogiri (~> 1.5.10)
80
+ rdoc (~> 3.12)
81
+ rspec (= 2.7.0)
82
+ tzinfo (~> 0.3)
83
+ uuidtools (~> 2.1)
84
+ webmock
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Alex
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # paycall-sms
2
+ Ruby api for sms service provider: PayCall
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "paycall-sms"
18
+ gem.homepage = "http://github.com/iplan/paycall-sms"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Ruby api for sms service provider: PayCall}
21
+ gem.description = %Q{Ruby api for sms service provider: PayCall}
22
+ gem.email = "tkachev.alex@gmail.com"
23
+ gem.authors = ["Alex Tkachev"]
24
+
25
+ gem.files.exclude 'api_docs/**/*'
26
+ # dependencies defined in Gemfile
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ spec.pattern = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
37
+ spec.pattern = 'spec/**/*_spec.rb'
38
+ spec.rcov = true
39
+ end
40
+
41
+ task :default => :spec
42
+
43
+ require 'rdoc/task'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "paycall-sms #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,66 @@
1
+ module PayCallSms
2
+ class DeliveryNotificationParser
3
+ attr_reader :logger
4
+
5
+ # Create new sms delivery notification parser
6
+ # options can contain the following keys:
7
+ # time_zone: the timezone through which the notification date would be parsed
8
+ def initialize(options = {})
9
+ @options = options
10
+ @logger = Logging.logger[self.class]
11
+ end
12
+
13
+ # params will look something like the following:
14
+ # {"PhoneNumber"=>"972545290862", "CustomerMessageId"=>"34", "Status"=>"inprogress", "dateTime"=>"20-11-2017 17:22:55"}
15
+ def from_http_push_params(params)
16
+ %w(PhoneNumber Status CustomerMessageId dateTime).each do |p|
17
+ raise ArgumentError.new("Missing http delivery notification push parameter #{p}. Parameters were: #{params.inspect}") if params[p].blank?
18
+ end
19
+ logger.debug "Parsing http push delivery notification params: #{params.inspect}"
20
+
21
+ values = {
22
+ :gateway_status => params['Status'],
23
+ :phone => params['PhoneNumber'],
24
+ :message_id => params['CustomerMessageId'],
25
+ :occurred_at => params['dateTime'],
26
+ }
27
+
28
+ parse_notification_values_hash(values)
29
+ end
30
+
31
+ # This method receives notification +values+ Hash and tries to type cast it's values and determine delivery status (add delivered?)
32
+ # @raises Smsim::GatewayError when values hash is missing attributes or when one of the attributes fails to be parsed
33
+ #
34
+ # Method returns object with the following attributes:
35
+ # * +gateway_status+ - gateway status: [inprogress,delivered]
36
+ # * +delivery_status+ - :delivered, :in_progress, :failed, :unknown
37
+ # * +occurred_at+ - when the sms became in gateway_status (as reported by gateway)
38
+ # * +phone+ - the phone to which sms was sent
39
+ # * +message_id+ - gateway message id of the sms that was sent
40
+ def parse_notification_values_hash(values)
41
+ logger.debug "Parsing delivery notification values hash: #{values.inspect}"
42
+ [:gateway_status, :phone, :message_id, :occurred_at].each do |key|
43
+ raise ArgumentError.new("Missing notification values key #{key}. Values were: #{values.inspect}") if values[key].blank?
44
+ end
45
+
46
+ values[:phone] = PhoneNumberUtils.ensure_country_code(values[:phone])
47
+ values[:delivery_status] = self.class.gateway_delivery_status_to_delivery_status(values[:gateway_status])
48
+
49
+ begin
50
+ Time.use_zone(@options[:time_zone] || Time.zone || 'Jerusalem') do
51
+ values[:occurred_at] = DateTime.strptime(values[:occurred_at], '%d-%m-%Y %H:%M:%S')
52
+ values[:occurred_at] = Time.zone.parse(values[:occurred_at].strftime('%d-%m-%Y %H:%M:%S')) #convert to ActiveSupport::TimeWithZone
53
+ end
54
+ rescue Exception => e
55
+ logger.error "occurred_at could not be converted to integer. occurred_at was: #{values[:occurred_at]}. \n\t #{e.message}: \n\t #{e.backtrace.join("\n\t")}"
56
+ raise ArgumentError.new("occurred_at could not be converted to date. occurred_at was: #{values[:occurred_at]}")
57
+ end
58
+ OpenStruct.new(values)
59
+ end
60
+
61
+ def self.gateway_delivery_status_to_delivery_status(gateway_status)
62
+ {inprogress: :in_progress, delivered: :delivered, failed: :failed}.with_indifferent_access[gateway_status] || :unknown
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,9 @@
1
+ require 'uuidtools'
2
+
3
+ module PayCallSms
4
+
5
+ class GatewayError < StandardError
6
+ end
7
+
8
+ end
9
+
@@ -0,0 +1,65 @@
1
+ module PayCallSms
2
+ class IncomingMessageParser
3
+ attr_reader :logger
4
+
5
+ # Create new sms sender with given +gateway+
6
+ def initialize(options={})
7
+ @options = options
8
+ @logger = Logging.logger[self.class]
9
+ end
10
+
11
+ # params will look something like the following:
12
+ # msgId - uniq id of the message
13
+ # sender - the phone that have sent the message
14
+ # recipient - the virtual phone number that received the message (at gateway operator)
15
+ # segments - number of segments
16
+ # content - text of the message
17
+ def from_http_push_params(params)
18
+ %w(msgId sender recipient content).each do |p|
19
+ raise ArgumentError.new("Missing http parameter #{p}. Parameters were: #{params.inspect}") if params[p].blank?
20
+ end
21
+
22
+ logger.debug "Parsing http push reply xml: \n#{params['IncomingXML']}"
23
+ parse_reply_values_hash(
24
+ phone: params['sender'],
25
+ reply_to_phone: params['recipient'],
26
+ text: params['content'],
27
+ message_id: params['msgId']
28
+ )
29
+ end
30
+
31
+ # This method receives sms reply +values+ Hash and tries to type cast it's values
32
+ # @raises Smsim::GatewayError when values hash is missing attributes or when one of attributes fails to be type casted
33
+ #
34
+ # Method returns object with the following attributes:
35
+ # * +phone+ - the phone that sent the sms (from which sms reply was received)
36
+ # * +text+ - contents of the message that were received
37
+ # * +reply_to_phone+ - the phone to sms which reply was sent (gateway phone number)
38
+ # * +received_at+ - when the sms was received (as reported by gateway server)
39
+ # * +message_id+ - uniq message id generated from phone,reply_to_phone and received_at timestamp
40
+ def parse_reply_values_hash(values)
41
+ logger.debug "Parsing reply_values_hash: #{values.inspect}"
42
+ [:message_id, :phone, :text, :reply_to_phone].each do |key|
43
+ raise ArgumentError.new("Missing sms reply values key #{key}. Values were: #{values.inspect}") if values[key].blank?
44
+ end
45
+
46
+ values[:phone] = PhoneNumberUtils.ensure_country_code(values[:phone])
47
+ values[:reply_to_phone] = PhoneNumberUtils.ensure_country_code(values[:reply_to_phone])
48
+
49
+ if values[:received_at].is_a?(String)
50
+ begin
51
+ Time.use_zone(@options[:time_zone] || Time.zone || 'Jerusalem') do
52
+ values[:received_at] = DateTime.strptime(values[:received_at], '%Y-%m-%d %H:%M:%S')
53
+ values[:received_at] = Time.zone.parse(values[:received_at].strftime('%Y-%m-%d %H:%M:%S')) #convert to ActiveSupport::TimeWithZone
54
+ end
55
+ rescue Exception => e
56
+ raise ArgumentError.new("received_at could not be converted to date. received_at was: #{values[:received_at]}")
57
+ end
58
+ else
59
+ values[:received_at] = Time.now
60
+ end
61
+ OpenStruct.new(values)
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,58 @@
1
+ module PayCallSms
2
+
3
+ class PhoneNumberUtils
4
+ @@valid_lengths = {
5
+ :cellular => '545123456'.length,
6
+ :land_line => ['31235678'.length, '777078406'.length]
7
+ }
8
+ @@country_code = '972'
9
+
10
+ # this method adds 972 country code to given phone if needed
11
+ # if phone is blank --> doesn't change it
12
+ def self.ensure_country_code(phone)
13
+ if !phone.blank? && !phone.start_with?(@@country_code)
14
+ phone = phone[1..phone.size] if phone.start_with?('0')
15
+ phone = "972#{phone}"
16
+ end
17
+ phone
18
+ end
19
+
20
+ def self.without_country_code(phone)
21
+ phone.start_with?('972') ? phone.gsub('972', '0') : phone
22
+ end
23
+
24
+ # validates that given phone is Israeli cellular format with country code: 972545123456
25
+ def self.valid_cellular_phone?(phone)
26
+ valid_phone_length?(phone, @@valid_lengths[:cellular])
27
+ end
28
+
29
+ # validates that given phone is Israeli landline format with country code: 972545123456
30
+ def self.valid_land_line_phone?(phone)
31
+ valid_phone_length?(phone, @@valid_lengths[:land_line].first) || valid_phone_length?(phone, @@valid_lengths[:land_line].last)
32
+ end
33
+
34
+ # valid sender number is between 4 and 14 digits
35
+ def self.valid_sender_number?(sender_number)
36
+ sender_number =~ /^[0-9]{4,14}$/i
37
+ end
38
+
39
+ # valid sender name is between 2 and 11 latin chars or digits
40
+ def self.valid_sender_name?(sender_name)
41
+ sender_name =~ /^[a-z0-9]{2,11}$/i
42
+ end
43
+
44
+ # make sure phone is in given length and starts with country code
45
+ def self.valid_phone_length?(phone, length)
46
+ phone = phone.to_s
47
+ phone.start_with?(@@country_code) && phone =~ /^#{@@country_code}[0-9]{#{length}}$/
48
+ end
49
+
50
+ # this method will convert given phone number to base 36 string if phone contains digits only
51
+ # if phone contains digits and letters it will leave it untouched
52
+ def self.phone_number_to_id_string(phone)
53
+ phone = phone.to_i.to_s(36) if phone =~ /^[0-9]+$/
54
+ phone
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,85 @@
1
+ require 'httparty'
2
+ require 'logging'
3
+
4
+ module PayCallSms
5
+
6
+ # this class sends smses and parses responses
7
+ class SmsSender
8
+ attr_reader :logger
9
+
10
+ # Create new sms sender with given +options+
11
+ # - api_url: endpoint api url to make request to
12
+ # - username: endpoint auth user
13
+ # - password: endpoint auth pass
14
+ def initialize(options = {})
15
+ @options = {api_url: 'https://api.multisend.co.il/MultiSendAPI/sendsms'}.merge(options)
16
+ @logger = Logging.logger[self.class]
17
+
18
+ %w(api_url username password).each{|key| raise ArgumentError.new("options :#{key} must be present") if @options[key.to_sym].blank? }
19
+ end
20
+
21
+ def api_url
22
+ @options[:api_url]
23
+ end
24
+
25
+ # send +text+ string to the +phones+ array of phone numbers
26
+ # +options+ - is a hash of optional configuration that can be passed to sms sender:
27
+ # * +sender_name+ - sender name that will override gateway sender name
28
+ # * +sender_number+ - sender number that will override gateway sender number
29
+ # * +delivery_notification_url+ - url which will be invoked upon notification delivery
30
+ # Returns response OpenStruct that contains:
31
+ # * +message_id+ - message id string. You must save this id if you want to receive delivery notifications via push/pull
32
+ def send_sms(message_text, phones, options = {})
33
+ raise ArgumentError.new("Text must be at least 1 character long") if message_text.blank?
34
+ raise ArgumentError.new("No phones were given") if phones.blank?
35
+ raise ArgumentError.new("Either :sender_name or :sender_number attribute required") if options[:sender_name].blank? && options[:sender_number].blank?
36
+ raise ArgumentError.new("Sender number must be between 4 to 14 digits: #{options[:sender_number]}") if options[:sender_number].present? && !PhoneNumberUtils.valid_sender_number?(options[:sender_number])
37
+ raise ArgumentError.new("Sender name must be between 2 and 11 latin chars") if options[:sender_name].present? && !PhoneNumberUtils.valid_sender_name?(options[:sender_name])
38
+
39
+ phones = [phones] unless phones.is_a?(Array)
40
+ phones.each do |p| # check that phones are in valid cellular format
41
+ raise ArgumentError.new("Phone number '#{p}' must be cellular phone with 972 country code") unless PhoneNumberUtils.valid_cellular_phone?(p)
42
+ end
43
+
44
+ message_id = UUIDTools::UUID.timestamp_create.to_str
45
+ body_params = build_send_sms_params(message_text, phones, message_id, options)
46
+ logger.debug "#send_sms - making post to #{@options[:api_url]} with params: \n #{body_params}"
47
+ http_response = HTTParty.post(@options[:api_url], :body => body_params, :headers => {'Accept' => 'application/json'})
48
+ logger.debug "#send_sms - got http response: code=#{http_response.code}; body=\n#{http_response.parsed_response}"
49
+ raise StandardError.new("Non 200 http response code: #{http_response.code} \n #{http_response.parsed_response}") if http_response.code != 200
50
+ if http_response.parsed_response.is_a?(Hash)
51
+ json = http_response.parsed_response
52
+ elsif http_response.parsed_response.is_a?(String)
53
+ begin
54
+ json = JSON.parse(http_response.parsed_response)
55
+ rescue JSON::ParserError => e
56
+ raise PayCallSms::GatewayError.new("Failed to parse response to json: #{http_response.parsed_response}")
57
+ end
58
+ logger.debug "#send_sms - parsed response: #{json.inspect}"
59
+ end
60
+ if json['success'] == true
61
+ OpenStruct.new(
62
+ message_id: message_id,
63
+ )
64
+ else
65
+ raise PayCallSms::GatewayError.new("Failed to send sms: #{json.inspect}")
66
+ end
67
+ end
68
+
69
+ def build_send_sms_params(message_text, phones, message_id, options = {})
70
+ result = {
71
+ user: @options[:username],
72
+ password: @options[:password],
73
+ recipient: phones.join(','),
74
+ message: message_text,
75
+ customermessageid: message_id
76
+ }
77
+ result[:deliverynotificationURL] = options[:delivery_notification_url] if options[:delivery_notification_url].present?
78
+ result[:from] = options[:sender_number]
79
+ result[:from] = options[:sender_name] if options[:sender_name].present?
80
+ result
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_support/all'
2
+
3
+ Dir[File.join(File.dirname(__FILE__), 'pay_call_sms', '*')].each do |file_name|
4
+ require file_name
5
+ end
6
+
7
+ module PayCallSms
8
+
9
+ end
@@ -0,0 +1,94 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: paycall-sms 0.1.0 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "paycall-sms"
9
+ s.version = "0.1.0"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
13
+ s.authors = ["Alex Tkachev"]
14
+ s.date = "2017-11-24"
15
+ s.description = "Ruby api for sms service provider: PayCall"
16
+ s.email = "tkachev.alex@gmail.com"
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt",
19
+ "README.md"
20
+ ]
21
+ s.files = [
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "lib/pay_call_sms/delivery_notification_parser.rb",
29
+ "lib/pay_call_sms/gateway_error.rb",
30
+ "lib/pay_call_sms/incoming_message_parser.rb",
31
+ "lib/pay_call_sms/phone_number_utils.rb",
32
+ "lib/pay_call_sms/sms_sender.rb",
33
+ "lib/paycall-sms.rb",
34
+ "paycall-sms.gemspec",
35
+ "spec/pay_call_sms/delivery_notifications_parser_spec.rb",
36
+ "spec/pay_call_sms/incoming_message_parser_spec.rb",
37
+ "spec/pay_call_sms/phone_number_utils_spec.rb",
38
+ "spec/pay_call_sms/sms_sender_spec.rb",
39
+ "spec/spec_helper.rb"
40
+ ]
41
+ s.homepage = "http://github.com/iplan/paycall-sms"
42
+ s.licenses = ["MIT"]
43
+ s.rubygems_version = "2.4.6"
44
+ s.summary = "Ruby api for sms service provider: PayCall"
45
+
46
+ if s.respond_to? :specification_version then
47
+ s.specification_version = 4
48
+
49
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
50
+ s.add_runtime_dependency(%q<httparty>, ["~> 0.10.2"])
51
+ s.add_runtime_dependency(%q<builder>, ["~> 2.1"])
52
+ s.add_runtime_dependency(%q<nokogiri>, ["~> 1.5.10"])
53
+ s.add_runtime_dependency(%q<uuidtools>, ["~> 2.1"])
54
+ s.add_runtime_dependency(%q<logging>, ["~> 1.7"])
55
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.9"])
56
+ s.add_runtime_dependency(%q<tzinfo>, ["~> 0.3"])
57
+ s.add_runtime_dependency(%q<i18n>, ["~> 0.5"])
58
+ s.add_development_dependency(%q<rspec>, ["= 2.7.0"])
59
+ s.add_development_dependency(%q<webmock>, [">= 0"])
60
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
61
+ s.add_development_dependency(%q<bundler>, [">= 0"])
62
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.8"])
63
+ else
64
+ s.add_dependency(%q<httparty>, ["~> 0.10.2"])
65
+ s.add_dependency(%q<builder>, ["~> 2.1"])
66
+ s.add_dependency(%q<nokogiri>, ["~> 1.5.10"])
67
+ s.add_dependency(%q<uuidtools>, ["~> 2.1"])
68
+ s.add_dependency(%q<logging>, ["~> 1.7"])
69
+ s.add_dependency(%q<activesupport>, ["~> 3.0.9"])
70
+ s.add_dependency(%q<tzinfo>, ["~> 0.3"])
71
+ s.add_dependency(%q<i18n>, ["~> 0.5"])
72
+ s.add_dependency(%q<rspec>, ["= 2.7.0"])
73
+ s.add_dependency(%q<webmock>, [">= 0"])
74
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
75
+ s.add_dependency(%q<bundler>, [">= 0"])
76
+ s.add_dependency(%q<jeweler>, ["~> 1.8.8"])
77
+ end
78
+ else
79
+ s.add_dependency(%q<httparty>, ["~> 0.10.2"])
80
+ s.add_dependency(%q<builder>, ["~> 2.1"])
81
+ s.add_dependency(%q<nokogiri>, ["~> 1.5.10"])
82
+ s.add_dependency(%q<uuidtools>, ["~> 2.1"])
83
+ s.add_dependency(%q<logging>, ["~> 1.7"])
84
+ s.add_dependency(%q<activesupport>, ["~> 3.0.9"])
85
+ s.add_dependency(%q<tzinfo>, ["~> 0.3"])
86
+ s.add_dependency(%q<i18n>, ["~> 0.5"])
87
+ s.add_dependency(%q<rspec>, ["= 2.7.0"])
88
+ s.add_dependency(%q<webmock>, [">= 0"])
89
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
90
+ s.add_dependency(%q<bundler>, [">= 0"])
91
+ s.add_dependency(%q<jeweler>, ["~> 1.8.8"])
92
+ end
93
+ end
94
+
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe PayCallSms::DeliveryNotificationParser do
4
+ let(:parser){ PayCallSms::DeliveryNotificationParser.new }
5
+
6
+ describe '#from_http_push_params' do
7
+ let(:http_params) { {'Status' => 'inprogress', 'CustomerMessageId' => 'a1', 'PhoneNumber' => '0545123456', 'dateTime' => "19-03-2012 23:29:12"} }
8
+ let(:notification) { parser.from_http_push_params(http_params) }
9
+
10
+ it 'should raise GatewayError if parameters are missing or not of expected type' do
11
+ ['PhoneNumber', 'Status', 'CustomerMessageId', 'dateTime'].each do |p|
12
+ params = http_params.clone
13
+ params.delete(p)
14
+ lambda { parser.from_http_push_params(params) }.should raise_error(ArgumentError)
15
+ end
16
+
17
+ lambda { parser.from_http_push_params(http_params.update('dateTime' => 'asdf')) }.should raise_error(ArgumentError)
18
+ end
19
+
20
+ it 'should return DeliveryNotification with all fields initialized' do
21
+ notification.should be_present
22
+ notification.message_id.should == 'a1'
23
+ notification.phone.should == '972545123456'
24
+ notification.gateway_status.should == 'inprogress'
25
+ notification.occurred_at.should be_present
26
+ notification.occurred_at.strftime('%d/%m/%Y %H:%M:%S').should == "19/03/2012 23:29:12"
27
+ end
28
+
29
+ it 'should be delivered when status is delivered' do
30
+ http_params.update('Status' => 'delivered')
31
+ notification.delivery_status.should == :delivered
32
+ end
33
+
34
+ it 'should be not delivered when status is failed' do
35
+ http_params.update('Status' => 'failed')
36
+ notification.delivery_status.should == :failed
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe PayCallSms::IncomingMessageParser do
4
+ let(:parser){ PayCallSms::IncomingMessageParser.new }
5
+
6
+ describe '#from_http_push_params' do
7
+ let(:http_params) { {'msgId' => 'a1234', 'sender' => '0541234567', 'recipient' => '972529992090', 'content' => 'kak dila'} }
8
+ let(:reply) { parser.from_http_push_params(http_params) }
9
+
10
+ it 'should raise ArgumentError if parameters are missing or not of expected type' do
11
+ %w(msgId sender recipient content).each do |p|
12
+ params = http_params.clone.tap{|h| h.delete(p) }
13
+ lambda{ parser.from_http_push_params(params) }.should raise_error(ArgumentError)
14
+ end
15
+ end
16
+
17
+ it 'should return incoming message object with all fields initialized' do
18
+ Time.stub(:now).and_return(Time.utc(2011, 8, 1, 11, 15, 00))
19
+
20
+ reply.should be_present
21
+ reply.message_id.should == 'a1234'
22
+ reply.phone.should == '972541234567'
23
+ reply.text.should == 'kak dila'
24
+ reply.reply_to_phone.should == '972529992090'
25
+ reply.received_at.strftime('%d/%m/%Y %H:%M:%S').should == '01/08/2011 11:15:00'
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe PayCallSms::PhoneNumberUtils do
4
+ let(:utils) { PayCallSms::PhoneNumberUtils }
5
+
6
+ describe '#valid_cellular_phone?' do
7
+ it 'should not be valid without country code' do
8
+ utils.valid_cellular_phone?('0545290862').should be_false
9
+ end
10
+
11
+ it 'should not be valid for landline phones' do
12
+ utils.valid_cellular_phone?('035447037').should be_false
13
+ utils.valid_cellular_phone?('97235447037').should be_false
14
+ end
15
+
16
+ it 'should not be valid with country code but of different lenth' do
17
+ utils.valid_cellular_phone?('9725452908622').should be_false
18
+ utils.valid_cellular_phone?('97254529086').should be_false
19
+ end
20
+
21
+ it 'should be valid with country code' do
22
+ utils.valid_cellular_phone?('972545290862').should be_true
23
+ end
24
+ end
25
+
26
+ describe '#valid_land_line_phone?' do
27
+ it 'should not be valid without country code' do
28
+ utils.valid_land_line_phone?('031234567').should be_false
29
+ end
30
+
31
+ it 'should be valid with country code' do
32
+ utils.valid_land_line_phone?('97231234567').should be_true
33
+ utils.valid_land_line_phone?('972771234567').should be_true
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe PayCallSms::SmsSender do
4
+ let(:sender){ ::PayCallSms::SmsSender.new(:username => 'user', :password => 'pass') }
5
+
6
+ describe '#send_sms' do
7
+ let(:message){ 'my message text' }
8
+ let(:phone){ '972541234567' }
9
+ let(:api_url){ sender.api_url }
10
+
11
+ it 'should raise error if text is blank' do
12
+ lambda{ sender.send_sms('', phone) }.should raise_error(ArgumentError)
13
+ end
14
+
15
+ it 'should raise error if phone is blank' do
16
+ lambda{ sender.send_sms(message, '') }.should raise_error(ArgumentError)
17
+ end
18
+
19
+ it 'should raise error if phone is not valid cellular phone' do
20
+ lambda{ sender.send_sms(message, '0541234567') }.should raise_error(ArgumentError)
21
+ lambda{ sender.send_sms(message, '541234567') }.should raise_error(ArgumentError)
22
+ end
23
+
24
+ it 'should raise error if url not found' do
25
+ stub_request(:any, api_url).to_return(:status => 404)
26
+ lambda{ sender.send_sms('asdf', phone, sender_name: '1234') }.should raise_error(StandardError)
27
+ end
28
+
29
+ it 'should raise error url if response code is 200 but json Success is false' do
30
+ stub_request(:any, api_url).to_return(:status => 200, :body => {success: false}.to_json)
31
+ lambda{ sender.send_sms('asdf', phone, sender_name: '1234') }.should raise_error(PayCallSms::GatewayError)
32
+ end
33
+
34
+ it 'should raise error url if response code is 200 and json Success is true' do
35
+ stub_request(:any, api_url).to_return(:status => 200, :body => {success: true}.to_json)
36
+ result = sender.send_sms('asdf', phone, sender_name: '1234')
37
+ result.class.should == OpenStruct
38
+ result.message_id.should be_present
39
+ end
40
+
41
+ it 'should not raise error url if response code is 200 and but response is not a json' do
42
+ stub_request(:any, api_url).to_return(:status => 200, :body => 'kljidf,dfdef')
43
+ lambda{ sender.send_sms('asdf', phone, sender_name: '1234') }.should raise_error(PayCallSms::GatewayError)
44
+ end
45
+
46
+ end
47
+
48
+ describe '#build_send_sms_params' do
49
+ let(:message){ 'my message text' }
50
+ let(:phones){ ['0541234567'] }
51
+ let(:http_params){ sender.build_send_sms_params(message, phones, '123', :sender_number => '972501234567') }
52
+
53
+ it 'should have username and password' do
54
+ http_params[:user].should == 'user'
55
+ http_params[:password].should == 'pass'
56
+ end
57
+
58
+ it 'should have message text' do
59
+ http_params[:message].should == message
60
+ end
61
+
62
+ it 'should have recepients phone number' do
63
+ http_params[:recipient].should == phones.first
64
+ end
65
+
66
+ it 'should have recepients phone numbers separated by ; without spaces' do
67
+ phones << '0541234568' << '0541234569'
68
+ http_params[:recipient].should == phones.join(',')
69
+ end
70
+
71
+ it 'should have sender number' do
72
+ http_params[:from].should == '972501234567'
73
+ end
74
+
75
+ it 'should have message_id' do
76
+ http_params[:customermessageid].should == '123'
77
+ end
78
+
79
+ it 'should have delivery notification url if specified' do
80
+ http_params = sender.build_send_sms_params(message, phones, '123', :sender_number => '972501234567', :delivery_notification_url => 'http://google.com?auth=1234&alex=king')
81
+ http_params[:deliverynotificationURL].should == "http://google.com?auth=1234&alex=king"
82
+ end
83
+
84
+ it 'should not have delivery notification url if not specified' do
85
+ http_params[:deliverynotificationURL].should be_nil
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'paycall-sms'
5
+ require 'nokogiri'
6
+ require 'ostruct'
7
+ require 'webmock/rspec'
8
+
9
+ # Requires supporting files with custom matchers and macros, etc,
10
+ # in ./support/ and its subdirectories.
11
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
12
+
13
+ RSpec.configure do |config|
14
+
15
+ end
metadata ADDED
@@ -0,0 +1,245 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paycall-sms
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Tkachev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: builder
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.5.10
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.5.10
55
+ - !ruby/object:Gem::Dependency
56
+ name: uuidtools
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: logging
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.9
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.9
97
+ - !ruby/object:Gem::Dependency
98
+ name: tzinfo
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '0.3'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '0.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: i18n
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '0.5'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '0.5'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '='
130
+ - !ruby/object:Gem::Version
131
+ version: 2.7.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '='
137
+ - !ruby/object:Gem::Version
138
+ version: 2.7.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ! '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rdoc
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ~>
158
+ - !ruby/object:Gem::Version
159
+ version: '3.12'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ~>
165
+ - !ruby/object:Gem::Version
166
+ version: '3.12'
167
+ - !ruby/object:Gem::Dependency
168
+ name: bundler
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ! '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: jeweler
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ~>
186
+ - !ruby/object:Gem::Version
187
+ version: 1.8.8
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ~>
193
+ - !ruby/object:Gem::Version
194
+ version: 1.8.8
195
+ description: ! 'Ruby api for sms service provider: PayCall'
196
+ email: tkachev.alex@gmail.com
197
+ executables: []
198
+ extensions: []
199
+ extra_rdoc_files:
200
+ - LICENSE.txt
201
+ - README.md
202
+ files:
203
+ - Gemfile
204
+ - Gemfile.lock
205
+ - LICENSE.txt
206
+ - README.md
207
+ - Rakefile
208
+ - VERSION
209
+ - lib/pay_call_sms/delivery_notification_parser.rb
210
+ - lib/pay_call_sms/gateway_error.rb
211
+ - lib/pay_call_sms/incoming_message_parser.rb
212
+ - lib/pay_call_sms/phone_number_utils.rb
213
+ - lib/pay_call_sms/sms_sender.rb
214
+ - lib/paycall-sms.rb
215
+ - paycall-sms.gemspec
216
+ - spec/pay_call_sms/delivery_notifications_parser_spec.rb
217
+ - spec/pay_call_sms/incoming_message_parser_spec.rb
218
+ - spec/pay_call_sms/phone_number_utils_spec.rb
219
+ - spec/pay_call_sms/sms_sender_spec.rb
220
+ - spec/spec_helper.rb
221
+ homepage: http://github.com/iplan/paycall-sms
222
+ licenses:
223
+ - MIT
224
+ metadata: {}
225
+ post_install_message:
226
+ rdoc_options: []
227
+ require_paths:
228
+ - lib
229
+ required_ruby_version: !ruby/object:Gem::Requirement
230
+ requirements:
231
+ - - ! '>='
232
+ - !ruby/object:Gem::Version
233
+ version: '0'
234
+ required_rubygems_version: !ruby/object:Gem::Requirement
235
+ requirements:
236
+ - - ! '>='
237
+ - !ruby/object:Gem::Version
238
+ version: '0'
239
+ requirements: []
240
+ rubyforge_project:
241
+ rubygems_version: 2.4.6
242
+ signing_key:
243
+ specification_version: 4
244
+ summary: ! 'Ruby api for sms service provider: PayCall'
245
+ test_files: []