cloud_payments 0.0.2 → 1.0.1

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