Compare commits

...

95 Commits

Author SHA1 Message Date
pozorvlak
4a12523793 Merge pull request #991 from Growstuff/dev
Release 12
2016-06-17 11:37:29 +01:00
pozorvlak
6aa601b8ab Merge pull request #993 from Growstuff/revert-988-dev
Revert "Handled Issue #950 (Branded Error Pages)"
2016-06-16 13:30:50 +01:00
pozorvlak
a66a040207 Revert "Handled Issue #950 (Branded Error Pages)" 2016-06-16 12:58:22 +01:00
pozorvlak
547a408a99 Merge pull request #988 from Prashanth261993/dev
Add branding to error pages (#950)
2016-06-16 11:12:23 +01:00
pozorvlak
8cd224b7fa Merge pull request #990 from maco/sparkpost_#917
don't override our environment email settings with plain old sendmail
2016-06-15 20:03:35 +01:00
Mackenzie Morgan
8428e93b47 don't override our environment email settings with plain old sendmail 2016-06-15 13:27:51 -04:00
Prashanth
483143f2fe Merge branch 'growstuff-dev' into dev
Conflicts:
	CONTRIBUTORS.md
2016-06-14 17:09:06 +05:30
pozorvlak
1b3d106b6d Merge pull request #989 from maco/sparkpost_#917
update to SparkPost's mailserver
2016-06-13 18:04:43 +01:00
Mackenzie Morgan
e7be006fa7 update to SparkPost's mailserver 2016-06-13 12:12:00 -04:00
Prashanth
b9f40151d8 Handled Issue #950 (Branded Error Pages)
Moved error related views from public to errors (asset pipeline can be used).
Header and footer were included along with the error message.
2016-06-10 18:35:48 +05:30
pozorvlak
e1ad0c31c8 Merge pull request #986 from CloCkWeRX/update_various_gems_nokogiri
Update various gems - nokogiri, mime-types, parser, guard-rspec
2016-06-10 11:09:12 +01:00
Daniel O'Connor
deaf49c18f Update selenium-webdriver 2016-06-10 15:40:56 +09:30
Daniel O'Connor
53543fff4c Update terminal-table 2016-06-10 15:40:29 +09:30
Daniel O'Connor
ea5e710a70 Update codeclimate-test-reporter 2016-06-10 15:38:43 +09:30
Daniel O'Connor
ad5a52ae74 Update tilt 2016-06-10 15:36:07 +09:30
Daniel O'Connor
c0cc5ab085 Update geocoder 2016-06-10 15:35:29 +09:30
Daniel O'Connor
cf784cbedd Update parser to current 2016-06-10 15:20:31 +09:30
Daniel O'Connor
5c68830919 Update guard-rspec 2016-06-10 15:19:57 +09:30
Daniel O'Connor
370aab41c3 Update mime-types 2016-06-10 15:19:28 +09:30
Daniel O'Connor
081f4021bc Upgrade nokogiri 2016-06-10 15:19:03 +09:30
Daniel O'Connor
36f846fabf Merge pull request #982 from CloCkWeRX/update_gibbon
Upgrade gibbon
2016-06-10 15:13:18 +09:30
pozorvlak
f6d094f57c Merge pull request #985 from lucasnogueira/fix_simplecov_deprecation
Fix SimpleCov Deprecation Warning
2016-06-09 22:37:40 +01:00
Lucas Nogueira
9ce097b7d6 Fix SimpleCov Deprecation Warning 2016-06-09 17:42:11 -03:00
pozorvlak
b03ccce575 Merge pull request #980 from lucasnogueira/replace_pluralize
Replace pluralize calls
2016-06-09 16:39:34 +01:00
Lucas Nogueira
63b8788c80 Change hash syntax 2016-06-09 11:57:56 -03:00
Daniel O'Connor
faa3beddbc Upgrade gibbon 2016-06-10 00:17:28 +09:30
Lucas Nogueira
5dbd5bc1d2 Correct minor typos 2016-06-09 08:21:28 -03:00
Lucas Nogueira
b8b0d98e07 Update the contributors list 2016-06-08 22:06:26 -03:00
Lucas Nogueira
b911ebfd07 Replace pluralize calls 2016-06-08 22:03:40 -03:00
Lucas Nogueira
1ca515bd4e Add missing activerecord model to locales 2016-06-08 21:56:29 -03:00
Lucas Nogueira
905710b6e9 Helper for plural localization 2016-06-08 21:55:33 -03:00
Mackenzie
12e0f674bc Merge pull request #977 from pozorvlak/fix_devise_warnings
Replace deprecated Devise `for` method with `permit`
2016-06-08 13:14:05 -04:00
Mackenzie
18739c2a24 Merge pull request #974 from pozorvlak/remove_vendor_dir
Remove vendor directory
2016-06-08 11:12:14 -04:00
pozorvlak
f3dc2ff7e5 Replace deprecated Devise for method with permit
This should eliminate the screens-ful of deprecation warnings when
running the test suite.
2016-06-08 15:00:05 +00:00
Mackenzie
dbf64a4e90 Merge pull request #975 from Growstuff/revert-972-show_concern
Revert "Show concern"
2016-06-08 09:26:58 -04:00
pozorvlak
a8961c3466 Revert "Show concern" 2016-06-08 12:58:32 +01:00
pozorvlak
6931952688 Whitespace fixes 2016-06-07 17:43:36 +00:00
pozorvlak
936aa17ecc Remove references to vendor directory
This consisted of instructions to various code-quality tools to
ignore it.
2016-06-07 17:43:36 +00:00
pozorvlak
f3d7420b8a Remove vendor directory
We no longer need it, since we're now getting the functionality we
needed from upstream gems.
2016-06-07 17:43:35 +00:00
pozorvlak
e419acea6e Install BogusPayPalGateway gem
We were maintaining a vendor fork of active_merchant because they
refused to merge this feature in; it's now been released as a
separate gem.
2016-06-07 17:43:35 +00:00
pozorvlak
95ae15b780 Merge pull request #972 from maco/show_concern
Show concern
2016-06-07 16:22:17 +01:00
Mackenzie Morgan
ed468e79fc Merge branch 'dev' of gitmaco:Growstuff/growstuff into show_concern 2016-06-07 09:57:47 -04:00
Daniel O'Connor
130565af75 Merge pull request #969 from maco/code_climate_token
add Code Climate token to Travis config
2016-06-07 09:01:27 +09:30
Daniel O'Connor
ad8f360336 Merge pull request #970 from maco/codeclimate_exclude_vendor
exclude vendor gems from code climate
2016-06-07 09:01:05 +09:30
Daniel O'Connor
15775403fd Merge pull request #971 from maco/readme_email
info@ email exists again
2016-06-07 09:00:38 +09:30
Mackenzie Morgan
e1b83c32af info@ email exists again 2016-06-06 13:27:13 -04:00
Mackenzie Morgan
3fc7c65247 exclude vendor gems from code climate 2016-06-06 13:14:01 -04:00
Mackenzie Morgan
c8d1239b79 add Code Climate token to Travis config 2016-06-06 09:40:30 -04:00
Cesy
7885257fae Merge pull request #943 from CloCkWeRX/adjust_helper
Deal with .nil quantity of a seed in rendering helper
2016-06-04 12:41:03 +01:00
Daniel O'Connor
a8d22709e6 Add explict test coverage of the helper with nil seeds (page level tests already cover this rendering on crop detail in general) 2016-06-04 20:51:20 +09:30
pozorvlak
31b7b45ae3 Merge pull request #966 from cesy/readme
Updating readme with current wiki status and Gitter link
2016-06-03 14:34:35 +01:00
Cesy Avon
681798b582 Updating readme with current wiki status and Gitter link 2016-06-03 13:12:19 +00:00
Cesy
f05ea56179 Merge pull request #965 from dv2/dev
Add test coverage for GardensHelper
2016-06-03 14:03:07 +01:00
DV Dasari
ed4269ea4c fixing grammar in test descriptions 2016-06-03 07:54:24 -05:00
DV Dasari
085fcc958e Add test coverage for GardensHelper 2016-06-02 22:22:14 -05:00
pozorvlak
8f33fe3595 Merge pull request #960 from CloCkWeRX/update_various_gems
Update various gems
2016-06-02 18:18:12 +01:00
pozorvlak
980a7d79d3 Merge pull request #963 from CloCkWeRX/add_better_opengraph_squashed
Various bugfixes for facebook/opengraph
2016-06-02 17:58:05 +01:00
pozorvlak
9c5c07e087 Merge pull request #948 from cesy/issue875
Fix #875 edited date on comments on homepage
2016-06-02 17:41:59 +01:00
Daniel O'Connor
faaf07cad8 Render the full size image, as facebook doesn't like smaller thumbnails 2016-06-03 01:58:34 +09:30
Daniel O'Connor
0e83a230b9 Helps not to render excess info 2016-06-03 01:54:26 +09:30
Daniel O'Connor
c278b36858 Up the various opengraph images to 200x200 2016-06-03 01:45:43 +09:30
pozorvlak
0805f86b86 Merge pull request #961 from cesy/issue476
Issue 476 edited by date on comments and posts
2016-06-02 16:53:44 +01:00
pozorvlak
aad88f1da6 Merge pull request #962 from CloCkWeRX/add_better_opengraph_squashed
Add better opengraph behaviour, fix facebook share thumbnail size
2016-06-02 16:52:13 +01:00
Daniel O'Connor
7550bc860f #816 Add better opengraph behaviour, fix facebook share thumbnail size 2016-06-03 01:01:03 +09:30
Cesy Avon
fc38e1edea Issue #476 test update 2016-06-02 14:58:10 +00:00
Daniel O'Connor
88a66a705b Update devise to 4.1.X and unpin 2016-06-03 00:23:58 +09:30
Daniel O'Connor
f77fd00931 Remove version pin for rspec-rails 2016-06-03 00:19:49 +09:30
Daniel O'Connor
b5c030905a Upgrade to geocoder current (we shouldn't be affected by the deprecations in 1.2.X or 1.3.X) 2016-06-03 00:16:00 +09:30
Daniel O'Connor
2844e13298 Upgrade factory_girl_rails, factory_girl 2016-06-03 00:10:25 +09:30
Cesy Avon
3e4dc1f9e3 Issue #875 typo causing test error 2016-06-02 14:39:56 +00:00
Cesy Avon
259c1e1731 Fix #476 show edited and posted date on posts and comments 2016-06-02 14:37:33 +00:00
Daniel O'Connor
870aa674b0 Upgrade autoprefixer-rails to current 2016-06-03 00:07:28 +09:30
Daniel O'Connor
b1ab319bf7 Update mime-types-data to current 2016-06-03 00:05:46 +09:30
Daniel O'Connor
857422719a Upgrade byebug to current 2016-06-03 00:04:09 +09:30
Daniel O'Connor
235314bc13 Upgrade js-routes to current 2016-06-03 00:03:48 +09:30
Daniel O'Connor
ac1cd88ae1 Upgrade kaminari to current 2016-06-03 00:01:38 +09:30
Daniel O'Connor
f93ea3c0a1 Upgrade httparty to current 2016-06-03 00:00:08 +09:30
Cesy Avon
0075040aab Fix #875 edited date on comments on homepage 2016-06-02 13:56:03 +00:00
Cesy
f9d51e623c Merge pull request #959 from pozorvlak/remove_capfile
Remove the Capistrano configuration file
2016-06-02 14:13:46 +01:00
Miles Gould
dcd36dcd67 Remove the Capistrano configuration file
Finishes work started in 93e468876d.
2016-06-02 13:45:53 +01:00
Cesy
608a921fce Merge pull request #956 from CloCkWeRX/upgrade_devise4
Upgrade to devise 4.0.*
2016-06-02 10:18:29 +01:00
pozorvlak
4bedf1e6ac Merge pull request #955 from CloCkWeRX/upgrade_geocoder
Swap to geocoder 1.1.9
2016-06-02 09:49:04 +01:00
pozorvlak
ac14c310f6 Merge pull request #954 from CloCkWeRX/ruby_231
Bump to Ruby 2.3.1
2016-06-02 09:48:20 +01:00
Daniel O'Connor
f979da315a Explicitly swap from config.email_regexp = /\A[^@\s]+@([^@\s]+\.)+[^@\W]+\z/
to the new default
2016-06-02 13:50:25 +09:30
Daniel O'Connor
a74ef7de6b Upgrade to devise 4.0.3 2016-06-02 13:49:13 +09:30
Daniel O'Connor
10064121a6 #953 Swap to geocoder 1.1.9; which is the release just after what we had previously pinned 2016-06-02 13:30:42 +09:30
Daniel O'Connor
f8a1ef6066 #952 Try ruby 2.3.1 2016-06-02 13:14:10 +09:30
Daniel O'Connor
40d7b11d90 #952 Try ruby 2.3.1 2016-06-02 13:13:52 +09:30
pozorvlak
fbf5164eca Merge pull request #937 from dv2/dev
Add test coverage for Seeds Helper
2016-06-01 16:33:38 +01:00
DV Dasari
21d86a8c2d improve the test descriptions to be more readable 2016-06-01 09:46:22 -05:00
DV Dasari
848c7e117b Add test coverage for Seeds Helper 2016-06-01 09:46:22 -05:00
Cesy
369868672b Merge pull request #942 from pozorvlak/fix_coverage_calculations
Fix coverage calculations
2016-06-01 15:41:42 +01:00
Daniel O'Connor
321f3517a7 #920 Deal with .nil quantity of a seed in rendering helper 2016-06-01 23:26:43 +09:30
Miles Gould
6dd7ec9f95 Fix coverage calculations
SimpleCov was reporting 0% coverage for any files loaded before
`SimpleCov.start` was called, even if they were fully tested. This patch
loads the application *after* starting SimpleCov, leading to more
accurate coverage figures.
2016-06-01 14:54:13 +01:00
Mackenzie Morgan
8648db5518 create SimpleShow concern for models where show's json call is simple 2016-05-25 13:37:01 -04:00
374 changed files with 555 additions and 52463 deletions

View File

@@ -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

View File

@@ -1 +1 @@
2.2.4
2.3.1

View File

@@ -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

View File

@@ -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)

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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).

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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
|

View File

@@ -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

View File

@@ -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

View File

@@ -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|

View File

@@ -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

View File

@@ -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" })

View File

@@ -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|

View File

@@ -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

View File

@@ -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(", ")

View File

@@ -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'

View File

@@ -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

View File

@@ -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'

View File

@@ -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).

View File

@@ -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

View File

@@ -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 }

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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 = ''

View File

@@ -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

View File

@@ -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

View File

@@ -179,6 +179,9 @@ en:
crop:
one: "crop"
other: "crops"
follow:
one: "follow"
other: "follows"
garden:
one: "garden"
other: "gardens"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View File

@@ -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'

View File

@@ -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

View File

View File

View File

@@ -1,5 +0,0 @@
pkg/*
*.gem
.bundle
.DS_Store
Gemfile.lock

View File

@@ -1,3 +0,0 @@
source "http://rubygems.org"
gemspec

View File

@@ -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.

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,3 +0,0 @@
module ActiveUtils
VERSION = "1.0.5"
end

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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)

View File

@@ -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.

View File

@@ -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.
[![Build Status](https://secure.travis-ci.org/Shopify/active_merchant.png)](http://travis-ci.org/Shopify/active_merchant)
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/Shopify/active_merchant)

View File

@@ -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-----

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 merchants 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 customers 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 customers credit card information
add_arb_credit_card(xml, options)
# Contains the customers bank account information
add_arb_bank_account(xml, options)
end
end
# Adds customers 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 customers 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 customers 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

View File

@@ -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 merchants 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 customers 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 customers 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 customers 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 customers 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 customers 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

View File

@@ -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

View File

@@ -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