cloud_payments 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +4 -0
- data/.travis.yml +8 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +106 -0
- data/Rakefile +7 -0
- data/cloud_payments.gemspec +28 -0
- data/config.ru +58 -0
- data/lib/cloud_payments.rb +34 -0
- data/lib/cloud_payments/client.rb +50 -0
- data/lib/cloud_payments/client/errors.rb +68 -0
- data/lib/cloud_payments/client/gateway_errors.rb +36 -0
- data/lib/cloud_payments/client/response.rb +22 -0
- data/lib/cloud_payments/client/serializer.rb +9 -0
- data/lib/cloud_payments/client/serializer/base.rb +46 -0
- data/lib/cloud_payments/client/serializer/multi_json.rb +17 -0
- data/lib/cloud_payments/config.rb +44 -0
- data/lib/cloud_payments/models.rb +4 -0
- data/lib/cloud_payments/models/model.rb +5 -0
- data/lib/cloud_payments/models/secure3d.rb +15 -0
- data/lib/cloud_payments/models/subscription.rb +49 -0
- data/lib/cloud_payments/models/transaction.rb +82 -0
- data/lib/cloud_payments/namespaces.rb +23 -0
- data/lib/cloud_payments/namespaces/base.rb +50 -0
- data/lib/cloud_payments/namespaces/cards.rb +25 -0
- data/lib/cloud_payments/namespaces/payments.rb +32 -0
- data/lib/cloud_payments/namespaces/subscriptions.rb +24 -0
- data/lib/cloud_payments/namespaces/tokens.rb +15 -0
- data/lib/cloud_payments/version.rb +3 -0
- data/spec/cloud_payments/client/response_spec.rb +35 -0
- data/spec/cloud_payments/client/serializer/multi_json_spec.rb +16 -0
- data/spec/cloud_payments/client_spec.rb +5 -0
- data/spec/cloud_payments/models/secure3d_spec.rb +37 -0
- data/spec/cloud_payments/models/subscription_spec.rb +141 -0
- data/spec/cloud_payments/models/transaction_spec.rb +254 -0
- data/spec/cloud_payments/namespaces/base_spec.rb +75 -0
- data/spec/cloud_payments/namespaces/cards_spec.rb +119 -0
- data/spec/cloud_payments/namespaces/payments_spec.rb +96 -0
- data/spec/cloud_payments/namespaces/subscriptions_spec.rb +82 -0
- data/spec/cloud_payments/namespaces/tokens_spec.rb +90 -0
- data/spec/cloud_payments/namespaces_spec.rb +45 -0
- data/spec/cloud_payments_spec.rb +14 -0
- data/spec/fixtures/apis/cards/auth/failed.yml +45 -0
- data/spec/fixtures/apis/cards/auth/secure3d.yml +15 -0
- data/spec/fixtures/apis/cards/auth/successful.yml +48 -0
- data/spec/fixtures/apis/cards/charge/failed.yml +45 -0
- data/spec/fixtures/apis/cards/charge/secure3d.yml +15 -0
- data/spec/fixtures/apis/cards/charge/successful.yml +48 -0
- data/spec/fixtures/apis/payments/confirm/failed.yml +6 -0
- data/spec/fixtures/apis/payments/confirm/failed_with_message.yml +6 -0
- data/spec/fixtures/apis/payments/confirm/successful.yml +6 -0
- data/spec/fixtures/apis/payments/post3ds/failed.yml +45 -0
- data/spec/fixtures/apis/payments/post3ds/successful.yml +48 -0
- data/spec/fixtures/apis/payments/refund/failed.yml +6 -0
- data/spec/fixtures/apis/payments/refund/failed_with_message.yml +6 -0
- data/spec/fixtures/apis/payments/refund/successful.yml +6 -0
- data/spec/fixtures/apis/payments/void/failed.yml +6 -0
- data/spec/fixtures/apis/payments/void/failed_with_message.yml +6 -0
- data/spec/fixtures/apis/payments/void/successful.yml +6 -0
- data/spec/fixtures/apis/ping/failed.yml +5 -0
- data/spec/fixtures/apis/ping/successful.yml +5 -0
- data/spec/fixtures/apis/subscriptions/cancel/successful.yml +6 -0
- data/spec/fixtures/apis/subscriptions/create/successful.yml +31 -0
- data/spec/fixtures/apis/subscriptions/find/successful.yml +31 -0
- data/spec/fixtures/apis/subscriptions/update/successful.yml +31 -0
- data/spec/fixtures/apis/tokens/auth/failed.yml +45 -0
- data/spec/fixtures/apis/tokens/auth/successful.yml +48 -0
- data/spec/fixtures/apis/tokens/charge/failed.yml +45 -0
- data/spec/fixtures/apis/tokens/charge/successful.yml +48 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/examples.rb +27 -0
- data/spec/support/helpers.rb +89 -0
- 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,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,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
|