mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-05-26 01:37:39 -04:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a12523793 | ||
|
|
6aa601b8ab | ||
|
|
a66a040207 | ||
|
|
547a408a99 | ||
|
|
8cd224b7fa | ||
|
|
8428e93b47 | ||
|
|
483143f2fe | ||
|
|
1b3d106b6d | ||
|
|
e7be006fa7 | ||
|
|
b9f40151d8 | ||
|
|
e1ad0c31c8 | ||
|
|
deaf49c18f | ||
|
|
53543fff4c | ||
|
|
ea5e710a70 | ||
|
|
ad5a52ae74 | ||
|
|
c0cc5ab085 | ||
|
|
cf784cbedd | ||
|
|
5c68830919 | ||
|
|
370aab41c3 | ||
|
|
081f4021bc | ||
|
|
36f846fabf | ||
|
|
f6d094f57c | ||
|
|
9ce097b7d6 | ||
|
|
b03ccce575 | ||
|
|
63b8788c80 | ||
|
|
faa3beddbc | ||
|
|
5dbd5bc1d2 | ||
|
|
b8b0d98e07 | ||
|
|
b911ebfd07 | ||
|
|
1ca515bd4e | ||
|
|
905710b6e9 | ||
|
|
12e0f674bc | ||
|
|
18739c2a24 | ||
|
|
f3dc2ff7e5 | ||
|
|
dbf64a4e90 | ||
|
|
a8961c3466 | ||
|
|
6931952688 | ||
|
|
936aa17ecc | ||
|
|
f3d7420b8a | ||
|
|
e419acea6e | ||
|
|
95ae15b780 | ||
|
|
ed468e79fc | ||
|
|
130565af75 | ||
|
|
ad8f360336 | ||
|
|
15775403fd | ||
|
|
e1b83c32af | ||
|
|
3fc7c65247 | ||
|
|
c8d1239b79 | ||
|
|
7885257fae | ||
|
|
a8d22709e6 | ||
|
|
31b7b45ae3 | ||
|
|
681798b582 | ||
|
|
f05ea56179 | ||
|
|
ed4269ea4c | ||
|
|
085fcc958e | ||
|
|
8f33fe3595 | ||
|
|
980a7d79d3 | ||
|
|
9c5c07e087 | ||
|
|
faaf07cad8 | ||
|
|
0e83a230b9 | ||
|
|
c278b36858 | ||
|
|
0805f86b86 | ||
|
|
aad88f1da6 | ||
|
|
7550bc860f | ||
|
|
fc38e1edea | ||
|
|
88a66a705b | ||
|
|
f77fd00931 | ||
|
|
b5c030905a | ||
|
|
2844e13298 | ||
|
|
3e4dc1f9e3 | ||
|
|
259c1e1731 | ||
|
|
870aa674b0 | ||
|
|
b1ab319bf7 | ||
|
|
857422719a | ||
|
|
235314bc13 | ||
|
|
ac1cd88ae1 | ||
|
|
f93ea3c0a1 | ||
|
|
0075040aab | ||
|
|
f9d51e623c | ||
|
|
dcd36dcd67 | ||
|
|
608a921fce | ||
|
|
4bedf1e6ac | ||
|
|
ac14c310f6 | ||
|
|
f979da315a | ||
|
|
a74ef7de6b | ||
|
|
10064121a6 | ||
|
|
f8a1ef6066 | ||
|
|
40d7b11d90 | ||
|
|
fbf5164eca | ||
|
|
21d86a8c2d | ||
|
|
848c7e117b | ||
|
|
369868672b | ||
|
|
321f3517a7 | ||
|
|
6dd7ec9f95 | ||
|
|
8648db5518 |
@@ -8,7 +8,7 @@ engines:
|
||||
eslint:
|
||||
enabled: true
|
||||
coffeelint:
|
||||
enabled: true
|
||||
enabled: true
|
||||
brakeman:
|
||||
enabled: true
|
||||
bundler-audit:
|
||||
@@ -25,7 +25,7 @@ ratings:
|
||||
paths:
|
||||
- "**.rb"
|
||||
- "**.js"
|
||||
- "**.coffee"
|
||||
- "**.coffee"
|
||||
- "**.sass"
|
||||
- "**.haml"
|
||||
- Gemfile.lock
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.2.4
|
||||
2.3.1
|
||||
|
||||
@@ -8,7 +8,7 @@ env:
|
||||
global:
|
||||
secure: "Z5TpM2jEX4UCvNePnk/LwltQX48U2u9BRc+Iypr1x9QW2o228QJhPIOH39a8RMUrepGnkQIq9q3ZRUn98RfrJz1yThtlNFL3NmzdQ57gKgjGwfpa0e4Dwj/ZJqV2D84tDGjvdVYLP7zzaYZxQcwk/cgNpzKf/jq97HLNP7CYuf4="
|
||||
rvm:
|
||||
- 2.2.4
|
||||
- 2.3.1
|
||||
before_script:
|
||||
- psql -c 'create database growstuff_test;' -U postgres
|
||||
script:
|
||||
@@ -34,4 +34,6 @@ deploy:
|
||||
- restart
|
||||
after_deploy:
|
||||
- bundle exec script/heroku_maintenance.rb off
|
||||
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token: 462e015bbdaabfb20910fc07f2fea253410ecb131444e00f97dbf32dc6789ca6
|
||||
|
||||
@@ -68,3 +68,4 @@ submit the change with your pull request.
|
||||
- Daniel O'Connor / [CloCkWeRX](https://github.com/CloCkWeRX)
|
||||
- DV Dasari / [dv2](https://github.com/dv2)
|
||||
- Eric Tillberg / [Thrillberg](https://github.com/Thrillberg)
|
||||
- Lucas Nogueira / [lucasnogueira](https://github.com/lucasnogueira)
|
||||
|
||||
4
Capfile
4
Capfile
@@ -1,4 +0,0 @@
|
||||
load 'deploy'
|
||||
# Uncomment if you are using Rails' asset pipeline
|
||||
# load 'deploy/assets'
|
||||
load 'config/deploy' # remove this line to skip loading any of the default tasks
|
||||
22
Gemfile
22
Gemfile
@@ -1,6 +1,6 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
ruby '2.2.4'
|
||||
ruby '2.3.1'
|
||||
|
||||
gem 'rails', '~> 4.1.11'
|
||||
|
||||
@@ -36,13 +36,8 @@ gem 'comfortable_mexican_sofa', '~> 1.12.0' # content management system
|
||||
gem 'kaminari' # pagination
|
||||
gem 'bootstrap-kaminari-views' # bootstrap views for kaminari
|
||||
|
||||
# vendored activemerchant for testing- needed for bogus paypal
|
||||
# gateway monkeypatch
|
||||
gem 'activemerchant', '1.33.0',
|
||||
:path => 'vendor/gems/activemerchant-1.33.0',
|
||||
:require => 'active_merchant'
|
||||
gem 'active_utils', '1.0.5',
|
||||
:path => 'vendor/gems/active_utils-1.0.5'
|
||||
gem 'activemerchant', '1.33.0'
|
||||
gem 'active_utils', '1.0.5'
|
||||
|
||||
# Markdown formatting for updates etc
|
||||
gem 'bluecloth'
|
||||
@@ -51,7 +46,7 @@ gem 'bluecloth'
|
||||
gem 'will_paginate', '~> 3.0'
|
||||
|
||||
# user signup/login/etc
|
||||
gem 'devise', '~> 3.5.0'
|
||||
gem 'devise', '>= 4.0.0'
|
||||
|
||||
# nicely formatted URLs
|
||||
gem 'friendly_id', '~> 5.0.4'
|
||||
@@ -60,9 +55,7 @@ gem 'friendly_id', '~> 5.0.4'
|
||||
gem 'gravatar-ultimate'
|
||||
|
||||
# For geolocation
|
||||
gem 'geocoder',
|
||||
:git => 'https://github.com/alexreisner/geocoder.git',
|
||||
:ref => '104d46'
|
||||
gem 'geocoder'
|
||||
|
||||
# For easy calendar selection
|
||||
gem 'bootstrap-datepicker-rails'
|
||||
@@ -103,12 +96,12 @@ end
|
||||
|
||||
group :development, :test do
|
||||
gem 'haml-rails' # HTML templating language
|
||||
gem 'rspec-rails', '~> 3.4.0' # unit testing framework
|
||||
gem 'rspec-rails' # unit testing framework
|
||||
gem 'rspec-activemodel-mocks'
|
||||
gem 'byebug' # debugging
|
||||
gem 'database_cleaner', '~> 1.5.0'
|
||||
gem 'webrat' # provides HTML matchers for view tests
|
||||
gem 'factory_girl_rails', '~> 4.5.0' # for creating test data
|
||||
gem 'factory_girl_rails' # for creating test data
|
||||
gem 'coveralls', require: false # coverage analysis
|
||||
gem 'capybara' # integration tests
|
||||
gem 'capybara-email' # integration tests for email
|
||||
@@ -117,6 +110,7 @@ group :development, :test do
|
||||
gem 'i18n-tasks' # adds tests for finding missing and unused translations
|
||||
gem 'selenium-webdriver'
|
||||
gem "codeclimate-test-reporter", group: :test, require: nil
|
||||
gem "active_merchant-paypal-bogus-gateway"
|
||||
end
|
||||
|
||||
group :travis do
|
||||
|
||||
103
Gemfile.lock
103
Gemfile.lock
@@ -1,22 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/alexreisner/geocoder.git
|
||||
revision: 104d466ba7097b7dce5ba19f8e4091b7f69ccdf6
|
||||
ref: 104d46
|
||||
specs:
|
||||
geocoder (1.1.8)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/active_utils-1.0.5
|
||||
specs:
|
||||
active_utils (1.0.5)
|
||||
activesupport (>= 2.3.11)
|
||||
i18n
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/activemerchant-1.33.0
|
||||
specs:
|
||||
activemerchant (1.33.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@@ -35,6 +16,19 @@ GEM
|
||||
erubis (~> 2.7.0)
|
||||
active_link_to (1.0.3)
|
||||
actionpack
|
||||
active_merchant-paypal-bogus-gateway (0.1.0)
|
||||
activemerchant
|
||||
active_utils (1.0.5)
|
||||
activesupport (>= 2.3.11)
|
||||
i18n
|
||||
activemerchant (1.33.0)
|
||||
active_utils (>= 1.0.2)
|
||||
activesupport (>= 2.3.14)
|
||||
builder (>= 2.0.0)
|
||||
i18n
|
||||
json (>= 1.5.1)
|
||||
money
|
||||
nokogiri
|
||||
activemodel (4.1.15)
|
||||
activesupport (= 4.1.15)
|
||||
builder (~> 3.1)
|
||||
@@ -50,8 +44,8 @@ GEM
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.4.0)
|
||||
arel (5.0.1.20140414130214)
|
||||
ast (2.2.0)
|
||||
autoprefixer-rails (6.3.6.1)
|
||||
ast (2.3.0)
|
||||
autoprefixer-rails (6.3.6.2)
|
||||
execjs
|
||||
bcrypt (3.1.11)
|
||||
better_errors (2.1.1)
|
||||
@@ -72,7 +66,7 @@ GEM
|
||||
sass (>= 3.3.4)
|
||||
bootstrap_form (2.3.0)
|
||||
builder (3.2.2)
|
||||
byebug (9.0.4)
|
||||
byebug (9.0.5)
|
||||
cancancan (1.14.0)
|
||||
capybara (2.7.1)
|
||||
addressable
|
||||
@@ -94,7 +88,7 @@ GEM
|
||||
cliver (0.3.2)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
codeclimate-test-reporter (0.5.0)
|
||||
codeclimate-test-reporter (0.5.1)
|
||||
simplecov (>= 0.7.1, < 1.0.0)
|
||||
codemirror-rails (5.11)
|
||||
railties (>= 3.0, < 5)
|
||||
@@ -133,12 +127,11 @@ GEM
|
||||
dalli (2.7.6)
|
||||
database_cleaner (1.5.3)
|
||||
debug_inspector (0.0.2)
|
||||
devise (3.5.10)
|
||||
devise (4.1.1)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 3.2.6, < 5)
|
||||
railties (>= 4.1.0, < 5.1)
|
||||
responders
|
||||
thread_safe (~> 0.1)
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.2.5)
|
||||
docile (1.1.5)
|
||||
@@ -162,10 +155,10 @@ GEM
|
||||
erubis (2.7.0)
|
||||
excon (0.49.0)
|
||||
execjs (2.7.0)
|
||||
factory_girl (4.5.0)
|
||||
factory_girl (4.7.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.5.0)
|
||||
factory_girl (~> 4.5.0)
|
||||
factory_girl_rails (4.7.0)
|
||||
factory_girl (~> 4.7.0)
|
||||
railties (>= 3.0.0)
|
||||
faraday (0.9.2)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
@@ -178,7 +171,8 @@ GEM
|
||||
formatador (0.2.5)
|
||||
friendly_id (5.0.5)
|
||||
activerecord (>= 4.0.0)
|
||||
gibbon (1.2.0)
|
||||
geocoder (1.3.7)
|
||||
gibbon (1.2.1)
|
||||
httparty
|
||||
multi_json (>= 1.9.0)
|
||||
gravatar-ultimate (2.0.0)
|
||||
@@ -194,7 +188,7 @@ GEM
|
||||
shellany (~> 0.0)
|
||||
thor (>= 0.18.1)
|
||||
guard-compat (1.2.1)
|
||||
guard-rspec (4.6.5)
|
||||
guard-rspec (4.7.2)
|
||||
guard (~> 2.1)
|
||||
guard-compat (~> 1.1)
|
||||
rspec (>= 2.99.0, < 4.0)
|
||||
@@ -216,7 +210,7 @@ GEM
|
||||
haml (~> 4.0.0)
|
||||
nokogiri (~> 1.6.0)
|
||||
ruby_parser (~> 3.5)
|
||||
httparty (0.13.3)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.7.0)
|
||||
@@ -235,11 +229,11 @@ GEM
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-ui-rails (5.0.5)
|
||||
railties (>= 3.2.16)
|
||||
js-routes (1.2.5)
|
||||
js-routes (1.2.6)
|
||||
railties (>= 3.2)
|
||||
sprockets-rails
|
||||
json (1.8.3)
|
||||
kaminari (0.16.3)
|
||||
kaminari (0.17.0)
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
kgio (2.10.0)
|
||||
@@ -260,19 +254,23 @@ GEM
|
||||
mime-types (>= 1.16, < 4)
|
||||
memcachier (0.0.2)
|
||||
method_source (0.8.2)
|
||||
mime-types (3.0)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0221)
|
||||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.0)
|
||||
mini_portile2 (2.0.0)
|
||||
mini_portile2 (2.1.0)
|
||||
minitest (5.9.0)
|
||||
money (6.7.1)
|
||||
i18n (>= 0.6.4, <= 0.7.0)
|
||||
sixarm_ruby_unaccent (>= 1.1.1, < 2)
|
||||
multi_json (1.11.3)
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
nenv (0.3.0)
|
||||
newrelic_rpm (3.15.2.317)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
nokogiri (1.6.8)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
pkg-config (~> 1.1.7)
|
||||
notiffany (0.1.0)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
@@ -296,9 +294,10 @@ GEM
|
||||
cocaine (~> 0.5.5)
|
||||
mime-types
|
||||
mimemagic (= 0.3.0)
|
||||
parser (2.3.1.0)
|
||||
parser (2.3.1.2)
|
||||
ast (~> 2.2)
|
||||
pg (0.18.4)
|
||||
pkg-config (1.1.7)
|
||||
plupload-rails (1.2.1)
|
||||
rails (>= 3.1)
|
||||
poltergeist (1.9.0)
|
||||
@@ -382,7 +381,7 @@ GEM
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
selenium-webdriver (2.53.0)
|
||||
selenium-webdriver (2.53.1)
|
||||
childprocess (~> 0.5)
|
||||
rubyzip (~> 1.0)
|
||||
websocket (~> 1.0)
|
||||
@@ -393,6 +392,7 @@ GEM
|
||||
json (~> 1.8)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
sixarm_ruby_unaccent (1.1.1)
|
||||
slop (3.6.0)
|
||||
sprockets (3.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
@@ -403,11 +403,11 @@ GEM
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
term-ansicolor (1.3.2)
|
||||
tins (~> 1.0)
|
||||
terminal-table (1.5.2)
|
||||
terminal-table (1.6.0)
|
||||
thor (0.19.1)
|
||||
thread (0.2.2)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.4)
|
||||
tilt (2.0.5)
|
||||
tins (1.6.0)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
@@ -435,8 +435,9 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
active_utils (= 1.0.5)!
|
||||
activemerchant (= 1.33.0)!
|
||||
active_merchant-paypal-bogus-gateway
|
||||
active_utils (= 1.0.5)
|
||||
activemerchant (= 1.33.0)
|
||||
better_errors
|
||||
binding_of_caller
|
||||
bluecloth
|
||||
@@ -457,15 +458,15 @@ DEPENDENCIES
|
||||
csv_shaper
|
||||
dalli
|
||||
database_cleaner (~> 1.5.0)
|
||||
devise (~> 3.5.0)
|
||||
devise (>= 4.0.0)
|
||||
elasticsearch-model
|
||||
elasticsearch-rails
|
||||
factory_girl_rails (~> 4.5.0)
|
||||
factory_girl_rails
|
||||
figaro
|
||||
flickraw
|
||||
font-awesome-sass
|
||||
friendly_id (~> 5.0.4)
|
||||
geocoder!
|
||||
geocoder
|
||||
gibbon (~> 1.2.0)
|
||||
gravatar-ultimate
|
||||
guard
|
||||
@@ -494,7 +495,7 @@ DEPENDENCIES
|
||||
rails_12factor
|
||||
rake (>= 10.0.0)
|
||||
rspec-activemodel-mocks
|
||||
rspec-rails (~> 3.4.0)
|
||||
rspec-rails
|
||||
ruby-units
|
||||
sass-rails (~> 5.0.4)
|
||||
selenium-webdriver
|
||||
@@ -504,7 +505,7 @@ DEPENDENCIES
|
||||
will_paginate (~> 3.0)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.1.8p440
|
||||
ruby 2.3.1p112
|
||||
|
||||
BUNDLED WITH
|
||||
1.12.4
|
||||
1.12.5
|
||||
|
||||
@@ -19,8 +19,9 @@ encourage participation from people of all backgrounds and skill levels.
|
||||
|
||||
* [Issues](http://github.com/Growstuff/growstuff/issues) (features we're
|
||||
working on, known bugs, etc)
|
||||
* [Discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) (mailing lists, IRC, etc)
|
||||
* [Wiki](http://wiki.growstuff.org/) (general documentation)
|
||||
* [Discussion forums](http://talk.growstuff.org/) (design ideas, planning releases)
|
||||
* IRC: #growstuff on Freenode (general chat, brainstorming and troubleshooting) or [Gitter](https://gitter.im/Growstuff/growstuff)
|
||||
* [Wiki](http://wiki.growstuff.org/) (general documentation, currently down but should be fixed soon)
|
||||
|
||||
## For coders
|
||||
|
||||
@@ -30,7 +31,7 @@ frontend features. We welcome contributions -- see
|
||||
|
||||
* To set up your development environment, see [Getting started](http://wiki.growstuff.org/index.php/Development/Getting_Started).
|
||||
* We encourage [pair programming](http://wiki.growstuff.org/index.php/Pairing), especially for newer developers. [Find a pair programming partner.](http://talk.growstuff.org/t/find-a-pair-programming-partner/13)
|
||||
* Drop in to one of our [discussion forums](http://wiki.growstuff.org/index.php/Discussion_forums) to chat to other developers, get help, etc.
|
||||
* Drop in to our [discussion forums](http://talk.growstuff.org/), IRC or Gitter to chat to other developers, get help, etc.
|
||||
* You may also be interested in our [API](http://wiki.growstuff.org/index.php/API).
|
||||
|
||||
The wiki is down right now, so here's what you need to do on Mac OS X to get set up.
|
||||
@@ -58,6 +59,7 @@ Here on Github, you might find these useful:
|
||||
* [needs: visual design](https://github.com/Growstuff/growstuff/labels/needs:%20visual design) - tasks requiring visual/graphical design
|
||||
* [needs: documentation](https://github.com/Growstuff/growstuff/labels/needs:%20documentation)
|
||||
* [needs: data](https://github.com/Growstuff/growstuff/labels/needs:%20data) - tasks requiring data entry, data design, data import, or similar
|
||||
* [curated:beginner](https://github.com/Growstuff/growstuff/labels/curated:%20beginner) - tasks that are ideal for beginner programmers or people new to the project
|
||||
|
||||
Feel free to comment on any of the issues you find there, or open up a broader conversation on [Growstuff Talk](http://talk.growstuff.org).
|
||||
|
||||
|
||||
BIN
app/assets/images/facebook-thumbnail.png
Normal file
BIN
app/assets/images/facebook-thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -52,7 +52,7 @@ class ApplicationController < ActionController::Base
|
||||
protected
|
||||
|
||||
def configure_permitted_parameters
|
||||
devise_parameter_sanitizer.for(:sign_up) do |member|
|
||||
devise_parameter_sanitizer.permit(:sign_up) do |member|
|
||||
member.permit(:login_name, :email, :password, :password_confirmation,
|
||||
:remember_me, :login,
|
||||
# terms of service
|
||||
@@ -64,7 +64,7 @@ class ApplicationController < ActionController::Base
|
||||
)
|
||||
end
|
||||
|
||||
devise_parameter_sanitizer.for(:account_update) do |member|
|
||||
devise_parameter_sanitizer.permit(:account_update) do |member|
|
||||
member.permit(:login_name, :email, :password, :password_confirmation,
|
||||
:remember_me, :login,
|
||||
# terms of service
|
||||
|
||||
@@ -22,7 +22,7 @@ module ApplicationHelper
|
||||
return link_to "(convert)",
|
||||
link,
|
||||
target: "_blank"
|
||||
end
|
||||
end
|
||||
|
||||
# Produces a cache key for uniquely identifying cached fragments.
|
||||
def cache_key_for(klass, identifier="all")
|
||||
@@ -50,5 +50,13 @@ module ApplicationHelper
|
||||
default: :identicon
|
||||
})
|
||||
end
|
||||
|
||||
# Returns a string with the quantity and the right pluralization for a
|
||||
# given collection and model.
|
||||
def localize_plural(collection, model)
|
||||
size = collection.size
|
||||
model_name = model.model_name.human(count: size)
|
||||
"#{size} #{model_name}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
module CropsHelper
|
||||
def display_seed_availability(member, crop)
|
||||
total_quantity = 0
|
||||
member.seeds.each do |seed|
|
||||
if seed.crop.name == crop.name
|
||||
total_quantity = total_quantity + seed.quantity
|
||||
end
|
||||
|
||||
seeds = member.seeds.select {|seed| seed.crop.name == crop.name }
|
||||
|
||||
seeds.each do |seed|
|
||||
total_quantity = total_quantity + seed.quantity if seed.quantity
|
||||
end
|
||||
|
||||
if !seeds.any?
|
||||
return "You don't have any seeds of this crop."
|
||||
end
|
||||
|
||||
if (total_quantity != 0)
|
||||
"You have #{pluralize(total_quantity, "seed")} of this crop."
|
||||
"You have #{total_quantity} #{Seed.model_name.human(count: total_quantity)} of this crop."
|
||||
else
|
||||
"You don't have any seeds of this crop."
|
||||
"You have an unknown quantity of seeds of this crop."
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
= content_for :title, @alternate_name.name
|
||||
- content_for :opengraph do
|
||||
= tag("meta", property: "og:title", content: @alternate_name.name)
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
%p#notice= notice
|
||||
|
||||
= render :partial => 'crops/approval_status_message', :locals => { :crop => @alternate_name.crop }
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
= render :partial => "members/avatar", :locals => { :member => comment.author }
|
||||
.col-md-11
|
||||
.comment-meta
|
||||
= (comment.created_at == comment.updated_at) ? 'Posted by' : 'Edited by'
|
||||
Posted by
|
||||
= link_to comment.author.login_name, member_path(comment.author)
|
||||
on
|
||||
= (comment.created_at == comment.updated_at) ? comment.created_at : comment.updated_at
|
||||
= comment.created_at
|
||||
- if comment.updated_at > comment.created_at
|
||||
and edited at
|
||||
= comment.updated_at
|
||||
|
||||
.comment-body
|
||||
:growstuff_markdown
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
= content_for :title, @comment.post.subject
|
||||
- content_for :opengraph do
|
||||
= tag("meta", property: "og:image", content: avatar_uri(@comment.post.author, 200))
|
||||
= tag("meta", property: "og:image:user_generated", content: "true")
|
||||
= tag("meta", property: "og:title", content: @comment.post.subject)
|
||||
= tag("meta", property: "og:description", content: strip_tags(@comment.post.body).split(' ')[0..20].join(' '))
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
= render :partial => "posts/single", :locals => { :post => @comment.post }
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
- content_for :title, @crop.name
|
||||
- content_for :subtitle, @crop.default_scientific_name
|
||||
- content_for :opengraph do
|
||||
- @crop.photos.each do |photo|
|
||||
= tag("meta", property: "og:image", content: photo.fullsize_url)
|
||||
= tag("meta", property: "og:title", content: @crop.name)
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
= render :partial => 'approval_status_message', :locals => { :crop => @crop }
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
- @forums.each do |forum|
|
||||
%h2= forum
|
||||
%p
|
||||
= pluralize(forum.posts.size, "post")
|
||||
= localize_plural(forum.posts, Post)
|
||||
|
|
||||
=link_to "Visit forum", forum
|
||||
|
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
- content_for :title, @forum.name
|
||||
- content_for :opengraph do
|
||||
- if @forum.description
|
||||
= tag("meta", property: "og:description", content: strip_tags(@forum.description).split(' ')[0..20].join(' '))
|
||||
= tag("meta", property: "og:title", content: @forum.name)
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
%p#notice= notice
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
%dd= garden.active ? "Yes" : "No"
|
||||
.col-md-12
|
||||
%b
|
||||
= "#{pluralize(garden.plantings.size, "Planting")} : "
|
||||
= "#{localize_plural(garden.plantings, Planting)} : "
|
||||
= display_garden_plantings(garden.plantings.current)
|
||||
- if garden.plantings.size > 2
|
||||
%br
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
=content_for :title, "#{@garden.owner}'s #{@garden}"
|
||||
|
||||
- content_for :opengraph do
|
||||
- @garden.photos.each do |photo|
|
||||
= tag("meta", property: "og:image", content: photo.fullsize_url)
|
||||
- if @garden.description
|
||||
= tag("meta", property: "og:description", content: @garden.description)
|
||||
= tag("meta", property: "og:title", content: "#{@garden.owner}'s #{@garden}")
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
.row
|
||||
.col-md-9
|
||||
- if can? :edit, @garden or can? :delete, @garden
|
||||
@@ -39,7 +47,7 @@
|
||||
- if @garden.photos.size > 0 or (can? :edit, @garden and can? :create, Photo)
|
||||
.row-fluid
|
||||
%h3 Photos
|
||||
%p= pluralize(@garden.photos.length, "photo")
|
||||
%p= localize_plural(@garden.photos, Photo)
|
||||
.row-fluid
|
||||
%ul.thumbnails
|
||||
- @garden.photos.each do |p|
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
=content_for :title, "#{@harvest.crop} harvested by #{@harvest.owner}"
|
||||
- content_for :opengraph do
|
||||
- @harvest.photos.each do |photo|
|
||||
= tag("meta", property: "og:image", content: photo.fullsize_url)
|
||||
= tag("meta", property: "og:image:user_generated", content: "true")
|
||||
= tag("meta", property: "og:title", content: "#{@harvest.crop} harvested by #{@harvest.owner}")
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
%head
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta property="og:image" content="#{image_url 'growstuff-apple-touch-icon-precomposed.png'}"/>
|
||||
<meta property="og:title" content="#{content_for?(:title) ? yield(:title) + " - #{ ENV['GROWSTUFF_SITE_NAME']} " : ENV['GROWSTUFF_SITE_NAME']}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="#{root_url}" />
|
||||
<meta property="og:site_name" content="#{ENV['GROWSTUFF_SITE_NAME']}" />
|
||||
- if content_for?(:opengraph)
|
||||
= yield(:opengraph)
|
||||
- else
|
||||
= tag("meta", property: "og:image", content: image_url('facebook-thumbnail.png'))
|
||||
= tag("meta", property: "og:title", content: "#{content_for?(:title) ? yield(:title) + " - #{ ENV['GROWSTUFF_SITE_NAME']} " : ENV['GROWSTUFF_SITE_NAME']}")
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: root_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
- if (content_for?(:member_rss_login_name) && content_for(:member_rss_slug))
|
||||
= auto_discovery_link_tag(:rss, { :controller => "/members", :action => 'show', :format => "rss", :id => yield(:member_rss_slug) }, { :title => "#{ ENV['GROWSTUFF_SITE_NAME'] }- #{yield(:member_rss_login_name)}'s posts" })
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
- if g.photos.size > 0 or (can? :edit, g and can? :create, Photo)
|
||||
.row
|
||||
%h3 Photos
|
||||
%p= pluralize(g.photos.length, "photo")
|
||||
%p= localize_plural(g.photos, Photo)
|
||||
.row
|
||||
%ul.thumbnails
|
||||
- g.photos.each do |p|
|
||||
|
||||
@@ -3,28 +3,28 @@
|
||||
%ul.list-inline
|
||||
%li
|
||||
- if member.plantings.size > 0
|
||||
= link_to pluralize(member.plantings.size, "planting"), plantings_by_owner_path(:owner => member)
|
||||
= link_to localize_plural(member.plantings, Planting), plantings_by_owner_path(owner: member)
|
||||
- else
|
||||
0 plantings
|
||||
%li
|
||||
- if member.harvests.size > 0
|
||||
= link_to pluralize(member.harvests.size, "harvest"), harvests_by_owner_path(:owner => member)
|
||||
= link_to localize_plural(member.harvests, Harvest), harvests_by_owner_path(owner: member)
|
||||
- else
|
||||
0 harvests
|
||||
%li
|
||||
- if member.seeds.size > 0
|
||||
= link_to pluralize(member.seeds.size, "seeds"), seeds_by_owner_path(:owner => member)
|
||||
= link_to localize_plural(member.seeds, Seed), seeds_by_owner_path(owner: member)
|
||||
- else
|
||||
0 seeds
|
||||
%li
|
||||
- if member.posts.size > 0
|
||||
= link_to pluralize(member.posts.size, "post"), posts_by_author_path(:author => member)
|
||||
= link_to localize_plural(member.posts, Post), posts_by_author_path(author: member)
|
||||
- else
|
||||
0 posts
|
||||
|
||||
%li
|
||||
- if member.followed.size > 0
|
||||
= link_to pluralize(member.followed.size, "follow"), member_follows_path(member)
|
||||
= link_to localize_plural(member.followed, Follow), member_follows_path(member)
|
||||
- else
|
||||
0 following
|
||||
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
ago.
|
||||
%p
|
||||
%small
|
||||
= [pluralize(member.gardens.size, "garden"), pluralize(member.plantings.size, "planting"), pluralize(member.seeds.size, "seed")].join(", ")
|
||||
= [localize_plural(member.gardens, Garden), localize_plural(member.plantings, Planting), localize_plural(member.seeds, Seed)].join(", ")
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
- content_for :title, @member.login_name
|
||||
- content_for :subtitle, @member.location
|
||||
- content_for :opengraph do
|
||||
= tag("meta", property: "og:image", content: avatar_uri(@member, 200))
|
||||
= tag("meta", property: "og:image:user_generated", content: "true")
|
||||
= tag("meta", property: "og:title", content: @member.login_name)
|
||||
= tag("meta", property: "og:type", content: "profile")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
- content_for :buttonbar do
|
||||
- if can? :update, @member
|
||||
= link_to 'Edit profile', edit_member_registration_path, :class => 'btn btn-default'
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
-content_for :title, @photo.title
|
||||
- content_for :title, @photo.title
|
||||
- content_for :opengraph do
|
||||
= tag("meta", property: "og:title", content: @photo.title)
|
||||
= tag("meta", property: "og:image", content: @photo.fullsize_url)
|
||||
= tag("meta", property: "og:image:user_generated", content: "true")
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
-content_for :title, "#{ENV['GROWSTUFF_SITE_NAME']} community near #{@place}"
|
||||
- content_for :opengraph do
|
||||
= tag("meta", property: "og:title", content: "#{ENV['GROWSTUFF_SITE_NAME']} community near #{@place}")
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
= render partial: 'search_form'
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
- content_for :title, @plant_part.name.titlecase
|
||||
- content_for :opengraph do
|
||||
= tag("meta", property: "og:title", content: @plant_part.name.titlecase)
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
- if @plant_part.crops.empty?
|
||||
%p No crops are harvested for this plant part (yet).
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
=content_for :title, "#{@planting.crop} in #{@planting.location}"
|
||||
- content_for :opengraph do
|
||||
- @planting.crop.photos.each do |photo|
|
||||
= tag("meta", property: "og:image", content: photo.fullsize_url)
|
||||
= tag("meta", property: "og:title", content: "#{@planting.crop} in #{@planting.location}")
|
||||
- if @planting.description
|
||||
= tag("meta", property: "og:description", content: @planting.description)
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
.row.planting
|
||||
.col-md-6
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%a{:name => "comments"}
|
||||
- if post.comments
|
||||
%h2
|
||||
=pluralize(post.comments.size, "comment")
|
||||
=localize_plural(post.comments, Comment)
|
||||
- post.comments.post_order.each do |c|
|
||||
= render :partial => "comments/single", :locals => { :comment => c }
|
||||
|
||||
|
||||
@@ -9,13 +9,16 @@
|
||||
|
||||
.post-meta
|
||||
%p
|
||||
= (post.created_at == post.updated_at) ? 'Posted by' : 'Edited by'
|
||||
Posted by
|
||||
= link_to post.author.login_name, member_path(post.author)
|
||||
- if post.forum
|
||||
in
|
||||
= link_to post.forum, post.forum
|
||||
on
|
||||
= (post.created_at == post.updated_at) ? post.created_at : post.updated_at
|
||||
= post.created_at
|
||||
- if post.updated_at > post.created_at
|
||||
and edited at
|
||||
= post.updated_at
|
||||
|
||||
.post-body
|
||||
:growstuff_markdown
|
||||
@@ -24,7 +27,7 @@
|
||||
- unless defined?(hide_comments)
|
||||
.post-comments
|
||||
%ul.list-inline
|
||||
%li.first= link_to pluralize(post.comments.size, "comment"),
|
||||
%li.first= link_to localize_plural(post.comments, Comment),
|
||||
post_path(post, :anchor => 'comments')
|
||||
-if can? :create, Comment
|
||||
%li= link_to "Reply", new_comment_path(:post_id => post.id)
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
%td.hidden-xs
|
||||
=link_to post.author, post.author
|
||||
%td
|
||||
= post.recent_activity.to_date.to_formatted_s(:short)
|
||||
- if post.updated_at > post.recent_activity
|
||||
= post.updated_at.to_date.to_formatted_s(:short)
|
||||
- else
|
||||
= post.recent_activity.to_date.to_formatted_s(:short)
|
||||
// once the site gets more active, can change this to include time as well
|
||||
// can't make it relative (distance_of_time_in_words) as it's cached
|
||||
%td.hidden-xs
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
= content_for :title, @post.subject
|
||||
- content_for :opengraph do
|
||||
= tag("meta", property: "og:image", content: avatar_uri(@post.author, 200))
|
||||
= tag("meta", property: "og:description", content: "#{strip_tags(@post.body).split(' ')[0..20].join(' ')}...")
|
||||
= tag("meta", property: "og:title", content: @post.subject)
|
||||
= tag("meta", property: "og:type", content: "article")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
- unless current_member
|
||||
.alert.alert-info
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
- content_for :opengraph do
|
||||
- @scientific_name.crop.photos.each do |photo|
|
||||
= tag("meta", property: "og:image", content: photo.fullsize_url)
|
||||
|
||||
= tag("meta", property: "og:title", content: @scientific_name.scientific_name)
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
%p#notice= notice
|
||||
|
||||
= render :partial => 'crops/approval_status_message', :locals => { :crop => @scientific_name.crop }
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
- content_for :title, "#{@seed.owner}'s #{@seed.crop} seeds"
|
||||
- content_for :opengraph do
|
||||
- @seed.crop.photos.each do |photo|
|
||||
= tag("meta", property: "og:image", content: photo.fullsize_url)
|
||||
- if @seed.description
|
||||
= tag("meta", property: "og:description", content: @seed.description)
|
||||
= tag("meta", property: "og:image", content: "#{@seed.owner}'s #{@seed.crop} seeds")
|
||||
= tag("meta", property: "og:type", content: "website")
|
||||
= tag("meta", property: "og:url", content: request.original_url)
|
||||
= tag("meta", property: "og:site_name", content: ENV['GROWSTUFF_SITE_NAME'])
|
||||
|
||||
.row
|
||||
.col-md-6
|
||||
|
||||
@@ -82,12 +82,6 @@ module Growstuff
|
||||
g.javascripts false
|
||||
end
|
||||
|
||||
config.action_mailer.delivery_method = :sendmail
|
||||
config.action_mailer.sendmail_settings = {
|
||||
location: '/usr/sbin/sendmail',
|
||||
arguments: '-i -t',
|
||||
openssl_verify_mode: 'none'
|
||||
}
|
||||
|
||||
# Growstuff-specific configuration variables
|
||||
config.currency = 'AUD'
|
||||
|
||||
@@ -69,15 +69,15 @@ Growstuff::Application.configure do
|
||||
# Growstuff configuration
|
||||
config.action_mailer.default_url_options = { host: 'growstuff.org' }
|
||||
|
||||
config.action_mailer.smtp_settings = {
|
||||
port: '587',
|
||||
address: 'smtp.mandrillapp.com',
|
||||
user_name: ENV['GROWSTUFF_MANDRILL_USERNAME'],
|
||||
password: ENV['GROWSTUFF_MANDRILL_APIKEY'],
|
||||
domain: 'heroku.com',
|
||||
authentication: :plain
|
||||
ActionMailer::Base.smtp_settings = {
|
||||
port: ENV['SPARKPOST_SMTP_PORT'],
|
||||
address: ENV['SPARKPOST_SMTP_HOST'],
|
||||
user_name: ENV['SPARKPOST_SMTP_USERNAME'],
|
||||
password: ENV['SPARKPOST_SMTP_PASSWORD'],
|
||||
authentication: :login,
|
||||
enable_starttls_auto: true
|
||||
}
|
||||
config.action_mailer.delivery_method = :smtp
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
|
||||
config.host = 'growstuff.org'
|
||||
config.analytics_code = <<-eos
|
||||
|
||||
@@ -71,15 +71,15 @@ Growstuff::Application.configure do
|
||||
# Growstuff configuration
|
||||
config.action_mailer.default_url_options = { host: 'staging.growstuff.org' }
|
||||
|
||||
config.action_mailer.smtp_settings = {
|
||||
port: '587',
|
||||
address: 'smtp.mandrillapp.com',
|
||||
user_name: ENV['GROWSTUFF_MANDRILL_USERNAME'],
|
||||
password: ENV['GROWSTUFF_MANDRILL_APIKEY'],
|
||||
domain: 'heroku.com',
|
||||
authentication: :plain
|
||||
ActionMailer::Base.smtp_settings = {
|
||||
port: ENV['SPARKPOST_SMTP_PORT'],
|
||||
address: ENV['SPARKPOST_SMTP_HOST'],
|
||||
user_name: ENV['SPARKPOST_SMTP_USERNAME'],
|
||||
password: ENV['SPARKPOST_SMTP_PASSWORD'],
|
||||
authentication: :login,
|
||||
enable_starttls_auto: true
|
||||
}
|
||||
config.action_mailer.delivery_method = :smtp
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
|
||||
config.host = 'staging.growstuff.org'
|
||||
config.analytics_code = ''
|
||||
|
||||
@@ -49,6 +49,7 @@ Growstuff::Application.configure do
|
||||
end
|
||||
|
||||
config.after_initialize do
|
||||
require "active_merchant/ext/paypal_bogus_gateway"
|
||||
ActiveMerchant::Billing::Base.mode = :test
|
||||
::STANDARD_GATEWAY = ActiveMerchant::Billing::PaypalBogusGateway.new
|
||||
::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalBogusGateway.new
|
||||
|
||||
@@ -121,7 +121,7 @@ Devise.setup do |config|
|
||||
# Email regex used to validate email formats. It simply asserts that
|
||||
# an one (and only one) @ exists in the given string. This is mainly
|
||||
# to give user feedback and not to assert the e-mail validity.
|
||||
# config.email_regexp = /\A[^@]+@[^@]+\z/
|
||||
config.email_regexp = /\A[^@]+@[^@]+\z/
|
||||
|
||||
# ==> Configuration for :timeoutable
|
||||
# The time you want to timeout the user session without activity. After this
|
||||
|
||||
@@ -179,6 +179,9 @@ en:
|
||||
crop:
|
||||
one: "crop"
|
||||
other: "crops"
|
||||
follow:
|
||||
one: "follow"
|
||||
other: "follows"
|
||||
garden:
|
||||
one: "garden"
|
||||
other: "gardens"
|
||||
|
||||
@@ -27,7 +27,7 @@ feature 'Commenting on a post' do
|
||||
fill_in "comment_body", with: "Testing edit for comment"
|
||||
click_button "Post comment"
|
||||
expect(page).to have_content "Comment was successfully updated"
|
||||
expect(page).to have_content "Edited by"
|
||||
expect(page).to have_content "edited at"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,7 +27,7 @@ feature 'Post a post' do
|
||||
fill_in "post_subject", with: "Testing Edit"
|
||||
click_button "Post"
|
||||
expect(page).to have_content "Post was successfully updated"
|
||||
expect(page).to have_content "Edited by"
|
||||
expect(page).to have_content "edited at"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,4 +43,32 @@ describe ApplicationHelper do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#localize_plural' do
|
||||
let(:post) { create(:post) }
|
||||
|
||||
context 'with a populated collection' do
|
||||
context 'with one element' do
|
||||
before { create(:comment, post: post) }
|
||||
|
||||
it 'returns a string with the quantity and the plural of the model' do
|
||||
expect(localize_plural(post.comments, Comment)).to eq '1 comment'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with more than one element' do
|
||||
before { create_list(:comment, 2, post: post) }
|
||||
|
||||
it 'returns a string with the quantity and the plural of the model' do
|
||||
expect(localize_plural(post.comments, Comment)).to eq '2 comments'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a populated collection' do
|
||||
it 'returns a string with the quantity and the plural of the model' do
|
||||
expect(localize_plural(post.comments, Comment)).to eq '0 comments'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
41
spec/helpers/crops_helper_spec.rb
Normal file
41
spec/helpers/crops_helper_spec.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe CropsHelper do
|
||||
describe "display_seed_availability" do
|
||||
before :each do
|
||||
@member = create :member
|
||||
@crop = create :tomato
|
||||
end
|
||||
|
||||
context "with no seeds" do
|
||||
it 'should render' do
|
||||
expect(helper.display_seed_availability(@member, @crop)).to eq "You don't have any seeds of this crop."
|
||||
end
|
||||
end
|
||||
|
||||
context "with an unknown quantity of seeds" do
|
||||
before do
|
||||
create :seed, crop: @crop, quantity: nil, owner: @member
|
||||
end
|
||||
|
||||
it 'should render' do
|
||||
expect(helper.display_seed_availability(@member, @crop)).to eq "You have an unknown quantity of seeds of this crop."
|
||||
end
|
||||
end
|
||||
|
||||
context "with an quantity of seeds" do
|
||||
before do
|
||||
a_different_crop = create :apple
|
||||
|
||||
create :seed, crop: @crop, quantity: 20, owner: @member
|
||||
create :seed, crop: @crop, quantity: 13, owner: @member
|
||||
|
||||
create :seed, crop: a_different_crop, quantity: 3, owner: @member
|
||||
end
|
||||
|
||||
it 'should render' do
|
||||
expect(helper.display_seed_availability(@member, @crop)).to eq "You have 33 seeds of this crop."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
110
spec/helpers/gardens_helper_spec.rb
Normal file
110
spec/helpers/gardens_helper_spec.rb
Normal file
@@ -0,0 +1,110 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe GardensHelper do
|
||||
describe "garden description" do
|
||||
it "is missing" do
|
||||
garden = FactoryGirl.create(:garden,
|
||||
description: nil
|
||||
)
|
||||
result = helper.display_garden_description(garden)
|
||||
expect(result).to eq "no description provided."
|
||||
end
|
||||
|
||||
it "is less than 130 characters long" do
|
||||
garden = FactoryGirl.create(:garden,
|
||||
description: 'a' * 20
|
||||
)
|
||||
result = helper.display_garden_description(garden)
|
||||
expect(result).to eq 'a' * 20
|
||||
end
|
||||
|
||||
it "is 130 characters long" do
|
||||
garden = FactoryGirl.create(:garden,
|
||||
description: 'a' * 130
|
||||
)
|
||||
result = helper.display_garden_description(garden)
|
||||
link = link_to("Read more", garden_path(garden))
|
||||
expect(result).to eq 'a' * 130
|
||||
end
|
||||
|
||||
it "is more than 130 characters long" do
|
||||
garden = FactoryGirl.create(:garden,
|
||||
description: 'a' * 140
|
||||
)
|
||||
result = helper.display_garden_description(garden)
|
||||
expect(result).to eq 'a' * 126 + '...' + ' ' + link_to("Read more", garden_path(garden))
|
||||
end
|
||||
end
|
||||
|
||||
describe "garden plantings" do
|
||||
it "is missing" do
|
||||
garden = FactoryGirl.create(:garden)
|
||||
plantings = nil
|
||||
result = helper.display_garden_plantings(plantings)
|
||||
expect(result).to eq "None"
|
||||
end
|
||||
|
||||
it "has 1 planting" do
|
||||
garden = FactoryGirl.create(:garden)
|
||||
plantings = []
|
||||
crop = FactoryGirl.create(:crop)
|
||||
plantings << FactoryGirl.create(:planting, quantity: 10, crop: crop)
|
||||
result = helper.display_garden_plantings(plantings)
|
||||
|
||||
output = "<li>"
|
||||
output += "10 " + link_to(crop.name, crop)
|
||||
output += ", planted on #{plantings.first.planted_at}"
|
||||
output += "</li>"
|
||||
expect(result).to eq output
|
||||
end
|
||||
|
||||
it "has 2 plantings" do
|
||||
garden = FactoryGirl.create(:garden)
|
||||
plantings = []
|
||||
|
||||
crop1 = FactoryGirl.create(:crop)
|
||||
plantings << FactoryGirl.create(:planting, quantity: 10, crop: crop1)
|
||||
|
||||
crop2 = FactoryGirl.create(:crop)
|
||||
plantings << FactoryGirl.create(:planting, quantity: 10, crop: crop2)
|
||||
|
||||
result = helper.display_garden_plantings(plantings)
|
||||
|
||||
output = "<li>"
|
||||
output += "10 " + link_to(crop1.name, crop1)
|
||||
output += ", planted on #{plantings.first.planted_at}"
|
||||
output += "</li>"
|
||||
output += "<li>"
|
||||
output += "10 " + link_to(crop2.name, crop2)
|
||||
output += ", planted on #{plantings.first.planted_at}"
|
||||
output += "</li>"
|
||||
expect(result).to eq output
|
||||
end
|
||||
|
||||
it "has 3 plantings" do
|
||||
garden = FactoryGirl.create(:garden)
|
||||
plantings = []
|
||||
|
||||
crop1 = FactoryGirl.create(:crop)
|
||||
plantings << FactoryGirl.create(:planting, quantity: 10, crop: crop1)
|
||||
|
||||
crop2 = FactoryGirl.create(:crop)
|
||||
plantings << FactoryGirl.create(:planting, quantity: 10, crop: crop2)
|
||||
|
||||
crop3 = FactoryGirl.create(:crop)
|
||||
plantings << FactoryGirl.create(:planting, quantity: 10, crop: crop3)
|
||||
|
||||
result = helper.display_garden_plantings(plantings)
|
||||
|
||||
output = "<li>"
|
||||
output += "10 " + link_to(crop1.name, crop1)
|
||||
output += ", planted on #{plantings.first.planted_at}"
|
||||
output += "</li>"
|
||||
output += "<li>"
|
||||
output += "10 " + link_to(crop2.name, crop2)
|
||||
output += ", planted on #{plantings.first.planted_at}"
|
||||
output += "</li>"
|
||||
expect(result).to eq output
|
||||
end
|
||||
end
|
||||
end
|
||||
38
spec/helpers/seeds_helper_spec.rb
Normal file
38
spec/helpers/seeds_helper_spec.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe SeedsHelper do
|
||||
describe "seed description" do
|
||||
it "is missing" do
|
||||
seed = FactoryGirl.create(:seed,
|
||||
description: nil
|
||||
)
|
||||
result = helper.display_seed_description(seed)
|
||||
expect(result).to eq "no description provided."
|
||||
end
|
||||
|
||||
it "is less than 130 characters long" do
|
||||
seed = FactoryGirl.create(:seed,
|
||||
description: 'a' * 20
|
||||
)
|
||||
result = helper.display_seed_description(seed)
|
||||
expect(result).to eq 'a' * 20
|
||||
end
|
||||
|
||||
it "is 130 characters long" do
|
||||
seed = FactoryGirl.create(:seed,
|
||||
description: 'a' * 130
|
||||
)
|
||||
result = helper.display_seed_description(seed)
|
||||
link = link_to("Read more", seed_path(seed))
|
||||
expect(result).to eq 'a' * 130
|
||||
end
|
||||
|
||||
it "is more than 130 characters long" do
|
||||
seed = FactoryGirl.create(:seed,
|
||||
description: 'a' * 140
|
||||
)
|
||||
result = helper.display_seed_description(seed)
|
||||
expect(result).to eq 'a' * 126 + '...' + ' ' + link_to("Read more", seed_path(seed))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,26 +1,27 @@
|
||||
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
||||
ENV["RAILS_ENV"] ||= 'test'
|
||||
require 'spec_helper'
|
||||
require File.expand_path("../../config/environment", __FILE__)
|
||||
require 'rspec/rails'
|
||||
# Add additional requires below this line. Rails is not loaded until this point!
|
||||
require 'simplecov'
|
||||
require 'coveralls'
|
||||
|
||||
# output coverage locally AND send it to coveralls
|
||||
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
||||
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
||||
SimpleCov::Formatter::HTMLFormatter,
|
||||
Coveralls::SimpleCov::Formatter
|
||||
]
|
||||
])
|
||||
|
||||
# fail if there's a significant test coverage drop
|
||||
SimpleCov.maximum_coverage_drop 1
|
||||
|
||||
SimpleCov.start :rails do
|
||||
add_filter 'spec/'
|
||||
add_filter 'vendor/'
|
||||
end
|
||||
|
||||
require 'spec_helper'
|
||||
require File.expand_path("../../config/environment", __FILE__)
|
||||
require 'rspec/rails'
|
||||
# Add additional requires below this line. Rails is not loaded until this point!
|
||||
Rails.application.eager_load!
|
||||
|
||||
require 'capybara'
|
||||
require 'capybara/poltergeist'
|
||||
require 'capybara/rspec'
|
||||
|
||||
@@ -129,8 +129,75 @@ describe "posts/_single" do
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "when post has been edited" do
|
||||
before(:each) do
|
||||
@member = FactoryGirl.create(:member)
|
||||
sign_in @member
|
||||
controller.stub(:current_user) { @member }
|
||||
@post = FactoryGirl.create(:post, :author => @member)
|
||||
@post.update(body: "I am updated")
|
||||
render_post
|
||||
end
|
||||
|
||||
it "shows edited at" do
|
||||
rendered.should have_content "edited at"
|
||||
end
|
||||
|
||||
it "shows the updated time" do
|
||||
rendered.should have_content @post.updated_at
|
||||
end
|
||||
end
|
||||
|
||||
context "when comment has been edited" do
|
||||
before(:each) do
|
||||
@member = FactoryGirl.create(:member)
|
||||
sign_in @member
|
||||
controller.stub(:current_user) { @member }
|
||||
@post = FactoryGirl.create(:post, :author => @member)
|
||||
@comment = FactoryGirl.create(:comment, :post => @post)
|
||||
@comment.update(body: "I've been updated")
|
||||
render :partial => "comments/single", :locals => { :comment => @comment }
|
||||
end
|
||||
|
||||
it "shows edited at time" do
|
||||
rendered.should have_content "edited at"
|
||||
end
|
||||
|
||||
it "shows updated time" do
|
||||
rendered.should have_content @comment.updated_at
|
||||
end
|
||||
end
|
||||
|
||||
context "when post has not been edited" do
|
||||
before(:each) do
|
||||
@member = FactoryGirl.create(:member)
|
||||
sign_in @member
|
||||
controller.stub(:current_user) { @member }
|
||||
@post = FactoryGirl.create(:post, :author => @member)
|
||||
@post.update(updated_at: @post.created_at)
|
||||
render_post
|
||||
end
|
||||
|
||||
it "does not show edited at" do
|
||||
rendered.should_not have_content "edited at #{@post.updated_at}"
|
||||
end
|
||||
end
|
||||
|
||||
context "when comment has not been edited" do
|
||||
before(:each) do
|
||||
@member = FactoryGirl.create(:member)
|
||||
sign_in @member
|
||||
controller.stub(:current_user) { @member }
|
||||
@post = FactoryGirl.create(:post, :author => @member)
|
||||
@comment = FactoryGirl.create(:comment, :post => @post)
|
||||
@comment.update(updated_at: @comment.created_at)
|
||||
render :partial => "comments/single", :locals => { :comment => @comment }
|
||||
end
|
||||
|
||||
it "does not show edited at" do
|
||||
rendered.should_not have_content "edited at #{@comment.updated_at}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
0
vendor/assets/javascripts/.gitkeep
vendored
0
vendor/assets/javascripts/.gitkeep
vendored
0
vendor/assets/stylesheets/.gitkeep
vendored
0
vendor/assets/stylesheets/.gitkeep
vendored
5
vendor/gems/active_utils-1.0.5/.gitignore
vendored
5
vendor/gems/active_utils-1.0.5/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
pkg/*
|
||||
*.gem
|
||||
.bundle
|
||||
.DS_Store
|
||||
Gemfile.lock
|
||||
3
vendor/gems/active_utils-1.0.5/Gemfile
vendored
3
vendor/gems/active_utils-1.0.5/Gemfile
vendored
@@ -1,3 +0,0 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gemspec
|
||||
20
vendor/gems/active_utils-1.0.5/MIT-LICENSE
vendored
20
vendor/gems/active_utils-1.0.5/MIT-LICENSE
vendored
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2011 Shopify
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
15
vendor/gems/active_utils-1.0.5/README.md
vendored
15
vendor/gems/active_utils-1.0.5/README.md
vendored
@@ -1,15 +0,0 @@
|
||||
# Active Utils
|
||||
|
||||
Active Utils extracts commonly used modules and classes used by [Active Merchant](http://github.com/Shopify/active_merchant), [Active Shipping](http://github.com/Shopify/active_shipping), and [Active Fulfillment](http://github.com/Shopify/active_fulfillment).
|
||||
|
||||
### Includes
|
||||
|
||||
* Connection - base class for making HTTP requests
|
||||
* Country - find countries mapped by name, country codes, and numeric values
|
||||
* Error - common error classes used throughout the Active projects
|
||||
* PostData - helper class for managing required fields that are to be POST-ed
|
||||
* PostsData - making SSL HTTP requests
|
||||
* RequiresParameters - helper method to ensure the required parameters are passed in
|
||||
* Utils - common utils such as uid generator
|
||||
* Validateable - module used for making models validateable
|
||||
* NetworkConnectionRetries - module for retrying network connections when connection errors occur
|
||||
13
vendor/gems/active_utils-1.0.5/Rakefile
vendored
13
vendor/gems/active_utils-1.0.5/Rakefile
vendored
@@ -1,13 +0,0 @@
|
||||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
require 'rake/testtask'
|
||||
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.pattern = 'test/unit/**/*_test.rb'
|
||||
t.ruby_opts << '-rubygems'
|
||||
t.libs << 'test'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
task :default => "test"
|
||||
@@ -1,26 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
require "active_utils/version"
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "active_utils"
|
||||
s.version = ActiveUtils::VERSION
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.authors = ["Shopify"]
|
||||
s.email = ["developers@jadedpixel.com"]
|
||||
s.homepage = "http://github.com/shopify/active_utils"
|
||||
s.summary = %q{Common utils used by active_merchant, active_fulfillment, and active_shipping}
|
||||
|
||||
s.rubyforge_project = "active_utils"
|
||||
|
||||
s.add_dependency('activesupport', '>= 2.3.11')
|
||||
s.add_dependency('i18n')
|
||||
|
||||
s.add_development_dependency('rake')
|
||||
s.add_development_dependency('mocha')
|
||||
|
||||
s.files = `git ls-files`.split("\n")
|
||||
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
||||
s.require_paths = ["lib"]
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
require 'active_support/core_ext/hash/indifferent_access'
|
||||
require 'active_support/core_ext/hash/conversions'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
|
||||
module ActiveMerchant
|
||||
autoload :NetworkConnectionRetries, 'active_utils/common/network_connection_retries'
|
||||
autoload :Connection, 'active_utils/common/connection'
|
||||
autoload :Country, 'active_utils/common/country'
|
||||
autoload :CountryCode, 'active_utils/common/country'
|
||||
autoload :ActiveMerchantError, 'active_utils/common/error'
|
||||
autoload :ConnectionError, 'active_utils/common/error'
|
||||
autoload :RetriableConnectionError, 'active_utils/common/error'
|
||||
autoload :ResponseError, 'active_utils/common/error'
|
||||
autoload :ClientCertificateError, 'active_utils/common/error'
|
||||
autoload :PostData, 'active_utils/common/post_data'
|
||||
autoload :PostsData, 'active_utils/common/posts_data'
|
||||
autoload :RequiresParameters, 'active_utils/common/requires_parameters'
|
||||
autoload :Utils, 'active_utils/common/utils'
|
||||
autoload :Validateable, 'active_utils/common/validateable'
|
||||
end
|
||||
@@ -1,147 +0,0 @@
|
||||
require 'uri'
|
||||
require 'net/http'
|
||||
require 'net/https'
|
||||
require 'benchmark'
|
||||
|
||||
module ActiveMerchant
|
||||
class Connection
|
||||
include NetworkConnectionRetries
|
||||
|
||||
MAX_RETRIES = 3
|
||||
OPEN_TIMEOUT = 60
|
||||
READ_TIMEOUT = 60
|
||||
VERIFY_PEER = true
|
||||
RETRY_SAFE = false
|
||||
RUBY_184_POST_HEADERS = { "Content-Type" => "application/x-www-form-urlencoded" }
|
||||
|
||||
attr_accessor :endpoint
|
||||
attr_accessor :open_timeout
|
||||
attr_accessor :read_timeout
|
||||
attr_accessor :verify_peer
|
||||
attr_accessor :pem
|
||||
attr_accessor :pem_password
|
||||
attr_accessor :wiredump_device
|
||||
attr_accessor :logger
|
||||
attr_accessor :tag
|
||||
attr_accessor :ignore_http_status
|
||||
|
||||
def initialize(endpoint)
|
||||
@endpoint = endpoint.is_a?(URI) ? endpoint : URI.parse(endpoint)
|
||||
@open_timeout = OPEN_TIMEOUT
|
||||
@read_timeout = READ_TIMEOUT
|
||||
@retry_safe = RETRY_SAFE
|
||||
@verify_peer = VERIFY_PEER
|
||||
@ignore_http_status = false
|
||||
end
|
||||
|
||||
def request(method, body, headers = {})
|
||||
retry_exceptions(:max_retries => MAX_RETRIES, :logger => logger, :tag => tag) do
|
||||
begin
|
||||
info "#{method.to_s.upcase} #{endpoint}", tag
|
||||
|
||||
result = nil
|
||||
|
||||
realtime = Benchmark.realtime do
|
||||
result = case method
|
||||
when :get
|
||||
raise ArgumentError, "GET requests do not support a request body" if body
|
||||
http.get(endpoint.request_uri, headers)
|
||||
when :post
|
||||
debug body
|
||||
http.post(endpoint.request_uri, body, RUBY_184_POST_HEADERS.merge(headers))
|
||||
when :put
|
||||
debug body
|
||||
http.put(endpoint.request_uri, body, headers)
|
||||
when :delete
|
||||
# It's kind of ambiguous whether the RFC allows bodies
|
||||
# for DELETE requests. But Net::HTTP's delete method
|
||||
# very unambiguously does not.
|
||||
raise ArgumentError, "DELETE requests do not support a request body" if body
|
||||
http.delete(endpoint.request_uri, headers)
|
||||
else
|
||||
raise ArgumentError, "Unsupported request method #{method.to_s.upcase}"
|
||||
end
|
||||
end
|
||||
|
||||
info "--> %d %s (%d %.4fs)" % [result.code, result.message, result.body ? result.body.length : 0, realtime], tag
|
||||
debug result.body
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def http
|
||||
http = Net::HTTP.new(endpoint.host, endpoint.port)
|
||||
configure_debugging(http)
|
||||
configure_timeouts(http)
|
||||
configure_ssl(http)
|
||||
configure_cert(http)
|
||||
http
|
||||
end
|
||||
|
||||
def configure_debugging(http)
|
||||
http.set_debug_output(wiredump_device)
|
||||
end
|
||||
|
||||
def configure_timeouts(http)
|
||||
http.open_timeout = open_timeout
|
||||
http.read_timeout = read_timeout
|
||||
end
|
||||
|
||||
def configure_ssl(http)
|
||||
return unless endpoint.scheme == "https"
|
||||
|
||||
http.use_ssl = true
|
||||
|
||||
if verify_peer
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
http.ca_file = File.dirname(__FILE__) + '/../../certs/cacert.pem'
|
||||
else
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
end
|
||||
|
||||
def configure_cert(http)
|
||||
return if pem.blank?
|
||||
|
||||
http.cert = OpenSSL::X509::Certificate.new(pem)
|
||||
|
||||
if pem_password
|
||||
http.key = OpenSSL::PKey::RSA.new(pem, pem_password)
|
||||
else
|
||||
http.key = OpenSSL::PKey::RSA.new(pem)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
if @ignore_http_status then
|
||||
return response.body
|
||||
else
|
||||
case response.code.to_i
|
||||
when 200...300
|
||||
response.body
|
||||
else
|
||||
raise ResponseError.new(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def debug(message, tag = nil)
|
||||
log(:debug, message, tag)
|
||||
end
|
||||
|
||||
def info(message, tag = nil)
|
||||
log(:info, message, tag)
|
||||
end
|
||||
|
||||
def error(message, tag = nil)
|
||||
log(:error, message, tag)
|
||||
end
|
||||
|
||||
def log(level, message, tag)
|
||||
message = "[#{tag}] #{message}" if tag
|
||||
logger.send(level, message) if logger
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,328 +0,0 @@
|
||||
#!ruby19
|
||||
# encoding: utf-8
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
class InvalidCountryCodeError < StandardError
|
||||
end
|
||||
|
||||
class CountryCodeFormatError < StandardError
|
||||
end
|
||||
|
||||
class CountryCode
|
||||
attr_reader :value, :format
|
||||
def initialize(value)
|
||||
@value = value.to_s.upcase
|
||||
detect_format
|
||||
end
|
||||
|
||||
def to_s
|
||||
value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def detect_format
|
||||
case @value
|
||||
when /^[[:alpha:]]{2}$/
|
||||
@format = :alpha2
|
||||
when /^[[:alpha:]]{3}$/
|
||||
@format = :alpha3
|
||||
when /^[[:digit:]]{3}$/
|
||||
@format = :numeric
|
||||
else
|
||||
raise CountryCodeFormatError, "The country code is not formatted correctly #{@value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Country
|
||||
include RequiresParameters
|
||||
attr_reader :name
|
||||
|
||||
def initialize(options = {})
|
||||
requires!(options, :name, :alpha2, :alpha3, :numeric)
|
||||
@name = options.delete(:name)
|
||||
@codes = options.collect{|k,v| CountryCode.new(v)}
|
||||
end
|
||||
|
||||
def code(format)
|
||||
@codes.detect{|c| c.format == format}
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
(@name == other.name)
|
||||
end
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
@name.hash
|
||||
end
|
||||
|
||||
def to_s
|
||||
@name
|
||||
end
|
||||
|
||||
COUNTRIES = [
|
||||
{ :alpha2 => 'AF', :name => 'Afghanistan', :alpha3 => 'AFG', :numeric => '004' },
|
||||
{ :alpha2 => 'AL', :name => 'Albania', :alpha3 => 'ALB', :numeric => '008' },
|
||||
{ :alpha2 => 'DZ', :name => 'Algeria', :alpha3 => 'DZA', :numeric => '012' },
|
||||
{ :alpha2 => 'AS', :name => 'American Samoa', :alpha3 => 'ASM', :numeric => '016' },
|
||||
{ :alpha2 => 'AD', :name => 'Andorra', :alpha3 => 'AND', :numeric => '020' },
|
||||
{ :alpha2 => 'AO', :name => 'Angola', :alpha3 => 'AGO', :numeric => '024' },
|
||||
{ :alpha2 => 'AI', :name => 'Anguilla', :alpha3 => 'AIA', :numeric => '660' },
|
||||
{ :alpha2 => 'AG', :name => 'Antigua and Barbuda', :alpha3 => 'ATG', :numeric => '028' },
|
||||
{ :alpha2 => 'AR', :name => 'Argentina', :alpha3 => 'ARG', :numeric => '032' },
|
||||
{ :alpha2 => 'AM', :name => 'Armenia', :alpha3 => 'ARM', :numeric => '051' },
|
||||
{ :alpha2 => 'AW', :name => 'Aruba', :alpha3 => 'ABW', :numeric => '533' },
|
||||
{ :alpha2 => 'AU', :name => 'Australia', :alpha3 => 'AUS', :numeric => '036' },
|
||||
{ :alpha2 => 'AT', :name => 'Austria', :alpha3 => 'AUT', :numeric => '040' },
|
||||
{ :alpha2 => 'AZ', :name => 'Azerbaijan', :alpha3 => 'AZE', :numeric => '031' },
|
||||
{ :alpha2 => 'BS', :name => 'Bahamas', :alpha3 => 'BHS', :numeric => '044' },
|
||||
{ :alpha2 => 'BH', :name => 'Bahrain', :alpha3 => 'BHR', :numeric => '048' },
|
||||
{ :alpha2 => 'BD', :name => 'Bangladesh', :alpha3 => 'BGD', :numeric => '050' },
|
||||
{ :alpha2 => 'BB', :name => 'Barbados', :alpha3 => 'BRB', :numeric => '052' },
|
||||
{ :alpha2 => 'BY', :name => 'Belarus', :alpha3 => 'BLR', :numeric => '112' },
|
||||
{ :alpha2 => 'BE', :name => 'Belgium', :alpha3 => 'BEL', :numeric => '056' },
|
||||
{ :alpha2 => 'BZ', :name => 'Belize', :alpha3 => 'BLZ', :numeric => '084' },
|
||||
{ :alpha2 => 'BJ', :name => 'Benin', :alpha3 => 'BEN', :numeric => '204' },
|
||||
{ :alpha2 => 'BM', :name => 'Bermuda', :alpha3 => 'BMU', :numeric => '060' },
|
||||
{ :alpha2 => 'BT', :name => 'Bhutan', :alpha3 => 'BTN', :numeric => '064' },
|
||||
{ :alpha2 => 'BO', :name => 'Bolivia', :alpha3 => 'BOL', :numeric => '068' },
|
||||
{ :alpha2 => 'BA', :name => 'Bosnia and Herzegovina', :alpha3 => 'BIH', :numeric => '070' },
|
||||
{ :alpha2 => 'BW', :name => 'Botswana', :alpha3 => 'BWA', :numeric => '072' },
|
||||
{ :alpha2 => 'BV', :name => 'Bouvet Island', :alpha3 => 'BVD', :numeric => '074' },
|
||||
{ :alpha2 => 'BR', :name => 'Brazil', :alpha3 => 'BRA', :numeric => '076' },
|
||||
{ :alpha2 => 'IO', :name => 'British Indian Ocean Territory', :alpha3 => 'IOT', :numeric => '086' },
|
||||
{ :alpha2 => 'BN', :name => 'Brunei Darussalam', :alpha3 => 'BRN', :numeric => '096' },
|
||||
{ :alpha2 => 'BG', :name => 'Bulgaria', :alpha3 => 'BGR', :numeric => '100' },
|
||||
{ :alpha2 => 'BF', :name => 'Burkina Faso', :alpha3 => 'BFA', :numeric => '854' },
|
||||
{ :alpha2 => 'BI', :name => 'Burundi', :alpha3 => 'BDI', :numeric => '108' },
|
||||
{ :alpha2 => 'KH', :name => 'Cambodia', :alpha3 => 'KHM', :numeric => '116' },
|
||||
{ :alpha2 => 'CM', :name => 'Cameroon', :alpha3 => 'CMR', :numeric => '120' },
|
||||
{ :alpha2 => 'CA', :name => 'Canada', :alpha3 => 'CAN', :numeric => '124' },
|
||||
{ :alpha2 => 'CV', :name => 'Cape Verde', :alpha3 => 'CPV', :numeric => '132' },
|
||||
{ :alpha2 => 'KY', :name => 'Cayman Islands', :alpha3 => 'CYM', :numeric => '136' },
|
||||
{ :alpha2 => 'CF', :name => 'Central African Republic', :alpha3 => 'CAF', :numeric => '140' },
|
||||
{ :alpha2 => 'TD', :name => 'Chad', :alpha3 => 'TCD', :numeric => '148' },
|
||||
{ :alpha2 => 'CL', :name => 'Chile', :alpha3 => 'CHL', :numeric => '152' },
|
||||
{ :alpha2 => 'CN', :name => 'China', :alpha3 => 'CHN', :numeric => '156' },
|
||||
{ :alpha2 => 'CX', :name => 'Christmas Island', :alpha3 => 'CXR', :numeric => '162' },
|
||||
{ :alpha2 => 'CC', :name => 'Cocos (Keeling) Islands', :alpha3 => 'CCK', :numeric => '166' },
|
||||
{ :alpha2 => 'CO', :name => 'Colombia', :alpha3 => 'COL', :numeric => '170' },
|
||||
{ :alpha2 => 'KM', :name => 'Comoros', :alpha3 => 'COM', :numeric => '174' },
|
||||
{ :alpha2 => 'CG', :name => 'Congo', :alpha3 => 'COG', :numeric => '178' },
|
||||
{ :alpha2 => 'CD', :name => 'Congo, the Democratic Republic of the', :alpha3 => 'COD', :numeric => '180' },
|
||||
{ :alpha2 => 'CK', :name => 'Cook Islands', :alpha3 => 'COK', :numeric => '184' },
|
||||
{ :alpha2 => 'CR', :name => 'Costa Rica', :alpha3 => 'CRI', :numeric => '188' },
|
||||
{ :alpha2 => 'CI', :name => 'Cote D\'Ivoire', :alpha3 => 'CIV', :numeric => '384' },
|
||||
{ :alpha2 => 'HR', :name => 'Croatia', :alpha3 => 'HRV', :numeric => '191' },
|
||||
{ :alpha2 => 'CU', :name => 'Cuba', :alpha3 => 'CUB', :numeric => '192' },
|
||||
{ :alpha2 => 'CY', :name => 'Cyprus', :alpha3 => 'CYP', :numeric => '196' },
|
||||
{ :alpha2 => 'CZ', :name => 'Czech Republic', :alpha3 => 'CZE', :numeric => '203' },
|
||||
{ :alpha2 => 'DK', :name => 'Denmark', :alpha3 => 'DNK', :numeric => '208' },
|
||||
{ :alpha2 => 'DJ', :name => 'Djibouti', :alpha3 => 'DJI', :numeric => '262' },
|
||||
{ :alpha2 => 'DM', :name => 'Dominica', :alpha3 => 'DMA', :numeric => '212' },
|
||||
{ :alpha2 => 'DO', :name => 'Dominican Republic', :alpha3 => 'DOM', :numeric => '214' },
|
||||
{ :alpha2 => 'EC', :name => 'Ecuador', :alpha3 => 'ECU', :numeric => '218' },
|
||||
{ :alpha2 => 'EG', :name => 'Egypt', :alpha3 => 'EGY', :numeric => '818' },
|
||||
{ :alpha2 => 'SV', :name => 'El Salvador', :alpha3 => 'SLV', :numeric => '222' },
|
||||
{ :alpha2 => 'GQ', :name => 'Equatorial Guinea', :alpha3 => 'GNQ', :numeric => '226' },
|
||||
{ :alpha2 => 'ER', :name => 'Eritrea', :alpha3 => 'ERI', :numeric => '232' },
|
||||
{ :alpha2 => 'EE', :name => 'Estonia', :alpha3 => 'EST', :numeric => '233' },
|
||||
{ :alpha2 => 'ET', :name => 'Ethiopia', :alpha3 => 'ETH', :numeric => '231' },
|
||||
{ :alpha2 => 'FK', :name => 'Falkland Islands (Malvinas)', :alpha3 => 'FLK', :numeric => '238' },
|
||||
{ :alpha2 => 'FO', :name => 'Faroe Islands', :alpha3 => 'FRO', :numeric => '234' },
|
||||
{ :alpha2 => 'FJ', :name => 'Fiji', :alpha3 => 'FJI', :numeric => '242' },
|
||||
{ :alpha2 => 'FI', :name => 'Finland', :alpha3 => 'FIN', :numeric => '246' },
|
||||
{ :alpha2 => 'FR', :name => 'France', :alpha3 => 'FRA', :numeric => '250' },
|
||||
{ :alpha2 => 'GF', :name => 'French Guiana', :alpha3 => 'GUF', :numeric => '254' },
|
||||
{ :alpha2 => 'PF', :name => 'French Polynesia', :alpha3 => 'PYF', :numeric => '258' },
|
||||
{ :alpha2 => 'TF', :name => 'French Southern Territories', :alpha3 => 'ATF', :numeric => '260' },
|
||||
{ :alpha2 => 'GA', :name => 'Gabon', :alpha3 => 'GAB', :numeric => '266' },
|
||||
{ :alpha2 => 'GM', :name => 'Gambia', :alpha3 => 'GMB', :numeric => '270' },
|
||||
{ :alpha2 => 'GE', :name => 'Georgia', :alpha3 => 'GEO', :numeric => '268' },
|
||||
{ :alpha2 => 'DE', :name => 'Germany', :alpha3 => 'DEU', :numeric => '276' },
|
||||
{ :alpha2 => 'GH', :name => 'Ghana', :alpha3 => 'GHA', :numeric => '288' },
|
||||
{ :alpha2 => 'GI', :name => 'Gibraltar', :alpha3 => 'GIB', :numeric => '292' },
|
||||
{ :alpha2 => 'GR', :name => 'Greece', :alpha3 => 'GRC', :numeric => '300' },
|
||||
{ :alpha2 => 'GL', :name => 'Greenland', :alpha3 => 'GRL', :numeric => '304' },
|
||||
{ :alpha2 => 'GD', :name => 'Grenada', :alpha3 => 'GRD', :numeric => '308' },
|
||||
{ :alpha2 => 'GP', :name => 'Guadeloupe', :alpha3 => 'GLP', :numeric => '312' },
|
||||
{ :alpha2 => 'GU', :name => 'Guam', :alpha3 => 'GUM', :numeric => '316' },
|
||||
{ :alpha2 => 'GT', :name => 'Guatemala', :alpha3 => 'GTM', :numeric => '320' },
|
||||
{ :alpha2 => 'GG', :name => 'Guernsey', :alpha3 => 'GGY', :numeric => '831' },
|
||||
{ :alpha2 => 'GN', :name => 'Guinea', :alpha3 => 'GIN', :numeric => '324' },
|
||||
{ :alpha2 => 'GW', :name => 'Guinea-Bissau', :alpha3 => 'GNB', :numeric => '624' },
|
||||
{ :alpha2 => 'GY', :name => 'Guyana', :alpha3 => 'GUY', :numeric => '328' },
|
||||
{ :alpha2 => 'HT', :name => 'Haiti', :alpha3 => 'HTI', :numeric => '332' },
|
||||
{ :alpha2 => 'HM', :name => 'Heard Island And Mcdonald Islands', :alpha3 => 'HMD', :numeric => '334' },
|
||||
{ :alpha2 => 'VA', :name => 'Holy See (Vatican City State)', :alpha3 => 'VAT', :numeric => '336' },
|
||||
{ :alpha2 => 'HN', :name => 'Honduras', :alpha3 => 'HND', :numeric => '340' },
|
||||
{ :alpha2 => 'HK', :name => 'Hong Kong', :alpha3 => 'HKG', :numeric => '344' },
|
||||
{ :alpha2 => 'HU', :name => 'Hungary', :alpha3 => 'HUN', :numeric => '348' },
|
||||
{ :alpha2 => 'IS', :name => 'Iceland', :alpha3 => 'ISL', :numeric => '352' },
|
||||
{ :alpha2 => 'IN', :name => 'India', :alpha3 => 'IND', :numeric => '356' },
|
||||
{ :alpha2 => 'ID', :name => 'Indonesia', :alpha3 => 'IDN', :numeric => '360' },
|
||||
{ :alpha2 => 'IR', :name => 'Iran, Islamic Republic of', :alpha3 => 'IRN', :numeric => '364' },
|
||||
{ :alpha2 => 'IQ', :name => 'Iraq', :alpha3 => 'IRQ', :numeric => '368' },
|
||||
{ :alpha2 => 'IE', :name => 'Ireland', :alpha3 => 'IRL', :numeric => '372' },
|
||||
{ :alpha2 => 'IM', :name => 'Isle Of Man', :alpha3 => 'IMN', :numeric => '833' },
|
||||
{ :alpha2 => 'IL', :name => 'Israel', :alpha3 => 'ISR', :numeric => '376' },
|
||||
{ :alpha2 => 'IT', :name => 'Italy', :alpha3 => 'ITA', :numeric => '380' },
|
||||
{ :alpha2 => 'JM', :name => 'Jamaica', :alpha3 => 'JAM', :numeric => '388' },
|
||||
{ :alpha2 => 'JP', :name => 'Japan', :alpha3 => 'JPN', :numeric => '392' },
|
||||
{ :alpha2 => 'JE', :name => 'Jersey', :alpha3 => 'JEY', :numeric => '832' },
|
||||
{ :alpha2 => 'JO', :name => 'Jordan', :alpha3 => 'JOR', :numeric => '400' },
|
||||
{ :alpha2 => 'KZ', :name => 'Kazakhstan', :alpha3 => 'KAZ', :numeric => '398' },
|
||||
{ :alpha2 => 'KE', :name => 'Kenya', :alpha3 => 'KEN', :numeric => '404' },
|
||||
{ :alpha2 => 'KI', :name => 'Kiribati', :alpha3 => 'KIR', :numeric => '296' },
|
||||
{ :alpha2 => 'KP', :name => 'Korea, Democratic People\'s Republic of', :alpha3 => 'PRK', :numeric => '408' },
|
||||
{ :alpha2 => 'KR', :name => 'Korea, Republic of', :alpha3 => 'KOR', :numeric => '410' },
|
||||
{ :alpha2 => 'KW', :name => 'Kuwait', :alpha3 => 'KWT', :numeric => '414' },
|
||||
{ :alpha2 => 'KG', :name => 'Kyrgyzstan', :alpha3 => 'KGZ', :numeric => '417' },
|
||||
{ :alpha2 => 'LA', :name => 'Lao People\'s Democratic Republic', :alpha3 => 'LAO', :numeric => '418' },
|
||||
{ :alpha2 => 'LV', :name => 'Latvia', :alpha3 => 'LVA', :numeric => '428' },
|
||||
{ :alpha2 => 'LB', :name => 'Lebanon', :alpha3 => 'LBN', :numeric => '422' },
|
||||
{ :alpha2 => 'LS', :name => 'Lesotho', :alpha3 => 'LSO', :numeric => '426' },
|
||||
{ :alpha2 => 'LR', :name => 'Liberia', :alpha3 => 'LBR', :numeric => '430' },
|
||||
{ :alpha2 => 'LY', :name => 'Libyan Arab Jamahiriya', :alpha3 => 'LBY', :numeric => '434' },
|
||||
{ :alpha2 => 'LI', :name => 'Liechtenstein', :alpha3 => 'LIE', :numeric => '438' },
|
||||
{ :alpha2 => 'LT', :name => 'Lithuania', :alpha3 => 'LTU', :numeric => '440' },
|
||||
{ :alpha2 => 'LU', :name => 'Luxembourg', :alpha3 => 'LUX', :numeric => '442' },
|
||||
{ :alpha2 => 'MO', :name => 'Macao', :alpha3 => 'MAC', :numeric => '446' },
|
||||
{ :alpha2 => 'MK', :name => 'Macedonia, the Former Yugoslav Republic of', :alpha3 => 'MKD', :numeric => '807' },
|
||||
{ :alpha2 => 'MG', :name => 'Madagascar', :alpha3 => 'MDG', :numeric => '450' },
|
||||
{ :alpha2 => 'MW', :name => 'Malawi', :alpha3 => 'MWI', :numeric => '454' },
|
||||
{ :alpha2 => 'MY', :name => 'Malaysia', :alpha3 => 'MYS', :numeric => '458' },
|
||||
{ :alpha2 => 'MV', :name => 'Maldives', :alpha3 => 'MDV', :numeric => '462' },
|
||||
{ :alpha2 => 'ML', :name => 'Mali', :alpha3 => 'MLI', :numeric => '466' },
|
||||
{ :alpha2 => 'MT', :name => 'Malta', :alpha3 => 'MLT', :numeric => '470' },
|
||||
{ :alpha2 => 'MH', :name => 'Marshall Islands', :alpha3 => 'MHL', :numeric => '584' },
|
||||
{ :alpha2 => 'MQ', :name => 'Martinique', :alpha3 => 'MTQ', :numeric => '474' },
|
||||
{ :alpha2 => 'MR', :name => 'Mauritania', :alpha3 => 'MRT', :numeric => '478' },
|
||||
{ :alpha2 => 'MU', :name => 'Mauritius', :alpha3 => 'MUS', :numeric => '480' },
|
||||
{ :alpha2 => 'YT', :name => 'Mayotte', :alpha3 => 'MYT', :numeric => '175' },
|
||||
{ :alpha2 => 'MX', :name => 'Mexico', :alpha3 => 'MEX', :numeric => '484' },
|
||||
{ :alpha2 => 'FM', :name => 'Micronesia, Federated States of', :alpha3 => 'FSM', :numeric => '583' },
|
||||
{ :alpha2 => 'MD', :name => 'Moldova, Republic of', :alpha3 => 'MDA', :numeric => '498' },
|
||||
{ :alpha2 => 'MC', :name => 'Monaco', :alpha3 => 'MCO', :numeric => '492' },
|
||||
{ :alpha2 => 'MN', :name => 'Mongolia', :alpha3 => 'MNG', :numeric => '496' },
|
||||
{ :alpha2 => 'ME', :name => 'Montenegro', :alpha3 => 'MNE', :numeric => '499' },
|
||||
{ :alpha2 => 'MS', :name => 'Montserrat', :alpha3 => 'MSR', :numeric => '500' },
|
||||
{ :alpha2 => 'MA', :name => 'Morocco', :alpha3 => 'MAR', :numeric => '504' },
|
||||
{ :alpha2 => 'MZ', :name => 'Mozambique', :alpha3 => 'MOZ', :numeric => '508' },
|
||||
{ :alpha2 => 'MM', :name => 'Myanmar', :alpha3 => 'MMR', :numeric => '104' },
|
||||
{ :alpha2 => 'NA', :name => 'Namibia', :alpha3 => 'NAM', :numeric => '516' },
|
||||
{ :alpha2 => 'NR', :name => 'Nauru', :alpha3 => 'NRU', :numeric => '520' },
|
||||
{ :alpha2 => 'NP', :name => 'Nepal', :alpha3 => 'NPL', :numeric => '524' },
|
||||
{ :alpha2 => 'NL', :name => 'Netherlands', :alpha3 => 'NLD', :numeric => '528' },
|
||||
{ :alpha2 => 'AN', :name => 'Netherlands Antilles', :alpha3 => 'ANT', :numeric => '530' },
|
||||
{ :alpha2 => 'NC', :name => 'New Caledonia', :alpha3 => 'NCL', :numeric => '540' },
|
||||
{ :alpha2 => 'NZ', :name => 'New Zealand', :alpha3 => 'NZL', :numeric => '554' },
|
||||
{ :alpha2 => 'NI', :name => 'Nicaragua', :alpha3 => 'NIC', :numeric => '558' },
|
||||
{ :alpha2 => 'NE', :name => 'Niger', :alpha3 => 'NER', :numeric => '562' },
|
||||
{ :alpha2 => 'NG', :name => 'Nigeria', :alpha3 => 'NGA', :numeric => '566' },
|
||||
{ :alpha2 => 'NU', :name => 'Niue', :alpha3 => 'NIU', :numeric => '570' },
|
||||
{ :alpha2 => 'NF', :name => 'Norfolk Island', :alpha3 => 'NFK', :numeric => '574' },
|
||||
{ :alpha2 => 'MP', :name => 'Northern Mariana Islands', :alpha3 => 'MNP', :numeric => '580' },
|
||||
{ :alpha2 => 'NO', :name => 'Norway', :alpha3 => 'NOR', :numeric => '578' },
|
||||
{ :alpha2 => 'OM', :name => 'Oman', :alpha3 => 'OMN', :numeric => '512' },
|
||||
{ :alpha2 => 'PK', :name => 'Pakistan', :alpha3 => 'PAK', :numeric => '586' },
|
||||
{ :alpha2 => 'PW', :name => 'Palau', :alpha3 => 'PLW', :numeric => '585' },
|
||||
{ :alpha2 => 'PS', :name => 'Palestinian Territory, Occupied', :alpha3 => 'PSE', :numeric => '275' },
|
||||
{ :alpha2 => 'PA', :name => 'Panama', :alpha3 => 'PAN', :numeric => '591' },
|
||||
{ :alpha2 => 'PG', :name => 'Papua New Guinea', :alpha3 => 'PNG', :numeric => '598' },
|
||||
{ :alpha2 => 'PY', :name => 'Paraguay', :alpha3 => 'PRY', :numeric => '600' },
|
||||
{ :alpha2 => 'PE', :name => 'Peru', :alpha3 => 'PER', :numeric => '604' },
|
||||
{ :alpha2 => 'PH', :name => 'Philippines', :alpha3 => 'PHL', :numeric => '608' },
|
||||
{ :alpha2 => 'PN', :name => 'Pitcairn', :alpha3 => 'PCN', :numeric => '612' },
|
||||
{ :alpha2 => 'PL', :name => 'Poland', :alpha3 => 'POL', :numeric => '616' },
|
||||
{ :alpha2 => 'PT', :name => 'Portugal', :alpha3 => 'PRT', :numeric => '620' },
|
||||
{ :alpha2 => 'PR', :name => 'Puerto Rico', :alpha3 => 'PRI', :numeric => '630' },
|
||||
{ :alpha2 => 'QA', :name => 'Qatar', :alpha3 => 'QAT', :numeric => '634' },
|
||||
{ :alpha2 => 'RE', :name => 'Reunion', :alpha3 => 'REU', :numeric => '638' },
|
||||
{ :alpha2 => 'RO', :name => 'Romania', :alpha3 => 'ROM', :numeric => '642' },
|
||||
{ :alpha2 => 'RU', :name => 'Russian Federation', :alpha3 => 'RUS', :numeric => '643' },
|
||||
{ :alpha2 => 'RW', :name => 'Rwanda', :alpha3 => 'RWA', :numeric => '646' },
|
||||
{ :alpha2 => 'BL', :name => 'Saint Barthélemy', :alpha3 => 'BLM', :numeric => '652' },
|
||||
{ :alpha2 => 'SH', :name => 'Saint Helena', :alpha3 => 'SHN', :numeric => '654' },
|
||||
{ :alpha2 => 'KN', :name => 'Saint Kitts and Nevis', :alpha3 => 'KNA', :numeric => '659' },
|
||||
{ :alpha2 => 'LC', :name => 'Saint Lucia', :alpha3 => 'LCA', :numeric => '662' },
|
||||
{ :alpha2 => 'MF', :name => 'Saint Martin (French part)', :alpha3 => 'MAF', :numeric => '663' },
|
||||
{ :alpha2 => 'PM', :name => 'Saint Pierre and Miquelon', :alpha3 => 'SPM', :numeric => '666' },
|
||||
{ :alpha2 => 'VC', :name => 'Saint Vincent and the Grenadines', :alpha3 => 'VCT', :numeric => '670' },
|
||||
{ :alpha2 => 'WS', :name => 'Samoa', :alpha3 => 'WSM', :numeric => '882' },
|
||||
{ :alpha2 => 'SM', :name => 'San Marino', :alpha3 => 'SMR', :numeric => '674' },
|
||||
{ :alpha2 => 'ST', :name => 'Sao Tome and Principe', :alpha3 => 'STP', :numeric => '678' },
|
||||
{ :alpha2 => 'SA', :name => 'Saudi Arabia', :alpha3 => 'SAU', :numeric => '682' },
|
||||
{ :alpha2 => 'SN', :name => 'Senegal', :alpha3 => 'SEN', :numeric => '686' },
|
||||
{ :alpha2 => 'RS', :name => 'Serbia', :alpha3 => 'SRB', :numeric => '688' },
|
||||
{ :alpha2 => 'SC', :name => 'Seychelles', :alpha3 => 'SYC', :numeric => '690' },
|
||||
{ :alpha2 => 'SL', :name => 'Sierra Leone', :alpha3 => 'SLE', :numeric => '694' },
|
||||
{ :alpha2 => 'SG', :name => 'Singapore', :alpha3 => 'SGP', :numeric => '702' },
|
||||
{ :alpha2 => 'SK', :name => 'Slovakia', :alpha3 => 'SVK', :numeric => '703' },
|
||||
{ :alpha2 => 'SI', :name => 'Slovenia', :alpha3 => 'SVN', :numeric => '705' },
|
||||
{ :alpha2 => 'SB', :name => 'Solomon Islands', :alpha3 => 'SLB', :numeric => '090' },
|
||||
{ :alpha2 => 'SO', :name => 'Somalia', :alpha3 => 'SOM', :numeric => '706' },
|
||||
{ :alpha2 => 'ZA', :name => 'South Africa', :alpha3 => 'ZAF', :numeric => '710' },
|
||||
{ :alpha2 => 'GS', :name => 'South Georgia and the South Sandwich Islands', :alpha3 => 'SGS', :numeric => '239' },
|
||||
{ :alpha2 => 'ES', :name => 'Spain', :alpha3 => 'ESP', :numeric => '724' },
|
||||
{ :alpha2 => 'LK', :name => 'Sri Lanka', :alpha3 => 'LKA', :numeric => '144' },
|
||||
{ :alpha2 => 'SD', :name => 'Sudan', :alpha3 => 'SDN', :numeric => '736' },
|
||||
{ :alpha2 => 'SR', :name => 'Suriname', :alpha3 => 'SUR', :numeric => '740' },
|
||||
{ :alpha2 => 'SJ', :name => 'Svalbard and Jan Mayen', :alpha3 => 'SJM', :numeric => '744' },
|
||||
{ :alpha2 => 'SZ', :name => 'Swaziland', :alpha3 => 'SWZ', :numeric => '748' },
|
||||
{ :alpha2 => 'SE', :name => 'Sweden', :alpha3 => 'SWE', :numeric => '752' },
|
||||
{ :alpha2 => 'CH', :name => 'Switzerland', :alpha3 => 'CHE', :numeric => '756' },
|
||||
{ :alpha2 => 'SY', :name => 'Syrian Arab Republic', :alpha3 => 'SYR', :numeric => '760' },
|
||||
{ :alpha2 => 'TW', :name => 'Taiwan, Province of China', :alpha3 => 'TWN', :numeric => '158' },
|
||||
{ :alpha2 => 'TJ', :name => 'Tajikistan', :alpha3 => 'TJK', :numeric => '762' },
|
||||
{ :alpha2 => 'TZ', :name => 'Tanzania, United Republic of', :alpha3 => 'TZA', :numeric => '834' },
|
||||
{ :alpha2 => 'TH', :name => 'Thailand', :alpha3 => 'THA', :numeric => '764' },
|
||||
{ :alpha2 => 'TL', :name => 'Timor Leste', :alpha3 => 'TLS', :numeric => '626' },
|
||||
{ :alpha2 => 'TG', :name => 'Togo', :alpha3 => 'TGO', :numeric => '768' },
|
||||
{ :alpha2 => 'TK', :name => 'Tokelau', :alpha3 => 'TKL', :numeric => '772' },
|
||||
{ :alpha2 => 'TO', :name => 'Tonga', :alpha3 => 'TON', :numeric => '776' },
|
||||
{ :alpha2 => 'TT', :name => 'Trinidad and Tobago', :alpha3 => 'TTO', :numeric => '780' },
|
||||
{ :alpha2 => 'TN', :name => 'Tunisia', :alpha3 => 'TUN', :numeric => '788' },
|
||||
{ :alpha2 => 'TR', :name => 'Turkey', :alpha3 => 'TUR', :numeric => '792' },
|
||||
{ :alpha2 => 'TM', :name => 'Turkmenistan', :alpha3 => 'TKM', :numeric => '795' },
|
||||
{ :alpha2 => 'TC', :name => 'Turks and Caicos Islands', :alpha3 => 'TCA', :numeric => '796' },
|
||||
{ :alpha2 => 'TV', :name => 'Tuvalu', :alpha3 => 'TUV', :numeric => '798' },
|
||||
{ :alpha2 => 'UG', :name => 'Uganda', :alpha3 => 'UGA', :numeric => '800' },
|
||||
{ :alpha2 => 'UA', :name => 'Ukraine', :alpha3 => 'UKR', :numeric => '804' },
|
||||
{ :alpha2 => 'AE', :name => 'United Arab Emirates', :alpha3 => 'ARE', :numeric => '784' },
|
||||
{ :alpha2 => 'GB', :name => 'United Kingdom', :alpha3 => 'GBR', :numeric => '826' },
|
||||
{ :alpha2 => 'US', :name => 'United States', :alpha3 => 'USA', :numeric => '840' },
|
||||
{ :alpha2 => 'UM', :name => 'United States Minor Outlying Islands', :alpha3 => 'UMI', :numeric => '581' },
|
||||
{ :alpha2 => 'UY', :name => 'Uruguay', :alpha3 => 'URY', :numeric => '858' },
|
||||
{ :alpha2 => 'UZ', :name => 'Uzbekistan', :alpha3 => 'UZB', :numeric => '860' },
|
||||
{ :alpha2 => 'VU', :name => 'Vanuatu', :alpha3 => 'VUT', :numeric => '548' },
|
||||
{ :alpha2 => 'VE', :name => 'Venezuela', :alpha3 => 'VEN', :numeric => '862' },
|
||||
{ :alpha2 => 'VN', :name => 'Viet Nam', :alpha3 => 'VNM', :numeric => '704' },
|
||||
{ :alpha2 => 'VG', :name => 'Virgin Islands, British', :alpha3 => 'VGB', :numeric => '092' },
|
||||
{ :alpha2 => 'VI', :name => 'Virgin Islands, U.S.', :alpha3 => 'VIR', :numeric => '850' },
|
||||
{ :alpha2 => 'WF', :name => 'Wallis and Futuna', :alpha3 => 'WLF', :numeric => '876' },
|
||||
{ :alpha2 => 'EH', :name => 'Western Sahara', :alpha3 => 'ESH', :numeric => '732' },
|
||||
{ :alpha2 => 'YE', :name => 'Yemen', :alpha3 => 'YEM', :numeric => '887' },
|
||||
{ :alpha2 => 'ZM', :name => 'Zambia', :alpha3 => 'ZMB', :numeric => '894' },
|
||||
{ :alpha2 => 'ZW', :name => 'Zimbabwe', :alpha3 => 'ZWE', :numeric => '716' },
|
||||
{ :alpha2 => 'AX', :name => 'Åland Islands', :alpha3 => 'ALA', :numeric => '248' }
|
||||
]
|
||||
|
||||
def self.find(name)
|
||||
raise InvalidCountryCodeError, "Cannot lookup country for an empty name" if name.blank?
|
||||
|
||||
case name.length
|
||||
when 2, 3
|
||||
upcase_name = name.upcase
|
||||
country_code = CountryCode.new(name)
|
||||
country = COUNTRIES.detect{|c| c[country_code.format] == upcase_name }
|
||||
else
|
||||
country = COUNTRIES.detect{|c| c[:name] == name }
|
||||
end
|
||||
raise InvalidCountryCodeError, "No country could be found for the country #{name}" if country.nil?
|
||||
Country.new(country.dup)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,26 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
class ActiveMerchantError < StandardError #:nodoc:
|
||||
end
|
||||
|
||||
class ConnectionError < ActiveMerchantError # :nodoc:
|
||||
end
|
||||
|
||||
class RetriableConnectionError < ConnectionError # :nodoc:
|
||||
end
|
||||
|
||||
class ResponseError < ActiveMerchantError # :nodoc:
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, message = nil)
|
||||
@response = response
|
||||
@message = message
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
|
||||
end
|
||||
end
|
||||
|
||||
class ClientCertificateError < ActiveMerchantError # :nodoc
|
||||
end
|
||||
end
|
||||
@@ -1,58 +0,0 @@
|
||||
module ActiveMerchant
|
||||
module NetworkConnectionRetries
|
||||
DEFAULT_RETRIES = 3
|
||||
DEFAULT_CONNECTION_ERRORS = {
|
||||
EOFError => "The remote server dropped the connection",
|
||||
Errno::ECONNRESET => "The remote server reset the connection",
|
||||
Timeout::Error => "The connection to the remote server timed out",
|
||||
Errno::ETIMEDOUT => "The connection to the remote server timed out"
|
||||
}
|
||||
|
||||
def self.included(base)
|
||||
base.send(:attr_accessor, :retry_safe)
|
||||
end
|
||||
|
||||
def retry_exceptions(options={})
|
||||
connection_errors = DEFAULT_CONNECTION_ERRORS.merge(options[:connection_exceptions] || {})
|
||||
|
||||
retry_network_exceptions(options) do
|
||||
begin
|
||||
yield
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
raise ActiveMerchant::RetriableConnectionError, "The remote server refused the connection"
|
||||
rescue OpenSSL::X509::CertificateError => e
|
||||
NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag])
|
||||
raise ActiveMerchant::ClientCertificateError, "The remote server did not accept the provided SSL certificate"
|
||||
rescue *connection_errors.keys => e
|
||||
raise ActiveMerchant::ConnectionError, connection_errors[e.class]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def retry_network_exceptions(options = {})
|
||||
retries = options[:max] || DEFAULT_RETRIES
|
||||
|
||||
begin
|
||||
yield
|
||||
rescue ActiveMerchant::RetriableConnectionError => e
|
||||
retries -= 1
|
||||
retry unless retries.zero?
|
||||
NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag])
|
||||
raise ActiveMerchant::ConnectionError, e.message
|
||||
rescue ActiveMerchant::ConnectionError => e
|
||||
retries -= 1
|
||||
retry if (options[:retry_safe] || retry_safe) && !retries.zero?
|
||||
NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag])
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def self.log(logger, level, message, tag=nil)
|
||||
tag ||= self.class.to_s
|
||||
message = "[#{tag}] #{message}"
|
||||
logger.send(level, message) if logger
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
require 'cgi'
|
||||
|
||||
module ActiveMerchant
|
||||
class PostData < Hash
|
||||
class_attribute :required_fields, :instance_writer => false
|
||||
self.required_fields = []
|
||||
|
||||
def []=(key, value)
|
||||
return if value.blank? && !required?(key)
|
||||
super
|
||||
end
|
||||
|
||||
def to_post_data
|
||||
collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
||||
end
|
||||
|
||||
alias_method :to_s, :to_post_data
|
||||
|
||||
private
|
||||
def required?(key)
|
||||
required_fields.include?(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,69 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module PostsData #:nodoc:
|
||||
|
||||
def self.included(base)
|
||||
base.superclass_delegating_accessor :ssl_strict
|
||||
base.ssl_strict = true
|
||||
|
||||
base.class_attribute :retry_safe
|
||||
base.retry_safe = false
|
||||
|
||||
base.superclass_delegating_accessor :open_timeout
|
||||
base.open_timeout = 60
|
||||
|
||||
base.superclass_delegating_accessor :read_timeout
|
||||
base.read_timeout = 60
|
||||
|
||||
base.superclass_delegating_accessor :logger
|
||||
base.superclass_delegating_accessor :wiredump_device
|
||||
end
|
||||
|
||||
def ssl_get(endpoint, headers={})
|
||||
ssl_request(:get, endpoint, nil, headers)
|
||||
end
|
||||
|
||||
def ssl_post(endpoint, data, headers = {})
|
||||
ssl_request(:post, endpoint, data, headers)
|
||||
end
|
||||
|
||||
def ssl_request(method, endpoint, data, headers)
|
||||
handle_response(raw_ssl_request(method, endpoint, data, headers))
|
||||
end
|
||||
|
||||
def raw_ssl_request(method, endpoint, data, headers = {})
|
||||
logger.warn "#{self.class} using ssl_strict=false, which is insecure" if logger unless ssl_strict
|
||||
|
||||
connection = new_connection(endpoint)
|
||||
connection.open_timeout = open_timeout
|
||||
connection.read_timeout = read_timeout
|
||||
connection.retry_safe = retry_safe
|
||||
connection.verify_peer = ssl_strict
|
||||
connection.logger = logger
|
||||
connection.tag = self.class.name
|
||||
connection.wiredump_device = wiredump_device
|
||||
|
||||
connection.pem = @options[:pem] if @options
|
||||
connection.pem_password = @options[:pem_password] if @options
|
||||
|
||||
connection.ignore_http_status = @options[:ignore_http_status] if @options
|
||||
|
||||
connection.request(method, data, headers)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def new_connection(endpoint)
|
||||
Connection.new(endpoint)
|
||||
end
|
||||
|
||||
def handle_response(response)
|
||||
case response.code.to_i
|
||||
when 200...300
|
||||
response.body
|
||||
else
|
||||
raise ResponseError.new(response)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -1,16 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module RequiresParameters #:nodoc:
|
||||
def requires!(hash, *params)
|
||||
params.each do |param|
|
||||
if param.is_a?(Array)
|
||||
raise ArgumentError.new("Missing required parameter: #{param.first}") unless hash.has_key?(param.first)
|
||||
|
||||
valid_options = param[1..-1]
|
||||
raise ArgumentError.new("Parameter: #{param.first} must be one of #{valid_options.to_sentence(:words_connector => 'or')}") unless valid_options.include?(hash[param.first])
|
||||
else
|
||||
raise ArgumentError.new("Missing required parameter: #{param}") unless hash.has_key?(param)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +0,0 @@
|
||||
require 'securerandom'
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Utils #:nodoc:
|
||||
def generate_unique_id
|
||||
SecureRandom.hex(16)
|
||||
end
|
||||
|
||||
module_function :generate_unique_id
|
||||
|
||||
def deprecated(message)
|
||||
warning = Kernel.caller[1] + message
|
||||
if respond_to?(:logger) && logger.present?
|
||||
logger.warn(warning)
|
||||
else
|
||||
warn(warning)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,81 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Validateable #:nodoc:
|
||||
def valid?
|
||||
errors.clear
|
||||
|
||||
before_validate if respond_to?(:before_validate, true)
|
||||
validate if respond_to?(:validate, true)
|
||||
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
def initialize(attributes = {})
|
||||
self.attributes = attributes
|
||||
end
|
||||
|
||||
def errors
|
||||
@errors ||= Errors.new(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def attributes=(attributes)
|
||||
unless attributes.nil?
|
||||
for key, value in attributes
|
||||
send("#{key}=", value )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This hash keeps the errors of the object
|
||||
class Errors < HashWithIndifferentAccess
|
||||
|
||||
def initialize(base)
|
||||
super() { |h, k| h[k] = [] ; h[k] }
|
||||
@base = base
|
||||
end
|
||||
|
||||
def count
|
||||
size
|
||||
end
|
||||
|
||||
def empty?
|
||||
all? { |k, v| v && v.empty? }
|
||||
end
|
||||
|
||||
# returns a specific fields error message.
|
||||
# if more than one error is available we will only return the first. If no error is available
|
||||
# we return an empty string
|
||||
def on(field)
|
||||
self[field].to_a.first
|
||||
end
|
||||
|
||||
def add(field, error)
|
||||
self[field] << error
|
||||
end
|
||||
|
||||
def add_to_base(error)
|
||||
add(:base, error)
|
||||
end
|
||||
|
||||
def each_full
|
||||
full_messages.each { |msg| yield msg }
|
||||
end
|
||||
|
||||
def full_messages
|
||||
result = []
|
||||
|
||||
self.each do |key, messages|
|
||||
next if messages.blank?
|
||||
if key == 'base'
|
||||
result << "#{messages.first}"
|
||||
else
|
||||
result << "#{key.to_s.humanize} #{messages.first}"
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
module ActiveUtils
|
||||
VERSION = "1.0.5"
|
||||
end
|
||||
7815
vendor/gems/active_utils-1.0.5/lib/certs/cacert.pem
vendored
7815
vendor/gems/active_utils-1.0.5/lib/certs/cacert.pem
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require 'rubygems'
|
||||
require 'bundler'
|
||||
Bundler.setup
|
||||
|
||||
require 'test/unit'
|
||||
require 'active_utils'
|
||||
require 'mocha'
|
||||
|
||||
include ActiveMerchant
|
||||
@@ -1,149 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class ConnectionTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@ok = stub(:code => 200, :message => 'OK', :body => 'success')
|
||||
|
||||
@endpoint = 'https://example.com/tx.php'
|
||||
@connection = ActiveMerchant::Connection.new(@endpoint)
|
||||
@connection.logger = stub(:info => nil, :debug => nil, :error => nil)
|
||||
end
|
||||
|
||||
def test_connection_endpoint_parses_string_to_uri
|
||||
assert_equal URI.parse(@endpoint), @connection.endpoint
|
||||
end
|
||||
|
||||
def test_connection_endpoint_accepts_uri
|
||||
endpoint = URI.parse(@endpoint)
|
||||
connection = ActiveMerchant::Connection.new(endpoint)
|
||||
assert_equal endpoint, connection.endpoint
|
||||
end
|
||||
|
||||
def test_connection_endpoint_raises_uri_error
|
||||
assert_raises URI::InvalidURIError do
|
||||
ActiveMerchant::Connection.new("not a URI")
|
||||
end
|
||||
end
|
||||
|
||||
def test_successful_get_request
|
||||
@connection.logger.expects(:info).twice
|
||||
Net::HTTP.any_instance.expects(:get).with('/tx.php', {}).returns(@ok)
|
||||
response = @connection.request(:get, nil, {})
|
||||
assert_equal 'success', response.body
|
||||
end
|
||||
|
||||
def test_successful_post_request
|
||||
Net::HTTP.any_instance.expects(:post).with('/tx.php', 'data', ActiveMerchant::Connection::RUBY_184_POST_HEADERS).returns(@ok)
|
||||
response = @connection.request(:post, 'data', {})
|
||||
assert_equal 'success', response.body
|
||||
end
|
||||
|
||||
def test_successful_put_request
|
||||
Net::HTTP.any_instance.expects(:put).with('/tx.php', 'data', {}).returns(@ok)
|
||||
response = @connection.request(:put, 'data', {})
|
||||
assert_equal 'success', response.body
|
||||
end
|
||||
|
||||
def test_successful_delete_request
|
||||
Net::HTTP.any_instance.expects(:delete).with('/tx.php', {}).returns(@ok)
|
||||
response = @connection.request(:delete, nil, {})
|
||||
assert_equal 'success', response.body
|
||||
end
|
||||
|
||||
def test_get_raises_argument_error_if_passed_data
|
||||
assert_raise(ArgumentError) do
|
||||
@connection.request(:get, 'data', {})
|
||||
end
|
||||
end
|
||||
|
||||
def test_request_raises_when_request_method_not_supported
|
||||
assert_raise(ArgumentError) do
|
||||
@connection.request(:head, nil, {})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
def test_default_read_timeout
|
||||
assert_equal ActiveMerchant::Connection::READ_TIMEOUT, @connection.read_timeout
|
||||
end
|
||||
|
||||
def test_override_read_timeout
|
||||
@connection.read_timeout = 20
|
||||
assert_equal 20, @connection.read_timeout
|
||||
end
|
||||
|
||||
def test_default_open_timeout
|
||||
@connection.open_timeout = 20
|
||||
assert_equal 20, @connection.open_timeout
|
||||
end
|
||||
|
||||
def test_default_verify_peer
|
||||
assert_equal ActiveMerchant::Connection::VERIFY_PEER, @connection.verify_peer
|
||||
end
|
||||
|
||||
def test_override_verify_peer
|
||||
@connection.verify_peer = false
|
||||
assert_equal false, @connection.verify_peer
|
||||
end
|
||||
|
||||
def test_unrecoverable_exception
|
||||
@connection.logger.expects(:error).once
|
||||
Net::HTTP.any_instance.expects(:post).raises(EOFError)
|
||||
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
@connection.request(:post, '')
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_then_success_with_recoverable_exception
|
||||
@connection.logger.expects(:error).never
|
||||
Net::HTTP.any_instance.expects(:post).times(2).raises(Errno::ECONNREFUSED).then.returns(@ok)
|
||||
|
||||
assert_nothing_raised do
|
||||
@connection.request(:post, '')
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_limit_reached
|
||||
@connection.logger.expects(:error).once
|
||||
Net::HTTP.any_instance.expects(:post).times(ActiveMerchant::Connection::MAX_RETRIES).raises(Errno::ECONNREFUSED)
|
||||
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
@connection.request(:post, '')
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_then_success_with_retry_safe_enabled
|
||||
Net::HTTP.any_instance.expects(:post).times(2).raises(EOFError).then.returns(@ok)
|
||||
|
||||
@connection.retry_safe = true
|
||||
|
||||
assert_nothing_raised do
|
||||
@connection.request(:post, '')
|
||||
end
|
||||
end
|
||||
|
||||
def test_mixture_of_failures_with_retry_safe_enabled
|
||||
Net::HTTP.any_instance.expects(:post).times(3).raises(Errno::ECONNRESET).
|
||||
raises(Errno::ECONNREFUSED).
|
||||
raises(EOFError)
|
||||
|
||||
@connection.retry_safe = true
|
||||
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
@connection.request(:post, '')
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_with_ssl_certificate
|
||||
@connection.logger.expects(:error).once
|
||||
Net::HTTP.any_instance.expects(:post).raises(OpenSSL::X509::CertificateError)
|
||||
|
||||
assert_raises(ActiveMerchant::ClientCertificateError) do
|
||||
@connection.request(:post, '')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,31 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class CountryCodeTest < Test::Unit::TestCase
|
||||
def test_alpha2_country_code
|
||||
code = CountryCode.new('CA')
|
||||
assert_equal 'CA', code.value
|
||||
assert_equal 'CA', code.to_s
|
||||
assert_equal :alpha2, code.format
|
||||
end
|
||||
|
||||
def test_lower_alpha2_country_code
|
||||
code = CountryCode.new('ca')
|
||||
assert_equal 'CA', code.value
|
||||
assert_equal 'CA', code.to_s
|
||||
assert_equal :alpha2, code.format
|
||||
end
|
||||
|
||||
def test_alpha2_country_code
|
||||
code = CountryCode.new('CAN')
|
||||
assert_equal :alpha3, code.format
|
||||
end
|
||||
|
||||
def test_numeric_code
|
||||
code = CountryCode.new('004')
|
||||
assert_equal :numeric, code.format
|
||||
end
|
||||
|
||||
def test_invalid_code_format
|
||||
assert_raise(CountryCodeFormatError){ CountryCode.new('Canada') }
|
||||
end
|
||||
end
|
||||
@@ -1,68 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class CountryTest < Test::Unit::TestCase
|
||||
def test_country_from_hash
|
||||
country = Country.new(:name => 'Canada', :alpha2 => 'CA', :alpha3 => 'CAN', :numeric => '124')
|
||||
assert_equal 'CA', country.code(:alpha2).value
|
||||
assert_equal 'CAN', country.code(:alpha3).value
|
||||
assert_equal '124', country.code(:numeric).value
|
||||
assert_equal 'Canada', country.to_s
|
||||
end
|
||||
|
||||
def test_country_for_alpha2_code
|
||||
country = Country.find('CA')
|
||||
assert_equal 'CA', country.code(:alpha2).value
|
||||
assert_equal 'CAN', country.code(:alpha3).value
|
||||
assert_equal '124', country.code(:numeric).value
|
||||
assert_equal 'Canada', country.to_s
|
||||
end
|
||||
|
||||
def test_country_for_alpha3_code
|
||||
country = Country.find('CAN')
|
||||
assert_equal 'Canada', country.to_s
|
||||
end
|
||||
|
||||
def test_country_for_numeric_code
|
||||
country = Country.find('124')
|
||||
assert_equal 'Canada', country.to_s
|
||||
end
|
||||
|
||||
def test_find_country_by_name
|
||||
country = Country.find('Canada')
|
||||
assert_equal 'Canada', country.to_s
|
||||
end
|
||||
|
||||
def test_find_unknown_country_name
|
||||
assert_raise(InvalidCountryCodeError) do
|
||||
Country.find('Asskickistan')
|
||||
end
|
||||
end
|
||||
|
||||
def test_find_australia
|
||||
country = Country.find('AU')
|
||||
assert_equal 'AU', country.code(:alpha2).value
|
||||
|
||||
country = Country.find('Australia')
|
||||
assert_equal 'AU', country.code(:alpha2).value
|
||||
end
|
||||
|
||||
def test_find_united_kingdom
|
||||
country = Country.find('GB')
|
||||
assert_equal 'GB', country.code(:alpha2).value
|
||||
|
||||
country = Country.find('United Kingdom')
|
||||
assert_equal 'GB', country.code(:alpha2).value
|
||||
end
|
||||
|
||||
def test_raise_on_nil_name
|
||||
assert_raise(InvalidCountryCodeError) do
|
||||
Country.find(nil)
|
||||
end
|
||||
end
|
||||
|
||||
def test_country_names_are_alphabetized
|
||||
country_names = Country::COUNTRIES.map { | each | each[:name] }
|
||||
assert_equal(country_names.sort, country_names)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,127 +0,0 @@
|
||||
require 'test_helper'
|
||||
require 'openssl'
|
||||
require 'net/http'
|
||||
|
||||
class NetworkConnectionRetriesTest < Test::Unit::TestCase
|
||||
class MyNewError < StandardError
|
||||
end
|
||||
|
||||
include NetworkConnectionRetries
|
||||
|
||||
def setup
|
||||
@logger = stubs(:logger)
|
||||
@requester = stubs(:requester)
|
||||
@ok = stub(:code => 200, :message => 'OK', :body => 'success')
|
||||
end
|
||||
|
||||
def test_unrecoverable_exception
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
retry_exceptions do
|
||||
raise EOFError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_unrecoverable_exception_logged_if_logger_provided
|
||||
@logger.expects(:error).once
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
retry_exceptions :logger => @logger do
|
||||
raise EOFError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_then_success_with_recoverable_exception
|
||||
@requester.expects(:post).times(2).raises(Errno::ECONNREFUSED).then.returns(@ok)
|
||||
|
||||
assert_nothing_raised do
|
||||
retry_exceptions do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_limit_reached
|
||||
@requester.expects(:post).times(ActiveMerchant::NetworkConnectionRetries::DEFAULT_RETRIES).raises(Errno::ECONNREFUSED)
|
||||
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
retry_exceptions do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_limit_reached_logs_final_error
|
||||
@logger.expects(:error).once
|
||||
@requester.expects(:post).times(ActiveMerchant::NetworkConnectionRetries::DEFAULT_RETRIES).raises(Errno::ECONNREFUSED)
|
||||
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
retry_exceptions(:logger => @logger) do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_then_success_with_retry_safe_enabled
|
||||
@requester.expects(:post).times(2).raises(EOFError).then.returns(@ok)
|
||||
|
||||
assert_nothing_raised do
|
||||
retry_exceptions :retry_safe => true do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_mixture_of_failures_with_retry_safe_enabled
|
||||
@requester.expects(:post).times(3).raises(Errno::ECONNRESET).
|
||||
raises(Errno::ECONNREFUSED).
|
||||
raises(EOFError)
|
||||
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
retry_exceptions :retry_safe => true do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_with_ssl_certificate
|
||||
@requester.expects(:post).raises(OpenSSL::X509::CertificateError)
|
||||
|
||||
assert_raises(ActiveMerchant::ClientCertificateError) do
|
||||
retry_exceptions do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_with_ssl_certificate_logs_error_if_logger_specified
|
||||
@logger.expects(:error).once
|
||||
@requester.expects(:post).raises(OpenSSL::X509::CertificateError)
|
||||
|
||||
assert_raises(ActiveMerchant::ClientCertificateError) do
|
||||
retry_exceptions :logger => @logger do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_with_additional_exceptions_specified
|
||||
@requester.expects(:post).raises(MyNewError)
|
||||
|
||||
assert_raises(ActiveMerchant::ConnectionError) do
|
||||
retry_exceptions :connection_exceptions => {MyNewError => "my message"} do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_failure_without_additional_exceptions_specified
|
||||
@requester.expects(:post).raises(MyNewError)
|
||||
|
||||
assert_raises(MyNewError) do
|
||||
retry_exceptions do
|
||||
@requester.post
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,50 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class MyPost < ActiveMerchant::PostData
|
||||
self.required_fields = [ :ccnumber, :ccexp, :firstname, :lastname, :username, :password, :order_id, :key, :time ]
|
||||
end
|
||||
|
||||
class PostDataTest < Test::Unit::TestCase
|
||||
def teardown
|
||||
ActiveMerchant::PostData.required_fields = []
|
||||
end
|
||||
|
||||
def test_element_assignment
|
||||
name = 'Cody Fauser'
|
||||
post = ActiveMerchant::PostData.new
|
||||
|
||||
post[:name] = name
|
||||
assert_equal name, post[:name]
|
||||
end
|
||||
|
||||
def test_ignore_blank_fields
|
||||
post = ActiveMerchant::PostData.new
|
||||
assert_equal 0, post.keys.size
|
||||
|
||||
post[:name] = ''
|
||||
assert_equal 0, post.keys.size
|
||||
|
||||
post[:name] = nil
|
||||
assert_equal 0, post.keys.size
|
||||
end
|
||||
|
||||
def test_dont_ignore_required_blank_fields
|
||||
ActiveMerchant::PostData.required_fields = [ :name ]
|
||||
post = ActiveMerchant::PostData.new
|
||||
|
||||
assert_equal 0, post.keys.size
|
||||
|
||||
post[:name] = ''
|
||||
assert_equal 1, post.keys.size
|
||||
assert_equal '', post[:name]
|
||||
|
||||
post[:name] = nil
|
||||
assert_equal 1, post.keys.size
|
||||
assert_nil post[:name]
|
||||
end
|
||||
|
||||
def test_subclass
|
||||
post = MyPost.new
|
||||
assert_equal [ :ccnumber, :ccexp, :firstname, :lastname, :username, :password, :order_id, :key, :time ], post.required_fields
|
||||
end
|
||||
end
|
||||
@@ -1,35 +0,0 @@
|
||||
require 'test_helper'
|
||||
require 'active_support/core_ext/class'
|
||||
|
||||
class PostsDataTest < Test::Unit::TestCase
|
||||
|
||||
class SSLPoster
|
||||
include PostsData
|
||||
|
||||
attr_accessor :logger
|
||||
end
|
||||
|
||||
def setup
|
||||
@poster = SSLPoster.new
|
||||
end
|
||||
|
||||
def test_logger_warns_if_ssl_strict_disabled
|
||||
@poster.logger = stub()
|
||||
@poster.logger.expects(:warn).with("PostsDataTest::SSLPoster using ssl_strict=false, which is insecure")
|
||||
|
||||
Connection.any_instance.stubs(:request)
|
||||
|
||||
SSLPoster.ssl_strict = false
|
||||
@poster.raw_ssl_request(:post, "https://shopify.com", "", {})
|
||||
end
|
||||
|
||||
def test_logger_no_warning_if_ssl_strict_enabled
|
||||
@poster.logger = stub()
|
||||
@poster.logger.stubs(:warn).never
|
||||
Connection.any_instance.stubs(:request)
|
||||
|
||||
SSLPoster.ssl_strict = true
|
||||
@poster.raw_ssl_request(:post, "https://shopify.com", "", {})
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,7 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class UtilsTest < Test::Unit::TestCase
|
||||
def test_unique_id_should_be_32_chars_and_alphanumeric
|
||||
assert_match /^\w{32}$/, ActiveMerchant::Utils.generate_unique_id
|
||||
end
|
||||
end
|
||||
@@ -1,59 +0,0 @@
|
||||
require 'test_helper'
|
||||
|
||||
class Dood
|
||||
include ActiveMerchant::Validateable
|
||||
|
||||
attr_accessor :name, :email, :country
|
||||
|
||||
def validate
|
||||
errors.add "name", "cannot be empty" if name.blank?
|
||||
errors.add "email", "cannot be empty" if email.blank?
|
||||
errors.add_to_base "The country cannot be blank" if country.blank?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ValidateableTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@dood = Dood.new
|
||||
end
|
||||
|
||||
def test_validation
|
||||
assert ! @dood.valid?
|
||||
assert ! @dood.errors.empty?
|
||||
end
|
||||
|
||||
def test_assigns
|
||||
@dood = Dood.new(:name => "tobi", :email => "tobi@neech.de", :country => 'DE')
|
||||
|
||||
assert_equal "tobi", @dood.name
|
||||
assert_equal "tobi@neech.de", @dood.email
|
||||
assert @dood.valid?
|
||||
end
|
||||
|
||||
def test_multiple_calls
|
||||
@dood.name = "tobi"
|
||||
assert !@dood.valid?
|
||||
|
||||
@dood.email = "tobi@neech.de"
|
||||
assert !@dood.valid?
|
||||
|
||||
@dood.country = 'DE'
|
||||
assert @dood.valid?
|
||||
end
|
||||
|
||||
def test_messages
|
||||
@dood.valid?
|
||||
assert_equal "cannot be empty", @dood.errors.on('name')
|
||||
assert_equal "cannot be empty", @dood.errors.on('email')
|
||||
assert_equal nil, @dood.errors.on('doesnt_exist')
|
||||
|
||||
end
|
||||
|
||||
def test_full_messages
|
||||
@dood.valid?
|
||||
assert_equal ["Email cannot be empty", "Name cannot be empty", "The country cannot be blank"], @dood.errors.full_messages.sort
|
||||
end
|
||||
|
||||
end
|
||||
1143
vendor/gems/activemerchant-1.33.0/CHANGELOG
vendored
1143
vendor/gems/activemerchant-1.33.0/CHANGELOG
vendored
File diff suppressed because it is too large
Load Diff
402
vendor/gems/activemerchant-1.33.0/CONTRIBUTORS
vendored
402
vendor/gems/activemerchant-1.33.0/CONTRIBUTORS
vendored
@@ -1,402 +0,0 @@
|
||||
Protx Gateway
|
||||
|
||||
* Contributed by shiftx (Vincent)
|
||||
|
||||
Verifi Gateway
|
||||
|
||||
* Contributed by Paul Hepworth on 2007-05-12.
|
||||
* Portions of Verifi Gateway Copyright (c) 2007 Paul Hepworth
|
||||
|
||||
Plug 'N Pay Gateway
|
||||
|
||||
* Contributed by Ryan Norbauer
|
||||
|
||||
PayJunction Gateway
|
||||
|
||||
* Contributed by Matt Sanders
|
||||
|
||||
E-xact Gateway
|
||||
|
||||
* Contributed by James Edward Gray II
|
||||
|
||||
Linkpoint Gateway
|
||||
|
||||
* Portions of the LinkPoint Gateway by Ryan Heneise
|
||||
|
||||
eWay Gateway
|
||||
|
||||
* Originally contributed by Lucas Carlson (mailto:lucas@rufy.com)
|
||||
* Managed Payments support by Jason Stirk with improvements by Keith Pitt
|
||||
|
||||
CardStream Gateway
|
||||
|
||||
* Portions of the Cardstream gateway by Jonah Fox and Thomas Nichols
|
||||
|
||||
CyberSource Gateway
|
||||
|
||||
* Contributed by Matt Margolis (matt@mattmargolis.net)
|
||||
|
||||
NetRegistry Gateway
|
||||
|
||||
* Originally contributed by George Ogata (mailto: george.ogata@gmail.com)
|
||||
|
||||
DataCash Gateway (March 2, 2007)
|
||||
|
||||
* MoneySpyder, http://moneyspyder.co.uk and E-consultancy, http://www.e-consultancy.com
|
||||
|
||||
PSL Card Gateway (March 27, 2007)
|
||||
|
||||
* MoneySpyder, http://moneyspyder.co.uk
|
||||
|
||||
Viaklix Gateway (Sep 3, 2007)
|
||||
|
||||
* Originally contributed by Sal Scotto
|
||||
|
||||
Braintree Gateway (Sep 4, 2007)
|
||||
|
||||
* Originally contributed by Michael J. Mangino
|
||||
* Portions of the BrainTree gateway by Jeremy Voorhis
|
||||
|
||||
Concord Efsnet Gateway (Sep 7, 2007)
|
||||
|
||||
* Originally contributed by snacktime
|
||||
|
||||
SecurePayTech Gateway (Oct 23, 2007)
|
||||
|
||||
* Originally contributed by Jasper Bryant-Greene
|
||||
|
||||
SkipJack Gateway (Nov 29, 2007)
|
||||
|
||||
* Originally contributed by Bill Bereza - http://atomicobject.com
|
||||
|
||||
HiTRUST Gateway (Dec 10, 2007)
|
||||
|
||||
* Jaded Pixel
|
||||
|
||||
Payflow NV Gateway (Mar 03, 2008)
|
||||
|
||||
* Greg Furmanek
|
||||
|
||||
PaypalNVGateway (Apr 12, 2008)
|
||||
|
||||
* Greg Furmanek
|
||||
|
||||
Beanstream (May 13, 2008)
|
||||
|
||||
* Created by xiaobozz ( xiaobozzz at gmail dot com )
|
||||
* Secure Profiles support by Forrest Zeisler (http://github.com/forrest)
|
||||
|
||||
Sage (June, 2008)
|
||||
|
||||
* Cody
|
||||
|
||||
Modern Payments (June 13, 2008)
|
||||
|
||||
* Initial implementation by Jeremy Nicoll
|
||||
* Additional portions by Cody Fauser
|
||||
|
||||
Wirecard Gateway (June 30, 2008)
|
||||
|
||||
* Initial implementation by Soleone
|
||||
|
||||
Transax Gateway (May 3, 2009)
|
||||
|
||||
* Mike Mangino
|
||||
|
||||
Merchant E-Solutions Gateway (May 3, 2009)
|
||||
|
||||
* Zac Williams, Robby Russell
|
||||
|
||||
Instapay Gateway (May 3, 2009)
|
||||
|
||||
* Thomas Rideout
|
||||
|
||||
Iridium Gateway (June 13, 2009)
|
||||
|
||||
* Phil Smy
|
||||
|
||||
MerchantWARE (July 7, 2009)
|
||||
|
||||
* Cody Fauser
|
||||
|
||||
FirstPay (July 24, 2009)
|
||||
|
||||
* Phil R
|
||||
|
||||
Ogone (July 20, 2009)
|
||||
|
||||
* Nicolas Jacobeus
|
||||
|
||||
Elavon (August 09, 2009)
|
||||
|
||||
* Jesse Storimer
|
||||
|
||||
JetPay (September 29, 2009)
|
||||
|
||||
* Phil Ripperger, Peter Williams
|
||||
|
||||
SallieMae (October 2, 2009)
|
||||
|
||||
* iamjwc
|
||||
|
||||
Netaxept (February 08, 2010)
|
||||
|
||||
* Nathaniel Talbott
|
||||
|
||||
Garanti (May 05, 2010)
|
||||
|
||||
* Selem Delul (moon@mac.home)
|
||||
|
||||
Braintree Blue Gateway (May 19th, 2010)
|
||||
|
||||
* Braintree (code@getbraintree.com)
|
||||
|
||||
Inspire Gateway (September 27, 2010)
|
||||
|
||||
* ryan r. smith
|
||||
|
||||
SecureNet Gateway (September 27, 2010)
|
||||
|
||||
* Kal
|
||||
|
||||
PayboxDirect Gateway (September 27, 2010)
|
||||
|
||||
* Donald Piret <donald@donaldpiret.com>
|
||||
|
||||
SagePay Form Offsite Gateway (October 14, 2010)
|
||||
|
||||
* Adrian Irving-Beer
|
||||
|
||||
DirecPay Gateway (October 14, 2010)
|
||||
|
||||
* Soleone
|
||||
|
||||
ePay Gateway (November 23, 2010)
|
||||
|
||||
* Original code by ePay (epay.dk)
|
||||
* Refactored by Jonathan Rudenberg
|
||||
|
||||
iDEAL/Rabobank Gateway (January 10, 2011)
|
||||
|
||||
* Original code by Soemirno Kartosoewito
|
||||
* Refactored by Cody Fauser
|
||||
* Refactored and updated by Jonathan Rudenberg
|
||||
|
||||
Quantum Gateway
|
||||
|
||||
* Joshua Lippiner
|
||||
* Refactored by Nathaniel Talbott
|
||||
|
||||
BluePay Gateway
|
||||
|
||||
* Mel Sleight
|
||||
* Refactored by Nathaniel Talbott
|
||||
|
||||
Valitor Integration (January 2011)
|
||||
|
||||
* Nathaniel Talbott
|
||||
* Sponsored by Sævar Öfjörð Magnússon
|
||||
|
||||
Barclays ePDQ
|
||||
|
||||
* Original code by Rob W (rfwatson)
|
||||
* Refactored by Nathaniel Talbott
|
||||
|
||||
Federated Canada
|
||||
|
||||
* Bob Larrick (deathbob)
|
||||
|
||||
NMI
|
||||
|
||||
* Nathaniel Talbott (ntalbott)
|
||||
|
||||
QBMS
|
||||
|
||||
* Will Glozer (wg)
|
||||
|
||||
WorldPay Integration (Feb 17, 2011)
|
||||
|
||||
* Original code by Unknown from this patch: https://jadedpixel.lighthouseapp.com/projects/11599/tickets/3-patch-integration-support-for-worldpay-uk
|
||||
* Refactored by Soleone
|
||||
|
||||
WorldPay Gateway
|
||||
|
||||
* Original code by Amit kumar (ask4amit@gmail.com)
|
||||
* Refactored by Nathaniel Talbott (ntalbott)
|
||||
|
||||
Orbital Paymentech Gateway (July, 2009)
|
||||
|
||||
* Sam Vincent - http://ecommerce.versapay.com
|
||||
|
||||
DIRECTebanking - Payment Network AG (May, 2011)
|
||||
|
||||
* Gerwin Brunner (Vilango)
|
||||
|
||||
Stripe
|
||||
|
||||
* Ross Boucher (boucher)
|
||||
|
||||
Paystation (July, 2011)
|
||||
|
||||
* Nik Wakelin (nikz)
|
||||
|
||||
ePaymentPlans offsite gatway (June, 2011)
|
||||
|
||||
* Roberto Miranda (robertomiranda)
|
||||
|
||||
Optimal Payments (August, 2011)
|
||||
|
||||
* Jamie Macey (jamie)
|
||||
|
||||
CardSave (August, 2011)
|
||||
|
||||
* Tom Crinson (MrJaba)
|
||||
|
||||
Dwolla (September, 2011)
|
||||
|
||||
* James Armstead (armsteadj1)
|
||||
|
||||
Samurai (November, 2011)
|
||||
|
||||
* Joshua Krall (jkrall)
|
||||
|
||||
CertoDirect Gateway (February, 2012)
|
||||
|
||||
* Aleksei Gusev (hron)
|
||||
|
||||
Authorize.Net SIM Integration (February, 2012)
|
||||
|
||||
* Roger Pack (rdp)
|
||||
* Nick Rogers (courtland)
|
||||
|
||||
NAB Transact (AU) Gateway (February, 2012)
|
||||
|
||||
* Tom Meier (tommeier)
|
||||
|
||||
iTransact XML Gateway (March, 2012)
|
||||
|
||||
* Kevin Motschiedler (motske)
|
||||
|
||||
Robokassa Integration (March, 2012)
|
||||
|
||||
* Vasiliy Ermolovich (nashby)
|
||||
|
||||
Moneris US Gateway (March, 2012)
|
||||
|
||||
* Michael Wood (eddanger)
|
||||
|
||||
Dotpay Integration (March, 2012)
|
||||
|
||||
* Przemysław Ciąćka (kacperix)
|
||||
|
||||
Vindicia gateway (April 2012)
|
||||
|
||||
* Steven Davidovitz (steved555)
|
||||
|
||||
MiGS gateway (April 2012)
|
||||
|
||||
* Michael Noack (mnoack)
|
||||
* Justin Jones (nagash)
|
||||
|
||||
ePay integration (April 2012)
|
||||
|
||||
* Michael (ePay)
|
||||
|
||||
Litle gateway (May 2012)
|
||||
|
||||
* Gregory Drake (GregDrake)
|
||||
|
||||
Fat Zebra gateway (June 2012)
|
||||
|
||||
* Matthew Savage (amasses)
|
||||
|
||||
Metrics Global gateway (June 2012)
|
||||
|
||||
* Dan Knox (DanKnox)
|
||||
|
||||
EasyPay integration (July 2012)
|
||||
|
||||
* Vasiliy Ermolovich (nashby)
|
||||
|
||||
PayGateXML gateway (July 2012)
|
||||
|
||||
* bryan (rubyisbeautiful)
|
||||
|
||||
PayWay gateway (July 2012)
|
||||
|
||||
* Ben Zhang (BenZhang)
|
||||
|
||||
First Data integration (July 2012)
|
||||
|
||||
* Nick Rogers (courtland)
|
||||
|
||||
WebPay integration (July 2012)
|
||||
|
||||
* Vasiliy Ermolovich (nashby)
|
||||
|
||||
Suomen Maksuturva integration (July 2012)
|
||||
|
||||
* Antti Akonniemi (akonan)
|
||||
|
||||
Paxum integration (July 2012)
|
||||
|
||||
* Stanislav Mekhonoshin (Mehonoshin)
|
||||
|
||||
Balanced gateway (July 2012)
|
||||
|
||||
* Marshall Jones (mjallday)
|
||||
|
||||
PayFast integration (October 2012)
|
||||
|
||||
* Vasiliy Ermolovich (nashby)
|
||||
|
||||
A1Agregator (November 2012)
|
||||
|
||||
* Roman Ivanilov (england)
|
||||
|
||||
Liqpay integration (November 2012)
|
||||
|
||||
* beorc
|
||||
|
||||
eWay Rapid 3.0 gateway (December 2012)
|
||||
|
||||
* Nathaniel Talbott (ntalbott)
|
||||
|
||||
FirstData Global Gateway e4 (December 2012)
|
||||
|
||||
* Chris Sheppard (frobcode)
|
||||
|
||||
Spreedly Core gateway (December 2012)
|
||||
|
||||
* Duff OMelia (duff)
|
||||
|
||||
Pin gateway (February 2013)
|
||||
|
||||
* Myles Eftos (madpilot)
|
||||
|
||||
Merchant Warrior (February 2013)
|
||||
|
||||
* Ben Bruscella (benbruscella)
|
||||
* Дмитрий Василец (pronix)
|
||||
* Kirill Shirinkin (Fodoj)
|
||||
* Nathaniel Talbott (ntalbott)
|
||||
|
||||
Paymill (February 2013)
|
||||
|
||||
* Duff O'Melia (duff)
|
||||
|
||||
EVO Canada (February 2013)
|
||||
|
||||
* Alex Dunae (alexdunae)
|
||||
|
||||
Finansbank WebPOS (March 2013)
|
||||
|
||||
* scamurcuoglu
|
||||
|
||||
Cardstream Modern (March 2013)
|
||||
|
||||
* Vincens (ExxKA)
|
||||
|
||||
Transnational (May 2013)
|
||||
|
||||
* Ben VandenBos (bvandenbos)
|
||||
20
vendor/gems/activemerchant-1.33.0/MIT-LICENSE
vendored
20
vendor/gems/activemerchant-1.33.0/MIT-LICENSE
vendored
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2005-2010 Tobias Luetke
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
221
vendor/gems/activemerchant-1.33.0/README.md
vendored
221
vendor/gems/activemerchant-1.33.0/README.md
vendored
@@ -1,221 +0,0 @@
|
||||
# Active Merchant
|
||||
|
||||
Active Merchant is an extraction from the e-commerce system [Shopify](http://www.shopify.com).
|
||||
Shopify's requirements for a simple and unified API to access dozens of different payment
|
||||
gateways with very different internal APIs was the chief principle in designing the library.
|
||||
|
||||
It was developed for usage in Ruby on Rails web applications and integrates seamlessly
|
||||
as a Rails plugin, but it also works excellently as a stand alone Ruby library.
|
||||
|
||||
Active Merchant has been in production use since June 2006 and is now used in most modern
|
||||
Ruby applications which deal with financial transactions. It is maintained by the
|
||||
[Shopify](http://www.shopify.com) and [Spreedly](https://spreedly.com) teams, with much help
|
||||
from an ever-growing set of contributors.
|
||||
|
||||
See [GettingStarted.md](GettingStarted.md) if you want to learn more about using Active Merchant in your
|
||||
applications.
|
||||
|
||||
## Installation
|
||||
|
||||
### From Git
|
||||
|
||||
You can check out the latest source from git:
|
||||
|
||||
git clone git://github.com/Shopify/active_merchant.git
|
||||
|
||||
### From RubyGems
|
||||
|
||||
Installation from RubyGems:
|
||||
|
||||
gem install activemerchant
|
||||
|
||||
Or, if you're using Bundler, just add the following to your Gemfile:
|
||||
|
||||
gem 'activemerchant'
|
||||
|
||||
## Usage
|
||||
|
||||
This simple example demonstrates how a purchase can be made using a person's
|
||||
credit card details.
|
||||
|
||||
require 'rubygems'
|
||||
require 'active_merchant'
|
||||
|
||||
# Use the TrustCommerce test servers
|
||||
ActiveMerchant::Billing::Base.mode = :test
|
||||
|
||||
gateway = ActiveMerchant::Billing::TrustCommerceGateway.new(
|
||||
:login => 'TestMerchant',
|
||||
:password => 'password')
|
||||
|
||||
# ActiveMerchant accepts all amounts as Integer values in cents
|
||||
amount = 1000 # $10.00
|
||||
|
||||
# The card verification value is also known as CVV2, CVC2, or CID
|
||||
credit_card = ActiveMerchant::Billing::CreditCard.new(
|
||||
:first_name => 'Bob',
|
||||
:last_name => 'Bobsen',
|
||||
:number => '4242424242424242',
|
||||
:month => '8',
|
||||
:year => Time.now.year+1,
|
||||
:verification_value => '000')
|
||||
|
||||
# Validating the card automatically detects the card type
|
||||
if credit_card.valid?
|
||||
# Capture $10 from the credit card
|
||||
response = gateway.purchase(amount, credit_card)
|
||||
|
||||
if response.success?
|
||||
puts "Successfully charged $#{sprintf("%.2f", amount / 100)} to the credit card #{credit_card.display_number}"
|
||||
else
|
||||
raise StandardError, response.message
|
||||
end
|
||||
end
|
||||
|
||||
For more in-depth documentation and tutorials, see [GettingStarted.md](GettingStarted.md) and the
|
||||
[API documentation](http://rubydoc.info/github/Shopify/active_merchant/master/file/README.md).
|
||||
|
||||
## Supported Direct Payment Gateways
|
||||
|
||||
The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) contains a [table of features supported by each gateway](http://github.com/Shopify/active_merchant/wikis/gatewayfeaturematrix).
|
||||
|
||||
* [Authorize.Net](http://www.authorize.net/) - US
|
||||
* [Authorize.Net CIM](http://www.authorize.net/) - US
|
||||
* [Balanced](https://www.balancedpayments.com/) - US
|
||||
* [Banwire](https://www.banwire.com/) - MX
|
||||
* [Barclays ePDQ](http://www.barclaycard.co.uk/business/accepting-payments/epdq-mpi/) - UK
|
||||
* [Beanstream.com](http://www.beanstream.com/) - CA
|
||||
* [BluePay](http://www.bluepay.com/) - US
|
||||
* [Braintree](http://www.braintreepaymentsolutions.com) - US
|
||||
* [CardStream](http://www.cardstream.com/) - UK
|
||||
* [CertoDirect](http://www.certodirect.com/) - BE, BG, CZ, DK, DE, EE, IE, EL, ES, FR, IT, CY, LV, LT, LU, HU, MT, NL, AT, PL, PT, RO, SI, SK, FI, SE, UK
|
||||
* [CyberSource](http://www.cybersource.com) - US
|
||||
* [DataCash](http://www.datacash.com/) - UK
|
||||
* [Efsnet](http://www.concordefsnet.com/) - US
|
||||
* [Elavon MyVirtualMerchant](http://www.elavon.com) - US, CA
|
||||
* [ePay](http://www.epay.dk/) - DK, SE, NO
|
||||
* [EVO Canada](http://www.evocanada.com/) - CA
|
||||
* [eWAY](http://www.eway.com.au/) - AU
|
||||
* [eWay Rapid 3.0](http://www.eway.com.au/) - AU
|
||||
* [E-xact](http://www.e-xact.com) - CA, US
|
||||
* [Fat Zebra](https://www.fatzebra.com.au) - AU
|
||||
* [Federated Canada](http://www.federatedcanada.com/) - CA
|
||||
* [Finansbank WebPOS](https://www.fbwebpos.com/) - US, TR
|
||||
* [FirstData Global Gateway e4](http://www.firstdata.com) - CA, US
|
||||
* [FirstPay](http://www.first-pay.com) - US
|
||||
* [Garanti Sanal POS](https://ccpos.garanti.com.tr/ccRaporlar/garanti/ccReports) - US, TR
|
||||
* [HDFC](http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i) - IN
|
||||
* [Inspire](http://www.inspiregateway.com) - US
|
||||
* [InstaPay](http://www.instapayllc.com) - US
|
||||
* [Iridium](http://www.iridiumcorp.co.uk/) - UK, ES
|
||||
* [iTransact](http://www.itransact.com/) - US
|
||||
* [JetPay](http://www.jetpay.com) - US
|
||||
* [LinkPoint](http://www.linkpoint.com/) - US
|
||||
* [Litle](http://www.litle.com/) - US
|
||||
* [Merchant e-Solutions](http://merchante-solutions.com/) - US
|
||||
* [MerchantWare](http://merchantwarehouse.com/merchantware) - US
|
||||
* [Merchant Warrior] (http://merchantwarrior.com) - AU
|
||||
* [Mercury](http://www.mercurypay.com) - US
|
||||
* [MasterCard Internet Gateway Service (MiGS)](http://mastercard.com/mastercardsps) - AU, AE, BD, BN, EG, HK, ID, IN, JO, KW, LB, LK, MU, MV, MY, NZ, OM, PH, QA, SA, SG, TT, VN
|
||||
* [Modern Payments](http://www.modpay.com) - US
|
||||
* [Moneris](http://www.moneris.com/) - CA
|
||||
* [Moneris US](http://www.monerisusa.com/) - US
|
||||
* [NABTransact](http://www.nab.com.au/nabtransact/) - AU
|
||||
* [NELiX TransaX Gateway](http://www.nelixtransax.com) - US
|
||||
* [Netaxept](http://www.betalingsterminal.no/Netthandel-forside) - NO, DK, SE, FI
|
||||
* [NETbilling](http://www.netbilling.com) - US
|
||||
* [NetPay](http://www.netpay.com.mx) - MX
|
||||
* [NetRegistry](http://www.netregistry.com.au) - AU
|
||||
* [NMI](http://nmi.com/) - US
|
||||
* [Ogone DirectLink](http://www.ogone.com) - BE, DE, FR, NL, AT, CH
|
||||
* [Optimal Payments](http://www.optimalpayments.com/) - CA, US, UK
|
||||
* [Orbital Paymentech](http://chasepaymentech.com/) - CA, US, UK, GB
|
||||
* [PayBox Direct](http://www.paybox.com) - FR
|
||||
* [PayFast](https://www.payfast.co.za/) - ZA
|
||||
* [PayGate PayXML](http://paygate.co.za/) - US, ZA
|
||||
* [PayJunction](http://www.payjunction.com/) - US
|
||||
* [PaymentExpress](http://www.paymentexpress.com/) - AU, MY, NZ, SG, ZA, UK, US
|
||||
* [PAYMILL](https://www.paymill.com) - AD, AT, BE, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, SE, SI, SK, TR, VA
|
||||
* [PayPal Express Checkout](https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside) - US, CA, SG, AU
|
||||
* [PayPal Payflow Pro](https://www.paypal.com/cgi-bin/webscr?cmd=_payflow-pro-overview-outside) - US, CA, SG, AU
|
||||
* [PayPal Website Payments Pro (UK)](https://www.paypal.com/uk/cgi-bin/webscr?cmd=_wp-pro-overview-outside) - UK
|
||||
* [PayPal Website Payments Pro (CA)](https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside) - CA
|
||||
* [PayPal Express Checkout](https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside) - US
|
||||
* [PayPal Website Payments Pro (US)](https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside) - US
|
||||
* [PaySecure](http://www.commsecure.com.au/paysecure.shtml) - AU
|
||||
* [PayWay](https://www.payway.com.au) - AU
|
||||
* [Pin](http://www.pin.net.au/) - AU
|
||||
* [Plug'n Pay](http://www.plugnpay.com/) - US
|
||||
* [Psigate](http://www.psigate.com/) - CA
|
||||
* [PSL Payment Solutions](http://www.paymentsolutionsltd.com/) - UK
|
||||
* [Quantum](http://www.quantumgateway.com) - US
|
||||
* [QuickBooks Merchant Services](http://payments.intuit.com/) - US
|
||||
* [Quickpay](http://quickpay.dk/) - DK, SE
|
||||
* [Rabobank Nederland](http://www.rabobank.nl/) - NL
|
||||
* [Realex](http://www.realexpayments.com/) - IE, UK
|
||||
* [Redsys](http://www.redsys.es) - ES
|
||||
* [SagePay](http://www.sagepay.com) - UK
|
||||
* [Sage Payment Solutions](http://www.sagepayments.com) - US, CA
|
||||
* [Sallie Mae](http://www.salliemae.com) - US
|
||||
* [SecureNet](http://www.securenet.com) - US
|
||||
* [SecurePay](http://securepay.com.au) - AU
|
||||
* [SecurePay](http://www.securepay.com/) - US
|
||||
* [SecurePayTech](http://www.securepaytech.com/) - NZ
|
||||
* [SkipJack](http://www.skipjack.com/) - US, CA
|
||||
* [Spreedly Core](https://spreedlycore.com/) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA
|
||||
* [Stripe](https://stripe.com/) - US
|
||||
* [TransFirst](http://www.transfirst.com/) - US
|
||||
* [Transnational](http://www.tnbci.com/) - US
|
||||
* [TrustCommerce](http://www.trustcommerce.com/) - US
|
||||
* [USA ePay](http://www.usaepay.com/) - US
|
||||
* [Verifi](http://www.verifi.com/) - US
|
||||
* [ViaKLIX](http://viaklix.com) - US
|
||||
* [Vindica](http://www.vindicia.com/) - US, CA, UK, AU, MX, BR, DE, KR, CN, HK
|
||||
* [WebPay](https://webpay.jp/) - JP
|
||||
* [Wirecard](http://www.wirecard.com) - DE
|
||||
* [WorldPay](http://www.worldpay.com) - AU, HK, UK, US
|
||||
|
||||
## Supported Offsite Payment Gateways
|
||||
|
||||
* [2 Checkout](http://www.2checkout.com)
|
||||
* [A1Agregator](http://a1agregator.ru/) - RU
|
||||
* [Authorize.Net SIM](http://developer.authorize.net/api/sim/) - US
|
||||
* [Banca Sella GestPay](https://www.sella.it/banca/ecommerce/gestpay/gestpay.jsp)
|
||||
* [Chronopay](http://www.chronopay.com)
|
||||
* [DirecPay](http://www.timesofmoney.com/direcpay/jsp/home.jsp)
|
||||
* [Direct-eBanking / sofortueberweisung.de by Payment-Networks AG](https://www.payment-network.com/deb_com_en/merchantarea/home) - DE, AT, CH, BE, UK, NL
|
||||
* [Dotpay](http://dotpay.pl)
|
||||
* [Dwolla](https://www.dwolla.com/default.aspx)
|
||||
* [ePay](http://www.epay.dk/epay-payment-solutions/)
|
||||
* [First Data](https://firstdata.zendesk.com/entries/407522-first-data-global-gateway-e4sm-payment-pages-integration-manual)
|
||||
* [HiTRUST](http://www.hitrust.com.hk/)
|
||||
* [Moneybookers](http://www.moneybookers.com)
|
||||
* [Nochex](http://www.nochex.com)
|
||||
* [Paxum](https://www.paxum.com/)
|
||||
* [PayPal Website Payments Standard](https://www.paypal.com/cgi-bin/webscr?cmd#_wp-standard-overview-outside)
|
||||
* [Paysbuy](https://www.paysbuy.com/) - TH
|
||||
* [RBK Money](https://rbkmoney.ru/) - RU
|
||||
* [Robokassa](http://robokassa.ru/) - RU
|
||||
* [SagePay Form](http://www.sagepay.com/products_services/sage_pay_go/integration/form)
|
||||
* [Suomen Maksuturva](https://www.maksuturva.fi/services/vendor_services/integration_guidelines.html)
|
||||
* [Valitor](http://www.valitor.is/) - IS
|
||||
* [Verkkomaksut](http://www.verkkomaksut.fi) - FI
|
||||
* [WebMoney](http://www.webmoney.ru) - RU
|
||||
* [WebPay](http://webpay.by/)
|
||||
* [WorldPay](http://www.worldpay.com)
|
||||
|
||||
## Contributing
|
||||
|
||||
The source code is hosted at [GitHub](http://github.com/Shopify/active_merchant), and can be fetched using:
|
||||
|
||||
git clone git://github.com/Shopify/active_merchant.git
|
||||
|
||||
Please see the [ActiveMerchant Guide to Contributing](http://github.com/Shopify/active_merchant/wikis/contributing) for
|
||||
information on adding a new gateway to ActiveMerchant.
|
||||
|
||||
Please don't touch the CHANGELOG in your pull requests, we'll add the appropriate CHANGELOG entries
|
||||
at release time.
|
||||
|
||||
[](http://travis-ci.org/Shopify/active_merchant)
|
||||
|
||||
[](https://codeclimate.com/github/Shopify/active_merchant)
|
||||
@@ -1,20 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApjb2R5
|
||||
ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
||||
b20wHhcNMDcwMjIyMTcyMTI3WhcNMDgwMjIyMTcyMTI3WjBBMRMwEQYDVQQDDApj
|
||||
b2R5ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
||||
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6T4Iqt5iWvAlU
|
||||
iXI6L8UO0URQhIC65X/gJ9hL/x4lwSl/ckVm/R/bPrJGmifT+YooFv824N3y/TIX
|
||||
25o/lZtRj1TUZJK4OCb0aVzosQVxBHSe6rLmxO8cItNTMOM9wn3thaITFrTa1DOQ
|
||||
O3wqEjvW2L6VMozVfK1MfjL9IGgy0rCnl+2g4Gh4jDDpkLfnMG5CWI6cTCf3C1ye
|
||||
ytOpWgi0XpOEy8nQWcFmt/KCQ/kFfzBo4QxqJi54b80842EyvzWT9OB7Oew/CXZG
|
||||
F2yIHtiYxonz6N09vvSzq4CvEuisoUFLKZnktndxMEBKwJU3XeSHAbuS7ix40OKO
|
||||
WKuI54fHAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
||||
BBR9QQpefI3oDCAxiqJW/3Gg6jI6qjANBgkqhkiG9w0BAQUFAAOCAQEAs0lX26O+
|
||||
HpyMp7WL+SgZuM8k76AjfOHuKajl2GEn3S8pWYGpsa0xu07HtehJhKLiavrfUYeE
|
||||
qlFtyYMUyOh6/1S2vfkH6VqjX7mWjoi7XKHW/99fkMS40B5SbN+ypAUst+6c5R84
|
||||
w390mjtLHpdDE6WQYhS6bFvBN53vK6jG3DLyCJc0K9uMQ7gdHWoxq7RnG92ncQpT
|
||||
ThpRA+fky5Xt2Q63YJDnJpkYAz79QIama1enSnd4jslKzSl89JS2luq/zioPe/Us
|
||||
hbyalWR1+HrhgPoSPq7nk+s2FQUBJ9UZFK1lgMzho/4fZgzJwbu+cO8SNuaLS/bj
|
||||
hPaSTyVU0yCSnw==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,63 +0,0 @@
|
||||
#--
|
||||
# Copyright (c) 2005-2010 Tobias Luetke
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
require 'active_support'
|
||||
require 'active_support/core_ext/string/inflections'
|
||||
require 'active_support/core_ext/hash/indifferent_access'
|
||||
require 'active_support/core_ext/hash/conversions'
|
||||
require 'active_support/core_ext/object/conversions'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
require 'active_support/core_ext/class/attribute_accessors'
|
||||
require 'active_support/core_ext/class/delegating_attributes'
|
||||
require 'active_support/core_ext/module/attribute_accessors'
|
||||
|
||||
begin
|
||||
require 'active_support/base64'
|
||||
|
||||
unless defined?(Base64)
|
||||
Base64 = ActiveSupport::Base64
|
||||
end
|
||||
|
||||
unless Base64.respond_to?(:strict_encode64)
|
||||
def Base64.strict_encode64(v)
|
||||
ActiveSupport::Base64.encode64s(v)
|
||||
end
|
||||
end
|
||||
rescue LoadError
|
||||
require 'base64'
|
||||
end
|
||||
|
||||
require 'securerandom'
|
||||
require 'builder'
|
||||
require 'cgi'
|
||||
require 'rexml/document'
|
||||
|
||||
require 'active_utils'
|
||||
require 'active_merchant/billing'
|
||||
require 'active_merchant/version'
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
autoload :Integrations, 'active_merchant/billing/integrations'
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
require 'active_merchant/billing/avs_result'
|
||||
require 'active_merchant/billing/cvv_result'
|
||||
require 'active_merchant/billing/credit_card_methods'
|
||||
require 'active_merchant/billing/credit_card_formatting'
|
||||
require 'active_merchant/billing/credit_card'
|
||||
require 'active_merchant/billing/base'
|
||||
require 'active_merchant/billing/check'
|
||||
require 'active_merchant/billing/response'
|
||||
require 'active_merchant/billing/gateways'
|
||||
@@ -1,98 +0,0 @@
|
||||
#!ruby19
|
||||
# encoding: utf-8
|
||||
|
||||
module ActiveMerchant
|
||||
module Billing
|
||||
# Implements the Address Verification System
|
||||
# https://www.wellsfargo.com/downloads/pdf/biz/merchant/visa_avs.pdf
|
||||
# http://en.wikipedia.org/wiki/Address_Verification_System
|
||||
# http://apps.cybersource.com/library/documentation/dev_guides/CC_Svcs_IG/html/app_avs_cvn_codes.htm#app_AVS_CVN_codes_7891_48375
|
||||
# http://imgserver.skipjack.com/imgServer/5293710/AVS%20and%20CVV2.pdf
|
||||
# http://www.emsecommerce.net/avs_cvv2_response_codes.htm
|
||||
class AVSResult
|
||||
MESSAGES = {
|
||||
'A' => 'Street address matches, but 5-digit and 9-digit postal code do not match.',
|
||||
'B' => 'Street address matches, but postal code not verified.',
|
||||
'C' => 'Street address and postal code do not match.',
|
||||
'D' => 'Street address and postal code match.',
|
||||
'E' => 'AVS data is invalid or AVS is not allowed for this card type.',
|
||||
'F' => 'Card member\'s name does not match, but billing postal code matches.',
|
||||
'G' => 'Non-U.S. issuing bank does not support AVS.',
|
||||
'H' => 'Card member\'s name does not match. Street address and postal code match.',
|
||||
'I' => 'Address not verified.',
|
||||
'J' => 'Card member\'s name, billing address, and postal code match. Shipping information verified and chargeback protection guaranteed through the Fraud Protection Program.',
|
||||
'K' => 'Card member\'s name matches but billing address and billing postal code do not match.',
|
||||
'L' => 'Card member\'s name and billing postal code match, but billing address does not match.',
|
||||
'M' => 'Street address and postal code match.',
|
||||
'N' => 'Street address and postal code do not match.',
|
||||
'O' => 'Card member\'s name and billing address match, but billing postal code does not match.',
|
||||
'P' => 'Postal code matches, but street address not verified.',
|
||||
'Q' => 'Card member\'s name, billing address, and postal code match. Shipping information verified but chargeback protection not guaranteed.',
|
||||
'R' => 'System unavailable.',
|
||||
'S' => 'U.S.-issuing bank does not support AVS.',
|
||||
'T' => 'Card member\'s name does not match, but street address matches.',
|
||||
'U' => 'Address information unavailable.',
|
||||
'V' => 'Card member\'s name, billing address, and billing postal code match.',
|
||||
'W' => 'Street address does not match, but 9-digit postal code matches.',
|
||||
'X' => 'Street address and 9-digit postal code match.',
|
||||
'Y' => 'Street address and 5-digit postal code match.',
|
||||
'Z' => 'Street address does not match, but 5-digit postal code matches.'
|
||||
}
|
||||
|
||||
# Map vendor's AVS result code to a postal match code
|
||||
POSTAL_MATCH_CODE = {
|
||||
'Y' => %w( D H F H J L M P Q V W X Y Z ),
|
||||
'N' => %w( A C K N O ),
|
||||
'X' => %w( G S ),
|
||||
nil => %w( B E I R T U )
|
||||
}.inject({}) do |map, (type, codes)|
|
||||
codes.each { |code| map[code] = type }
|
||||
map
|
||||
end
|
||||
|
||||
# Map vendor's AVS result code to a street match code
|
||||
STREET_MATCH_CODE = {
|
||||
'Y' => %w( A B D H J M O Q T V X Y ),
|
||||
'N' => %w( C K L N W Z ),
|
||||
'X' => %w( G S ),
|
||||
nil => %w( E F I P R U )
|
||||
}.inject({}) do |map, (type, codes)|
|
||||
codes.each { |code| map[code] = type }
|
||||
map
|
||||
end
|
||||
|
||||
attr_reader :code, :message, :street_match, :postal_match
|
||||
|
||||
def self.messages
|
||||
MESSAGES
|
||||
end
|
||||
|
||||
def initialize(attrs)
|
||||
attrs ||= {}
|
||||
|
||||
@code = attrs[:code].upcase unless attrs[:code].blank?
|
||||
@message = self.class.messages[code]
|
||||
|
||||
if attrs[:street_match].blank?
|
||||
@street_match = STREET_MATCH_CODE[code]
|
||||
else
|
||||
@street_match = attrs[:street_match].upcase
|
||||
end
|
||||
|
||||
if attrs[:postal_match].blank?
|
||||
@postal_match = POSTAL_MATCH_CODE[code]
|
||||
else
|
||||
@postal_match = attrs[:postal_match].upcase
|
||||
end
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{ 'code' => code,
|
||||
'message' => message,
|
||||
'street_match' => street_match,
|
||||
'postal_match' => postal_match
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,56 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
module Base
|
||||
# Set ActiveMerchant gateways in test mode.
|
||||
#
|
||||
# ActiveMerchant::Billing::Base.gateway_mode = :test
|
||||
mattr_accessor :gateway_mode
|
||||
|
||||
# Set ActiveMerchant integrations in test mode.
|
||||
#
|
||||
# ActiveMerchant::Billing::Base.integration_mode = :test
|
||||
mattr_accessor :integration_mode
|
||||
|
||||
# Set both the mode of both the gateways and integrations
|
||||
# at once
|
||||
mattr_reader :mode
|
||||
|
||||
def self.mode=(mode)
|
||||
@@mode = mode
|
||||
self.gateway_mode = mode
|
||||
self.integration_mode = mode
|
||||
end
|
||||
|
||||
self.mode = :production
|
||||
|
||||
# Return the matching gateway for the provider
|
||||
# * <tt>bogus</tt>: BogusGateway - Does nothing (for testing)
|
||||
# * <tt>moneris</tt>: MonerisGateway
|
||||
# * <tt>authorize_net</tt>: AuthorizeNetGateway
|
||||
# * <tt>trust_commerce</tt>: TrustCommerceGateway
|
||||
#
|
||||
# ActiveMerchant::Billing::Base.gateway('moneris').new
|
||||
def self.gateway(name)
|
||||
Billing.const_get("#{name.to_s.downcase}_gateway".camelize)
|
||||
end
|
||||
|
||||
# Return the matching integration module
|
||||
# You can then get the notification from the module
|
||||
# * <tt>bogus</tt>: Bogus - Does nothing (for testing)
|
||||
# * <tt>chronopay</tt>: Chronopay
|
||||
# * <tt>paypal</tt>: Paypal
|
||||
#
|
||||
# chronopay = ActiveMerchant::Billing::Base.integration('chronopay')
|
||||
# notification = chronopay.notification(raw_post)
|
||||
#
|
||||
def self.integration(name)
|
||||
Billing::Integrations.const_get("#{name.to_s.downcase}".camelize)
|
||||
end
|
||||
|
||||
# A check to see if we're in test mode
|
||||
def self.test?
|
||||
self.gateway_mode == :test
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,69 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
# The Check object is a plain old Ruby object, similar to CreditCard. It supports validation
|
||||
# of necessary attributes such as checkholder's name, routing and account numbers, but it is
|
||||
# not backed by any database.
|
||||
#
|
||||
# You may use Check in place of CreditCard with any gateway that supports it.
|
||||
class Check
|
||||
include Validateable
|
||||
|
||||
attr_accessor :first_name, :last_name,
|
||||
:bank_name, :routing_number, :account_number,
|
||||
:account_holder_type, :account_type, :number
|
||||
|
||||
# Used for Canadian bank accounts
|
||||
attr_accessor :institution_number, :transit_number
|
||||
|
||||
def name
|
||||
@name ||= "#{@first_name} #{@last_name}".strip
|
||||
end
|
||||
|
||||
def name=(value)
|
||||
return if value.blank?
|
||||
|
||||
@name = value
|
||||
segments = value.split(' ')
|
||||
@last_name = segments.pop
|
||||
@first_name = segments.join(' ')
|
||||
end
|
||||
|
||||
def validate
|
||||
[:name, :routing_number, :account_number].each do |attr|
|
||||
errors.add(attr, "cannot be empty") if self.send(attr).blank?
|
||||
end
|
||||
|
||||
errors.add(:routing_number, "is invalid") unless valid_routing_number?
|
||||
|
||||
errors.add(:account_holder_type, "must be personal or business") if
|
||||
!account_holder_type.blank? && !%w[business personal].include?(account_holder_type.to_s)
|
||||
|
||||
errors.add(:account_type, "must be checking or savings") if
|
||||
!account_type.blank? && !%w[checking savings].include?(account_type.to_s)
|
||||
end
|
||||
|
||||
def type
|
||||
'check'
|
||||
end
|
||||
|
||||
# Routing numbers may be validated by calculating a checksum and dividing it by 10. The
|
||||
# formula is:
|
||||
# (3(d1 + d4 + d7) + 7(d2 + d5 + d8) + 1(d3 + d6 + d9))mod 10 = 0
|
||||
# See http://en.wikipedia.org/wiki/Routing_transit_number#Internal_checksums
|
||||
def valid_routing_number?
|
||||
d = routing_number.to_s.split('').map(&:to_i).select { |d| (0..9).include?(d) }
|
||||
case d.size
|
||||
when 9 then
|
||||
checksum = ((3 * (d[0] + d[3] + d[6])) +
|
||||
(7 * (d[1] + d[4] + d[7])) +
|
||||
(d[2] + d[5] + d[8])) % 10
|
||||
case checksum
|
||||
when 0 then true
|
||||
else false
|
||||
end
|
||||
else false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,278 +0,0 @@
|
||||
require 'time'
|
||||
require 'date'
|
||||
require 'active_merchant/billing/expiry_date'
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
# A +CreditCard+ object represents a physical credit card, and is capable of validating the various
|
||||
# data associated with these.
|
||||
#
|
||||
# At the moment, the following credit card types are supported:
|
||||
#
|
||||
# * Visa
|
||||
# * MasterCard
|
||||
# * Discover
|
||||
# * American Express
|
||||
# * Diner's Club
|
||||
# * JCB
|
||||
# * Switch
|
||||
# * Solo
|
||||
# * Dankort
|
||||
# * Maestro
|
||||
# * Forbrugsforeningen
|
||||
# * Laser
|
||||
#
|
||||
# For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of
|
||||
# validations, allowing you to focus on your core concerns until you're ready to be more concerned
|
||||
# with the details of particular credit cards or your gateway.
|
||||
#
|
||||
# == Testing With CreditCard
|
||||
# Often when testing we don't care about the particulars of a given card brand. When using the 'test'
|
||||
# mode in your {Gateway}, there are six different valid card numbers: 1, 2, 3, 'success', 'fail',
|
||||
# and 'error'.
|
||||
#
|
||||
# For details, see {CreditCardMethods::ClassMethods#valid_number?}
|
||||
#
|
||||
# == Example Usage
|
||||
# cc = CreditCard.new(
|
||||
# :first_name => 'Steve',
|
||||
# :last_name => 'Smith',
|
||||
# :month => '9',
|
||||
# :year => '2010',
|
||||
# :brand => 'visa',
|
||||
# :number => '4242424242424242'
|
||||
# )
|
||||
#
|
||||
# cc.valid? # => true
|
||||
# cc.display_number # => XXXX-XXXX-XXXX-4242
|
||||
#
|
||||
class CreditCard
|
||||
include CreditCardMethods
|
||||
include Validateable
|
||||
|
||||
cattr_accessor :require_verification_value
|
||||
self.require_verification_value = true
|
||||
|
||||
# Returns or sets the credit card number.
|
||||
#
|
||||
# @return [String]
|
||||
attr_accessor :number
|
||||
|
||||
# Returns or sets the expiry month for the card.
|
||||
#
|
||||
# @return [Integer]
|
||||
attr_accessor :month
|
||||
|
||||
# Returns or sets the expiry year for the card.
|
||||
#
|
||||
# @return [Integer]
|
||||
attr_accessor :year
|
||||
|
||||
# Returns or sets the credit card brand.
|
||||
#
|
||||
# Valid card types are
|
||||
#
|
||||
# * +'visa'+
|
||||
# * +'master'+
|
||||
# * +'discover'+
|
||||
# * +'american_express'+
|
||||
# * +'diners_club'+
|
||||
# * +'jcb'+
|
||||
# * +'switch'+
|
||||
# * +'solo'+
|
||||
# * +'dankort'+
|
||||
# * +'maestro'+
|
||||
# * +'forbrugsforeningen'+
|
||||
# * +'laser'+
|
||||
#
|
||||
# Or, if you wish to test your implementation, +'bogus'+.
|
||||
#
|
||||
# @return (String) the credit card brand
|
||||
attr_accessor :brand
|
||||
|
||||
# Returns or sets the first name of the card holder.
|
||||
#
|
||||
# @return [String]
|
||||
attr_accessor :first_name
|
||||
|
||||
# Returns or sets the last name of the card holder.
|
||||
#
|
||||
# @return [String]
|
||||
attr_accessor :last_name
|
||||
|
||||
# Required for Switch / Solo cards
|
||||
attr_accessor :start_month, :start_year, :issue_number
|
||||
|
||||
# Returns or sets the card verification value.
|
||||
#
|
||||
# This attribute is optional but recommended. The verification value is
|
||||
# a {card security code}[http://en.wikipedia.org/wiki/Card_security_code]. If provided,
|
||||
# the gateway will attempt to validate the value.
|
||||
#
|
||||
# @return [String] the verification value
|
||||
attr_accessor :verification_value
|
||||
|
||||
def type
|
||||
self.class.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead."
|
||||
brand
|
||||
end
|
||||
|
||||
def type=(value)
|
||||
self.class.deprecated "CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead."
|
||||
self.brand = value
|
||||
end
|
||||
|
||||
# Provides proxy access to an expiry date object
|
||||
#
|
||||
# @return [ExpiryDate]
|
||||
def expiry_date
|
||||
ExpiryDate.new(@month, @year)
|
||||
end
|
||||
|
||||
# Returns whether the credit card has expired.
|
||||
#
|
||||
# @return +true+ if the card has expired, +false+ otherwise
|
||||
def expired?
|
||||
expiry_date.expired?
|
||||
end
|
||||
|
||||
# Returns whether either the +first_name+ or the +last_name+ attributes has been set.
|
||||
def name?
|
||||
first_name? || last_name?
|
||||
end
|
||||
|
||||
# Returns whether the +first_name+ attribute has been set.
|
||||
def first_name?
|
||||
@first_name.present?
|
||||
end
|
||||
|
||||
# Returns whether the +last_name+ attribute has been set.
|
||||
def last_name?
|
||||
@last_name.present?
|
||||
end
|
||||
|
||||
# Returns the full name of the card holder.
|
||||
#
|
||||
# @return [String] the full name of the card holder
|
||||
def name
|
||||
[@first_name, @last_name].compact.join(' ')
|
||||
end
|
||||
|
||||
def name=(full_name)
|
||||
names = full_name.split
|
||||
self.last_name = names.pop
|
||||
self.first_name = names.join(" ")
|
||||
end
|
||||
|
||||
def verification_value?
|
||||
!@verification_value.blank?
|
||||
end
|
||||
|
||||
# Returns a display-friendly version of the card number.
|
||||
#
|
||||
# All but the last 4 numbers are replaced with an "X", and hyphens are
|
||||
# inserted in order to improve legibility.
|
||||
#
|
||||
# @example
|
||||
# credit_card = CreditCard.new(:number => "2132542376824338")
|
||||
# credit_card.display_number # "XXXX-XXXX-XXXX-4338"
|
||||
#
|
||||
# @return [String] a display-friendly version of the card number
|
||||
def display_number
|
||||
self.class.mask(number)
|
||||
end
|
||||
|
||||
def first_digits
|
||||
self.class.first_digits(number)
|
||||
end
|
||||
|
||||
def last_digits
|
||||
self.class.last_digits(number)
|
||||
end
|
||||
|
||||
# Validates the credit card details.
|
||||
#
|
||||
# Any validation errors are added to the {#errors} attribute.
|
||||
def validate
|
||||
validate_essential_attributes
|
||||
|
||||
# Bogus card is pretty much for testing purposes. Lets just skip these extra tests if its used
|
||||
return if brand == 'bogus'
|
||||
|
||||
validate_card_brand
|
||||
validate_card_number
|
||||
validate_verification_value
|
||||
validate_switch_or_solo_attributes
|
||||
end
|
||||
|
||||
def self.requires_verification_value?
|
||||
require_verification_value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def before_validate #:nodoc:
|
||||
self.month = month.to_i
|
||||
self.year = year.to_i
|
||||
self.start_month = start_month.to_i unless start_month.nil?
|
||||
self.start_year = start_year.to_i unless start_year.nil?
|
||||
self.number = number.to_s.gsub(/[^\d]/, "")
|
||||
self.brand.downcase! if brand.respond_to?(:downcase)
|
||||
self.brand = self.class.brand?(number) if brand.blank?
|
||||
end
|
||||
|
||||
def validate_card_number #:nodoc:
|
||||
if number.blank?
|
||||
errors.add :number, "is required"
|
||||
elsif !CreditCard.valid_number?(number)
|
||||
errors.add :number, "is not a valid credit card number"
|
||||
end
|
||||
|
||||
unless errors.on(:number) || errors.on(:brand)
|
||||
errors.add :brand, "does not match the card number" unless CreditCard.matching_brand?(number, brand)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_card_brand #:nodoc:
|
||||
errors.add :brand, "is required" if brand.blank? && number.present?
|
||||
errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand)
|
||||
end
|
||||
|
||||
alias_method :validate_card_type, :validate_card_brand
|
||||
|
||||
def validate_essential_attributes #:nodoc:
|
||||
errors.add :first_name, "cannot be empty" if @first_name.blank?
|
||||
errors.add :last_name, "cannot be empty" if @last_name.blank?
|
||||
|
||||
if @month.to_i.zero? || @year.to_i.zero?
|
||||
errors.add :month, "is required" if @month.to_i.zero?
|
||||
errors.add :year, "is required" if @year.to_i.zero?
|
||||
else
|
||||
errors.add :month, "is not a valid month" unless valid_month?(@month)
|
||||
errors.add :year, "expired" if expired?
|
||||
errors.add :year, "is not a valid year" unless expired? || valid_expiry_year?(@year)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_switch_or_solo_attributes #:nodoc:
|
||||
if %w[switch solo].include?(brand)
|
||||
unless valid_month?(@start_month) && valid_start_year?(@start_year) || valid_issue_number?(@issue_number)
|
||||
if @issue_number.blank?
|
||||
errors.add :start_month, "is invalid" unless valid_month?(@start_month)
|
||||
errors.add :start_year, "is invalid" unless valid_start_year?(@start_year)
|
||||
errors.add :issue_number, "cannot be empty"
|
||||
else
|
||||
errors.add :issue_number, "is invalid" unless valid_issue_number?(@issue_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_verification_value #:nodoc:
|
||||
if CreditCard.requires_verification_value?
|
||||
errors.add :verification_value, "is required" unless verification_value?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
module CreditCardFormatting
|
||||
|
||||
# This method is used to format numerical information pertaining to credit cards.
|
||||
#
|
||||
# format(2005, :two_digits) # => "05"
|
||||
# format(05, :four_digits) # => "0005"
|
||||
def format(number, option)
|
||||
return '' if number.blank?
|
||||
|
||||
case option
|
||||
when :two_digits ; sprintf("%.2i", number.to_i)[-2..-1]
|
||||
when :four_digits ; sprintf("%.4i", number.to_i)[-4..-1]
|
||||
else number
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,143 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
# Convenience methods that can be included into a custom Credit Card object, such as an ActiveRecord based Credit Card object.
|
||||
module CreditCardMethods
|
||||
CARD_COMPANIES = {
|
||||
'visa' => /^4\d{12}(\d{3})?$/,
|
||||
'master' => /^(5[1-5]\d{4}|677189)\d{10}$/,
|
||||
'discover' => /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/,
|
||||
'american_express' => /^3[47]\d{13}$/,
|
||||
'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
|
||||
'jcb' => /^35(28|29|[3-8]\d)\d{12}$/,
|
||||
'switch' => /^6759\d{12}(\d{2,3})?$/,
|
||||
'solo' => /^6767\d{12}(\d{2,3})?$/,
|
||||
'dankort' => /^5019\d{12}$/,
|
||||
'maestro' => /^(5[06-8]|6\d)\d{10,17}$/,
|
||||
'forbrugsforeningen' => /^600722\d{10}$/,
|
||||
'laser' => /^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/
|
||||
}
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def valid_month?(month)
|
||||
(1..12).include?(month.to_i)
|
||||
end
|
||||
|
||||
def valid_expiry_year?(year)
|
||||
(Time.now.year..Time.now.year + 20).include?(year.to_i)
|
||||
end
|
||||
|
||||
def valid_start_year?(year)
|
||||
year.to_s =~ /^\d{4}$/ && year.to_i > 1987
|
||||
end
|
||||
|
||||
def valid_issue_number?(number)
|
||||
number.to_s =~ /^\d{1,2}$/
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Returns true if it validates. Optionally, you can pass a card brand as an argument and
|
||||
# make sure it is of the correct brand.
|
||||
#
|
||||
# References:
|
||||
# - http://perl.about.com/compute/perl/library/nosearch/P073000.htm
|
||||
# - http://www.beachnet.com/~hstiles/cardtype.html
|
||||
def valid_number?(number)
|
||||
valid_test_mode_card_number?(number) ||
|
||||
valid_card_number_length?(number) &&
|
||||
valid_checksum?(number)
|
||||
end
|
||||
|
||||
# Regular expressions for the known card companies.
|
||||
#
|
||||
# References:
|
||||
# - http://en.wikipedia.org/wiki/Credit_card_number
|
||||
# - http://www.barclaycardbusiness.co.uk/information_zone/processing/bin_rules.html
|
||||
def card_companies
|
||||
CARD_COMPANIES
|
||||
end
|
||||
|
||||
# Returns a string containing the brand of card from the list of known information below.
|
||||
# Need to check the cards in a particular order, as there is some overlap of the allowable ranges
|
||||
#--
|
||||
# TODO Refactor this method. We basically need to tighten up the Maestro Regexp.
|
||||
#
|
||||
# Right now the Maestro regexp overlaps with the MasterCard regexp (IIRC). If we can tighten
|
||||
# things up, we can boil this whole thing down to something like...
|
||||
#
|
||||
# def brand?(number)
|
||||
# return 'visa' if valid_test_mode_card_number?(number)
|
||||
# card_companies.find([nil]) { |brand, regexp| number =~ regexp }.first.dup
|
||||
# end
|
||||
#
|
||||
def brand?(number)
|
||||
return 'bogus' if valid_test_mode_card_number?(number)
|
||||
|
||||
card_companies.reject { |c,p| c == 'maestro' }.each do |company, pattern|
|
||||
return company.dup if number =~ pattern
|
||||
end
|
||||
|
||||
return 'maestro' if number =~ card_companies['maestro']
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
def type?(number)
|
||||
deprecated "CreditCard#type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand? instead."
|
||||
brand?(number)
|
||||
end
|
||||
|
||||
def first_digits(number)
|
||||
number.to_s.slice(0,6)
|
||||
end
|
||||
|
||||
def last_digits(number)
|
||||
number.to_s.length <= 4 ? number : number.to_s.slice(-4..-1)
|
||||
end
|
||||
|
||||
def mask(number)
|
||||
"XXXX-XXXX-XXXX-#{last_digits(number)}"
|
||||
end
|
||||
|
||||
# Checks to see if the calculated brand matches the specified brand
|
||||
def matching_brand?(number, brand)
|
||||
brand?(number) == brand
|
||||
end
|
||||
|
||||
def matching_type?(number, brand)
|
||||
deprecated "CreditCard#matching_type? is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#matching_brand? instead."
|
||||
matching_brand?(number, brand)
|
||||
end
|
||||
|
||||
def deprecated(message)
|
||||
warn(Kernel.caller[1] + message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_card_number_length?(number) #:nodoc:
|
||||
number.to_s.length >= 12
|
||||
end
|
||||
|
||||
def valid_test_mode_card_number?(number) #:nodoc:
|
||||
ActiveMerchant::Billing::Base.test? &&
|
||||
%w[1 2 3 success failure error].include?(number.to_s)
|
||||
end
|
||||
|
||||
# Checks the validity of a card number by use of the the Luhn Algorithm.
|
||||
# Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details.
|
||||
def valid_checksum?(number) #:nodoc:
|
||||
sum = 0
|
||||
for i in 0..number.length
|
||||
weight = number[-1 * (i + 2), 1].to_i * (2 - (i % 2))
|
||||
sum += (weight < 10) ? weight : weight - 9
|
||||
end
|
||||
|
||||
(number[-1,1].to_i == (10 - sum % 10) % 10)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,38 +0,0 @@
|
||||
module ActiveMerchant
|
||||
module Billing
|
||||
# Result of the Card Verification Value check
|
||||
# http://www.bbbonline.org/eExport/doc/MerchantGuide_cvv2.pdf
|
||||
# Check additional codes from cybersource website
|
||||
class CVVResult
|
||||
|
||||
MESSAGES = {
|
||||
'D' => 'Suspicious transaction',
|
||||
'I' => 'Failed data validation check',
|
||||
'M' => 'Match',
|
||||
'N' => 'No Match',
|
||||
'P' => 'Not Processed',
|
||||
'S' => 'Should have been present',
|
||||
'U' => 'Issuer unable to process request',
|
||||
'X' => 'Card does not support verification'
|
||||
}
|
||||
|
||||
def self.messages
|
||||
MESSAGES
|
||||
end
|
||||
|
||||
attr_reader :code, :message
|
||||
|
||||
def initialize(code)
|
||||
@code = code.upcase unless code.blank?
|
||||
@message = MESSAGES[@code]
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{
|
||||
'code' => code,
|
||||
'message' => message
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,34 +0,0 @@
|
||||
require 'date'
|
||||
|
||||
module ActiveMerchant
|
||||
module Billing
|
||||
class CreditCard
|
||||
class ExpiryDate #:nodoc:
|
||||
attr_reader :month, :year
|
||||
def initialize(month, year)
|
||||
@month = month.to_i
|
||||
@year = year.to_i
|
||||
end
|
||||
|
||||
def expired? #:nodoc:
|
||||
Time.now.utc > expiration
|
||||
end
|
||||
|
||||
def expiration #:nodoc:
|
||||
begin
|
||||
Time.utc(year, month, month_days, 23, 59, 59)
|
||||
rescue ArgumentError
|
||||
Time.at(0).utc
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def month_days
|
||||
mdays = [nil,31,28,31,30,31,30,31,31,30,31,30,31]
|
||||
mdays[2] = 29 if Date.leap?(year)
|
||||
mdays[month]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,177 +0,0 @@
|
||||
require 'net/http'
|
||||
require 'net/https'
|
||||
require 'active_merchant/billing/response'
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
#
|
||||
# == Description
|
||||
# The Gateway class is the base class for all ActiveMerchant gateway implementations.
|
||||
#
|
||||
# The standard list of gateway functions that most concrete gateway subclasses implement is:
|
||||
#
|
||||
# * <tt>purchase(money, creditcard, options = {})</tt>
|
||||
# * <tt>authorize(money, creditcard, options = {})</tt>
|
||||
# * <tt>capture(money, authorization, options = {})</tt>
|
||||
# * <tt>void(identification, options = {})</tt>
|
||||
# * <tt>credit(money, identification, options = {})</tt>
|
||||
#
|
||||
# Some gateways include features for recurring billing
|
||||
#
|
||||
# * <tt>recurring(money, creditcard, options = {})</tt>
|
||||
#
|
||||
# Some gateways also support features for storing credit cards:
|
||||
#
|
||||
# * <tt>store(creditcard, options = {})</tt>
|
||||
# * <tt>unstore(identification, options = {})</tt>
|
||||
#
|
||||
# === Gateway Options
|
||||
# The options hash consists of the following options:
|
||||
#
|
||||
# * <tt>:order_id</tt> - The order number
|
||||
# * <tt>:ip</tt> - The IP address of the customer making the purchase
|
||||
# * <tt>:customer</tt> - The name, customer number, or other information that identifies the customer
|
||||
# * <tt>:invoice</tt> - The invoice number
|
||||
# * <tt>:merchant</tt> - The name or description of the merchant offering the product
|
||||
# * <tt>:description</tt> - A description of the transaction
|
||||
# * <tt>:email</tt> - The email address of the customer
|
||||
# * <tt>:currency</tt> - The currency of the transaction. Only important when you are using a currency that is not the default with a gateway that supports multiple currencies.
|
||||
# * <tt>:billing_address</tt> - A hash containing the billing address of the customer.
|
||||
# * <tt>:shipping_address</tt> - A hash containing the shipping address of the customer.
|
||||
#
|
||||
# The <tt>:billing_address</tt>, and <tt>:shipping_address</tt> hashes can have the following keys:
|
||||
#
|
||||
# * <tt>:name</tt> - The full name of the customer.
|
||||
# * <tt>:company</tt> - The company name of the customer.
|
||||
# * <tt>:address1</tt> - The primary street address of the customer.
|
||||
# * <tt>:address2</tt> - Additional line of address information.
|
||||
# * <tt>:city</tt> - The city of the customer.
|
||||
# * <tt>:state</tt> - The state of the customer. The 2 digit code for US and Canadian addresses. The full name of the state or province for foreign addresses.
|
||||
# * <tt>:country</tt> - The [ISO 3166-1-alpha-2 code](http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm) for the customer.
|
||||
# * <tt>:zip</tt> - The zip or postal code of the customer.
|
||||
# * <tt>:phone</tt> - The phone number of the customer.
|
||||
#
|
||||
# == Implmenting new gateways
|
||||
#
|
||||
# See the {ActiveMerchant Guide to Contributing}[https://github.com/Shopify/active_merchant/wiki/Contributing]
|
||||
#
|
||||
class Gateway
|
||||
include PostsData
|
||||
include RequiresParameters
|
||||
include CreditCardFormatting
|
||||
include Utils
|
||||
|
||||
DEBIT_CARDS = [ :switch, :solo ]
|
||||
CURRENCIES_WITHOUT_FRACTIONS = [ 'JPY', 'HUF', 'TWD' ]
|
||||
CREDIT_DEPRECATION_MESSAGE = "Support for using credit to refund existing transactions is deprecated and will be removed from a future release of ActiveMerchant. Please use the refund method instead."
|
||||
|
||||
cattr_reader :implementations
|
||||
@@implementations = []
|
||||
|
||||
def self.inherited(subclass)
|
||||
super
|
||||
@@implementations << subclass
|
||||
end
|
||||
|
||||
# The format of the amounts used by the gateway
|
||||
# :dollars => '12.50'
|
||||
# :cents => '1250'
|
||||
class_attribute :money_format
|
||||
self.money_format = :dollars
|
||||
|
||||
# The default currency for the transactions if no currency is provided
|
||||
class_attribute :default_currency
|
||||
|
||||
# The countries of merchants the gateway supports
|
||||
class_attribute :supported_countries
|
||||
self.supported_countries = []
|
||||
|
||||
# The supported card types for the gateway
|
||||
class_attribute :supported_cardtypes
|
||||
self.supported_cardtypes = []
|
||||
|
||||
class_attribute :homepage_url
|
||||
class_attribute :display_name
|
||||
|
||||
class_attribute :test_url, :live_url
|
||||
|
||||
class_attribute :abstract_class
|
||||
|
||||
self.abstract_class = false
|
||||
|
||||
# The application making the calls to the gateway
|
||||
# Useful for things like the PayPal build notation (BN) id fields
|
||||
superclass_delegating_accessor :application_id
|
||||
self.application_id = 'ActiveMerchant'
|
||||
|
||||
attr_reader :options
|
||||
|
||||
# Use this method to check if your gateway of interest supports a credit card of some type
|
||||
def self.supports?(card_type)
|
||||
supported_cardtypes.include?(card_type.to_sym)
|
||||
end
|
||||
|
||||
def self.card_brand(source)
|
||||
result = source.respond_to?(:brand) ? source.brand : source.type
|
||||
result.to_s.downcase
|
||||
end
|
||||
|
||||
def card_brand(source)
|
||||
self.class.card_brand(source)
|
||||
end
|
||||
|
||||
# Initialize a new gateway.
|
||||
#
|
||||
# See the documentation for the gateway you will be using to make sure there are no other
|
||||
# required options.
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
end
|
||||
|
||||
# Are we running in test mode?
|
||||
def test?
|
||||
(@options.has_key?(:test) ? @options[:test] : Base.test?)
|
||||
end
|
||||
|
||||
private # :nodoc: all
|
||||
|
||||
def name
|
||||
self.class.name.scan(/\:\:(\w+)Gateway/).flatten.first
|
||||
end
|
||||
|
||||
def amount(money)
|
||||
return nil if money.nil?
|
||||
cents = if money.respond_to?(:cents)
|
||||
deprecated "Support for Money objects is deprecated and will be removed from a future release of ActiveMerchant. Please use an Integer value in cents"
|
||||
money.cents
|
||||
else
|
||||
money
|
||||
end
|
||||
|
||||
if money.is_a?(String)
|
||||
raise ArgumentError, 'money amount must be a positive Integer in cents.'
|
||||
end
|
||||
|
||||
if self.money_format == :cents
|
||||
cents.to_s
|
||||
else
|
||||
sprintf("%.2f", cents.to_f / 100)
|
||||
end
|
||||
end
|
||||
|
||||
def localized_amount(money, currency)
|
||||
amount = amount(money)
|
||||
CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) ? amount.split('.').first : amount
|
||||
end
|
||||
|
||||
def currency(money)
|
||||
money.respond_to?(:currency) ? money.currency : self.default_currency
|
||||
end
|
||||
|
||||
def requires_start_date_or_issue_number?(credit_card)
|
||||
return false if card_brand(credit_card).blank?
|
||||
DEBIT_CARDS.include?(card_brand(credit_card).to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,17 +0,0 @@
|
||||
module ActiveMerchant
|
||||
module Billing
|
||||
autoload :Gateway, 'active_merchant/billing/gateway'
|
||||
|
||||
Dir[File.dirname(__FILE__) + '/gateways/**/*.rb'].each do |f|
|
||||
# Get camelized class name
|
||||
filename = File.basename(f, '.rb')
|
||||
# Add _gateway suffix
|
||||
gateway_name = filename + '_gateway'
|
||||
# Camelize the string to get the class name
|
||||
gateway_class = gateway_name.camelize
|
||||
|
||||
# Register for autoloading
|
||||
autoload gateway_class, f
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,724 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
# For more information on the Authorize.Net Gateway please visit their {Integration Center}[http://developer.authorize.net/]
|
||||
#
|
||||
# The login and password are not the username and password you use to
|
||||
# login to the Authorize.Net Merchant Interface. Instead, you will
|
||||
# use the API Login ID as the login and Transaction Key as the
|
||||
# password.
|
||||
#
|
||||
# ==== How to Get Your API Login ID and Transaction Key
|
||||
#
|
||||
# 1. Log into the Merchant Interface
|
||||
# 2. Select Settings from the Main Menu
|
||||
# 3. Click on API Login ID and Transaction Key in the Security section
|
||||
# 4. Type in the answer to the secret question configured on setup
|
||||
# 5. Click Submit
|
||||
#
|
||||
# ==== Automated Recurring Billing (ARB)
|
||||
#
|
||||
# Automated Recurring Billing (ARB) is an optional service for submitting and managing recurring, or subscription-based, transactions.
|
||||
#
|
||||
# To use recurring, update_recurring, cancel_recurring and status_recurring ARB must be enabled for your account.
|
||||
#
|
||||
# Information about ARB is available on the {Authorize.Net website}[http://www.authorize.net/solutions/merchantsolutions/merchantservices/automatedrecurringbilling/].
|
||||
# Information about the ARB API is available at the {Authorize.Net Integration Center}[http://developer.authorize.net/]
|
||||
class AuthorizeNetGateway < Gateway
|
||||
API_VERSION = '3.1'
|
||||
|
||||
class_attribute :arb_test_url, :arb_live_url
|
||||
|
||||
self.test_url = "https://test.authorize.net/gateway/transact.dll"
|
||||
self.live_url = "https://secure.authorize.net/gateway/transact.dll"
|
||||
|
||||
self.arb_test_url = 'https://apitest.authorize.net/xml/v1/request.api'
|
||||
self.arb_live_url = 'https://api.authorize.net/xml/v1/request.api'
|
||||
|
||||
class_attribute :duplicate_window
|
||||
|
||||
APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4
|
||||
|
||||
RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT = 0, 2, 3
|
||||
AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38
|
||||
|
||||
self.default_currency = 'USD'
|
||||
|
||||
self.supported_countries = ['US', 'CA', 'GB']
|
||||
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
|
||||
self.homepage_url = 'http://www.authorize.net/'
|
||||
self.display_name = 'Authorize.Net'
|
||||
|
||||
CARD_CODE_ERRORS = %w( N S )
|
||||
AVS_ERRORS = %w( A E N R W Z )
|
||||
AVS_REASON_CODES = %w(27 45)
|
||||
|
||||
AUTHORIZE_NET_ARB_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd'
|
||||
|
||||
RECURRING_ACTIONS = {
|
||||
:create => 'ARBCreateSubscription',
|
||||
:update => 'ARBUpdateSubscription',
|
||||
:cancel => 'ARBCancelSubscription',
|
||||
:status => 'ARBGetSubscriptionStatus'
|
||||
}
|
||||
|
||||
# Creates a new AuthorizeNetGateway
|
||||
#
|
||||
# The gateway requires that a valid login and password be passed
|
||||
# in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:login</tt> -- The Authorize.Net API Login ID (REQUIRED)
|
||||
# * <tt>:password</tt> -- The Authorize.Net Transaction Key. (REQUIRED)
|
||||
# * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
|
||||
# Otherwise, perform transactions against the production server.
|
||||
def initialize(options = {})
|
||||
requires!(options, :login, :password)
|
||||
super
|
||||
end
|
||||
|
||||
# Performs an authorization, which reserves the funds on the customer's credit card, but does not
|
||||
# charge the card.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be authorized as an Integer value in cents.
|
||||
# * <tt>paysource</tt> -- The CreditCard or Check details for the transaction.
|
||||
# * <tt>options</tt> -- A hash of optional parameters.
|
||||
def authorize(money, paysource, options = {})
|
||||
post = {}
|
||||
add_currency_code(post, money, options)
|
||||
add_invoice(post, options)
|
||||
add_payment_source(post, paysource, options)
|
||||
add_address(post, options)
|
||||
add_customer_data(post, options)
|
||||
add_duplicate_window(post)
|
||||
|
||||
commit('AUTH_ONLY', money, post)
|
||||
end
|
||||
|
||||
# Perform a purchase, which is essentially an authorization and capture in a single operation.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be purchased as an Integer value in cents.
|
||||
# * <tt>paysource</tt> -- The CreditCard or Check details for the transaction.
|
||||
# * <tt>options</tt> -- A hash of optional parameters.
|
||||
def purchase(money, paysource, options = {})
|
||||
post = {}
|
||||
add_currency_code(post, money, options)
|
||||
add_invoice(post, options)
|
||||
add_payment_source(post, paysource, options)
|
||||
add_address(post, options)
|
||||
add_customer_data(post, options)
|
||||
add_duplicate_window(post)
|
||||
|
||||
commit('AUTH_CAPTURE', money, post)
|
||||
end
|
||||
|
||||
# Captures the funds from an authorized transaction.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be captured as an Integer value in cents.
|
||||
# * <tt>authorization</tt> -- The authorization returned from the previous authorize request.
|
||||
def capture(money, authorization, options = {})
|
||||
post = {:trans_id => authorization}
|
||||
add_customer_data(post, options)
|
||||
add_invoice(post, options)
|
||||
commit('PRIOR_AUTH_CAPTURE', money, post)
|
||||
end
|
||||
|
||||
# Void a previous transaction
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>authorization</tt> - The authorization returned from the previous authorize request.
|
||||
def void(authorization, options = {})
|
||||
post = {:trans_id => authorization}
|
||||
add_duplicate_window(post)
|
||||
commit('VOID', nil, post)
|
||||
end
|
||||
|
||||
# Refund a transaction.
|
||||
#
|
||||
# This transaction indicates to the gateway that
|
||||
# money should flow from the merchant to the customer.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be credited to the customer as an Integer value in cents.
|
||||
# * <tt>identification</tt> -- The ID of the original transaction against which the refund is being issued.
|
||||
# * <tt>options</tt> -- A hash of parameters.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:card_number</tt> -- The credit card number the refund is being issued to. (REQUIRED)
|
||||
# You can either pass the last four digits of the card number or the full card number.
|
||||
# * <tt>:first_name</tt> -- The first name of the account being refunded.
|
||||
# * <tt>:last_name</tt> -- The last name of the account being refunded.
|
||||
# * <tt>:zip</tt> -- The postal code of the account being refunded.
|
||||
def refund(money, identification, options = {})
|
||||
requires!(options, :card_number)
|
||||
|
||||
post = { :trans_id => identification,
|
||||
:card_num => options[:card_number]
|
||||
}
|
||||
|
||||
post[:first_name] = options[:first_name] if options[:first_name]
|
||||
post[:last_name] = options[:last_name] if options[:last_name]
|
||||
post[:zip] = options[:zip] if options[:zip]
|
||||
|
||||
add_invoice(post, options)
|
||||
add_duplicate_window(post)
|
||||
|
||||
commit('CREDIT', money, post)
|
||||
end
|
||||
|
||||
def credit(money, identification, options = {})
|
||||
deprecated CREDIT_DEPRECATION_MESSAGE
|
||||
refund(money, identification, options)
|
||||
end
|
||||
|
||||
# Create a recurring payment.
|
||||
#
|
||||
# This transaction creates a new Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be charged to the customer at each interval as an Integer value in cents.
|
||||
# * <tt>creditcard</tt> -- The CreditCard details for the transaction.
|
||||
# * <tt>options</tt> -- A hash of parameters.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:interval</tt> -- A hash containing information about the interval of time between payments. Must
|
||||
# contain the keys <tt>:length</tt> and <tt>:unit</tt>. <tt>:unit</tt> can be either <tt>:months</tt> or <tt>:days</tt>.
|
||||
# If <tt>:unit</tt> is <tt>:months</tt> then <tt>:length</tt> must be an integer between 1 and 12 inclusive.
|
||||
# If <tt>:unit</tt> is <tt>:days</tt> then <tt>:length</tt> must be an integer between 7 and 365 inclusive.
|
||||
# For example, to charge the customer once every three months the hash would be
|
||||
# +:interval => { :unit => :months, :length => 3 }+ (REQUIRED)
|
||||
# * <tt>:duration</tt> -- A hash containing keys for the <tt>:start_date</tt> the subscription begins (also the date the
|
||||
# initial billing occurs) and the total number of billing <tt>:occurences</tt> or payments for the subscription. (REQUIRED)
|
||||
def recurring(money, creditcard, options={})
|
||||
requires!(options, :interval, :duration, :billing_address)
|
||||
requires!(options[:interval], :length, [:unit, :days, :months])
|
||||
requires!(options[:duration], :start_date, :occurrences)
|
||||
requires!(options[:billing_address], :first_name, :last_name)
|
||||
|
||||
options[:credit_card] = creditcard
|
||||
options[:amount] = money
|
||||
|
||||
request = build_recurring_request(:create, options)
|
||||
recurring_commit(:create, request)
|
||||
end
|
||||
|
||||
# Update a recurring payment's details.
|
||||
#
|
||||
# This transaction updates an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled
|
||||
# and the subscription must have already been created previously by calling +recurring()+. The ability to change certain
|
||||
# details about a recurring payment is dependent on transaction history and cannot be determined until after calling
|
||||
# +update_recurring()+. See the ARB XML Guide for such conditions.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>options</tt> -- A hash of parameters.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:subscription_id</tt> -- A string containing the <tt>:subscription_id</tt> of the recurring payment already in place
|
||||
# for a given credit card. (REQUIRED)
|
||||
def update_recurring(options={})
|
||||
requires!(options, :subscription_id)
|
||||
request = build_recurring_request(:update, options)
|
||||
recurring_commit(:update, request)
|
||||
end
|
||||
|
||||
# Cancel a recurring payment.
|
||||
#
|
||||
# This transaction cancels an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled
|
||||
# and the subscription must have already been created previously by calling recurring()
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>subscription_id</tt> -- A string containing the +subscription_id+ of the recurring payment already in place
|
||||
# for a given credit card. (REQUIRED)
|
||||
def cancel_recurring(subscription_id)
|
||||
request = build_recurring_request(:cancel, :subscription_id => subscription_id)
|
||||
recurring_commit(:cancel, request)
|
||||
end
|
||||
|
||||
# Get Subscription Status of a recurring payment.
|
||||
#
|
||||
# This transaction gets the status of an existing Automated Recurring Billing (ARB) subscription. Your account must have ARB enabled.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>subscription_id</tt> -- A string containing the +subscription_id+ of the recurring payment already in place
|
||||
# for a given credit card. (REQUIRED)
|
||||
def status_recurring(subscription_id)
|
||||
request = build_recurring_request(:status, :subscription_id => subscription_id)
|
||||
recurring_commit(:status, request)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def commit(action, money, parameters)
|
||||
parameters[:amount] = amount(money) unless action == 'VOID'
|
||||
|
||||
# Only activate the test_request when the :test option is passed in
|
||||
parameters[:test_request] = @options[:test] ? 'TRUE' : 'FALSE'
|
||||
|
||||
url = test? ? self.test_url : self.live_url
|
||||
data = ssl_post url, post_data(action, parameters)
|
||||
|
||||
response = parse(data)
|
||||
response[:action] = action
|
||||
|
||||
message = message_from(response)
|
||||
|
||||
# Return the response. The authorization can be taken out of the transaction_id
|
||||
# Test Mode on/off is something we have to parse from the response text.
|
||||
# It usually looks something like this
|
||||
#
|
||||
# (TESTMODE) Successful Sale
|
||||
test_mode = test? || message =~ /TESTMODE/
|
||||
|
||||
Response.new(success?(response), message, response,
|
||||
:test => test_mode,
|
||||
:authorization => response[:transaction_id],
|
||||
:fraud_review => fraud_review?(response),
|
||||
:avs_result => { :code => response[:avs_result_code] },
|
||||
:cvv_result => response[:card_code]
|
||||
)
|
||||
end
|
||||
|
||||
def success?(response)
|
||||
response[:response_code] == APPROVED
|
||||
end
|
||||
|
||||
def fraud_review?(response)
|
||||
response[:response_code] == FRAUD_REVIEW
|
||||
end
|
||||
|
||||
def parse(body)
|
||||
fields = split(body)
|
||||
|
||||
results = {
|
||||
:response_code => fields[RESPONSE_CODE].to_i,
|
||||
:response_reason_code => fields[RESPONSE_REASON_CODE],
|
||||
:response_reason_text => fields[RESPONSE_REASON_TEXT],
|
||||
:avs_result_code => fields[AVS_RESULT_CODE],
|
||||
:transaction_id => fields[TRANSACTION_ID],
|
||||
:card_code => fields[CARD_CODE_RESPONSE_CODE]
|
||||
}
|
||||
results
|
||||
end
|
||||
|
||||
def post_data(action, parameters = {})
|
||||
post = {}
|
||||
|
||||
post[:version] = API_VERSION
|
||||
post[:login] = @options[:login]
|
||||
post[:tran_key] = @options[:password]
|
||||
post[:relay_response] = "FALSE"
|
||||
post[:type] = action
|
||||
post[:delim_data] = "TRUE"
|
||||
post[:delim_char] = ","
|
||||
post[:encap_char] = "$"
|
||||
post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant"
|
||||
|
||||
request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
||||
request
|
||||
end
|
||||
|
||||
def add_currency_code(post, money, options)
|
||||
post[:currency_code] = options[:currency] || currency(money)
|
||||
end
|
||||
|
||||
def add_invoice(post, options)
|
||||
post[:invoice_num] = options[:order_id]
|
||||
post[:description] = options[:description]
|
||||
end
|
||||
|
||||
def add_creditcard(post, creditcard)
|
||||
post[:card_num] = creditcard.number
|
||||
post[:card_code] = creditcard.verification_value if creditcard.verification_value?
|
||||
post[:exp_date] = expdate(creditcard)
|
||||
post[:first_name] = creditcard.first_name
|
||||
post[:last_name] = creditcard.last_name
|
||||
end
|
||||
|
||||
def add_payment_source(params, source, options={})
|
||||
if card_brand(source) == "check"
|
||||
add_check(params, source, options)
|
||||
else
|
||||
add_creditcard(params, source)
|
||||
end
|
||||
end
|
||||
|
||||
def add_check(post, check, options)
|
||||
post[:method] = "ECHECK"
|
||||
post[:bank_name] = check.bank_name
|
||||
post[:bank_aba_code] = check.routing_number
|
||||
post[:bank_acct_num] = check.account_number
|
||||
post[:bank_acct_type] = check.account_type
|
||||
post[:echeck_type] = "WEB"
|
||||
post[:bank_acct_name] = check.name
|
||||
post[:bank_check_number] = check.number if check.number.present?
|
||||
post[:recurring_billing] = (options[:recurring] ? "TRUE" : "FALSE")
|
||||
end
|
||||
|
||||
def add_customer_data(post, options)
|
||||
if options.has_key? :email
|
||||
post[:email] = options[:email]
|
||||
post[:email_customer] = false
|
||||
end
|
||||
|
||||
if options.has_key? :customer
|
||||
post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil
|
||||
end
|
||||
|
||||
if options.has_key? :ip
|
||||
post[:customer_ip] = options[:ip]
|
||||
end
|
||||
end
|
||||
|
||||
# x_duplicate_window won't be sent by default, because sending it changes the response.
|
||||
# "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent."
|
||||
# (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf
|
||||
def add_duplicate_window(post)
|
||||
unless duplicate_window.nil?
|
||||
post[:duplicate_window] = duplicate_window
|
||||
end
|
||||
end
|
||||
|
||||
def add_address(post, options)
|
||||
if address = options[:billing_address] || options[:address]
|
||||
post[:address] = address[:address1].to_s
|
||||
post[:company] = address[:company].to_s
|
||||
post[:phone] = address[:phone].to_s
|
||||
post[:zip] = address[:zip].to_s
|
||||
post[:city] = address[:city].to_s
|
||||
post[:country] = address[:country].to_s
|
||||
post[:state] = address[:state].blank? ? 'n/a' : address[:state]
|
||||
end
|
||||
|
||||
if address = options[:shipping_address]
|
||||
post[:ship_to_first_name] = address[:first_name].to_s
|
||||
post[:ship_to_last_name] = address[:last_name].to_s
|
||||
post[:ship_to_address] = address[:address1].to_s
|
||||
post[:ship_to_company] = address[:company].to_s
|
||||
post[:ship_to_phone] = address[:phone].to_s
|
||||
post[:ship_to_zip] = address[:zip].to_s
|
||||
post[:ship_to_city] = address[:city].to_s
|
||||
post[:ship_to_country] = address[:country].to_s
|
||||
post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state]
|
||||
end
|
||||
end
|
||||
|
||||
# Make a ruby type out of the response string
|
||||
def normalize(field)
|
||||
case field
|
||||
when "true" then true
|
||||
when "false" then false
|
||||
when "" then nil
|
||||
when "null" then nil
|
||||
else field
|
||||
end
|
||||
end
|
||||
|
||||
def message_from(results)
|
||||
if results[:response_code] == DECLINED
|
||||
return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code])
|
||||
if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code])
|
||||
return AVSResult.messages[ results[:avs_result_code] ]
|
||||
end
|
||||
end
|
||||
|
||||
(results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '')
|
||||
end
|
||||
|
||||
def expdate(creditcard)
|
||||
year = sprintf("%.4i", creditcard.year)
|
||||
month = sprintf("%.2i", creditcard.month)
|
||||
|
||||
"#{month}#{year[-2..-1]}"
|
||||
end
|
||||
|
||||
def split(response)
|
||||
response[1..-2].split(/\$,\$/)
|
||||
end
|
||||
|
||||
# ARB
|
||||
|
||||
# Builds recurring billing request
|
||||
def build_recurring_request(action, options = {})
|
||||
unless RECURRING_ACTIONS.include?(action)
|
||||
raise StandardError, "Invalid Automated Recurring Billing Action: #{action}"
|
||||
end
|
||||
|
||||
xml = Builder::XmlMarkup.new(:indent => 2)
|
||||
xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
|
||||
xml.tag!("#{RECURRING_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_ARB_NAMESPACE) do
|
||||
add_arb_merchant_authentication(xml)
|
||||
# Merchant-assigned reference ID for the request
|
||||
xml.tag!('refId', options[:ref_id]) if options[:ref_id]
|
||||
send("build_arb_#{action}_subscription_request", xml, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Contains the merchant’s payment gateway account authentication information
|
||||
def add_arb_merchant_authentication(xml)
|
||||
xml.tag!('merchantAuthentication') do
|
||||
xml.tag!('name', @options[:login])
|
||||
xml.tag!('transactionKey', @options[:password])
|
||||
end
|
||||
end
|
||||
|
||||
# Builds body for ARBCreateSubscriptionRequest
|
||||
def build_arb_create_subscription_request(xml, options)
|
||||
# Subscription
|
||||
add_arb_subscription(xml, options)
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
# Builds body for ARBUpdateSubscriptionRequest
|
||||
def build_arb_update_subscription_request(xml, options)
|
||||
xml.tag!('subscriptionId', options[:subscription_id])
|
||||
# Adds Subscription
|
||||
add_arb_subscription(xml, options)
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
# Builds body for ARBCancelSubscriptionRequest
|
||||
def build_arb_cancel_subscription_request(xml, options)
|
||||
xml.tag!('subscriptionId', options[:subscription_id])
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
# Builds body for ARBGetSubscriptionStatusRequest
|
||||
def build_arb_status_subscription_request(xml, options)
|
||||
xml.tag!('subscriptionId', options[:subscription_id])
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
# Adds subscription information
|
||||
def add_arb_subscription(xml, options)
|
||||
xml.tag!('subscription') do
|
||||
# Merchant-assigned name for the subscription (optional)
|
||||
xml.tag!('name', options[:subscription_name]) if options[:subscription_name]
|
||||
# Contains information about the payment schedule
|
||||
add_arb_payment_schedule(xml, options)
|
||||
# The amount to be billed to the customer
|
||||
# for each payment in the subscription
|
||||
xml.tag!('amount', amount(options[:amount])) if options[:amount]
|
||||
if trial = options[:trial]
|
||||
# The amount to be charged for each payment during a trial period (conditional)
|
||||
xml.tag!('trialAmount', amount(trial[:amount])) if trial[:amount]
|
||||
end
|
||||
# Contains either the customer’s credit card
|
||||
# or bank account payment information
|
||||
add_arb_payment(xml, options)
|
||||
# Contains order information (optional)
|
||||
add_arb_order(xml, options)
|
||||
# Contains information about the customer
|
||||
add_arb_customer(xml, options)
|
||||
# Contains the customer's billing address information
|
||||
add_arb_address(xml, 'billTo', options[:billing_address])
|
||||
# Contains the customer's shipping address information (optional)
|
||||
add_arb_address(xml, 'shipTo', options[:shipping_address])
|
||||
end
|
||||
end
|
||||
|
||||
# Adds information about the interval of time between payments
|
||||
def add_arb_interval(xml, options)
|
||||
interval = options[:interval]
|
||||
return unless interval
|
||||
xml.tag!('interval') do
|
||||
# The measurement of time, in association with the Interval Unit,
|
||||
# that is used to define the frequency of the billing occurrences
|
||||
xml.tag!('length', interval[:length])
|
||||
# The unit of time, in association with the Interval Length,
|
||||
# between each billing occurrence
|
||||
xml.tag!('unit', interval[:unit].to_s)
|
||||
end
|
||||
end
|
||||
|
||||
# Adds information about the subscription duration
|
||||
def add_arb_duration(xml, options)
|
||||
duration = options[:duration]
|
||||
return unless duration
|
||||
# The date the subscription begins
|
||||
# (also the date the initial billing occurs)
|
||||
xml.tag!('startDate', duration[:start_date]) if duration[:start_date]
|
||||
# Number of billing occurrences or payments for the subscription
|
||||
xml.tag!('totalOccurrences', duration[:occurrences]) if duration[:occurrences]
|
||||
end
|
||||
|
||||
def add_arb_payment_schedule(xml, options)
|
||||
return unless options[:interval] || options[:duration]
|
||||
xml.tag!('paymentSchedule') do
|
||||
# Contains information about the interval of time between payments
|
||||
add_arb_interval(xml, options)
|
||||
add_arb_duration(xml, options)
|
||||
if trial = options[:trial]
|
||||
# Number of billing occurrences or payments in the trial period (optional)
|
||||
xml.tag!('trialOccurrences', trial[:occurrences]) if trial[:occurrences]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Adds customer's credit card or bank account payment information
|
||||
def add_arb_payment(xml, options)
|
||||
return unless options[:credit_card] || options[:bank_account]
|
||||
xml.tag!('payment') do
|
||||
# Contains the customer’s credit card information
|
||||
add_arb_credit_card(xml, options)
|
||||
# Contains the customer’s bank account information
|
||||
add_arb_bank_account(xml, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Adds customer’s credit card information
|
||||
# Note: This element should only be included
|
||||
# when the payment method is credit card.
|
||||
def add_arb_credit_card(xml, options)
|
||||
credit_card = options[:credit_card]
|
||||
return unless credit_card
|
||||
xml.tag!('creditCard') do
|
||||
# The credit card number used for payment of the subscription
|
||||
xml.tag!('cardNumber', credit_card.number)
|
||||
# The expiration date of the credit card used for the subscription
|
||||
xml.tag!('expirationDate', arb_expdate(credit_card))
|
||||
end
|
||||
end
|
||||
|
||||
# Adds customer’s bank account information
|
||||
# Note: This element should only be included
|
||||
# when the payment method is bank account.
|
||||
def add_arb_bank_account(xml, options)
|
||||
bank_account = options[:bank_account]
|
||||
return unless bank_account
|
||||
xml.tag!('bankAccount') do
|
||||
# The type of bank account used for payment of the subscription
|
||||
xml.tag!('accountType', bank_account[:account_type])
|
||||
# The routing number of the customer’s bank
|
||||
xml.tag!('routingNumber', bank_account[:routing_number])
|
||||
# The bank account number used for payment of the subscription
|
||||
xml.tag!('accountNumber', bank_account[:account_number])
|
||||
# The full name of the individual associated
|
||||
# with the bank account number
|
||||
xml.tag!('nameOfAccount', bank_account[:name_of_account])
|
||||
# The full name of the individual associated
|
||||
# with the bank account number (optional)
|
||||
xml.tag!('bankName', bank_account[:bank_name]) if bank_account[:bank_name]
|
||||
# The type of electronic check transaction used for the subscription
|
||||
xml.tag!('echeckType', bank_account[:echeck_type])
|
||||
end
|
||||
end
|
||||
|
||||
# Adds order information (optional)
|
||||
def add_arb_order(xml, options)
|
||||
order = options[:order]
|
||||
return unless order
|
||||
xml.tag!('order') do
|
||||
# Merchant-assigned invoice number for the subscription (optional)
|
||||
xml.tag!('invoiceNumber', order[:invoice_number])
|
||||
# Description of the subscription (optional)
|
||||
xml.tag!('description', order[:description])
|
||||
end
|
||||
end
|
||||
|
||||
# Adds information about the customer
|
||||
def add_arb_customer(xml, options)
|
||||
customer = options[:customer]
|
||||
return unless customer
|
||||
xml.tag!('customer') do
|
||||
xml.tag!('type', customer[:type]) if customer[:type]
|
||||
xml.tag!('id', customer[:id]) if customer[:id]
|
||||
xml.tag!('email', customer[:email]) if customer[:email]
|
||||
xml.tag!('phoneNumber', customer[:phone_number]) if customer[:phone_number]
|
||||
xml.tag!('faxNumber', customer[:fax_number]) if customer[:fax_number]
|
||||
add_arb_drivers_license(xml, options)
|
||||
xml.tag!('taxId', customer[:tax_id]) if customer[:tax_id]
|
||||
end
|
||||
end
|
||||
|
||||
# Adds the customer's driver's license information (conditional)
|
||||
def add_arb_drivers_license(xml, options)
|
||||
return unless customer = options[:customer]
|
||||
return unless drivers_license = customer[:drivers_license]
|
||||
xml.tag!('driversLicense') do
|
||||
# The customer's driver's license number
|
||||
xml.tag!('number', drivers_license[:number])
|
||||
# The customer's driver's license state
|
||||
xml.tag!('state', drivers_license[:state])
|
||||
# The customer's driver's license date of birth
|
||||
xml.tag!('dateOfBirth', drivers_license[:date_of_birth])
|
||||
end
|
||||
end
|
||||
|
||||
# Adds address information
|
||||
def add_arb_address(xml, container_name, address)
|
||||
return if address.blank?
|
||||
xml.tag!(container_name) do
|
||||
xml.tag!('firstName', address[:first_name])
|
||||
xml.tag!('lastName', address[:last_name])
|
||||
xml.tag!('company', address[:company])
|
||||
xml.tag!('address', address[:address1])
|
||||
xml.tag!('city', address[:city])
|
||||
xml.tag!('state', address[:state])
|
||||
xml.tag!('zip', address[:zip])
|
||||
xml.tag!('country', address[:country])
|
||||
end
|
||||
end
|
||||
|
||||
def arb_expdate(credit_card)
|
||||
sprintf('%04d-%02d', credit_card.year, credit_card.month)
|
||||
end
|
||||
|
||||
def recurring_commit(action, request)
|
||||
url = test? ? arb_test_url : arb_live_url
|
||||
xml = ssl_post(url, request, "Content-Type" => "text/xml")
|
||||
|
||||
response = recurring_parse(action, xml)
|
||||
|
||||
message = response[:message] || response[:text]
|
||||
test_mode = test? || message =~ /Test Mode/
|
||||
success = response[:result_code] == 'Ok'
|
||||
|
||||
Response.new(success, message, response,
|
||||
:test => test_mode,
|
||||
:authorization => response[:subscription_id]
|
||||
)
|
||||
end
|
||||
|
||||
def recurring_parse(action, xml)
|
||||
response = {}
|
||||
xml = REXML::Document.new(xml)
|
||||
root = REXML::XPath.first(xml, "//#{RECURRING_ACTIONS[action]}Response") ||
|
||||
REXML::XPath.first(xml, "//ErrorResponse")
|
||||
if root
|
||||
root.elements.to_a.each do |node|
|
||||
recurring_parse_element(response, node)
|
||||
end
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
def recurring_parse_element(response, node)
|
||||
if node.has_elements?
|
||||
node.elements.each{|e| recurring_parse_element(response, e) }
|
||||
else
|
||||
response[node.name.underscore.to_sym] = node.text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,956 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
# ==== Customer Information Manager (CIM)
|
||||
#
|
||||
# The Authorize.Net Customer Information Manager (CIM) is an optional additional service that allows you to store sensitive payment information on
|
||||
# Authorize.Net's servers, simplifying payments for returning customers and recurring transactions. It can also help with Payment Card Industry (PCI)
|
||||
# Data Security Standard compliance, since customer data is no longer stored locally.
|
||||
#
|
||||
# To use the AuthorizeNetCimGateway CIM must be enabled for your account.
|
||||
#
|
||||
# Information about CIM is available on the {Authorize.Net website}[http://www.authorize.net/solutions/merchantsolutions/merchantservices/cim/].
|
||||
# Information about the CIM API is available at the {Authorize.Net Integration Center}[http://developer.authorize.net/]
|
||||
#
|
||||
# ==== Login and Password
|
||||
#
|
||||
# The login and password are not the username and password you use to
|
||||
# login to the Authorize.Net Merchant Interface. Instead, you will
|
||||
# use the API Login ID as the login and Transaction Key as the
|
||||
# password.
|
||||
#
|
||||
# ==== How to Get Your API Login ID and Transaction Key
|
||||
#
|
||||
# 1. Log into the Merchant Interface
|
||||
# 2. Select Settings from the Main Menu
|
||||
# 3. Click on API Login ID and Transaction Key in the Security section
|
||||
# 4. Type in the answer to the secret question configured on setup
|
||||
# 5. Click Submit
|
||||
class AuthorizeNetCimGateway < Gateway
|
||||
self.test_url = 'https://apitest.authorize.net/xml/v1/request.api'
|
||||
self.live_url = 'https://api.authorize.net/xml/v1/request.api'
|
||||
|
||||
AUTHORIZE_NET_CIM_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd'
|
||||
|
||||
CIM_ACTIONS = {
|
||||
:create_customer_profile => 'createCustomerProfile',
|
||||
:create_customer_payment_profile => 'createCustomerPaymentProfile',
|
||||
:create_customer_shipping_address => 'createCustomerShippingAddress',
|
||||
:get_customer_profile => 'getCustomerProfile',
|
||||
:get_customer_profile_ids => 'getCustomerProfileIds',
|
||||
:get_customer_payment_profile => 'getCustomerPaymentProfile',
|
||||
:get_customer_shipping_address => 'getCustomerShippingAddress',
|
||||
:delete_customer_profile => 'deleteCustomerProfile',
|
||||
:delete_customer_payment_profile => 'deleteCustomerPaymentProfile',
|
||||
:delete_customer_shipping_address => 'deleteCustomerShippingAddress',
|
||||
:update_customer_profile => 'updateCustomerProfile',
|
||||
:update_customer_payment_profile => 'updateCustomerPaymentProfile',
|
||||
:update_customer_shipping_address => 'updateCustomerShippingAddress',
|
||||
:create_customer_profile_transaction => 'createCustomerProfileTransaction',
|
||||
:validate_customer_payment_profile => 'validateCustomerPaymentProfile'
|
||||
}
|
||||
|
||||
CIM_TRANSACTION_TYPES = {
|
||||
:auth_capture => 'profileTransAuthCapture',
|
||||
:auth_only => 'profileTransAuthOnly',
|
||||
:capture_only => 'profileTransCaptureOnly',
|
||||
:prior_auth_capture => 'profileTransPriorAuthCapture',
|
||||
:refund => 'profileTransRefund',
|
||||
:void => 'profileTransVoid'
|
||||
}
|
||||
|
||||
CIM_VALIDATION_MODES = {
|
||||
:none => 'none',
|
||||
:test => 'testMode',
|
||||
:live => 'liveMode',
|
||||
:old => 'oldLiveMode'
|
||||
}
|
||||
|
||||
BANK_ACCOUNT_TYPES = {
|
||||
:checking => 'checking',
|
||||
:savings => 'savings',
|
||||
:business_checking => 'businessChecking'
|
||||
}
|
||||
|
||||
ECHECK_TYPES = {
|
||||
:ccd => 'CCD',
|
||||
:ppd => 'PPD',
|
||||
:web => 'WEB'
|
||||
}
|
||||
|
||||
self.homepage_url = 'http://www.authorize.net/'
|
||||
self.display_name = 'Authorize.Net CIM'
|
||||
self.supported_countries = ['US']
|
||||
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
|
||||
|
||||
# Creates a new AuthorizeNetCimGateway
|
||||
#
|
||||
# The gateway requires that a valid API Login ID and Transaction Key be passed
|
||||
# in the +options+ hash.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:login</tt> -- The Authorize.Net API Login ID (REQUIRED)
|
||||
# * <tt>:password</tt> -- The Authorize.Net Transaction Key. (REQUIRED)
|
||||
# * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
|
||||
# Otherwise, perform transactions against the production server.
|
||||
# * <tt>:delimiter</tt> -- The delimiter used in the direct response. Default is ',' (comma).
|
||||
def initialize(options = {})
|
||||
requires!(options, :login, :password)
|
||||
super
|
||||
end
|
||||
|
||||
# Creates a new customer profile along with any customer payment profiles and customer shipping addresses
|
||||
# for the customer profile.
|
||||
#
|
||||
# Returns a Response with the Customer Profile ID of the new customer profile in the authorization field.
|
||||
# It is *CRITICAL* that you save this ID. There is no way to retrieve this through the API. You will not
|
||||
# be able to create another Customer Profile with the same information.
|
||||
#
|
||||
#
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:profile</tt> -- A hash containing at least one of the CONDITIONAL profile options below (REQUIRED)
|
||||
#
|
||||
# ==== Profile
|
||||
#
|
||||
# * <tt>:email</tt> -- Email address associated with the customer profile (CONDITIONAL)
|
||||
# * <tt>:description</tt> -- Description of the customer or customer profile (CONDITIONAL)
|
||||
# * <tt>:merchant_customer_id</tt> -- Merchant assigned ID for the customer (CONDITIONAL)
|
||||
# * <tt>:payment_profile</tt> -- A hash containing the elements of the new payment profile (optional)
|
||||
#
|
||||
# ==== Payment Profile
|
||||
#
|
||||
# * <tt>:payment</tt> -- A hash containing information on payment. Either :credit_card or :bank_account (optional)
|
||||
def create_customer_profile(options)
|
||||
requires!(options, :profile)
|
||||
requires!(options[:profile], :email) unless options[:profile][:merchant_customer_id] || options[:profile][:description]
|
||||
requires!(options[:profile], :description) unless options[:profile][:email] || options[:profile][:merchant_customer_id]
|
||||
requires!(options[:profile], :merchant_customer_id) unless options[:profile][:description] || options[:profile][:email]
|
||||
|
||||
request = build_request(:create_customer_profile, options)
|
||||
commit(:create_customer_profile, request)
|
||||
end
|
||||
|
||||
# Creates a new customer payment profile for an existing customer profile.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer the payment profile will be added to. (REQUIRED)
|
||||
# * <tt>:payment_profile</tt> -- A hash containing the elements of the new payment profile (REQUIRED)
|
||||
#
|
||||
# ==== Payment Profile
|
||||
#
|
||||
# * <tt>:payment</tt> -- A hash containing information on payment. Either :credit_card or :bank_account (REQUIRED)
|
||||
def create_customer_payment_profile(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
requires!(options, :payment_profile)
|
||||
requires!(options[:payment_profile], :payment)
|
||||
|
||||
request = build_request(:create_customer_payment_profile, options)
|
||||
commit(:create_customer_payment_profile, request)
|
||||
end
|
||||
|
||||
# Creates a new customer shipping address for an existing customer profile.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer the payment profile will be added to. (REQUIRED)
|
||||
# * <tt>:address</tt> -- A hash containing the elements of the shipping address (REQUIRED)
|
||||
def create_customer_shipping_address(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
requires!(options, :address)
|
||||
|
||||
request = build_request(:create_customer_shipping_address, options)
|
||||
commit(:create_customer_shipping_address, request)
|
||||
end
|
||||
|
||||
# Deletes an existing customer profile along with all associated customer payment profiles and customer shipping addresses.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to be deleted. (REQUIRED)
|
||||
def delete_customer_profile(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
|
||||
request = build_request(:delete_customer_profile, options)
|
||||
commit(:delete_customer_profile, request)
|
||||
end
|
||||
|
||||
# Deletes a customer payment profile from an existing customer profile.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer with the payment profile to be deleted. (REQUIRED)
|
||||
# * <tt>:customer_payment_profile_id</tt> -- The Payment Profile ID of the payment profile to be deleted. (REQUIRED)
|
||||
def delete_customer_payment_profile(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
requires!(options, :customer_payment_profile_id)
|
||||
|
||||
request = build_request(:delete_customer_payment_profile, options)
|
||||
commit(:delete_customer_payment_profile, request)
|
||||
end
|
||||
|
||||
# Deletes a customer shipping address from an existing customer profile.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer with the payment profile to be deleted. (REQUIRED)
|
||||
# * <tt>:customer_address_id</tt> -- The Shipping Address ID of the shipping address to be deleted. (REQUIRED)
|
||||
def delete_customer_shipping_address(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
requires!(options, :customer_address_id)
|
||||
|
||||
request = build_request(:delete_customer_shipping_address, options)
|
||||
commit(:delete_customer_shipping_address, request)
|
||||
end
|
||||
|
||||
# Retrieves an existing customer profile along with all the associated customer payment profiles and customer shipping addresses.
|
||||
#
|
||||
# Returns a Response whose params hash contains all the profile information.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to retrieve. (REQUIRED)
|
||||
def get_customer_profile(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
|
||||
request = build_request(:get_customer_profile, options)
|
||||
commit(:get_customer_profile, request)
|
||||
end
|
||||
|
||||
def get_customer_profile_ids(options = {})
|
||||
request = build_request(:get_customer_profile_ids, options)
|
||||
commit(:get_customer_profile_ids, request)
|
||||
end
|
||||
|
||||
# Retrieve a customer payment profile for an existing customer profile.
|
||||
#
|
||||
# Returns a Response whose params hash contains all the payment profile information. Sensitive information such as credit card
|
||||
# numbers will be masked.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer with the payment profile to be retrieved. (REQUIRED)
|
||||
# * <tt>:customer_payment_profile_id</tt> -- The Payment Profile ID of the payment profile to be retrieved. (REQUIRED)
|
||||
def get_customer_payment_profile(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
requires!(options, :customer_payment_profile_id)
|
||||
|
||||
request = build_request(:get_customer_payment_profile, options)
|
||||
commit(:get_customer_payment_profile, request)
|
||||
end
|
||||
|
||||
# Retrieve a customer shipping address for an existing customer profile.
|
||||
#
|
||||
# Returns a Response whose params hash contains all the shipping address information.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer with the payment profile to be retrieved. (REQUIRED)
|
||||
# * <tt>:customer_address_id</tt> -- The Shipping Address ID of the shipping address to be retrieved. (REQUIRED)
|
||||
def get_customer_shipping_address(options)
|
||||
requires!(options, :customer_profile_id)
|
||||
requires!(options, :customer_address_id)
|
||||
|
||||
request = build_request(:get_customer_shipping_address, options)
|
||||
commit(:get_customer_shipping_address, request)
|
||||
end
|
||||
|
||||
# Updates an existing customer profile.
|
||||
#
|
||||
# Warning: if you do not provide a parameter in the <tt>:payment_profile</tt> hash, it is automatically set to nil at
|
||||
# Authorize.Net. You will most likely want to first get the profile hash using get_customer_profile and then only change the
|
||||
# elements you wish to change.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:profile</tt> -- A hash containing the values the Customer Profile should be updated to. (REQUIRED)
|
||||
#
|
||||
# ==== Profile
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer profile to update. (REQUIRED)
|
||||
def update_customer_profile(options)
|
||||
requires!(options, :profile)
|
||||
requires!(options[:profile], :customer_profile_id)
|
||||
|
||||
request = build_request(:update_customer_profile, options)
|
||||
commit(:update_customer_profile, request)
|
||||
end
|
||||
|
||||
# Updates a customer payment profile for an existing customer profile.
|
||||
#
|
||||
# Warning: if you do not provide a parameter in the <tt>:payment_profile</tt> hash, it is automatically set to nil at
|
||||
# Authorize.Net. You will most likely want to first get the profile hash using get_customer_payment_profile and then only
|
||||
# change the elements you wish to change.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer with the payment profile to be updated. (REQUIRED)
|
||||
# * <tt>:payment_profile</tt> -- A hash containing the values the Customer Payment Profile should be updated to. (REQUIRED)
|
||||
#
|
||||
# ==== Payment Profile
|
||||
#
|
||||
# * <tt>:customer_payment_profile_id</tt> -- The Customer Payment Profile ID of the Customer Payment Profile to update. (REQUIRED)
|
||||
def update_customer_payment_profile(options)
|
||||
requires!(options, :customer_profile_id, :payment_profile)
|
||||
requires!(options[:payment_profile], :customer_payment_profile_id)
|
||||
|
||||
request = build_request(:update_customer_payment_profile, options)
|
||||
commit(:update_customer_payment_profile, request)
|
||||
end
|
||||
|
||||
# Updates a customer shipping address for an existing customer profile.
|
||||
#
|
||||
# Warning: if you do not provide a parameter in the <tt>:address</tt> hash, it is automatically set to nil at
|
||||
# Authorize.Net. You will most likely want to first get the profile hash using get_customer_shipping_address and then only
|
||||
# change the elements you wish to change.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer with the payment profile to be updated. (REQUIRED)
|
||||
# * <tt>:address</tt> -- A hash containing the values the Customer Shipping Address should be updated to. (REQUIRED)
|
||||
#
|
||||
# ==== Address
|
||||
#
|
||||
# * <tt>:customer_address_id</tt> -- The Customer Address ID of the Customer Payment Profile to update. (REQUIRED)
|
||||
def update_customer_shipping_address(options)
|
||||
requires!(options, :customer_profile_id, :address)
|
||||
requires!(options[:address], :customer_address_id)
|
||||
|
||||
request = build_request(:update_customer_shipping_address, options)
|
||||
commit(:update_customer_shipping_address, request)
|
||||
end
|
||||
|
||||
# Creates a new payment transaction from an existing customer profile
|
||||
#
|
||||
# This is what is used to charge a customer whose information you have stored in a Customer Profile.
|
||||
#
|
||||
# Returns a Response object that contains the result of the transaction in <tt>params['direct_response']</tt>
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:transaction</tt> -- A hash containing information on the transaction that is being requested. (REQUIRED)
|
||||
#
|
||||
# ==== Transaction
|
||||
#
|
||||
# * <tt>:type</tt> -- The type of transaction. Can be either <tt>:auth_only</tt>, <tt>:capture_only</tt>, <tt>:auth_capture</tt>, <tt>:prior_auth_capture</tt>, <tt>:refund</tt> or <tt>:void</tt>. (REQUIRED)
|
||||
# * <tt>:amount</tt> -- The amount for the tranaction. Formatted with a decimal. For example "4.95" (CONDITIONAL)
|
||||
# - :type == :void (NOT USED)
|
||||
# - :type == :refund (OPTIONAL)
|
||||
# - :type == (:auth_only, :capture_only, :auth_capture, :prior_auth_capture) (REQUIRED)
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to use in this transaction. (CONDITIONAL)
|
||||
# - :type == (:void, :prior_auth_capture) (OPTIONAL)
|
||||
# - :type == :refund (CONDITIONAL - required if masked information is not being submitted [see below])
|
||||
# - :type == (:auth_only, :capture_only, :auth_capture) (REQUIRED)
|
||||
#
|
||||
# * <tt>:customer_payment_profile_id</tt> -- The Customer Payment Profile ID of the Customer Payment Profile to use in this transaction. (CONDITIONAL)
|
||||
# - :type == (:void, :prior_auth_capture) (OPTIONAL)
|
||||
# - :type == :refund (CONDITIONAL - required if masked information is not being submitted [see below])
|
||||
# - :type == (:auth_only, :capture_only, :auth_capture) (REQUIRED)
|
||||
#
|
||||
# * <tt>:trans_id</tt> -- The payment gateway assigned transaction ID of the original transaction (CONDITIONAL):
|
||||
# - :type = (:void, :refund, :prior_auth_capture) (REQUIRED)
|
||||
# - :type = (:auth_only, :capture_only, :auth_capture) (NOT USED)
|
||||
#
|
||||
# * <tt>:card_code</tt> -- CVV/CCV code (OPTIONAL)
|
||||
# - :type = (:void, :refund, :prior_auth_capture) (NOT USED)
|
||||
# - :type = (:auth_only, :capture_only, :auth_capture) (OPTIONAL)
|
||||
#
|
||||
# * <tt>:customer_shipping_address_id</tt> -- Payment gateway assigned ID associated with the customer shipping address (CONDITIONAL)
|
||||
# - :type = (:void, :refund) (OPTIONAL)
|
||||
# - :type = (:auth_only, :capture_only, :auth_capture) (NOT USED)
|
||||
# - :type = (:prior_auth_capture) (OPTIONAL)
|
||||
#
|
||||
# ==== For :type == :refund only
|
||||
# * <tt>:credit_card_number_masked</tt> -- (CONDITIONAL - requied for credit card refunds is :customer_profile_id AND :customer_payment_profile_id are missing)
|
||||
# * <tt>:bank_routing_number_masked && :bank_account_number_masked</tt> -- (CONDITIONAL - requied for electronic check refunds is :customer_profile_id AND :customer_payment_profile_id are missing) (NOT ABLE TO TEST - I keep getting "ACH transactions are not accepted by this merchant." when trying to make a payment and, until that's possible I can't refund (wiseleyb@gmail.com))
|
||||
def create_customer_profile_transaction(options)
|
||||
requires!(options, :transaction)
|
||||
requires!(options[:transaction], :type)
|
||||
case options[:transaction][:type]
|
||||
when :void
|
||||
requires!(options[:transaction], :trans_id)
|
||||
when :refund
|
||||
requires!(options[:transaction], :trans_id) &&
|
||||
(
|
||||
(options[:transaction][:customer_profile_id] && options[:transaction][:customer_payment_profile_id]) ||
|
||||
options[:transaction][:credit_card_number_masked] ||
|
||||
(options[:transaction][:bank_routing_number_masked] && options[:transaction][:bank_account_number_masked])
|
||||
)
|
||||
when :prior_auth_capture
|
||||
requires!(options[:transaction], :amount, :trans_id)
|
||||
else
|
||||
requires!(options[:transaction], :amount, :customer_profile_id, :customer_payment_profile_id)
|
||||
end
|
||||
request = build_request(:create_customer_profile_transaction, options)
|
||||
commit(:create_customer_profile_transaction, request)
|
||||
end
|
||||
|
||||
# Creates a new payment transaction for refund from an existing customer profile
|
||||
#
|
||||
# This is what is used to refund a transaction you have stored in a Customer Profile.
|
||||
#
|
||||
# Returns a Response object that contains the result of the transaction in <tt>params['direct_response']</tt>
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:transaction</tt> -- A hash containing information on the transaction that is being requested. (REQUIRED)
|
||||
#
|
||||
# ==== Transaction
|
||||
#
|
||||
# * <tt>:amount</tt> -- The total amount to be refunded (REQUIRED)
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to use in this transaction. (CONDITIONAL :customer_payment_profile_id must be included if used)
|
||||
# * <tt>:customer_payment_profile_id</tt> -- The Customer Payment Profile ID of the Customer Payment Profile to use in this transaction. (CONDITIONAL :customer_profile_id must be included if used)
|
||||
#
|
||||
# * <tt>:credit_card_number_masked</tt> -- Four Xs follwed by the last four digits of the credit card (CONDITIONAL - used if customer_profile_id and customer_payment_profile_id aren't given)
|
||||
#
|
||||
# * <tt>:bank_routing_number_masked</tt> -- The last four gidits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked)
|
||||
# * <tt>:bank_account_number_masked</tt> -- The last four digis of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked)
|
||||
#
|
||||
# * <tt>:tax</tt> - A hash containing tax information for the refund (OPTIONAL - <tt>:amount</tt>, <tt>:name</tt> (31 characters), <tt>:description</tt> (255 characters))
|
||||
# * <tt>:duty</tt> - A hash containting duty information for the refund (OPTIONAL - <tt>:amount</tt>, <tt>:name</tt> (31 characters), <tt>:description</tt> (255 characters))
|
||||
# * <tt>:shipping</tt> - A hash containing shipping information for the refund (OPTIONAL - <tt>:amount</tt>, <tt>:name</tt> (31 characters), <tt>:description</tt> (255 characters))
|
||||
def create_customer_profile_transaction_for_refund(options)
|
||||
requires!(options, :transaction)
|
||||
options[:transaction][:type] = :refund
|
||||
requires!(options[:transaction], :trans_id)
|
||||
requires!(options[:transaction], :amount)
|
||||
request = build_request(:create_customer_profile_transaction, options)
|
||||
commit(:create_customer_profile_transaction, request)
|
||||
end
|
||||
|
||||
# Creates a new payment transaction for void from an existing customer profile
|
||||
#
|
||||
# This is what is used to void a transaction you have stored in a Customer Profile.
|
||||
#
|
||||
# Returns a Response object that contains the result of the transaction in <tt>params['direct_response']</tt>
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:transaction</tt> -- A hash containing information on the transaction that is being requested. (REQUIRED)
|
||||
#
|
||||
# ==== Transaction
|
||||
#
|
||||
# * <tt>:trans_id</tt> -- The payment gateway assigned transaction id of the original transaction. (REQUIRED)
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to use in this transaction.
|
||||
# * <tt>:customer_payment_profile_id</tt> -- The Customer Payment Profile ID of the Customer Payment Profile to use in this transaction.
|
||||
# * <tt>:customer_shipping_address_id</tt> -- Payment gateway assigned ID associated with the customer shipping address.
|
||||
def create_customer_profile_transaction_for_void(options)
|
||||
requires!(options, :transaction)
|
||||
options[:transaction][:type] = :void
|
||||
requires!(options[:transaction], :trans_id)
|
||||
request = build_request(:create_customer_profile_transaction, options)
|
||||
commit(:create_customer_profile_transaction, request)
|
||||
end
|
||||
|
||||
# Verifies an existing customer payment profile by generating a test transaction
|
||||
#
|
||||
# Returns a Response object that contains the result of the transaction in <tt>params['direct_response']</tt>
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to use in this transaction. (REQUIRED)
|
||||
# * <tt>:customer_payment_profile_id</tt> -- The Customer Payment Profile ID of the Customer Payment Profile to be verified. (REQUIRED)
|
||||
# * <tt>:customer_address_id</tt> -- The Customer Address ID of the Customer Shipping Address to be verified. (OPTIONAL)
|
||||
# * <tt>:card_code</tt> -- If the payment profile is a credit card, the CCV/CVV code to validate with (OPTIONAL)
|
||||
# * <tt>:validation_mode</tt> -- <tt>:live</tt> or <tt>:test</tt> In Test Mode, only field validation is performed. (REQUIRED
|
||||
# In Live Mode, a transaction is generated and submitted to the processor with the amount of $0.01. If successful, the transaction is immediately voided. (REQUIRED)
|
||||
def validate_customer_payment_profile(options)
|
||||
requires!(options, :customer_profile_id, :customer_payment_profile_id, :validation_mode)
|
||||
|
||||
request = build_request(:validate_customer_payment_profile, options)
|
||||
commit(:validate_customer_payment_profile, request)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expdate(credit_card)
|
||||
if credit_card.year.present? && credit_card.month.present?
|
||||
sprintf('%04d-%02d', credit_card.year, credit_card.month)
|
||||
else
|
||||
'XXXX'
|
||||
end
|
||||
end
|
||||
|
||||
def build_request(action, options = {})
|
||||
unless CIM_ACTIONS.include?(action)
|
||||
raise StandardError, "Invalid Customer Information Manager Action: #{action}"
|
||||
end
|
||||
|
||||
xml = Builder::XmlMarkup.new(:indent => 2)
|
||||
xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
|
||||
xml.tag!("#{CIM_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_CIM_NAMESPACE) do
|
||||
add_merchant_authentication(xml)
|
||||
# Merchant-assigned reference ID for the request
|
||||
xml.tag!('refId', options[:ref_id]) if options[:ref_id]
|
||||
# Order options
|
||||
add_order(xml, options[:order]) if options[:order]
|
||||
send("build_#{action}_request", xml, options)
|
||||
end
|
||||
end
|
||||
|
||||
# Contains the merchant’s payment gateway account authentication information
|
||||
def add_merchant_authentication(xml)
|
||||
xml.tag!('merchantAuthentication') do
|
||||
xml.tag!('name', @options[:login])
|
||||
xml.tag!('transactionKey', @options[:password])
|
||||
end
|
||||
end
|
||||
|
||||
def build_create_customer_profile_request(xml, options)
|
||||
add_profile(xml, options[:profile])
|
||||
|
||||
xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode]
|
||||
|
||||
if options.has_key?(:payment_profile)
|
||||
xml.tag!('paymentProfile') do
|
||||
add_payment_profile(xml, options[:payment_profile])
|
||||
end
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_create_customer_payment_profile_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
|
||||
xml.tag!('paymentProfile') do
|
||||
add_payment_profile(xml, options[:payment_profile])
|
||||
end
|
||||
|
||||
xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode]
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_create_customer_shipping_address_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
|
||||
xml.tag!('address') do
|
||||
add_address(xml, options[:address])
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_delete_customer_profile_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_delete_customer_payment_profile_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id])
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_delete_customer_shipping_address_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
xml.tag!('customerAddressId', options[:customer_address_id])
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_get_customer_profile_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_get_customer_profile_ids_request(xml, options)
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_get_customer_payment_profile_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id])
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_get_customer_shipping_address_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
xml.tag!('customerAddressId', options[:customer_address_id])
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_update_customer_profile_request(xml, options)
|
||||
add_profile(xml, options[:profile], true)
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_update_customer_payment_profile_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
|
||||
xml.tag!('paymentProfile') do
|
||||
add_payment_profile(xml, options[:payment_profile])
|
||||
end
|
||||
|
||||
xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode]
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_update_customer_shipping_address_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
|
||||
xml.tag!('address') do
|
||||
add_address(xml, options[:address])
|
||||
end
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_create_customer_profile_transaction_request(xml, options)
|
||||
options[:extra_options] ||= {}
|
||||
options[:extra_options].merge!('x_test_request' => 'TRUE') if @options[:test]
|
||||
|
||||
add_transaction(xml, options[:transaction])
|
||||
tag_unless_blank(xml, 'extraOptions', format_extra_options(options[:extra_options]))
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
def build_validate_customer_payment_profile_request(xml, options)
|
||||
xml.tag!('customerProfileId', options[:customer_profile_id])
|
||||
xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id])
|
||||
xml.tag!('customerShippingAddressId', options[:customer_address_id]) if options[:customer_address_id]
|
||||
tag_unless_blank(xml, 'cardCode', options[:card_code])
|
||||
xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode]
|
||||
|
||||
xml.target!
|
||||
end
|
||||
|
||||
# :merchant_customer_id (Optional)
|
||||
# :description (Optional)
|
||||
# :email (Optional)
|
||||
# :payment_profiles (Optional)
|
||||
def add_profile(xml, profile, update = false)
|
||||
xml.tag!('profile') do
|
||||
# Merchant assigned ID for the customer. Up to 20 characters. (optional)
|
||||
xml.tag!('merchantCustomerId', profile[:merchant_customer_id]) if profile[:merchant_customer_id]
|
||||
# Description of the customer. Up to 255 Characters (optional)
|
||||
xml.tag!('description', profile[:description]) if profile[:description]
|
||||
# Email Address for the customer. Up to 255 Characters (optional)
|
||||
xml.tag!('email', profile[:email]) if profile[:email]
|
||||
|
||||
if update
|
||||
xml.tag!('customerProfileId', profile[:customer_profile_id])
|
||||
else
|
||||
add_payment_profiles(xml, profile[:payment_profiles]) if profile[:payment_profiles]
|
||||
add_ship_to_list(xml, profile[:ship_to_list]) if profile[:ship_to_list]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_transaction(xml, transaction)
|
||||
unless CIM_TRANSACTION_TYPES.include?(transaction[:type])
|
||||
raise StandardError, "Invalid Customer Information Manager Transaction Type: #{transaction[:type]}"
|
||||
end
|
||||
|
||||
xml.tag!('transaction') do
|
||||
xml.tag!(CIM_TRANSACTION_TYPES[transaction[:type]]) do
|
||||
# The amount to be billed to the customer
|
||||
case transaction[:type]
|
||||
when :void
|
||||
tag_unless_blank(xml,'customerProfileId', transaction[:customer_profile_id])
|
||||
tag_unless_blank(xml,'customerPaymentProfileId', transaction[:customer_payment_profile_id])
|
||||
tag_unless_blank(xml,'customerShippingAddressId', transaction[:customer_shipping_address_id])
|
||||
xml.tag!('transId', transaction[:trans_id])
|
||||
when :refund
|
||||
#TODO - add lineItems field
|
||||
xml.tag!('amount', transaction[:amount])
|
||||
tag_unless_blank(xml, 'customerProfileId', transaction[:customer_profile_id])
|
||||
tag_unless_blank(xml, 'customerPaymentProfileId', transaction[:customer_payment_profile_id])
|
||||
tag_unless_blank(xml, 'customerShippingAddressId', transaction[:customer_shipping_address_id])
|
||||
tag_unless_blank(xml, 'creditCardNumberMasked', transaction[:credit_card_number_masked])
|
||||
tag_unless_blank(xml, 'bankRoutingNumberMasked', transaction[:bank_routing_number_masked])
|
||||
tag_unless_blank(xml, 'bankAccountNumberMasked', transaction[:bank_account_number_masked])
|
||||
xml.tag!('transId', transaction[:trans_id])
|
||||
add_tax(xml, transaction[:tax]) if transaction[:tax]
|
||||
add_duty(xml, transaction[:duty]) if transaction[:duty]
|
||||
add_shipping(xml, transaction[:shipping]) if transaction[:shipping]
|
||||
when :prior_auth_capture
|
||||
xml.tag!('amount', transaction[:amount])
|
||||
xml.tag!('transId', transaction[:trans_id])
|
||||
else
|
||||
xml.tag!('amount', transaction[:amount])
|
||||
xml.tag!('customerProfileId', transaction[:customer_profile_id])
|
||||
xml.tag!('customerPaymentProfileId', transaction[:customer_payment_profile_id])
|
||||
xml.tag!('approvalCode', transaction[:approval_code]) if transaction[:type] == :capture_only
|
||||
end
|
||||
add_order(xml, transaction[:order]) if transaction[:order].present?
|
||||
unless [:void,:refund,:prior_auth_capture].include?(transaction[:type])
|
||||
tag_unless_blank(xml, 'cardCode', transaction[:card_code])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_tax(xml, tax)
|
||||
xml.tag!('tax') do
|
||||
xml.tag!('amount', tax[:amount]) if tax[:amount]
|
||||
xml.tag!('name', tax[:name]) if tax[:name]
|
||||
xml.tag!('description', tax[:description]) if tax[:description]
|
||||
end
|
||||
end
|
||||
|
||||
def add_duty(xml, duty)
|
||||
xml.tag!('duty') do
|
||||
xml.tag!('amount', duty[:amount]) if duty[:amount]
|
||||
xml.tag!('name', duty[:name]) if duty[:name]
|
||||
xml.tag!('description', duty[:description]) if duty[:description]
|
||||
end
|
||||
end
|
||||
|
||||
def add_shipping(xml, shipping)
|
||||
xml.tag!('shipping') do
|
||||
xml.tag!('amount', shipping[:amount]) if shipping[:amount]
|
||||
xml.tag!('name', shipping[:name]) if shipping[:name]
|
||||
xml.tag!('description', shipping[:description]) if shipping[:description]
|
||||
end
|
||||
end
|
||||
|
||||
def add_order(xml, order)
|
||||
xml.tag!('order') do
|
||||
xml.tag!('invoiceNumber', order[:invoice_number]) if order[:invoice_number]
|
||||
xml.tag!('description', order[:description]) if order[:description]
|
||||
xml.tag!('purchaseOrderNumber', order[:purchase_order_number]) if order[:purchase_order_number]
|
||||
end
|
||||
end
|
||||
|
||||
def add_payment_profiles(xml, payment_profiles)
|
||||
xml.tag!('paymentProfiles') do
|
||||
add_payment_profile(xml, payment_profiles)
|
||||
end
|
||||
end
|
||||
|
||||
# :customer_type => 'individual or business', # Optional
|
||||
# :bill_to => @address,
|
||||
# :payment => @payment
|
||||
def add_payment_profile(xml, payment_profile)
|
||||
# 'individual' or 'business' (optional)
|
||||
xml.tag!('customerType', payment_profile[:customer_type]) if payment_profile[:customer_type]
|
||||
|
||||
if payment_profile[:bill_to]
|
||||
xml.tag!('billTo') do
|
||||
add_address(xml, payment_profile[:bill_to])
|
||||
end
|
||||
end
|
||||
|
||||
if payment_profile[:payment]
|
||||
xml.tag!('payment') do
|
||||
add_credit_card(xml, payment_profile[:payment][:credit_card]) if payment_profile[:payment].has_key?(:credit_card)
|
||||
add_bank_account(xml, payment_profile[:payment][:bank_account]) if payment_profile[:payment].has_key?(:bank_account)
|
||||
add_drivers_license(xml, payment_profile[:payment][:drivers_license]) if payment_profile[:payment].has_key?(:drivers_license)
|
||||
# This element is only required for Wells Fargo SecureSource eCheck.Net merchants
|
||||
# The customer's Social Security Number or Tax ID
|
||||
xml.tag!('taxId', payment_profile[:payment]) if payment_profile[:payment].has_key?(:tax_id)
|
||||
end
|
||||
end
|
||||
|
||||
xml.tag!('customerPaymentProfileId', payment_profile[:customer_payment_profile_id]) if payment_profile[:customer_payment_profile_id]
|
||||
end
|
||||
|
||||
def add_ship_to_list(xml, ship_to_list)
|
||||
xml.tag!('shipToList') do
|
||||
add_address(xml, ship_to_list)
|
||||
end
|
||||
end
|
||||
|
||||
def add_address(xml, address)
|
||||
xml.tag!('firstName', address[:first_name])
|
||||
xml.tag!('lastName', address[:last_name])
|
||||
xml.tag!('company', address[:company])
|
||||
xml.tag!('address', address[:address1]) if address[:address1]
|
||||
xml.tag!('address', address[:address]) if address[:address]
|
||||
xml.tag!('city', address[:city])
|
||||
xml.tag!('state', address[:state])
|
||||
xml.tag!('zip', address[:zip])
|
||||
xml.tag!('country', address[:country])
|
||||
xml.tag!('phoneNumber', address[:phone_number]) if address[:phone_number]
|
||||
xml.tag!('faxNumber', address[:fax_number]) if address[:fax_number]
|
||||
|
||||
xml.tag!('customerAddressId', address[:customer_address_id]) if address[:customer_address_id]
|
||||
end
|
||||
|
||||
# Adds customer’s credit card information
|
||||
# Note: This element should only be included
|
||||
# when the payment method is credit card.
|
||||
def add_credit_card(xml, credit_card)
|
||||
return unless credit_card
|
||||
xml.tag!('creditCard') do
|
||||
# The credit card number used for payment of the subscription
|
||||
xml.tag!('cardNumber', credit_card.number)
|
||||
# The expiration date of the credit card used for the subscription
|
||||
xml.tag!('expirationDate', expdate(credit_card))
|
||||
# Note that Authorize.net does not save CVV codes as part of the
|
||||
# payment profile. Any transactions/validations after the payment
|
||||
# profile is created that wish to use CVV verification must pass
|
||||
# the CVV code to authorize.net again.
|
||||
xml.tag!('cardCode', credit_card.verification_value) if credit_card.verification_value?
|
||||
end
|
||||
end
|
||||
|
||||
# Adds customer’s bank account information
|
||||
# Note: This element should only be included
|
||||
# when the payment method is bank account.
|
||||
def add_bank_account(xml, bank_account)
|
||||
raise StandardError, "Invalid Bank Account Type: #{bank_account[:account_type]}" unless BANK_ACCOUNT_TYPES.include?(bank_account[:account_type])
|
||||
raise StandardError, "Invalid eCheck Type: #{bank_account[:echeck_type]}" unless ECHECK_TYPES.include?(bank_account[:echeck_type])
|
||||
|
||||
xml.tag!('bankAccount') do
|
||||
# The type of bank account
|
||||
xml.tag!('accountType', BANK_ACCOUNT_TYPES[bank_account[:account_type]])
|
||||
# The routing number of the customer’s bank
|
||||
xml.tag!('routingNumber', bank_account[:routing_number])
|
||||
# The bank account number
|
||||
xml.tag!('accountNumber', bank_account[:account_number])
|
||||
# The full name of the individual associated
|
||||
# with the bank account number
|
||||
xml.tag!('nameOnAccount', bank_account[:name_on_account])
|
||||
# The type of electronic check transaction
|
||||
xml.tag!('echeckType', ECHECK_TYPES[bank_account[:echeck_type]])
|
||||
# The full name of the individual associated
|
||||
# with the bank account number (optional)
|
||||
xml.tag!('bankName', bank_account[:bank_name]) if bank_account[:bank_name]
|
||||
end
|
||||
end
|
||||
|
||||
# Adds customer’s driver's license information
|
||||
# Note: This element is only required for
|
||||
# Wells Fargo SecureSource eCheck.Net merchants
|
||||
def add_drivers_license(xml, drivers_license)
|
||||
xml.tag!('driversLicense') do
|
||||
# The state of the customer's driver's license
|
||||
# A valid two character state code
|
||||
xml.tag!('state', drivers_license[:state])
|
||||
# The customer’s driver's license number
|
||||
xml.tag!('number', drivers_license[:number])
|
||||
# The date of birth listed on the customer's driver's license
|
||||
# YYYY-MM-DD
|
||||
xml.tag!('dateOfBirth', drivers_license[:date_of_birth])
|
||||
end
|
||||
end
|
||||
|
||||
def commit(action, request)
|
||||
url = test? ? test_url : live_url
|
||||
xml = ssl_post(url, request, "Content-Type" => "text/xml")
|
||||
|
||||
response_params = parse(action, xml)
|
||||
|
||||
message = response_params['messages']['message']['text']
|
||||
test_mode = test? || message =~ /Test Mode/
|
||||
success = response_params['messages']['result_code'] == 'Ok'
|
||||
response_params['direct_response'] = parse_direct_response(response_params['direct_response']) if response_params['direct_response']
|
||||
transaction_id = response_params['direct_response']['transaction_id'] if response_params['direct_response']
|
||||
|
||||
Response.new(success, message, response_params,
|
||||
:test => test_mode,
|
||||
:authorization => transaction_id || response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil)
|
||||
)
|
||||
end
|
||||
|
||||
def tag_unless_blank(xml, tag_name, data)
|
||||
xml.tag!(tag_name, data) unless data.blank? || data.nil?
|
||||
end
|
||||
|
||||
def format_extra_options(options)
|
||||
options.map{ |k, v| "#{k}=#{v}" }.join(',') unless options.nil?
|
||||
end
|
||||
|
||||
def parse_direct_response(params)
|
||||
delimiter = @options[:delimiter] || ','
|
||||
direct_response = {'raw' => params}
|
||||
direct_response_fields = params.split(delimiter)
|
||||
direct_response.merge(
|
||||
{
|
||||
'response_code' => direct_response_fields[0],
|
||||
'response_subcode' => direct_response_fields[1],
|
||||
'response_reason_code' => direct_response_fields[2],
|
||||
'message' => direct_response_fields[3],
|
||||
'approval_code' => direct_response_fields[4],
|
||||
'avs_response' => direct_response_fields[5],
|
||||
'transaction_id' => direct_response_fields[6],
|
||||
'invoice_number' => direct_response_fields[7],
|
||||
'order_description' => direct_response_fields[8],
|
||||
'amount' => direct_response_fields[9],
|
||||
'method' => direct_response_fields[10],
|
||||
'transaction_type' => direct_response_fields[11],
|
||||
'customer_id' => direct_response_fields[12],
|
||||
'first_name' => direct_response_fields[13],
|
||||
'last_name' => direct_response_fields[14],
|
||||
'company' => direct_response_fields[15],
|
||||
'address' => direct_response_fields[16],
|
||||
'city' => direct_response_fields[17],
|
||||
'state' => direct_response_fields[18],
|
||||
'zip_code' => direct_response_fields[19],
|
||||
'country' => direct_response_fields[20],
|
||||
'phone' => direct_response_fields[21],
|
||||
'fax' => direct_response_fields[22],
|
||||
'email_address' => direct_response_fields[23],
|
||||
'ship_to_first_name' => direct_response_fields[24],
|
||||
'ship_to_last_name' => direct_response_fields[25],
|
||||
'ship_to_company' => direct_response_fields[26],
|
||||
'ship_to_address' => direct_response_fields[27],
|
||||
'ship_to_city' => direct_response_fields[28],
|
||||
'ship_to_state' => direct_response_fields[29],
|
||||
'ship_to_zip_code' => direct_response_fields[30],
|
||||
'ship_to_country' => direct_response_fields[31],
|
||||
'tax' => direct_response_fields[32],
|
||||
'duty' => direct_response_fields[33],
|
||||
'freight' => direct_response_fields[34],
|
||||
'tax_exempt' => direct_response_fields[35],
|
||||
'purchase_order_number' => direct_response_fields[36],
|
||||
'md5_hash' => direct_response_fields[37],
|
||||
'card_code' => direct_response_fields[38],
|
||||
'cardholder_authentication_verification_response' => direct_response_fields[39],
|
||||
# The following direct response fields are only available in version 3.1 of the
|
||||
# transaction response. Check your merchant account settings for details.
|
||||
'account_number' => direct_response_fields[50] || '',
|
||||
'card_type' => direct_response_fields[51] || '',
|
||||
'split_tender_id' => direct_response_fields[52] || '',
|
||||
'requested_amount' => direct_response_fields[53] || '',
|
||||
'balance_on_card' => direct_response_fields[54] || '',
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def parse(action, xml)
|
||||
xml = REXML::Document.new(xml)
|
||||
root = REXML::XPath.first(xml, "//#{CIM_ACTIONS[action]}Response") ||
|
||||
REXML::XPath.first(xml, "//ErrorResponse")
|
||||
if root
|
||||
response = parse_element(root)
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
|
||||
def parse_element(node)
|
||||
if node.has_elements?
|
||||
response = {}
|
||||
node.elements.each{ |e|
|
||||
key = e.name.underscore
|
||||
value = parse_element(e)
|
||||
if response.has_key?(key)
|
||||
if response[key].is_a?(Array)
|
||||
response[key].push(value)
|
||||
else
|
||||
response[key] = [response[key], value]
|
||||
end
|
||||
else
|
||||
response[key] = parse_element(e)
|
||||
end
|
||||
}
|
||||
else
|
||||
response = node.text
|
||||
end
|
||||
|
||||
response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,467 +0,0 @@
|
||||
require 'json'
|
||||
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
|
||||
# For more information on Balanced visit https://www.balancedpayments.com
|
||||
# or visit #balanced on irc.freenode.net
|
||||
#
|
||||
# Instantiate a instance of BalancedGateway by passing through your
|
||||
# Balanced API key secret.
|
||||
#
|
||||
# ==== To obtain an API key of your own
|
||||
#
|
||||
# 1. Visit https://www.balancedpayments.com
|
||||
# 2. Click "Get started"
|
||||
# 3. The next screen will give you a test API key of your own
|
||||
# 4. When you're ready to generate a production API key click the "Go
|
||||
# live" button on the Balanced dashboard and fill in your marketplace
|
||||
# details.
|
||||
#
|
||||
# ==== Overview
|
||||
#
|
||||
# Balanced provides a RESTful API, all entities within Balanced are
|
||||
# represented by their respective URIs, these are returned in the
|
||||
# `authorization` parameter of the Active Merchant Response object.
|
||||
#
|
||||
# All Response objects will contain a hash property called `params` which
|
||||
# holds the raw JSON dictionary returned by Balanced. You can find
|
||||
# properties about the operation performed and the object that represents
|
||||
# it within this hash.
|
||||
#
|
||||
# All operations within Balanced are tied to an account, as such, when you
|
||||
# perform an `authorization` or a `capture` with a new credit card you
|
||||
# must ensure you also pass the `:email` property within the `options`
|
||||
# parameter.
|
||||
#
|
||||
# For more details about Balanced's API visit:
|
||||
# https://www.balancedpayments.com/docs
|
||||
#
|
||||
# ==== Terminology & Transaction Flow
|
||||
#
|
||||
# * An `authorization` operation will return a Hold URI. An `authorization`
|
||||
# within Balanced is valid until the `expires_at` property. You can see the
|
||||
# exact date of the expiry on the Response object by inspecting the
|
||||
# property `response.params['expires_at']`. The resulting Hold may be
|
||||
# `capture`d or `void`ed at any time before the `expires_at` date for
|
||||
# any amount up to the full amount of the original `authorization`.
|
||||
# * A `capture` operation will return a Debit URI. You must pass the URI of
|
||||
# the previously performed `authorization`
|
||||
# * A `purchase` will create a Hold and Debit in a single operation and
|
||||
# return the URI of the resulting Debit.
|
||||
# * A `void` operation must be performed on an existing `authorization`
|
||||
# and will result in releasing the funds reserved by the
|
||||
# `authorization`.
|
||||
# * The `refund` operation must be performed on a previously captured
|
||||
# Debit URI. You may refund any fraction of the original amount of the
|
||||
# debit up to the original total.
|
||||
#
|
||||
class BalancedGateway < Gateway
|
||||
VERSION = '1.0.0'
|
||||
|
||||
TEST_URL = LIVE_URL = 'https://api.balancedpayments.com'
|
||||
|
||||
# The countries the gateway supports merchants from as 2 digit ISO
|
||||
# country codes
|
||||
self.supported_countries = ['US']
|
||||
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
|
||||
self.homepage_url = 'https://www.balancedpayments.com/'
|
||||
self.display_name = 'Balanced'
|
||||
self.money_format = :cents
|
||||
|
||||
class Error < StandardError
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response, msg=nil)
|
||||
@response = response
|
||||
super(msg || response['description'])
|
||||
end
|
||||
end
|
||||
|
||||
class CardDeclined < Error
|
||||
end
|
||||
|
||||
# Creates a new BalancedGateway
|
||||
#
|
||||
# The gateway requires that a valid api_key be passed in the +options+
|
||||
# hash.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>:login</tt> -- The Balanced API Secret (REQUIRED)
|
||||
def initialize(options = {})
|
||||
requires!(options, :login)
|
||||
super
|
||||
initialize_marketplace(options[:marketplace] || load_marketplace)
|
||||
end
|
||||
|
||||
# Performs an authorization (Hold in Balanced nonclementure), which
|
||||
# reserves the funds on the customer's credit card, but does not charge
|
||||
# the card. An authorization is valid until the `expires_at` field in
|
||||
# the params Hash passes. See `response.params['expires_at']`. The exact
|
||||
# amount of time until an authorization expires depends on the card
|
||||
# issuer.
|
||||
#
|
||||
# If you pass a previously tokenized `credit_card` URI the only other
|
||||
# parameter required is `money`. If you pass `credit_card` as a hash of
|
||||
# credit card information you must also pass `options` with a `:email`
|
||||
# entry.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be authorized as an Integer value in cents.
|
||||
# * <tt>credit_card</tt> -- A hash of credit card details for this
|
||||
# transaction or the URI of a card previously stored in Balanced.
|
||||
# * <tt>options</tt> -- A hash of optional parameters.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# If you are passing a new credit card you must pass one of these two
|
||||
# parameters
|
||||
#
|
||||
# * <tt>email</tt> -- the email address of user associated with this
|
||||
# purchase.
|
||||
# * <tt>account_uri</tt> -- `account_uri` is the URI of an existing
|
||||
# Balanced account.
|
||||
def authorize(money, credit_card, options = {})
|
||||
if credit_card.respond_to?(:number)
|
||||
requires!(options, :email) unless options[:account_uri]
|
||||
end
|
||||
|
||||
post = {}
|
||||
post[:amount] = money
|
||||
post[:description] = options[:description]
|
||||
|
||||
create_or_find_account(post, options)
|
||||
add_credit_card(post, credit_card, options)
|
||||
add_address(credit_card, options)
|
||||
|
||||
create_transaction(:post, @holds_uri, post)
|
||||
rescue Error => ex
|
||||
failed_response(ex.response)
|
||||
end
|
||||
|
||||
# Perform a purchase, which is an authorization and capture in a single
|
||||
# operation.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be purchased as an Integer value in cents.
|
||||
# * <tt>credit_card</tt> -- A hash of credit card details for this
|
||||
# transaction or the URI of a card previously stored in Balanced.
|
||||
# * <tt>options</tt> -- A hash of optional parameters.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# If you are passing a new credit card you must pass one of these two
|
||||
# parameters
|
||||
#
|
||||
# * <tt>email</tt> -- the email address of user associated with this
|
||||
# purchase.
|
||||
# * <tt>account_uri</tt> -- `account_uri` is the URI of an existing
|
||||
# Balanced account.
|
||||
def purchase(money, credit_card, options = {})
|
||||
if credit_card.respond_to?('number')
|
||||
requires!(options, :email) unless options[:account_uri]
|
||||
end
|
||||
|
||||
post = {}
|
||||
post[:amount] = money
|
||||
post[:description] = options[:description]
|
||||
|
||||
create_or_find_account(post, options)
|
||||
add_credit_card(post, credit_card, options)
|
||||
add_address(credit_card, options)
|
||||
|
||||
create_transaction(:post, @debits_uri, post)
|
||||
rescue Error => ex
|
||||
failed_response(ex.response)
|
||||
end
|
||||
|
||||
# Captures the funds from an authorized transaction (Hold).
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>money</tt> -- The amount to be captured as an Integer value in
|
||||
# cents. If omitted the full amount of the original authorization
|
||||
# transaction will be captured.
|
||||
# * <tt>authorization</tt> -- The uri of an authorization returned from
|
||||
# an authorize request.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>description</tt> -- A string that will be displayed on the
|
||||
# Balanced dashboard
|
||||
def capture(money, authorization, options = {})
|
||||
post = {}
|
||||
post[:hold_uri] = authorization
|
||||
post[:amount] = money if money
|
||||
post[:description] = options[:description] if options[:description]
|
||||
post[:on_behalf_of_uri] = options[:on_behalf_of_uri] if options[:on_behalf_of_uri]
|
||||
|
||||
create_transaction(:post, @debits_uri, post)
|
||||
rescue Error => ex
|
||||
failed_response(ex.response)
|
||||
end
|
||||
|
||||
# Void a previous authorization (Hold)
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>authorization</tt> -- The uri of the authorization returned from
|
||||
# an `authorize` request.
|
||||
def void(authorization)
|
||||
post = {}
|
||||
post[:is_void] = true
|
||||
|
||||
create_transaction(:put, authorization, post)
|
||||
rescue Error => ex
|
||||
failed_response(ex.response)
|
||||
end
|
||||
|
||||
# Refund a transaction.
|
||||
#
|
||||
# Returns the money debited from a card to the card from the
|
||||
# marketplace's escrow balance.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>debit_uri</tt> -- The uri of the original transaction against
|
||||
# which the refund is being issued.
|
||||
# * <tt>options</tt> -- A hash of parameters.
|
||||
#
|
||||
# ==== Options
|
||||
#
|
||||
# * <tt>`:amount`<tt> -- specify an amount if you want to perform a
|
||||
# partial refund. This value will default to the total amount of the
|
||||
# debit that has not been refunded so far.
|
||||
def refund(amount, debit_uri = "deprecated", options = {})
|
||||
if(debit_uri == "deprecated" || debit_uri.kind_of?(Hash))
|
||||
deprecated "Calling the refund method without an amount parameter is deprecated and will be removed in a future version."
|
||||
return refund(options[:amount], amount, options)
|
||||
end
|
||||
|
||||
requires!(debit_uri)
|
||||
post = {}
|
||||
post[:debit_uri] = debit_uri
|
||||
post[:amount] = amount
|
||||
post[:description] = options[:description]
|
||||
create_transaction(:post, @refunds_uri, post)
|
||||
rescue Error => ex
|
||||
failed_response(ex.response)
|
||||
end
|
||||
|
||||
# Stores a card and email address
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * <tt>credit_card</tt> --
|
||||
def store(credit_card, options = {})
|
||||
requires!(options, :email)
|
||||
post = {}
|
||||
account_uri = create_or_find_account(post, options)
|
||||
if credit_card.respond_to? :number
|
||||
add_credit_card(post, credit_card, options)
|
||||
else
|
||||
associate_card_to_account(account_uri, credit_card)
|
||||
credit_card
|
||||
end
|
||||
rescue Error => ex
|
||||
failed_response(ex.response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Load URIs for this marketplace by inspecting the marketplace object
|
||||
# returned from the uri. http://en.wikipedia.org/wiki/HATEOAS
|
||||
def load_marketplace
|
||||
response = http_request(:get, '/v1/marketplaces')
|
||||
if error?(response)
|
||||
raise Error.new(response, 'Invalid login credentials supplied')
|
||||
end
|
||||
response['items'][0]
|
||||
end
|
||||
|
||||
def initialize_marketplace(marketplace)
|
||||
@marketplace_uri = marketplace['uri']
|
||||
@holds_uri = marketplace['holds_uri']
|
||||
@debits_uri = marketplace['debits_uri']
|
||||
@cards_uri = marketplace['cards_uri']
|
||||
@accounts_uri = marketplace['accounts_uri']
|
||||
@refunds_uri = marketplace['refunds_uri']
|
||||
end
|
||||
|
||||
def create_or_find_account(post, options)
|
||||
account_uri = nil
|
||||
|
||||
if options.has_key? :account_uri
|
||||
account_uri = options[:account_uri]
|
||||
end
|
||||
|
||||
if account_uri == nil
|
||||
post[:email_address] = options[:email]
|
||||
|
||||
# create an account
|
||||
response = http_request(:post, @accounts_uri, post)
|
||||
|
||||
if response.has_key? 'uri'
|
||||
account_uri = response['uri']
|
||||
elsif error?(response)
|
||||
# lookup account from Balanced, account_uri should be in the
|
||||
# exception in a dictionary called extras
|
||||
account_uri = response['extras']['account_uri']
|
||||
end
|
||||
end
|
||||
|
||||
post[:account_uri] = account_uri
|
||||
|
||||
account_uri
|
||||
end
|
||||
|
||||
def add_address(credit_card, options)
|
||||
return unless credit_card.kind_of?(Hash)
|
||||
if address = options[:billing_address] || options[:address]
|
||||
credit_card[:street_address] = address[:address1] if address[:address1]
|
||||
credit_card[:street_address] += ' ' + address[:address2] if address[:address2]
|
||||
credit_card[:postal_code] = address[:zip] if address[:zip]
|
||||
credit_card[:country] = address[:country] if address[:country]
|
||||
end
|
||||
end
|
||||
|
||||
def add_credit_card(post, credit_card, options)
|
||||
if credit_card.respond_to? :number
|
||||
card = {}
|
||||
card[:card_number] = credit_card.number
|
||||
card[:expiration_month] = credit_card.month
|
||||
card[:expiration_year] = credit_card.year
|
||||
card[:security_code] = credit_card.verification_value if credit_card.verification_value?
|
||||
card[:name] = credit_card.name if credit_card.name
|
||||
|
||||
add_address(card, options)
|
||||
|
||||
response = http_request(:post, @cards_uri, card)
|
||||
if error?(response)
|
||||
raise CardDeclined, response
|
||||
end
|
||||
card_uri = response['uri']
|
||||
|
||||
associate_card_to_account(post[:account_uri], card_uri)
|
||||
|
||||
post[:card_uri] = card_uri
|
||||
elsif credit_card.kind_of?(String)
|
||||
post[:card_uri] = credit_card
|
||||
end
|
||||
|
||||
post[:card_uri]
|
||||
end
|
||||
|
||||
def associate_card_to_account(account_uri, card_uri)
|
||||
http_request(:put, account_uri, :card_uri => card_uri)
|
||||
end
|
||||
|
||||
def http_request(method, url, parameters={}, meta={})
|
||||
begin
|
||||
if method == :get
|
||||
raw_response = ssl_get(LIVE_URL + url, headers(meta))
|
||||
else
|
||||
raw_response = ssl_request(method,
|
||||
LIVE_URL + url,
|
||||
post_data(parameters),
|
||||
headers(meta))
|
||||
end
|
||||
parse(raw_response)
|
||||
rescue ResponseError => e
|
||||
raw_response = e.response.body
|
||||
response_error(raw_response)
|
||||
rescue JSON::ParserError
|
||||
json_error(raw_response)
|
||||
end
|
||||
end
|
||||
|
||||
def create_transaction(method, url, parameters, meta={})
|
||||
response = http_request(method, url, parameters, meta)
|
||||
success = !error?(response)
|
||||
|
||||
Response.new(success,
|
||||
(success ? "Transaction approved" : response["description"]),
|
||||
response,
|
||||
:test => (@marketplace_uri.index("TEST") ? true : false),
|
||||
:authorization => response["uri"]
|
||||
)
|
||||
end
|
||||
|
||||
def failed_response(response)
|
||||
is_test = false
|
||||
if @marketplace_uri
|
||||
is_test = (@marketplace_uri.index("TEST") ? true : false)
|
||||
end
|
||||
|
||||
Response.new(false,
|
||||
response["description"],
|
||||
response,
|
||||
:test => is_test
|
||||
)
|
||||
end
|
||||
|
||||
def parse(body)
|
||||
JSON.parse(body)
|
||||
end
|
||||
|
||||
def response_error(raw_response)
|
||||
begin
|
||||
parse(raw_response)
|
||||
rescue JSON::ParserError
|
||||
json_error(raw_response)
|
||||
end
|
||||
end
|
||||
|
||||
def json_error(raw_response)
|
||||
msg = 'Invalid response received from the Balanced API. Please contact support@balancedpayments.com if you continue to receive this message.'
|
||||
msg += " (The raw response returned by the API was #{raw_response.inspect})"
|
||||
{
|
||||
"error" => {
|
||||
"message" => msg
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def error?(response)
|
||||
response.key?('status_code')
|
||||
end
|
||||
|
||||
def post_data(params)
|
||||
return nil unless params
|
||||
|
||||
params.map do |key, value|
|
||||
next if value.blank?
|
||||
if value.is_a?(Hash)
|
||||
h = {}
|
||||
value.each do |k, v|
|
||||
h["#{key}[#{k}]"] = v unless v.blank?
|
||||
end
|
||||
post_data(h)
|
||||
else
|
||||
"#{key}=#{CGI.escape(value.to_s)}"
|
||||
end
|
||||
end.compact.join("&")
|
||||
end
|
||||
|
||||
def headers(meta={})
|
||||
@@ua ||= JSON.dump({
|
||||
:bindings_version => ActiveMerchant::VERSION,
|
||||
:lang => 'ruby',
|
||||
:lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
|
||||
:lib_version => BalancedGateway::VERSION,
|
||||
:platform => RUBY_PLATFORM,
|
||||
:publisher => 'active_merchant'
|
||||
})
|
||||
|
||||
{
|
||||
"Authorization" => "Basic " + Base64.encode64(@options[:login].to_s + ":").strip,
|
||||
"User-Agent" => "Balanced/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
|
||||
"X-Balanced-User-Agent" => @@ua,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,105 +0,0 @@
|
||||
module ActiveMerchant #:nodoc:
|
||||
module Billing #:nodoc:
|
||||
class BanwireGateway < Gateway
|
||||
URL = 'https://banwire.com/api.pago_pro'
|
||||
|
||||
self.supported_countries = ['MX']
|
||||
self.supported_cardtypes = [:visa, :master, :american_express]
|
||||
self.homepage_url = 'http://www.banwire.com/'
|
||||
self.display_name = 'Banwire'
|
||||
|
||||
def initialize(options = {})
|
||||
requires!(options, :login)
|
||||
super
|
||||
end
|
||||
|
||||
def purchase(money, creditcard, options = {})
|
||||
post = {}
|
||||
add_response_type(post)
|
||||
add_customer_data(post, options)
|
||||
add_order_data(post, options)
|
||||
add_creditcard(post, creditcard)
|
||||
add_address(post, creditcard, options)
|
||||
add_customer_data(post, options)
|
||||
add_amount(post, money, options)
|
||||
|
||||
commit(money, post)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_response_type(post)
|
||||
post[:response_format] = "JSON"
|
||||
end
|
||||
|
||||
def add_customer_data(post, options)
|
||||
post[:user] = @options[:login]
|
||||
post[:phone] = options[:billing_address][:phone]
|
||||
post[:mail] = options[:email]
|
||||
end
|
||||
|
||||
def add_order_data(post, options)
|
||||
post[:reference] = options[:order_id]
|
||||
post[:concept] = options[:description]
|
||||
end
|
||||
|
||||
def add_address(post, creditcard, options)
|
||||
post[:address] = options[:billing_address][:address1]
|
||||
post[:post_code] = options[:billing_address][:zip]
|
||||
end
|
||||
|
||||
def add_creditcard(post, creditcard)
|
||||
post[:card_num] = creditcard.number
|
||||
post[:card_name] = creditcard.name
|
||||
post[:card_type] = card_brand(creditcard)
|
||||
post[:card_exp] = "#{sprintf("%02d", creditcard.month)}/#{"#{creditcard.year}"[-2, 2]}"
|
||||
post[:card_ccv2] = creditcard.verification_value
|
||||
end
|
||||
|
||||
def add_amount(post, money, options)
|
||||
post[:ammount] = amount(money)
|
||||
post[:currency] = options[:currency]
|
||||
end
|
||||
|
||||
def card_brand(card)
|
||||
brand = super
|
||||
({"master" => "mastercard", "american_express" => "amex"}[brand] || brand)
|
||||
end
|
||||
|
||||
def parse(body)
|
||||
JSON.parse(body)
|
||||
end
|
||||
|
||||
def commit(money, parameters)
|
||||
raw_response = ssl_post(URL, post_data(parameters))
|
||||
begin
|
||||
response = parse(raw_response)
|
||||
rescue JSON::ParserError
|
||||
response = json_error(raw_response)
|
||||
end
|
||||
|
||||
Response.new(success?(response),
|
||||
response["message"],
|
||||
response,
|
||||
:test => test?,
|
||||
:authorization => response["code_auth"])
|
||||
end
|
||||
|
||||
def success?(response)
|
||||
(response["response"] == "ok")
|
||||
end
|
||||
|
||||
def post_data(parameters = {})
|
||||
parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
|
||||
end
|
||||
|
||||
def json_error(raw_response)
|
||||
msg = 'Invalid response received from the Banwire API. Please contact Banwire support if you continue to receive this message.'
|
||||
msg += " (The raw response returned by the API was #{raw_response.inspect})"
|
||||
{
|
||||
"message" => msg
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user