cloud_payments 0.0.2

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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +106 -0
  8. data/Rakefile +7 -0
  9. data/cloud_payments.gemspec +28 -0
  10. data/config.ru +58 -0
  11. data/lib/cloud_payments.rb +34 -0
  12. data/lib/cloud_payments/client.rb +50 -0
  13. data/lib/cloud_payments/client/errors.rb +68 -0
  14. data/lib/cloud_payments/client/gateway_errors.rb +36 -0
  15. data/lib/cloud_payments/client/response.rb +22 -0
  16. data/lib/cloud_payments/client/serializer.rb +9 -0
  17. data/lib/cloud_payments/client/serializer/base.rb +46 -0
  18. data/lib/cloud_payments/client/serializer/multi_json.rb +17 -0
  19. data/lib/cloud_payments/config.rb +44 -0
  20. data/lib/cloud_payments/models.rb +4 -0
  21. data/lib/cloud_payments/models/model.rb +5 -0
  22. data/lib/cloud_payments/models/secure3d.rb +15 -0
  23. data/lib/cloud_payments/models/subscription.rb +49 -0
  24. data/lib/cloud_payments/models/transaction.rb +82 -0
  25. data/lib/cloud_payments/namespaces.rb +23 -0
  26. data/lib/cloud_payments/namespaces/base.rb +50 -0
  27. data/lib/cloud_payments/namespaces/cards.rb +25 -0
  28. data/lib/cloud_payments/namespaces/payments.rb +32 -0
  29. data/lib/cloud_payments/namespaces/subscriptions.rb +24 -0
  30. data/lib/cloud_payments/namespaces/tokens.rb +15 -0
  31. data/lib/cloud_payments/version.rb +3 -0
  32. data/spec/cloud_payments/client/response_spec.rb +35 -0
  33. data/spec/cloud_payments/client/serializer/multi_json_spec.rb +16 -0
  34. data/spec/cloud_payments/client_spec.rb +5 -0
  35. data/spec/cloud_payments/models/secure3d_spec.rb +37 -0
  36. data/spec/cloud_payments/models/subscription_spec.rb +141 -0
  37. data/spec/cloud_payments/models/transaction_spec.rb +254 -0
  38. data/spec/cloud_payments/namespaces/base_spec.rb +75 -0
  39. data/spec/cloud_payments/namespaces/cards_spec.rb +119 -0
  40. data/spec/cloud_payments/namespaces/payments_spec.rb +96 -0
  41. data/spec/cloud_payments/namespaces/subscriptions_spec.rb +82 -0
  42. data/spec/cloud_payments/namespaces/tokens_spec.rb +90 -0
  43. data/spec/cloud_payments/namespaces_spec.rb +45 -0
  44. data/spec/cloud_payments_spec.rb +14 -0
  45. data/spec/fixtures/apis/cards/auth/failed.yml +45 -0
  46. data/spec/fixtures/apis/cards/auth/secure3d.yml +15 -0
  47. data/spec/fixtures/apis/cards/auth/successful.yml +48 -0
  48. data/spec/fixtures/apis/cards/charge/failed.yml +45 -0
  49. data/spec/fixtures/apis/cards/charge/secure3d.yml +15 -0
  50. data/spec/fixtures/apis/cards/charge/successful.yml +48 -0
  51. data/spec/fixtures/apis/payments/confirm/failed.yml +6 -0
  52. data/spec/fixtures/apis/payments/confirm/failed_with_message.yml +6 -0
  53. data/spec/fixtures/apis/payments/confirm/successful.yml +6 -0
  54. data/spec/fixtures/apis/payments/post3ds/failed.yml +45 -0
  55. data/spec/fixtures/apis/payments/post3ds/successful.yml +48 -0
  56. data/spec/fixtures/apis/payments/refund/failed.yml +6 -0
  57. data/spec/fixtures/apis/payments/refund/failed_with_message.yml +6 -0
  58. data/spec/fixtures/apis/payments/refund/successful.yml +6 -0
  59. data/spec/fixtures/apis/payments/void/failed.yml +6 -0
  60. data/spec/fixtures/apis/payments/void/failed_with_message.yml +6 -0
  61. data/spec/fixtures/apis/payments/void/successful.yml +6 -0
  62. data/spec/fixtures/apis/ping/failed.yml +5 -0
  63. data/spec/fixtures/apis/ping/successful.yml +5 -0
  64. data/spec/fixtures/apis/subscriptions/cancel/successful.yml +6 -0
  65. data/spec/fixtures/apis/subscriptions/create/successful.yml +31 -0
  66. data/spec/fixtures/apis/subscriptions/find/successful.yml +31 -0
  67. data/spec/fixtures/apis/subscriptions/update/successful.yml +31 -0
  68. data/spec/fixtures/apis/tokens/auth/failed.yml +45 -0
  69. data/spec/fixtures/apis/tokens/auth/successful.yml +48 -0
  70. data/spec/fixtures/apis/tokens/charge/failed.yml +45 -0
  71. data/spec/fixtures/apis/tokens/charge/successful.yml +48 -0
  72. data/spec/spec_helper.rb +38 -0
  73. data/spec/support/examples.rb +27 -0
  74. data/spec/support/helpers.rb +89 -0
  75. metadata +244 -0
@@ -0,0 +1,22 @@
1
+ module CloudPayments
2
+ class Client
3
+ class Response
4
+ attr_reader :status, :origin_body, :headers
5
+
6
+ def initialize(status, body, headers = {})
7
+ @status, @origin_body, @headers = status, body, headers
8
+ @origin_body = body.force_encoding('UTF-8') if body.respond_to?(:force_encoding)
9
+ end
10
+
11
+ def body
12
+ @body ||= headers && headers['content-type'] =~ /json/ ? serializer.load(origin_body) : origin_body
13
+ end
14
+
15
+ private
16
+
17
+ def serializer
18
+ CloudPayments.config.serializer
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ require 'cloud_payments/client/serializer/base'
2
+ require 'cloud_payments/client/serializer/multi_json'
3
+
4
+ module CloudPayments
5
+ class Client
6
+ module Serializer
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module CloudPayments
2
+ class Client
3
+ module Serializer
4
+ class Base
5
+ attr_reader :config
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ def load(json)
12
+ convert_keys_from_api(json)
13
+ end
14
+
15
+ def dump(hash)
16
+ convert_keys_to_api(hash)
17
+ end
18
+
19
+ protected
20
+
21
+ def convert_keys_from_api(attributes)
22
+ attributes.each_with_object({}) do |(key, value), result|
23
+ value = convert_keys_from_api(value) if value.is_a?(Hash)
24
+
25
+ key = key.to_s.gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
26
+ key.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
27
+ key.tr!('-', '_')
28
+ key.downcase!
29
+ result[key.to_sym] = value
30
+ end
31
+ end
32
+
33
+ def convert_keys_to_api(attributes)
34
+ attributes.each_with_object({}) do |(key, value), result|
35
+ value = convert_keys_to_api(value) if value.is_a?(Hash)
36
+
37
+ key = key.to_s.gsub(/^[a-z\d]*/){ $&.capitalize }
38
+ key.gsub!(/(?:_|(\/))([a-z\d]*)/i){ "#{$1}#{$2.capitalize}" }
39
+ key.gsub!('/', '::')
40
+ result[key] = value
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module CloudPayments
2
+ class Client
3
+ module Serializer
4
+ class MultiJson < Base
5
+ def load(json)
6
+ return nil if json.empty?
7
+ super(::MultiJson.load(json))
8
+ end
9
+
10
+ def dump(data)
11
+ return '' if data.nil?
12
+ ::MultiJson.dump(super(data))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ module CloudPayments
2
+ class Config
3
+ attr_accessor :connection_options, :serializer, :log, :public_key, :secret_key, :host, :raise_banking_errors
4
+ attr_writer :logger
5
+
6
+ DEFAULT_LOGGER = ->{
7
+ require 'logger'
8
+ logger = Logger.new(STDERR)
9
+ logger.progname = 'cloud_payments'
10
+ logger.formatter = ->(severity, datetime, progname, msg){ "#{datetime} (#{progname}): #{msg}\n" }
11
+ logger
12
+ }
13
+
14
+ def initialize
15
+ @log = false
16
+ @serializer = Client::Serializer::MultiJson.new(self)
17
+ @connection_options = {}
18
+ @connection_block = nil
19
+ @host = 'https://api.cloudpayments.ru'
20
+ end
21
+
22
+ def logger
23
+ @logger ||= log ? DEFAULT_LOGGER.call : nil
24
+ end
25
+
26
+ def available_currencies
27
+ %w{RUB USD EUR}
28
+ end
29
+
30
+ def connection_block(&block)
31
+ if block_given?
32
+ @connection_block = block
33
+ else
34
+ @connection_block
35
+ end
36
+ end
37
+
38
+ def dup
39
+ clone = super
40
+ clone.connection_options = connection_options.dup
41
+ clone
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,4 @@
1
+ require 'cloud_payments/models/model'
2
+ require 'cloud_payments/models/secure3d'
3
+ require 'cloud_payments/models/transaction'
4
+ require 'cloud_payments/models/subscription'
@@ -0,0 +1,5 @@
1
+ module CloudPayments
2
+ class Model < Hashie::Trash
3
+ include Hashie::Extensions::IgnoreUndeclared
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ module CloudPayments
2
+ class Secure3D < Model
3
+ property :transaction_id, required: true
4
+ property :pa_req, required: true
5
+ property :acs_url, required: true
6
+
7
+ def id
8
+ transaction_id
9
+ end
10
+
11
+ def required_secure3d?
12
+ true
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ module CloudPayments
2
+ class Subscription < Model
3
+ ACTIVE = 'Active'
4
+ PAST_DUE = 'PastDue'
5
+ CANCELLED = 'Cancelled'
6
+ REJECTED = 'Rejected'
7
+ EXPIRED = 'Expired'
8
+
9
+ property :id, required: true
10
+ property :account_id, required: true
11
+ property :description, required: true
12
+ property :email, required: true
13
+ property :amount, required: true
14
+ property :currency, required: true
15
+ property :currency_code, required: true
16
+ property :require_confirmation, required: true
17
+ property :started_at, from: :start_date_iso, with: ->(v){ DateTime.parse(v) if v }, required: true
18
+ property :interval, required: true
19
+ property :interval_code, required: true
20
+ property :period, required: true
21
+ property :max_periods
22
+ property :status, required: true
23
+ property :status_code, required: true
24
+ property :successful_transactions, from: :successful_transactions_number, required: true
25
+ property :failed_transactions, from: :failed_transactions_number, required: true
26
+ property :last_transaction_at, from: :last_transaction_date_iso, with: ->(v){ DateTime.parse(v) if v }
27
+ property :next_transaction_at, from: :next_transaction_date_iso, with: ->(v){ DateTime.parse(v) if v }
28
+
29
+ def active?
30
+ status == ACTIVE
31
+ end
32
+
33
+ def past_due?
34
+ status == PAST_DUE
35
+ end
36
+
37
+ def cancelled?
38
+ status == CANCELLED
39
+ end
40
+
41
+ def rejected?
42
+ status == REJECTED
43
+ end
44
+
45
+ def expired?
46
+ status == EXPIRED
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,82 @@
1
+ module CloudPayments
2
+ class Transaction < Model
3
+ AWAITING_AUTHENTICATION = 'AwaitingAuthentication'
4
+ COMPLECTED = 'Completed'
5
+ AUTHORIZED = 'Authorized'
6
+ CANCELLED = 'Cancelled'
7
+ DECLINED = 'Declined'
8
+
9
+ property :id, from: :transaction_id, required: true
10
+ property :amount, required: true
11
+ property :currency, required: true
12
+ property :currency_code
13
+ property :invoice_id
14
+ property :account_id
15
+ property :subscription_id
16
+ property :email
17
+ property :description
18
+ property :metadata, from: :json_data, default: {}
19
+ property :date_time, transform_with: ->(v){ DateTime.parse(v) if v }
20
+ property :created_at, from: :created_date_iso, with: ->(v){ DateTime.parse(v) if v }
21
+ property :authorized_at, from: :auth_date_iso, with: ->(v){ DateTime.parse(v) if v }
22
+ property :confirmed_at, from: :confirm_date_iso, with: ->(v){ DateTime.parse(v) if v }
23
+ property :auth_code
24
+ property :test_mode, required: true
25
+ property :ip_address
26
+ property :ip_country
27
+ property :ip_city
28
+ property :ip_region
29
+ property :ip_district
30
+ property :ip_lat, from: :ip_latitude
31
+ property :ip_lng, from: :ip_longitude
32
+ property :card_first_six, required: true
33
+ property :card_last_four, required: true
34
+ property :card_type, required: true
35
+ property :card_type_code
36
+ property :card_exp_date
37
+ property :name
38
+ property :issuer_bank_country
39
+ property :status, required: true
40
+ property :status_code
41
+ property :reason
42
+ property :reason_code
43
+ property :card_holder_message
44
+ property :token
45
+
46
+ def required_secure3d?
47
+ false
48
+ end
49
+
50
+ def subscription
51
+ @subscription ||= CloudPayments.client.subscriptions.find(subscription_id) if subscription_id
52
+ end
53
+
54
+ def card_number
55
+ @card_number ||= "#{card_first_six}XXXXXX#{card_last_four}".gsub(/(.{4})/, '\1 ').rstrip
56
+ end
57
+
58
+ def ip_location
59
+ [ip_lat, ip_lng] if ip_lng && ip_lat
60
+ end
61
+
62
+ def awaiting_authentication?
63
+ status == AWAITING_AUTHENTICATION
64
+ end
65
+
66
+ def completed?
67
+ status == COMPLECTED
68
+ end
69
+
70
+ def authorized?
71
+ status == AUTHORIZED
72
+ end
73
+
74
+ def cancelled?
75
+ status == CANCELLED
76
+ end
77
+
78
+ def declined?
79
+ status == DECLINED
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,23 @@
1
+ require 'cloud_payments/namespaces/base'
2
+ require 'cloud_payments/namespaces/cards'
3
+ require 'cloud_payments/namespaces/tokens'
4
+ require 'cloud_payments/namespaces/payments'
5
+ require 'cloud_payments/namespaces/subscriptions'
6
+
7
+ module CloudPayments
8
+ module Namespaces
9
+ def payments
10
+ Payments.new(self)
11
+ end
12
+
13
+ def subscriptions
14
+ Subscriptions.new(self)
15
+ end
16
+
17
+ def ping
18
+ !!(perform_request('/test').body || {})[:success]
19
+ rescue ::Faraday::Error::ConnectionFailed, ::Faraday::Error::TimeoutError, CloudPayments::Client::ServerError => e
20
+ false
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ module CloudPayments
2
+ module Namespaces
3
+ class Base
4
+ attr_reader :client, :parent_path
5
+
6
+ class << self
7
+ def resource_name
8
+ self.name.split('::').last.downcase
9
+ end
10
+ end
11
+
12
+ def initialize(client, parent_path = nil)
13
+ @client = client
14
+ @parent_path = parent_path
15
+ end
16
+
17
+ def request(path, params = {})
18
+ response = client.perform_request(resource_path(path), params)
19
+ raise_gateway_error(response.body) unless response.body[:success]
20
+ response.body
21
+ end
22
+
23
+ protected
24
+
25
+ def api_exceptions
26
+ [::Faraday::Error::ConnectionFailed, ::Faraday::Error::TimeoutError, Client::ServerError, Client::GatewayError]
27
+ end
28
+
29
+ def resource_path(path = nil)
30
+ [parent_path, self.class.resource_name, path].flatten.compact.join(?/).squeeze(?/)
31
+ end
32
+
33
+ def raise_gateway_error(body)
34
+ raise_reasoned_gateway_error(body) || raise_raw_gateway_error(body)
35
+ end
36
+
37
+ def raise_reasoned_gateway_error(body)
38
+ fail Client::GATEWAY_ERRORS[body[:model][:reason_code]].new(body) if reason_present?(body)
39
+ end
40
+
41
+ def raise_raw_gateway_error(body)
42
+ fail Client::GatewayError.new(body[:message], body) if !body[:message].nil? && !body[:message].empty?
43
+ end
44
+
45
+ def reason_present?(body)
46
+ !body[:model].nil? && !body[:model].empty? && !body[:model][:reason_code].nil? && CloudPayments.config.raise_banking_errors
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ module CloudPayments
2
+ module Namespaces
3
+ class Cards < Base
4
+ def charge(attributes)
5
+ response = request(:charge, attributes)
6
+ instantiate(response[:model])
7
+ end
8
+
9
+ def auth(attributes)
10
+ response = request(:auth, attributes)
11
+ instantiate(response[:model])
12
+ end
13
+
14
+ private
15
+
16
+ def instantiate(model)
17
+ if model[:pa_req]
18
+ Secure3D.new(model)
19
+ else
20
+ Transaction.new(model)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ module CloudPayments
2
+ module Namespaces
3
+ class Payments < Base
4
+ def cards
5
+ Cards.new(client, resource_path)
6
+ end
7
+
8
+ def tokens
9
+ Tokens.new(client, resource_path)
10
+ end
11
+
12
+ def confirm(id, amount)
13
+ request(:confirm, transaction_id: id, amount: amount)[:success]
14
+ end
15
+
16
+ def void(id)
17
+ request(:void, transaction_id: id)[:success]
18
+ end
19
+
20
+ alias :cancel :void
21
+
22
+ def refund(id, amount)
23
+ request(:refund, transaction_id: id, amount: amount)[:success]
24
+ end
25
+
26
+ def post3ds(id, pa_res)
27
+ response = request(:post3ds, transaction_id: id, pa_res: pa_res)
28
+ Transaction.new(response[:model])
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module CloudPayments
2
+ module Namespaces
3
+ class Subscriptions < Base
4
+ def find(id)
5
+ response = request(:get, id: id)
6
+ Subscription.new(response[:model])
7
+ end
8
+
9
+ def create(attributes)
10
+ response = request(:create, attributes)
11
+ Subscription.new(response[:model])
12
+ end
13
+
14
+ def update(id, attributes)
15
+ response = request(:update, attributes.merge(id: id))
16
+ Subscription.new(response[:model])
17
+ end
18
+
19
+ def cancel(id)
20
+ request(:cancel, id: id)[:success]
21
+ end
22
+ end
23
+ end
24
+ end