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.
- 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
|