epaybg 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d760a58e9e281c33db44a196e128081008e3396e
4
- data.tar.gz: 7413851d1401e57b921af07da2e34d0430d70434
3
+ metadata.gz: cee407808cf74a63c73afee331e1b67bc6f96ebd
4
+ data.tar.gz: d02597a63bfb46b455bcd96aba99e0b9aac656f8
5
5
  SHA512:
6
- metadata.gz: df47f5d8d0ea4a4aa6ca968b876f5095b9d0577dce9d491844a5ade81e0f2133ad3627ed46ae22e40d913d1fca7c1c97c670c58f5d5de073b5ed62624571f09b
7
- data.tar.gz: f1ab7d15cd79bd089e99fba3056e85c72aded6bdc654d436739b6b42c2efc945a6f5bf62b0a0515da8e0c428e43db4b7d36e6612e6b5e4211539cda508097059
6
+ metadata.gz: ff4ee0df4a72c92f381e6d8063969c5fd08ccb689e646f515545d8172826def364865affdad77f8c50d6ca22612a055d667f12732f51ac210f90811c08124691
7
+ data.tar.gz: 88600f123e37752b2a6aab735da4dbd671cf12b5c54612d3d91f009e3b64c181ce07dbd1cfddf7929fa3267361c3aadfd7a79ba51362c63543a9b0eaf316c028
@@ -1,6 +1,3 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - jruby-19mode # JRuby in 1.9 mode
5
- - rbx-19mode
6
3
  - ruby-head
data/README.md CHANGED
@@ -21,7 +21,7 @@ Or install it yourself as:
21
21
 
22
22
  ## Configuration
23
23
 
24
- Create the config file config/epaybg.yml with the following contents:
24
+ Create the config file `config/epaybg.yml` with the following contents:
25
25
 
26
26
  ```yml
27
27
  production:
@@ -37,7 +37,7 @@ test:
37
37
  url_idn: "https://demo.epay.bg/ezp/reg_bill.cgi"
38
38
  ```
39
39
 
40
- Set the mode to production in your config/environments/production.rb file:
40
+ Set the mode to production in your `config/environments/production.rb` file:
41
41
 
42
42
  ```ruby
43
43
  # Add to bottom of the file
@@ -45,10 +45,10 @@ Epaybg.mode = :production
45
45
  ```
46
46
 
47
47
  ## Usage
48
- Handle a response callback from epay:
48
+ Handle a response callback from EpayBG:
49
49
 
50
50
  ```ruby
51
- response = Epaybg::Response.new(params[:encoded], params[:checksum])
51
+ response = Epaybg::Response.new params[:encoded], params[:checksum]
52
52
  response.valid?
53
53
  # => true
54
54
 
@@ -57,6 +57,7 @@ response.status
57
57
  ```
58
58
 
59
59
  Respond to their callback:
60
+
60
61
  ```ruby
61
62
  response = Epaybg::Response.new(params[:encoded], params[:checksum])
62
63
 
@@ -67,6 +68,174 @@ response.response_for(:ok)
67
68
  => "INVOICE=f5b1eaf:STATUS=PAID"
68
69
  ```
69
70
 
71
+ ## Recurring payments
72
+
73
+ There is no testing environment. You're on your own.
74
+
75
+ Your best bet is to contact EpayBG and ask for the technical specification because it can not be
76
+ found on their site.
77
+
78
+ ### Workflow
79
+
80
+ If your application has a subscription feature, you may consider implementing recurrent payments.
81
+ EpayBG has a rather strange way of doing this.
82
+
83
+ Here are the steps in their recurring payment cycle.
84
+
85
+ - Your user subscribes or makes a purchase.
86
+ - You provide them with an unique 'subscription' number.
87
+ - The user registers this number with EpayBG.
88
+ - From now on, every month EpayBG will 'ask' your application
89
+ (on a TCP server provided by you, explained below), if the registered
90
+ subscription has any outstanding debts.
91
+ - Your application searches for a debt related to the number and returns an answer.
92
+ - If a debt is returned, the user will receive a notification from EpayBG that there
93
+ is a pending payment.
94
+ - The user pays this pending payment.
95
+ - On the TCP server you will then receive a payment notification, process it and return
96
+ a response to EpayBG.
97
+ - The next month you will get another 'debt request' for this number.
98
+
99
+ It is rather hard to find this information anywhere in the web. It's unclear even in the official
100
+ documentation that
101
+ EpayBG send to the developers.
102
+
103
+ ### Implementing a TCP server
104
+
105
+ In order to accept recurring payment you have to implement a TCP server.
106
+
107
+ Here is an example server for accepting incoming TCP requests.
108
+
109
+ ````ruby
110
+ require 'socket'
111
+
112
+ server = TCPServer.new 2000
113
+
114
+ loop do
115
+ # The code in this block has to be threadsafe
116
+ Thread.start(server.accept) do |client|
117
+ begin
118
+ # This line will read the incoming data stream until the tcp client on the other side sends a
119
+ # 'shutdown' message.
120
+ message = client.read
121
+
122
+ # Handle the payment.
123
+ rescue => e
124
+ # Handle an unexpected error
125
+ ensure
126
+ client.close
127
+ end
128
+ end
129
+ end
130
+ ````
131
+
132
+ ### Handling debt requests
133
+
134
+ The example will work with the TCP server implementation described above.
135
+ EpayBG sends a list of messages separated by new lines `\n`.
136
+ After that they stop sending data and now only wait for a response in the same TCP session.
137
+
138
+ #### An example request
139
+
140
+ ```
141
+ XTYPE=QBN
142
+ AID=700021
143
+ ACSID=0000900
144
+ BORIKAID=0000900
145
+ CLIENTID=67600000000000000
146
+ LANG=1
147
+ IDN=000000000001
148
+ TID=20111010103406700021592704
149
+ ```
150
+
151
+ Refer to the technical documentation for further details.
152
+
153
+ #### Handling the request
154
+
155
+ The gem provides a method which turns the message into a hash.
156
+
157
+ ````ruby
158
+ message = client.read
159
+ data = Epaybg::Recurring.parse_request_body(message)
160
+ request = Epaybg::Recurring::Debt::Request.new data
161
+ ````
162
+
163
+ After you do the processing of the request and find out that this number has a pending payment,
164
+ you have to build a response object.
165
+
166
+ This is done through a Epaybg::Recurring::Debt::Request object. It accepts a hash with parameters,
167
+ validates them and builds a response array.
168
+
169
+ ````ruby
170
+ # ...
171
+
172
+ subscription = Subscription.find_by_epay_number request.idn
173
+
174
+ response_params = {
175
+ xvalidto: (Time.now + 5.days), # Due date of the subscription
176
+ secondid: 45, # Custom id for this debt (Optional)
177
+ amount: 5000, # Debt in coins (bulgarian stotinki)
178
+ status: '00', # If the status is not '00' (debt found) the other fields will be ignored.
179
+ # Look up the documentation for the other status codes.
180
+ shortdesc: 'Debt description',
181
+ longdesc: 'Debt details' # Optional
182
+ }
183
+
184
+ response = Epaybg::Recurring::Debt::Response.new response_params
185
+ ````
186
+
187
+ Now that you have a response object, you can send back an answer in the current TCP session.
188
+
189
+ ````ruby
190
+ response.body_array.each do |element|
191
+ client.puts element.encode('cp1251') # EpayBG requires that responses are windows-1251 encoded
192
+ end
193
+ ````
194
+
195
+ We have notified EpayBG that this subscription has a pending payment. Now we wait for a payment
196
+ request.
197
+
198
+ ### Payments
199
+
200
+ After the user pays their debt through EpayBG's system, EpayBG will send a payment notification on
201
+ the same TCP server used for debt request processing.
202
+
203
+ This is an example payment request:
204
+
205
+ ```
206
+ XTYPE=QBC
207
+ AID=700021
208
+ ACSID=0000900
209
+ BORIKAID=0000900
210
+ CLIENTID=67600000000000000
211
+ IDN=000000000001
212
+ NEWAMOUNT=000000003000
213
+ AMOUNT=5000
214
+ TID=20111010103406700021592705
215
+ REF=592460592460
216
+ TDATE=20111010103409
217
+ ```
218
+
219
+ Different request types are identified by the `XTYPE` parameter. Refer to the technical
220
+ documentation for further details.
221
+
222
+ ````ruby
223
+ message = client.read
224
+ data = Epaybg::Recurring.parse_request_body(message)
225
+ epay_recurrent_payment = Epaybg::Recurring::Payment.new data
226
+
227
+ # Handle the payment
228
+
229
+ epay_recurrent_payment.respond_with(:ok) # This will generate a response for this session.
230
+
231
+ epay_recurrent_payment.response_array.each do |element|
232
+ client.puts element
233
+ end
234
+ ````
235
+
236
+ The payment is accepted and EpayBG has been notified that the payment has been
237
+ processed.
238
+
70
239
  ## Contributing
71
240
 
72
241
  1. Fork it
@@ -2,6 +2,7 @@ require 'epaybg/railtie' if defined?(Rails)
2
2
  require 'epaybg/transaction'
3
3
  require 'epaybg/response'
4
4
  require 'epaybg/version'
5
+ require 'epaybg/recurring'
5
6
 
6
7
  module Epaybg
7
8
  class << self
@@ -0,0 +1,17 @@
1
+ require 'epaybg/recurring/payment'
2
+ require 'epaybg/recurring/debt/request'
3
+ require 'epaybg/recurring/debt/response'
4
+
5
+
6
+ module Epaybg
7
+ module Recurring
8
+ def self.parse_request_body(body)
9
+ array = body.split(/\s/).reject(&:empty?).compact
10
+ array.inject({}) do |hash, element|
11
+ key, value = *element.strip.split('=')
12
+ hash[key.downcase] = value
13
+ hash
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Epaybg
2
+ module Recurring
3
+ module Debt
4
+ class Request
5
+ attr_accessor :xtype, :idn, :tid, :aid, :clientid
6
+
7
+ def initialize(params = {})
8
+ params.each do |k, v|
9
+ instance_variable_set("@#{k}", v)
10
+ end
11
+
12
+ yield self if block_given?
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,73 @@
1
+ module Epaybg
2
+ module Recurring
3
+ module Debt
4
+ class Response
5
+ attr_accessor :xvalidto, :secondid, :amount, :status, :shortdesc, :longdesc, :errors
6
+
7
+ XTYPE = 'RBN'
8
+ STATUSES = %w(00 62 14 80 96)
9
+
10
+ # Status codes for epay
11
+
12
+ # 00 върнато е задължение / debt returned
13
+ # 62 няма задължение. / no debts for this isd
14
+ # 14 невалиден номер(idn) / invalid idn
15
+ # 80 заявката временно не може да бъде изпълнена / timeout, server buisy
16
+ # 96 обща грешка / other errors
17
+
18
+ def initialize(params = {})
19
+ @errors = []
20
+
21
+ params.each do |k, v|
22
+ instance_variable_set("@#{k}", v)
23
+ end
24
+
25
+ yield self if block_given?
26
+ validate!
27
+ end
28
+
29
+ def validate!
30
+ [:xvalidto, :amount, :status, :shortdesc].each do |element|
31
+ @errors << "Attribute #{element} is required!" if send(element).blank?
32
+ end
33
+
34
+ @errors << "'xvalidto' should be a time type field!" unless xvalidto.kind_of?(Time)
35
+
36
+ {secondid: 15, shortdesc: 40, longdesc: 1800}.each do |k, v|
37
+ @errors << "Attribute #{k} is too long. Maximum length should be #{v}." if send(k).to_s.length > v
38
+ end
39
+
40
+ @errors << "Invalid value #{status} for status" unless STATUSES.include?(status)
41
+ end
42
+
43
+ def valid?
44
+ @errors.empty?
45
+ end
46
+
47
+ def longdesc
48
+ return nil unless @longdesc
49
+
50
+ @longdesc.gsub("\n", "\\n")
51
+ end
52
+
53
+ def body_array
54
+ @body_array = [
55
+ "XTYPE=#{XTYPE}",
56
+ "XVALIDTO=#{xvalidto.strftime('%Y%m%d000000')}",
57
+ "AMOUNT=#{amount}",
58
+ "STATUS=#{status}",
59
+ "SHORTDESC=#{shortdesc}"
60
+ ]
61
+
62
+ @body_array << "SECONDID=#{secondid}" if secondid
63
+ @body_array << "LONGDESC=#{longdesc}" if longdesc
64
+ @body_array
65
+ end
66
+
67
+ def body
68
+ body_array.join("\n") + "\n"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,37 @@
1
+ module Epaybg
2
+ module Recurring
3
+ class Payment
4
+
5
+ RESPONSE_STATUS_CODES = {
6
+ ok: '00',
7
+ err: '96',
8
+ duplicate: '94'
9
+ }
10
+
11
+ attr_accessor :xtype, :idn, :tid, :amount, :secondid, :ref, :aid, :tdate, :clientid
12
+
13
+ def initialize(params = {})
14
+ params.each do |k, v|
15
+ instance_variable_set("@#{k}", v)
16
+ end
17
+
18
+ yield self if block_given?
19
+ end
20
+
21
+ def respond_with(symbol)
22
+ raise 'Invalid symbol' unless RESPONSE_STATUS_CODES.keys.include?(symbol)
23
+
24
+ code = RESPONSE_STATUS_CODES[symbol]
25
+ @response = "XTYPE=RBC\nSTATUS=#{code}\n"
26
+ end
27
+
28
+ def response_array
29
+ @response.split("\n")
30
+ end
31
+
32
+ def tdate
33
+ Date.parse(@tdate)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Epaybg
2
- VERSION = '0.3.1'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+
4
+ describe 'Recurring Payments' do
5
+ let(:request_string) {
6
+ "XTYPE=QBN\nAID=000100\nACSID=0000897\nBORIKAID=0000897\nCLIENTID=67600000000000000\nIDN=6460392\nTID=20160225080306000100705669\n"
7
+ }
8
+
9
+ let(:debt_params){
10
+ Epaybg::Recurring.parse_request_body(request_string)
11
+ }
12
+
13
+ before do
14
+ Epaybg.config = YAML.load_file('spec/test_config.yml')
15
+ end
16
+
17
+ describe 'Recurring module methods' do
18
+ it 'should parse a request string' do
19
+ expect(debt_params).to be_kind_of Hash
20
+ expect(debt_params['xtype']).to eq 'QBN'
21
+ expect(debt_params['idn']).to eq '6460392'
22
+ end
23
+ end
24
+
25
+ describe 'Recurrent payment debt request' do
26
+ it 'should create a valid object' do
27
+ request = Epaybg::Recurring::Debt::Request.new debt_params
28
+
29
+ expect(request.xtype).not_to be_nil
30
+ expect(request.idn).not_to be_nil
31
+ expect(request.tid).not_to be_nil
32
+ expect(request.aid).not_to be_nil
33
+ expect(request.clientid).not_to be_nil
34
+ end
35
+ end
36
+
37
+ describe 'Recurrent payment debt response' do
38
+ let(:valid_params){
39
+ {
40
+ xvalidto: ( Time.now + 5.days ),
41
+ secondid: 'sid25',
42
+ amount: 5000,
43
+ status: '00',
44
+ shortdesc: 'Some description',
45
+ longdesc: 'Some long description'
46
+ }
47
+ }
48
+
49
+ it 'create a valid object' do
50
+ response = Epaybg::Recurring::Debt::Response.new valid_params
51
+ expect(response).to be_valid
52
+ expect(response.body_array).not_to be_empty
53
+ end
54
+
55
+ describe 'Invalid objects' do
56
+ it 'should have the right elements' do
57
+ response = Epaybg::Recurring::Debt::Response.new {}
58
+ expect(response).not_to be_valid
59
+ end
60
+
61
+ it 'should not have too long messages' do
62
+ invalid_params = {
63
+ shortdesc: "a" * 60,
64
+ longdesc: "b" * 1801,
65
+ }
66
+
67
+ response = Epaybg::Recurring::Debt::Response.new(valid_params.merge(invalid_params))
68
+ expect(response).not_to be_valid
69
+ end
70
+
71
+ it 'should not accept invalid status codes' do
72
+ invalid_params = {
73
+ status: '1233'
74
+ }
75
+ response = Epaybg::Recurring::Debt::Response.new(valid_params.merge(invalid_params))
76
+ expect(response).not_to be_valid
77
+ end
78
+
79
+ end
80
+ end
81
+
82
+ describe 'Recurring payment object' do
83
+ let(:valid_params) {
84
+ {
85
+ xtype: 'RBN',
86
+ idn: '0000000001',
87
+ tid: '423423423423423423',
88
+ amount: 5000,
89
+ secondid: 'si45',
90
+ ref: '234234234234234',
91
+ aid: '000100',
92
+ tdate: '20160224173336',
93
+ clientid: '07728424664'
94
+ }
95
+ }
96
+
97
+ it 'should create a valid object' do
98
+ payment = Epaybg::Recurring::Payment.new valid_params
99
+
100
+ expect(payment.tdate).to be_kind_of(Date)
101
+
102
+ expect{ payment.respond_with(:ok) }.not_to raise_error
103
+ expect{ payment.respond_with(:err) }.not_to raise_error
104
+ expect{ payment.respond_with(:duplicate) }.not_to raise_error
105
+
106
+ expect{ payment.respond_with(:something_else) }.to raise_error('Invalid symbol')
107
+
108
+ expect(payment.respond_with(:ok)).not_to be_nil
109
+ end
110
+ end
111
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epaybg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - gmitrev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-17 00:00:00.000000000 Z
11
+ date: 2016-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -69,12 +69,16 @@ files:
69
69
  - epaybg.gemspec
70
70
  - lib/epaybg.rb
71
71
  - lib/epaybg/railtie.rb
72
+ - lib/epaybg/recurring.rb
73
+ - lib/epaybg/recurring/debt/request.rb
74
+ - lib/epaybg/recurring/debt/response.rb
75
+ - lib/epaybg/recurring/payment.rb
72
76
  - lib/epaybg/response.rb
73
77
  - lib/epaybg/transaction.rb
74
78
  - lib/epaybg/version.rb
75
79
  - lib/epaybg/view_helpers.rb
76
- - log/test.log
77
80
  - spec/epaybg/transaction_spec.rb
81
+ - spec/recurring/recurring_payments_spec.rb
78
82
  - spec/spec_helper.rb
79
83
  - spec/test_config.yml
80
84
  homepage: http://github.com/gmitrev/epaybg
@@ -96,12 +100,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
100
  version: '0'
97
101
  requirements: []
98
102
  rubyforge_project:
99
- rubygems_version: 2.4.5.1
103
+ rubygems_version: 2.5.1
100
104
  signing_key:
101
105
  specification_version: 4
102
106
  summary: Epaybg provides integration with the epay.bg payment services. It supports
103
107
  payments through epay.bg, credit cards and in EasyPay offices.
104
108
  test_files:
105
109
  - spec/epaybg/transaction_spec.rb
110
+ - spec/recurring/recurring_payments_spec.rb
106
111
  - spec/spec_helper.rb
107
112
  - spec/test_config.yml