paycall-sms 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []