payu_payments 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1 @@
1
+ payu_payments
@@ -0,0 +1 @@
1
+ 1.9.3-p194
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in payu_payments.gemspec
4
+ gemspec
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/payu_payments/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 CristianV
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,178 @@
1
+ # PayuPayments
2
+
3
+ A wrapper for the [PayuLatam.com](http://www.payulatam.com/) payment gateway, it include the
4
+ managment of clients, plans, subscriptions and creditCards.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'payu_payments'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install payu_payments
19
+
20
+ ## Usage
21
+
22
+ All the clases work as a simple CRUD objects, the models also have a
23
+ basic attributes validations.
24
+
25
+ ## Configuration
26
+
27
+ To use the gem you need to set the keys available on your account
28
+
29
+
30
+ ```ruby
31
+ PayuPayments.config do |config|
32
+ config.api_key = "xxxxxxxxxxxxxx"
33
+ config.api_login = "xxxxxxx"
34
+ config.merchant_id = "123456"
35
+ config.account = "7890"
36
+ config.mode = "development" # or production to point to production end-point
37
+ end
38
+
39
+ ```
40
+
41
+
42
+ ### Clients
43
+
44
+ To perform transactions with the API an entity representing the
45
+ custormer needs to be created, this entity is de Client and can be
46
+ created and updated using the PayuPayments::Client class.
47
+
48
+ ```ruby
49
+ @client = PayuPayments::Client.new(:fullName => "john Doe", :email => "johndoe@gmail.com")
50
+
51
+ # or
52
+
53
+ @client = PayuPayments::Client.new
54
+ @client.fullName = "john Doe"
55
+ @client.email = "johndoe@gmail.com"
56
+
57
+ @client.save
58
+ ```
59
+ You can also retrieve clients from the API if you know it's id
60
+
61
+ ```ruby
62
+
63
+ @client = PayuPayments::Client.find(123)
64
+ @client.fullName = "New name"
65
+ @client.save
66
+
67
+ ```
68
+
69
+
70
+ ### Credit Cards
71
+
72
+ You can store a tokenized credit card on the gateway tekenization
73
+ service by adding it to a client, in that way the credit card will be
74
+ stored directly in the PCI compliant gateway and can be used to charge the
75
+ client in future transactiosn without requiring to fill the credit card
76
+ form again.
77
+
78
+ ```ruby
79
+ @client = PayuPayments::Client.find(123)
80
+
81
+ creditCard: {
82
+ name: "Sample User Name",
83
+ document: "1020304050",
84
+ number: "4242424242424242",
85
+ expMonth: "01",
86
+ expYear: "2020",
87
+ type: "VISA",
88
+ address: {
89
+ line1: "Address Name",
90
+ line2: "17 25",
91
+ line3: "Of 301",
92
+ postalCode: "00000",
93
+ city: "City Name",
94
+ state: "State Name",
95
+ country: "CO",
96
+ phone: "300300300"
97
+ }
98
+ }
99
+
100
+ credit_card = @client.add_credit_card(creditCard)
101
+
102
+ ### To list the credit cards from a client
103
+
104
+ @client.credit_cards
105
+ ```
106
+
107
+ ### Plans
108
+
109
+ To create a recurring payment model you need to create plans, plans
110
+ describe the different ways that you can sell your suscription based
111
+ products, here you describe the product how much you will charge for it an
112
+ how you want to charge for it
113
+
114
+ ```ruby
115
+ planParams = {
116
+ plan: {
117
+ planCode: "sample-plan-code-001",
118
+ description: "Sample Plan 001",
119
+ accountId: "1",
120
+ intervalCount: "1",
121
+ interval: "MONTH",
122
+ maxPaymentsAllowed: "12",
123
+ maxPaymentAttempts: "3",
124
+ paymentAttemptsDelay: "1",
125
+ maxPendingPayments: "0",
126
+ trialDays: "30",
127
+ additionalValues: {
128
+ additionalValue: [{ name: "PLAN_VALUE",
129
+ value: "20000",
130
+ currency: "COP"
131
+ },
132
+ {
133
+ name: "PLAN_TAX",
134
+ value: "1600",
135
+ currency: "COP"
136
+ }]
137
+ }
138
+ }
139
+ }
140
+ plan = PayuPayments::Plan.new(planParams)
141
+ plan.save
142
+
143
+ # or
144
+ plan = PayuPayments::Plan.create(planParams)
145
+ ```
146
+ ### Subscriptions
147
+
148
+ Subscription will glue all together to create a subscription based
149
+ products, with a suscription you will bind a
150
+ client with a tokenized credit card with a plan,
151
+
152
+ ```ruby
153
+ PayuPayments::Subscription.create(subscription: {
154
+ quantity: "1",
155
+ installments: "1",
156
+ trialDays: "15",
157
+ customer: {
158
+ id: @client.id,
159
+ creditCards: {
160
+ creditCard: { "token": credit_card.token }
161
+ }
162
+ },
163
+ plan: { "planCode": plan.planCode }
164
+ })
165
+
166
+ # to check a client susbscriptions
167
+ @client.subscriptions
168
+
169
+ ```
170
+
171
+
172
+ ## Contributing
173
+
174
+ 1. Fork it ( http://github.com/<my-github-username>/payu_payments/fork )
175
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
176
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
177
+ 4. Push to the branch (`git push origin my-new-feature`)
178
+ 5. Create new Pull Request
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env rake
2
+ $LOAD_PATH << File.expand_path("../lib", __FILE__)
3
+ Dir.chdir(File.expand_path("..", __FILE__))
4
+
5
+ require 'payu_payments'
6
+ require "bundler/gem_tasks"
7
+ require 'rspec/core/rake_task'
8
+
9
+ RSpec::Core::RakeTask.new('spec')
10
+
11
+ desc "Payupayments console"
12
+ task :console do
13
+ require 'irb'
14
+ require 'irb/completion'
15
+ require 'payu_payments'
16
+ require 'pry'
17
+
18
+ ARGV.clear
19
+ IRB.start
20
+ end
21
+
22
+
23
+ # If you want to make this the default task
24
+ task :default => :spec
@@ -0,0 +1,34 @@
1
+ require "payu_payments/version"
2
+
3
+ %w{ model caller client plan credit_card subscription recurring_bill_item }.each do |f|
4
+ require "payu_payments/#{f}"
5
+ end
6
+
7
+
8
+ module PayuPayments
9
+
10
+ def self.config(&block)
11
+ @configuration ||= Configuration.new
12
+
13
+ unless block.nil?
14
+ yield @configuration
15
+ else
16
+ @configuration.config
17
+ end
18
+ end
19
+
20
+ class Configuration
21
+ attr_accessor :api_key, :api_login, :merchant_id, :account, :mode, :show_log
22
+ def initialize
23
+ @show_log = false
24
+ end
25
+ def config
26
+ {:api_key => @api_key,
27
+ :api_login => @api_login,
28
+ :merchant_id => @merchant_id,
29
+ :account => @account,
30
+ :show_log => @show_log,
31
+ :mode => @mode}
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ require 'httparty'
2
+ require "base64"
3
+ require 'ostruct'
4
+
5
+ module PayuPayments
6
+ class Caller
7
+ include ::HTTParty
8
+ include Model
9
+
10
+ API = "https://api.payulatam.com"
11
+ API_SANDBOX = "https://stg.api.payulatam.com"
12
+
13
+ attr_accessor :access, :base, :resource, :errors
14
+ format :json
15
+ alias :attr :base
16
+
17
+ def set_base_uri
18
+ self.class.base_uri(PayuPayments.config[:mode] == "production" ? API : API_SANDBOX)
19
+ end
20
+
21
+ def initialize(params={})
22
+ @access = PayuPayments.config
23
+ self.set_base_uri
24
+ self.class.debug_output $stdout if @access[:show_log]
25
+
26
+ @base = OpenStruct.new
27
+ base.marshal_load params
28
+ @errors = []
29
+ end
30
+
31
+ def http_call(type, url, params={})
32
+ if ["post", "put"].include? type
33
+ headers = { 'Accept' => "application/json",
34
+ 'Content-Type' => 'application/json; charset=UTF-8',
35
+ 'Authorization' => "Basic #{basic_auth}"}
36
+ resp = self.class.send(type, url, :body => params.to_json, :verify => (access[:mode] == "production"), :headers => headers)
37
+ else
38
+ headers = { 'Accept' => "application/json",
39
+ 'Authorization' => "Basic #{basic_auth}"}
40
+ resp = self.class.send(type, url, :query => params, :verify => (access[:mode] == "production"), :headers => headers)
41
+ end
42
+
43
+ respond_with = (resp == "" or resp.nil?) ? {} : resp.inject({ }) { |h, (k,v)| h[k.to_sym] = v; h }
44
+
45
+ if resp.code.to_s.match(/2\d\d/)
46
+ respond_with
47
+ else
48
+ {"type" => respond_with[:type], "description" => respond_with[:description]}
49
+ end
50
+ end
51
+
52
+
53
+ private
54
+
55
+ def basic_auth
56
+ Base64.encode64("#{access[:api_login]}:#{access[:api_key]}")
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ module PayuPayments
2
+ class Client < Caller
3
+
4
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
5
+ # Client attributes from Payu documentation
6
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
7
+ #
8
+ # id
9
+ # ullName
10
+ # emai
11
+ # creditCards
12
+ # subscriptions
13
+
14
+ def initialize(params={})
15
+ super
16
+ @resource = "customers"
17
+ end
18
+
19
+ def add_credit_card(params)
20
+ cc = CreditCard.new(params)
21
+ cc.attr.customerId = self.base.id
22
+ cc.save
23
+ end
24
+
25
+ def subscriptions
26
+ self.base.subscriptions.map do |sub|
27
+ subscription = Subscription.new
28
+ subscription.base.marshal_load(sub)
29
+ subscription
30
+ end
31
+ end
32
+
33
+ def credit_cards
34
+ self.base.creditCards.each.map do |sub|
35
+ cc = CreditCard.new
36
+ cc.base.marshal_load(sub)
37
+ cc
38
+ end
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,78 @@
1
+ module PayuPayments
2
+
3
+ class CreditCard < Caller
4
+
5
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
6
+ # Credit Card attributes from Payu documentation
7
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
8
+ #
9
+ # Token Longitud = [0,36] String Token asociado a una tarjeta de crédito.
10
+ # customerId Longitud = #[0,64] String Código de identificación del cliente al cual pertenece la tarjeta de crédito
11
+ # Number Longitud = [13,20] # Patrón = [0-9]*{13,20} String Número de la tarjeta de crédito
12
+ # expMonth Min = 1,#Máx = 12 Integer Mes de expiración de la tarjeta de crédito.
13
+ # expYear Min= 0,
14
+ # Max = 2999 Integer Año de expiración de la tarjeta de crédito.
15
+ # Si el valor es de dos dígitos corresponde al año comprendido entre 2000 (00) y 2099 (99).
16
+ # Si el valor es de más de dos dígitos se toma literal, siendo el año 2000 como mínimo posible.
17
+ # Type Enumeration Corresponde a la franquicia o tipo de tarjeta de crédito. Los posibles valores son:
18
+ # VISA, AMEX, DINERS, MASTERCARD, ELO, SHOPPING, NARANJA, CABAL y ARGENCARD.
19
+ # Name 255 String Nombre del tarjeta habiente.
20
+ # document Longitud = #[5, 30] String Número del documento de identificad del tarjeta habiente.
21
+ # address - Address Dirección de correspondencia del tarjeta habiente asociado con la tarjeta de crédito.
22
+ # issuerBank 255 String Nombre del banco emisor de la tarjeta de crédito.
23
+ #
24
+ # City Longitud = [0, 50] String Nombre de la ciudad.
25
+ # country Longitud = [2, 2] String Código ISO 3166 (2 letras – Alpha 2) del país de la dirección.
26
+ #Ver países soportados
27
+ # linea1 Longitud = [0, 100] String Primera línea de la dirección.
28
+ # linea2 Longitud = [0, 100] String Segunda línea de la dirección o número de la dirección.
29
+ # linea3 Longitud = [0, 100] String Tercera línea de la dirección o complemento de la dirección.
30
+ # phone Longitud = [0, 20] String Teléfono asociado a la dirección.
31
+ # postalCode Longitud = [0, 20] String Código postal de la dirección.
32
+ # State Longitud = [0, 40] String Nombre del estado de la dirección.
33
+
34
+ def initialize(params={})
35
+ super
36
+ @resource = "creditCards"
37
+ end
38
+
39
+ def save
40
+ if valid?
41
+ @url = new? ? "#{API_PATH}/customers/#{attr.customerId}/#{@resource}" : "#{API_PATH}/#{@resource}/#{base.id}"
42
+ super
43
+ else
44
+ false
45
+ end
46
+ end
47
+
48
+ def self.create(customer_id, params)
49
+ url = "#{API_PATH}/customers/#{attr.customerId}/#{@resource}"
50
+ self.base.marshal_load params
51
+ resp = http_call("post", url, attr.marshal_dump)
52
+ base.marshal_load resp
53
+ end
54
+
55
+ def destroy(customer_id, id)
56
+ customer_id = self.attr.customerId
57
+ id = self.attr.id
58
+ @url = "#{API_PATH}/customers/#{customer_id}/#{@resource}/#{id}}"
59
+ super
60
+ end
61
+
62
+ def valid?
63
+ self.validate
64
+ self.errors.count == 0
65
+ end
66
+
67
+ def validate
68
+ self.errors = []
69
+ [:customerId, :number, :expMonth, :expYear, :type, :document, :address, :name].each do |field|
70
+ validate_presence_of(field)
71
+ end
72
+
73
+ validate_lenght_of(:expYear, 4)
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,88 @@
1
+ module Model
2
+ API_PATH = "/payments-api/rest/v4.3"
3
+
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ def save
9
+ verb = new? ? "post" : "put"
10
+ @url ||= new? ? "#{API_PATH}/#{@resource}" : "#{API_PATH}/#{@resource}/#{base.id}"
11
+ resp = http_call(verb, @url, base.marshal_dump)
12
+
13
+ if resp.is_a?(Array)
14
+ error = {field: resp[0], message: resp[1]}
15
+ self.errors << error
16
+ false
17
+ else
18
+ base.marshal_load resp
19
+ true
20
+ end
21
+ end
22
+
23
+ def load
24
+ resp = http_call("get", "#{API_PATH}/#{@resource}/#{self.attr.id}")
25
+ base.marshal_load resp
26
+ end
27
+
28
+ def destroy
29
+ @id ||= self.attr.id
30
+ @url ||= "#{API_PATH}/#{@resource}/#{@id}"
31
+ resp = http_call("delete", @url)
32
+ base.marshal_load resp unless resp.is_a? Array
33
+ resp
34
+ end
35
+
36
+ def new?
37
+ base.id.nil?
38
+ end
39
+
40
+ def method_missing(method_name, *arguments, &block)
41
+ if base.marshal_dump.include?(method_name.to_s.strip.to_sym) || method_name.match(/.*=$/)
42
+ base.send(method_name.to_s.strip, *arguments, &block)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+
49
+ # xxxxxxxxxxxxxxxxx validations xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
50
+
51
+ def validate_presence_of(field)
52
+ if (self.attr.send(field.to_s).nil? or self.attr.send(field.to_s) == "")
53
+ error = {}
54
+ error[:field] = field
55
+ error[:message] = "#{field} can't be blank"
56
+ self.errors << error
57
+ end
58
+ end
59
+
60
+ def validate_lenght_of(field, lenght)
61
+ unless self.attr.send(field.to_s).nil?
62
+ if self.attr.send(field.to_s).length != lenght
63
+ error = {}
64
+ error[:field] = field
65
+ error[:message] = "lenght of #{field} should be #{lenght}"
66
+ self.errors << error
67
+ end
68
+ end
69
+ end
70
+
71
+ # Class Methods
72
+ module ClassMethods
73
+ def find(id)
74
+ resp = self.new
75
+ json = resp.http_call("get", "#{API_PATH}/#{resp.resource}/#{id}")
76
+
77
+ unless json["type"] == "NOT_FOUND"
78
+ self.new json
79
+ else
80
+ nil
81
+ end
82
+ end
83
+
84
+ def create(params)
85
+ http_call("post", "#{API_PATH}/#{@resource}", base.marshal_dump)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,72 @@
1
+ module PayuPayments
2
+ class Plan < Caller
3
+
4
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
5
+ # Plan attributes from Payu documentation
6
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
7
+ #
8
+ # id 36 String Identificador del plan en la plataforma de PayU.
9
+ # planCode 255 String Código único que el comercio le asigna al plan para su posterior identificación. Este código de plan no se puede volver a repetir aun cuando el plan sea borrado.
10
+ # description 255 String Descripción del plan.
11
+ # accountId Integer Identificador de la cuenta del comercio al cual se asocia el plan.
12
+ # interval 5 String Intervalo que define cada cuanto se realiza el cobro de la suscripción. Los valores posibles son:
13
+ # DAY, WEEK, MONTH y YEAR.
14
+ # intervalCount Integer Cantidad del intervalo que define cada cuanto se realiza el cobro de la suscripción.
15
+ # additionalValue.currency 3 String Código ISO 4217 de la moneda del valor asociado al plan.
16
+ # additionalValue.name 255 String Nombre del campo del valor asociado al plan. Los valores posibles son:
17
+ # PLAN_VALUE: valor total de plan.
18
+ # PLAN_TAX_VALUE: valor de los impuestos asociados al valor del plan.
19
+ # PLAN_TAX_RETURN_BASE: valor base para el retorno de impuestos asociados al plan.
20
+ # additionalValue.value Decimal Valor del plan, impuesto o base de retorno de acuerdo a plan.additionalValue.name.
21
+ # trialDays Integer Cantidad de días de prueba de la suscripción.
22
+ # maxPaymentAttempts Máx. 3 Integer Cantidad total de reintentos para cada pago rechazado de la suscripción.
23
+ # maxPaymentsAllowed Integer Cantidad total de pagos de la suscripción.
24
+ # maxPendingPayments Integer Cantidad máxima de pagos pendientes que puede tener una suscripción antes de ser cancelada.
25
+ # paymentAttemptsDelay Integer Cantidad de días de espera entre los reintentos de pago de la suscripción.
26
+
27
+ def initialize(params={})
28
+ super
29
+ @resource = "plans"
30
+ end
31
+
32
+ def save
33
+ verb = new? ? "post" : "put"
34
+ url = new? ? "#{API_PATH}/#{@resource}" : "#{API_PATH}/#{@resource}/#{base.planCode}"
35
+ not_allowed_for_update = [:maxPaymentsAllowed, :trialDays, :id, :intervalCount, :interval, :accountId]
36
+
37
+ params = base.marshal_dump
38
+ not_allowed_for_update.each {|x| params.delete(x) } unless new?
39
+ resp = http_call(verb, url, params)
40
+
41
+ base.marshal_load resp
42
+ end
43
+
44
+ def self.create_base_month_plan(name, value, custom_options={})
45
+ params = {
46
+ planCode: name,
47
+ description: "#{name} plan",
48
+ accountId: PayuPayments.config[:account],
49
+ intervalCount: 1,
50
+ interval: "MONTH",
51
+ maxPaymentsAllowed: 2000,
52
+ maxPaymentAttempts: 3,
53
+ paymentAttemptsDelay: 5,
54
+ maxPendingPayments: 0,
55
+ trialDays: 0,
56
+ additionalValues: [{
57
+ name: "PLAN_VALUE",
58
+ value: value,
59
+ currency: "COP"
60
+ }]
61
+ }
62
+ params.merge!(custom_options)
63
+ self.new params
64
+ end
65
+
66
+
67
+ def destroy
68
+ @id = self.attr.planCode
69
+ super
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,30 @@
1
+ module PayuPayments
2
+
3
+ class RecurringBillItem < Caller
4
+ def initialize(params={})
5
+ super
6
+ @resource = "recurringBillItems"
7
+ end
8
+
9
+ def save
10
+ verb = new? ? "post" : "put"
11
+ url = new? ? "#{API_PATH}/subscriptions/#{base.subscription_id}/#{@resource}" : "#{API_PATH}/#{@resource}/#{base.id}"
12
+ resp = http_call(verb, url, base.marshal_dump)
13
+ base.marshal_load resp
14
+ end
15
+
16
+ def create(params)
17
+ url = "#{API_PATH}/subscriptions/#{attr.subscription_id}/#{@resource}"
18
+ resp = http_call(verb, url, base.marshal_dump)
19
+ base.marshal_load resp
20
+ end
21
+
22
+ def get_recurring_bill_items
23
+ url = "#{API_PATH}/#{@resource}/params"
24
+ resp = http_call(verb, url, base.marshal_dump)
25
+ base.marshal_load resp
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,28 @@
1
+ module PayuPayments
2
+
3
+ class Subscription < Caller
4
+
5
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
6
+ # Subscription attributes from Payu documentation
7
+ # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
8
+ #
9
+ # id 36 String Identificador de la suscripción en la plataforma PayU.
10
+ # quantity - Integer Cantidad de planes a adquirir con la suscripción.
11
+ # installments - Integer Número de cuotas en las que se diferirá cada cobro de la suscripción.
12
+ # trialDays - Integer Días de prueba que téndra la suscripción sin generar cobros.
13
+ # customer - Customer Cliente asociado a la suscripción.
14
+ # customer.credidcards - CreditCard Tarjeta de crédito asociada al cliente.
15
+ # plan - Plan Plan asociado a la suscripción.
16
+
17
+ def initialize(params={})
18
+ super
19
+ @resource = "subscriptions"
20
+ end
21
+
22
+ def add_extra_charges(params={})
23
+ url = "#{API_PATH}/#{self.attr.id}/recurringBillItems"
24
+ http_call("put", url, params)
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,3 @@
1
+ module PayuPayments
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'payu_payments/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "payu_payments"
8
+ spec.version = PayuPayments::VERSION
9
+ spec.authors = ["CristianV"]
10
+ spec.email = ["cristian@kommit.co"]
11
+ spec.summary = %q{Payulatam payments gatewat API gem}
12
+ spec.description = %q{Payulatam payments gatewat API gem}
13
+ spec.homepage = "https://github.com/kommitters/payu_payments"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ # Dependency
22
+ spec.add_dependency "httparty"
23
+
24
+ # Development
25
+ spec.add_development_dependency "bundler", "~> 1.5"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ spec.add_development_dependency "guard-rspec"
29
+ spec.add_development_dependency "pry"
30
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ module PayuPayments
4
+
5
+ describe Caller do
6
+
7
+ it "Should set a base container to hold attributes" do
8
+ call = Caller.new
9
+ expect(call.base).to be_an_instance_of(OpenStruct)
10
+ end
11
+
12
+ it "should get access credentials from config" do
13
+ ::PayuPayments.config do |config|
14
+ config.api_key = "6u39nqhq8ftd0hlvnjfs66eh8c"
15
+ config.api_login = "11959c415b33d0c"
16
+ config.merchant_id = "500238"
17
+ config.account = "5009"
18
+ config.mode = "development"
19
+ end
20
+
21
+ call = Caller.new
22
+ expect(call.access[:api_key]).to eq("6u39nqhq8ftd0hlvnjfs66eh8c")
23
+ expect(call.access[:api_login]).to eq("11959c415b33d0c")
24
+ expect(call.access[:merchant_id]).to eq("500238")
25
+ expect(call.access[:account]).to eq("5009")
26
+ expect(call.access[:mode]).to eq("development")
27
+ end
28
+
29
+ it "Should construct a base object with the initial params" do
30
+ call = Caller.new(:name => "jhon", :last_name => "Doe", :number => 123)
31
+ expect(call.base.name).to eq("jhon")
32
+ expect(call.base.last_name).to eq("Doe")
33
+ expect(call.base.number).to eq(123)
34
+ end
35
+
36
+ context "validations" do
37
+
38
+ it "should be able to validate the lenght of a fiekd" do
39
+ call = Caller.new(:name => "jhon")
40
+ call.validate_lenght_of(:name, 5)
41
+ expect(call.errors.count).to eq(1)
42
+ expect(call.errors.first[:message]).to eq("lenght of name should be 5")
43
+ end
44
+
45
+ end
46
+
47
+
48
+
49
+ context "performing http calls" do
50
+ let (:resp) {a = {"valor1" => "hola"}; a.stub(:code => "200"); a}
51
+ before :each do
52
+ ::PayuPayments.config do |config|
53
+ config.api_key = "6u39nqhq8ftd0hlvnjfs66eh8c"
54
+ config.api_login = "11959c415b33d0c"
55
+ config.merchant_id = "500238"
56
+ config.account = "5009"
57
+ config.mode = "development"
58
+ end
59
+ end
60
+
61
+ it "should be able to perform POST calls" do
62
+ attr = {fullName: "john doe", email: "jhon@doe.com"}
63
+ call = Caller.new(attr)
64
+ call.stub(:basic_auth => "abc123")
65
+ headers = { 'Accept' => "application/json",
66
+ 'Content-Type' => 'application/json; charset=UTF-8',
67
+ 'Authorization' => "Basic abc123"}
68
+ Caller.should_receive(:send).with("post", "/someurl", :body => attr.to_json, :verify => false, :headers => headers).and_return(resp)
69
+ call.http_call("post", "/someurl", attr)
70
+ end
71
+
72
+ it "should be able to perform PUT calls" do
73
+ attr = {fullName: "john doe", email: "jhon@doe.com"}
74
+ call = Caller.new(attr)
75
+ call.stub(:basic_auth => "abc123")
76
+ headers = { 'Accept' => "application/json",
77
+ 'Content-Type' => 'application/json; charset=UTF-8',
78
+ 'Authorization' => "Basic abc123"}
79
+ Caller.should_receive(:send).with("put", "/someurl", :body => attr.to_json, :verify => false, :headers => headers).and_return(resp)
80
+ call.http_call("put", "/someurl", attr)
81
+ end
82
+
83
+
84
+ it "should be able to perform GET calls" do
85
+ attr = {fullName: "john doe", email: "jhon@doe.com"}
86
+ call = Caller.new(attr)
87
+ call.stub(:basic_auth => "abc123")
88
+ headers = { 'Accept' => "application/json",
89
+ 'Authorization' => "Basic abc123"}
90
+ Caller.should_receive(:send).with("get", "/someurl", :query => attr, :verify => false, :headers => headers).and_return(resp)
91
+ call.http_call("get", "/someurl", attr)
92
+ end
93
+
94
+ it "should be able to perform DELETE calls" do
95
+ attr = {fullName: "john doe", email: "jhon@doe.com"}
96
+ call = Caller.new(attr)
97
+ call.stub(:basic_auth => "abc123")
98
+ headers = { 'Accept' => "application/json",
99
+ 'Authorization' => "Basic abc123"}
100
+ Caller.should_receive(:send).with("delete", "/someurl", :query => attr, :verify => false, :headers => headers).and_return(resp)
101
+ call.http_call("delete", "/someurl", attr)
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+
108
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ module PayuPayments
4
+
5
+ describe Client do
6
+ it "should be able to create a client" do
7
+ client = Client.new(:fullName => "John Doe", :email => "johndoe@gmail.com")
8
+ client.should_receive(:http_call).with("post", "/payments-api/rest/v4.3/customers", {:fullName => "John Doe", :email => "johndoe@gmail.com"}).and_return(:fullName => "John Doe", :email => "johndoe@gmail.com")
9
+ client.save
10
+ end
11
+
12
+ it "should be able to find a client with its id" do
13
+ call = Client.new
14
+ call.should_receive(:http_call).with("get","/payments-api/rest/v4.3/customers/1").and_return(:fullName => "John Doe", :email => "johndoe@gmail.com", :id => 1)
15
+ Client.should_receive(:new).twice.and_return(call)
16
+ client = Client.find(1)
17
+ expect(client).to be_an_instance_of(Client)
18
+ end
19
+
20
+ it "should save a credit card related to the client" do
21
+ client = Client.new(:id => "123", :fullName => "John Doe", :email => "johndoe@gmail.com")
22
+ cc = CreditCard.new
23
+ CreditCard.should_receive(:new).and_return(cc)
24
+ cc.stub(:save => true)
25
+ client.add_credit_card({number: "1234", :name => "John Doe"})
26
+ end
27
+
28
+ it "should be able to load credit cards" do
29
+ response = {
30
+ id: "2mkls9xekm",
31
+ fullName: "Pedro Perez",
32
+ email: "pperez@payulatam.com",
33
+ creditCards: [
34
+ {
35
+ token: "da2224a9-58b7-482a-9866-199de911c23f",
36
+ customerId: "2mkls9xekm",
37
+ number: "************4242",
38
+ name: "Usuario Prueba",
39
+ type: "VISA",
40
+ address: {
41
+ line1: "Street 93B",
42
+ line2: "17 25",
43
+ line3: "Office 301",
44
+ city: "Bogota",
45
+ country: "CO",
46
+ postalCode: "00000",
47
+ phone: "300300300"
48
+ }
49
+ }
50
+ ]
51
+ }
52
+ Client.should_receive(:find).and_return(Client.new(response))
53
+ client = Client.find("2mkls9xekm")
54
+ expect(client.base.id).to eq("2mkls9xekm")
55
+ expect(client.credit_cards[0].attr.number).to eql("************4242")
56
+ end
57
+
58
+ it "should be able to load subscriptions" do
59
+ response = {
60
+ id: "2mkls9xekm",
61
+ fullName: "Pedro Perez",
62
+ email: "pperez@payulatam.com",
63
+ subscriptions: [
64
+ {
65
+ id: "2mlhk3qxji",
66
+ quantity: "1",
67
+ installments: "1",
68
+ currentPeriodStart: "2013-08-30T10:46:41.477-05:00",
69
+ currentPeriodEnd: "2013-09-29T10:46:41.477-05:00",
70
+ plan: {
71
+ id: "414215a2-c990-4525-ba84-072181988d09",
72
+ planCode: "PLAN-REST-16",
73
+ description: "Plan rest test",
74
+ accountId: "1",
75
+ intervalCount: "1",
76
+ interval: "MONTH",
77
+ additionalValues: [
78
+ {
79
+ name: "PLAN_VALUE",
80
+ value: "20000",
81
+ currency: "COP"
82
+ }]
83
+ }
84
+ }
85
+ ]
86
+ }
87
+ Client.should_receive(:find).and_return(Client.new(response))
88
+ client = Client.find("2mkls9xekm")
89
+ expect(client.base.id).to eq("2mkls9xekm")
90
+ expect(client.subscriptions[0].attr.id).to eql("2mlhk3qxji")
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ describe PayuPayments do
3
+
4
+ context "Configuration" do
5
+ it "should be able to set configuration keys" do
6
+ PayuPayments.config do |config|
7
+ config.api_key = "xxx"
8
+ config.merchant_id = "123"
9
+ config.account = "abc123"
10
+ end
11
+ end
12
+
13
+ it "should be able to read configuration keys" do
14
+ PayuPayments.config[:api_key].should == "xxx"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ module PayuPayments
4
+
5
+ describe CreditCard do
6
+ it "should be able to validate a credit card" do
7
+ cc = CreditCard.new
8
+ expect(cc.valid?).to eq(false)
9
+ end
10
+
11
+ it "should tell me where the errors are" do
12
+ cc = CreditCard.new
13
+ cc.valid?
14
+ expect(cc.errors.count).to be > 0
15
+ expect(cc.errors.first[:field]).to eql(:customerId)
16
+ expect(cc.errors.first[:message]).to eql("customerId can't be blank")
17
+ end
18
+
19
+ it "should not allow to save a credit card with erros" do
20
+ expect(CreditCard.new.save).to be(false)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ module PayuPayments
4
+ describe Plan do
5
+
6
+ it "should be able to create a plan" do
7
+ plan = {
8
+ plan: {
9
+ planCode: "sample-plan-code-001",
10
+ description: "Sample Plan 001",
11
+ accountId: "1",
12
+ intervalCount: "1",
13
+ interval: "MONTH",
14
+ maxPaymentsAllowed: "12",
15
+ maxPaymentAttempts: "3",
16
+ paymentAttemptsDelay: "1",
17
+ maxPendingPayments: "0",
18
+ trialDays: "30",
19
+ additionalValues: {
20
+ additionalValue: [{ name: "PLAN_VALUE",
21
+ value: "20000",
22
+ currency: "COP"
23
+ },
24
+ {
25
+ name: "PLAN_TAX",
26
+ value: "1600",
27
+ currency: "COP"
28
+ }]
29
+ }
30
+ }
31
+ }
32
+ my_plan = Plan.new(plan)
33
+ my_plan.should_receive(:http_call).with("post", "/payments-api/rest/v4.3/plans", plan).and_return(plan)
34
+ my_plan.save
35
+
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'payu_payments'
5
+
6
+ RSpec.configure do |config|
7
+ # some (optional) config here
8
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: payu_payments
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - CristianV
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-03-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: httparty
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.5'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.5'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: guard-rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: pry
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Payulatam payments gatewat API gem
111
+ email:
112
+ - cristian@kommit.co
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - .rspec
119
+ - .ruby-gemset
120
+ - .ruby-version
121
+ - Gemfile
122
+ - Guardfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - lib/payu_payments.rb
127
+ - lib/payu_payments/caller.rb
128
+ - lib/payu_payments/client.rb
129
+ - lib/payu_payments/credit_card.rb
130
+ - lib/payu_payments/model.rb
131
+ - lib/payu_payments/plan.rb
132
+ - lib/payu_payments/recurring_bill_item.rb
133
+ - lib/payu_payments/subscription.rb
134
+ - lib/payu_payments/version.rb
135
+ - payu_payments.gemspec
136
+ - spec/caller_spec.rb
137
+ - spec/client_spec.rb
138
+ - spec/config_spec.rb
139
+ - spec/credit_card_spec.rb
140
+ - spec/plan_spec.rb
141
+ - spec/spec_helper.rb
142
+ homepage: https://github.com/kommitters/payu_payments
143
+ licenses:
144
+ - MIT
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ! '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ none: false
157
+ requirements:
158
+ - - ! '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 1.8.23
164
+ signing_key:
165
+ specification_version: 3
166
+ summary: Payulatam payments gatewat API gem
167
+ test_files:
168
+ - spec/caller_spec.rb
169
+ - spec/client_spec.rb
170
+ - spec/config_spec.rb
171
+ - spec/credit_card_spec.rb
172
+ - spec/plan_spec.rb
173
+ - spec/spec_helper.rb