cloud_payments 0.0.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +12 -6
  3. data/Gemfile +2 -2
  4. data/README.md +88 -5
  5. data/Rakefile +1 -0
  6. data/cloud_payments.gemspec +8 -8
  7. data/config.ru +1 -0
  8. data/lib/cloud_payments.rb +6 -1
  9. data/lib/cloud_payments/client.rb +3 -2
  10. data/lib/cloud_payments/client/errors.rb +1 -0
  11. data/lib/cloud_payments/client/gateway_errors.rb +3 -1
  12. data/lib/cloud_payments/client/response.rb +2 -1
  13. data/lib/cloud_payments/client/serializer.rb +1 -0
  14. data/lib/cloud_payments/client/serializer/base.rb +9 -1
  15. data/lib/cloud_payments/client/serializer/multi_json.rb +1 -0
  16. data/lib/cloud_payments/config.rb +4 -0
  17. data/lib/cloud_payments/models.rb +8 -0
  18. data/lib/cloud_payments/models/like_subscription.rb +30 -0
  19. data/lib/cloud_payments/models/model.rb +9 -0
  20. data/lib/cloud_payments/models/on_fail.rb +31 -0
  21. data/lib/cloud_payments/models/on_kassa_receipt.rb +23 -0
  22. data/lib/cloud_payments/models/on_pay.rb +37 -0
  23. data/lib/cloud_payments/models/on_recurrent.rb +22 -0
  24. data/lib/cloud_payments/models/order.rb +14 -0
  25. data/lib/cloud_payments/models/secure3d.rb +1 -0
  26. data/lib/cloud_payments/models/subscription.rb +9 -32
  27. data/lib/cloud_payments/models/transaction.rb +13 -4
  28. data/lib/cloud_payments/namespaces.rb +12 -1
  29. data/lib/cloud_payments/namespaces/base.rb +2 -1
  30. data/lib/cloud_payments/namespaces/cards.rb +6 -0
  31. data/lib/cloud_payments/namespaces/kassa.rb +22 -0
  32. data/lib/cloud_payments/namespaces/orders.rb +15 -0
  33. data/lib/cloud_payments/namespaces/payments.rb +11 -0
  34. data/lib/cloud_payments/namespaces/subscriptions.rb +6 -0
  35. data/lib/cloud_payments/namespaces/tokens.rb +1 -0
  36. data/lib/cloud_payments/version.rb +2 -1
  37. data/lib/cloud_payments/webhooks.rb +42 -0
  38. data/spec/cloud_payments/client/response_spec.rb +3 -1
  39. data/spec/cloud_payments/client/serializer/multi_json_spec.rb +1 -0
  40. data/spec/cloud_payments/models/order_spec.rb +62 -0
  41. data/spec/cloud_payments/models/secure3d_spec.rb +1 -0
  42. data/spec/cloud_payments/models/subscription_spec.rb +1 -1
  43. data/spec/cloud_payments/models/transaction_spec.rb +25 -1
  44. data/spec/cloud_payments/namespaces/base_spec.rb +10 -3
  45. data/spec/cloud_payments/namespaces/cards_spec.rb +42 -2
  46. data/spec/cloud_payments/namespaces/kassa_spec.rb +43 -0
  47. data/spec/cloud_payments/namespaces/orders_spec.rb +57 -0
  48. data/spec/cloud_payments/namespaces/payments_spec.rb +119 -0
  49. data/spec/cloud_payments/namespaces/subscriptions_spec.rb +21 -1
  50. data/spec/cloud_payments/namespaces/tokens_spec.rb +1 -0
  51. data/spec/cloud_payments/namespaces_spec.rb +2 -1
  52. data/spec/cloud_payments/webhooks_spec.rb +272 -0
  53. data/spec/cloud_payments_spec.rb +27 -4
  54. data/spec/fixtures/apis/cards/post3ds/failed.yml +45 -0
  55. data/spec/fixtures/apis/cards/post3ds/successful.yml +48 -0
  56. data/spec/fixtures/apis/orders/cancel/failed.yml +6 -0
  57. data/spec/fixtures/apis/orders/cancel/successful.yml +6 -0
  58. data/spec/fixtures/apis/orders/create/successful.yml +20 -0
  59. data/spec/fixtures/apis/payments/find/failed.yml +45 -0
  60. data/spec/fixtures/apis/payments/find/failed_with_message.yml +6 -0
  61. data/spec/fixtures/apis/payments/find/successful.yml +48 -0
  62. data/spec/fixtures/apis/payments/get/failed.yml +46 -0
  63. data/spec/fixtures/apis/payments/get/failed_with_message.yml +6 -0
  64. data/spec/fixtures/apis/payments/get/refunded.yml +49 -0
  65. data/spec/fixtures/apis/payments/get/successful.yml +49 -0
  66. data/spec/fixtures/apis/subscriptions/find/successful.yml +4 -4
  67. data/spec/fixtures/apis/subscriptions/get/successful.yml +31 -0
  68. data/spec/spec_helper.rb +13 -24
  69. data/spec/support/examples.rb +1 -0
  70. data/spec/support/helpers.rb +3 -2
  71. metadata +62 -34
  72. data/spec/cloud_payments/client_spec.rb +0 -5
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module CloudPayments
3
+ class OnPay < Model
4
+ property :id, from: :transaction_id, required: true
5
+ property :amount, transform_with: DecimalTransform, required: true
6
+ property :currency, required: true
7
+ property :invoice_id
8
+ property :account_id
9
+ property :subscription_id
10
+ property :email
11
+ property :description
12
+ property :metadata, from: :data, default: {}
13
+ property :date_time, transform_with: DateTimeTransform
14
+ property :auth_code
15
+ property :test_mode, required: true
16
+ property :ip_address
17
+ property :ip_country
18
+ property :ip_city
19
+ property :ip_region
20
+ property :ip_district
21
+ property :ip_lat, from: :ip_latitude
22
+ property :ip_lng, from: :ip_longitude
23
+ property :card_first_six, required: true
24
+ property :card_last_four, required: true
25
+ property :card_type, required: true
26
+ property :card_type_code
27
+ property :card_exp_date
28
+ property :name
29
+ property :issuer
30
+ property :issuer_bank_country
31
+ property :status, required: true
32
+ property :status_code
33
+ property :reason
34
+ property :reason_code
35
+ property :token
36
+ end
37
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module CloudPayments
3
+ class OnRecurrent < Model
4
+ property :id, required: true
5
+ property :account_id, required: true
6
+ property :description, required: true
7
+ property :email, required: true
8
+ property :amount, transform_with: DecimalTransform, required: true
9
+ property :currency, required: true
10
+ property :require_confirmation, transform_with: BooleanTransform, required: true
11
+ property :started_at, from: :start_date, with: DateTimeTransform, required: true
12
+ property :interval, required: true
13
+ property :period, transform_with: IntegralTransform, required: true
14
+ property :status, required: true
15
+ property :successful_transactions, from: :successful_transactions_number, with: IntegralTransform, required: true
16
+ property :failed_transactions, from: :failed_transactions_number, with: IntegralTransform, required: true
17
+ property :last_transaction_at, from: :last_transaction_date, with: DateTimeTransform
18
+ property :next_transaction_at, from: :next_transaction_date, with: DateTimeTransform
19
+
20
+ include LikeSubscription
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module CloudPayments
3
+ class Order < Model
4
+ property :id, required: true
5
+ property :number, required: true
6
+ property :amount, transform_with: DecimalTransform, required: true
7
+ property :currency, required: true
8
+ property :currency_code, required: true
9
+ property :email
10
+ property :description, required: true
11
+ property :require_confirmation, transform_with: BooleanTransform, required: true
12
+ property :url, required: true
13
+ end
14
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  class Secure3D < Model
3
4
  property :transaction_id, required: true
@@ -1,49 +1,26 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  class Subscription < Model
3
- ACTIVE = 'Active'
4
- PAST_DUE = 'PastDue'
5
- CANCELLED = 'Cancelled'
6
- REJECTED = 'Rejected'
7
- EXPIRED = 'Expired'
4
+ include LikeSubscription
8
5
 
9
6
  property :id, required: true
10
7
  property :account_id, required: true
11
- property :description, required: true
8
+ property :description
12
9
  property :email, required: true
13
- property :amount, required: true
10
+ property :amount, transform_with: DecimalTransform, required: true
14
11
  property :currency, required: true
15
12
  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
13
+ property :require_confirmation, transform_with: BooleanTransform, required: true
14
+ property :started_at, from: :start_date_iso, with: DateTimeTransform, required: true
18
15
  property :interval, required: true
19
16
  property :interval_code, required: true
20
- property :period, required: true
17
+ property :period, transform_with: IntegralTransform, required: true
21
18
  property :max_periods
22
19
  property :status, required: true
23
20
  property :status_code, required: true
24
21
  property :successful_transactions, from: :successful_transactions_number, required: true
25
22
  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
23
+ property :last_transaction_at, from: :last_transaction_date_iso, with: DateTimeTransform
24
+ property :next_transaction_at, from: :next_transaction_date_iso, with: DateTimeTransform
48
25
  end
49
26
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  class Transaction < Model
3
4
  AWAITING_AUTHENTICATION = 'AwaitingAuthentication'
@@ -16,10 +17,10 @@ module CloudPayments
16
17
  property :email
17
18
  property :description
18
19
  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 }
20
+ property :date_time, transform_with: DateTimeTransform
21
+ property :created_at, from: :created_date_iso, with: DateTimeTransform
22
+ property :authorized_at, from: :auth_date_iso, with: DateTimeTransform
23
+ property :confirmed_at, from: :confirm_date_iso, with: DateTimeTransform
23
24
  property :auth_code
24
25
  property :test_mode, required: true
25
26
  property :ip_address
@@ -35,13 +36,17 @@ module CloudPayments
35
36
  property :card_type_code
36
37
  property :card_exp_date
37
38
  property :name
39
+ property :issuer
38
40
  property :issuer_bank_country
39
41
  property :status, required: true
40
42
  property :status_code
41
43
  property :reason
42
44
  property :reason_code
45
+ property :refunded
43
46
  property :card_holder_message
44
47
  property :token
48
+ property :apple_pay
49
+ property :android_pay
45
50
 
46
51
  def required_secure3d?
47
52
  false
@@ -78,5 +83,9 @@ module CloudPayments
78
83
  def declined?
79
84
  status == DECLINED
80
85
  end
86
+
87
+ def refunded?
88
+ refunded
89
+ end
81
90
  end
82
91
  end
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  require 'cloud_payments/namespaces/base'
2
3
  require 'cloud_payments/namespaces/cards'
3
4
  require 'cloud_payments/namespaces/tokens'
4
5
  require 'cloud_payments/namespaces/payments'
5
6
  require 'cloud_payments/namespaces/subscriptions'
7
+ require 'cloud_payments/namespaces/orders'
8
+ require 'cloud_payments/namespaces/kassa'
6
9
 
7
10
  module CloudPayments
8
11
  module Namespaces
@@ -10,13 +13,21 @@ module CloudPayments
10
13
  Payments.new(self)
11
14
  end
12
15
 
16
+ def kassa
17
+ Kassa.new(self)
18
+ end
19
+
13
20
  def subscriptions
14
21
  Subscriptions.new(self)
15
22
  end
16
23
 
24
+ def orders
25
+ Orders.new(self)
26
+ end
27
+
17
28
  def ping
18
29
  !!(perform_request('/test').body || {})[:success]
19
- rescue ::Faraday::Error::ConnectionFailed, ::Faraday::Error::TimeoutError, CloudPayments::Client::ServerError => e
30
+ rescue ::Faraday::ConnectionFailed, ::Faraday::TimeoutError, CloudPayments::Client::ServerError => e
20
31
  false
21
32
  end
22
33
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  module Namespaces
3
4
  class Base
@@ -23,7 +24,7 @@ module CloudPayments
23
24
  protected
24
25
 
25
26
  def api_exceptions
26
- [::Faraday::Error::ConnectionFailed, ::Faraday::Error::TimeoutError, Client::ServerError, Client::GatewayError]
27
+ [::Faraday::ConnectionFailed, ::Faraday::TimeoutError, Client::ServerError, Client::GatewayError]
27
28
  end
28
29
 
29
30
  def resource_path(path = nil)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  module Namespaces
3
4
  class Cards < Base
@@ -11,6 +12,11 @@ module CloudPayments
11
12
  instantiate(response[:model])
12
13
  end
13
14
 
15
+ def post3ds(attributes)
16
+ response = request(:post3ds, attributes)
17
+ instantiate(response[:model])
18
+ end
19
+
14
20
  private
15
21
 
16
22
  def instantiate(model)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module CloudPayments
3
+ module Namespaces
4
+ class Kassa < Base
5
+ InnNotProvided = Class.new(StandardError)
6
+ TypeNotProvided = Class.new(StandardError)
7
+ CustomerReceiptNotProvided = Class.new(StandardError)
8
+
9
+ def self.resource_name
10
+ 'kkt'
11
+ end
12
+
13
+ def receipt(attributes)
14
+ attributes.fetch(:inn) { raise InnNotProvided.new('inn attribute is required') }
15
+ attributes.fetch(:type) { raise TypeNotProvided.new('type attribute is required') }
16
+ attributes.fetch(:customer_receipt) { raise CustomerReceiptNotProvided.new('customer_receipt is required') }
17
+
18
+ request(:receipt, attributes)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module CloudPayments
3
+ module Namespaces
4
+ class Orders < Base
5
+ def create(attributes)
6
+ response = request(:create, attributes)
7
+ Order.new(response[:model])
8
+ end
9
+
10
+ def cancel(order_id)
11
+ request(:cancel, id: order_id)[:success]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  module Namespaces
3
4
  class Payments < Base
@@ -27,6 +28,16 @@ module CloudPayments
27
28
  response = request(:post3ds, transaction_id: id, pa_res: pa_res)
28
29
  Transaction.new(response[:model])
29
30
  end
31
+
32
+ def get(id)
33
+ response = request(:get, transaction_id: id)
34
+ Transaction.new(response[:model])
35
+ end
36
+
37
+ def find(invoice_id)
38
+ response = request(:find, invoice_id: invoice_id)
39
+ Transaction.new(response[:model])
40
+ end
30
41
  end
31
42
  end
32
43
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  module Namespaces
3
4
  class Subscriptions < Base
@@ -6,6 +7,11 @@ module CloudPayments
6
7
  Subscription.new(response[:model])
7
8
  end
8
9
 
10
+ def find_all(account_id)
11
+ response = request(:find, account_id: account_id)
12
+ Array(response[:model]).map { |item| Subscription.new(item) }
13
+ end
14
+
9
15
  def create(attributes)
10
16
  response = request(:create, attributes)
11
17
  Subscription.new(response[:model])
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
3
  module Namespaces
3
4
  class Tokens < Base
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module CloudPayments
2
- VERSION = '0.0.2'
3
+ VERSION = '1.0.1'
3
4
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require 'openssl'
3
+ require 'base64'
4
+
5
+ module CloudPayments
6
+ class Webhooks
7
+ class HMACError < StandardError; end
8
+
9
+ attr_reader :config
10
+
11
+ def initialize(config = nil)
12
+ @config = config || CloudPayments.config
13
+ @digest = OpenSSL::Digest.new('sha256')
14
+ @serializer = Client::Serializer::Base.new(config)
15
+ end
16
+
17
+ def data_valid?(data, hmac)
18
+ Base64.decode64(hmac) == OpenSSL::HMAC.digest(@digest, config.secret_key, data)
19
+ end
20
+
21
+ def validate_data!(data, hmac)
22
+ raise HMACError unless data_valid?(data, hmac)
23
+ true
24
+ end
25
+
26
+ def kassa_receipt(data)
27
+ OnKassaReceipt.new(@serializer.load(data))
28
+ end
29
+
30
+ def on_recurrent(data)
31
+ OnRecurrent.new(@serializer.load(data))
32
+ end
33
+
34
+ def on_pay(data)
35
+ OnPay.new(@serializer.load(data))
36
+ end
37
+
38
+ def on_fail(data)
39
+ OnFail.new(@serializer.load(data))
40
+ end
41
+ end
42
+ end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe CloudPayments::Client::Response do
4
5
  let(:status){ 200 }
5
- let(:body){ '{"Model":{"Id":123,"CurrencyCode":"RUB","Amount":120},"Success":true}' }
6
+ let(:body){ '{"Model":{"Id":123,"CurrencyCode":"RUB","Amount":120},"Success":true}'.dup.force_encoding('CP1251').freeze }
6
7
  let(:headers){ { 'content-type' => 'application/json' } }
7
8
 
8
9
  subject{ CloudPayments::Client::Response.new(status, body, headers) }
@@ -13,6 +14,7 @@ describe CloudPayments::Client::Response do
13
14
  context 'wnen content type does not match /json/' do
14
15
  let(:headers){ { 'content-type' => 'text/plain' } }
15
16
  specify{ expect(subject.body).to eq(body) }
17
+ specify{ expect(subject.body.encoding.name).to eq('UTF-8') }
16
18
  end
17
19
  end
18
20
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'spec_helper'
2
3
 
3
4
  describe CloudPayments::Client::Serializer::MultiJson do
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # frozen_string_literal: true
4
+ require 'spec_helper'
5
+
6
+ describe CloudPayments::Order do
7
+ subject{ described_class.new(attributes) }
8
+
9
+ let(:attributes) do
10
+ {
11
+ id: 'f2K8LV6reGE9WBFn',
12
+ number: 61,
13
+ amount: 10.0,
14
+ currency: 'RUB',
15
+ currency_code: 0,
16
+ email: 'client@test.local',
17
+ description: 'Оплата на сайте example.com',
18
+ require_confirmation: true,
19
+ url:'https://orders.cloudpayments.ru/d/f2K8LV6reGE9WBFn'
20
+ }
21
+ end
22
+
23
+ describe 'properties' do
24
+ specify{ expect(subject.id).to eq('f2K8LV6reGE9WBFn') }
25
+ specify{ expect(subject.number).to eq(61) }
26
+ specify{ expect(subject.amount).to eq(10.0) }
27
+ specify{ expect(subject.currency).to eq('RUB') }
28
+ specify{ expect(subject.currency_code).to eq(0) }
29
+ specify{ expect(subject.email).to eq('client@test.local') }
30
+ specify{ expect(subject.description).to eq('Оплата на сайте example.com') }
31
+ specify{ expect(subject.require_confirmation).to eq(true) }
32
+ specify{ expect(subject.url).to eq('https://orders.cloudpayments.ru/d/f2K8LV6reGE9WBFn') }
33
+
34
+ it_behaves_like :raise_without_attribute, :id
35
+ it_behaves_like :raise_without_attribute, :number
36
+ it_behaves_like :raise_without_attribute, :amount
37
+ it_behaves_like :raise_without_attribute, :currency
38
+ it_behaves_like :raise_without_attribute, :currency_code
39
+ it_behaves_like :raise_without_attribute, :description
40
+ it_behaves_like :raise_without_attribute, :require_confirmation
41
+ it_behaves_like :raise_without_attribute, :url
42
+
43
+ it_behaves_like :not_raise_without_attribute, :email
44
+ end
45
+
46
+ describe 'transformations' do
47
+ context 'amount from string' do
48
+ before { attributes[:amount] = '293.42' }
49
+ specify{ expect(subject.amount).to eql(293.42) }
50
+ end
51
+
52
+ context 'require_confirmation from "1"' do
53
+ before { attributes[:require_confirmation] = '1' }
54
+ specify{ expect(subject.require_confirmation).to eql(true) }
55
+ end
56
+
57
+ context 'require_confirmation from "0"' do
58
+ before { attributes[:require_confirmation] = '0' }
59
+ specify{ expect(subject.require_confirmation).to eql(false) }
60
+ end
61
+ end
62
+ end