cloud_payments 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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