epaybg 0.3.1 → 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 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