prodamus 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '08f4d1e8a6e80f4d32aca52776e9e798ade8acc9def89e98e957a32b65414522'
4
+ data.tar.gz: b0d257bf1f9904ead2472a6337ed5adb7fa6a64e7a7e4d837eb4a3764245827e
5
+ SHA512:
6
+ metadata.gz: 5d5215fca8b35e60c4937f6e6f9c94fc45f96ba769e7f3203fc737b25d2f1e71a6cf5c6f912b0b8001ab97a5c23eb04a112b8d4fb120d43967228c9b27774833
7
+ data.tar.gz: 73fb2ea1c256fcb6b5aba066ac0e25da371d48086b1ddeae67a88a0e2e34687d73a35820871bef5ce10633f8bb724996832249e3d370afd89af4866179bd2b9b
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Purchase integration with https://prodamus.ru/
2
+
3
+ ## Client library for ruby apps
4
+
5
+ Prodamus docs https://help.prodamus.ru/
6
+
7
+ # Example usage:
8
+
9
+ * configure connection
10
+
11
+ Prodamus.config do |conf|
12
+ conf.main_payment_form_url = <payment form provided by prodamus>
13
+ conf.secret_key = <key from main form settings>
14
+ end
15
+
16
+ or
17
+
18
+ Prodamus.main_payment_form_url = <payment form provided by prodamus>
19
+ Prodamus.secret_key = <key from main form settings>
20
+
21
+ * configure request wich be sent for get purchase link
22
+
23
+ Prodamus.link_config do |conf|
24
+ conf.order_id = 12
25
+ conf.currency = 'rub'
26
+ ...
27
+ end
28
+ To see more parameters https://help.prodamus.ru/payform/integracii/rest-api/instrukcii-dlya-samostoyatelnaya-integracii-servisov#obshie-neobyazatelnye-parametry
29
+
30
+ * add a product or products to be purchased (must have at least one)
31
+
32
+ Prodamus.link_config.add_product(
33
+ name: <name>, price: <price>,
34
+ quantity: 1, sku: <id in your system>
35
+ )
36
+
37
+ Description product params https://help.prodamus.ru/payform/integracii/rest-api/instrukcii-dlya-samostoyatelnaya-integracii-servisov#parametry-massiva-products
38
+
39
+ * set link expired datetime
40
+
41
+ Prodamus.link_config.form_access_duration = 1.hour
42
+
43
+ * get payment link
44
+
45
+ Prodamus.link
46
+
47
+ ## Submit callback
48
+
49
+ Your callback from Prodamus server can be look like this:
50
+
51
+ data = {
52
+ date: '2022-12-08T10:42:10+03:00',
53
+ order_id: <id in prodamus system>,
54
+ order_num: <id in your system>,
55
+ domain: <your main form>,
56
+ sum: '770.00',
57
+ currency: 'rub',
58
+ customer_phone: '+78005553535',
59
+ customer_extra: '',
60
+ payment_type: 'Оплата картой, выпущенной в РФ',
61
+ commission: '100',
62
+ commission_sum: '770.00',
63
+ attempt: '2',
64
+ callbackType: 'json',
65
+ link_expired: '2022-12-08 11:38',
66
+ products: [
67
+ {
68
+ name: <product name>,
69
+ price: '770.00',
70
+ quantity: '1',
71
+ sum: '770.00'
72
+ }
73
+ ],
74
+ payment_status: payment_status,
75
+ payment_status_description: 'Успешная оплата',
76
+ payment_init: 'manual',
77
+ submit: {
78
+ date: '2022-12-08T10:42:10+03:00',
79
+ order_id: <id in prodamus system>,
80
+ order_num: <id in your system>,
81
+ domain: <your main form>,
82
+ sum: '770.00',
83
+ currency: 'rub',
84
+ customer_phone: '+78005553535',
85
+ customer_extra: '',
86
+ payment_type: 'Оплата картой, выпущенной в РФ',
87
+ commission: '100',
88
+ commission_sum: '770.00',
89
+ attempt: '2',
90
+ callbackType: 'json',
91
+ link_expired: '2022-12-08 11:38',
92
+ products: [{ name: <product name>, price: '770.00', quantity: '1', sum: '770.00' }],
93
+ payment_status: 'success',
94
+ payment_status_description: 'Успешная оплата',
95
+ payment_init: 'manual'
96
+ }
97
+ }
98
+
99
+ Need get signature from header 'Sign' and verify this data:
100
+
101
+ sign = request.headers['Sign']
102
+ Prodamus.verify(data[:submit], sign)
103
+
104
+ This method returns 'true' or 'false'.
105
+
106
+ You must perform some actions in your system to complete purchase if this method returns'true'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../prodamus'
4
+
5
+ module Prodamus
6
+ # Some product to be sent to prodamus
7
+ class Product
8
+ attr_reader :name, :sku, :quantity, :price
9
+
10
+ def initialize(name:, price:, quantity: 1, sku: nil)
11
+ @sku = sku.to_s
12
+ @name = name
13
+ @price = price.to_s
14
+ @quantity = quantity.to_s
15
+ end
16
+ end
17
+ end
data/lib/helper.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prodamus
4
+ NON_INSTALLMENTS_PAYMENT_METHODS = %w[
5
+ AC ACkz ACkztjp ACf ACeuruk ACusduk ACEURNMBX
6
+ ACUSDNMBX SBP PC QW GP sbol invoice
7
+ ].freeze
8
+
9
+ INSTALLMENTS_PAYMENT_METHODS = %w[
10
+ installment installment_5_21 installment_6_28
11
+ installment_10_28 installment_12_28 installment_0_0_3
12
+ installment_0_0_4 installment_0_0_6 installment_0_0_10
13
+ installment_0_0_12 installment_0_0_24 credit
14
+ vsegdada_installment_0_0_4 vsegdada_installment_0_0_6 vsegdada_installment_0_0_10
15
+ vsegdada_installment_0_0_12 vsegdada_installment_0_0_24
16
+ ].freeze
17
+
18
+ ALL_AVAIBLE_PAYMENT_METHODS = NON_INSTALLMENTS_PAYMENT_METHODS + INSTALLMENTS_PAYMENT_METHODS
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prodamus
4
+ VERSION = '1.0.0'
5
+ end
data/lib/prodamus.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require_relative 'helper'
5
+ require_relative 'entities/product'
6
+ require_relative 'services/verifier'
7
+ require_relative 'services/link_config'
8
+
9
+ # Main settings
10
+ module Prodamus
11
+ class << self
12
+ # payment form provided by prodamus
13
+ attr_accessor :main_payment_form_url
14
+
15
+ # key from main form settings
16
+ attr_accessor :secret_key
17
+
18
+ def config
19
+ yield self if block_given?
20
+ end
21
+
22
+ # verify signatured data
23
+ def verify(data, sign)
24
+ verifier(data).verify(sign)
25
+ end
26
+
27
+ # get link to payment form
28
+ def link
29
+ response = Faraday.new(
30
+ url: @main_payment_form_url,
31
+ params: @link_config.format_result,
32
+ headers: { 'Content-Type' => 'text/plain', 'charset' => 'utf-8' },
33
+ request: { timeout: 5 }
34
+ ).get
35
+
36
+ raise response.body.force_encoding('utf-8') if response.status != 200
37
+
38
+ response.body
39
+ end
40
+
41
+ # configure the payload to receive the payment form
42
+ def link_config(&block)
43
+ @link_config ||= LinkConfig.new(&block)
44
+ end
45
+
46
+ def verifier(data, algorithm = 'sha256')
47
+ raise 'Missing secret_key.' if @secret_key.nil?
48
+
49
+ @verifier ||= Verifier.new(data, @secret_key, algorithm)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/time_with_zone'
5
+ require_relative '../prodamus'
6
+
7
+ # Main settings
8
+ module Prodamus
9
+ # Configure request wich be sent for get purchase link
10
+ class LinkConfig
11
+ DEFAULT_PAYMENTS_LIMIT = 1
12
+
13
+ # rubocop:disable Naming/MethodName
14
+ attr_accessor :order_id, :sys, :customer_phone, :customer_email,
15
+ :customer_extra, :payment_method,
16
+ :urlReturn, :urlSuccess, :urlNotification,
17
+ :currency, :payments_limit
18
+ # rubocop:enable Naming/MethodName
19
+
20
+ attr_writer :form_access_duration, :timezone
21
+ attr_reader :products, :available_payment_methods
22
+
23
+ def initialize
24
+ @products = []
25
+ yield self if block_given?
26
+ end
27
+
28
+ # Transform payload to be sent
29
+ # rubocop:disable Metrics/MethodLength
30
+ def format_result
31
+ raise 'Product must be exists' if @products.empty?
32
+
33
+ result = {
34
+ 'do' => 'link',
35
+ 'callbackType' => 'json',
36
+ 'sys' => @sys,
37
+ 'order_id' => @order_id,
38
+ 'customer_phone' => @customer_phone,
39
+ 'customer_email' => @customer_email,
40
+ 'customer_extra' => @customer_extra,
41
+ 'payment_method' => @payment_method,
42
+ 'available_payment_methods' => (@available_payment_methods || NON_INSTALLMENTS_PAYMENT_METHODS).join('|'),
43
+ 'urlReturn' => @urlReturn,
44
+ 'urlSuccess' => @urlSuccess,
45
+ 'urlNotification' => @urlNotification,
46
+ 'installments_disabled' => (@installments_disabled || 1).to_s,
47
+ 'currency' => @currency,
48
+ 'payments_limit' => (@payments_limit || DEFAULT_PAYMENTS_LIMIT).to_s,
49
+ 'link_expired' => link_expired
50
+ }.compact!
51
+
52
+ @products.each_with_index do |product, index|
53
+ result.merge!({
54
+ "products[#{index}][name]" => product.name,
55
+ "products[#{index}][sku]" => product.sku,
56
+ "products[#{index}][price]" => product.price,
57
+ "products[#{index}][quantity]" => product.quantity
58
+ })
59
+ end
60
+
61
+ result
62
+ end
63
+ # rubocop:enable Metrics/MethodLength
64
+
65
+ def add_product(name:, price:, quantity: 1, sku: nil)
66
+ @products << Product.new(name: name, price: price, quantity: quantity, sku: sku)
67
+ end
68
+
69
+ def add_available_payment_methods(*payment_methods)
70
+ payment_methods.flatten!
71
+
72
+ raise 'payment_method is already defined' if @payment
73
+ raise ArgumentError, 'Not avaible payment methods.' unless (payment_methods - ALL_AVAIBLE_PAYMENT_METHODS).empty?
74
+
75
+ if @installments_disabled&.positive? && !(payment_methods - NON_INSTALLMENTS_PAYMENT_METHODS).empty?
76
+ raise ArgumentError, 'Installments payment methods is not avaible when installments disabled.'
77
+ end
78
+
79
+ @available_payment_methods = [] if @available_payment_methods.nil?
80
+ @available_payment_methods += payment_methods
81
+ end
82
+
83
+ def installments_disabled=(disabled)
84
+ @installments_disabled = disabled ? 1 : 0
85
+ end
86
+
87
+ def installments_disabled
88
+ !@installments_disabled.zero?
89
+ end
90
+
91
+ def link_expired
92
+ tz = ActiveSupport::TimeZone.new(timezone)
93
+ date = tz.now + form_access_duration.to_i.seconds
94
+ date.strftime('%Y-%m-%d %H:%M')
95
+ end
96
+
97
+ def form_access_duration
98
+ @form_access_duration || 1.day
99
+ end
100
+
101
+ def timezone
102
+ @timezone || 'Moscow'
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../prodamus'
4
+
5
+ module Prodamus
6
+ # HMAC encoding and verify signature
7
+ class Verifier
8
+ attr_accessor :data, :key, :algorithm
9
+
10
+ def initialize(data, key, algorithm = 'sha256')
11
+ @data = data
12
+ @key = key
13
+ @algorithm = algorithm
14
+ end
15
+
16
+ def verify(sign)
17
+ encoded_data = encode
18
+ encoded_data && (encoded_data == sign)
19
+ end
20
+
21
+ def encode
22
+ data = sort(@data).to_json
23
+ digest = OpenSSL::Digest.new(@algorithm)
24
+
25
+ OpenSSL::HMAC.hexdigest(digest, @key, data)
26
+ rescue NoMethodError
27
+ raise ArgumentError, 'Expected a Hash with array of hashes.'
28
+ end
29
+
30
+ private
31
+
32
+ def sort(data)
33
+ data.sort.to_h.transform_values do |value|
34
+ value.is_a?(Array) ? value.map { |product| product.sort.to_h.transform_values(&:to_s) } : value.to_s
35
+ end
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,207 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: prodamus
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nikita Kondrashov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-03-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '7.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '7.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.6'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.47'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.47'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop-performance
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.16'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.16'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.19'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.19'
139
+ - !ruby/object:Gem::Dependency
140
+ name: vcr
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '6.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '6.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.14'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.14'
167
+ description: This gem allows to integrate Prodamus purchases service in your Rails
168
+ app or simple Ruby app.
169
+ email:
170
+ - hasenberg41@mail.ru
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files:
174
+ - README.md
175
+ files:
176
+ - README.md
177
+ - lib/entities/product.rb
178
+ - lib/helper.rb
179
+ - lib/prodamus.rb
180
+ - lib/prodamus/version.rb
181
+ - lib/services/link_config.rb
182
+ - lib/services/verifier.rb
183
+ homepage: https://github.com/hasenberg41/prodamus
184
+ licenses:
185
+ - MIT
186
+ metadata:
187
+ rubygems_mfa_required: 'true'
188
+ post_install_message:
189
+ rdoc_options: []
190
+ require_paths:
191
+ - lib
192
+ required_ruby_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: 2.7.0
197
+ required_rubygems_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ requirements: []
203
+ rubygems_version: 3.4.7
204
+ signing_key:
205
+ specification_version: 4
206
+ summary: Prodamus purchases integration for Ruby on Rails
207
+ test_files: []