From cad5f453faaffd9b7b79b5724bf6adf9bcf10882 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 7 May 2013 20:56:10 +1000 Subject: [PATCH 001/184] rails g scaffold Product... --- config/routes.rb | 3 +++ db/schema.rb | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index cf757e9af..0b8154a02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Growstuff::Application.routes.draw do + resources :products + + devise_for :members, :controllers => { :registrations => "registrations" } resources :authentications diff --git a/db/schema.rb b/db/schema.rb index 4c82164fe..4577387cb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130418102558) do +ActiveRecord::Schema.define(:version => 20130507105357) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -155,6 +155,14 @@ ActiveRecord::Schema.define(:version => 20130418102558) do add_index "posts", ["created_at", "author_id"], :name => "index_updates_on_created_at_and_user_id" add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true + create_table "products", :force => true do |t| + t.string "name", :null => false + t.string "description", :null => false + t.decimal "min_price", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "roles", :force => true do |t| t.string "name", :null => false t.text "description" From 3eef44cce7c6c5c51bbafebf3a58051b0de50cac Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 7 May 2013 21:00:10 +1000 Subject: [PATCH 002/184] added products to seeds.rb in dev/test environments --- db/seeds.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index 427caf22b..df347f6bd 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -57,7 +57,24 @@ if Rails.env.development? or Rails.env.test? @wrangler_user.confirm! @wrangler_user.roles << @wrangler @wrangler_user.save! - puts "Done!" + puts "Adding products..." + Product.create!( + :name => "Monthly subscription", + :description => "Paid account, renews monthly", + :min_price => 3.00 + ) + Product.create!( + :name => "Annual subscription", + :description => "Paid account, renews yearly", + :min_price => 30.00 + ) + Product.create!( + :name => "Seed account", + :description => "Paid account, in perpetuity", + :min_price => 150.00 + ) end +puts "Done!" + From af38f8574eda7f1fa0fd50904424c741cd2f25dc Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 7 May 2013 20:56:10 +1000 Subject: [PATCH 003/184] rails g scaffold Product... --- app/assets/javascripts/products.js.coffee | 3 + app/controllers/products_controller.rb | 83 +++++++++++ app/helpers/products_helper.rb | 2 + app/models/ability.rb | 4 +- app/models/product.rb | 3 + app/views/products/_form.html.haml | 19 +++ app/views/products/edit.html.haml | 7 + app/views/products/index.html.haml | 23 +++ app/views/products/new.html.haml | 5 + app/views/products/show.html.haml | 15 ++ db/migrate/20130507105357_create_products.rb | 11 ++ spec/controllers/products_controller_spec.rb | 144 +++++++++++++++++++ spec/factories/products.rb | 9 ++ spec/models/product_spec.rb | 5 + spec/requests/products_spec.rb | 11 ++ spec/routing/products_routing_spec.rb | 35 +++++ spec/views/products/edit.html.haml_spec.rb | 22 +++ spec/views/products/index.html.haml_spec.rb | 26 ++++ spec/views/products/new.html.haml_spec.rb | 22 +++ spec/views/products/show.html.haml_spec.rb | 19 +++ 20 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/products.js.coffee create mode 100644 app/controllers/products_controller.rb create mode 100644 app/helpers/products_helper.rb create mode 100644 app/models/product.rb create mode 100644 app/views/products/_form.html.haml create mode 100644 app/views/products/edit.html.haml create mode 100644 app/views/products/index.html.haml create mode 100644 app/views/products/new.html.haml create mode 100644 app/views/products/show.html.haml create mode 100644 db/migrate/20130507105357_create_products.rb create mode 100644 spec/controllers/products_controller_spec.rb create mode 100644 spec/factories/products.rb create mode 100644 spec/models/product_spec.rb create mode 100644 spec/requests/products_spec.rb create mode 100644 spec/routing/products_routing_spec.rb create mode 100644 spec/views/products/edit.html.haml_spec.rb create mode 100644 spec/views/products/index.html.haml_spec.rb create mode 100644 spec/views/products/new.html.haml_spec.rb create mode 100644 spec/views/products/show.html.haml_spec.rb diff --git a/app/assets/javascripts/products.js.coffee b/app/assets/javascripts/products.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/products.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb new file mode 100644 index 000000000..1fbfefc6c --- /dev/null +++ b/app/controllers/products_controller.rb @@ -0,0 +1,83 @@ +class ProductsController < ApplicationController + # GET /products + # GET /products.json + def index + @products = Product.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @products } + end + end + + # GET /products/1 + # GET /products/1.json + def show + @product = Product.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @product } + end + end + + # GET /products/new + # GET /products/new.json + def new + @product = Product.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @product } + end + end + + # GET /products/1/edit + def edit + @product = Product.find(params[:id]) + end + + # POST /products + # POST /products.json + def create + @product = Product.new(params[:product]) + + respond_to do |format| + if @product.save + format.html { redirect_to @product, notice: 'Product was successfully created.' } + format.json { render json: @product, status: :created, location: @product } + else + format.html { render action: "new" } + format.json { render json: @product.errors, status: :unprocessable_entity } + end + end + end + + # PUT /products/1 + # PUT /products/1.json + def update + @product = Product.find(params[:id]) + + respond_to do |format| + if @product.update_attributes(params[:product]) + format.html { redirect_to @product, notice: 'Product was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @product.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /products/1 + # DELETE /products/1.json + def destroy + @product = Product.find(params[:id]) + @product.destroy + + respond_to do |format| + format.html { redirect_to products_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb new file mode 100644 index 000000000..ab5c42b32 --- /dev/null +++ b/app/helpers/products_helper.rb @@ -0,0 +1,2 @@ +module ProductsHelper +end diff --git a/app/models/ability.rb b/app/models/ability.rb index e8ffaf75c..c29f7cbbd 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,12 +6,14 @@ class Ability # everyone can do these things, even non-logged in can :read, :all + + # except these, which don't make sense if you're not logged in cannot :read, Notification - cannot :create, Notification cannot :read, Authentication # nobody should be able to view this except admins cannot :read, Role + cannot :read, Product if member diff --git a/app/models/product.rb b/app/models/product.rb new file mode 100644 index 000000000..9e32d40d5 --- /dev/null +++ b/app/models/product.rb @@ -0,0 +1,3 @@ +class Product < ActiveRecord::Base + attr_accessible :description, :min_price, :name +end diff --git a/app/views/products/_form.html.haml b/app/views/products/_form.html.haml new file mode 100644 index 000000000..8b2909af7 --- /dev/null +++ b/app/views/products/_form.html.haml @@ -0,0 +1,19 @@ += form_for @product do |f| + - if @product.errors.any? + #error_explanation + %h2= "#{pluralize(@product.errors.count, "error")} prohibited this product from being saved:" + %ul + - @product.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :name + = f.text_field :name + .field + = f.label :description + = f.text_field :description + .field + = f.label :min_price + = f.text_field :min_price + .actions + = f.submit 'Save' diff --git a/app/views/products/edit.html.haml b/app/views/products/edit.html.haml new file mode 100644 index 000000000..9c16087ea --- /dev/null +++ b/app/views/products/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing product + += render 'form' + += link_to 'Show', @product +\| += link_to 'Back', products_path diff --git a/app/views/products/index.html.haml b/app/views/products/index.html.haml new file mode 100644 index 000000000..caf508db7 --- /dev/null +++ b/app/views/products/index.html.haml @@ -0,0 +1,23 @@ +%h1 Listing products + +%table + %tr + %th Name + %th Description + %th Min price + %th + %th + %th + + - @products.each do |product| + %tr + %td= product.name + %td= product.description + %td= product.min_price + %td= link_to 'Show', product + %td= link_to 'Edit', edit_product_path(product) + %td= link_to 'Destroy', product, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + += link_to 'New Product', new_product_path diff --git a/app/views/products/new.html.haml b/app/views/products/new.html.haml new file mode 100644 index 000000000..71ed863eb --- /dev/null +++ b/app/views/products/new.html.haml @@ -0,0 +1,5 @@ +%h1 New product + += render 'form' + += link_to 'Back', products_path diff --git a/app/views/products/show.html.haml b/app/views/products/show.html.haml new file mode 100644 index 000000000..03b2e6e08 --- /dev/null +++ b/app/views/products/show.html.haml @@ -0,0 +1,15 @@ +%p#notice= notice + +%p + %b Name: + = @product.name +%p + %b Description: + = @product.description +%p + %b Min price: + = @product.min_price + += link_to 'Edit', edit_product_path(@product) +\| += link_to 'Back', products_path diff --git a/db/migrate/20130507105357_create_products.rb b/db/migrate/20130507105357_create_products.rb new file mode 100644 index 000000000..0ad9b1063 --- /dev/null +++ b/db/migrate/20130507105357_create_products.rb @@ -0,0 +1,11 @@ +class CreateProducts < ActiveRecord::Migration + def change + create_table :products do |t| + t.string :name, :null => false + t.string :description, :null => false + t.decimal :min_price, :null => false + + t.timestamps + end + end +end diff --git a/spec/controllers/products_controller_spec.rb b/spec/controllers/products_controller_spec.rb new file mode 100644 index 000000000..c31c2c9ae --- /dev/null +++ b/spec/controllers/products_controller_spec.rb @@ -0,0 +1,144 @@ +require 'spec_helper' + +describe ProductsController do + + def valid_attributes + { + :name => "product name", + :description => 'some description', + :min_price => 9.99 + } + end + + def valid_session + {} + end + + describe "GET index" do + it "assigns all products as @products" do + product = Product.create! valid_attributes + get :index, {}, valid_session + assigns(:products).should eq([product]) + end + end + + describe "GET show" do + it "assigns the requested product as @product" do + product = Product.create! valid_attributes + get :show, {:id => product.to_param}, valid_session + assigns(:product).should eq(product) + end + end + + describe "GET new" do + it "assigns a new product as @product" do + get :new, {}, valid_session + assigns(:product).should be_a_new(Product) + end + end + + describe "GET edit" do + it "assigns the requested product as @product" do + product = Product.create! valid_attributes + get :edit, {:id => product.to_param}, valid_session + assigns(:product).should eq(product) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new Product" do + expect { + post :create, {:product => valid_attributes}, valid_session + }.to change(Product, :count).by(1) + end + + it "assigns a newly created product as @product" do + post :create, {:product => valid_attributes}, valid_session + assigns(:product).should be_a(Product) + assigns(:product).should be_persisted + end + + it "redirects to the created product" do + post :create, {:product => valid_attributes}, valid_session + response.should redirect_to(Product.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved product as @product" do + # Trigger the behavior that occurs when invalid params are submitted + Product.any_instance.stub(:save).and_return(false) + post :create, {:product => { "name" => "invalid value" }}, valid_session + assigns(:product).should be_a_new(Product) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + Product.any_instance.stub(:save).and_return(false) + post :create, {:product => { "name" => "invalid value" }}, valid_session + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested product" do + product = Product.create! valid_attributes + # Assuming there are no other products in the database, this + # specifies that the Product created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Product.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) + put :update, {:id => product.to_param, :product => { "name" => "MyString" }}, valid_session + end + + it "assigns the requested product as @product" do + product = Product.create! valid_attributes + put :update, {:id => product.to_param, :product => valid_attributes}, valid_session + assigns(:product).should eq(product) + end + + it "redirects to the product" do + product = Product.create! valid_attributes + put :update, {:id => product.to_param, :product => valid_attributes}, valid_session + response.should redirect_to(product) + end + end + + describe "with invalid params" do + it "assigns the product as @product" do + product = Product.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Product.any_instance.stub(:save).and_return(false) + put :update, {:id => product.to_param, :product => { "name" => "invalid value" }}, valid_session + assigns(:product).should eq(product) + end + + it "re-renders the 'edit' template" do + product = Product.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Product.any_instance.stub(:save).and_return(false) + put :update, {:id => product.to_param, :product => { "name" => "invalid value" }}, valid_session + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested product" do + product = Product.create! valid_attributes + expect { + delete :destroy, {:id => product.to_param}, valid_session + }.to change(Product, :count).by(-1) + end + + it "redirects to the products list" do + product = Product.create! valid_attributes + delete :destroy, {:id => product.to_param}, valid_session + response.should redirect_to(products_url) + end + end + +end diff --git a/spec/factories/products.rb b/spec/factories/products.rb new file mode 100644 index 000000000..405924518 --- /dev/null +++ b/spec/factories/products.rb @@ -0,0 +1,9 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :product do + name "annual subscription" + description "paid membership, renewing yearly" + min_price "9.99" + end +end diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb new file mode 100644 index 000000000..267720887 --- /dev/null +++ b/spec/models/product_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Product do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/products_spec.rb b/spec/requests/products_spec.rb new file mode 100644 index 000000000..5618d7115 --- /dev/null +++ b/spec/requests/products_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "Products" do + describe "GET /products" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get products_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/products_routing_spec.rb b/spec/routing/products_routing_spec.rb new file mode 100644 index 000000000..e50a77956 --- /dev/null +++ b/spec/routing/products_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe ProductsController do + describe "routing" do + + it "routes to #index" do + get("/products").should route_to("products#index") + end + + it "routes to #new" do + get("/products/new").should route_to("products#new") + end + + it "routes to #show" do + get("/products/1").should route_to("products#show", :id => "1") + end + + it "routes to #edit" do + get("/products/1/edit").should route_to("products#edit", :id => "1") + end + + it "routes to #create" do + post("/products").should route_to("products#create") + end + + it "routes to #update" do + put("/products/1").should route_to("products#update", :id => "1") + end + + it "routes to #destroy" do + delete("/products/1").should route_to("products#destroy", :id => "1") + end + + end +end diff --git a/spec/views/products/edit.html.haml_spec.rb b/spec/views/products/edit.html.haml_spec.rb new file mode 100644 index 000000000..26cd392c5 --- /dev/null +++ b/spec/views/products/edit.html.haml_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "products/edit" do + before(:each) do + @product = assign(:product, stub_model(Product, + :name => "MyString", + :description => "MyString", + :min_price => "9.99" + )) + end + + it "renders the edit product form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => products_path(@product), :method => "post" do + assert_select "input#product_name", :name => "product[name]" + assert_select "input#product_description", :name => "product[description]" + assert_select "input#product_min_price", :name => "product[min_price]" + end + end +end diff --git a/spec/views/products/index.html.haml_spec.rb b/spec/views/products/index.html.haml_spec.rb new file mode 100644 index 000000000..5f300415a --- /dev/null +++ b/spec/views/products/index.html.haml_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe "products/index" do + before(:each) do + assign(:products, [ + stub_model(Product, + :name => "Name", + :description => "Description", + :min_price => "9.99" + ), + stub_model(Product, + :name => "Name", + :description => "Description", + :min_price => "9.99" + ) + ]) + end + + it "renders a list of products" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => "Name".to_s, :count => 2 + assert_select "tr>td", :text => "Description".to_s, :count => 2 + assert_select "tr>td", :text => "9.99".to_s, :count => 2 + end +end diff --git a/spec/views/products/new.html.haml_spec.rb b/spec/views/products/new.html.haml_spec.rb new file mode 100644 index 000000000..c508a89c5 --- /dev/null +++ b/spec/views/products/new.html.haml_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "products/new" do + before(:each) do + assign(:product, stub_model(Product, + :name => "MyString", + :description => "MyString", + :min_price => "9.99" + ).as_new_record) + end + + it "renders new product form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => products_path, :method => "post" do + assert_select "input#product_name", :name => "product[name]" + assert_select "input#product_description", :name => "product[description]" + assert_select "input#product_min_price", :name => "product[min_price]" + end + end +end diff --git a/spec/views/products/show.html.haml_spec.rb b/spec/views/products/show.html.haml_spec.rb new file mode 100644 index 000000000..b49bbbae2 --- /dev/null +++ b/spec/views/products/show.html.haml_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe "products/show" do + before(:each) do + @product = assign(:product, stub_model(Product, + :name => "Name", + :description => "Description", + :min_price => "9.99" + )) + end + + it "renders attributes in

" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/Name/) + rendered.should match(/Description/) + rendered.should match(/9.99/) + end +end From 245115fd7fd8f380319440a1f1335cdadb95d09b Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 7 May 2013 21:14:08 +1000 Subject: [PATCH 004/184] rails g scaffold Order... --- app/assets/javascripts/orders.js.coffee | 3 + app/controllers/orders_controller.rb | 83 ++++++++++++ app/helpers/orders_helper.rb | 2 + app/models/member.rb | 1 + app/models/order.rb | 4 + app/views/orders/_form.html.haml | 13 ++ app/views/orders/edit.html.haml | 7 + app/views/orders/index.html.haml | 19 +++ app/views/orders/new.html.haml | 5 + app/views/orders/show.html.haml | 9 ++ config/routes.rb | 3 + db/migrate/20130507110411_create_orders.rb | 9 ++ db/schema.rb | 8 +- spec/controllers/orders_controller_spec.rb | 141 +++++++++++++++++++++ spec/factories/orders.rb | 7 + spec/models/order_spec.rb | 5 + spec/requests/orders_spec.rb | 11 ++ spec/routing/orders_routing_spec.rb | 35 +++++ spec/views/orders/edit.html.haml_spec.rb | 18 +++ spec/views/orders/index.html.haml_spec.rb | 20 +++ spec/views/orders/new.html.haml_spec.rb | 18 +++ spec/views/orders/show.html.haml_spec.rb | 15 +++ 22 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/orders.js.coffee create mode 100644 app/controllers/orders_controller.rb create mode 100644 app/helpers/orders_helper.rb create mode 100644 app/models/order.rb create mode 100644 app/views/orders/_form.html.haml create mode 100644 app/views/orders/edit.html.haml create mode 100644 app/views/orders/index.html.haml create mode 100644 app/views/orders/new.html.haml create mode 100644 app/views/orders/show.html.haml create mode 100644 db/migrate/20130507110411_create_orders.rb create mode 100644 spec/controllers/orders_controller_spec.rb create mode 100644 spec/factories/orders.rb create mode 100644 spec/models/order_spec.rb create mode 100644 spec/requests/orders_spec.rb create mode 100644 spec/routing/orders_routing_spec.rb create mode 100644 spec/views/orders/edit.html.haml_spec.rb create mode 100644 spec/views/orders/index.html.haml_spec.rb create mode 100644 spec/views/orders/new.html.haml_spec.rb create mode 100644 spec/views/orders/show.html.haml_spec.rb diff --git a/app/assets/javascripts/orders.js.coffee b/app/assets/javascripts/orders.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/orders.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb new file mode 100644 index 000000000..2268ce821 --- /dev/null +++ b/app/controllers/orders_controller.rb @@ -0,0 +1,83 @@ +class OrdersController < ApplicationController + # GET /orders + # GET /orders.json + def index + @orders = Order.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @orders } + end + end + + # GET /orders/1 + # GET /orders/1.json + def show + @order = Order.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @order } + end + end + + # GET /orders/new + # GET /orders/new.json + def new + @order = Order.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @order } + end + end + + # GET /orders/1/edit + def edit + @order = Order.find(params[:id]) + end + + # POST /orders + # POST /orders.json + def create + @order = Order.new(params[:order]) + + respond_to do |format| + if @order.save + format.html { redirect_to @order, notice: 'Order was successfully created.' } + format.json { render json: @order, status: :created, location: @order } + else + format.html { render action: "new" } + format.json { render json: @order.errors, status: :unprocessable_entity } + end + end + end + + # PUT /orders/1 + # PUT /orders/1.json + def update + @order = Order.find(params[:id]) + + respond_to do |format| + if @order.update_attributes(params[:order]) + format.html { redirect_to @order, notice: 'Order was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @order.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /orders/1 + # DELETE /orders/1.json + def destroy + @order = Order.find(params[:id]) + @order.destroy + + respond_to do |format| + format.html { redirect_to orders_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb new file mode 100644 index 000000000..443227fd4 --- /dev/null +++ b/app/helpers/orders_helper.rb @@ -0,0 +1,2 @@ +module OrdersHelper +end diff --git a/app/models/member.rb b/app/models/member.rb index 3e3790a46..f2eae64ad 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -11,6 +11,7 @@ class Member < ActiveRecord::Base has_many :notifications, :foreign_key => 'recipient_id' has_many :sent_notifications, :foreign_key => 'sender_id' has_many :authentications + has_many :orders default_scope order("lower(login_name) asc") scope :confirmed, where('confirmed_at IS NOT NULL') diff --git a/app/models/order.rb b/app/models/order.rb new file mode 100644 index 000000000..022579351 --- /dev/null +++ b/app/models/order.rb @@ -0,0 +1,4 @@ +class Order < ActiveRecord::Base + attr_accessible :member_id + belongs_to :member +end diff --git a/app/views/orders/_form.html.haml b/app/views/orders/_form.html.haml new file mode 100644 index 000000000..fb4fa9dcb --- /dev/null +++ b/app/views/orders/_form.html.haml @@ -0,0 +1,13 @@ += form_for @order do |f| + - if @order.errors.any? + #error_explanation + %h2= "#{pluralize(@order.errors.count, "error")} prohibited this order from being saved:" + %ul + - @order.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :member_id + = f.text_field :member_id + .actions + = f.submit 'Save' diff --git a/app/views/orders/edit.html.haml b/app/views/orders/edit.html.haml new file mode 100644 index 000000000..9d5b393f0 --- /dev/null +++ b/app/views/orders/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing order + += render 'form' + += link_to 'Show', @order +\| += link_to 'Back', orders_path diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml new file mode 100644 index 000000000..4384b82fc --- /dev/null +++ b/app/views/orders/index.html.haml @@ -0,0 +1,19 @@ +%h1 Listing orders + +%table + %tr + %th Member + %th + %th + %th + + - @orders.each do |order| + %tr + %td= order.member_id + %td= link_to 'Show', order + %td= link_to 'Edit', edit_order_path(order) + %td= link_to 'Destroy', order, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + += link_to 'New Order', new_order_path diff --git a/app/views/orders/new.html.haml b/app/views/orders/new.html.haml new file mode 100644 index 000000000..42e0872a8 --- /dev/null +++ b/app/views/orders/new.html.haml @@ -0,0 +1,5 @@ +%h1 New order + += render 'form' + += link_to 'Back', orders_path diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml new file mode 100644 index 000000000..9c484a59d --- /dev/null +++ b/app/views/orders/show.html.haml @@ -0,0 +1,9 @@ +%p#notice= notice + +%p + %b Member: + = @order.member_id + += link_to 'Edit', edit_order_path(@order) +\| += link_to 'Back', orders_path diff --git a/config/routes.rb b/config/routes.rb index 0b8154a02..24a6b14b2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Growstuff::Application.routes.draw do + resources :orders + + resources :products diff --git a/db/migrate/20130507110411_create_orders.rb b/db/migrate/20130507110411_create_orders.rb new file mode 100644 index 000000000..0aed92dfb --- /dev/null +++ b/db/migrate/20130507110411_create_orders.rb @@ -0,0 +1,9 @@ +class CreateOrders < ActiveRecord::Migration + def change + create_table :orders do |t| + t.string :member_id, :null => false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 4577387cb..bf7df1f06 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130507105357) do +ActiveRecord::Schema.define(:version => 20130507110411) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -120,6 +120,12 @@ ActiveRecord::Schema.define(:version => 20130507105357) do t.datetime "updated_at", :null => false end + create_table "orders", :force => true do |t| + t.string "member_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "payments", :force => true do |t| t.integer "payer_id" t.string "payment_type" diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb new file mode 100644 index 000000000..d9d5e8406 --- /dev/null +++ b/spec/controllers/orders_controller_spec.rb @@ -0,0 +1,141 @@ +require 'spec_helper' + + +describe OrdersController do + + def valid_attributes + { "member_id" => "MyString" } + end + + def valid_session + {} + end + + describe "GET index" do + it "assigns all orders as @orders" do + order = Order.create! valid_attributes + get :index, {}, valid_session + assigns(:orders).should eq([order]) + end + end + + describe "GET show" do + it "assigns the requested order as @order" do + order = Order.create! valid_attributes + get :show, {:id => order.to_param}, valid_session + assigns(:order).should eq(order) + end + end + + describe "GET new" do + it "assigns a new order as @order" do + get :new, {}, valid_session + assigns(:order).should be_a_new(Order) + end + end + + describe "GET edit" do + it "assigns the requested order as @order" do + order = Order.create! valid_attributes + get :edit, {:id => order.to_param}, valid_session + assigns(:order).should eq(order) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new Order" do + expect { + post :create, {:order => valid_attributes}, valid_session + }.to change(Order, :count).by(1) + end + + it "assigns a newly created order as @order" do + post :create, {:order => valid_attributes}, valid_session + assigns(:order).should be_a(Order) + assigns(:order).should be_persisted + end + + it "redirects to the created order" do + post :create, {:order => valid_attributes}, valid_session + response.should redirect_to(Order.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved order as @order" do + # Trigger the behavior that occurs when invalid params are submitted + Order.any_instance.stub(:save).and_return(false) + post :create, {:order => { "member_id" => "invalid value" }}, valid_session + assigns(:order).should be_a_new(Order) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + Order.any_instance.stub(:save).and_return(false) + post :create, {:order => { "member_id" => "invalid value" }}, valid_session + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested order" do + order = Order.create! valid_attributes + # Assuming there are no other orders in the database, this + # specifies that the Order created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Order.any_instance.should_receive(:update_attributes).with({ "member_id" => "MyString" }) + put :update, {:id => order.to_param, :order => { "member_id" => "MyString" }}, valid_session + end + + it "assigns the requested order as @order" do + order = Order.create! valid_attributes + put :update, {:id => order.to_param, :order => valid_attributes}, valid_session + assigns(:order).should eq(order) + end + + it "redirects to the order" do + order = Order.create! valid_attributes + put :update, {:id => order.to_param, :order => valid_attributes}, valid_session + response.should redirect_to(order) + end + end + + describe "with invalid params" do + it "assigns the order as @order" do + order = Order.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Order.any_instance.stub(:save).and_return(false) + put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }}, valid_session + assigns(:order).should eq(order) + end + + it "re-renders the 'edit' template" do + order = Order.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Order.any_instance.stub(:save).and_return(false) + put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }}, valid_session + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested order" do + order = Order.create! valid_attributes + expect { + delete :destroy, {:id => order.to_param}, valid_session + }.to change(Order, :count).by(-1) + end + + it "redirects to the orders list" do + order = Order.create! valid_attributes + delete :destroy, {:id => order.to_param}, valid_session + response.should redirect_to(orders_url) + end + end + +end diff --git a/spec/factories/orders.rb b/spec/factories/orders.rb new file mode 100644 index 000000000..19ea6ba3b --- /dev/null +++ b/spec/factories/orders.rb @@ -0,0 +1,7 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :order do + member + end +end diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb new file mode 100644 index 000000000..9b13d51ee --- /dev/null +++ b/spec/models/order_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Order do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/orders_spec.rb b/spec/requests/orders_spec.rb new file mode 100644 index 000000000..0ab48154e --- /dev/null +++ b/spec/requests/orders_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "Orders" do + describe "GET /orders" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get orders_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/orders_routing_spec.rb b/spec/routing/orders_routing_spec.rb new file mode 100644 index 000000000..77e2c131d --- /dev/null +++ b/spec/routing/orders_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe OrdersController do + describe "routing" do + + it "routes to #index" do + get("/orders").should route_to("orders#index") + end + + it "routes to #new" do + get("/orders/new").should route_to("orders#new") + end + + it "routes to #show" do + get("/orders/1").should route_to("orders#show", :id => "1") + end + + it "routes to #edit" do + get("/orders/1/edit").should route_to("orders#edit", :id => "1") + end + + it "routes to #create" do + post("/orders").should route_to("orders#create") + end + + it "routes to #update" do + put("/orders/1").should route_to("orders#update", :id => "1") + end + + it "routes to #destroy" do + delete("/orders/1").should route_to("orders#destroy", :id => "1") + end + + end +end diff --git a/spec/views/orders/edit.html.haml_spec.rb b/spec/views/orders/edit.html.haml_spec.rb new file mode 100644 index 000000000..f46e402d5 --- /dev/null +++ b/spec/views/orders/edit.html.haml_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe "orders/edit" do + before(:each) do + @order = assign(:order, stub_model(Order, + :member_id => "MyString" + )) + end + + it "renders the edit order form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => orders_path(@order), :method => "post" do + assert_select "input#order_member_id", :name => "order[member_id]" + end + end +end diff --git a/spec/views/orders/index.html.haml_spec.rb b/spec/views/orders/index.html.haml_spec.rb new file mode 100644 index 000000000..23c957236 --- /dev/null +++ b/spec/views/orders/index.html.haml_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "orders/index" do + before(:each) do + assign(:orders, [ + stub_model(Order, + :member_id => "Member" + ), + stub_model(Order, + :member_id => "Member" + ) + ]) + end + + it "renders a list of orders" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => "Member".to_s, :count => 2 + end +end diff --git a/spec/views/orders/new.html.haml_spec.rb b/spec/views/orders/new.html.haml_spec.rb new file mode 100644 index 000000000..3e81fc519 --- /dev/null +++ b/spec/views/orders/new.html.haml_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe "orders/new" do + before(:each) do + assign(:order, stub_model(Order, + :member_id => "MyString" + ).as_new_record) + end + + it "renders new order form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => orders_path, :method => "post" do + assert_select "input#order_member_id", :name => "order[member_id]" + end + end +end diff --git a/spec/views/orders/show.html.haml_spec.rb b/spec/views/orders/show.html.haml_spec.rb new file mode 100644 index 000000000..e342fee26 --- /dev/null +++ b/spec/views/orders/show.html.haml_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe "orders/show" do + before(:each) do + @order = assign(:order, stub_model(Order, + :member_id => "Member" + )) + end + + it "renders attributes in

" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/Member/) + end +end From 26d16b9d862805f5ebcad94e43312ec351ef2ba9 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 7 May 2013 21:24:26 +1000 Subject: [PATCH 005/184] only admins can mess with products/orders for now --- app/controllers/orders_controller.rb | 1 + app/controllers/products_controller.rb | 1 + app/models/ability.rb | 5 ++ spec/models/ability_spec.rb | 83 ++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 2268ce821..27ad4c62e 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -1,4 +1,5 @@ class OrdersController < ApplicationController + load_and_authorize_resource # GET /orders # GET /orders.json def index diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb index 1fbfefc6c..dc3ea516f 100644 --- a/app/controllers/products_controller.rb +++ b/app/controllers/products_controller.rb @@ -1,4 +1,5 @@ class ProductsController < ApplicationController + load_and_authorize_resource # GET /products # GET /products.json def index diff --git a/app/models/ability.rb b/app/models/ability.rb index c29f7cbbd..c9ba053a5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -14,6 +14,7 @@ class Ability # nobody should be able to view this except admins cannot :read, Role cannot :read, Product + cannot :read, Order if member @@ -24,6 +25,10 @@ class Ability # for now, only admins can create/edit forums can :manage, Forum + + # admins can manage products and orders + can :manage, Product + can :manage, Order # for now end # managing your own user settings diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 04d710ef5..5f944d8fe 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -78,4 +78,87 @@ describe Ability do end end + context "products" do + + before(:each) do + @product = FactoryGirl.create(:product) + end + + context "standard member" do + it "can't read or manage products" do + @ability.should_not be_able_to(:read, @product) + @ability.should_not be_able_to(:create, Product) + @ability.should_not be_able_to(:update, @product) + @ability.should_not be_able_to(:destroy, @product) + end + + end + + context "admin" do + before(:each) do + @role = FactoryGirl.create(:admin) + @member.roles << @role + @admin_ability = Ability.new(@member) + end + + it "has admin role" do + @member.has_role?(:admin).should be true + end + + it "can read products" do + @admin_ability.should be_able_to(:read, @product) + end + it "can create products" do + @admin_ability.should be_able_to(:create, Product) + end + it "can update products" do + @admin_ability.should be_able_to(:update, @product) + end + it "can destroy products" do + @admin_ability.should be_able_to(:destroy, @product) + end + end + end + + context "orders" do + + before(:each) do + @order = FactoryGirl.create(:order) + end + + context "standard member" do + it "can't read or manage orders" do + @ability.should_not be_able_to(:read, @order) + @ability.should_not be_able_to(:create, Order) + @ability.should_not be_able_to(:update, @order) + @ability.should_not be_able_to(:destroy, @order) + end + + end + + context "admin" do + before(:each) do + @role = FactoryGirl.create(:admin) + @member.roles << @role + @admin_ability = Ability.new(@member) + end + + it "has admin role" do + @member.has_role?(:admin).should be true + end + + it "can read orders" do + @admin_ability.should be_able_to(:read, @order) + end + it "can create orders" do + @admin_ability.should be_able_to(:create, Order) + end + it "can update orders" do + @admin_ability.should be_able_to(:update, @order) + end + it "can destroy orders" do + @admin_ability.should be_able_to(:destroy, @order) + end + end + end end From 2d444423e4459831bc599554292d865995b9d982 Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 7 May 2013 22:09:48 +1000 Subject: [PATCH 006/184] moved member post feed to HAML --- app/views/members/show.html.haml | 2 +- app/views/members/show.rss.builder | 18 ------------------ app/views/members/show.rss.haml | 17 +++++++++++++++++ ...s.builder_spec.rb => show.rss.haml_spec.rb} | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) delete mode 100644 app/views/members/show.rss.builder create mode 100644 app/views/members/show.rss.haml rename spec/views/members/{show.rss.builder_spec.rb => show.rss.haml_spec.rb} (89%) diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index 297bac8c7..f84db801b 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -66,7 +66,7 @@ %div :markdown - #{g.description} + #{ strip_tags g.description } %h3 What's planted here? - g.featured_plantings.each do |p| diff --git a/app/views/members/show.rss.builder b/app/views/members/show.rss.builder deleted file mode 100644 index 69a0ebba4..000000000 --- a/app/views/members/show.rss.builder +++ /dev/null @@ -1,18 +0,0 @@ -xml.instruct! :xml, :version => "1.0" -xml.rss :version => "2.0" do - xml.channel do - xml.title "#{Growstuff::Application.config.site_name} - #{@member.login_name}'s recent posts" - xml.link member_url(@member) - - for post in @posts - xml.item do - xml.author @member.login_name - xml.title post.subject - xml.description post.body - xml.pubDate post.created_at.to_s(:rfc822) - xml.link post_url(post) - xml.guid post_url(post) - end - end - end -end diff --git a/app/views/members/show.rss.haml b/app/views/members/show.rss.haml new file mode 100644 index 000000000..f88f44db4 --- /dev/null +++ b/app/views/members/show.rss.haml @@ -0,0 +1,17 @@ + +%feed{:version => "2.0", "xmlns:atom"=>"http://www.w3.org/2005/Atom"} + %channel + %title #{Growstuff::Application.config.site_name} - #{@member.login_name}'s recent posts + %atom:link= member_url(@member) + %atom:link{ :type => "application/rss+xml", :href => member_url(@member), :rel => "self" } + + - @posts.each do |post| + %entry + %updated post.created_at.to_s(:rfc822) + %link{ :href => post_url(post) } + %guid post_url(post) + %author @member.login_name + %title post.subject + %description + :markdown + #{ strip_tags post.body } diff --git a/spec/views/members/show.rss.builder_spec.rb b/spec/views/members/show.rss.haml_spec.rb similarity index 89% rename from spec/views/members/show.rss.builder_spec.rb rename to spec/views/members/show.rss.haml_spec.rb index 320229e3a..a9a73686a 100644 --- a/spec/views/members/show.rss.builder_spec.rb +++ b/spec/views/members/show.rss.haml_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'members/show.rss.builder', :type => "view" do +describe 'members/show.rss.haml', :type => "view" do before(:each) do @member = assign(:member, FactoryGirl.create(:member)) assign(:posts, [ From 42cfa17b7efdf0a9077929768982d5e79937dbac Mon Sep 17 00:00:00 2001 From: Skud Date: Tue, 7 May 2013 21:42:58 +1000 Subject: [PATCH 007/184] order HABTM product --- app/models/order.rb | 2 ++ app/models/product.rb | 1 + .../20130507113915_add_orders_products_table.rb | 8 ++++++++ db/schema.rb | 7 ++++++- spec/models/order_spec.rb | 11 ++++++++++- 5 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20130507113915_add_orders_products_table.rb diff --git a/app/models/order.rb b/app/models/order.rb index 022579351..ae1af4da3 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -1,4 +1,6 @@ class Order < ActiveRecord::Base attr_accessible :member_id belongs_to :member + + has_and_belongs_to_many :products end diff --git a/app/models/product.rb b/app/models/product.rb index 9e32d40d5..cc20fe696 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -1,3 +1,4 @@ class Product < ActiveRecord::Base attr_accessible :description, :min_price, :name + has_and_belongs_to_many :orders end diff --git a/db/migrate/20130507113915_add_orders_products_table.rb b/db/migrate/20130507113915_add_orders_products_table.rb new file mode 100644 index 000000000..92af21427 --- /dev/null +++ b/db/migrate/20130507113915_add_orders_products_table.rb @@ -0,0 +1,8 @@ +class AddOrdersProductsTable < ActiveRecord::Migration + def change + create_table :orders_products, :id => false do |t| + t.integer :order_id + t.integer :product_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index bf7df1f06..1a49dfa34 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130507110411) do +ActiveRecord::Schema.define(:version => 20130507113915) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -126,6 +126,11 @@ ActiveRecord::Schema.define(:version => 20130507110411) do t.datetime "updated_at", :null => false end + create_table "orders_products", :id => false, :force => true do |t| + t.integer "order_id" + t.integer "product_id" + end + create_table "payments", :force => true do |t| t.integer "payer_id" t.string "payment_type" diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index 9b13d51ee..eac421c21 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -1,5 +1,14 @@ require 'spec_helper' describe Order do - pending "add some examples to (or delete) #{__FILE__}" + before(:each) do + @order = FactoryGirl.create(:order) + @product = FactoryGirl.create(:product) + @order.products << @product + end + + it 'has products' do + @order.products.first.should eq @product + end + end From 42ea14a8e5246e10db417e5f03df7b739d4939f9 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 14:02:33 +1000 Subject: [PATCH 008/184] added shop page at /shop --- app/controllers/shop_controller.rb | 2 ++ app/views/shop/index.html.haml | 2 ++ config/routes.rb | 4 ++-- spec/views/shop/index_spec.rb | 8 ++++++++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 app/controllers/shop_controller.rb create mode 100644 app/views/shop/index.html.haml create mode 100644 spec/views/shop/index_spec.rb diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb new file mode 100644 index 000000000..85e2e465b --- /dev/null +++ b/app/controllers/shop_controller.rb @@ -0,0 +1,2 @@ +class ShopController < ApplicationController +end diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml new file mode 100644 index 000000000..da1f41c0a --- /dev/null +++ b/app/views/shop/index.html.haml @@ -0,0 +1,2 @@ +-content_for :title, 'Shop' + diff --git a/config/routes.rb b/config/routes.rb index 24a6b14b2..42a68df17 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,10 +2,8 @@ Growstuff::Application.routes.draw do resources :orders - resources :products - devise_for :members, :controllers => { :registrations => "registrations" } resources :authentications @@ -88,5 +86,7 @@ Growstuff::Application.routes.draw do match '/support/:action' => 'support#:action' match '/about' => 'about#index' match '/about/:action' => 'about#:action' + match '/shop' => 'shop#index' + match '/shop/:action' => 'shop#:action' end diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb new file mode 100644 index 000000000..c2f306fd7 --- /dev/null +++ b/spec/views/shop/index_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +describe 'shop/index.html.haml', :type => "view" do + before(:each) do + render + end + +end From 5c96bf15412ab6efa92805bf4e883395c4a2cc88 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 14:50:11 +1000 Subject: [PATCH 009/184] added basic index page for shop --- app/controllers/shop_controller.rb | 6 ++++ app/views/shop/index.html.haml | 25 ++++++++++++++ config/environments/development.rb | 1 + config/environments/production.rb | 1 + config/environments/staging.rb | 1 + config/environments/test.rb | 1 + config/routes.rb | 6 ++-- spec/controllers/orders_controller_spec.rb | 35 ++++++++++---------- spec/controllers/products_controller_spec.rb | 34 ++++++++++--------- spec/controllers/shop_controller_spec.rb | 17 ++++++++++ spec/requests/orders_spec.rb | 11 ------ spec/requests/products_spec.rb | 11 ------ spec/views/shop/index_spec.rb | 11 ++++++ 13 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 spec/controllers/shop_controller_spec.rb delete mode 100644 spec/requests/orders_spec.rb delete mode 100644 spec/requests/products_spec.rb diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 85e2e465b..50c5c67a0 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -1,2 +1,8 @@ class ShopController < ApplicationController + def index + @products = Product.all + respond_to do |format| + format.html # index.html.haml + end + end end diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index da1f41c0a..409373444 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -1,2 +1,27 @@ -content_for :title, 'Shop' +%p + Growstuff relies on your support to build and run this open source + platform for food growers. + +%p + We offer paid accounts which have access to exclusive features. You can + buy a monthly, yearly, or permanent ("seed") account. + +%p + All our accounts are priced on a sliding scale. You can choose how much + you want to pay. Remember, your subscription supports an open source + platform promoting sustainable lifestyles! + +- @products.each do |p| + %h2= p.name + + %div + :markdown + #{ strip_tags p.description } + + %div + Base price: + #{ number_with_precision(p.min_price, :precision => 2) } + #{ Growstuff::Application.config.currency } + diff --git a/config/environments/development.rb b/config/environments/development.rb index 1ee8742f6..76d4a5ded 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -51,5 +51,6 @@ Growstuff::Application.configure do Growstuff::Application.configure do config.site_name = "Growstuff (dev)" config.analytics_code = '' + config.currency = 'AUD' end end diff --git a/config/environments/production.rb b/config/environments/production.rb index 03d206851..a97654cc6 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -85,6 +85,7 @@ Growstuff::Application.configure do

Clicky

eos + config.currency = 'AUD' end end diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 1ad079660..1085a2ef0 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -81,6 +81,7 @@ Growstuff::Application.configure do Growstuff::Application.configure do config.site_name = "Growstuff (staging)" config.analytics_code = '' + config.currency = 'AUD' end end diff --git a/config/environments/test.rb b/config/environments/test.rb index 5f48eccfa..57e8c5d1a 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -43,6 +43,7 @@ Growstuff::Application.configure do Growstuff::Application.configure do config.site_name = "Growstuff (test)" config.analytics_code = '' + config.currency = 'AUD' end end diff --git a/config/routes.rb b/config/routes.rb index 42a68df17..0ed446792 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,9 +1,5 @@ Growstuff::Application.routes.draw do - resources :orders - - resources :products - devise_for :members, :controllers => { :registrations => "registrations" } resources :authentications @@ -17,6 +13,8 @@ Growstuff::Application.routes.draw do resources :roles resources :forums resources :notifications + resources :orders + resources :products get "home/index" diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb index d9d5e8406..40da86b21 100644 --- a/spec/controllers/orders_controller_spec.rb +++ b/spec/controllers/orders_controller_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' - describe OrdersController do + login_member(:admin_member) + def valid_attributes { "member_id" => "MyString" } end @@ -14,7 +15,7 @@ describe OrdersController do describe "GET index" do it "assigns all orders as @orders" do order = Order.create! valid_attributes - get :index, {}, valid_session + get :index, {} assigns(:orders).should eq([order]) end end @@ -22,14 +23,14 @@ describe OrdersController do describe "GET show" do it "assigns the requested order as @order" do order = Order.create! valid_attributes - get :show, {:id => order.to_param}, valid_session + get :show, {:id => order.to_param} assigns(:order).should eq(order) end end describe "GET new" do it "assigns a new order as @order" do - get :new, {}, valid_session + get :new, {} assigns(:order).should be_a_new(Order) end end @@ -37,7 +38,7 @@ describe OrdersController do describe "GET edit" do it "assigns the requested order as @order" do order = Order.create! valid_attributes - get :edit, {:id => order.to_param}, valid_session + get :edit, {:id => order.to_param} assigns(:order).should eq(order) end end @@ -46,18 +47,18 @@ describe OrdersController do describe "with valid params" do it "creates a new Order" do expect { - post :create, {:order => valid_attributes}, valid_session + post :create, {:order => valid_attributes} }.to change(Order, :count).by(1) end it "assigns a newly created order as @order" do - post :create, {:order => valid_attributes}, valid_session + post :create, {:order => valid_attributes} assigns(:order).should be_a(Order) assigns(:order).should be_persisted end it "redirects to the created order" do - post :create, {:order => valid_attributes}, valid_session + post :create, {:order => valid_attributes} response.should redirect_to(Order.last) end end @@ -66,14 +67,14 @@ describe OrdersController do it "assigns a newly created but unsaved order as @order" do # Trigger the behavior that occurs when invalid params are submitted Order.any_instance.stub(:save).and_return(false) - post :create, {:order => { "member_id" => "invalid value" }}, valid_session + post :create, {:order => { "member_id" => "invalid value" }} assigns(:order).should be_a_new(Order) end it "re-renders the 'new' template" do # Trigger the behavior that occurs when invalid params are submitted Order.any_instance.stub(:save).and_return(false) - post :create, {:order => { "member_id" => "invalid value" }}, valid_session + post :create, {:order => { "member_id" => "invalid value" }} response.should render_template("new") end end @@ -88,18 +89,18 @@ describe OrdersController do # receives the :update_attributes message with whatever params are # submitted in the request. Order.any_instance.should_receive(:update_attributes).with({ "member_id" => "MyString" }) - put :update, {:id => order.to_param, :order => { "member_id" => "MyString" }}, valid_session + put :update, {:id => order.to_param, :order => { "member_id" => "MyString" }} end it "assigns the requested order as @order" do order = Order.create! valid_attributes - put :update, {:id => order.to_param, :order => valid_attributes}, valid_session + put :update, {:id => order.to_param, :order => valid_attributes} assigns(:order).should eq(order) end it "redirects to the order" do order = Order.create! valid_attributes - put :update, {:id => order.to_param, :order => valid_attributes}, valid_session + put :update, {:id => order.to_param, :order => valid_attributes} response.should redirect_to(order) end end @@ -109,7 +110,7 @@ describe OrdersController do order = Order.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted Order.any_instance.stub(:save).and_return(false) - put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }}, valid_session + put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }} assigns(:order).should eq(order) end @@ -117,7 +118,7 @@ describe OrdersController do order = Order.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted Order.any_instance.stub(:save).and_return(false) - put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }}, valid_session + put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }} response.should render_template("edit") end end @@ -127,13 +128,13 @@ describe OrdersController do it "destroys the requested order" do order = Order.create! valid_attributes expect { - delete :destroy, {:id => order.to_param}, valid_session + delete :destroy, {:id => order.to_param} }.to change(Order, :count).by(-1) end it "redirects to the orders list" do order = Order.create! valid_attributes - delete :destroy, {:id => order.to_param}, valid_session + delete :destroy, {:id => order.to_param} response.should redirect_to(orders_url) end end diff --git a/spec/controllers/products_controller_spec.rb b/spec/controllers/products_controller_spec.rb index c31c2c9ae..13f77f195 100644 --- a/spec/controllers/products_controller_spec.rb +++ b/spec/controllers/products_controller_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe ProductsController do + login_member(:admin_member) + def valid_attributes { :name => "product name", @@ -17,7 +19,7 @@ describe ProductsController do describe "GET index" do it "assigns all products as @products" do product = Product.create! valid_attributes - get :index, {}, valid_session + get :index, {} assigns(:products).should eq([product]) end end @@ -25,14 +27,14 @@ describe ProductsController do describe "GET show" do it "assigns the requested product as @product" do product = Product.create! valid_attributes - get :show, {:id => product.to_param}, valid_session + get :show, {:id => product.to_param} assigns(:product).should eq(product) end end describe "GET new" do it "assigns a new product as @product" do - get :new, {}, valid_session + get :new, {} assigns(:product).should be_a_new(Product) end end @@ -40,7 +42,7 @@ describe ProductsController do describe "GET edit" do it "assigns the requested product as @product" do product = Product.create! valid_attributes - get :edit, {:id => product.to_param}, valid_session + get :edit, {:id => product.to_param} assigns(:product).should eq(product) end end @@ -49,18 +51,18 @@ describe ProductsController do describe "with valid params" do it "creates a new Product" do expect { - post :create, {:product => valid_attributes}, valid_session + post :create, {:product => valid_attributes} }.to change(Product, :count).by(1) end it "assigns a newly created product as @product" do - post :create, {:product => valid_attributes}, valid_session + post :create, {:product => valid_attributes} assigns(:product).should be_a(Product) assigns(:product).should be_persisted end it "redirects to the created product" do - post :create, {:product => valid_attributes}, valid_session + post :create, {:product => valid_attributes} response.should redirect_to(Product.last) end end @@ -69,14 +71,14 @@ describe ProductsController do it "assigns a newly created but unsaved product as @product" do # Trigger the behavior that occurs when invalid params are submitted Product.any_instance.stub(:save).and_return(false) - post :create, {:product => { "name" => "invalid value" }}, valid_session + post :create, {:product => { "name" => "invalid value" }} assigns(:product).should be_a_new(Product) end it "re-renders the 'new' template" do # Trigger the behavior that occurs when invalid params are submitted Product.any_instance.stub(:save).and_return(false) - post :create, {:product => { "name" => "invalid value" }}, valid_session + post :create, {:product => { "name" => "invalid value" }} response.should render_template("new") end end @@ -91,18 +93,18 @@ describe ProductsController do # receives the :update_attributes message with whatever params are # submitted in the request. Product.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) - put :update, {:id => product.to_param, :product => { "name" => "MyString" }}, valid_session + put :update, {:id => product.to_param, :product => { "name" => "MyString" }} end it "assigns the requested product as @product" do product = Product.create! valid_attributes - put :update, {:id => product.to_param, :product => valid_attributes}, valid_session + put :update, {:id => product.to_param, :product => valid_attributes} assigns(:product).should eq(product) end it "redirects to the product" do product = Product.create! valid_attributes - put :update, {:id => product.to_param, :product => valid_attributes}, valid_session + put :update, {:id => product.to_param, :product => valid_attributes} response.should redirect_to(product) end end @@ -112,7 +114,7 @@ describe ProductsController do product = Product.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted Product.any_instance.stub(:save).and_return(false) - put :update, {:id => product.to_param, :product => { "name" => "invalid value" }}, valid_session + put :update, {:id => product.to_param, :product => { "name" => "invalid value" }} assigns(:product).should eq(product) end @@ -120,7 +122,7 @@ describe ProductsController do product = Product.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted Product.any_instance.stub(:save).and_return(false) - put :update, {:id => product.to_param, :product => { "name" => "invalid value" }}, valid_session + put :update, {:id => product.to_param, :product => { "name" => "invalid value" }} response.should render_template("edit") end end @@ -130,13 +132,13 @@ describe ProductsController do it "destroys the requested product" do product = Product.create! valid_attributes expect { - delete :destroy, {:id => product.to_param}, valid_session + delete :destroy, {:id => product.to_param} }.to change(Product, :count).by(-1) end it "redirects to the products list" do product = Product.create! valid_attributes - delete :destroy, {:id => product.to_param}, valid_session + delete :destroy, {:id => product.to_param} response.should redirect_to(products_url) end end diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb new file mode 100644 index 000000000..fad178cc6 --- /dev/null +++ b/spec/controllers/shop_controller_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe ShopController do + + before :each do + @product1 = FactoryGirl.create(:product) + @product2 = FactoryGirl.create(:product) + end + + describe "GET index" do + it "assigns all products as @products" do + get :index, {} + assigns(:products).should eq([@product1, @product2]) + end + end + +end diff --git a/spec/requests/orders_spec.rb b/spec/requests/orders_spec.rb deleted file mode 100644 index 0ab48154e..000000000 --- a/spec/requests/orders_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'spec_helper' - -describe "Orders" do - describe "GET /orders" do - it "works! (now write some real specs)" do - # Run the generator again with the --webrat flag if you want to use webrat methods/matchers - get orders_path - response.status.should be(200) - end - end -end diff --git a/spec/requests/products_spec.rb b/spec/requests/products_spec.rb deleted file mode 100644 index 5618d7115..000000000 --- a/spec/requests/products_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'spec_helper' - -describe "Products" do - describe "GET /products" do - it "works! (now write some real specs)" do - # Run the generator again with the --webrat flag if you want to use webrat methods/matchers - get products_path - response.status.should be(200) - end - end -end diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index c2f306fd7..d6dce7065 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -2,7 +2,18 @@ require 'spec_helper' describe 'shop/index.html.haml', :type => "view" do before(:each) do + @product1 = FactoryGirl.create(:product) + @product2 = FactoryGirl.create(:product) + assign(:products, [@product1, @product2]) render end + it 'shows products' do + assert_select("h2", :text => @product1.name) + end + + it 'shows prices in AUD' do + rendered.should contain '9.99 AUD' + end + end From 85a121c73935d7bbb35f8059365636a8600230a9 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 15:05:55 +1000 Subject: [PATCH 010/184] only let signed in members buy stuff --- app/models/ability.rb | 6 ++++++ app/views/shop/index.html.haml | 10 ++++++++++ spec/models/ability_spec.rb | 8 ++++---- spec/views/shop/index_spec.rb | 32 +++++++++++++++++++++++++++----- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index c9ba053a5..f31a1e06e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -72,6 +72,12 @@ class Ability can :create, Planting can :update, Planting, :garden => { :owner_id => member.id } can :destroy, Planting, :garden => { :owner_id => member.id } + + # orders/shop/etc + can :create, Order + can :read, Order, :member_id => member.id + can :update, Order, :member_id => member.id + end end end diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 409373444..66bad7280 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -25,3 +25,13 @@ #{ number_with_precision(p.min_price, :precision => 2) } #{ Growstuff::Application.config.currency } + %div + - if can? :create, Order + %p This is the form for buying a product. + - else + Please + =link_to "sign in", new_member_session_path + or + =link_to "sign up", new_member_registration_path + to purchase. + diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 5f944d8fe..4a798074f 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -123,14 +123,14 @@ describe Ability do context "orders" do before(:each) do - @order = FactoryGirl.create(:order) + @order = FactoryGirl.create(:order, :member => @member) end context "standard member" do it "can't read or manage orders" do - @ability.should_not be_able_to(:read, @order) - @ability.should_not be_able_to(:create, Order) - @ability.should_not be_able_to(:update, @order) + @ability.should be_able_to(:read, @order) + @ability.should be_able_to(:create, Order) + @ability.should be_able_to(:update, @order) @ability.should_not be_able_to(:destroy, @order) end diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index d6dce7065..1e7cbf071 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -5,15 +5,37 @@ describe 'shop/index.html.haml', :type => "view" do @product1 = FactoryGirl.create(:product) @product2 = FactoryGirl.create(:product) assign(:products, [@product1, @product2]) - render end - it 'shows products' do - assert_select("h2", :text => @product1.name) + context "signed in" do + before(:each) do + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + render + end + + it 'shows products' do + assert_select("h2", :text => @product1.name) + end + + it 'shows prices in AUD' do + rendered.should contain '9.99 AUD' + end + + it 'displays the order form' do + rendered.should contain 'This is the form' + end end - it 'shows prices in AUD' do - rendered.should contain '9.99 AUD' + context "signed out" do + before(:each) do + controller.stub(:current_user) { nil } + render + end + + it "tells you to sign up/sign in" do + rendered.should contain "sign in or sign up" + end end end From ddfafa032a3641909210f77d9824f9f29fdcdec4 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 15:11:20 +1000 Subject: [PATCH 011/184] added completed_at field to Order --- app/models/order.rb | 2 ++ db/schema.rb | 9 +++++---- spec/models/order_spec.rb | 5 +++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index ae1af4da3..853f7bdc0 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -3,4 +3,6 @@ class Order < ActiveRecord::Base belongs_to :member has_and_belongs_to_many :products + + default_scope order('created_at DESC') end diff --git a/db/schema.rb b/db/schema.rb index 1a49dfa34..090be1b27 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130507113915) do +ActiveRecord::Schema.define(:version => 20130508050711) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -121,9 +121,10 @@ ActiveRecord::Schema.define(:version => 20130507113915) do end create_table "orders", :force => true do |t| - t.string "member_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.string "member_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.datetime "completed_at" end create_table "orders_products", :id => false, :force => true do |t| diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index eac421c21..6a0cc6103 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -11,4 +11,9 @@ describe Order do @order.products.first.should eq @product end + it 'sorts by created_at DESC' do + @order2 = FactoryGirl.create(:order) + Order.all.should eq [@order2, @order] + end + end From 2563787dc7f22732ee3890453696d4bcd870ce61 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 15:39:50 +1000 Subject: [PATCH 012/184] added Member.current_order method --- app/models/member.rb | 5 +++++ app/models/order.rb | 2 +- .../20130508050711_add_completed_to_order.rb | 5 +++++ spec/factories/orders.rb | 3 +++ spec/models/member_spec.rb | 16 ++++++++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20130508050711_add_completed_to_order.rb diff --git a/app/models/member.rb b/app/models/member.rb index f2eae64ad..19c43f3b8 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -86,6 +86,10 @@ class Member < ActiveRecord::Base roles.any? { |r| r.name.gsub(/\s+/, "_").underscore.to_sym == role_sym } end + def current_order + orders.where(:completed_at => nil).first + end + protected def empty_unwanted_geocodes if self.location.to_s == '' @@ -94,4 +98,5 @@ class Member < ActiveRecord::Base end end + end diff --git a/app/models/order.rb b/app/models/order.rb index 853f7bdc0..9e86912a5 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -1,5 +1,5 @@ class Order < ActiveRecord::Base - attr_accessible :member_id + attr_accessible :member_id, :completed_at belongs_to :member has_and_belongs_to_many :products diff --git a/db/migrate/20130508050711_add_completed_to_order.rb b/db/migrate/20130508050711_add_completed_to_order.rb new file mode 100644 index 000000000..bcaffebbf --- /dev/null +++ b/db/migrate/20130508050711_add_completed_to_order.rb @@ -0,0 +1,5 @@ +class AddCompletedToOrder < ActiveRecord::Migration + def change + add_column :orders, :completed_at, :datetime + end +end diff --git a/spec/factories/orders.rb b/spec/factories/orders.rb index 19ea6ba3b..34e357daf 100644 --- a/spec/factories/orders.rb +++ b/spec/factories/orders.rb @@ -3,5 +3,8 @@ FactoryGirl.define do factory :order do member + factory :completed_order do + completed_at '2013-05-08 01:01:01' + end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index cd5decf14..7037ce904 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -235,4 +235,20 @@ describe 'member' do end end + context 'orders' do + it 'finds the current order' do + @member = FactoryGirl.create(:member) + @order1 = FactoryGirl.create(:completed_order, :member => @member) + @order2 = FactoryGirl.create(:order, :member => @member) + @member.current_order.should eq @order2 + end + + it "copes if there's no current order" do + @member = FactoryGirl.create(:member) + @order1 = FactoryGirl.create(:completed_order, :member => @member) + @order2 = FactoryGirl.create(:completed_order, :member => @member) + @member.current_order.should be_nil + end + end + end From 5ae5e0f9594b7938a546bceac59f30069131b2a4 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 20:52:35 +1000 Subject: [PATCH 013/184] rails g scaffold Photo... --- app/assets/javascripts/photos.js.coffee | 3 + app/controllers/photos_controller.rb | 83 ++++++++++++ app/helpers/photos_helper.rb | 2 + app/models/member.rb | 1 + app/models/photo.rb | 4 + app/views/photos/_form.html.haml | 22 ++++ app/views/photos/edit.html.haml | 7 + app/views/photos/index.html.haml | 25 ++++ app/views/photos/new.html.haml | 5 + app/views/photos/show.html.haml | 18 +++ config/routes.rb | 3 + db/migrate/20130508104506_create_photos.rb | 12 ++ db/schema.rb | 11 +- spec/controllers/photos_controller_spec.rb | 146 +++++++++++++++++++++ spec/factories/photos.rb | 10 ++ spec/helpers/photos_helper_spec.rb | 15 +++ spec/models/photo_spec.rb | 5 + spec/requests/photos_spec.rb | 11 ++ spec/routing/photos_routing_spec.rb | 35 +++++ spec/views/photos/edit.html.haml_spec.rb | 24 ++++ spec/views/photos/index.html.haml_spec.rb | 29 ++++ spec/views/photos/new.html.haml_spec.rb | 24 ++++ spec/views/photos/show.html.haml_spec.rb | 21 +++ 23 files changed, 515 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/photos.js.coffee create mode 100644 app/controllers/photos_controller.rb create mode 100644 app/helpers/photos_helper.rb create mode 100644 app/models/photo.rb create mode 100644 app/views/photos/_form.html.haml create mode 100644 app/views/photos/edit.html.haml create mode 100644 app/views/photos/index.html.haml create mode 100644 app/views/photos/new.html.haml create mode 100644 app/views/photos/show.html.haml create mode 100644 db/migrate/20130508104506_create_photos.rb create mode 100644 spec/controllers/photos_controller_spec.rb create mode 100644 spec/factories/photos.rb create mode 100644 spec/helpers/photos_helper_spec.rb create mode 100644 spec/models/photo_spec.rb create mode 100644 spec/requests/photos_spec.rb create mode 100644 spec/routing/photos_routing_spec.rb create mode 100644 spec/views/photos/edit.html.haml_spec.rb create mode 100644 spec/views/photos/index.html.haml_spec.rb create mode 100644 spec/views/photos/new.html.haml_spec.rb create mode 100644 spec/views/photos/show.html.haml_spec.rb diff --git a/app/assets/javascripts/photos.js.coffee b/app/assets/javascripts/photos.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/photos.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb new file mode 100644 index 000000000..b5ac8c9f7 --- /dev/null +++ b/app/controllers/photos_controller.rb @@ -0,0 +1,83 @@ +class PhotosController < ApplicationController + # GET /photos + # GET /photos.json + def index + @photos = Photo.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @photos } + end + end + + # GET /photos/1 + # GET /photos/1.json + def show + @photo = Photo.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @photo } + end + end + + # GET /photos/new + # GET /photos/new.json + def new + @photo = Photo.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @photo } + end + end + + # GET /photos/1/edit + def edit + @photo = Photo.find(params[:id]) + end + + # POST /photos + # POST /photos.json + def create + @photo = Photo.new(params[:photo]) + + respond_to do |format| + if @photo.save + format.html { redirect_to @photo, notice: 'Photo was successfully created.' } + format.json { render json: @photo, status: :created, location: @photo } + else + format.html { render action: "new" } + format.json { render json: @photo.errors, status: :unprocessable_entity } + end + end + end + + # PUT /photos/1 + # PUT /photos/1.json + def update + @photo = Photo.find(params[:id]) + + respond_to do |format| + if @photo.update_attributes(params[:photo]) + format.html { redirect_to @photo, notice: 'Photo was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @photo.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /photos/1 + # DELETE /photos/1.json + def destroy + @photo = Photo.find(params[:id]) + @photo.destroy + + respond_to do |format| + format.html { redirect_to photos_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/photos_helper.rb b/app/helpers/photos_helper.rb new file mode 100644 index 000000000..0a10d472b --- /dev/null +++ b/app/helpers/photos_helper.rb @@ -0,0 +1,2 @@ +module PhotosHelper +end diff --git a/app/models/member.rb b/app/models/member.rb index 3e3790a46..95863bb73 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -11,6 +11,7 @@ class Member < ActiveRecord::Base has_many :notifications, :foreign_key => 'recipient_id' has_many :sent_notifications, :foreign_key => 'sender_id' has_many :authentications + has_many :photos default_scope order("lower(login_name) asc") scope :confirmed, where('confirmed_at IS NOT NULL') diff --git a/app/models/photo.rb b/app/models/photo.rb new file mode 100644 index 000000000..24e3cc581 --- /dev/null +++ b/app/models/photo.rb @@ -0,0 +1,4 @@ +class Photo < ActiveRecord::Base + attr_accessible :flickr_photo_id, :fullsize_url, :owner_id, :thumbnail_url + belongs_to :owner, :class_name => 'Member' +end diff --git a/app/views/photos/_form.html.haml b/app/views/photos/_form.html.haml new file mode 100644 index 000000000..8745c195b --- /dev/null +++ b/app/views/photos/_form.html.haml @@ -0,0 +1,22 @@ += form_for @photo do |f| + - if @photo.errors.any? + #error_explanation + %h2= "#{pluralize(@photo.errors.count, "error")} prohibited this photo from being saved:" + %ul + - @photo.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :owner_id + = f.number_field :owner_id + .field + = f.label :flickr_photo_id + = f.number_field :flickr_photo_id + .field + = f.label :thumbnail_url + = f.text_field :thumbnail_url + .field + = f.label :fullsize_url + = f.text_field :fullsize_url + .actions + = f.submit 'Save' diff --git a/app/views/photos/edit.html.haml b/app/views/photos/edit.html.haml new file mode 100644 index 000000000..73440055c --- /dev/null +++ b/app/views/photos/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing photo + += render 'form' + += link_to 'Show', @photo +\| += link_to 'Back', photos_path diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml new file mode 100644 index 000000000..3f16c5677 --- /dev/null +++ b/app/views/photos/index.html.haml @@ -0,0 +1,25 @@ +%h1 Listing photos + +%table + %tr + %th Owner + %th Flickr photo + %th Thumbnail url + %th Fullsize url + %th + %th + %th + + - @photos.each do |photo| + %tr + %td= photo.owner_id + %td= photo.flickr_photo_id + %td= photo.thumbnail_url + %td= photo.fullsize_url + %td= link_to 'Show', photo + %td= link_to 'Edit', edit_photo_path(photo) + %td= link_to 'Destroy', photo, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + += link_to 'New Photo', new_photo_path diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml new file mode 100644 index 000000000..9506ac3ce --- /dev/null +++ b/app/views/photos/new.html.haml @@ -0,0 +1,5 @@ +%h1 New photo + += render 'form' + += link_to 'Back', photos_path diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml new file mode 100644 index 000000000..093aaedec --- /dev/null +++ b/app/views/photos/show.html.haml @@ -0,0 +1,18 @@ +%p#notice= notice + +%p + %b Owner: + = @photo.owner_id +%p + %b Flickr photo: + = @photo.flickr_photo_id +%p + %b Thumbnail url: + = @photo.thumbnail_url +%p + %b Fullsize url: + = @photo.fullsize_url + += link_to 'Edit', edit_photo_path(@photo) +\| += link_to 'Back', photos_path diff --git a/config/routes.rb b/config/routes.rb index cf757e9af..2398cc988 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Growstuff::Application.routes.draw do + resources :photos + + devise_for :members, :controllers => { :registrations => "registrations" } resources :authentications diff --git a/db/migrate/20130508104506_create_photos.rb b/db/migrate/20130508104506_create_photos.rb new file mode 100644 index 000000000..f7c56188d --- /dev/null +++ b/db/migrate/20130508104506_create_photos.rb @@ -0,0 +1,12 @@ +class CreatePhotos < ActiveRecord::Migration + def change + create_table :photos do |t| + t.integer :owner_id, :null => false + t.integer :flickr_photo_id, :null => false + t.string :thumbnail_url, :null => false + t.string :fullsize_url, :null => false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 090be1b27..e6810afb1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130508050711) do +ActiveRecord::Schema.define(:version => 20130508104506) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -140,6 +140,15 @@ ActiveRecord::Schema.define(:version => 20130508050711) do t.datetime "updated_at", :null => false end + create_table "photos", :force => true do |t| + t.integer "owner_id", :null => false + t.integer "flickr_photo_id", :null => false + t.string "thumbnail_url", :null => false + t.string "fullsize_url", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "plantings", :force => true do |t| t.integer "garden_id", :null => false t.integer "crop_id", :null => false diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb new file mode 100644 index 000000000..b1c74fb75 --- /dev/null +++ b/spec/controllers/photos_controller_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper' + + +describe PhotosController do + + def valid_attributes + { + "owner_id" => "1", + "flickr_photo_id" => 1, + "thumbnail_url" => 'http://example.com', + "fullsize_url" => 'http://example.com' + } + end + + def valid_session + {} + end + + describe "GET index" do + it "assigns all photos as @photos" do + photo = Photo.create! valid_attributes + get :index, {}, valid_session + assigns(:photos).should eq([photo]) + end + end + + describe "GET show" do + it "assigns the requested photo as @photo" do + photo = Photo.create! valid_attributes + get :show, {:id => photo.to_param}, valid_session + assigns(:photo).should eq(photo) + end + end + + describe "GET new" do + it "assigns a new photo as @photo" do + get :new, {}, valid_session + assigns(:photo).should be_a_new(Photo) + end + end + + describe "GET edit" do + it "assigns the requested photo as @photo" do + photo = Photo.create! valid_attributes + get :edit, {:id => photo.to_param}, valid_session + assigns(:photo).should eq(photo) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new Photo" do + expect { + post :create, {:photo => valid_attributes}, valid_session + }.to change(Photo, :count).by(1) + end + + it "assigns a newly created photo as @photo" do + post :create, {:photo => valid_attributes}, valid_session + assigns(:photo).should be_a(Photo) + assigns(:photo).should be_persisted + end + + it "redirects to the created photo" do + post :create, {:photo => valid_attributes}, valid_session + response.should redirect_to(Photo.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved photo as @photo" do + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + post :create, {:photo => { "owner_id" => "invalid value" }}, valid_session + assigns(:photo).should be_a_new(Photo) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + post :create, {:photo => { "owner_id" => "invalid value" }}, valid_session + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested photo" do + photo = Photo.create! valid_attributes + # Assuming there are no other photos in the database, this + # specifies that the Photo created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Photo.any_instance.should_receive(:update_attributes).with({ "owner_id" => "1" }) + put :update, {:id => photo.to_param, :photo => { "owner_id" => "1" }}, valid_session + end + + it "assigns the requested photo as @photo" do + photo = Photo.create! valid_attributes + put :update, {:id => photo.to_param, :photo => valid_attributes}, valid_session + assigns(:photo).should eq(photo) + end + + it "redirects to the photo" do + photo = Photo.create! valid_attributes + put :update, {:id => photo.to_param, :photo => valid_attributes}, valid_session + response.should redirect_to(photo) + end + end + + describe "with invalid params" do + it "assigns the photo as @photo" do + photo = Photo.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }}, valid_session + assigns(:photo).should eq(photo) + end + + it "re-renders the 'edit' template" do + photo = Photo.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }}, valid_session + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested photo" do + photo = Photo.create! valid_attributes + expect { + delete :destroy, {:id => photo.to_param}, valid_session + }.to change(Photo, :count).by(-1) + end + + it "redirects to the photos list" do + photo = Photo.create! valid_attributes + delete :destroy, {:id => photo.to_param}, valid_session + response.should redirect_to(photos_url) + end + end + +end diff --git a/spec/factories/photos.rb b/spec/factories/photos.rb new file mode 100644 index 000000000..409d2bdf0 --- /dev/null +++ b/spec/factories/photos.rb @@ -0,0 +1,10 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :photo do + owner_id 1 + flickr_photo_id 1 + thumbnail_url "MyString" + fullsize_url "MyString" + end +end diff --git a/spec/helpers/photos_helper_spec.rb b/spec/helpers/photos_helper_spec.rb new file mode 100644 index 000000000..c4b452740 --- /dev/null +++ b/spec/helpers/photos_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the PhotosHelper. For example: +# +# describe PhotosHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe PhotosHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb new file mode 100644 index 000000000..0cfe452fe --- /dev/null +++ b/spec/models/photo_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Photo do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/photos_spec.rb b/spec/requests/photos_spec.rb new file mode 100644 index 000000000..bfb595245 --- /dev/null +++ b/spec/requests/photos_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "Photos" do + describe "GET /photos" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get photos_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/photos_routing_spec.rb b/spec/routing/photos_routing_spec.rb new file mode 100644 index 000000000..d7dc394ed --- /dev/null +++ b/spec/routing/photos_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe PhotosController do + describe "routing" do + + it "routes to #index" do + get("/photos").should route_to("photos#index") + end + + it "routes to #new" do + get("/photos/new").should route_to("photos#new") + end + + it "routes to #show" do + get("/photos/1").should route_to("photos#show", :id => "1") + end + + it "routes to #edit" do + get("/photos/1/edit").should route_to("photos#edit", :id => "1") + end + + it "routes to #create" do + post("/photos").should route_to("photos#create") + end + + it "routes to #update" do + put("/photos/1").should route_to("photos#update", :id => "1") + end + + it "routes to #destroy" do + delete("/photos/1").should route_to("photos#destroy", :id => "1") + end + + end +end diff --git a/spec/views/photos/edit.html.haml_spec.rb b/spec/views/photos/edit.html.haml_spec.rb new file mode 100644 index 000000000..fa4c72673 --- /dev/null +++ b/spec/views/photos/edit.html.haml_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe "photos/edit" do + before(:each) do + @photo = assign(:photo, stub_model(Photo, + :owner_id => 1, + :flickr_photo_id => 1, + :thumbnail_url => "MyString", + :fullsize_url => "MyString" + )) + end + + it "renders the edit photo form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => photos_path(@photo), :method => "post" do + assert_select "input#photo_owner_id", :name => "photo[owner_id]" + assert_select "input#photo_flickr_photo_id", :name => "photo[flickr_photo_id]" + assert_select "input#photo_thumbnail_url", :name => "photo[thumbnail_url]" + assert_select "input#photo_fullsize_url", :name => "photo[fullsize_url]" + end + end +end diff --git a/spec/views/photos/index.html.haml_spec.rb b/spec/views/photos/index.html.haml_spec.rb new file mode 100644 index 000000000..8dc87e540 --- /dev/null +++ b/spec/views/photos/index.html.haml_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe "photos/index" do + before(:each) do + assign(:photos, [ + stub_model(Photo, + :owner_id => 1, + :flickr_photo_id => 2, + :thumbnail_url => "Thumbnail Url", + :fullsize_url => "Fullsize Url" + ), + stub_model(Photo, + :owner_id => 1, + :flickr_photo_id => 2, + :thumbnail_url => "Thumbnail Url", + :fullsize_url => "Fullsize Url" + ) + ]) + end + + it "renders a list of photos" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => 1.to_s, :count => 2 + assert_select "tr>td", :text => 2.to_s, :count => 2 + assert_select "tr>td", :text => "Thumbnail Url".to_s, :count => 2 + assert_select "tr>td", :text => "Fullsize Url".to_s, :count => 2 + end +end diff --git a/spec/views/photos/new.html.haml_spec.rb b/spec/views/photos/new.html.haml_spec.rb new file mode 100644 index 000000000..fdf65d126 --- /dev/null +++ b/spec/views/photos/new.html.haml_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe "photos/new" do + before(:each) do + assign(:photo, stub_model(Photo, + :owner_id => 1, + :flickr_photo_id => 1, + :thumbnail_url => "MyString", + :fullsize_url => "MyString" + ).as_new_record) + end + + it "renders new photo form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => photos_path, :method => "post" do + assert_select "input#photo_owner_id", :name => "photo[owner_id]" + assert_select "input#photo_flickr_photo_id", :name => "photo[flickr_photo_id]" + assert_select "input#photo_thumbnail_url", :name => "photo[thumbnail_url]" + assert_select "input#photo_fullsize_url", :name => "photo[fullsize_url]" + end + end +end diff --git a/spec/views/photos/show.html.haml_spec.rb b/spec/views/photos/show.html.haml_spec.rb new file mode 100644 index 000000000..0eb478998 --- /dev/null +++ b/spec/views/photos/show.html.haml_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe "photos/show" do + before(:each) do + @photo = assign(:photo, stub_model(Photo, + :owner_id => 1, + :flickr_photo_id => 2, + :thumbnail_url => "Thumbnail Url", + :fullsize_url => "Fullsize Url" + )) + end + + it "renders attributes in

" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/1/) + rendered.should match(/2/) + rendered.should match(/Thumbnail Url/) + rendered.should match(/Fullsize Url/) + end +end From ffb5d6e7f81d5ebb9392125532d4364d55d11352 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 20:54:48 +1000 Subject: [PATCH 014/184] only members can manage photos --- app/controllers/photos_controller.rb | 1 + app/models/ability.rb | 4 +++ spec/controllers/photos_controller_spec.rb | 35 +++++++++++----------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index b5ac8c9f7..8a1df7ed0 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -1,4 +1,5 @@ class PhotosController < ApplicationController + load_and_authorize_resource # GET /photos # GET /photos.json def index diff --git a/app/models/ability.rb b/app/models/ability.rb index e8ffaf75c..d128c4717 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -65,6 +65,10 @@ class Ability can :create, Planting can :update, Planting, :garden => { :owner_id => member.id } can :destroy, Planting, :garden => { :owner_id => member.id } + + can :create, Photo + can :update, Photo, :owner_id => member.id + can :destroy, Photo, :owner_id => member.id end end end diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index b1c74fb75..7f0c3bbfc 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' - describe PhotosController do + login_member + def valid_attributes { "owner_id" => "1", @@ -19,7 +20,7 @@ describe PhotosController do describe "GET index" do it "assigns all photos as @photos" do photo = Photo.create! valid_attributes - get :index, {}, valid_session + get :index, {} assigns(:photos).should eq([photo]) end end @@ -27,14 +28,14 @@ describe PhotosController do describe "GET show" do it "assigns the requested photo as @photo" do photo = Photo.create! valid_attributes - get :show, {:id => photo.to_param}, valid_session + get :show, {:id => photo.to_param} assigns(:photo).should eq(photo) end end describe "GET new" do it "assigns a new photo as @photo" do - get :new, {}, valid_session + get :new, {} assigns(:photo).should be_a_new(Photo) end end @@ -42,7 +43,7 @@ describe PhotosController do describe "GET edit" do it "assigns the requested photo as @photo" do photo = Photo.create! valid_attributes - get :edit, {:id => photo.to_param}, valid_session + get :edit, {:id => photo.to_param} assigns(:photo).should eq(photo) end end @@ -51,18 +52,18 @@ describe PhotosController do describe "with valid params" do it "creates a new Photo" do expect { - post :create, {:photo => valid_attributes}, valid_session + post :create, {:photo => valid_attributes} }.to change(Photo, :count).by(1) end it "assigns a newly created photo as @photo" do - post :create, {:photo => valid_attributes}, valid_session + post :create, {:photo => valid_attributes} assigns(:photo).should be_a(Photo) assigns(:photo).should be_persisted end it "redirects to the created photo" do - post :create, {:photo => valid_attributes}, valid_session + post :create, {:photo => valid_attributes} response.should redirect_to(Photo.last) end end @@ -71,14 +72,14 @@ describe PhotosController do it "assigns a newly created but unsaved photo as @photo" do # Trigger the behavior that occurs when invalid params are submitted Photo.any_instance.stub(:save).and_return(false) - post :create, {:photo => { "owner_id" => "invalid value" }}, valid_session + post :create, {:photo => { "owner_id" => "invalid value" }} assigns(:photo).should be_a_new(Photo) end it "re-renders the 'new' template" do # Trigger the behavior that occurs when invalid params are submitted Photo.any_instance.stub(:save).and_return(false) - post :create, {:photo => { "owner_id" => "invalid value" }}, valid_session + post :create, {:photo => { "owner_id" => "invalid value" }} response.should render_template("new") end end @@ -93,18 +94,18 @@ describe PhotosController do # receives the :update_attributes message with whatever params are # submitted in the request. Photo.any_instance.should_receive(:update_attributes).with({ "owner_id" => "1" }) - put :update, {:id => photo.to_param, :photo => { "owner_id" => "1" }}, valid_session + put :update, {:id => photo.to_param, :photo => { "owner_id" => "1" }} end it "assigns the requested photo as @photo" do photo = Photo.create! valid_attributes - put :update, {:id => photo.to_param, :photo => valid_attributes}, valid_session + put :update, {:id => photo.to_param, :photo => valid_attributes} assigns(:photo).should eq(photo) end it "redirects to the photo" do photo = Photo.create! valid_attributes - put :update, {:id => photo.to_param, :photo => valid_attributes}, valid_session + put :update, {:id => photo.to_param, :photo => valid_attributes} response.should redirect_to(photo) end end @@ -114,7 +115,7 @@ describe PhotosController do photo = Photo.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted Photo.any_instance.stub(:save).and_return(false) - put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }}, valid_session + put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }} assigns(:photo).should eq(photo) end @@ -122,7 +123,7 @@ describe PhotosController do photo = Photo.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted Photo.any_instance.stub(:save).and_return(false) - put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }}, valid_session + put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }} response.should render_template("edit") end end @@ -132,13 +133,13 @@ describe PhotosController do it "destroys the requested photo" do photo = Photo.create! valid_attributes expect { - delete :destroy, {:id => photo.to_param}, valid_session + delete :destroy, {:id => photo.to_param} }.to change(Photo, :count).by(-1) end it "redirects to the photos list" do photo = Photo.create! valid_attributes - delete :destroy, {:id => photo.to_param}, valid_session + delete :destroy, {:id => photo.to_param} response.should redirect_to(photos_url) end end From acf164990d15dcaa26c4a620d68276a9bec220a1 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 21:17:03 +1000 Subject: [PATCH 015/184] tell user to connect to flickr --- app/controllers/authentications_controller.rb | 2 +- app/controllers/photos_controller.rb | 1 + app/views/photos/new.html.haml | 11 ++++++++--- spec/controllers/photos_controller_spec.rb | 8 ++++++++ spec/views/photos/new.html.haml_spec.rb | 10 ++++------ 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/controllers/authentications_controller.rb b/app/controllers/authentications_controller.rb index 96456d996..b4e376dca 100644 --- a/app/controllers/authentications_controller.rb +++ b/app/controllers/authentications_controller.rb @@ -37,7 +37,7 @@ class AuthenticationsController < ApplicationController else flash[:notice] = "Authentication failed." end - redirect_to edit_member_registration_path + redirect_to request.env['omniauth.origin'] || edit_member_registration_path end # DELETE /authentications/1 diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 8a1df7ed0..d4fc62f5e 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -26,6 +26,7 @@ class PhotosController < ApplicationController # GET /photos/new.json def new @photo = Photo.new + @flickr_auth = current_member.authentications.find_by_provider('flickr') respond_to do |format| format.html # new.html.erb diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 9506ac3ce..c4de9cd39 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -1,5 +1,10 @@ -%h1 New photo +- content_for :title, "New Photo" -= render 'form' +- if @flickr_auth + = render 'form' +- else + .alert + You must + =link_to "connect your account to Flickr", '/auth/flickr' + to add photos. -= link_to 'Back', photos_path diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 7f0c3bbfc..f3432148e 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -34,6 +34,14 @@ describe PhotosController do end describe "GET new" do + it "assigns the flickr auth as @flickr_auth" do + @member = FactoryGirl.create(:member) + sign_in @member + @auth = FactoryGirl.create(:flickr_authentication, :member => @member) + get :new, {} + assigns(:flickr_auth).should be_an_instance_of(Authentication) + end + it "assigns a new photo as @photo" do get :new, {} assigns(:photo).should be_a_new(Photo) diff --git a/spec/views/photos/new.html.haml_spec.rb b/spec/views/photos/new.html.haml_spec.rb index fdf65d126..042a5dbcf 100644 --- a/spec/views/photos/new.html.haml_spec.rb +++ b/spec/views/photos/new.html.haml_spec.rb @@ -2,12 +2,10 @@ require 'spec_helper' describe "photos/new" do before(:each) do - assign(:photo, stub_model(Photo, - :owner_id => 1, - :flickr_photo_id => 1, - :thumbnail_url => "MyString", - :fullsize_url => "MyString" - ).as_new_record) + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + assign(:photo, FactoryGirl.create(:photo)) + assign(:flickr_auth, FactoryGirl.create(:flickr_authentication, :member => @member)) end it "renders new photo form" do From cc6d6633cc8a5af901f119797bc21086bdbcab97 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 8 May 2013 22:01:09 +1000 Subject: [PATCH 016/184] Rough attempt at showing photos from flickr. You can see a list of your recently uploaded Flickr photos at /photos/new, which is nice! However the tests are broken and there's a lot of mess that should be in the model instead of the controller. Committing as-is because I'm tired. --- Gemfile | 1 + Gemfile.lock | 2 ++ app/controllers/photos_controller.rb | 14 ++++++++++++++ app/views/photos/_form.html.haml | 22 ---------------------- app/views/photos/edit.html.haml | 7 +------ app/views/photos/new.html.haml | 22 +++++++++++++++++++++- 6 files changed, 39 insertions(+), 29 deletions(-) delete mode 100644 app/views/photos/_form.html.haml diff --git a/Gemfile b/Gemfile index 3f4318236..6eb4bbca7 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ group :assets do end gem 'jquery-rails' +gem 'flickraw' # To use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 35ab7f28b..6c7782522 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -85,6 +85,7 @@ GEM factory_girl (~> 4.2.0) railties (>= 3.0.0) fastthread (1.0.7) + flickraw (0.9.6) friendly_id (4.0.9) fssm (0.2.10) geocoder (1.1.6) @@ -247,6 +248,7 @@ DEPENDENCIES devise diff-lcs factory_girl_rails (~> 4.0) + flickraw friendly_id geocoder gravatar-ultimate diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index d4fc62f5e..2454ab668 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -26,8 +26,22 @@ class PhotosController < ApplicationController # GET /photos/new.json def new @photo = Photo.new + + # NOTE: we should move a bunch of this into the Member model + @flickr_login = nil @flickr_auth = current_member.authentications.find_by_provider('flickr') + if @flickr_auth + FlickRaw.api_key = ENV['FLICKR_KEY'] + FlickRaw.shared_secret = ENV['FLICKR_SECRET'] + flickr = FlickRaw::Flickr.new + flickr.access_token = @flickr_auth.token + flickr.access_secret = @flickr_auth.secret + @flickr_login = flickr.test.login + + @photos = flickr.people.getPhotos(:user_id => 'me', :per_page => 30) + end + respond_to do |format| format.html # new.html.erb format.json { render json: @photo } diff --git a/app/views/photos/_form.html.haml b/app/views/photos/_form.html.haml deleted file mode 100644 index 8745c195b..000000000 --- a/app/views/photos/_form.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -= form_for @photo do |f| - - if @photo.errors.any? - #error_explanation - %h2= "#{pluralize(@photo.errors.count, "error")} prohibited this photo from being saved:" - %ul - - @photo.errors.full_messages.each do |msg| - %li= msg - - .field - = f.label :owner_id - = f.number_field :owner_id - .field - = f.label :flickr_photo_id - = f.number_field :flickr_photo_id - .field - = f.label :thumbnail_url - = f.text_field :thumbnail_url - .field - = f.label :fullsize_url - = f.text_field :fullsize_url - .actions - = f.submit 'Save' diff --git a/app/views/photos/edit.html.haml b/app/views/photos/edit.html.haml index 73440055c..5a43dd437 100644 --- a/app/views/photos/edit.html.haml +++ b/app/views/photos/edit.html.haml @@ -1,7 +1,2 @@ -%h1 Editing photo +- content_for :title, "Edit Photo" -= render 'form' - -= link_to 'Show', @photo -\| -= link_to 'Back', photos_path diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index c4de9cd39..f23caecdb 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -1,7 +1,27 @@ - content_for :title, "New Photo" - if @flickr_auth - = render 'form' + %p + Connected to Flickr as + = succeed "." do + = link_to @flickr_login.username, "http://flickr.com/photos/#{@flickr_login.id}" + + %p Select a photo from your recent uploads: + + - c = 0 + %ul.thumbnails + .row + - @photos.each do |p| + - c += 1 + %li.span2 + .thumbnail(style='height: 220px') + = image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded') + %p + =p.title + - if (c % 6) == 0 + .row + + - else .alert You must From ce3de63a46a72e8dedd4ad756e91c4bda9c71ad1 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Wed, 8 May 2013 14:59:08 +0100 Subject: [PATCH 017/184] Factor out flickr controller code, for easier stubbing. --- app/controllers/photos_controller.rb | 26 +++++++++++++++------- spec/controllers/photos_controller_spec.rb | 2 ++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 2454ab668..460f8f60c 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -22,6 +22,22 @@ class PhotosController < ApplicationController end end + # I really hate the mixture of side-effects and return values here. + # It's done like this for easier mocking. Hopefully it'll soon be deleted! + def login_to_flickr() + FlickRaw.api_key = ENV['FLICKR_KEY'] + FlickRaw.shared_secret = ENV['FLICKR_SECRET'] + flickr = FlickRaw::Flickr.new + flickr.access_token = @flickr_auth.token + flickr.access_secret = @flickr_auth.secret + @flickr_login = flickr.test.login + return flickr + end + + def get_photos(flickr) + @photos = flickr.people.getPhotos(:user_id => 'me', :per_page => 30) + end + # GET /photos/new # GET /photos/new.json def new @@ -32,14 +48,8 @@ class PhotosController < ApplicationController @flickr_auth = current_member.authentications.find_by_provider('flickr') if @flickr_auth - FlickRaw.api_key = ENV['FLICKR_KEY'] - FlickRaw.shared_secret = ENV['FLICKR_SECRET'] - flickr = FlickRaw::Flickr.new - flickr.access_token = @flickr_auth.token - flickr.access_secret = @flickr_auth.secret - @flickr_login = flickr.test.login - - @photos = flickr.people.getPhotos(:user_id => 'me', :per_page => 30) + flickr = login_to_flickr() + get_photos(flickr) end respond_to do |format| diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index f3432148e..306d758e9 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -38,6 +38,8 @@ describe PhotosController do @member = FactoryGirl.create(:member) sign_in @member @auth = FactoryGirl.create(:flickr_authentication, :member => @member) + subject.stub(:login_to_flickr) + subject.stub(:get_photos) { [FactoryGirl.create(:photo)] } get :new, {} assigns(:flickr_auth).should be_an_instance_of(Authentication) end From 8af105fad73f3774129d38a48b43e144e62cddd5 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Wed, 8 May 2013 15:27:10 +0100 Subject: [PATCH 018/184] Test for photo thumbnails; stub Flickr username/id. --- spec/views/photos/new.html.haml_spec.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/spec/views/photos/new.html.haml_spec.rb b/spec/views/photos/new.html.haml_spec.rb index 042a5dbcf..1657f59cf 100644 --- a/spec/views/photos/new.html.haml_spec.rb +++ b/spec/views/photos/new.html.haml_spec.rb @@ -4,19 +4,16 @@ describe "photos/new" do before(:each) do @member = FactoryGirl.create(:member) controller.stub(:current_user) { @member } - assign(:photo, FactoryGirl.create(:photo)) + flickr_login = double("flickr_login") + flickr_login.stub(:username) { @member.login_name } + flickr_login.stub(:id) { 1337 } + assign(:flickr_login, flickr_login) + assign(:photos, []) assign(:flickr_auth, FactoryGirl.create(:flickr_authentication, :member => @member)) end - it "renders new photo form" do + it "shows a list of photos" do render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => photos_path, :method => "post" do - assert_select "input#photo_owner_id", :name => "photo[owner_id]" - assert_select "input#photo_flickr_photo_id", :name => "photo[flickr_photo_id]" - assert_select "input#photo_thumbnail_url", :name => "photo[thumbnail_url]" - assert_select "input#photo_fullsize_url", :name => "photo[fullsize_url]" - end + assert_select "ul.thumbnails" end end From 2fb8c380199f76b65af756255e5d0a114b0e7a9a Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Wed, 8 May 2013 15:39:17 +0100 Subject: [PATCH 019/184] Removed obsolete test from views/photos/edit We should consider just deleting this action entirely. --- spec/views/photos/edit.html.haml_spec.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/spec/views/photos/edit.html.haml_spec.rb b/spec/views/photos/edit.html.haml_spec.rb index fa4c72673..3b8cde557 100644 --- a/spec/views/photos/edit.html.haml_spec.rb +++ b/spec/views/photos/edit.html.haml_spec.rb @@ -10,15 +10,4 @@ describe "photos/edit" do )) end - it "renders the edit photo form" do - render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => photos_path(@photo), :method => "post" do - assert_select "input#photo_owner_id", :name => "photo[owner_id]" - assert_select "input#photo_flickr_photo_id", :name => "photo[flickr_photo_id]" - assert_select "input#photo_thumbnail_url", :name => "photo[thumbnail_url]" - assert_select "input#photo_fullsize_url", :name => "photo[fullsize_url]" - end - end end From 035946edcd24d8977a790a40fe3616075082afc8 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Wed, 8 May 2013 15:55:32 +0100 Subject: [PATCH 020/184] "auth" helper method in Member --- app/controllers/members_controller.rb | 4 ++-- app/controllers/photos_controller.rb | 2 +- app/controllers/registrations_controller.rb | 4 ++-- app/models/member.rb | 4 ++++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index d05c85f01..846d28f0a 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -12,8 +12,8 @@ class MembersController < ApplicationController def show @member = Member.confirmed.find(params[:id]) - @twitter_auth = @member.authentications.find_by_provider('twitter') - @flickr_auth = @member.authentications.find_by_provider('flickr') + @twitter_auth = @member.auth('twitter') + @flickr_auth = @member.auth('flickr') @posts = @member.posts # The garden form partial is called from the "New Garden" tab; # it requires a garden to be passed in @garden. diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 460f8f60c..1ff5ec057 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -45,7 +45,7 @@ class PhotosController < ApplicationController # NOTE: we should move a bunch of this into the Member model @flickr_login = nil - @flickr_auth = current_member.authentications.find_by_provider('flickr') + @flickr_auth = current_member.auth('flickr') if @flickr_auth flickr = login_to_flickr() diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index e23bebf0c..2be1c9c33 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -2,8 +2,8 @@ class RegistrationsController < Devise::RegistrationsController def edit - @twitter_auth = current_member.authentications.find_by_provider('twitter') - @flickr_auth = current_member.authentications.find_by_provider('flickr') + @twitter_auth = current_member.auth('twitter') + @flickr_auth = current_member.auth('flickr') render "edit" end diff --git a/app/models/member.rb b/app/models/member.rb index 95863bb73..8e6f2b5d3 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -86,6 +86,10 @@ class Member < ActiveRecord::Base roles.any? { |r| r.name.gsub(/\s+/, "_").underscore.to_sym == role_sym } end + def auth(provider) + return authentications.find_by_provider(provider) + end + protected def empty_unwanted_geocodes if self.location.to_s == '' From 016172f0e134d206e252d56581bb8bb4ad8cedb7 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Wed, 8 May 2013 16:23:54 +0100 Subject: [PATCH 021/184] Delete @flickr_login var in photos controller. We didn't need any data from it that couldn't be got from @flickr_auth, and eliminating it makes login_to_flickr less side-effecting and easier to move around. --- app/controllers/photos_controller.rb | 2 -- app/views/photos/new.html.haml | 2 +- spec/views/photos/new.html.haml_spec.rb | 4 ---- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 1ff5ec057..f48d659e2 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -30,7 +30,6 @@ class PhotosController < ApplicationController flickr = FlickRaw::Flickr.new flickr.access_token = @flickr_auth.token flickr.access_secret = @flickr_auth.secret - @flickr_login = flickr.test.login return flickr end @@ -44,7 +43,6 @@ class PhotosController < ApplicationController @photo = Photo.new # NOTE: we should move a bunch of this into the Member model - @flickr_login = nil @flickr_auth = current_member.auth('flickr') if @flickr_auth diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index f23caecdb..e93f99920 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -4,7 +4,7 @@ %p Connected to Flickr as = succeed "." do - = link_to @flickr_login.username, "http://flickr.com/photos/#{@flickr_login.id}" + = link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}" %p Select a photo from your recent uploads: diff --git a/spec/views/photos/new.html.haml_spec.rb b/spec/views/photos/new.html.haml_spec.rb index 1657f59cf..1ea5a34d9 100644 --- a/spec/views/photos/new.html.haml_spec.rb +++ b/spec/views/photos/new.html.haml_spec.rb @@ -4,10 +4,6 @@ describe "photos/new" do before(:each) do @member = FactoryGirl.create(:member) controller.stub(:current_user) { @member } - flickr_login = double("flickr_login") - flickr_login.stub(:username) { @member.login_name } - flickr_login.stub(:id) { 1337 } - assign(:flickr_login, flickr_login) assign(:photos, []) assign(:flickr_auth, FactoryGirl.create(:flickr_authentication, :member => @member)) end From ae785c6f4b2255eb40a6987161969051a73453af Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Wed, 8 May 2013 16:43:30 +0100 Subject: [PATCH 022/184] Move flickr code into Member model. --- app/controllers/photos_controller.rb | 20 +------------------- app/models/member.rb | 18 ++++++++++++++++++ spec/controllers/photos_controller_spec.rb | 4 ++-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index f48d659e2..a99724427 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -22,32 +22,14 @@ class PhotosController < ApplicationController end end - # I really hate the mixture of side-effects and return values here. - # It's done like this for easier mocking. Hopefully it'll soon be deleted! - def login_to_flickr() - FlickRaw.api_key = ENV['FLICKR_KEY'] - FlickRaw.shared_secret = ENV['FLICKR_SECRET'] - flickr = FlickRaw::Flickr.new - flickr.access_token = @flickr_auth.token - flickr.access_secret = @flickr_auth.secret - return flickr - end - - def get_photos(flickr) - @photos = flickr.people.getPhotos(:user_id => 'me', :per_page => 30) - end - # GET /photos/new # GET /photos/new.json def new @photo = Photo.new - # NOTE: we should move a bunch of this into the Member model @flickr_auth = current_member.auth('flickr') - if @flickr_auth - flickr = login_to_flickr() - get_photos(flickr) + @photos = current_member.flickr_photos end respond_to do |format| diff --git a/app/models/member.rb b/app/models/member.rb index 8e6f2b5d3..53f8c0a73 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -90,6 +90,24 @@ class Member < ActiveRecord::Base return authentications.find_by_provider(provider) end + def flickr + if @flickr.nil? + flickr_auth = auth('flickr') + if flickr_auth + FlickRaw.api_key = ENV['FLICKR_KEY'] + FlickRaw.shared_secret = ENV['FLICKR_SECRET'] + @flickr = FlickRaw::Flickr.new + @flickr.access_token = flickr_auth.token + @flickr.access_secret = flickr_auth.secret + end + end + return @flickr + end + + def flickr_photos + return flickr.people.getPhotos(:user_id => 'me', :per_page => 30) + end + protected def empty_unwanted_geocodes if self.location.to_s == '' diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 306d758e9..1d0e8f740 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -37,9 +37,9 @@ describe PhotosController do it "assigns the flickr auth as @flickr_auth" do @member = FactoryGirl.create(:member) sign_in @member + @member.stub(:flickr_photos) { [] } + controller.stub(:current_member) { @member } @auth = FactoryGirl.create(:flickr_authentication, :member => @member) - subject.stub(:login_to_flickr) - subject.stub(:get_photos) { [FactoryGirl.create(:photo)] } get :new, {} assigns(:flickr_auth).should be_an_instance_of(Authentication) end From 6fa94e321a3fa71566cb45db523d21b3baf64926 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Thu, 9 May 2013 13:12:54 +0100 Subject: [PATCH 023/184] Save photos. --- app/controllers/photos_controller.rb | 2 ++ app/models/photo.rb | 17 +++++++++++++++++ app/views/photos/new.html.haml | 2 +- spec/controllers/photos_controller_spec.rb | 11 ++++++++--- spec/models/photo_spec.rb | 11 ++++++++++- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index a99724427..7434c17ae 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -47,6 +47,8 @@ class PhotosController < ApplicationController # POST /photos.json def create @photo = Photo.new(params[:photo]) + @photo.owner_id = current_member.id + @photo.set_flickr_urls respond_to do |format| if @photo.save diff --git a/app/models/photo.rb b/app/models/photo.rb index 24e3cc581..7c80ef53c 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -1,4 +1,21 @@ class Photo < ActiveRecord::Base attr_accessible :flickr_photo_id, :fullsize_url, :owner_id, :thumbnail_url belongs_to :owner, :class_name => 'Member' + + # This is split into a side-effect free method and a side-effecting method + # for easier stubbing and testing. + def flickr_urls + flickr = owner.flickr + info = flickr.photos.getInfo(:photo_id => flickr_photo_id) + thumb = FlickRaw.url_q(info) + full = FlickRaw.url_z(info) + return [thumb, full] + end + + def set_flickr_urls + urls = flickr_urls + self.thumbnail_url = urls[0] + self.fullsize_url = urls[1] + end + end diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index e93f99920..8bfdc7ab9 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -15,7 +15,7 @@ - c += 1 %li.span2 .thumbnail(style='height: 220px') - = image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded') + = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }), :method => :post %p =p.title - if (c % 6) == 0 diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 1d0e8f740..63a638b1c 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -59,21 +59,26 @@ describe PhotosController do end describe "POST create" do + before(:each) do + Photo.any_instance.stub(:flickr_urls).and_return( + ["http://example.com", "http://example.com" ] + ) + end describe "with valid params" do it "creates a new Photo" do expect { - post :create, {:photo => valid_attributes} + post :create, {:photo => { :flickr_photo_id => 1 } } }.to change(Photo, :count).by(1) end it "assigns a newly created photo as @photo" do - post :create, {:photo => valid_attributes} + post :create, {:photo => { :flickr_photo_id => 1 } } assigns(:photo).should be_a(Photo) assigns(:photo).should be_persisted end it "redirects to the created photo" do - post :create, {:photo => valid_attributes} + post :create, {:photo => { :flickr_photo_id => 1 } } response.should redirect_to(Photo.last) end end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb index 0cfe452fe..1b8bbfbf8 100644 --- a/spec/models/photo_spec.rb +++ b/spec/models/photo_spec.rb @@ -1,5 +1,14 @@ require 'spec_helper' describe Photo do - pending "add some examples to (or delete) #{__FILE__}" + + describe 'generate_flickr_urls' do + # Any further tests led to us MOCKING ALL THE THINGS + # which was epistemologically unsatisfactory. + # So we're just going to test that the method exists. + it 'exists' do + photo = Photo.new(:owner_id => 1) + photo.should.respond_to? :generate_flickr_urls + end + end end From 536760ccae3ccc31f124d328d9af236b9c38ba18 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Thu, 9 May 2013 13:24:23 +0100 Subject: [PATCH 024/184] Show image on photos/show. Placeholder text for metadata. --- app/views/photos/show.html.haml | 27 +++++++++++------------- spec/factories/photos.rb | 4 ++-- spec/views/photos/show.html.haml_spec.rb | 15 +++---------- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 093aaedec..c27d217f7 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -1,18 +1,15 @@ %p#notice= notice -%p - %b Owner: - = @photo.owner_id -%p - %b Flickr photo: - = @photo.flickr_photo_id -%p - %b Thumbnail url: - = @photo.thumbnail_url -%p - %b Fullsize url: - = @photo.fullsize_url += image_tag(@photo.fullsize_url, :alt => 'A plant', :class => 'img-rounded') + +%p + [Photo title goes here] + by + [Flickr username goes here] + on Flickr. + [if licence] + Licensed under [Licence goes here]. + [else] + [Copyright, all rights reserved] + [endif] -= link_to 'Edit', edit_photo_path(@photo) -\| -= link_to 'Back', photos_path diff --git a/spec/factories/photos.rb b/spec/factories/photos.rb index 409d2bdf0..40f7824b2 100644 --- a/spec/factories/photos.rb +++ b/spec/factories/photos.rb @@ -4,7 +4,7 @@ FactoryGirl.define do factory :photo do owner_id 1 flickr_photo_id 1 - thumbnail_url "MyString" - fullsize_url "MyString" + thumbnail_url "http://example.com/thumb.jpg" + fullsize_url "http://example.com/full.jpg" end end diff --git a/spec/views/photos/show.html.haml_spec.rb b/spec/views/photos/show.html.haml_spec.rb index 0eb478998..a7298fe7c 100644 --- a/spec/views/photos/show.html.haml_spec.rb +++ b/spec/views/photos/show.html.haml_spec.rb @@ -2,20 +2,11 @@ require 'spec_helper' describe "photos/show" do before(:each) do - @photo = assign(:photo, stub_model(Photo, - :owner_id => 1, - :flickr_photo_id => 2, - :thumbnail_url => "Thumbnail Url", - :fullsize_url => "Fullsize Url" - )) + @photo = assign(:photo, FactoryGirl.create(:photo)) end - it "renders attributes in

" do + it "shows the image" do render - # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should match(/1/) - rendered.should match(/2/) - rendered.should match(/Thumbnail Url/) - rendered.should match(/Fullsize Url/) + assert_select "img[src=#{@photo.fullsize_url}]" end end From 746386731a813c431c23800c4556d8111bb3c903 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Thu, 9 May 2013 14:51:38 +0100 Subject: [PATCH 025/184] Add metadata to photos and display it on photos/show. --- app/controllers/photos_controller.rb | 2 +- app/models/photo.rb | 25 +++++++----- app/views/photos/show.html.haml | 18 +++++---- .../20130509123711_add_metadata_to_photos.rb | 22 +++++++++++ db/schema.rb | 6 ++- spec/controllers/photos_controller_spec.rb | 18 ++++++--- spec/factories/photos.rb | 11 +++++- spec/models/photo_spec.rb | 4 +- spec/views/photos/show.html.haml_spec.rb | 39 ++++++++++++++++--- 9 files changed, 112 insertions(+), 33 deletions(-) create mode 100644 db/migrate/20130509123711_add_metadata_to_photos.rb diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 7434c17ae..960fd36bc 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -48,7 +48,7 @@ class PhotosController < ApplicationController def create @photo = Photo.new(params[:photo]) @photo.owner_id = current_member.id - @photo.set_flickr_urls + @photo.set_flickr_metadata respond_to do |format| if @photo.save diff --git a/app/models/photo.rb b/app/models/photo.rb index 7c80ef53c..84b22bcdd 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -1,21 +1,28 @@ class Photo < ActiveRecord::Base - attr_accessible :flickr_photo_id, :fullsize_url, :owner_id, :thumbnail_url + attr_accessible :flickr_photo_id, :owner_id, :title, :license_name, + :license_url, :thumbnail_url, :fullsize_url, :link_url belongs_to :owner, :class_name => 'Member' # This is split into a side-effect free method and a side-effecting method # for easier stubbing and testing. - def flickr_urls + def flickr_metadata flickr = owner.flickr info = flickr.photos.getInfo(:photo_id => flickr_photo_id) - thumb = FlickRaw.url_q(info) - full = FlickRaw.url_z(info) - return [thumb, full] + licenses = flickr.photos.licenses.getInfo() + license = licenses.find { |l| l.id == info.license } + return { + :title => info.title || "Untitled", + :license_name => license.name, + :license_url => license.url, + :thumbnail_url => FlickRaw.url_q(info), + :fullsize_url => FlickRaw.url_z(info), + :link_url => FlickRaw.url_photopage(info) + } + end - def set_flickr_urls - urls = flickr_urls - self.thumbnail_url = urls[0] - self.fullsize_url = urls[1] + def set_flickr_metadata + self.update_attributes(flickr_metadata) end end diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index c27d217f7..912ca5113 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -3,13 +3,15 @@ = image_tag(@photo.fullsize_url, :alt => 'A plant', :class => 'img-rounded') %p - [Photo title goes here] + = link_to @photo.title, @photo.link_url by - [Flickr username goes here] - on Flickr. - [if licence] - Licensed under [Licence goes here]. - [else] - [Copyright, all rights reserved] - [endif] + = succeed "." do + = link_to @photo.owner, @photo.owner + License: + - if @photo.license_url + = succeed "." do + = link_to @photo.license_name, @photo.license_url + - else + = succeed "." do + = @photo.license_name diff --git a/db/migrate/20130509123711_add_metadata_to_photos.rb b/db/migrate/20130509123711_add_metadata_to_photos.rb new file mode 100644 index 000000000..9e73a6272 --- /dev/null +++ b/db/migrate/20130509123711_add_metadata_to_photos.rb @@ -0,0 +1,22 @@ +class AddMetadataToPhotos < ActiveRecord::Migration + def up + change_table :photos do |t| + t.string :title + t.string :license_name + t.string :license_url + t.string :link_url + t.change :title, :string, :null => false + t.change :license_name, :string, :null => false + t.change :link_url, :string, :null => false + end + end + + def down + change_table :photos do |t| + t.remove :title + t.remove :license_name + t.remove :license_url + t.remove :link_url + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e6810afb1..99a501fb4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130508104506) do +ActiveRecord::Schema.define(:version => 20130509123711) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -147,6 +147,10 @@ ActiveRecord::Schema.define(:version => 20130508104506) do t.string "fullsize_url", :null => false t.datetime "created_at", :null => false t.datetime "updated_at", :null => false + t.string "title", :null => false + t.string "license_name", :null => false + t.string "license_url" + t.string "link_url", :null => false end create_table "plantings", :force => true do |t| diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 63a638b1c..c61c7e244 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -8,8 +8,11 @@ describe PhotosController do { "owner_id" => "1", "flickr_photo_id" => 1, - "thumbnail_url" => 'http://example.com', - "fullsize_url" => 'http://example.com' + "title" => "Photo", + "license_name" => "CC-BY", + "thumbnail_url" => 'http://example.com/thumb.jpg', + "fullsize_url" => 'http://example.com/full.jpg', + "link_url" => 'http://example.com' } end @@ -60,9 +63,14 @@ describe PhotosController do describe "POST create" do before(:each) do - Photo.any_instance.stub(:flickr_urls).and_return( - ["http://example.com", "http://example.com" ] - ) + Photo.any_instance.stub(:flickr_metadata).and_return( { + :title => "A Heartbreaking work of staggering genius", + :license_name => "CC-BY", + :license_url => "http://example.com/aybpl", + :thumbnail_url => "http://example.com/thumb.jpg", + :fullsize_url => "http://example.com/full.jpg", + :link_url => "http://example.com" + }) end describe "with valid params" do it "creates a new Photo" do diff --git a/spec/factories/photos.rb b/spec/factories/photos.rb index 40f7824b2..70c1fb4fb 100644 --- a/spec/factories/photos.rb +++ b/spec/factories/photos.rb @@ -2,9 +2,18 @@ FactoryGirl.define do factory :photo do - owner_id 1 + owner flickr_photo_id 1 + title "Still life with chillies" + license_name "CC-BY" + license_url "http://example.com/license.html" thumbnail_url "http://example.com/thumb.jpg" fullsize_url "http://example.com/full.jpg" + link_url "http://example.com/" + + factory :unlicensed_photo do + license_name "All rights reserved" + license_url "" + end end end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb index 1b8bbfbf8..8ac067ab7 100644 --- a/spec/models/photo_spec.rb +++ b/spec/models/photo_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' describe Photo do - describe 'generate_flickr_urls' do + describe 'flickr_metadata' do # Any further tests led to us MOCKING ALL THE THINGS # which was epistemologically unsatisfactory. # So we're just going to test that the method exists. it 'exists' do photo = Photo.new(:owner_id => 1) - photo.should.respond_to? :generate_flickr_urls + photo.should.respond_to? :flickr_metadata end end end diff --git a/spec/views/photos/show.html.haml_spec.rb b/spec/views/photos/show.html.haml_spec.rb index a7298fe7c..5b8cebfbf 100644 --- a/spec/views/photos/show.html.haml_spec.rb +++ b/spec/views/photos/show.html.haml_spec.rb @@ -1,12 +1,39 @@ require 'spec_helper' describe "photos/show" do - before(:each) do - @photo = assign(:photo, FactoryGirl.create(:photo)) + context "CC-licensed photo" do + before(:each) do + @photo = assign(:photo, FactoryGirl.create(:photo)) + render + end + + it "shows the image" do + assert_select "img[src=#{@photo.fullsize_url}]" + end + + it "links to the owner's profile" do + assert_select "a", :href => @photo.owner + end + + it "links to the CC license" do + assert_select "a", :href => @photo.license_url, + :text => @photo.license_name + end + + it "shows the title as a link to the original image" do + assert_select "a", :href => @photo.link_url, :text => @photo.title + end end - it "shows the image" do - render - assert_select "img[src=#{@photo.fullsize_url}]" - end + context "unlicensed photo" do + before(:each) do + @photo = assign(:photo, FactoryGirl.create(:unlicensed_photo)) + render + end + + it "contains the phrase 'All rights reserved'" do + rendered.should contain "All rights reserved" + end +end + end From 7d74203735542bdc4edce6146a67ca1a4e6f9b8a Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 13:45:32 +1000 Subject: [PATCH 026/184] rails g scaffold OrderItem... --- app/assets/javascripts/order_items.js.coffee | 3 + app/controllers/order_items_controller.rb | 83 +++++++++ app/helpers/order_items_helper.rb | 2 + app/models/order.rb | 2 +- app/models/order_item.rb | 7 + app/views/order_items/_form.html.haml | 22 +++ app/views/order_items/edit.html.haml | 7 + app/views/order_items/index.html.haml | 25 +++ app/views/order_items/new.html.haml | 5 + app/views/order_items/show.html.haml | 18 ++ config/routes.rb | 3 + .../20130515033842_create_order_items.rb | 12 ++ db/schema.rb | 25 ++- .../order_items_controller_spec.rb | 164 ++++++++++++++++++ spec/factories/order_items.rb | 10 ++ spec/helpers/order_items_helper_spec.rb | 15 ++ spec/models/order_item_spec.rb | 13 ++ spec/models/order_spec.rb | 7 +- spec/requests/order_items_spec.rb | 11 ++ spec/routing/order_items_routing_spec.rb | 35 ++++ spec/views/order_items/edit.html.haml_spec.rb | 24 +++ .../views/order_items/index.html.haml_spec.rb | 29 ++++ spec/views/order_items/new.html.haml_spec.rb | 24 +++ spec/views/order_items/show.html.haml_spec.rb | 21 +++ 24 files changed, 562 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/order_items.js.coffee create mode 100644 app/controllers/order_items_controller.rb create mode 100644 app/helpers/order_items_helper.rb create mode 100644 app/models/order_item.rb create mode 100644 app/views/order_items/_form.html.haml create mode 100644 app/views/order_items/edit.html.haml create mode 100644 app/views/order_items/index.html.haml create mode 100644 app/views/order_items/new.html.haml create mode 100644 app/views/order_items/show.html.haml create mode 100644 db/migrate/20130515033842_create_order_items.rb create mode 100644 spec/controllers/order_items_controller_spec.rb create mode 100644 spec/factories/order_items.rb create mode 100644 spec/helpers/order_items_helper_spec.rb create mode 100644 spec/models/order_item_spec.rb create mode 100644 spec/requests/order_items_spec.rb create mode 100644 spec/routing/order_items_routing_spec.rb create mode 100644 spec/views/order_items/edit.html.haml_spec.rb create mode 100644 spec/views/order_items/index.html.haml_spec.rb create mode 100644 spec/views/order_items/new.html.haml_spec.rb create mode 100644 spec/views/order_items/show.html.haml_spec.rb diff --git a/app/assets/javascripts/order_items.js.coffee b/app/assets/javascripts/order_items.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/order_items.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb new file mode 100644 index 000000000..ae076281e --- /dev/null +++ b/app/controllers/order_items_controller.rb @@ -0,0 +1,83 @@ +class OrderItemsController < ApplicationController + # GET /order_items + # GET /order_items.json + def index + @order_items = OrderItem.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @order_items } + end + end + + # GET /order_items/1 + # GET /order_items/1.json + def show + @order_item = OrderItem.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @order_item } + end + end + + # GET /order_items/new + # GET /order_items/new.json + def new + @order_item = OrderItem.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @order_item } + end + end + + # GET /order_items/1/edit + def edit + @order_item = OrderItem.find(params[:id]) + end + + # POST /order_items + # POST /order_items.json + def create + @order_item = OrderItem.new(params[:order_item]) + + respond_to do |format| + if @order_item.save + format.html { redirect_to @order_item, notice: 'Order item was successfully created.' } + format.json { render json: @order_item, status: :created, location: @order_item } + else + format.html { render action: "new" } + format.json { render json: @order_item.errors, status: :unprocessable_entity } + end + end + end + + # PUT /order_items/1 + # PUT /order_items/1.json + def update + @order_item = OrderItem.find(params[:id]) + + respond_to do |format| + if @order_item.update_attributes(params[:order_item]) + format.html { redirect_to @order_item, notice: 'Order item was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @order_item.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /order_items/1 + # DELETE /order_items/1.json + def destroy + @order_item = OrderItem.find(params[:id]) + @order_item.destroy + + respond_to do |format| + format.html { redirect_to order_items_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/order_items_helper.rb b/app/helpers/order_items_helper.rb new file mode 100644 index 000000000..e197528ae --- /dev/null +++ b/app/helpers/order_items_helper.rb @@ -0,0 +1,2 @@ +module OrderItemsHelper +end diff --git a/app/models/order.rb b/app/models/order.rb index 9e86912a5..3b524a97b 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -2,7 +2,7 @@ class Order < ActiveRecord::Base attr_accessible :member_id, :completed_at belongs_to :member - has_and_belongs_to_many :products + has_many :order_items default_scope order('created_at DESC') end diff --git a/app/models/order_item.rb b/app/models/order_item.rb new file mode 100644 index 000000000..250723909 --- /dev/null +++ b/app/models/order_item.rb @@ -0,0 +1,7 @@ +class OrderItem < ActiveRecord::Base + attr_accessible :order_id, :price, :product_id, :quantity + + belongs_to :order + belongs_to :product + +end diff --git a/app/views/order_items/_form.html.haml b/app/views/order_items/_form.html.haml new file mode 100644 index 000000000..ff2924af4 --- /dev/null +++ b/app/views/order_items/_form.html.haml @@ -0,0 +1,22 @@ += form_for @order_item do |f| + - if @order_item.errors.any? + #error_explanation + %h2= "#{pluralize(@order_item.errors.count, "error")} prohibited this order_item from being saved:" + %ul + - @order_item.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :order_id + = f.number_field :order_id + .field + = f.label :product_id + = f.number_field :product_id + .field + = f.label :price + = f.text_field :price + .field + = f.label :quantity + = f.number_field :quantity + .actions + = f.submit 'Save' diff --git a/app/views/order_items/edit.html.haml b/app/views/order_items/edit.html.haml new file mode 100644 index 000000000..da759ba68 --- /dev/null +++ b/app/views/order_items/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing order_item + += render 'form' + += link_to 'Show', @order_item +\| += link_to 'Back', order_items_path diff --git a/app/views/order_items/index.html.haml b/app/views/order_items/index.html.haml new file mode 100644 index 000000000..6b63af7f9 --- /dev/null +++ b/app/views/order_items/index.html.haml @@ -0,0 +1,25 @@ +%h1 Listing order_items + +%table + %tr + %th Order + %th Product + %th Price + %th Quantity + %th + %th + %th + + - @order_items.each do |order_item| + %tr + %td= order_item.order_id + %td= order_item.product_id + %td= order_item.price + %td= order_item.quantity + %td= link_to 'Show', order_item + %td= link_to 'Edit', edit_order_item_path(order_item) + %td= link_to 'Destroy', order_item, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + += link_to 'New Order item', new_order_item_path diff --git a/app/views/order_items/new.html.haml b/app/views/order_items/new.html.haml new file mode 100644 index 000000000..2ba254512 --- /dev/null +++ b/app/views/order_items/new.html.haml @@ -0,0 +1,5 @@ +%h1 New order_item + += render 'form' + += link_to 'Back', order_items_path diff --git a/app/views/order_items/show.html.haml b/app/views/order_items/show.html.haml new file mode 100644 index 000000000..16551615a --- /dev/null +++ b/app/views/order_items/show.html.haml @@ -0,0 +1,18 @@ +%p#notice= notice + +%p + %b Order: + = @order_item.order_id +%p + %b Product: + = @order_item.product_id +%p + %b Price: + = @order_item.price +%p + %b Quantity: + = @order_item.quantity + += link_to 'Edit', edit_order_item_path(@order_item) +\| += link_to 'Back', order_items_path diff --git a/config/routes.rb b/config/routes.rb index 0ed446792..93d1a0c4a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Growstuff::Application.routes.draw do + resources :order_items + + devise_for :members, :controllers => { :registrations => "registrations" } resources :authentications diff --git a/db/migrate/20130515033842_create_order_items.rb b/db/migrate/20130515033842_create_order_items.rb new file mode 100644 index 000000000..68b496dda --- /dev/null +++ b/db/migrate/20130515033842_create_order_items.rb @@ -0,0 +1,12 @@ +class CreateOrderItems < ActiveRecord::Migration + def change + create_table :order_items do |t| + t.integer :order_id + t.integer :product_id + t.decimal :price + t.integer :quantity + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 090be1b27..56bbe7036 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130508050711) do +ActiveRecord::Schema.define(:version => 20130515033842) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -40,6 +40,7 @@ ActiveRecord::Schema.define(:version => 20130508050711) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.string "slug" + t.integer "parent_id" end add_index "crops", ["slug"], :name => "index_crops_on_slug", :unique => true @@ -120,6 +121,15 @@ ActiveRecord::Schema.define(:version => 20130508050711) do t.datetime "updated_at", :null => false end + create_table "order_items", :force => true do |t| + t.integer "order_id" + t.integer "product_id" + t.decimal "price" + t.integer "quantity" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "orders", :force => true do |t| t.string "member_id", :null => false t.datetime "created_at", :null => false @@ -140,6 +150,19 @@ ActiveRecord::Schema.define(:version => 20130508050711) do t.datetime "updated_at", :null => false end + create_table "photos", :force => true do |t| + t.integer "owner_id", :null => false + t.integer "flickr_photo_id", :null => false + t.string "thumbnail_url", :null => false + t.string "fullsize_url", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "title", :null => false + t.string "license_name", :null => false + t.string "license_url" + t.string "link_url", :null => false + end + create_table "plantings", :force => true do |t| t.integer "garden_id", :null => false t.integer "crop_id", :null => false diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb new file mode 100644 index 000000000..74cd6d2ad --- /dev/null +++ b/spec/controllers/order_items_controller_spec.rb @@ -0,0 +1,164 @@ +require 'spec_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +describe OrderItemsController do + + # This should return the minimal set of attributes required to create a valid + # OrderItem. As you add validations to OrderItem, be sure to + # update the return value of this method accordingly. + def valid_attributes + { "order_id" => "1" } + end + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # OrderItemsController. Be sure to keep this updated too. + def valid_session + {} + end + + describe "GET index" do + it "assigns all order_items as @order_items" do + order_item = OrderItem.create! valid_attributes + get :index, {}, valid_session + assigns(:order_items).should eq([order_item]) + end + end + + describe "GET show" do + it "assigns the requested order_item as @order_item" do + order_item = OrderItem.create! valid_attributes + get :show, {:id => order_item.to_param}, valid_session + assigns(:order_item).should eq(order_item) + end + end + + describe "GET new" do + it "assigns a new order_item as @order_item" do + get :new, {}, valid_session + assigns(:order_item).should be_a_new(OrderItem) + end + end + + describe "GET edit" do + it "assigns the requested order_item as @order_item" do + order_item = OrderItem.create! valid_attributes + get :edit, {:id => order_item.to_param}, valid_session + assigns(:order_item).should eq(order_item) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new OrderItem" do + expect { + post :create, {:order_item => valid_attributes}, valid_session + }.to change(OrderItem, :count).by(1) + end + + it "assigns a newly created order_item as @order_item" do + post :create, {:order_item => valid_attributes}, valid_session + assigns(:order_item).should be_a(OrderItem) + assigns(:order_item).should be_persisted + end + + it "redirects to the created order_item" do + post :create, {:order_item => valid_attributes}, valid_session + response.should redirect_to(OrderItem.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved order_item as @order_item" do + # Trigger the behavior that occurs when invalid params are submitted + OrderItem.any_instance.stub(:save).and_return(false) + post :create, {:order_item => { "order_id" => "invalid value" }}, valid_session + assigns(:order_item).should be_a_new(OrderItem) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + OrderItem.any_instance.stub(:save).and_return(false) + post :create, {:order_item => { "order_id" => "invalid value" }}, valid_session + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested order_item" do + order_item = OrderItem.create! valid_attributes + # Assuming there are no other order_items in the database, this + # specifies that the OrderItem created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + OrderItem.any_instance.should_receive(:update_attributes).with({ "order_id" => "1" }) + put :update, {:id => order_item.to_param, :order_item => { "order_id" => "1" }}, valid_session + end + + it "assigns the requested order_item as @order_item" do + order_item = OrderItem.create! valid_attributes + put :update, {:id => order_item.to_param, :order_item => valid_attributes}, valid_session + assigns(:order_item).should eq(order_item) + end + + it "redirects to the order_item" do + order_item = OrderItem.create! valid_attributes + put :update, {:id => order_item.to_param, :order_item => valid_attributes}, valid_session + response.should redirect_to(order_item) + end + end + + describe "with invalid params" do + it "assigns the order_item as @order_item" do + order_item = OrderItem.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + OrderItem.any_instance.stub(:save).and_return(false) + put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }}, valid_session + assigns(:order_item).should eq(order_item) + end + + it "re-renders the 'edit' template" do + order_item = OrderItem.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + OrderItem.any_instance.stub(:save).and_return(false) + put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }}, valid_session + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested order_item" do + order_item = OrderItem.create! valid_attributes + expect { + delete :destroy, {:id => order_item.to_param}, valid_session + }.to change(OrderItem, :count).by(-1) + end + + it "redirects to the order_items list" do + order_item = OrderItem.create! valid_attributes + delete :destroy, {:id => order_item.to_param}, valid_session + response.should redirect_to(order_items_url) + end + end + +end diff --git a/spec/factories/order_items.rb b/spec/factories/order_items.rb new file mode 100644 index 000000000..d99c324a5 --- /dev/null +++ b/spec/factories/order_items.rb @@ -0,0 +1,10 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :order_item do + order + product + price "9.99" + quantity 1 + end +end diff --git a/spec/helpers/order_items_helper_spec.rb b/spec/helpers/order_items_helper_spec.rb new file mode 100644 index 000000000..5c4f6ae82 --- /dev/null +++ b/spec/helpers/order_items_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the OrderItemsHelper. For example: +# +# describe OrderItemsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe OrderItemsHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/order_item_spec.rb b/spec/models/order_item_spec.rb new file mode 100644 index 000000000..959ea5fc0 --- /dev/null +++ b/spec/models/order_item_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe OrderItem do + before(:each) do + @order_item = FactoryGirl.create(:order_item) + end + + it "has an order and a product" do + @order_item.order.should be_an_instance_of Order + @order_item.product.should be_an_instance_of Product + end + +end diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index 6a0cc6103..57a878701 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -4,11 +4,12 @@ describe Order do before(:each) do @order = FactoryGirl.create(:order) @product = FactoryGirl.create(:product) - @order.products << @product + @order_item = FactoryGirl.create(:order_item, + :order_id => @order.id, :product_id => @product.id) end - it 'has products' do - @order.products.first.should eq @product + it 'has order_items' do + @order.order_items.first.should eq @order_item end it 'sorts by created_at DESC' do diff --git a/spec/requests/order_items_spec.rb b/spec/requests/order_items_spec.rb new file mode 100644 index 000000000..dcf665e2b --- /dev/null +++ b/spec/requests/order_items_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "OrderItems" do + describe "GET /order_items" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get order_items_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/order_items_routing_spec.rb b/spec/routing/order_items_routing_spec.rb new file mode 100644 index 000000000..1993db17d --- /dev/null +++ b/spec/routing/order_items_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe OrderItemsController do + describe "routing" do + + it "routes to #index" do + get("/order_items").should route_to("order_items#index") + end + + it "routes to #new" do + get("/order_items/new").should route_to("order_items#new") + end + + it "routes to #show" do + get("/order_items/1").should route_to("order_items#show", :id => "1") + end + + it "routes to #edit" do + get("/order_items/1/edit").should route_to("order_items#edit", :id => "1") + end + + it "routes to #create" do + post("/order_items").should route_to("order_items#create") + end + + it "routes to #update" do + put("/order_items/1").should route_to("order_items#update", :id => "1") + end + + it "routes to #destroy" do + delete("/order_items/1").should route_to("order_items#destroy", :id => "1") + end + + end +end diff --git a/spec/views/order_items/edit.html.haml_spec.rb b/spec/views/order_items/edit.html.haml_spec.rb new file mode 100644 index 000000000..49dbc8005 --- /dev/null +++ b/spec/views/order_items/edit.html.haml_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe "order_items/edit" do + before(:each) do + @order_item = assign(:order_item, stub_model(OrderItem, + :order_id => 1, + :product_id => 1, + :price => "9.99", + :quantity => 1 + )) + end + + it "renders the edit order_item form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => order_items_path(@order_item), :method => "post" do + assert_select "input#order_item_order_id", :name => "order_item[order_id]" + assert_select "input#order_item_product_id", :name => "order_item[product_id]" + assert_select "input#order_item_price", :name => "order_item[price]" + assert_select "input#order_item_quantity", :name => "order_item[quantity]" + end + end +end diff --git a/spec/views/order_items/index.html.haml_spec.rb b/spec/views/order_items/index.html.haml_spec.rb new file mode 100644 index 000000000..0b4bdfb3d --- /dev/null +++ b/spec/views/order_items/index.html.haml_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe "order_items/index" do + before(:each) do + assign(:order_items, [ + stub_model(OrderItem, + :order_id => 1, + :product_id => 2, + :price => "9.99", + :quantity => 3 + ), + stub_model(OrderItem, + :order_id => 1, + :product_id => 2, + :price => "9.99", + :quantity => 3 + ) + ]) + end + + it "renders a list of order_items" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => 1.to_s, :count => 2 + assert_select "tr>td", :text => 2.to_s, :count => 2 + assert_select "tr>td", :text => "9.99".to_s, :count => 2 + assert_select "tr>td", :text => 3.to_s, :count => 2 + end +end diff --git a/spec/views/order_items/new.html.haml_spec.rb b/spec/views/order_items/new.html.haml_spec.rb new file mode 100644 index 000000000..70ed7cfa1 --- /dev/null +++ b/spec/views/order_items/new.html.haml_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe "order_items/new" do + before(:each) do + assign(:order_item, stub_model(OrderItem, + :order_id => 1, + :product_id => 1, + :price => "9.99", + :quantity => 1 + ).as_new_record) + end + + it "renders new order_item form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => order_items_path, :method => "post" do + assert_select "input#order_item_order_id", :name => "order_item[order_id]" + assert_select "input#order_item_product_id", :name => "order_item[product_id]" + assert_select "input#order_item_price", :name => "order_item[price]" + assert_select "input#order_item_quantity", :name => "order_item[quantity]" + end + end +end diff --git a/spec/views/order_items/show.html.haml_spec.rb b/spec/views/order_items/show.html.haml_spec.rb new file mode 100644 index 000000000..8a445309a --- /dev/null +++ b/spec/views/order_items/show.html.haml_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe "order_items/show" do + before(:each) do + @order_item = assign(:order_item, stub_model(OrderItem, + :order_id => 1, + :product_id => 2, + :price => "9.99", + :quantity => 3 + )) + end + + it "renders attributes in

" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/1/) + rendered.should match(/2/) + rendered.should match(/9.99/) + rendered.should match(/3/) + end +end From a047f60af3eee91d00b490676a941501014c61c0 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 14:15:08 +1000 Subject: [PATCH 027/184] added cancan rules for order items --- app/controllers/order_items_controller.rb | 1 + app/models/ability.rb | 29 +++++--- spec/models/ability_spec.rb | 86 +++++++++++++++++++++-- 3 files changed, 101 insertions(+), 15 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index ae076281e..58e17825b 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -1,4 +1,5 @@ class OrderItemsController < ApplicationController + load_and_authorize_resource # GET /order_items # GET /order_items.json def index diff --git a/app/models/ability.rb b/app/models/ability.rb index f31a1e06e..29cbc26e1 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -15,6 +15,7 @@ class Ability cannot :read, Role cannot :read, Product cannot :read, Order + cannot :read, OrderItem if member @@ -57,26 +58,32 @@ class Ability # anyone can create a post, or comment on a post, # but only the author can edit/destroy it. - can :create, Post - can :update, Post, :author_id => member.id + can :create, Post + can :update, Post, :author_id => member.id can :destroy, Post, :author_id => member.id - can :create, Comment - can :update, Comment, :author_id => member.id + can :create, Comment + can :update, Comment, :author_id => member.id can :destroy, Comment, :author_id => member.id # same deal for gardens and plantings - can :create, Garden - can :update, Garden, :owner_id => member.id + can :create, Garden + can :update, Garden, :owner_id => member.id can :destroy, Garden, :owner_id => member.id - can :create, Planting - can :update, Planting, :garden => { :owner_id => member.id } + can :create, Planting + can :update, Planting, :garden => { :owner_id => member.id } can :destroy, Planting, :garden => { :owner_id => member.id } # orders/shop/etc - can :create, Order - can :read, Order, :member_id => member.id - can :update, Order, :member_id => member.id + can :create, Order + can :read, Order, :member_id => member.id + can :update, Order, :member_id => member.id, :completed_at => nil + can :destroy, Order, :member_id => member.id, :completed_at => nil + + can :create, OrderItem + can :read, OrderItem, :order => { :member_id => member.id } + can :update, OrderItem, :order => { :member_id => member.id, :completed_at => nil } + can :destroy, OrderItem, :order => { :member_id => member.id, :completed_at => nil } end end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 4a798074f..e4bc288d2 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -124,16 +124,94 @@ describe Ability do before(:each) do @order = FactoryGirl.create(:order, :member => @member) + @strangers_order = FactoryGirl.create(:order, + :member => FactoryGirl.create(:member)) + @completed_order = FactoryGirl.create(:completed_order, + :member => @member) + + @order_item = FactoryGirl.create(:order_item, :order => @order) + @strangers_order_item = FactoryGirl.create(:order_item, + :order => @strangers_order) + @completed_order_item = FactoryGirl.create(:order_item, + :order => @completed_order) end context "standard member" do - it "can't read or manage orders" do + it "can read their own orders" do @ability.should be_able_to(:read, @order) - @ability.should be_able_to(:create, Order) - @ability.should be_able_to(:update, @order) - @ability.should_not be_able_to(:destroy, @order) + @ability.should be_able_to(:read, @completed_order) end + it "can't read other people's orders" do + @ability.should_not be_able_to(:read, @strangers_order) + end + + it "can create a new order" do + @ability.should be_able_to(:create, Order) + end + + it "can update their own current order" do + @ability.should be_able_to(:update, @order) + end + + it "can't update someone else's order" do + @ability.should_not be_able_to(:update, @strangers_order) + end + + it "can't update a completed order" do + @ability.should_not be_able_to(:update, @completed_order) + end + + it "can delete a current order" do + @ability.should be_able_to(:destroy, @order) + end + + it "can't delete someone else's order" do + @ability.should_not be_able_to(:destroy, @strangers_order) + end + + it "can't delete a completed order" do + @ability.should_not be_able_to(:destroy, @completed_order) + end + + it "can read their own order items" do + @ability.should be_able_to(:read, @order_item) + @ability.should be_able_to(:read, @completed_order_item) + end + + it "can't read other people's order items" do + @ability.should_not be_able_to(:read, @strangers_order_item) + end + + it "can create a new order item" do + @ability.should be_able_to(:create, OrderItem) + end + + it "can update their own order items" do + @ability.should be_able_to(:update, @order_item) + end + + it "can't update other people's order items" do + @ability.should_not be_able_to(:update, @strangers_order_item) + end + + it "can't updated items in completed orders" do + @ability.should_not be_able_to(:update, @completed_order_item) + end + + it "can delete their own order item" do + @ability.should be_able_to(:destroy, @order_item) + end + + it "can't delete someone else's order item" do + @ability.should_not be_able_to(:destroy, @strangers_order_item) + end + + it "can't delete items from completed orders" do + @ability.should_not be_able_to(:destroy, @completed_order_item) + end + + end context "admin" do From 8a35f3401bfae75f9232b37ba5760a7d80a91d01 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 14:20:31 +1000 Subject: [PATCH 028/184] removed unwanted methods from orders --- app/controllers/orders_controller.rb | 49 -------- app/models/ability.rb | 1 + app/views/orders/_form.html.haml | 13 -- app/views/orders/edit.html.haml | 7 -- app/views/orders/new.html.haml | 5 - .../order_items_controller_spec.rb | 64 +++------- spec/controllers/orders_controller_spec.rb | 112 ------------------ spec/requests/order_items_spec.rb | 11 -- spec/views/orders/edit.html.haml_spec.rb | 18 --- spec/views/orders/new.html.haml_spec.rb | 18 --- 10 files changed, 20 insertions(+), 278 deletions(-) delete mode 100644 app/views/orders/_form.html.haml delete mode 100644 app/views/orders/edit.html.haml delete mode 100644 app/views/orders/new.html.haml delete mode 100644 spec/requests/order_items_spec.rb delete mode 100644 spec/views/orders/edit.html.haml_spec.rb delete mode 100644 spec/views/orders/new.html.haml_spec.rb diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 27ad4c62e..ff6f0a9fb 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -32,53 +32,4 @@ class OrdersController < ApplicationController format.json { render json: @order } end end - - # GET /orders/1/edit - def edit - @order = Order.find(params[:id]) - end - - # POST /orders - # POST /orders.json - def create - @order = Order.new(params[:order]) - - respond_to do |format| - if @order.save - format.html { redirect_to @order, notice: 'Order was successfully created.' } - format.json { render json: @order, status: :created, location: @order } - else - format.html { render action: "new" } - format.json { render json: @order.errors, status: :unprocessable_entity } - end - end - end - - # PUT /orders/1 - # PUT /orders/1.json - def update - @order = Order.find(params[:id]) - - respond_to do |format| - if @order.update_attributes(params[:order]) - format.html { redirect_to @order, notice: 'Order was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: "edit" } - format.json { render json: @order.errors, status: :unprocessable_entity } - end - end - end - - # DELETE /orders/1 - # DELETE /orders/1.json - def destroy - @order = Order.find(params[:id]) - @order.destroy - - respond_to do |format| - format.html { redirect_to orders_url } - format.json { head :no_content } - end - end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 29cbc26e1..b57c51997 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -30,6 +30,7 @@ class Ability # admins can manage products and orders can :manage, Product can :manage, Order # for now + can :manage, OrderItem # for now end # managing your own user settings diff --git a/app/views/orders/_form.html.haml b/app/views/orders/_form.html.haml deleted file mode 100644 index fb4fa9dcb..000000000 --- a/app/views/orders/_form.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -= form_for @order do |f| - - if @order.errors.any? - #error_explanation - %h2= "#{pluralize(@order.errors.count, "error")} prohibited this order from being saved:" - %ul - - @order.errors.full_messages.each do |msg| - %li= msg - - .field - = f.label :member_id - = f.text_field :member_id - .actions - = f.submit 'Save' diff --git a/app/views/orders/edit.html.haml b/app/views/orders/edit.html.haml deleted file mode 100644 index 9d5b393f0..000000000 --- a/app/views/orders/edit.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%h1 Editing order - -= render 'form' - -= link_to 'Show', @order -\| -= link_to 'Back', orders_path diff --git a/app/views/orders/new.html.haml b/app/views/orders/new.html.haml deleted file mode 100644 index 42e0872a8..000000000 --- a/app/views/orders/new.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%h1 New order - -= render 'form' - -= link_to 'Back', orders_path diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index 74cd6d2ad..ff0740951 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -1,43 +1,17 @@ require 'spec_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. - describe OrderItemsController do - # This should return the minimal set of attributes required to create a valid - # OrderItem. As you add validations to OrderItem, be sure to - # update the return value of this method accordingly. - def valid_attributes - { "order_id" => "1" } - end + login_member(:admin_member) - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # OrderItemsController. Be sure to keep this updated too. - def valid_session - {} + def valid_attributes + { "order_id" => "1", "product_id" => "1" } end describe "GET index" do it "assigns all order_items as @order_items" do order_item = OrderItem.create! valid_attributes - get :index, {}, valid_session + get :index, {} assigns(:order_items).should eq([order_item]) end end @@ -45,14 +19,14 @@ describe OrderItemsController do describe "GET show" do it "assigns the requested order_item as @order_item" do order_item = OrderItem.create! valid_attributes - get :show, {:id => order_item.to_param}, valid_session + get :show, {:id => order_item.to_param} assigns(:order_item).should eq(order_item) end end describe "GET new" do it "assigns a new order_item as @order_item" do - get :new, {}, valid_session + get :new, {} assigns(:order_item).should be_a_new(OrderItem) end end @@ -60,7 +34,7 @@ describe OrderItemsController do describe "GET edit" do it "assigns the requested order_item as @order_item" do order_item = OrderItem.create! valid_attributes - get :edit, {:id => order_item.to_param}, valid_session + get :edit, {:id => order_item.to_param} assigns(:order_item).should eq(order_item) end end @@ -69,18 +43,18 @@ describe OrderItemsController do describe "with valid params" do it "creates a new OrderItem" do expect { - post :create, {:order_item => valid_attributes}, valid_session + post :create, {:order_item => valid_attributes} }.to change(OrderItem, :count).by(1) end it "assigns a newly created order_item as @order_item" do - post :create, {:order_item => valid_attributes}, valid_session + post :create, {:order_item => valid_attributes} assigns(:order_item).should be_a(OrderItem) assigns(:order_item).should be_persisted end it "redirects to the created order_item" do - post :create, {:order_item => valid_attributes}, valid_session + post :create, {:order_item => valid_attributes} response.should redirect_to(OrderItem.last) end end @@ -89,14 +63,14 @@ describe OrderItemsController do it "assigns a newly created but unsaved order_item as @order_item" do # Trigger the behavior that occurs when invalid params are submitted OrderItem.any_instance.stub(:save).and_return(false) - post :create, {:order_item => { "order_id" => "invalid value" }}, valid_session + post :create, {:order_item => { "order_id" => "invalid value" }} assigns(:order_item).should be_a_new(OrderItem) end it "re-renders the 'new' template" do # Trigger the behavior that occurs when invalid params are submitted OrderItem.any_instance.stub(:save).and_return(false) - post :create, {:order_item => { "order_id" => "invalid value" }}, valid_session + post :create, {:order_item => { "order_id" => "invalid value" }} response.should render_template("new") end end @@ -111,18 +85,18 @@ describe OrderItemsController do # receives the :update_attributes message with whatever params are # submitted in the request. OrderItem.any_instance.should_receive(:update_attributes).with({ "order_id" => "1" }) - put :update, {:id => order_item.to_param, :order_item => { "order_id" => "1" }}, valid_session + put :update, {:id => order_item.to_param, :order_item => { "order_id" => "1" }} end it "assigns the requested order_item as @order_item" do order_item = OrderItem.create! valid_attributes - put :update, {:id => order_item.to_param, :order_item => valid_attributes}, valid_session + put :update, {:id => order_item.to_param, :order_item => valid_attributes} assigns(:order_item).should eq(order_item) end it "redirects to the order_item" do order_item = OrderItem.create! valid_attributes - put :update, {:id => order_item.to_param, :order_item => valid_attributes}, valid_session + put :update, {:id => order_item.to_param, :order_item => valid_attributes} response.should redirect_to(order_item) end end @@ -132,7 +106,7 @@ describe OrderItemsController do order_item = OrderItem.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted OrderItem.any_instance.stub(:save).and_return(false) - put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }}, valid_session + put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }} assigns(:order_item).should eq(order_item) end @@ -140,7 +114,7 @@ describe OrderItemsController do order_item = OrderItem.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted OrderItem.any_instance.stub(:save).and_return(false) - put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }}, valid_session + put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }} response.should render_template("edit") end end @@ -150,13 +124,13 @@ describe OrderItemsController do it "destroys the requested order_item" do order_item = OrderItem.create! valid_attributes expect { - delete :destroy, {:id => order_item.to_param}, valid_session + delete :destroy, {:id => order_item.to_param} }.to change(OrderItem, :count).by(-1) end it "redirects to the order_items list" do order_item = OrderItem.create! valid_attributes - delete :destroy, {:id => order_item.to_param}, valid_session + delete :destroy, {:id => order_item.to_param} response.should redirect_to(order_items_url) end end diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb index 40da86b21..7c1c2ae7b 100644 --- a/spec/controllers/orders_controller_spec.rb +++ b/spec/controllers/orders_controller_spec.rb @@ -27,116 +27,4 @@ describe OrdersController do assigns(:order).should eq(order) end end - - describe "GET new" do - it "assigns a new order as @order" do - get :new, {} - assigns(:order).should be_a_new(Order) - end - end - - describe "GET edit" do - it "assigns the requested order as @order" do - order = Order.create! valid_attributes - get :edit, {:id => order.to_param} - assigns(:order).should eq(order) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Order" do - expect { - post :create, {:order => valid_attributes} - }.to change(Order, :count).by(1) - end - - it "assigns a newly created order as @order" do - post :create, {:order => valid_attributes} - assigns(:order).should be_a(Order) - assigns(:order).should be_persisted - end - - it "redirects to the created order" do - post :create, {:order => valid_attributes} - response.should redirect_to(Order.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved order as @order" do - # Trigger the behavior that occurs when invalid params are submitted - Order.any_instance.stub(:save).and_return(false) - post :create, {:order => { "member_id" => "invalid value" }} - assigns(:order).should be_a_new(Order) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Order.any_instance.stub(:save).and_return(false) - post :create, {:order => { "member_id" => "invalid value" }} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested order" do - order = Order.create! valid_attributes - # Assuming there are no other orders in the database, this - # specifies that the Order created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Order.any_instance.should_receive(:update_attributes).with({ "member_id" => "MyString" }) - put :update, {:id => order.to_param, :order => { "member_id" => "MyString" }} - end - - it "assigns the requested order as @order" do - order = Order.create! valid_attributes - put :update, {:id => order.to_param, :order => valid_attributes} - assigns(:order).should eq(order) - end - - it "redirects to the order" do - order = Order.create! valid_attributes - put :update, {:id => order.to_param, :order => valid_attributes} - response.should redirect_to(order) - end - end - - describe "with invalid params" do - it "assigns the order as @order" do - order = Order.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Order.any_instance.stub(:save).and_return(false) - put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }} - assigns(:order).should eq(order) - end - - it "re-renders the 'edit' template" do - order = Order.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Order.any_instance.stub(:save).and_return(false) - put :update, {:id => order.to_param, :order => { "member_id" => "invalid value" }} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested order" do - order = Order.create! valid_attributes - expect { - delete :destroy, {:id => order.to_param} - }.to change(Order, :count).by(-1) - end - - it "redirects to the orders list" do - order = Order.create! valid_attributes - delete :destroy, {:id => order.to_param} - response.should redirect_to(orders_url) - end - end - end diff --git a/spec/requests/order_items_spec.rb b/spec/requests/order_items_spec.rb deleted file mode 100644 index dcf665e2b..000000000 --- a/spec/requests/order_items_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'spec_helper' - -describe "OrderItems" do - describe "GET /order_items" do - it "works! (now write some real specs)" do - # Run the generator again with the --webrat flag if you want to use webrat methods/matchers - get order_items_path - response.status.should be(200) - end - end -end diff --git a/spec/views/orders/edit.html.haml_spec.rb b/spec/views/orders/edit.html.haml_spec.rb deleted file mode 100644 index f46e402d5..000000000 --- a/spec/views/orders/edit.html.haml_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe "orders/edit" do - before(:each) do - @order = assign(:order, stub_model(Order, - :member_id => "MyString" - )) - end - - it "renders the edit order form" do - render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => orders_path(@order), :method => "post" do - assert_select "input#order_member_id", :name => "order[member_id]" - end - end -end diff --git a/spec/views/orders/new.html.haml_spec.rb b/spec/views/orders/new.html.haml_spec.rb deleted file mode 100644 index 3e81fc519..000000000 --- a/spec/views/orders/new.html.haml_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe "orders/new" do - before(:each) do - assign(:order, stub_model(Order, - :member_id => "MyString" - ).as_new_record) - end - - it "renders new order form" do - render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => orders_path, :method => "post" do - assert_select "input#order_member_id", :name => "order[member_id]" - end - end -end From 86267025449f0d4b11951ebf841b4f1c4f25d5ff Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 15:43:18 +1000 Subject: [PATCH 029/184] Fixed type for member_id (should be integer) --- ...515054017_change_order_member_id_to_integer.rb | 5 +++++ db/schema.rb | 8 ++++---- spec/helpers/order_items_helper_spec.rb | 15 --------------- 3 files changed, 9 insertions(+), 19 deletions(-) create mode 100644 db/migrate/20130515054017_change_order_member_id_to_integer.rb delete mode 100644 spec/helpers/order_items_helper_spec.rb diff --git a/db/migrate/20130515054017_change_order_member_id_to_integer.rb b/db/migrate/20130515054017_change_order_member_id_to_integer.rb new file mode 100644 index 000000000..9ebb3b96a --- /dev/null +++ b/db/migrate/20130515054017_change_order_member_id_to_integer.rb @@ -0,0 +1,5 @@ +class ChangeOrderMemberIdToInteger < ActiveRecord::Migration + def change + change_column :orders, :member_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 56bbe7036..392658f7e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130515033842) do +ActiveRecord::Schema.define(:version => 20130515054017) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -131,9 +131,9 @@ ActiveRecord::Schema.define(:version => 20130515033842) do end create_table "orders", :force => true do |t| - t.string "member_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.integer "member_id", :limit => 255, :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.datetime "completed_at" end diff --git a/spec/helpers/order_items_helper_spec.rb b/spec/helpers/order_items_helper_spec.rb deleted file mode 100644 index 5c4f6ae82..000000000 --- a/spec/helpers/order_items_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the OrderItemsHelper. For example: -# -# describe OrderItemsHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end -describe OrderItemsHelper do - pending "add some examples to (or delete) #{__FILE__}" -end From 2955ca1559047b9cd9cb85a11ecc38edf6c755f8 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 15:51:51 +1000 Subject: [PATCH 030/184] delete order items if order is deleted --- app/models/order.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/order.rb b/app/models/order.rb index 3b524a97b..2171857f5 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -2,7 +2,7 @@ class Order < ActiveRecord::Base attr_accessible :member_id, :completed_at belongs_to :member - has_many :order_items + has_many :order_items, :dependent => :destroy default_scope order('created_at DESC') end From 3e1202cd623f288db133e0e77c882cb196456877 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 15:52:10 +1000 Subject: [PATCH 031/184] prettify display of orders --- app/controllers/order_items_controller.rb | 4 ++- app/controllers/orders_controller.rb | 3 ++- app/views/orders/index.html.haml | 25 ++++++++----------- .../order_items_controller_spec.rb | 12 ++++++++- spec/controllers/orders_controller_spec.rb | 10 +++++--- spec/views/orders/index.html.haml_spec.rb | 16 ++++++------ 6 files changed, 41 insertions(+), 29 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index 58e17825b..9da704fab 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -1,9 +1,10 @@ class OrderItemsController < ApplicationController load_and_authorize_resource + # GET /order_items # GET /order_items.json def index - @order_items = OrderItem.all + @order_items = OrderItem.joins(:order).where(:orders => {:member_id => current_member.id}) respond_to do |format| format.html # index.html.erb @@ -42,6 +43,7 @@ class OrderItemsController < ApplicationController # POST /order_items.json def create @order_item = OrderItem.new(params[:order_item]) + @order_item.order = current_member.current_order || Order.create(:member_id => current_member.id) respond_to do |format| if @order_item.save diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index ff6f0a9fb..f8b24442d 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -1,9 +1,10 @@ class OrdersController < ApplicationController load_and_authorize_resource + # GET /orders # GET /orders.json def index - @orders = Order.all + @orders = Order.find_all_by_member_id(current_member.id) respond_to do |format| format.html # index.html.erb diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml index 4384b82fc..d3b5dee7f 100644 --- a/app/views/orders/index.html.haml +++ b/app/views/orders/index.html.haml @@ -1,19 +1,16 @@ -%h1 Listing orders +- content_for :title, "Order History" -%table +%table.table-striped %tr - %th Member + %th Order number + %th Order began + %th Order completed + %th Number of items %th - %th - %th - - @orders.each do |order| %tr - %td= order.member_id - %td= link_to 'Show', order - %td= link_to 'Edit', edit_order_path(order) - %td= link_to 'Destroy', order, :method => :delete, :data => { :confirm => 'Are you sure?' } - -%br - -= link_to 'New Order', new_order_path + %td= order.id + %td= order.created_at.to_s + %td= order.completed_at.to_s + %td= order.order_items.count + %td= link_to 'Show', order, :class => 'btn btn-small' diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index ff0740951..801b687f4 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -10,7 +10,10 @@ describe OrderItemsController do describe "GET index" do it "assigns all order_items as @order_items" do - order_item = OrderItem.create! valid_attributes + member = FactoryGirl.create(:member) + sign_in member + order = FactoryGirl.create(:order, :member_id => member.id) + order_item = FactoryGirl.create(:order_item, :order_id => order.id) get :index, {} assigns(:order_items).should eq([order_item]) end @@ -57,6 +60,13 @@ describe OrderItemsController do post :create, {:order_item => valid_attributes} response.should redirect_to(OrderItem.last) end + + it 'creates an order for you' do + expect { + post :create, {:order_item => valid_attributes} + }.to change(Order, :count).by(1) + OrderItem.last.order.should be_an_instance_of Order + end end describe "with invalid params" do diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb index 7c1c2ae7b..fe3557556 100644 --- a/spec/controllers/orders_controller_spec.rb +++ b/spec/controllers/orders_controller_spec.rb @@ -5,7 +5,7 @@ describe OrdersController do login_member(:admin_member) def valid_attributes - { "member_id" => "MyString" } + { "member_id" => 1 } end def valid_session @@ -14,7 +14,9 @@ describe OrdersController do describe "GET index" do it "assigns all orders as @orders" do - order = Order.create! valid_attributes + member = FactoryGirl.create(:member) + sign_in member + order = Order.create!(:member_id => member.id) get :index, {} assigns(:orders).should eq([order]) end @@ -22,7 +24,9 @@ describe OrdersController do describe "GET show" do it "assigns the requested order as @order" do - order = Order.create! valid_attributes + member = FactoryGirl.create(:member) + sign_in member + order = Order.create!(:member_id => member.id) get :show, {:id => order.to_param} assigns(:order).should eq(order) end diff --git a/spec/views/orders/index.html.haml_spec.rb b/spec/views/orders/index.html.haml_spec.rb index 23c957236..139efab9b 100644 --- a/spec/views/orders/index.html.haml_spec.rb +++ b/spec/views/orders/index.html.haml_spec.rb @@ -2,19 +2,17 @@ require 'spec_helper' describe "orders/index" do before(:each) do - assign(:orders, [ - stub_model(Order, - :member_id => "Member" - ), - stub_model(Order, - :member_id => "Member" - ) - ]) + @member = FactoryGirl.create(:member) + sign_in @member + @order1 = FactoryGirl.create(:order) + @order2 = FactoryGirl.create(:completed_order) + assign(:orders, [ @order1, @order2 ]) end it "renders a list of orders" do render # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "tr>td", :text => "Member".to_s, :count => 2 + assert_select "tr>td", :text => @order1.id + assert_select "tr>td", :text => @order2.id end end From c96876feeb6e26527fb9f9a04f04c949e4a675a9 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 21:26:01 +1000 Subject: [PATCH 032/184] prettify order display --- app/views/orders/index.html.haml | 32 +++++++++---- app/views/orders/show.html.haml | 58 +++++++++++++++++++++--- app/views/shop/index.html.haml | 5 +- spec/views/orders/show.html.haml_spec.rb | 29 +++++++++--- 4 files changed, 101 insertions(+), 23 deletions(-) diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml index d3b5dee7f..6626b0f2f 100644 --- a/app/views/orders/index.html.haml +++ b/app/views/orders/index.html.haml @@ -1,16 +1,32 @@ - content_for :title, "Order History" -%table.table-striped +%p + Your order history shows what you have bought via our + =succeed "." do + =link_to "shop", shop_path + +%table.table.table-striped %tr %th Order number - %th Order began - %th Order completed - %th Number of items + %th Date completed + %th Items %th - @orders.each do |order| %tr %td= order.id - %td= order.created_at.to_s - %td= order.completed_at.to_s - %td= order.order_items.count - %td= link_to 'Show', order, :class => 'btn btn-small' + %td + - if order.completed_at + = order.completed_at.to_s + - else + In progress + %td + - if order.order_items.count > 0 + - order.order_items.each do |o| + = o.quantity + x + = o.product.name + @ + = number_with_precision(o.price, :precision => 2) + = Growstuff::Application.config.currency + %br/ + %td= link_to 'Details', order, :class => 'btn btn-mini' diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 9c484a59d..262fb1149 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -1,9 +1,55 @@ -%p#notice= notice +-content_for :title, "Order details (##{@order.id})" %p - %b Member: - = @order.member_id + %strong Order number: + = @order.id -= link_to 'Edit', edit_order_path(@order) -\| -= link_to 'Back', orders_path +%p + %strong Ordered by: + =link_to @order.member, @order.member + +%p + %strong Date begun: + = @order.created_at.to_s + +%p + %strong Date completed: + - if @order.completed_at + = @order.completed_at.to_s + - else + Not completed. + +%h2 Order items + +%table.table.table-striped + %tr + %th Product + %th Price + %th Quantity + %th Subtotal + - total = 0 + - @order.order_items.each do |i| + %tr + %td= i.product.name + %td + = number_with_precision(i.price, :precision => 2) + = Growstuff::Application.config.currency + %td= i.quantity + %td + - subtotal = i.price * i.quantity + - total += subtotal + = number_with_precision(subtotal, :precision => 2) + = Growstuff::Application.config.currency + + %tr + %td + %td + %td + %strong Total: + %td + %strong + = number_with_precision(total, :precision => 2) + = Growstuff::Application.config.currency + +%p + = link_to "View other orders/order history", orders_path diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 66bad7280..566644663 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -22,8 +22,8 @@ %div Base price: - #{ number_with_precision(p.min_price, :precision => 2) } - #{ Growstuff::Application.config.currency } + = number_with_precision(p.min_price, :precision => 2) + = Growstuff::Application.config.currency %div - if can? :create, Order @@ -35,3 +35,4 @@ =link_to "sign up", new_member_registration_path to purchase. + diff --git a/spec/views/orders/show.html.haml_spec.rb b/spec/views/orders/show.html.haml_spec.rb index e342fee26..89971acd2 100644 --- a/spec/views/orders/show.html.haml_spec.rb +++ b/spec/views/orders/show.html.haml_spec.rb @@ -2,14 +2,29 @@ require 'spec_helper' describe "orders/show" do before(:each) do - @order = assign(:order, stub_model(Order, - :member_id => "Member" - )) + @member = FactoryGirl.create(:member) + sign_in @member + controller.stub(:current_user) { @member } + @order = assign(:order, FactoryGirl.create(:order, :member => @member)) + @order_item = FactoryGirl.create(:order_item, + :order => @order, + :quantity => 2, + :price => 99.00 + ) + render end - it "renders attributes in

" do - render - # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should match(/Member/) + it "displays order number" do + rendered.should contain "Order number" end + + it "shows order items in a table" do + assert_select "table>tr>th", :text => "Product" + end + + it "shows the total" do + rendered.should contain "Total:" + rendered.should contain "198.00" + end + end From abcba35a71eb46bb669beb877b36a8af3d09bf9e Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 23:09:35 +1000 Subject: [PATCH 033/184] Converted prices to integers/cents to avoid floating point trouble --- app/helpers/application_helper.rb | 10 +++++++ app/models/product.rb | 1 + app/views/orders/index.html.haml | 3 +- app/views/orders/show.html.haml | 9 ++---- app/views/shop/index.html.haml | 3 +- ...0130515122301_change_prices_to_integers.rb | 11 ++++++++ db/schema.rb | 28 ++----------------- spec/factories/order_items.rb | 2 +- spec/factories/products.rb | 2 +- spec/helpers/application_helper.rb | 7 +++++ spec/models/product_spec.rb | 1 - spec/views/orders/show.html.haml_spec.rb | 2 +- 12 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 app/helpers/application_helper.rb create mode 100644 db/migrate/20130515122301_change_prices_to_integers.rb create mode 100644 spec/helpers/application_helper.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 000000000..b9d601cfc --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,10 @@ +module ApplicationHelper + + # 999 cents becomes 9.99 AUD -- for products/orders/etc + def format_price(price) + return sprintf('%.2f %s', price / 100.0, + Growstuff::Application.config.currency) + end + +end + diff --git a/app/models/product.rb b/app/models/product.rb index cc20fe696..5ad15a2e8 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -1,4 +1,5 @@ class Product < ActiveRecord::Base attr_accessible :description, :min_price, :name has_and_belongs_to_many :orders + end diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml index 6626b0f2f..f3b5ca922 100644 --- a/app/views/orders/index.html.haml +++ b/app/views/orders/index.html.haml @@ -26,7 +26,6 @@ x = o.product.name @ - = number_with_precision(o.price, :precision => 2) - = Growstuff::Application.config.currency + = format_price(o.price) %br/ %td= link_to 'Details', order, :class => 'btn btn-mini' diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 262fb1149..e18fddc18 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -32,14 +32,12 @@ %tr %td= i.product.name %td - = number_with_precision(i.price, :precision => 2) - = Growstuff::Application.config.currency + = format_price(i.price) %td= i.quantity %td - subtotal = i.price * i.quantity - total += subtotal - = number_with_precision(subtotal, :precision => 2) - = Growstuff::Application.config.currency + = format_price(subtotal) %tr %td @@ -48,8 +46,7 @@ %strong Total: %td %strong - = number_with_precision(total, :precision => 2) - = Growstuff::Application.config.currency + = format_price(total) %p = link_to "View other orders/order history", orders_path diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 566644663..89eea100e 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -22,8 +22,7 @@ %div Base price: - = number_with_precision(p.min_price, :precision => 2) - = Growstuff::Application.config.currency + =format_price(p.min_price) %div - if can? :create, Order diff --git a/db/migrate/20130515122301_change_prices_to_integers.rb b/db/migrate/20130515122301_change_prices_to_integers.rb new file mode 100644 index 000000000..f07d45dea --- /dev/null +++ b/db/migrate/20130515122301_change_prices_to_integers.rb @@ -0,0 +1,11 @@ +class ChangePricesToIntegers < ActiveRecord::Migration + def up + change_column :order_items, :price, :integer + change_column :products, :min_price, :integer + end + + def down + change_column :order_items, :price, :decimal + change_column :products, :min_price, :decimal + end +end diff --git a/db/schema.rb b/db/schema.rb index 392658f7e..d4bf0005f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130515054017) do +ActiveRecord::Schema.define(:version => 20130515122301) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -40,7 +40,6 @@ ActiveRecord::Schema.define(:version => 20130515054017) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.string "slug" - t.integer "parent_id" end add_index "crops", ["slug"], :name => "index_crops_on_slug", :unique => true @@ -124,7 +123,7 @@ ActiveRecord::Schema.define(:version => 20130515054017) do create_table "order_items", :force => true do |t| t.integer "order_id" t.integer "product_id" - t.decimal "price" + t.integer "price" t.integer "quantity" t.datetime "created_at", :null => false t.datetime "updated_at", :null => false @@ -142,27 +141,6 @@ ActiveRecord::Schema.define(:version => 20130515054017) do t.integer "product_id" end - create_table "payments", :force => true do |t| - t.integer "payer_id" - t.string "payment_type" - t.decimal "amount" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "photos", :force => true do |t| - t.integer "owner_id", :null => false - t.integer "flickr_photo_id", :null => false - t.string "thumbnail_url", :null => false - t.string "fullsize_url", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "title", :null => false - t.string "license_name", :null => false - t.string "license_url" - t.string "link_url", :null => false - end - create_table "plantings", :force => true do |t| t.integer "garden_id", :null => false t.integer "crop_id", :null => false @@ -193,7 +171,7 @@ ActiveRecord::Schema.define(:version => 20130515054017) do create_table "products", :force => true do |t| t.string "name", :null => false t.string "description", :null => false - t.decimal "min_price", :null => false + t.integer "min_price", :null => false t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end diff --git a/spec/factories/order_items.rb b/spec/factories/order_items.rb index d99c324a5..94e001450 100644 --- a/spec/factories/order_items.rb +++ b/spec/factories/order_items.rb @@ -4,7 +4,7 @@ FactoryGirl.define do factory :order_item do order product - price "9.99" + price "999" quantity 1 end end diff --git a/spec/factories/products.rb b/spec/factories/products.rb index 405924518..84ca3163f 100644 --- a/spec/factories/products.rb +++ b/spec/factories/products.rb @@ -4,6 +4,6 @@ FactoryGirl.define do factory :product do name "annual subscription" description "paid membership, renewing yearly" - min_price "9.99" + min_price "999" end end diff --git a/spec/helpers/application_helper.rb b/spec/helpers/application_helper.rb new file mode 100644 index 000000000..c1a9fcfb2 --- /dev/null +++ b/spec/helpers/application_helper.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe ApplicationHelper do + it "formats prices" do + format_price(999).should eq '9.99 AUD' + end +end diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb index 267720887..88c9c9938 100644 --- a/spec/models/product_spec.rb +++ b/spec/models/product_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' describe Product do - pending "add some examples to (or delete) #{__FILE__}" end diff --git a/spec/views/orders/show.html.haml_spec.rb b/spec/views/orders/show.html.haml_spec.rb index 89971acd2..75915bffb 100644 --- a/spec/views/orders/show.html.haml_spec.rb +++ b/spec/views/orders/show.html.haml_spec.rb @@ -9,7 +9,7 @@ describe "orders/show" do @order_item = FactoryGirl.create(:order_item, :order => @order, :quantity => 2, - :price => 99.00 + :price => 9900 ) render end From af399311b2eb5af3e726c7c6cc2271cfd3b38538 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 15 May 2013 23:17:05 +1000 Subject: [PATCH 034/184] validate order_item.price must be > product.min_price --- app/models/order_item.rb | 8 ++++++++ spec/models/order_item_spec.rb | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/app/models/order_item.rb b/app/models/order_item.rb index 250723909..caf3e85f6 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -4,4 +4,12 @@ class OrderItem < ActiveRecord::Base belongs_to :order belongs_to :product + validate :price_must_be_greater_than_minimum + + def price_must_be_greater_than_minimum + @product = Product.find(product) + if price < @product.min_price + errors.add(:discount, "must be greater than the product's minimum value") + end + end end diff --git a/spec/models/order_item_spec.rb b/spec/models/order_item_spec.rb index 959ea5fc0..b4eea79da 100644 --- a/spec/models/order_item_spec.rb +++ b/spec/models/order_item_spec.rb @@ -10,4 +10,10 @@ describe OrderItem do @order_item.product.should be_an_instance_of Product end + it "validates price > product.min_price" do + @product = FactoryGirl.create(:product) + @order_item = FactoryGirl.build(:order_item, :price => @product.min_price - 1) + @order_item.should_not be_valid + end + end From c13ae183ec1fe77709311b5812327ecdaad5cba9 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 16 May 2013 00:24:51 +1000 Subject: [PATCH 035/184] order things direct from the /shop page --- app/controllers/order_items_controller.rb | 28 +---- app/controllers/shop_controller.rb | 1 + app/helpers/application_helper.rb | 6 +- app/models/order_item.rb | 4 +- app/views/order_items/index.html.haml | 25 ----- app/views/order_items/show.html.haml | 18 --- app/views/orders/index.html.haml | 2 +- app/views/orders/show.html.haml | 6 +- app/views/shop/index.html.haml | 19 +++- .../order_items_controller_spec.rb | 106 +++++++++--------- spec/factories/order_items.rb | 2 +- spec/helpers/application_helper.rb | 3 +- .../views/order_items/index.html.haml_spec.rb | 29 ----- spec/views/order_items/show.html.haml_spec.rb | 21 ---- spec/views/products/index.html.haml_spec.rb | 20 +--- spec/views/products/show.html.haml_spec.rb | 12 +- spec/views/shop/index_spec.rb | 3 +- 17 files changed, 101 insertions(+), 204 deletions(-) delete mode 100644 app/views/order_items/index.html.haml delete mode 100644 app/views/order_items/show.html.haml delete mode 100644 spec/views/order_items/index.html.haml_spec.rb delete mode 100644 spec/views/order_items/show.html.haml_spec.rb diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index 9da704fab..ee7b02d49 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -1,28 +1,6 @@ class OrderItemsController < ApplicationController load_and_authorize_resource - # GET /order_items - # GET /order_items.json - def index - @order_items = OrderItem.joins(:order).where(:orders => {:member_id => current_member.id}) - - respond_to do |format| - format.html # index.html.erb - format.json { render json: @order_items } - end - end - - # GET /order_items/1 - # GET /order_items/1.json - def show - @order_item = OrderItem.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.json { render json: @order_item } - end - end - # GET /order_items/new # GET /order_items/new.json def new @@ -43,11 +21,12 @@ class OrderItemsController < ApplicationController # POST /order_items.json def create @order_item = OrderItem.new(params[:order_item]) + @order_item.price = @order_item.price.to_f * 100 # convert to cents @order_item.order = current_member.current_order || Order.create(:member_id => current_member.id) respond_to do |format| if @order_item.save - format.html { redirect_to @order_item, notice: 'Order item was successfully created.' } + format.html { redirect_to @order_item.order, notice: 'Added item to your order.' } format.json { render json: @order_item, status: :created, location: @order_item } else format.html { render action: "new" } @@ -60,10 +39,11 @@ class OrderItemsController < ApplicationController # PUT /order_items/1.json def update @order_item = OrderItem.find(params[:id]) + @order_item.price = @order_item.price.to_f * 100 # convert to cents respond_to do |format| if @order_item.update_attributes(params[:order_item]) - format.html { redirect_to @order_item, notice: 'Order item was successfully updated.' } + format.html { redirect_to @order_item.order, notice: 'Order item was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 50c5c67a0..0f07c2125 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -1,6 +1,7 @@ class ShopController < ApplicationController def index @products = Product.all + @order_item = OrderItem.new respond_to do |format| format.html # index.html.haml end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b9d601cfc..51da9b2b3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,7 +1,11 @@ module ApplicationHelper + def price_in_dollars(price) + return sprintf('%.2f', price / 100.0) + end + # 999 cents becomes 9.99 AUD -- for products/orders/etc - def format_price(price) + def price_with_currency(price) return sprintf('%.2f %s', price / 100.0, Growstuff::Application.config.currency) end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index caf3e85f6..c3f51def5 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -7,9 +7,9 @@ class OrderItem < ActiveRecord::Base validate :price_must_be_greater_than_minimum def price_must_be_greater_than_minimum - @product = Product.find(product) + @product = Product.find(product_id) if price < @product.min_price - errors.add(:discount, "must be greater than the product's minimum value") + errors.add(:price, "must be greater than the product's minimum value") end end end diff --git a/app/views/order_items/index.html.haml b/app/views/order_items/index.html.haml deleted file mode 100644 index 6b63af7f9..000000000 --- a/app/views/order_items/index.html.haml +++ /dev/null @@ -1,25 +0,0 @@ -%h1 Listing order_items - -%table - %tr - %th Order - %th Product - %th Price - %th Quantity - %th - %th - %th - - - @order_items.each do |order_item| - %tr - %td= order_item.order_id - %td= order_item.product_id - %td= order_item.price - %td= order_item.quantity - %td= link_to 'Show', order_item - %td= link_to 'Edit', edit_order_item_path(order_item) - %td= link_to 'Destroy', order_item, :method => :delete, :data => { :confirm => 'Are you sure?' } - -%br - -= link_to 'New Order item', new_order_item_path diff --git a/app/views/order_items/show.html.haml b/app/views/order_items/show.html.haml deleted file mode 100644 index 16551615a..000000000 --- a/app/views/order_items/show.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -%p#notice= notice - -%p - %b Order: - = @order_item.order_id -%p - %b Product: - = @order_item.product_id -%p - %b Price: - = @order_item.price -%p - %b Quantity: - = @order_item.quantity - -= link_to 'Edit', edit_order_item_path(@order_item) -\| -= link_to 'Back', order_items_path diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml index f3b5ca922..6e1302765 100644 --- a/app/views/orders/index.html.haml +++ b/app/views/orders/index.html.haml @@ -26,6 +26,6 @@ x = o.product.name @ - = format_price(o.price) + = price_with_currency(o.price) %br/ %td= link_to 'Details', order, :class => 'btn btn-mini' diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index e18fddc18..0cd1efe89 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -32,12 +32,12 @@ %tr %td= i.product.name %td - = format_price(i.price) + = price_with_currency(i.price) %td= i.quantity %td - subtotal = i.price * i.quantity - total += subtotal - = format_price(subtotal) + = price_with_currency(subtotal) %tr %td @@ -46,7 +46,7 @@ %strong Total: %td %strong - = format_price(total) + = price_with_currency(total) %p = link_to "View other orders/order history", orders_path diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 89eea100e..6c45b33f3 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -20,13 +20,24 @@ :markdown #{ strip_tags p.description } - %div - Base price: - =format_price(p.min_price) + %p + Pay what you want, starting at + =succeed "." do + =price_with_currency(p.min_price) %div - if can? :create, Order - %p This is the form for buying a product. + + = form_for @order_item do |f| + .field + = f.text_field :price, :value => price_in_dollars(p.min_price) + .field + = f.hidden_field :product_id, :value => p.id + .field + = f.hidden_field :quantity, :value => 1 + .actions + = f.submit 'Buy', :class => 'btn btn-primary' + - else Please =link_to "sign in", new_member_session_path diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index 801b687f4..b99bfae80 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -4,27 +4,16 @@ describe OrderItemsController do login_member(:admin_member) - def valid_attributes - { "order_id" => "1", "product_id" => "1" } - end - - describe "GET index" do - it "assigns all order_items as @order_items" do - member = FactoryGirl.create(:member) - sign_in member - order = FactoryGirl.create(:order, :member_id => member.id) - order_item = FactoryGirl.create(:order_item, :order_id => order.id) - get :index, {} - assigns(:order_items).should eq([order_item]) - end - end - - describe "GET show" do - it "assigns the requested order_item as @order_item" do - order_item = OrderItem.create! valid_attributes - get :show, {:id => order_item.to_param} - assigns(:order_item).should eq(order_item) - end + before(:each) do + @member = FactoryGirl.create(:member) + sign_in @member + @product = FactoryGirl.create(:product) + @order = FactoryGirl.create(:order, :member => @member) + @order_item = FactoryGirl.create(:order_item, + :order => @order, + :product => @product, + :price => @product.min_price + ) end describe "GET new" do @@ -36,34 +25,52 @@ describe OrderItemsController do describe "GET edit" do it "assigns the requested order_item as @order_item" do - order_item = OrderItem.create! valid_attributes - get :edit, {:id => order_item.to_param} - assigns(:order_item).should eq(order_item) + get :edit, {:id => @order_item.to_param} + assigns(:order_item).should eq(@order_item) end end describe "POST create" do + describe "with valid params" do it "creates a new OrderItem" do expect { - post :create, {:order_item => valid_attributes} + post :create, {:order_item => { + :order_id => @order.id, + :product_id => @product.id, + :price => @product.min_price + }} }.to change(OrderItem, :count).by(1) end it "assigns a newly created order_item as @order_item" do - post :create, {:order_item => valid_attributes} + post :create, {:order_item => { + :order_id => @order.id, + :product_id => @product.id, + :price => @product.min_price + }} assigns(:order_item).should be_a(OrderItem) assigns(:order_item).should be_persisted end it "redirects to the created order_item" do - post :create, {:order_item => valid_attributes} - response.should redirect_to(OrderItem.last) + post :create, {:order_item => { + :order_id => @order.id, + :product_id => @product.id, + :price => @product.min_price + }} + response.should redirect_to(OrderItem.last.order) end it 'creates an order for you' do + @member = FactoryGirl.create(:member) + sign_in @member + @product = FactoryGirl.create(:product) expect { - post :create, {:order_item => valid_attributes} + post :create, {:order_item => { + :product_id => @product.id, + :price => @product.min_price + }} }.to change(Order, :count).by(1) OrderItem.last.order.should be_an_instance_of Order end @@ -87,44 +94,45 @@ describe OrderItemsController do end describe "PUT update" do + describe "with valid params" do + it "updates the requested order_item" do - order_item = OrderItem.create! valid_attributes - # Assuming there are no other order_items in the database, this - # specifies that the OrderItem created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. OrderItem.any_instance.should_receive(:update_attributes).with({ "order_id" => "1" }) - put :update, {:id => order_item.to_param, :order_item => { "order_id" => "1" }} + put :update, {:id => @order_item.to_param, :order_item => { "order_id" => "1" }} end it "assigns the requested order_item as @order_item" do - order_item = OrderItem.create! valid_attributes - put :update, {:id => order_item.to_param, :order_item => valid_attributes} - assigns(:order_item).should eq(order_item) + put :update, {:id => @order_item.to_param, :order_item => { + :order_id => @order.id, + :product_id => @product.id, + :price => @product.min_price + }} + assigns(:order_item).should eq(@order_item) end it "redirects to the order_item" do - order_item = OrderItem.create! valid_attributes - put :update, {:id => order_item.to_param, :order_item => valid_attributes} - response.should redirect_to(order_item) + put :update, {:id => @order_item.to_param, :order_item => { + :order_id => @order.id, + :product_id => @product.id, + :price => @product.min_price + }} + response.should redirect_to(@order_item.order) end end describe "with invalid params" do it "assigns the order_item as @order_item" do - order_item = OrderItem.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted OrderItem.any_instance.stub(:save).and_return(false) - put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }} - assigns(:order_item).should eq(order_item) + put :update, {:id => @order_item.to_param, :order_item => { "order_id" => "invalid value" }} + assigns(:order_item).should eq(@order_item) end it "re-renders the 'edit' template" do - order_item = OrderItem.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted OrderItem.any_instance.stub(:save).and_return(false) - put :update, {:id => order_item.to_param, :order_item => { "order_id" => "invalid value" }} + put :update, {:id => @order_item.to_param, :order_item => { "order_id" => "invalid value" }} response.should render_template("edit") end end @@ -132,15 +140,13 @@ describe OrderItemsController do describe "DELETE destroy" do it "destroys the requested order_item" do - order_item = OrderItem.create! valid_attributes expect { - delete :destroy, {:id => order_item.to_param} + delete :destroy, {:id => @order_item.to_param} }.to change(OrderItem, :count).by(-1) end it "redirects to the order_items list" do - order_item = OrderItem.create! valid_attributes - delete :destroy, {:id => order_item.to_param} + delete :destroy, {:id => @order_item.to_param} response.should redirect_to(order_items_url) end end diff --git a/spec/factories/order_items.rb b/spec/factories/order_items.rb index 94e001450..f3b6f04ef 100644 --- a/spec/factories/order_items.rb +++ b/spec/factories/order_items.rb @@ -5,6 +5,6 @@ FactoryGirl.define do order product price "999" - quantity 1 + quantity 42 end end diff --git a/spec/helpers/application_helper.rb b/spec/helpers/application_helper.rb index c1a9fcfb2..c65d0a8aa 100644 --- a/spec/helpers/application_helper.rb +++ b/spec/helpers/application_helper.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe ApplicationHelper do it "formats prices" do - format_price(999).should eq '9.99 AUD' + price_in_dollars(999).should eq '9.99' + price_with_currency(999).should eq '9.99 AUD' end end diff --git a/spec/views/order_items/index.html.haml_spec.rb b/spec/views/order_items/index.html.haml_spec.rb deleted file mode 100644 index 0b4bdfb3d..000000000 --- a/spec/views/order_items/index.html.haml_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'spec_helper' - -describe "order_items/index" do - before(:each) do - assign(:order_items, [ - stub_model(OrderItem, - :order_id => 1, - :product_id => 2, - :price => "9.99", - :quantity => 3 - ), - stub_model(OrderItem, - :order_id => 1, - :product_id => 2, - :price => "9.99", - :quantity => 3 - ) - ]) - end - - it "renders a list of order_items" do - render - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "tr>td", :text => 1.to_s, :count => 2 - assert_select "tr>td", :text => 2.to_s, :count => 2 - assert_select "tr>td", :text => "9.99".to_s, :count => 2 - assert_select "tr>td", :text => 3.to_s, :count => 2 - end -end diff --git a/spec/views/order_items/show.html.haml_spec.rb b/spec/views/order_items/show.html.haml_spec.rb deleted file mode 100644 index 8a445309a..000000000 --- a/spec/views/order_items/show.html.haml_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'spec_helper' - -describe "order_items/show" do - before(:each) do - @order_item = assign(:order_item, stub_model(OrderItem, - :order_id => 1, - :product_id => 2, - :price => "9.99", - :quantity => 3 - )) - end - - it "renders attributes in

" do - render - # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should match(/1/) - rendered.should match(/2/) - rendered.should match(/9.99/) - rendered.should match(/3/) - end -end diff --git a/spec/views/products/index.html.haml_spec.rb b/spec/views/products/index.html.haml_spec.rb index 5f300415a..0e18b8ddc 100644 --- a/spec/views/products/index.html.haml_spec.rb +++ b/spec/views/products/index.html.haml_spec.rb @@ -2,25 +2,15 @@ require 'spec_helper' describe "products/index" do before(:each) do - assign(:products, [ - stub_model(Product, - :name => "Name", - :description => "Description", - :min_price => "9.99" - ), - stub_model(Product, - :name => "Name", - :description => "Description", - :min_price => "9.99" - ) - ]) + @product = FactoryGirl.create(:product) + assign(:products, [@product, @product]) end it "renders a list of products" do render # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "tr>td", :text => "Name".to_s, :count => 2 - assert_select "tr>td", :text => "Description".to_s, :count => 2 - assert_select "tr>td", :text => "9.99".to_s, :count => 2 + assert_select "tr>td", :text => @product.name, :count => 2 + assert_select "tr>td", :text => @product.description, :count => 2 + assert_select "tr>td", :text => @product.min_price, :count => 2 end end diff --git a/spec/views/products/show.html.haml_spec.rb b/spec/views/products/show.html.haml_spec.rb index b49bbbae2..19d8256d7 100644 --- a/spec/views/products/show.html.haml_spec.rb +++ b/spec/views/products/show.html.haml_spec.rb @@ -2,18 +2,14 @@ require 'spec_helper' describe "products/show" do before(:each) do - @product = assign(:product, stub_model(Product, - :name => "Name", - :description => "Description", - :min_price => "9.99" - )) + @product = assign(:product, FactoryGirl.create(:product)) end it "renders attributes in

" do render # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should match(/Name/) - rendered.should match(/Description/) - rendered.should match(/9.99/) + rendered.should contain @product.name + rendered.should contain @product.description + rendered.should contain @product.min_price.to_s end end diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index 1e7cbf071..bef3ba4fb 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -5,6 +5,7 @@ describe 'shop/index.html.haml', :type => "view" do @product1 = FactoryGirl.create(:product) @product2 = FactoryGirl.create(:product) assign(:products, [@product1, @product2]) + assign(:order_item, OrderItem.new) end context "signed in" do @@ -23,7 +24,7 @@ describe 'shop/index.html.haml', :type => "view" do end it 'displays the order form' do - rendered.should contain 'This is the form' + assert_select "form", :count => 2 end end From a5e022790da1a076cee33e259a73ace2c86bd500 Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 16 May 2013 17:42:15 +1000 Subject: [PATCH 036/184] added shop link to header --- app/views/layouts/_header.html.haml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 2db2dea6f..da6b91970 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -12,6 +12,7 @@ %li= link_to "Members", members_path %li= link_to "Posts", posts_path %li= link_to "Forums", forums_path + %li= link_to "Shop", shop_path %li.divider-vertical - if member_signed_in? %li.dropdown< From 4dca148cbfb1d87c413072a2d742f861cf9f397d Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 16 May 2013 20:58:43 +1000 Subject: [PATCH 037/184] only let them order one thing (for now) --- app/controllers/shop_controller.rb | 16 ++++++ app/models/product.rb | 3 ++ app/views/shop/index.html.haml | 64 +++++++++++++++--------- spec/controllers/shop_controller_spec.rb | 19 +++++++ spec/models/product_spec.rb | 5 ++ 5 files changed, 82 insertions(+), 25 deletions(-) diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index 0f07c2125..d59a6428a 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -2,6 +2,22 @@ class ShopController < ApplicationController def index @products = Product.all @order_item = OrderItem.new + + # this is (hopefully) part of a short-term hack to prevent people from + # ordering multiple subscriptions, which would be very confusing to deal + # with. We check whether they have an order already in progress, and if + # so, point that out to them and encourage them to checkout, rather than + # letting them add more stuff to their order. + + @order = nil + @most_recent_item = nil + if current_member + @order = current_member.current_order + if @order + @most_recent_item = @order.order_items.first + end + end + respond_to do |format| format.html # index.html.haml end diff --git a/app/models/product.rb b/app/models/product.rb index 5ad15a2e8..88ee157b0 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -2,4 +2,7 @@ class Product < ActiveRecord::Base attr_accessible :description, :min_price, :name has_and_belongs_to_many :orders + def to_s + name + end end diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 6c45b33f3..52a280571 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -13,36 +13,50 @@ you want to pay. Remember, your subscription supports an open source platform promoting sustainable lifestyles! -- @products.each do |p| - %h2= p.name +- if @order and @order.order_items.count > 0 + %h2 Your current order - %div - :markdown - #{ strip_tags p.description } + %p + You currently have the following item in your cart: + %strong + = @most_recent_item.product + @ + = price_with_currency(@most_recent_item.price) %p - Pay what you want, starting at - =succeed "." do - =price_with_currency(p.min_price) + = link_to "View order and checkout", order_path(@order) - %div - - if can? :create, Order +- else + - @products.each do |p| + %h2= p.name - = form_for @order_item do |f| - .field - = f.text_field :price, :value => price_in_dollars(p.min_price) - .field - = f.hidden_field :product_id, :value => p.id - .field - = f.hidden_field :quantity, :value => 1 - .actions - = f.submit 'Buy', :class => 'btn btn-primary' + %div + :markdown + #{ strip_tags p.description } - - else - Please - =link_to "sign in", new_member_session_path - or - =link_to "sign up", new_member_registration_path - to purchase. + %p + Pay what you want, starting at + =succeed "." do + =price_with_currency(p.min_price) + + %div + - if can? :create, Order + + = form_for @order_item do |f| + .field + = f.text_field :price, :value => price_in_dollars(p.min_price) + .field + = f.hidden_field :product_id, :value => p.id + .field + = f.hidden_field :quantity, :value => 1 + .actions + = f.submit 'Buy', :class => 'btn btn-primary' + + - else + Please + =link_to "sign in", new_member_session_path + or + =link_to "sign up", new_member_registration_path + to purchase. diff --git a/spec/controllers/shop_controller_spec.rb b/spec/controllers/shop_controller_spec.rb index fad178cc6..2aaaa58ff 100644 --- a/spec/controllers/shop_controller_spec.rb +++ b/spec/controllers/shop_controller_spec.rb @@ -12,6 +12,25 @@ describe ShopController do get :index, {} assigns(:products).should eq([@product1, @product2]) end + + it "assigns a new @order_item to build forms" do + get :index, {} + assigns(:order_item).should be_an_instance_of OrderItem + end + + it "assigns @order as nil if the user doesn't have one" do + get :index, {} + assigns(:order).should be_nil + end + + it "assigns @order as current_order if there is one" do + @member = FactoryGirl.create(:member) + sign_in @member + @order = FactoryGirl.create(:order, :member => @member) + get :index, {} + assigns(:order).should eq @order + end + end end diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb index 88c9c9938..d8fc496a8 100644 --- a/spec/models/product_spec.rb +++ b/spec/models/product_spec.rb @@ -1,4 +1,9 @@ require 'spec_helper' describe Product do + + it "stringifies using the name" do + @product = FactoryGirl.create(:product) + @product.to_s.should eq @product.name + end end From 65431ed41f572bf13366ab4a7d6bfafe20fffcda Mon Sep 17 00:00:00 2001 From: Skud Date: Thu, 16 May 2013 21:39:17 +1000 Subject: [PATCH 038/184] some improvements to the HAML RSS --- app/views/members/show.rss.haml | 18 ++++++++--------- db/schema.rb | 35 ++++++++++++++++++++++++++------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/views/members/show.rss.haml b/app/views/members/show.rss.haml index f88f44db4..85a598de8 100644 --- a/app/views/members/show.rss.haml +++ b/app/views/members/show.rss.haml @@ -1,17 +1,15 @@ -%feed{:version => "2.0", "xmlns:atom"=>"http://www.w3.org/2005/Atom"} +%rss{:version => 2.0} %channel %title #{Growstuff::Application.config.site_name} - #{@member.login_name}'s recent posts - %atom:link= member_url(@member) - %atom:link{ :type => "application/rss+xml", :href => member_url(@member), :rel => "self" } - + %link= member_url(@member) - @posts.each do |post| - %entry - %updated post.created_at.to_s(:rfc822) - %link{ :href => post_url(post) } - %guid post_url(post) - %author @member.login_name - %title post.subject + %item + %pubDate= post.created_at.to_s(:rfc822) + %link= post_url(post) + %guid= post_url(post) + %author= @member.login_name + %title= post.subject %description :markdown #{ strip_tags post.body } diff --git a/db/schema.rb b/db/schema.rb index 4c82164fe..d4bf0005f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130418102558) do +ActiveRecord::Schema.define(:version => 20130515122301) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -120,12 +120,25 @@ ActiveRecord::Schema.define(:version => 20130418102558) do t.datetime "updated_at", :null => false end - create_table "payments", :force => true do |t| - t.integer "payer_id" - t.string "payment_type" - t.decimal "amount" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "order_items", :force => true do |t| + t.integer "order_id" + t.integer "product_id" + t.integer "price" + t.integer "quantity" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "orders", :force => true do |t| + t.integer "member_id", :limit => 255, :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.datetime "completed_at" + end + + create_table "orders_products", :id => false, :force => true do |t| + t.integer "order_id" + t.integer "product_id" end create_table "plantings", :force => true do |t| @@ -155,6 +168,14 @@ ActiveRecord::Schema.define(:version => 20130418102558) do add_index "posts", ["created_at", "author_id"], :name => "index_updates_on_created_at_and_user_id" add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true + create_table "products", :force => true do |t| + t.string "name", :null => false + t.string "description", :null => false + t.integer "min_price", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "roles", :force => true do |t| t.string "name", :null => false t.text "description" From 6b487ab1f604b690c7944bb9d1383f5c81179240 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Thu, 16 May 2013 15:08:50 +0100 Subject: [PATCH 039/184] Escaped_Markdown HAML filter, for use in RSS. I tried to do this using Haml::Filters::Markdown so it would be independent of our Markdown renderer, but I couldn't get Haml::Filters::Markdown.render to work programmatically - whatever I did, it returned its input unchanged. So I hardcoded a dependence on bluecloth. --- app/views/members/show.rss.haml | 2 +- config/initializers/escaped_markdown.rb | 1 + lib/haml/filters/escaped_markdown.rb | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 config/initializers/escaped_markdown.rb create mode 100644 lib/haml/filters/escaped_markdown.rb diff --git a/app/views/members/show.rss.haml b/app/views/members/show.rss.haml index 85a598de8..f6244623d 100644 --- a/app/views/members/show.rss.haml +++ b/app/views/members/show.rss.haml @@ -11,5 +11,5 @@ %author= @member.login_name %title= post.subject %description - :markdown + :escaped_markdown #{ strip_tags post.body } diff --git a/config/initializers/escaped_markdown.rb b/config/initializers/escaped_markdown.rb new file mode 100644 index 000000000..87837b0c5 --- /dev/null +++ b/config/initializers/escaped_markdown.rb @@ -0,0 +1 @@ +require 'haml/filters/escaped_markdown' diff --git a/lib/haml/filters/escaped_markdown.rb b/lib/haml/filters/escaped_markdown.rb new file mode 100644 index 000000000..5c172079c --- /dev/null +++ b/lib/haml/filters/escaped_markdown.rb @@ -0,0 +1,10 @@ +gem 'bluecloth' + +module Haml::Filters::Escaped_Markdown + include Haml::Filters::Base + + def render(text) + bc = BlueCloth.new(text) + return Haml::Helpers.html_escape bc.to_html + end +end From d5c781a97ff6636556c0b24b42c28284eea02b1d Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Thu, 16 May 2013 18:00:00 +0100 Subject: [PATCH 040/184] Tests for Haml::Filters::EscapedMarkdown. --- lib/haml/filters/escaped_markdown.rb | 19 +++++++++++++------ .../lib/haml/filters/escaped_markdown_spec.rb | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 spec/lib/haml/filters/escaped_markdown_spec.rb diff --git a/lib/haml/filters/escaped_markdown.rb b/lib/haml/filters/escaped_markdown.rb index 5c172079c..c60256ac9 100644 --- a/lib/haml/filters/escaped_markdown.rb +++ b/lib/haml/filters/escaped_markdown.rb @@ -1,10 +1,17 @@ -gem 'bluecloth' +require 'bluecloth' -module Haml::Filters::Escaped_Markdown - include Haml::Filters::Base +module Haml::Filters + module EscapedMarkdown + include Haml::Filters::Base - def render(text) - bc = BlueCloth.new(text) - return Haml::Helpers.html_escape bc.to_html + def render(text) + bc = BlueCloth.new(text) + return Haml::Helpers.html_escape bc.to_html + end end + +# Register it as the handler for the :escaped_markdown HAML command. +# The automatic system gives us :escapedmarkdown, which is ugly. +defined['escaped_markdown'] = EscapedMarkdown + end diff --git a/spec/lib/haml/filters/escaped_markdown_spec.rb b/spec/lib/haml/filters/escaped_markdown_spec.rb new file mode 100644 index 000000000..99c779e88 --- /dev/null +++ b/spec/lib/haml/filters/escaped_markdown_spec.rb @@ -0,0 +1,16 @@ +require 'haml/filters' +require 'haml/filters/escaped_markdown' +require 'haml/helpers' + +describe 'Haml::Filters::Escaped_Markdown' do + it 'is registered as the handler for :escaped_markdown' do + Haml::Filters::defined['escaped_markdown'].should == + Haml::Filters::EscapedMarkdown + end + + it 'converts Markdown to escaped HTML' do + rendered = Haml::Filters::EscapedMarkdown.render("**foo**") + rendered.should == "<p><strong>foo</strong></p>" + end + +end From 2d3b594ae160f80786d54de1af7c0e2348c8a444 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Thu, 16 May 2013 18:24:22 +0100 Subject: [PATCH 041/184] Test for expanded-markdown rendering. --- spec/views/members/show.rss.haml_spec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/views/members/show.rss.haml_spec.rb b/spec/views/members/show.rss.haml_spec.rb index a9a73686a..adb6e0bc3 100644 --- a/spec/views/members/show.rss.haml_spec.rb +++ b/spec/views/members/show.rss.haml_spec.rb @@ -5,7 +5,7 @@ describe 'members/show.rss.haml', :type => "view" do @member = assign(:member, FactoryGirl.create(:member)) assign(:posts, [ FactoryGirl.build(:post, :id => 1, :author => @member), - FactoryGirl.build(:post, :id => 2, :author => @member) + FactoryGirl.build(:markdown_post, :id => 2, :author => @member) ]) render end @@ -18,4 +18,10 @@ describe 'members/show.rss.haml', :type => "view" do rendered.should contain "This is some text." end + it 'renders post bodies to HTML and XML-escapes them' do +# The variable "rendered" has been entity-replaced and tag-stripped +# The literal string output contains "<strong>" etc. + rendered.should contain "strong" + end + end From 561dc837a7132cec8e37c730adefbeae54f1e81c Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 10:28:55 +1000 Subject: [PATCH 042/184] modify seeds.rb to use integer prices --- db/seeds.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index df347f6bd..ce30ed5c1 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -59,20 +59,15 @@ if Rails.env.development? or Rails.env.test? @wrangler_user.save! puts "Adding products..." - Product.create!( - :name => "Monthly subscription", - :description => "Paid account, renews monthly", - :min_price => 3.00 - ) Product.create!( :name => "Annual subscription", :description => "Paid account, renews yearly", - :min_price => 30.00 + :min_price => 3000 ) Product.create!( :name => "Seed account", :description => "Paid account, in perpetuity", - :min_price => 150.00 + :min_price => 15000 ) end From 0e6e2b975d27732141acbbe6131ad78535a30e09 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 10:53:16 +1000 Subject: [PATCH 043/184] added ability to delete current order --- app/controllers/orders_controller.rb | 12 ++++ app/views/orders/show.html.haml | 13 +++-- app/views/shop/index.html.haml | 6 +- spec/controllers/orders_controller_spec.rb | 20 +++++++ spec/views/orders/show.html.haml_spec.rb | 65 +++++++++++++++++----- 5 files changed, 96 insertions(+), 20 deletions(-) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index f8b24442d..472185783 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -33,4 +33,16 @@ class OrdersController < ApplicationController format.json { render json: @order } end end + + # DELETE /orders/1 + # DELETE /orders/1.json + def destroy + @order = Order.find(params[:id]) + @order.destroy + + respond_to do |format| + format.html { redirect_to shop_url, notice: 'Order was deleted.' } + format.json { head :no_content } + end + end end diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 0cd1efe89..4fb8858a4 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -1,4 +1,4 @@ --content_for :title, "Order details (##{@order.id})" +-content_for :title, @order.completed_at ? "Order details (##{@order.id})" : "Current order" %p %strong Order number: @@ -13,11 +13,9 @@ = @order.created_at.to_s %p - %strong Date completed: - if @order.completed_at + %strong Date completed: = @order.completed_at.to_s - - else - Not completed. %h2 Order items @@ -48,5 +46,12 @@ %strong = price_with_currency(total) +%p + - if can? :destroy, @order + = link_to 'Delete this order', @order, method: :delete, | + data: { confirm: 'Are you sure?' }, :class => 'btn' + - if can? :update, @order + = link_to 'Checkout', '', :class => 'btn btn-primary' + %p = link_to "View other orders/order history", orders_path diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 52a280571..1133df8a9 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -16,7 +16,7 @@ - if @order and @order.order_items.count > 0 %h2 Your current order - %p + %p You currently have the following item in your cart: %strong = @most_recent_item.product @@ -25,6 +25,10 @@ %p = link_to "View order and checkout", order_path(@order) + or + =succeed "." do + = link_to 'delete this order', @order, method: :delete, | + data: { confirm: 'Are you sure?' } - else - @products.each do |p| diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb index fe3557556..9b545b344 100644 --- a/spec/controllers/orders_controller_spec.rb +++ b/spec/controllers/orders_controller_spec.rb @@ -31,4 +31,24 @@ describe OrdersController do assigns(:order).should eq(order) end end + + describe "DELETE destroy" do + it "destroys the requested order" do + member = FactoryGirl.create(:member) + sign_in member + order = Order.create!(:member_id => member.id) + expect { + delete :destroy, {:id => order.id} + }.to change(Order, :count).by(-1) + end + + it "redirects to the posts list" do + member = FactoryGirl.create(:member) + sign_in member + order = Order.create!(:member_id => member.id) + delete :destroy, {:id => order.id} + response.should redirect_to(shop_url) + end + end + end diff --git a/spec/views/orders/show.html.haml_spec.rb b/spec/views/orders/show.html.haml_spec.rb index 75915bffb..749fa9002 100644 --- a/spec/views/orders/show.html.haml_spec.rb +++ b/spec/views/orders/show.html.haml_spec.rb @@ -1,30 +1,65 @@ require 'spec_helper' describe "orders/show" do + before(:each) do @member = FactoryGirl.create(:member) sign_in @member controller.stub(:current_user) { @member } - @order = assign(:order, FactoryGirl.create(:order, :member => @member)) - @order_item = FactoryGirl.create(:order_item, - :order => @order, - :quantity => 2, - :price => 9900 - ) - render end - it "displays order number" do - rendered.should contain "Order number" + context "current order" do + before(:each) do + @order = assign(:order, FactoryGirl.create(:order, :member => @member)) + @order_item = FactoryGirl.create(:order_item, + :order => @order, + :quantity => 2, + :price => 9900 + ) + render + end + + it "displays order number" do + rendered.should contain "Order number" + end + + it "shows order items in a table" do + assert_select "table>tr>th", :text => "Product" + end + + it "shows the total" do + rendered.should contain "Total:" + rendered.should contain "198.00" + end + + it "shows a checkout button" do + assert_select "a", :text => "Checkout" + end + + it "shows a delete order button" do + assert_select "a", :text => "Delete this order" + end end - it "shows order items in a table" do - assert_select "table>tr>th", :text => "Product" - end + context "completed order" do + before(:each) do + @order = assign(:order, FactoryGirl.create(:completed_order, :member => @member)) + @order_item = FactoryGirl.create(:order_item, + :order => @order, + :quantity => 2, + :price => 9900 + ) + render + end + + it "doesn't show a checkout button" do + assert_select "a", :text => "Checkout", :count => 0 + end + + it "doesn't show delete order button" do + assert_select "a", :text => "Delete this order", :count => 0 + end - it "shows the total" do - rendered.should contain "Total:" - rendered.should contain "198.00" end end From 7551b7b0d43203bc527e5e01b2cb04eb3d39af31 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 11:28:04 +1000 Subject: [PATCH 044/184] added order completion (very basic for now) --- app/controllers/orders_controller.rb | 21 +++++++---- app/models/ability.rb | 44 +++++++++++++--------- app/views/orders/complete.html.haml | 43 +++++++++++++++++++++ app/views/orders/show.html.haml | 4 +- config/routes.rb | 1 + spec/controllers/orders_controller_spec.rb | 10 +++++ spec/models/ability_spec.rb | 28 ++++++++------ 7 files changed, 111 insertions(+), 40 deletions(-) create mode 100644 app/views/orders/complete.html.haml diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 472185783..428c1ef37 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -2,47 +2,52 @@ class OrdersController < ApplicationController load_and_authorize_resource # GET /orders - # GET /orders.json def index @orders = Order.find_all_by_member_id(current_member.id) respond_to do |format| format.html # index.html.erb - format.json { render json: @orders } end end # GET /orders/1 - # GET /orders/1.json def show @order = Order.find(params[:id]) respond_to do |format| format.html # show.html.erb - format.json { render json: @order } end end # GET /orders/new - # GET /orders/new.json def new @order = Order.new respond_to do |format| format.html # new.html.erb - format.json { render json: @order } end end + def complete + @order = Order.find(params[:id]) + + @order.completed_at = Time.zone.now + # current_member.paid = true # or whatever + @order.save + + respond_to do |format| + format.html # new.html.erb + end + + end + # DELETE /orders/1 - # DELETE /orders/1.json def destroy @order = Order.find(params[:id]) @order.destroy respond_to do |format| format.html { redirect_to shop_url, notice: 'Order was deleted.' } - format.json { head :no_content } end end end diff --git a/app/models/ability.rb b/app/models/ability.rb index b57c51997..b4c71492f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -19,20 +19,6 @@ class Ability if member - if member.has_role? :admin - # admin user roles (for authorization) - can :read, Role - can :manage, Role - - # for now, only admins can create/edit forums - can :manage, Forum - - # admins can manage products and orders - can :manage, Product - can :manage, Order # for now - can :manage, OrderItem # for now - end - # managing your own user settings can :update, Member, :id => member.id @@ -76,16 +62,38 @@ class Ability can :destroy, Planting, :garden => { :owner_id => member.id } # orders/shop/etc - can :create, Order - can :read, Order, :member_id => member.id - can :update, Order, :member_id => member.id, :completed_at => nil - can :destroy, Order, :member_id => member.id, :completed_at => nil + can :create, Order + can :read, Order, :member_id => member.id + can :complete, Order, :member_id => member.id, :completed_at => nil + can :destroy, Order, :member_id => member.id, :completed_at => nil can :create, OrderItem can :read, OrderItem, :order => { :member_id => member.id } can :update, OrderItem, :order => { :member_id => member.id, :completed_at => nil } can :destroy, OrderItem, :order => { :member_id => member.id, :completed_at => nil } + if member.has_role? :admin + # admin user roles (for authorization) + can :read, Role + can :manage, Role + + # for now, only admins can create/edit forums + can :manage, Forum + + # admins can manage products + can :manage, Product + + # admins can read other people's orders... + can :read, Order + can :read, OrderItem + + # but they can't do anything to them, because orders are *history* + cannot :create, Order + cannot :complete, Order + cannot :destroy, Order + cannot :manage, OrderItem + end + end end end diff --git a/app/views/orders/complete.html.haml b/app/views/orders/complete.html.haml new file mode 100644 index 000000000..de2c7df75 --- /dev/null +++ b/app/views/orders/complete.html.haml @@ -0,0 +1,43 @@ +-content_for :title, "Completed order" + +%p Thank you for your order. + +%p + Completed at: + = @order.completed_at + +%p + %strong Order number: + = @order.id + +%h2 Order items + +%table.table.table-striped + %tr + %th Product + %th Price + %th Quantity + %th Subtotal + - total = 0 + - @order.order_items.each do |i| + %tr + %td= i.product.name + %td + = price_with_currency(i.price) + %td= i.quantity + %td + - subtotal = i.price * i.quantity + - total += subtotal + = price_with_currency(subtotal) + + %tr + %td + %td + %td + %strong Total: + %td + %strong + = price_with_currency(total) + +%p + = link_to "View other orders/order history", orders_path diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 4fb8858a4..1099a61e8 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -50,8 +50,8 @@ - if can? :destroy, @order = link_to 'Delete this order', @order, method: :delete, | data: { confirm: 'Are you sure?' }, :class => 'btn' - - if can? :update, @order - = link_to 'Checkout', '', :class => 'btn btn-primary' + - if can? :complete, @order + = link_to 'Checkout', complete_order_path(@order), :class => 'btn btn-primary' %p = link_to "View other orders/order history", orders_path diff --git a/config/routes.rb b/config/routes.rb index 93d1a0c4a..b387de162 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,6 +17,7 @@ Growstuff::Application.routes.draw do resources :forums resources :notifications resources :orders + match 'orders/:id/complete' => 'orders#complete', :as => 'complete_order' resources :products get "home/index" diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb index 9b545b344..3d209f8e4 100644 --- a/spec/controllers/orders_controller_spec.rb +++ b/spec/controllers/orders_controller_spec.rb @@ -32,6 +32,16 @@ describe OrdersController do end end + describe "GET complete" do + it "assigns the requested order as @order" do + member = FactoryGirl.create(:member) + sign_in member + order = Order.create!(:member_id => member.id) + get :complete, {:id => order.to_param} + assigns(:order).should eq(order) + end + end + describe "DELETE destroy" do it "destroys the requested order" do member = FactoryGirl.create(:member) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index e4bc288d2..6a19de7a8 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -150,16 +150,16 @@ describe Ability do @ability.should be_able_to(:create, Order) end - it "can update their own current order" do - @ability.should be_able_to(:update, @order) + it "can complete their own current order" do + @ability.should be_able_to(:complete, @order) end - it "can't update someone else's order" do - @ability.should_not be_able_to(:update, @strangers_order) + it "can't complete someone else's order" do + @ability.should_not be_able_to(:complete, @strangers_order) end - it "can't update a completed order" do - @ability.should_not be_able_to(:update, @completed_order) + it "can't complete a completed order" do + @ability.should_not be_able_to(:complete, @completed_order) end it "can delete a current order" do @@ -228,15 +228,19 @@ describe Ability do it "can read orders" do @admin_ability.should be_able_to(:read, @order) end - it "can create orders" do - @admin_ability.should be_able_to(:create, Order) + + it "cannot create orders" do + @admin_ability.should_not be_able_to(:create, @order) end - it "can update orders" do - @admin_ability.should be_able_to(:update, @order) + + it "cannot complete orders" do + @admin_ability.should_not be_able_to(:complete, @order) end - it "can destroy orders" do - @admin_ability.should be_able_to(:destroy, @order) + + it "cannot delete orders" do + @admin_ability.should_not be_able_to(:delete, @order) end + end end end From 0157229936a96d5bdb9f48d3d81df418eca6009b Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 12:11:43 +1000 Subject: [PATCH 045/184] added account_details table, set up abilities also tweaked some order/order_item related abilities --- .../javascripts/account_details.js.coffee | 3 + app/controllers/account_details_controller.rb | 83 +++++++++ app/helpers/account_details_helper.rb | 2 + app/models/ability.rb | 21 ++- app/models/account_detail.rb | 4 + app/models/account_settings.rb | 5 + app/models/member.rb | 1 + app/views/account_details/_form.html.haml | 19 ++ app/views/account_details/edit.html.haml | 7 + app/views/account_details/index.html.haml | 23 +++ app/views/account_details/new.html.haml | 5 + app/views/account_details/show.html.haml | 15 ++ config/routes.rb | 3 + .../20130517015920_create_account_details.rb | 11 ++ db/schema.rb | 10 +- .../account_details_controller_spec.rb | 164 ++++++++++++++++++ spec/factories/account_details.rb | 9 + spec/helpers/account_details_helper_spec.rb | 15 ++ spec/models/ability_spec.rb | 52 +++++- spec/models/account_detail_spec.rb | 5 + spec/requests/account_details_spec.rb | 11 ++ spec/routing/account_details_routing_spec.rb | 35 ++++ .../account_details/edit.html.haml_spec.rb | 20 +++ .../account_details/index.html.haml_spec.rb | 23 +++ .../account_details/new.html.haml_spec.rb | 20 +++ .../account_details/show.html.haml_spec.rb | 17 ++ 26 files changed, 568 insertions(+), 15 deletions(-) create mode 100644 app/assets/javascripts/account_details.js.coffee create mode 100644 app/controllers/account_details_controller.rb create mode 100644 app/helpers/account_details_helper.rb create mode 100644 app/models/account_detail.rb create mode 100644 app/models/account_settings.rb create mode 100644 app/views/account_details/_form.html.haml create mode 100644 app/views/account_details/edit.html.haml create mode 100644 app/views/account_details/index.html.haml create mode 100644 app/views/account_details/new.html.haml create mode 100644 app/views/account_details/show.html.haml create mode 100644 db/migrate/20130517015920_create_account_details.rb create mode 100644 spec/controllers/account_details_controller_spec.rb create mode 100644 spec/factories/account_details.rb create mode 100644 spec/helpers/account_details_helper_spec.rb create mode 100644 spec/models/account_detail_spec.rb create mode 100644 spec/requests/account_details_spec.rb create mode 100644 spec/routing/account_details_routing_spec.rb create mode 100644 spec/views/account_details/edit.html.haml_spec.rb create mode 100644 spec/views/account_details/index.html.haml_spec.rb create mode 100644 spec/views/account_details/new.html.haml_spec.rb create mode 100644 spec/views/account_details/show.html.haml_spec.rb diff --git a/app/assets/javascripts/account_details.js.coffee b/app/assets/javascripts/account_details.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/account_details.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/account_details_controller.rb b/app/controllers/account_details_controller.rb new file mode 100644 index 000000000..571ff7bc2 --- /dev/null +++ b/app/controllers/account_details_controller.rb @@ -0,0 +1,83 @@ +class AccountDetailsController < ApplicationController + # GET /account_details + # GET /account_details.json + def index + @account_details = AccountDetail.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @account_details } + end + end + + # GET /account_details/1 + # GET /account_details/1.json + def show + @account_detail = AccountDetail.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @account_detail } + end + end + + # GET /account_details/new + # GET /account_details/new.json + def new + @account_detail = AccountDetail.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @account_detail } + end + end + + # GET /account_details/1/edit + def edit + @account_detail = AccountDetail.find(params[:id]) + end + + # POST /account_details + # POST /account_details.json + def create + @account_detail = AccountDetail.new(params[:account_detail]) + + respond_to do |format| + if @account_detail.save + format.html { redirect_to @account_detail, notice: 'Account detail was successfully created.' } + format.json { render json: @account_detail, status: :created, location: @account_detail } + else + format.html { render action: "new" } + format.json { render json: @account_detail.errors, status: :unprocessable_entity } + end + end + end + + # PUT /account_details/1 + # PUT /account_details/1.json + def update + @account_detail = AccountDetail.find(params[:id]) + + respond_to do |format| + if @account_detail.update_attributes(params[:account_detail]) + format.html { redirect_to @account_detail, notice: 'Account detail was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @account_detail.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /account_details/1 + # DELETE /account_details/1.json + def destroy + @account_detail = AccountDetail.find(params[:id]) + @account_detail.destroy + + respond_to do |format| + format.html { redirect_to account_details_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/account_details_helper.rb b/app/helpers/account_details_helper.rb new file mode 100644 index 000000000..ea418e8ec --- /dev/null +++ b/app/helpers/account_details_helper.rb @@ -0,0 +1,2 @@ +module AccountDetailsHelper +end diff --git a/app/models/ability.rb b/app/models/ability.rb index b4c71492f..30221f6c3 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -10,13 +10,14 @@ class Ability # except these, which don't make sense if you're not logged in cannot :read, Notification cannot :read, Authentication - - # nobody should be able to view this except admins - cannot :read, Role - cannot :read, Product cannot :read, Order cannot :read, OrderItem + # and nobody should be able to view this except admins + cannot :read, Role + cannot :read, Product + cannot :read, AccountDetail + if member # managing your own user settings @@ -68,9 +69,10 @@ class Ability can :destroy, Order, :member_id => member.id, :completed_at => nil can :create, OrderItem - can :read, OrderItem, :order => { :member_id => member.id } - can :update, OrderItem, :order => { :member_id => member.id, :completed_at => nil } - can :destroy, OrderItem, :order => { :member_id => member.id, :completed_at => nil } + # for now, let's not let people mess with individual order items + cannot :read, OrderItem, :order => { :member_id => member.id } + cannot :update, OrderItem, :order => { :member_id => member.id, :completed_at => nil } + cannot :destroy, OrderItem, :order => { :member_id => member.id, :completed_at => nil } if member.has_role? :admin # admin user roles (for authorization) @@ -92,6 +94,11 @@ class Ability cannot :complete, Order cannot :destroy, Order cannot :manage, OrderItem + + # admins can read and manage members' account details (paid acct + # status, etc) + can :read, AccountDetail + can :manage, AccountDetail end end diff --git a/app/models/account_detail.rb b/app/models/account_detail.rb new file mode 100644 index 000000000..bce738194 --- /dev/null +++ b/app/models/account_detail.rb @@ -0,0 +1,4 @@ +class AccountDetail < ActiveRecord::Base + attr_accessible :account_type, :member_id, :paid_until + belongs_to :member +end diff --git a/app/models/account_settings.rb b/app/models/account_settings.rb new file mode 100644 index 000000000..8a98af04d --- /dev/null +++ b/app/models/account_settings.rb @@ -0,0 +1,5 @@ +class AccountSettings < ActiveRecord::Base + attr_accessible :account_type, :member_id, :paid_until + + belongs_to :member +end diff --git a/app/models/member.rb b/app/models/member.rb index 19c43f3b8..618583293 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -12,6 +12,7 @@ class Member < ActiveRecord::Base has_many :sent_notifications, :foreign_key => 'sender_id' has_many :authentications has_many :orders + has_one :account_detail default_scope order("lower(login_name) asc") scope :confirmed, where('confirmed_at IS NOT NULL') diff --git a/app/views/account_details/_form.html.haml b/app/views/account_details/_form.html.haml new file mode 100644 index 000000000..034828629 --- /dev/null +++ b/app/views/account_details/_form.html.haml @@ -0,0 +1,19 @@ += form_for @account_detail do |f| + - if @account_detail.errors.any? + #error_explanation + %h2= "#{pluralize(@account_detail.errors.count, "error")} prohibited this account_detail from being saved:" + %ul + - @account_detail.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :member_id + = f.number_field :member_id + .field + = f.label :account_type + = f.text_field :account_type + .field + = f.label :paid_until + = f.datetime_select :paid_until + .actions + = f.submit 'Save' diff --git a/app/views/account_details/edit.html.haml b/app/views/account_details/edit.html.haml new file mode 100644 index 000000000..4cca4b860 --- /dev/null +++ b/app/views/account_details/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing account_detail + += render 'form' + += link_to 'Show', @account_detail +\| += link_to 'Back', account_details_path diff --git a/app/views/account_details/index.html.haml b/app/views/account_details/index.html.haml new file mode 100644 index 000000000..839dbafd3 --- /dev/null +++ b/app/views/account_details/index.html.haml @@ -0,0 +1,23 @@ +%h1 Listing account_details + +%table + %tr + %th Member + %th Account type + %th Paid until + %th + %th + %th + + - @account_details.each do |account_detail| + %tr + %td= account_detail.member_id + %td= account_detail.account_type + %td= account_detail.paid_until + %td= link_to 'Show', account_detail + %td= link_to 'Edit', edit_account_detail_path(account_detail) + %td= link_to 'Destroy', account_detail, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + += link_to 'New Account detail', new_account_detail_path diff --git a/app/views/account_details/new.html.haml b/app/views/account_details/new.html.haml new file mode 100644 index 000000000..c9e8e4c4f --- /dev/null +++ b/app/views/account_details/new.html.haml @@ -0,0 +1,5 @@ +%h1 New account_detail + += render 'form' + += link_to 'Back', account_details_path diff --git a/app/views/account_details/show.html.haml b/app/views/account_details/show.html.haml new file mode 100644 index 000000000..55993efae --- /dev/null +++ b/app/views/account_details/show.html.haml @@ -0,0 +1,15 @@ +%p#notice= notice + +%p + %b Member: + = @account_detail.member_id +%p + %b Account type: + = @account_detail.account_type +%p + %b Paid until: + = @account_detail.paid_until + += link_to 'Edit', edit_account_detail_path(@account_detail) +\| += link_to 'Back', account_details_path diff --git a/config/routes.rb b/config/routes.rb index b387de162..463ad4041 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Growstuff::Application.routes.draw do + resources :account_details + + resources :order_items diff --git a/db/migrate/20130517015920_create_account_details.rb b/db/migrate/20130517015920_create_account_details.rb new file mode 100644 index 000000000..245091684 --- /dev/null +++ b/db/migrate/20130517015920_create_account_details.rb @@ -0,0 +1,11 @@ +class CreateAccountDetails < ActiveRecord::Migration + def change + create_table :account_details do |t| + t.integer :member_id, :null => false + t.string :account_type, :null => false, :default => 'free' + t.datetime :paid_until + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d4bf0005f..fb5dd517a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,15 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130515122301) do +ActiveRecord::Schema.define(:version => 20130517015920) do + + create_table "account_details", :force => true do |t| + t.integer "member_id", :null => false + t.string "account_type", :default => "free", :null => false + t.datetime "paid_until" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end create_table "authentications", :force => true do |t| t.integer "member_id", :null => false diff --git a/spec/controllers/account_details_controller_spec.rb b/spec/controllers/account_details_controller_spec.rb new file mode 100644 index 000000000..9d5360ee6 --- /dev/null +++ b/spec/controllers/account_details_controller_spec.rb @@ -0,0 +1,164 @@ +require 'spec_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +describe AccountDetailsController do + + # This should return the minimal set of attributes required to create a valid + # AccountDetail. As you add validations to AccountDetail, be sure to + # update the return value of this method accordingly. + def valid_attributes + { "member_id" => "1" } + end + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # AccountDetailsController. Be sure to keep this updated too. + def valid_session + {} + end + + describe "GET index" do + it "assigns all account_details as @account_details" do + account_detail = AccountDetail.create! valid_attributes + get :index, {}, valid_session + assigns(:account_details).should eq([account_detail]) + end + end + + describe "GET show" do + it "assigns the requested account_detail as @account_detail" do + account_detail = AccountDetail.create! valid_attributes + get :show, {:id => account_detail.to_param}, valid_session + assigns(:account_detail).should eq(account_detail) + end + end + + describe "GET new" do + it "assigns a new account_detail as @account_detail" do + get :new, {}, valid_session + assigns(:account_detail).should be_a_new(AccountDetail) + end + end + + describe "GET edit" do + it "assigns the requested account_detail as @account_detail" do + account_detail = AccountDetail.create! valid_attributes + get :edit, {:id => account_detail.to_param}, valid_session + assigns(:account_detail).should eq(account_detail) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new AccountDetail" do + expect { + post :create, {:account_detail => valid_attributes}, valid_session + }.to change(AccountDetail, :count).by(1) + end + + it "assigns a newly created account_detail as @account_detail" do + post :create, {:account_detail => valid_attributes}, valid_session + assigns(:account_detail).should be_a(AccountDetail) + assigns(:account_detail).should be_persisted + end + + it "redirects to the created account_detail" do + post :create, {:account_detail => valid_attributes}, valid_session + response.should redirect_to(AccountDetail.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved account_detail as @account_detail" do + # Trigger the behavior that occurs when invalid params are submitted + AccountDetail.any_instance.stub(:save).and_return(false) + post :create, {:account_detail => { "member_id" => "invalid value" }}, valid_session + assigns(:account_detail).should be_a_new(AccountDetail) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + AccountDetail.any_instance.stub(:save).and_return(false) + post :create, {:account_detail => { "member_id" => "invalid value" }}, valid_session + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested account_detail" do + account_detail = AccountDetail.create! valid_attributes + # Assuming there are no other account_details in the database, this + # specifies that the AccountDetail created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + AccountDetail.any_instance.should_receive(:update_attributes).with({ "member_id" => "1" }) + put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "1" }}, valid_session + end + + it "assigns the requested account_detail as @account_detail" do + account_detail = AccountDetail.create! valid_attributes + put :update, {:id => account_detail.to_param, :account_detail => valid_attributes}, valid_session + assigns(:account_detail).should eq(account_detail) + end + + it "redirects to the account_detail" do + account_detail = AccountDetail.create! valid_attributes + put :update, {:id => account_detail.to_param, :account_detail => valid_attributes}, valid_session + response.should redirect_to(account_detail) + end + end + + describe "with invalid params" do + it "assigns the account_detail as @account_detail" do + account_detail = AccountDetail.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + AccountDetail.any_instance.stub(:save).and_return(false) + put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }}, valid_session + assigns(:account_detail).should eq(account_detail) + end + + it "re-renders the 'edit' template" do + account_detail = AccountDetail.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + AccountDetail.any_instance.stub(:save).and_return(false) + put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }}, valid_session + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested account_detail" do + account_detail = AccountDetail.create! valid_attributes + expect { + delete :destroy, {:id => account_detail.to_param}, valid_session + }.to change(AccountDetail, :count).by(-1) + end + + it "redirects to the account_details list" do + account_detail = AccountDetail.create! valid_attributes + delete :destroy, {:id => account_detail.to_param}, valid_session + response.should redirect_to(account_details_url) + end + end + +end diff --git a/spec/factories/account_details.rb b/spec/factories/account_details.rb new file mode 100644 index 000000000..3c8ad48ec --- /dev/null +++ b/spec/factories/account_details.rb @@ -0,0 +1,9 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :account_detail do + member_id 1 + account_type "MyString" + paid_until "2013-05-17 11:59:20" + end +end diff --git a/spec/helpers/account_details_helper_spec.rb b/spec/helpers/account_details_helper_spec.rb new file mode 100644 index 000000000..4830dd76b --- /dev/null +++ b/spec/helpers/account_details_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the AccountDetailsHelper. For example: +# +# describe AccountDetailsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe AccountDetailsHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 6a19de7a8..74eca0f9d 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -174,9 +174,9 @@ describe Ability do @ability.should_not be_able_to(:destroy, @completed_order) end - it "can read their own order items" do - @ability.should be_able_to(:read, @order_item) - @ability.should be_able_to(:read, @completed_order_item) + it "can't read their own order items" do + @ability.should_not be_able_to(:read, @order_item) + @ability.should_not be_able_to(:read, @completed_order_item) end it "can't read other people's order items" do @@ -187,8 +187,8 @@ describe Ability do @ability.should be_able_to(:create, OrderItem) end - it "can update their own order items" do - @ability.should be_able_to(:update, @order_item) + it "can't update their own order items" do + @ability.should_not be_able_to(:update, @order_item) end it "can't update other people's order items" do @@ -199,8 +199,8 @@ describe Ability do @ability.should_not be_able_to(:update, @completed_order_item) end - it "can delete their own order item" do - @ability.should be_able_to(:destroy, @order_item) + it "can't delete their own order item" do + @ability.should_not be_able_to(:destroy, @order_item) end it "can't delete someone else's order item" do @@ -243,4 +243,42 @@ describe Ability do end end + + context 'account details' do + before(:each) do + @account_detail = FactoryGirl.create(:account_detail, :member => @member) + end + + context 'ordinary member' do + it "can't read account details" do + @ability.should_not be_able_to(:read, @account_detail) + end + it "can't manage account details" do + @ability.should_not be_able_to(:create, AccountDetail) + @ability.should_not be_able_to(:update, @account_detail) + @ability.should_not be_able_to(:destroy, @account_detail) + end + end + + context 'admin' do + + before(:each) do + @role = FactoryGirl.create(:admin) + @member.roles << @role + @admin_ability = Ability.new(@member) + end + + it "can read account details" do + @admin_ability.should be_able_to(:read, @account_detail) + end + it "can manage account details" do + @admin_ability.should be_able_to(:create, AccountDetail) + @admin_ability.should be_able_to(:update, @account_detail) + @admin_ability.should be_able_to(:destroy, @account_detail) + end + + end + + end + end diff --git a/spec/models/account_detail_spec.rb b/spec/models/account_detail_spec.rb new file mode 100644 index 000000000..cbf089704 --- /dev/null +++ b/spec/models/account_detail_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe AccountDetail do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/account_details_spec.rb b/spec/requests/account_details_spec.rb new file mode 100644 index 000000000..0384ae1a8 --- /dev/null +++ b/spec/requests/account_details_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "AccountDetails" do + describe "GET /account_details" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get account_details_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/account_details_routing_spec.rb b/spec/routing/account_details_routing_spec.rb new file mode 100644 index 000000000..538fa8ddf --- /dev/null +++ b/spec/routing/account_details_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe AccountDetailsController do + describe "routing" do + + it "routes to #index" do + get("/account_details").should route_to("account_details#index") + end + + it "routes to #new" do + get("/account_details/new").should route_to("account_details#new") + end + + it "routes to #show" do + get("/account_details/1").should route_to("account_details#show", :id => "1") + end + + it "routes to #edit" do + get("/account_details/1/edit").should route_to("account_details#edit", :id => "1") + end + + it "routes to #create" do + post("/account_details").should route_to("account_details#create") + end + + it "routes to #update" do + put("/account_details/1").should route_to("account_details#update", :id => "1") + end + + it "routes to #destroy" do + delete("/account_details/1").should route_to("account_details#destroy", :id => "1") + end + + end +end diff --git a/spec/views/account_details/edit.html.haml_spec.rb b/spec/views/account_details/edit.html.haml_spec.rb new file mode 100644 index 000000000..53d30247c --- /dev/null +++ b/spec/views/account_details/edit.html.haml_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "account_details/edit" do + before(:each) do + @account_detail = assign(:account_detail, stub_model(AccountDetail, + :member_id => 1, + :account_type => "MyString" + )) + end + + it "renders the edit account_detail form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => account_details_path(@account_detail), :method => "post" do + assert_select "input#account_detail_member_id", :name => "account_detail[member_id]" + assert_select "input#account_detail_account_type", :name => "account_detail[account_type]" + end + end +end diff --git a/spec/views/account_details/index.html.haml_spec.rb b/spec/views/account_details/index.html.haml_spec.rb new file mode 100644 index 000000000..69470a9d3 --- /dev/null +++ b/spec/views/account_details/index.html.haml_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe "account_details/index" do + before(:each) do + assign(:account_details, [ + stub_model(AccountDetail, + :member_id => 1, + :account_type => "Account Type" + ), + stub_model(AccountDetail, + :member_id => 1, + :account_type => "Account Type" + ) + ]) + end + + it "renders a list of account_details" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => 1.to_s, :count => 2 + assert_select "tr>td", :text => "Account Type".to_s, :count => 2 + end +end diff --git a/spec/views/account_details/new.html.haml_spec.rb b/spec/views/account_details/new.html.haml_spec.rb new file mode 100644 index 000000000..9c81cd855 --- /dev/null +++ b/spec/views/account_details/new.html.haml_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "account_details/new" do + before(:each) do + assign(:account_detail, stub_model(AccountDetail, + :member_id => 1, + :account_type => "MyString" + ).as_new_record) + end + + it "renders new account_detail form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => account_details_path, :method => "post" do + assert_select "input#account_detail_member_id", :name => "account_detail[member_id]" + assert_select "input#account_detail_account_type", :name => "account_detail[account_type]" + end + end +end diff --git a/spec/views/account_details/show.html.haml_spec.rb b/spec/views/account_details/show.html.haml_spec.rb new file mode 100644 index 000000000..766326f6b --- /dev/null +++ b/spec/views/account_details/show.html.haml_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe "account_details/show" do + before(:each) do + @account_detail = assign(:account_detail, stub_model(AccountDetail, + :member_id => 1, + :account_type => "Account Type" + )) + end + + it "renders attributes in

" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/1/) + rendered.should match(/Account Type/) + end +end From df5ee9cf4b4b131541cff326b4e793e2e53edc72 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 15:11:55 +1000 Subject: [PATCH 046/184] give each member an account_detail entry --- app/models/member.rb | 2 ++ .../20130517015920_create_account_details.rb | 2 +- lib/tasks/growstuff.rake | 10 ++++++++ .../account_details_controller_spec.rb | 24 ------------------- spec/factories/account_details.rb | 2 +- spec/models/member_spec.rb | 5 ++++ .../account_details/edit.html.haml_spec.rb | 2 +- .../account_details/index.html.haml_spec.rb | 4 ++-- .../account_details/new.html.haml_spec.rb | 2 +- .../account_details/show.html.haml_spec.rb | 2 +- 10 files changed, 24 insertions(+), 31 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index 618583293..d8766ea17 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -68,6 +68,8 @@ class Member < ActiveRecord::Base # Give each new member a default garden after_create {|member| Garden.create(:name => "Garden", :owner_id => member.id) } + # and an account details record + after_create {|member| AccountDetail.create(:member_id => member.id) } # allow login via either login_name or email address def self.find_first_by_auth_conditions(warden_conditions) diff --git a/db/migrate/20130517015920_create_account_details.rb b/db/migrate/20130517015920_create_account_details.rb index 245091684..c4fc5baef 100644 --- a/db/migrate/20130517015920_create_account_details.rb +++ b/db/migrate/20130517015920_create_account_details.rb @@ -2,7 +2,7 @@ class CreateAccountDetails < ActiveRecord::Migration def change create_table :account_details do |t| t.integer :member_id, :null => false - t.string :account_type, :null => false, :default => 'free' + t.integer :account_type t.datetime :paid_until t.timestamps diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index fb87ea319..89f4b0e8f 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -40,6 +40,16 @@ namespace :growstuff do end end + task :account_details => :environment do + desc "Give each member an account_detail record" + + Member.all.each do |m| + unless m.account_detail + AccountDetail.create(:member_id => m.id) + end + end + end + end end diff --git a/spec/controllers/account_details_controller_spec.rb b/spec/controllers/account_details_controller_spec.rb index 9d5360ee6..896b282fe 100644 --- a/spec/controllers/account_details_controller_spec.rb +++ b/spec/controllers/account_details_controller_spec.rb @@ -1,35 +1,11 @@ require 'spec_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. - describe AccountDetailsController do - # This should return the minimal set of attributes required to create a valid - # AccountDetail. As you add validations to AccountDetail, be sure to - # update the return value of this method accordingly. def valid_attributes { "member_id" => "1" } end - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # AccountDetailsController. Be sure to keep this updated too. def valid_session {} end diff --git a/spec/factories/account_details.rb b/spec/factories/account_details.rb index 3c8ad48ec..40e073679 100644 --- a/spec/factories/account_details.rb +++ b/spec/factories/account_details.rb @@ -3,7 +3,7 @@ FactoryGirl.define do factory :account_detail do member_id 1 - account_type "MyString" + account_type 1 paid_until "2013-05-17 11:59:20" end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 7037ce904..109a3bdcb 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -29,6 +29,11 @@ describe 'member' do @member.gardens.count.should == 1 end + it 'should have a account detail entry' do + @member.save + @member.account_detail.should be_an_instance_of AccountDetail + end + it "doesn't show email by default" do @member.save @member.show_email.should be_false diff --git a/spec/views/account_details/edit.html.haml_spec.rb b/spec/views/account_details/edit.html.haml_spec.rb index 53d30247c..7b02196fe 100644 --- a/spec/views/account_details/edit.html.haml_spec.rb +++ b/spec/views/account_details/edit.html.haml_spec.rb @@ -4,7 +4,7 @@ describe "account_details/edit" do before(:each) do @account_detail = assign(:account_detail, stub_model(AccountDetail, :member_id => 1, - :account_type => "MyString" + :account_type => 1 )) end diff --git a/spec/views/account_details/index.html.haml_spec.rb b/spec/views/account_details/index.html.haml_spec.rb index 69470a9d3..619f7ddf0 100644 --- a/spec/views/account_details/index.html.haml_spec.rb +++ b/spec/views/account_details/index.html.haml_spec.rb @@ -5,11 +5,11 @@ describe "account_details/index" do assign(:account_details, [ stub_model(AccountDetail, :member_id => 1, - :account_type => "Account Type" + :account_type => 1 ), stub_model(AccountDetail, :member_id => 1, - :account_type => "Account Type" + :account_type => 1 ) ]) end diff --git a/spec/views/account_details/new.html.haml_spec.rb b/spec/views/account_details/new.html.haml_spec.rb index 9c81cd855..a4ece5750 100644 --- a/spec/views/account_details/new.html.haml_spec.rb +++ b/spec/views/account_details/new.html.haml_spec.rb @@ -4,7 +4,7 @@ describe "account_details/new" do before(:each) do assign(:account_detail, stub_model(AccountDetail, :member_id => 1, - :account_type => "MyString" + :account_type => 1 ).as_new_record) end diff --git a/spec/views/account_details/show.html.haml_spec.rb b/spec/views/account_details/show.html.haml_spec.rb index 766326f6b..633dd6ee4 100644 --- a/spec/views/account_details/show.html.haml_spec.rb +++ b/spec/views/account_details/show.html.haml_spec.rb @@ -4,7 +4,7 @@ describe "account_details/show" do before(:each) do @account_detail = assign(:account_detail, stub_model(AccountDetail, :member_id => 1, - :account_type => "Account Type" + :account_type => 1 )) end From 669b4234f97425e16b043c9a6585146ae9c54b26 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 15:36:02 +1000 Subject: [PATCH 047/184] Marked failing order_item test as pending These are failing because we set CanCan to not allow these actions. We might want to delete them entirely, actually, but I'm not quite sure yet, so for now I'm just marking as pending. --- spec/controllers/order_items_controller_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index b99bfae80..882e2f007 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -95,6 +95,11 @@ describe OrderItemsController do describe "PUT update" do + # we've said nobody can update an OrderItem; in future, we might + # want to delete all this code, but I wanted to wait just in case we + # needed it. + before(:each) { pending("we don't permit this (see app/model/ability.rb") } + describe "with valid params" do it "updates the requested order_item" do @@ -139,6 +144,11 @@ describe OrderItemsController do end describe "DELETE destroy" do + # we've said nobody can update an OrderItem; in future, we might + # want to delete all this code, but I wanted to wait just in case we + # needed it. + before(:each) { pending("we don't permit this (see app/model/ability.rb") } + it "destroys the requested order_item" do expect { delete :destroy, {:id => @order_item.to_param} From d49ba175e97737f3f5986394554e5daf5279fcb5 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 15:40:59 +1000 Subject: [PATCH 048/184] deleting spurious model file left over from old work --- app/models/account_settings.rb | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 app/models/account_settings.rb diff --git a/app/models/account_settings.rb b/app/models/account_settings.rb deleted file mode 100644 index 8a98af04d..000000000 --- a/app/models/account_settings.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AccountSettings < ActiveRecord::Base - attr_accessible :account_type, :member_id, :paid_until - - belongs_to :member -end From c595b2181dea5068e630851dadf03882cd5ef15c Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 17 May 2013 16:07:46 +1000 Subject: [PATCH 049/184] added account_types, fixed a bunch of tests --- .../javascripts/account_types.js.coffee | 3 + app/controllers/account_types_controller.rb | 83 +++++++++ app/helpers/account_types_helper.rb | 2 + app/models/account_detail.rb | 3 +- app/models/account_type.rb | 3 + app/views/account_details/show.html.haml | 2 +- app/views/account_types/_form.html.haml | 19 ++ app/views/account_types/edit.html.haml | 7 + app/views/account_types/index.html.haml | 23 +++ app/views/account_types/new.html.haml | 5 + app/views/account_types/show.html.haml | 15 ++ config/routes.rb | 3 + .../20130517015920_create_account_details.rb | 2 +- .../20130517051922_create_account_types.rb | 11 ++ db/schema.rb | 18 +- .../account_types_controller_spec.rb | 164 ++++++++++++++++++ spec/factories/account_details.rb | 4 +- spec/factories/account_types.rb | 9 + spec/helpers/account_types_helper_spec.rb | 15 ++ spec/models/account_type_spec.rb | 5 + spec/requests/account_types_spec.rb | 11 ++ spec/routing/account_types_routing_spec.rb | 35 ++++ .../account_details/edit.html.haml_spec.rb | 7 +- .../account_details/index.html.haml_spec.rb | 15 +- .../account_details/new.html.haml_spec.rb | 5 +- .../account_details/show.html.haml_spec.rb | 12 +- .../account_types/edit.html.haml_spec.rb | 22 +++ .../account_types/index.html.haml_spec.rb | 14 ++ .../views/account_types/new.html.haml_spec.rb | 22 +++ .../account_types/show.html.haml_spec.rb | 19 ++ 30 files changed, 522 insertions(+), 36 deletions(-) create mode 100644 app/assets/javascripts/account_types.js.coffee create mode 100644 app/controllers/account_types_controller.rb create mode 100644 app/helpers/account_types_helper.rb create mode 100644 app/models/account_type.rb create mode 100644 app/views/account_types/_form.html.haml create mode 100644 app/views/account_types/edit.html.haml create mode 100644 app/views/account_types/index.html.haml create mode 100644 app/views/account_types/new.html.haml create mode 100644 app/views/account_types/show.html.haml create mode 100644 db/migrate/20130517051922_create_account_types.rb create mode 100644 spec/controllers/account_types_controller_spec.rb create mode 100644 spec/factories/account_types.rb create mode 100644 spec/helpers/account_types_helper_spec.rb create mode 100644 spec/models/account_type_spec.rb create mode 100644 spec/requests/account_types_spec.rb create mode 100644 spec/routing/account_types_routing_spec.rb create mode 100644 spec/views/account_types/edit.html.haml_spec.rb create mode 100644 spec/views/account_types/index.html.haml_spec.rb create mode 100644 spec/views/account_types/new.html.haml_spec.rb create mode 100644 spec/views/account_types/show.html.haml_spec.rb diff --git a/app/assets/javascripts/account_types.js.coffee b/app/assets/javascripts/account_types.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/account_types.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/account_types_controller.rb b/app/controllers/account_types_controller.rb new file mode 100644 index 000000000..73011a503 --- /dev/null +++ b/app/controllers/account_types_controller.rb @@ -0,0 +1,83 @@ +class AccountTypesController < ApplicationController + # GET /account_types + # GET /account_types.json + def index + @account_types = AccountType.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @account_types } + end + end + + # GET /account_types/1 + # GET /account_types/1.json + def show + @account_type = AccountType.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @account_type } + end + end + + # GET /account_types/new + # GET /account_types/new.json + def new + @account_type = AccountType.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @account_type } + end + end + + # GET /account_types/1/edit + def edit + @account_type = AccountType.find(params[:id]) + end + + # POST /account_types + # POST /account_types.json + def create + @account_type = AccountType.new(params[:account_type]) + + respond_to do |format| + if @account_type.save + format.html { redirect_to @account_type, notice: 'Account type was successfully created.' } + format.json { render json: @account_type, status: :created, location: @account_type } + else + format.html { render action: "new" } + format.json { render json: @account_type.errors, status: :unprocessable_entity } + end + end + end + + # PUT /account_types/1 + # PUT /account_types/1.json + def update + @account_type = AccountType.find(params[:id]) + + respond_to do |format| + if @account_type.update_attributes(params[:account_type]) + format.html { redirect_to @account_type, notice: 'Account type was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @account_type.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /account_types/1 + # DELETE /account_types/1.json + def destroy + @account_type = AccountType.find(params[:id]) + @account_type.destroy + + respond_to do |format| + format.html { redirect_to account_types_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/account_types_helper.rb b/app/helpers/account_types_helper.rb new file mode 100644 index 000000000..c56061a3a --- /dev/null +++ b/app/helpers/account_types_helper.rb @@ -0,0 +1,2 @@ +module AccountTypesHelper +end diff --git a/app/models/account_detail.rb b/app/models/account_detail.rb index bce738194..2714e6d29 100644 --- a/app/models/account_detail.rb +++ b/app/models/account_detail.rb @@ -1,4 +1,5 @@ class AccountDetail < ActiveRecord::Base - attr_accessible :account_type, :member_id, :paid_until + attr_accessible :account_type_id, :member_id, :paid_until belongs_to :member + belongs_to :account_type end diff --git a/app/models/account_type.rb b/app/models/account_type.rb new file mode 100644 index 000000000..d9b0df081 --- /dev/null +++ b/app/models/account_type.rb @@ -0,0 +1,3 @@ +class AccountType < ActiveRecord::Base + attr_accessible :is_paid, :is_permanent_paid, :name +end diff --git a/app/views/account_details/show.html.haml b/app/views/account_details/show.html.haml index 55993efae..4ecb8ad74 100644 --- a/app/views/account_details/show.html.haml +++ b/app/views/account_details/show.html.haml @@ -5,7 +5,7 @@ = @account_detail.member_id %p %b Account type: - = @account_detail.account_type + = @account_detail.account_type.name %p %b Paid until: = @account_detail.paid_until diff --git a/app/views/account_types/_form.html.haml b/app/views/account_types/_form.html.haml new file mode 100644 index 000000000..12fcda225 --- /dev/null +++ b/app/views/account_types/_form.html.haml @@ -0,0 +1,19 @@ += form_for @account_type do |f| + - if @account_type.errors.any? + #error_explanation + %h2= "#{pluralize(@account_type.errors.count, "error")} prohibited this account_type from being saved:" + %ul + - @account_type.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :name + = f.text_field :name + .field + = f.label :is_paid + = f.check_box :is_paid + .field + = f.label :is_permanent_paid + = f.check_box :is_permanent_paid + .actions + = f.submit 'Save' diff --git a/app/views/account_types/edit.html.haml b/app/views/account_types/edit.html.haml new file mode 100644 index 000000000..e8d328a21 --- /dev/null +++ b/app/views/account_types/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing account_type + += render 'form' + += link_to 'Show', @account_type +\| += link_to 'Back', account_types_path diff --git a/app/views/account_types/index.html.haml b/app/views/account_types/index.html.haml new file mode 100644 index 000000000..a27434af4 --- /dev/null +++ b/app/views/account_types/index.html.haml @@ -0,0 +1,23 @@ +%h1 Listing account_types + +%table + %tr + %th Name + %th Is paid + %th Is permanent paid + %th + %th + %th + + - @account_types.each do |account_type| + %tr + %td= account_type.name + %td= account_type.is_paid + %td= account_type.is_permanent_paid + %td= link_to 'Show', account_type + %td= link_to 'Edit', edit_account_type_path(account_type) + %td= link_to 'Destroy', account_type, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + += link_to 'New Account type', new_account_type_path diff --git a/app/views/account_types/new.html.haml b/app/views/account_types/new.html.haml new file mode 100644 index 000000000..bf85a1baf --- /dev/null +++ b/app/views/account_types/new.html.haml @@ -0,0 +1,5 @@ +%h1 New account_type + += render 'form' + += link_to 'Back', account_types_path diff --git a/app/views/account_types/show.html.haml b/app/views/account_types/show.html.haml new file mode 100644 index 000000000..cb8993a27 --- /dev/null +++ b/app/views/account_types/show.html.haml @@ -0,0 +1,15 @@ +%p#notice= notice + +%p + %b Name: + = @account_type.name +%p + %b Is paid: + = @account_type.is_paid +%p + %b Is permanent paid: + = @account_type.is_permanent_paid + += link_to 'Edit', edit_account_type_path(@account_type) +\| += link_to 'Back', account_types_path diff --git a/config/routes.rb b/config/routes.rb index 463ad4041..1d6ab4adc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Growstuff::Application.routes.draw do + resources :account_types + + resources :account_details diff --git a/db/migrate/20130517015920_create_account_details.rb b/db/migrate/20130517015920_create_account_details.rb index c4fc5baef..8eb899fd5 100644 --- a/db/migrate/20130517015920_create_account_details.rb +++ b/db/migrate/20130517015920_create_account_details.rb @@ -2,7 +2,7 @@ class CreateAccountDetails < ActiveRecord::Migration def change create_table :account_details do |t| t.integer :member_id, :null => false - t.integer :account_type + t.integer :account_type_id t.datetime :paid_until t.timestamps diff --git a/db/migrate/20130517051922_create_account_types.rb b/db/migrate/20130517051922_create_account_types.rb new file mode 100644 index 000000000..d300701d7 --- /dev/null +++ b/db/migrate/20130517051922_create_account_types.rb @@ -0,0 +1,11 @@ +class CreateAccountTypes < ActiveRecord::Migration + def change + create_table :account_types do |t| + t.string :name + t.boolean :is_paid + t.boolean :is_permanent_paid + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index fb5dd517a..6798d6d9a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,14 +11,22 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130517015920) do +ActiveRecord::Schema.define(:version => 20130517051922) do create_table "account_details", :force => true do |t| - t.integer "member_id", :null => false - t.string "account_type", :default => "free", :null => false + t.integer "member_id", :null => false + t.integer "account_type_id" t.datetime "paid_until" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + create_table "account_types", :force => true do |t| + t.string "name" + t.boolean "is_paid" + t.boolean "is_permanent_paid" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "authentications", :force => true do |t| diff --git a/spec/controllers/account_types_controller_spec.rb b/spec/controllers/account_types_controller_spec.rb new file mode 100644 index 000000000..2aa8517ac --- /dev/null +++ b/spec/controllers/account_types_controller_spec.rb @@ -0,0 +1,164 @@ +require 'spec_helper' + +# This spec was generated by rspec-rails when you ran the scaffold generator. +# It demonstrates how one might use RSpec to specify the controller code that +# was generated by Rails when you ran the scaffold generator. +# +# It assumes that the implementation code is generated by the rails scaffold +# generator. If you are using any extension libraries to generate different +# controller code, this generated spec may or may not pass. +# +# It only uses APIs available in rails and/or rspec-rails. There are a number +# of tools you can use to make these specs even more expressive, but we're +# sticking to rails and rspec-rails APIs to keep things simple and stable. +# +# Compared to earlier versions of this generator, there is very limited use of +# stubs and message expectations in this spec. Stubs are only used when there +# is no simpler way to get a handle on the object needed for the example. +# Message expectations are only used when there is no simpler way to specify +# that an instance is receiving a specific message. + +describe AccountTypesController do + + # This should return the minimal set of attributes required to create a valid + # AccountType. As you add validations to AccountType, be sure to + # update the return value of this method accordingly. + def valid_attributes + { "name" => "MyString" } + end + + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # AccountTypesController. Be sure to keep this updated too. + def valid_session + {} + end + + describe "GET index" do + it "assigns all account_types as @account_types" do + account_type = AccountType.create! valid_attributes + get :index, {}, valid_session + assigns(:account_types).should eq([account_type]) + end + end + + describe "GET show" do + it "assigns the requested account_type as @account_type" do + account_type = AccountType.create! valid_attributes + get :show, {:id => account_type.to_param}, valid_session + assigns(:account_type).should eq(account_type) + end + end + + describe "GET new" do + it "assigns a new account_type as @account_type" do + get :new, {}, valid_session + assigns(:account_type).should be_a_new(AccountType) + end + end + + describe "GET edit" do + it "assigns the requested account_type as @account_type" do + account_type = AccountType.create! valid_attributes + get :edit, {:id => account_type.to_param}, valid_session + assigns(:account_type).should eq(account_type) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new AccountType" do + expect { + post :create, {:account_type => valid_attributes}, valid_session + }.to change(AccountType, :count).by(1) + end + + it "assigns a newly created account_type as @account_type" do + post :create, {:account_type => valid_attributes}, valid_session + assigns(:account_type).should be_a(AccountType) + assigns(:account_type).should be_persisted + end + + it "redirects to the created account_type" do + post :create, {:account_type => valid_attributes}, valid_session + response.should redirect_to(AccountType.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved account_type as @account_type" do + # Trigger the behavior that occurs when invalid params are submitted + AccountType.any_instance.stub(:save).and_return(false) + post :create, {:account_type => { "name" => "invalid value" }}, valid_session + assigns(:account_type).should be_a_new(AccountType) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + AccountType.any_instance.stub(:save).and_return(false) + post :create, {:account_type => { "name" => "invalid value" }}, valid_session + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested account_type" do + account_type = AccountType.create! valid_attributes + # Assuming there are no other account_types in the database, this + # specifies that the AccountType created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + AccountType.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) + put :update, {:id => account_type.to_param, :account_type => { "name" => "MyString" }}, valid_session + end + + it "assigns the requested account_type as @account_type" do + account_type = AccountType.create! valid_attributes + put :update, {:id => account_type.to_param, :account_type => valid_attributes}, valid_session + assigns(:account_type).should eq(account_type) + end + + it "redirects to the account_type" do + account_type = AccountType.create! valid_attributes + put :update, {:id => account_type.to_param, :account_type => valid_attributes}, valid_session + response.should redirect_to(account_type) + end + end + + describe "with invalid params" do + it "assigns the account_type as @account_type" do + account_type = AccountType.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + AccountType.any_instance.stub(:save).and_return(false) + put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }}, valid_session + assigns(:account_type).should eq(account_type) + end + + it "re-renders the 'edit' template" do + account_type = AccountType.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + AccountType.any_instance.stub(:save).and_return(false) + put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }}, valid_session + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested account_type" do + account_type = AccountType.create! valid_attributes + expect { + delete :destroy, {:id => account_type.to_param}, valid_session + }.to change(AccountType, :count).by(-1) + end + + it "redirects to the account_types list" do + account_type = AccountType.create! valid_attributes + delete :destroy, {:id => account_type.to_param}, valid_session + response.should redirect_to(account_types_url) + end + end + +end diff --git a/spec/factories/account_details.rb b/spec/factories/account_details.rb index 40e073679..a1970165c 100644 --- a/spec/factories/account_details.rb +++ b/spec/factories/account_details.rb @@ -2,8 +2,8 @@ FactoryGirl.define do factory :account_detail do - member_id 1 - account_type 1 + member + account_type paid_until "2013-05-17 11:59:20" end end diff --git a/spec/factories/account_types.rb b/spec/factories/account_types.rb new file mode 100644 index 000000000..fa411ffbe --- /dev/null +++ b/spec/factories/account_types.rb @@ -0,0 +1,9 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :account_type do + name "MyString" + is_paid false + is_permanent_paid false + end +end diff --git a/spec/helpers/account_types_helper_spec.rb b/spec/helpers/account_types_helper_spec.rb new file mode 100644 index 000000000..bb01d3c45 --- /dev/null +++ b/spec/helpers/account_types_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the AccountTypesHelper. For example: +# +# describe AccountTypesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe AccountTypesHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/account_type_spec.rb b/spec/models/account_type_spec.rb new file mode 100644 index 000000000..c9bf250d9 --- /dev/null +++ b/spec/models/account_type_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe AccountType do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/requests/account_types_spec.rb b/spec/requests/account_types_spec.rb new file mode 100644 index 000000000..c7d67ac6b --- /dev/null +++ b/spec/requests/account_types_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "AccountTypes" do + describe "GET /account_types" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get account_types_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/account_types_routing_spec.rb b/spec/routing/account_types_routing_spec.rb new file mode 100644 index 000000000..456cedebe --- /dev/null +++ b/spec/routing/account_types_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe AccountTypesController do + describe "routing" do + + it "routes to #index" do + get("/account_types").should route_to("account_types#index") + end + + it "routes to #new" do + get("/account_types/new").should route_to("account_types#new") + end + + it "routes to #show" do + get("/account_types/1").should route_to("account_types#show", :id => "1") + end + + it "routes to #edit" do + get("/account_types/1/edit").should route_to("account_types#edit", :id => "1") + end + + it "routes to #create" do + post("/account_types").should route_to("account_types#create") + end + + it "routes to #update" do + put("/account_types/1").should route_to("account_types#update", :id => "1") + end + + it "routes to #destroy" do + delete("/account_types/1").should route_to("account_types#destroy", :id => "1") + end + + end +end diff --git a/spec/views/account_details/edit.html.haml_spec.rb b/spec/views/account_details/edit.html.haml_spec.rb index 7b02196fe..de8c793bd 100644 --- a/spec/views/account_details/edit.html.haml_spec.rb +++ b/spec/views/account_details/edit.html.haml_spec.rb @@ -2,10 +2,9 @@ require 'spec_helper' describe "account_details/edit" do before(:each) do - @account_detail = assign(:account_detail, stub_model(AccountDetail, - :member_id => 1, - :account_type => 1 - )) + @account_detail = assign(:account_detail, + FactoryGirl.create(:account_detail) + ) end it "renders the edit account_detail form" do diff --git a/spec/views/account_details/index.html.haml_spec.rb b/spec/views/account_details/index.html.haml_spec.rb index 619f7ddf0..a9d8314b6 100644 --- a/spec/views/account_details/index.html.haml_spec.rb +++ b/spec/views/account_details/index.html.haml_spec.rb @@ -2,22 +2,13 @@ require 'spec_helper' describe "account_details/index" do before(:each) do - assign(:account_details, [ - stub_model(AccountDetail, - :member_id => 1, - :account_type => 1 - ), - stub_model(AccountDetail, - :member_id => 1, - :account_type => 1 - ) - ]) + @account_detail = FactoryGirl.create(:account_detail) + assign(:account_details, [@account_detail, @account_detail]) end it "renders a list of account_details" do render # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "tr>td", :text => 1.to_s, :count => 2 - assert_select "tr>td", :text => "Account Type".to_s, :count => 2 + assert_select "tr>td", :text => @account_detail.member_id.to_s, :count => 2 end end diff --git a/spec/views/account_details/new.html.haml_spec.rb b/spec/views/account_details/new.html.haml_spec.rb index a4ece5750..553f5f15e 100644 --- a/spec/views/account_details/new.html.haml_spec.rb +++ b/spec/views/account_details/new.html.haml_spec.rb @@ -2,10 +2,7 @@ require 'spec_helper' describe "account_details/new" do before(:each) do - assign(:account_detail, stub_model(AccountDetail, - :member_id => 1, - :account_type => 1 - ).as_new_record) + assign(:account_detail, FactoryGirl.create(:account_detail)) end it "renders new account_detail form" do diff --git a/spec/views/account_details/show.html.haml_spec.rb b/spec/views/account_details/show.html.haml_spec.rb index 633dd6ee4..df73ec69c 100644 --- a/spec/views/account_details/show.html.haml_spec.rb +++ b/spec/views/account_details/show.html.haml_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' describe "account_details/show" do before(:each) do - @account_detail = assign(:account_detail, stub_model(AccountDetail, - :member_id => 1, - :account_type => 1 - )) + @account_detail = assign(:account_detail, + FactoryGirl.create(:account_detail) + ) end it "renders attributes in

" do render # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should match(/1/) - rendered.should match(/Account Type/) + rendered.should contain @account_detail.member_id.to_s + rendered.should contain @account_detail.account_type.name + rendered.should contain @account_detail.paid_until.to_s end end diff --git a/spec/views/account_types/edit.html.haml_spec.rb b/spec/views/account_types/edit.html.haml_spec.rb new file mode 100644 index 000000000..bb5fbf433 --- /dev/null +++ b/spec/views/account_types/edit.html.haml_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "account_types/edit" do + before(:each) do + @account_type = assign(:account_type, stub_model(AccountType, + :name => "MyString", + :is_paid => false, + :is_permanent_paid => false + )) + end + + it "renders the edit account_type form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => account_types_path(@account_type), :method => "post" do + assert_select "input#account_type_name", :name => "account_type[name]" + assert_select "input#account_type_is_paid", :name => "account_type[is_paid]" + assert_select "input#account_type_is_permanent_paid", :name => "account_type[is_permanent_paid]" + end + end +end diff --git a/spec/views/account_types/index.html.haml_spec.rb b/spec/views/account_types/index.html.haml_spec.rb new file mode 100644 index 000000000..cded5ef32 --- /dev/null +++ b/spec/views/account_types/index.html.haml_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe "account_types/index" do + before(:each) do + @type = FactoryGirl.create(:account_type) + assign(:account_types, [@type, @type]) + end + + it "renders a list of account_types" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => @type.name.to_s, :count => 2 + end +end diff --git a/spec/views/account_types/new.html.haml_spec.rb b/spec/views/account_types/new.html.haml_spec.rb new file mode 100644 index 000000000..3babc2fb9 --- /dev/null +++ b/spec/views/account_types/new.html.haml_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "account_types/new" do + before(:each) do + assign(:account_type, stub_model(AccountType, + :name => "MyString", + :is_paid => false, + :is_permanent_paid => false + ).as_new_record) + end + + it "renders new account_type form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => account_types_path, :method => "post" do + assert_select "input#account_type_name", :name => "account_type[name]" + assert_select "input#account_type_is_paid", :name => "account_type[is_paid]" + assert_select "input#account_type_is_permanent_paid", :name => "account_type[is_permanent_paid]" + end + end +end diff --git a/spec/views/account_types/show.html.haml_spec.rb b/spec/views/account_types/show.html.haml_spec.rb new file mode 100644 index 000000000..941026cf4 --- /dev/null +++ b/spec/views/account_types/show.html.haml_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe "account_types/show" do + before(:each) do + @account_type = assign(:account_type, stub_model(AccountType, + :name => "Name", + :is_paid => false, + :is_permanent_paid => false + )) + end + + it "renders attributes in

" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/Name/) + rendered.should match(/false/) + rendered.should match(/false/) + end +end From 871229ed2b75605cd57a7869b18ae23912674b25 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 09:33:26 +1000 Subject: [PATCH 050/184] account details: there can only be one Set up a validation to make sure there's only ever one account_detail attached to a member. This broke a bunch of tests, and means you basically can't use a factory to generate account details. Instead, to test account details, create a member and then look at their account details. --- app/controllers/account_details_controller.rb | 39 +----- app/controllers/account_types_controller.rb | 1 + app/models/ability.rb | 2 + app/models/account_detail.rb | 5 + app/models/member.rb | 5 +- app/views/account_details/show.html.haml | 5 +- .../account_details_controller_spec.rb | 111 +++++------------- .../account_types_controller_spec.rb | 62 +++------- spec/factories/account_details.rb | 8 +- spec/models/ability_spec.rb | 2 +- spec/models/account_detail_spec.rb | 13 +- spec/requests/account_details_spec.rb | 11 -- .../account_details/edit.html.haml_spec.rb | 5 +- .../account_details/index.html.haml_spec.rb | 3 +- .../account_details/new.html.haml_spec.rb | 3 +- .../account_details/show.html.haml_spec.rb | 7 +- 16 files changed, 91 insertions(+), 191 deletions(-) delete mode 100644 spec/requests/account_details_spec.rb diff --git a/app/controllers/account_details_controller.rb b/app/controllers/account_details_controller.rb index 571ff7bc2..364b5bee4 100644 --- a/app/controllers/account_details_controller.rb +++ b/app/controllers/account_details_controller.rb @@ -1,4 +1,5 @@ class AccountDetailsController < ApplicationController + load_and_authorize_resource # GET /account_details # GET /account_details.json def index @@ -21,38 +22,11 @@ class AccountDetailsController < ApplicationController end end - # GET /account_details/new - # GET /account_details/new.json - def new - @account_detail = AccountDetail.new - - respond_to do |format| - format.html # new.html.erb - format.json { render json: @account_detail } - end - end - # GET /account_details/1/edit def edit @account_detail = AccountDetail.find(params[:id]) end - # POST /account_details - # POST /account_details.json - def create - @account_detail = AccountDetail.new(params[:account_detail]) - - respond_to do |format| - if @account_detail.save - format.html { redirect_to @account_detail, notice: 'Account detail was successfully created.' } - format.json { render json: @account_detail, status: :created, location: @account_detail } - else - format.html { render action: "new" } - format.json { render json: @account_detail.errors, status: :unprocessable_entity } - end - end - end - # PUT /account_details/1 # PUT /account_details/1.json def update @@ -69,15 +43,4 @@ class AccountDetailsController < ApplicationController end end - # DELETE /account_details/1 - # DELETE /account_details/1.json - def destroy - @account_detail = AccountDetail.find(params[:id]) - @account_detail.destroy - - respond_to do |format| - format.html { redirect_to account_details_url } - format.json { head :no_content } - end - end end diff --git a/app/controllers/account_types_controller.rb b/app/controllers/account_types_controller.rb index 73011a503..d0864473e 100644 --- a/app/controllers/account_types_controller.rb +++ b/app/controllers/account_types_controller.rb @@ -1,4 +1,5 @@ class AccountTypesController < ApplicationController + load_and_authorize_resource # GET /account_types # GET /account_types.json def index diff --git a/app/models/ability.rb b/app/models/ability.rb index 30221f6c3..dd8a01a62 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -99,6 +99,8 @@ class Ability # status, etc) can :read, AccountDetail can :manage, AccountDetail + can :read, AccountType + can :manage, AccountType end end diff --git a/app/models/account_detail.rb b/app/models/account_detail.rb index 2714e6d29..c218a1c1f 100644 --- a/app/models/account_detail.rb +++ b/app/models/account_detail.rb @@ -2,4 +2,9 @@ class AccountDetail < ActiveRecord::Base attr_accessible :account_type_id, :member_id, :paid_until belongs_to :member belongs_to :account_type + + validates :member_id, :uniqueness => { + :message => 'already has account details associated with it' + } + end diff --git a/app/models/member.rb b/app/models/member.rb index d8766ea17..d8aa8e96c 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -68,8 +68,11 @@ class Member < ActiveRecord::Base # Give each new member a default garden after_create {|member| Garden.create(:name => "Garden", :owner_id => member.id) } + # and an account details record - after_create {|member| AccountDetail.create(:member_id => member.id) } + # we use find_or_create to avoid accidentally creating a second one, + # which can happen sometimes especially with FactoryGirl associations + after_create {|member| AccountDetail.find_or_create_by_member_id(:member_id => member.id) } # allow login via either login_name or email address def self.find_first_by_auth_conditions(warden_conditions) diff --git a/app/views/account_details/show.html.haml b/app/views/account_details/show.html.haml index 4ecb8ad74..e9d97aa4e 100644 --- a/app/views/account_details/show.html.haml +++ b/app/views/account_details/show.html.haml @@ -5,7 +5,10 @@ = @account_detail.member_id %p %b Account type: - = @account_detail.account_type.name + - if @account_detail.account_type + = @account_detail.account_type.name + - else + Free account %p %b Paid until: = @account_detail.paid_until diff --git a/spec/controllers/account_details_controller_spec.rb b/spec/controllers/account_details_controller_spec.rb index 896b282fe..ad0bcef48 100644 --- a/spec/controllers/account_details_controller_spec.rb +++ b/spec/controllers/account_details_controller_spec.rb @@ -2,139 +2,90 @@ require 'spec_helper' describe AccountDetailsController do + login_member(:admin_member) + def valid_attributes - { "member_id" => "1" } + { "paid_until" => Time.now } end - def valid_session - {} + def create_account_detail + # account details are automatically created when you create a new + # member; creating them manually will just cause errors as only one is + # allowed. + + member = FactoryGirl.create(:member) + return member.account_detail end describe "GET index" do it "assigns all account_details as @account_details" do - account_detail = AccountDetail.create! valid_attributes - get :index, {}, valid_session - assigns(:account_details).should eq([account_detail]) + account_detail = create_account_detail + get :index, {} + # can't match the whole list because it includes the one for the + # logged in admin member, sigh + assigns(:account_details).should include(account_detail) + assigns(:account_details).count.should eq 2 end end describe "GET show" do it "assigns the requested account_detail as @account_detail" do - account_detail = AccountDetail.create! valid_attributes - get :show, {:id => account_detail.to_param}, valid_session + account_detail = create_account_detail + get :show, {:id => account_detail.to_param} assigns(:account_detail).should eq(account_detail) end end - describe "GET new" do - it "assigns a new account_detail as @account_detail" do - get :new, {}, valid_session - assigns(:account_detail).should be_a_new(AccountDetail) - end - end - describe "GET edit" do it "assigns the requested account_detail as @account_detail" do - account_detail = AccountDetail.create! valid_attributes - get :edit, {:id => account_detail.to_param}, valid_session + account_detail = create_account_detail + get :edit, {:id => account_detail.to_param} assigns(:account_detail).should eq(account_detail) end end - describe "POST create" do - describe "with valid params" do - it "creates a new AccountDetail" do - expect { - post :create, {:account_detail => valid_attributes}, valid_session - }.to change(AccountDetail, :count).by(1) - end - - it "assigns a newly created account_detail as @account_detail" do - post :create, {:account_detail => valid_attributes}, valid_session - assigns(:account_detail).should be_a(AccountDetail) - assigns(:account_detail).should be_persisted - end - - it "redirects to the created account_detail" do - post :create, {:account_detail => valid_attributes}, valid_session - response.should redirect_to(AccountDetail.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved account_detail as @account_detail" do - # Trigger the behavior that occurs when invalid params are submitted - AccountDetail.any_instance.stub(:save).and_return(false) - post :create, {:account_detail => { "member_id" => "invalid value" }}, valid_session - assigns(:account_detail).should be_a_new(AccountDetail) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - AccountDetail.any_instance.stub(:save).and_return(false) - post :create, {:account_detail => { "member_id" => "invalid value" }}, valid_session - response.should render_template("new") - end - end - end - describe "PUT update" do describe "with valid params" do it "updates the requested account_detail" do - account_detail = AccountDetail.create! valid_attributes - # Assuming there are no other account_details in the database, this + account_detail = create_account_detail + # Assuming there are no other account_detail in the database, this # specifies that the AccountDetail created on the previous line # receives the :update_attributes message with whatever params are # submitted in the request. AccountDetail.any_instance.should_receive(:update_attributes).with({ "member_id" => "1" }) - put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "1" }}, valid_session + put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "1" }} end it "assigns the requested account_detail as @account_detail" do - account_detail = AccountDetail.create! valid_attributes - put :update, {:id => account_detail.to_param, :account_detail => valid_attributes}, valid_session + account_detail = create_account_detail + put :update, {:id => account_detail.to_param, :account_detail => valid_attributes} assigns(:account_detail).should eq(account_detail) end it "redirects to the account_detail" do - account_detail = AccountDetail.create! valid_attributes - put :update, {:id => account_detail.to_param, :account_detail => valid_attributes}, valid_session + account_detail = create_account_detail + put :update, {:id => account_detail.to_param, :account_detail => valid_attributes} response.should redirect_to(account_detail) end end describe "with invalid params" do it "assigns the account_detail as @account_detail" do - account_detail = AccountDetail.create! valid_attributes + account_detail = create_account_detail # Trigger the behavior that occurs when invalid params are submitted AccountDetail.any_instance.stub(:save).and_return(false) - put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }}, valid_session + put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }} assigns(:account_detail).should eq(account_detail) end it "re-renders the 'edit' template" do - account_detail = AccountDetail.create! valid_attributes + account_detail = create_account_detail # Trigger the behavior that occurs when invalid params are submitted AccountDetail.any_instance.stub(:save).and_return(false) - put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }}, valid_session + put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }} response.should render_template("edit") end end end - describe "DELETE destroy" do - it "destroys the requested account_detail" do - account_detail = AccountDetail.create! valid_attributes - expect { - delete :destroy, {:id => account_detail.to_param}, valid_session - }.to change(AccountDetail, :count).by(-1) - end - - it "redirects to the account_details list" do - account_detail = AccountDetail.create! valid_attributes - delete :destroy, {:id => account_detail.to_param}, valid_session - response.should redirect_to(account_details_url) - end - end - end diff --git a/spec/controllers/account_types_controller_spec.rb b/spec/controllers/account_types_controller_spec.rb index 2aa8517ac..070252672 100644 --- a/spec/controllers/account_types_controller_spec.rb +++ b/spec/controllers/account_types_controller_spec.rb @@ -1,43 +1,17 @@ require 'spec_helper' -# This spec was generated by rspec-rails when you ran the scaffold generator. -# It demonstrates how one might use RSpec to specify the controller code that -# was generated by Rails when you ran the scaffold generator. -# -# It assumes that the implementation code is generated by the rails scaffold -# generator. If you are using any extension libraries to generate different -# controller code, this generated spec may or may not pass. -# -# It only uses APIs available in rails and/or rspec-rails. There are a number -# of tools you can use to make these specs even more expressive, but we're -# sticking to rails and rspec-rails APIs to keep things simple and stable. -# -# Compared to earlier versions of this generator, there is very limited use of -# stubs and message expectations in this spec. Stubs are only used when there -# is no simpler way to get a handle on the object needed for the example. -# Message expectations are only used when there is no simpler way to specify -# that an instance is receiving a specific message. - describe AccountTypesController do - # This should return the minimal set of attributes required to create a valid - # AccountType. As you add validations to AccountType, be sure to - # update the return value of this method accordingly. + login_member(:admin_member) + def valid_attributes { "name" => "MyString" } end - # This should return the minimal set of values that should be in the session - # in order to pass any filters (e.g. authentication) defined in - # AccountTypesController. Be sure to keep this updated too. - def valid_session - {} - end - describe "GET index" do it "assigns all account_types as @account_types" do account_type = AccountType.create! valid_attributes - get :index, {}, valid_session + get :index, {} assigns(:account_types).should eq([account_type]) end end @@ -45,14 +19,14 @@ describe AccountTypesController do describe "GET show" do it "assigns the requested account_type as @account_type" do account_type = AccountType.create! valid_attributes - get :show, {:id => account_type.to_param}, valid_session + get :show, {:id => account_type.to_param} assigns(:account_type).should eq(account_type) end end describe "GET new" do it "assigns a new account_type as @account_type" do - get :new, {}, valid_session + get :new, {} assigns(:account_type).should be_a_new(AccountType) end end @@ -60,7 +34,7 @@ describe AccountTypesController do describe "GET edit" do it "assigns the requested account_type as @account_type" do account_type = AccountType.create! valid_attributes - get :edit, {:id => account_type.to_param}, valid_session + get :edit, {:id => account_type.to_param} assigns(:account_type).should eq(account_type) end end @@ -69,18 +43,18 @@ describe AccountTypesController do describe "with valid params" do it "creates a new AccountType" do expect { - post :create, {:account_type => valid_attributes}, valid_session + post :create, {:account_type => valid_attributes} }.to change(AccountType, :count).by(1) end it "assigns a newly created account_type as @account_type" do - post :create, {:account_type => valid_attributes}, valid_session + post :create, {:account_type => valid_attributes} assigns(:account_type).should be_a(AccountType) assigns(:account_type).should be_persisted end it "redirects to the created account_type" do - post :create, {:account_type => valid_attributes}, valid_session + post :create, {:account_type => valid_attributes} response.should redirect_to(AccountType.last) end end @@ -89,14 +63,14 @@ describe AccountTypesController do it "assigns a newly created but unsaved account_type as @account_type" do # Trigger the behavior that occurs when invalid params are submitted AccountType.any_instance.stub(:save).and_return(false) - post :create, {:account_type => { "name" => "invalid value" }}, valid_session + post :create, {:account_type => { "name" => "invalid value" }} assigns(:account_type).should be_a_new(AccountType) end it "re-renders the 'new' template" do # Trigger the behavior that occurs when invalid params are submitted AccountType.any_instance.stub(:save).and_return(false) - post :create, {:account_type => { "name" => "invalid value" }}, valid_session + post :create, {:account_type => { "name" => "invalid value" }} response.should render_template("new") end end @@ -111,18 +85,18 @@ describe AccountTypesController do # receives the :update_attributes message with whatever params are # submitted in the request. AccountType.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) - put :update, {:id => account_type.to_param, :account_type => { "name" => "MyString" }}, valid_session + put :update, {:id => account_type.to_param, :account_type => { "name" => "MyString" }} end it "assigns the requested account_type as @account_type" do account_type = AccountType.create! valid_attributes - put :update, {:id => account_type.to_param, :account_type => valid_attributes}, valid_session + put :update, {:id => account_type.to_param, :account_type => valid_attributes} assigns(:account_type).should eq(account_type) end it "redirects to the account_type" do account_type = AccountType.create! valid_attributes - put :update, {:id => account_type.to_param, :account_type => valid_attributes}, valid_session + put :update, {:id => account_type.to_param, :account_type => valid_attributes} response.should redirect_to(account_type) end end @@ -132,7 +106,7 @@ describe AccountTypesController do account_type = AccountType.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted AccountType.any_instance.stub(:save).and_return(false) - put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }}, valid_session + put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }} assigns(:account_type).should eq(account_type) end @@ -140,7 +114,7 @@ describe AccountTypesController do account_type = AccountType.create! valid_attributes # Trigger the behavior that occurs when invalid params are submitted AccountType.any_instance.stub(:save).and_return(false) - put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }}, valid_session + put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }} response.should render_template("edit") end end @@ -150,13 +124,13 @@ describe AccountTypesController do it "destroys the requested account_type" do account_type = AccountType.create! valid_attributes expect { - delete :destroy, {:id => account_type.to_param}, valid_session + delete :destroy, {:id => account_type.to_param} }.to change(AccountType, :count).by(-1) end it "redirects to the account_types list" do account_type = AccountType.create! valid_attributes - delete :destroy, {:id => account_type.to_param}, valid_session + delete :destroy, {:id => account_type.to_param} response.should redirect_to(account_types_url) end end diff --git a/spec/factories/account_details.rb b/spec/factories/account_details.rb index a1970165c..069c8e850 100644 --- a/spec/factories/account_details.rb +++ b/spec/factories/account_details.rb @@ -1,9 +1,7 @@ # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do - factory :account_detail do - member - account_type - paid_until "2013-05-17 11:59:20" - end + # never do this directly. + # create a member then look at member.account_detail instead. + # (because it's auto-created, and there can only be one.) end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 74eca0f9d..722b52274 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -246,7 +246,7 @@ describe Ability do context 'account details' do before(:each) do - @account_detail = FactoryGirl.create(:account_detail, :member => @member) + @account_detail = @member.account_detail end context 'ordinary member' do diff --git a/spec/models/account_detail_spec.rb b/spec/models/account_detail_spec.rb index cbf089704..94a786cd6 100644 --- a/spec/models/account_detail_spec.rb +++ b/spec/models/account_detail_spec.rb @@ -1,5 +1,16 @@ require 'spec_helper' describe AccountDetail do - pending "add some examples to (or delete) #{__FILE__}" + before(:each) do + @member = FactoryGirl.create(:member) + end + + it "auto-creates an account detail record when a member is created" do + @member.account_detail.should be_an_instance_of AccountDetail + end + + it "won't let you create two account details for the same member" do + @details = AccountDetail.new(:member_id => @member.id) + @details.should_not be_valid + end end diff --git a/spec/requests/account_details_spec.rb b/spec/requests/account_details_spec.rb deleted file mode 100644 index 0384ae1a8..000000000 --- a/spec/requests/account_details_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'spec_helper' - -describe "AccountDetails" do - describe "GET /account_details" do - it "works! (now write some real specs)" do - # Run the generator again with the --webrat flag if you want to use webrat methods/matchers - get account_details_path - response.status.should be(200) - end - end -end diff --git a/spec/views/account_details/edit.html.haml_spec.rb b/spec/views/account_details/edit.html.haml_spec.rb index de8c793bd..df62bbac2 100644 --- a/spec/views/account_details/edit.html.haml_spec.rb +++ b/spec/views/account_details/edit.html.haml_spec.rb @@ -2,9 +2,8 @@ require 'spec_helper' describe "account_details/edit" do before(:each) do - @account_detail = assign(:account_detail, - FactoryGirl.create(:account_detail) - ) + @member = FactoryGirl.create(:member) + @account_detail = assign(:account_detail, @member.account_detail) end it "renders the edit account_detail form" do diff --git a/spec/views/account_details/index.html.haml_spec.rb b/spec/views/account_details/index.html.haml_spec.rb index a9d8314b6..c2561eed8 100644 --- a/spec/views/account_details/index.html.haml_spec.rb +++ b/spec/views/account_details/index.html.haml_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe "account_details/index" do before(:each) do - @account_detail = FactoryGirl.create(:account_detail) + @member = FactoryGirl.create(:member) + @account_detail = @member.account_detail assign(:account_details, [@account_detail, @account_detail]) end diff --git a/spec/views/account_details/new.html.haml_spec.rb b/spec/views/account_details/new.html.haml_spec.rb index 553f5f15e..05900eafe 100644 --- a/spec/views/account_details/new.html.haml_spec.rb +++ b/spec/views/account_details/new.html.haml_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe "account_details/new" do before(:each) do - assign(:account_detail, FactoryGirl.create(:account_detail)) + @member = FactoryGirl.create(:member) + assign(:account_detail, @member.account_detail) end it "renders new account_detail form" do diff --git a/spec/views/account_details/show.html.haml_spec.rb b/spec/views/account_details/show.html.haml_spec.rb index df73ec69c..e510602b8 100644 --- a/spec/views/account_details/show.html.haml_spec.rb +++ b/spec/views/account_details/show.html.haml_spec.rb @@ -2,16 +2,15 @@ require 'spec_helper' describe "account_details/show" do before(:each) do - @account_detail = assign(:account_detail, - FactoryGirl.create(:account_detail) - ) + @member = FactoryGirl.create(:member) + @account_detail = assign(:account_detail, @member.account_detail) end it "renders attributes in

" do render # Run the generator again with the --webrat flag if you want to use webrat matchers rendered.should contain @account_detail.member_id.to_s - rendered.should contain @account_detail.account_type.name + rendered.should contain 'Free account' rendered.should contain @account_detail.paid_until.to_s end end From daa46d7acb65d2ca47ab31a316d37d7abc473876 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 09:49:00 +1000 Subject: [PATCH 051/184] seed account types; require name too --- ...0130517234458_require_account_type_name.rb | 9 ++++++++ db/schema.rb | 4 ++-- db/seeds.rb | 22 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20130517234458_require_account_type_name.rb diff --git a/db/migrate/20130517234458_require_account_type_name.rb b/db/migrate/20130517234458_require_account_type_name.rb new file mode 100644 index 000000000..c4bdfa69a --- /dev/null +++ b/db/migrate/20130517234458_require_account_type_name.rb @@ -0,0 +1,9 @@ +class RequireAccountTypeName < ActiveRecord::Migration + def up + change_column :account_types, :name, :string, :null => false + end + + def down + change_column :account_types, :name, :string, :null => true + end +end diff --git a/db/schema.rb b/db/schema.rb index 6798d6d9a..d70d92d58 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130517051922) do +ActiveRecord::Schema.define(:version => 20130517234458) do create_table "account_details", :force => true do |t| t.integer "member_id", :null => false @@ -22,7 +22,7 @@ ActiveRecord::Schema.define(:version => 20130517051922) do end create_table "account_types", :force => true do |t| - t.string "name" + t.string "name", :null => false t.boolean "is_paid" t.boolean "is_permanent_paid" t.datetime "created_at", :null => false diff --git a/db/seeds.rb b/db/seeds.rb index ce30ed5c1..601811628 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -58,6 +58,28 @@ if Rails.env.development? or Rails.env.test? @wrangler_user.roles << @wrangler @wrangler_user.save! + puts "Adding account types..." + AccountType.create!( + :name => "Free", + :is_paid => false, + :is_permanent_paid => false + ) + AccountType.create!( + :name => "Paid", + :is_paid => true, + :is_permanent_paid => false + ) + AccountType.create!( + :name => "Seed", + :is_paid => true, + :is_permanent_paid => true + ) + AccountType.create!( + :name => "Staff", + :is_paid => true, + :is_permanent_paid => true + ) + puts "Adding products..." Product.create!( :name => "Annual subscription", From 4aae4a7c85e157a58879445dc7e111fd8896e462 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 09:50:55 +1000 Subject: [PATCH 052/184] only admin can read/manage account types --- app/models/ability.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/ability.rb b/app/models/ability.rb index dd8a01a62..0d6da1bd7 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -17,6 +17,7 @@ class Ability cannot :read, Role cannot :read, Product cannot :read, AccountDetail + cannot :read, AccountType if member From 8b7f8faae6fa7f6daf9834726afc4feb710a3ac4 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 10:27:08 +1000 Subject: [PATCH 053/184] added account type and paid months columns to product --- app/models/account_type.rb | 1 + app/models/product.rb | 5 ++++- app/views/products/_form.html.haml | 8 ++++++++ app/views/products/index.html.haml | 4 ++++ app/views/products/show.html.haml | 6 ++++++ .../20130518000339_add_columns_to_product.rb | 6 ++++++ db/schema.rb | 14 ++++++++------ db/seeds.rb | 13 ++++++++----- spec/factories/products.rb | 2 ++ spec/requests/account_types_spec.rb | 11 ----------- spec/views/products/new.html.haml_spec.rb | 2 ++ 11 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 db/migrate/20130518000339_add_columns_to_product.rb delete mode 100644 spec/requests/account_types_spec.rb diff --git a/app/models/account_type.rb b/app/models/account_type.rb index d9b0df081..8aaa4f752 100644 --- a/app/models/account_type.rb +++ b/app/models/account_type.rb @@ -1,3 +1,4 @@ class AccountType < ActiveRecord::Base attr_accessible :is_paid, :is_permanent_paid, :name + has_many :products end diff --git a/app/models/product.rb b/app/models/product.rb index 88ee157b0..00dd79106 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -1,6 +1,9 @@ class Product < ActiveRecord::Base - attr_accessible :description, :min_price, :name + attr_accessible :description, :min_price, :name, + :account_type_id, :paid_months + has_and_belongs_to_many :orders + belongs_to :account_type def to_s name diff --git a/app/views/products/_form.html.haml b/app/views/products/_form.html.haml index 8b2909af7..798aa7208 100644 --- a/app/views/products/_form.html.haml +++ b/app/views/products/_form.html.haml @@ -15,5 +15,13 @@ .field = f.label :min_price = f.text_field :min_price + .field + = f.label :account_type + = collection_select(:product, :account_type_id, AccountType.all, :id, + :name, :selected => @product.account_type_id ) + .field + = f.label :paid_months + = f.text_field :paid_months + .control-group .actions = f.submit 'Save' diff --git a/app/views/products/index.html.haml b/app/views/products/index.html.haml index caf508db7..6a0cadb50 100644 --- a/app/views/products/index.html.haml +++ b/app/views/products/index.html.haml @@ -5,6 +5,8 @@ %th Name %th Description %th Min price + %th Account type + %th Paid months %th %th %th @@ -14,6 +16,8 @@ %td= product.name %td= product.description %td= product.min_price + %td= product.account_type ? product.account_type.name : "" + %td= product.paid_months %td= link_to 'Show', product %td= link_to 'Edit', edit_product_path(product) %td= link_to 'Destroy', product, :method => :delete, :data => { :confirm => 'Are you sure?' } diff --git a/app/views/products/show.html.haml b/app/views/products/show.html.haml index 03b2e6e08..874169c81 100644 --- a/app/views/products/show.html.haml +++ b/app/views/products/show.html.haml @@ -9,6 +9,12 @@ %p %b Min price: = @product.min_price +%p + %b Account type: + = @product.account_type.name +%p + %b Paid months: + = @product.paid_months = link_to 'Edit', edit_product_path(@product) \| diff --git a/db/migrate/20130518000339_add_columns_to_product.rb b/db/migrate/20130518000339_add_columns_to_product.rb new file mode 100644 index 000000000..bb3bcf781 --- /dev/null +++ b/db/migrate/20130518000339_add_columns_to_product.rb @@ -0,0 +1,6 @@ +class AddColumnsToProduct < ActiveRecord::Migration + def change + add_column :products, :account_type_id, :integer + add_column :products, :paid_months, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index d70d92d58..e2447b6b6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130517234458) do +ActiveRecord::Schema.define(:version => 20130518000339) do create_table "account_details", :force => true do |t| t.integer "member_id", :null => false @@ -185,11 +185,13 @@ ActiveRecord::Schema.define(:version => 20130517234458) do add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true create_table "products", :force => true do |t| - t.string "name", :null => false - t.string "description", :null => false - t.integer "min_price", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.string "name", :null => false + t.string "description", :null => false + t.integer "min_price", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.integer "account_type_id" + t.integer "paid_months" end create_table "roles", :force => true do |t| diff --git a/db/seeds.rb b/db/seeds.rb index 601811628..f307bb94a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -64,12 +64,12 @@ if Rails.env.development? or Rails.env.test? :is_paid => false, :is_permanent_paid => false ) - AccountType.create!( + @paid_account = AccountType.create!( :name => "Paid", :is_paid => true, :is_permanent_paid => false ) - AccountType.create!( + @seed_account = AccountType.create!( :name => "Seed", :is_paid => true, :is_permanent_paid => true @@ -83,13 +83,16 @@ if Rails.env.development? or Rails.env.test? puts "Adding products..." Product.create!( :name => "Annual subscription", - :description => "Paid account, renews yearly", - :min_price => 3000 + :description => "Paid account, 1 year", + :min_price => 3000, + :account_type_id => @paid_account.id, + :paid_months => 12 ) Product.create!( :name => "Seed account", :description => "Paid account, in perpetuity", - :min_price => 15000 + :min_price => 15000, + :account_type_id => @seed_account.id, ) end diff --git a/spec/factories/products.rb b/spec/factories/products.rb index 84ca3163f..037350ece 100644 --- a/spec/factories/products.rb +++ b/spec/factories/products.rb @@ -5,5 +5,7 @@ FactoryGirl.define do name "annual subscription" description "paid membership, renewing yearly" min_price "999" + account_type + paid_months 12 end end diff --git a/spec/requests/account_types_spec.rb b/spec/requests/account_types_spec.rb deleted file mode 100644 index c7d67ac6b..000000000 --- a/spec/requests/account_types_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'spec_helper' - -describe "AccountTypes" do - describe "GET /account_types" do - it "works! (now write some real specs)" do - # Run the generator again with the --webrat flag if you want to use webrat methods/matchers - get account_types_path - response.status.should be(200) - end - end -end diff --git a/spec/views/products/new.html.haml_spec.rb b/spec/views/products/new.html.haml_spec.rb index c508a89c5..a01ad897d 100644 --- a/spec/views/products/new.html.haml_spec.rb +++ b/spec/views/products/new.html.haml_spec.rb @@ -17,6 +17,8 @@ describe "products/new" do assert_select "input#product_name", :name => "product[name]" assert_select "input#product_description", :name => "product[description]" assert_select "input#product_min_price", :name => "product[min_price]" + assert_select "select#product_account_type_id", :name => "product[account_type_id]" + assert_select "input#product_paid_months", :name => "product[paid_months]" end end end From 865a1c705017e44886b57037da1f90c99f468aa9 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 10:29:18 +1000 Subject: [PATCH 054/184] validate paid_months (must be integer) --- app/models/product.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/product.rb b/app/models/product.rb index 00dd79106..031f59916 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -5,6 +5,9 @@ class Product < ActiveRecord::Base has_and_belongs_to_many :orders belongs_to :account_type + validates :paid_months, :numericality => { :only_integer => true, + :allow_nil => true } + def to_s name end From 3aaf344951fc5be91628212d784e798f6502d476 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 10:50:36 +1000 Subject: [PATCH 055/184] rename account details to just account --- .../javascripts/account_details.js.coffee | 3 - app/controllers/account_details_controller.rb | 46 ---------- app/controllers/accounts_controller.rb | 46 ++++++++++ app/helpers/account_details_helper.rb | 2 - app/models/ability.rb | 6 +- app/models/{account_detail.rb => account.rb} | 2 +- app/models/member.rb | 6 +- app/views/account_details/edit.html.haml | 7 -- app/views/account_details/index.html.haml | 23 ----- app/views/account_details/new.html.haml | 5 - app/views/account_details/show.html.haml | 18 ---- .../_form.html.haml | 8 +- app/views/accounts/edit.html.haml | 7 ++ app/views/accounts/index.html.haml | 23 +++++ app/views/accounts/new.html.haml | 5 + app/views/accounts/show.html.haml | 18 ++++ config/routes.rb | 14 +-- ...002942_rename_account_detail_to_account.rb | 6 ++ db/schema.rb | 18 ++-- .../account_details_controller_spec.rb | 91 ------------------- spec/controllers/accounts_controller_spec.rb | 91 +++++++++++++++++++ .../{account_details.rb => accounts.rb} | 0 spec/helpers/account_details_helper_spec.rb | 15 --- spec/models/ability_spec.rb | 18 ++-- ...account_detail_spec.rb => account_spec.rb} | 6 +- spec/models/member_spec.rb | 4 +- spec/routing/account_details_routing_spec.rb | 35 ------- .../account_details/edit.html.haml_spec.rb | 18 ---- .../account_details/index.html.haml_spec.rb | 15 --- .../account_details/new.html.haml_spec.rb | 18 ---- spec/views/accounts/edit.html.haml_spec.rb | 18 ++++ spec/views/accounts/index.html.haml_spec.rb | 15 +++ spec/views/accounts/new.html.haml_spec.rb | 18 ++++ .../show.html.haml_spec.rb | 8 +- 34 files changed, 290 insertions(+), 343 deletions(-) delete mode 100644 app/assets/javascripts/account_details.js.coffee delete mode 100644 app/controllers/account_details_controller.rb create mode 100644 app/controllers/accounts_controller.rb delete mode 100644 app/helpers/account_details_helper.rb rename app/models/{account_detail.rb => account.rb} (84%) delete mode 100644 app/views/account_details/edit.html.haml delete mode 100644 app/views/account_details/index.html.haml delete mode 100644 app/views/account_details/new.html.haml delete mode 100644 app/views/account_details/show.html.haml rename app/views/{account_details => accounts}/_form.html.haml (54%) create mode 100644 app/views/accounts/edit.html.haml create mode 100644 app/views/accounts/index.html.haml create mode 100644 app/views/accounts/new.html.haml create mode 100644 app/views/accounts/show.html.haml create mode 100644 db/migrate/20130518002942_rename_account_detail_to_account.rb delete mode 100644 spec/controllers/account_details_controller_spec.rb create mode 100644 spec/controllers/accounts_controller_spec.rb rename spec/factories/{account_details.rb => accounts.rb} (100%) delete mode 100644 spec/helpers/account_details_helper_spec.rb rename spec/models/{account_detail_spec.rb => account_spec.rb} (65%) delete mode 100644 spec/routing/account_details_routing_spec.rb delete mode 100644 spec/views/account_details/edit.html.haml_spec.rb delete mode 100644 spec/views/account_details/index.html.haml_spec.rb delete mode 100644 spec/views/account_details/new.html.haml_spec.rb create mode 100644 spec/views/accounts/edit.html.haml_spec.rb create mode 100644 spec/views/accounts/index.html.haml_spec.rb create mode 100644 spec/views/accounts/new.html.haml_spec.rb rename spec/views/{account_details => accounts}/show.html.haml_spec.rb (55%) diff --git a/app/assets/javascripts/account_details.js.coffee b/app/assets/javascripts/account_details.js.coffee deleted file mode 100644 index 761567942..000000000 --- a/app/assets/javascripts/account_details.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/account_details_controller.rb b/app/controllers/account_details_controller.rb deleted file mode 100644 index 364b5bee4..000000000 --- a/app/controllers/account_details_controller.rb +++ /dev/null @@ -1,46 +0,0 @@ -class AccountDetailsController < ApplicationController - load_and_authorize_resource - # GET /account_details - # GET /account_details.json - def index - @account_details = AccountDetail.all - - respond_to do |format| - format.html # index.html.erb - format.json { render json: @account_details } - end - end - - # GET /account_details/1 - # GET /account_details/1.json - def show - @account_detail = AccountDetail.find(params[:id]) - - respond_to do |format| - format.html # show.html.erb - format.json { render json: @account_detail } - end - end - - # GET /account_details/1/edit - def edit - @account_detail = AccountDetail.find(params[:id]) - end - - # PUT /account_details/1 - # PUT /account_details/1.json - def update - @account_detail = AccountDetail.find(params[:id]) - - respond_to do |format| - if @account_detail.update_attributes(params[:account_detail]) - format.html { redirect_to @account_detail, notice: 'Account detail was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: "edit" } - format.json { render json: @account_detail.errors, status: :unprocessable_entity } - end - end - end - -end diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb new file mode 100644 index 000000000..b3f7f606a --- /dev/null +++ b/app/controllers/accounts_controller.rb @@ -0,0 +1,46 @@ +class AccountsController < ApplicationController + load_and_authorize_resource + # GET /accounts + # GET /accounts.json + def index + @accounts = Account.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @accounts } + end + end + + # GET /accounts/1 + # GET /accounts/1.json + def show + @account = Account.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @account } + end + end + + # GET /accounts/1/edit + def edit + @account = Account.find(params[:id]) + end + + # PUT /accounts/1 + # PUT /accounts/1.json + def update + @account = Account.find(params[:id]) + + respond_to do |format| + if @account.update_attributes(params[:account]) + format.html { redirect_to @account, notice: 'Account detail was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @account.errors, status: :unprocessable_entity } + end + end + end + +end diff --git a/app/helpers/account_details_helper.rb b/app/helpers/account_details_helper.rb deleted file mode 100644 index ea418e8ec..000000000 --- a/app/helpers/account_details_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module AccountDetailsHelper -end diff --git a/app/models/ability.rb b/app/models/ability.rb index 0d6da1bd7..c3c59ee4d 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -16,7 +16,7 @@ class Ability # and nobody should be able to view this except admins cannot :read, Role cannot :read, Product - cannot :read, AccountDetail + cannot :read, Account cannot :read, AccountType if member @@ -98,8 +98,8 @@ class Ability # admins can read and manage members' account details (paid acct # status, etc) - can :read, AccountDetail - can :manage, AccountDetail + can :read, Account + can :manage, Account can :read, AccountType can :manage, AccountType end diff --git a/app/models/account_detail.rb b/app/models/account.rb similarity index 84% rename from app/models/account_detail.rb rename to app/models/account.rb index c218a1c1f..25cfc05ba 100644 --- a/app/models/account_detail.rb +++ b/app/models/account.rb @@ -1,4 +1,4 @@ -class AccountDetail < ActiveRecord::Base +class Account < ActiveRecord::Base attr_accessible :account_type_id, :member_id, :paid_until belongs_to :member belongs_to :account_type diff --git a/app/models/member.rb b/app/models/member.rb index d8aa8e96c..6f6686c90 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -12,7 +12,7 @@ class Member < ActiveRecord::Base has_many :sent_notifications, :foreign_key => 'sender_id' has_many :authentications has_many :orders - has_one :account_detail + has_one :account default_scope order("lower(login_name) asc") scope :confirmed, where('confirmed_at IS NOT NULL') @@ -69,10 +69,10 @@ class Member < ActiveRecord::Base # Give each new member a default garden after_create {|member| Garden.create(:name => "Garden", :owner_id => member.id) } - # and an account details record + # and an account record (for paid accounts etc) # we use find_or_create to avoid accidentally creating a second one, # which can happen sometimes especially with FactoryGirl associations - after_create {|member| AccountDetail.find_or_create_by_member_id(:member_id => member.id) } + after_create {|member| Account.find_or_create_by_member_id(:member_id => member.id) } # allow login via either login_name or email address def self.find_first_by_auth_conditions(warden_conditions) diff --git a/app/views/account_details/edit.html.haml b/app/views/account_details/edit.html.haml deleted file mode 100644 index 4cca4b860..000000000 --- a/app/views/account_details/edit.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%h1 Editing account_detail - -= render 'form' - -= link_to 'Show', @account_detail -\| -= link_to 'Back', account_details_path diff --git a/app/views/account_details/index.html.haml b/app/views/account_details/index.html.haml deleted file mode 100644 index 839dbafd3..000000000 --- a/app/views/account_details/index.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%h1 Listing account_details - -%table - %tr - %th Member - %th Account type - %th Paid until - %th - %th - %th - - - @account_details.each do |account_detail| - %tr - %td= account_detail.member_id - %td= account_detail.account_type - %td= account_detail.paid_until - %td= link_to 'Show', account_detail - %td= link_to 'Edit', edit_account_detail_path(account_detail) - %td= link_to 'Destroy', account_detail, :method => :delete, :data => { :confirm => 'Are you sure?' } - -%br - -= link_to 'New Account detail', new_account_detail_path diff --git a/app/views/account_details/new.html.haml b/app/views/account_details/new.html.haml deleted file mode 100644 index c9e8e4c4f..000000000 --- a/app/views/account_details/new.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%h1 New account_detail - -= render 'form' - -= link_to 'Back', account_details_path diff --git a/app/views/account_details/show.html.haml b/app/views/account_details/show.html.haml deleted file mode 100644 index e9d97aa4e..000000000 --- a/app/views/account_details/show.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -%p#notice= notice - -%p - %b Member: - = @account_detail.member_id -%p - %b Account type: - - if @account_detail.account_type - = @account_detail.account_type.name - - else - Free account -%p - %b Paid until: - = @account_detail.paid_until - -= link_to 'Edit', edit_account_detail_path(@account_detail) -\| -= link_to 'Back', account_details_path diff --git a/app/views/account_details/_form.html.haml b/app/views/accounts/_form.html.haml similarity index 54% rename from app/views/account_details/_form.html.haml rename to app/views/accounts/_form.html.haml index 034828629..6e648d622 100644 --- a/app/views/account_details/_form.html.haml +++ b/app/views/accounts/_form.html.haml @@ -1,9 +1,9 @@ -= form_for @account_detail do |f| - - if @account_detail.errors.any? += form_for @account do |f| + - if @account.errors.any? #error_explanation - %h2= "#{pluralize(@account_detail.errors.count, "error")} prohibited this account_detail from being saved:" + %h2= "#{pluralize(@account.errors.count, "error")} prohibited this account from being saved:" %ul - - @account_detail.errors.full_messages.each do |msg| + - @account.errors.full_messages.each do |msg| %li= msg .field diff --git a/app/views/accounts/edit.html.haml b/app/views/accounts/edit.html.haml new file mode 100644 index 000000000..5b4039cfa --- /dev/null +++ b/app/views/accounts/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing account + += render 'form' + += link_to 'Show', @account +\| += link_to 'Back', accounts_path diff --git a/app/views/accounts/index.html.haml b/app/views/accounts/index.html.haml new file mode 100644 index 000000000..66345747f --- /dev/null +++ b/app/views/accounts/index.html.haml @@ -0,0 +1,23 @@ +%h1 Listing accounts + +%table + %tr + %th Member + %th Account type + %th Paid until + %th + %th + %th + + - @accounts.each do |account| + %tr + %td= account.member_id + %td= account.account_type + %td= account.paid_until + %td= link_to 'Show', account + %td= link_to 'Edit', edit_account_path(account) + %td= link_to 'Destroy', account, :method => :delete, :data => { :confirm => 'Are you sure?' } + +%br + += link_to 'New Account detail', new_account_path diff --git a/app/views/accounts/new.html.haml b/app/views/accounts/new.html.haml new file mode 100644 index 000000000..489317b27 --- /dev/null +++ b/app/views/accounts/new.html.haml @@ -0,0 +1,5 @@ +%h1 New account + += render 'form' + += link_to 'Back', accounts_path diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml new file mode 100644 index 000000000..8a9b0e639 --- /dev/null +++ b/app/views/accounts/show.html.haml @@ -0,0 +1,18 @@ +%p#notice= notice + +%p + %b Member: + = @account.member_id +%p + %b Account type: + - if @account.account_type + = @account.account_type.name + - else + Free account +%p + %b Paid until: + = @account.paid_until + += link_to 'Edit', edit_account_path(@account) +\| += link_to 'Back', accounts_path diff --git a/config/routes.rb b/config/routes.rb index 1d6ab4adc..6064228c0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,14 +1,5 @@ Growstuff::Application.routes.draw do - resources :account_types - - - resources :account_details - - - resources :order_items - - devise_for :members, :controllers => { :registrations => "registrations" } resources :authentications @@ -22,8 +13,13 @@ Growstuff::Application.routes.draw do resources :roles resources :forums resources :notifications + + # everything for paid accounts etc + resources :account_types + resources :accounts resources :orders match 'orders/:id/complete' => 'orders#complete', :as => 'complete_order' + resources :order_items resources :products get "home/index" diff --git a/db/migrate/20130518002942_rename_account_detail_to_account.rb b/db/migrate/20130518002942_rename_account_detail_to_account.rb new file mode 100644 index 000000000..8c2243431 --- /dev/null +++ b/db/migrate/20130518002942_rename_account_detail_to_account.rb @@ -0,0 +1,6 @@ +class RenameAccountDetailToAccount < ActiveRecord::Migration + def change + rename_table :account_details, :accounts + end + +end diff --git a/db/schema.rb b/db/schema.rb index e2447b6b6..7a372a776 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,15 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130518000339) do - - create_table "account_details", :force => true do |t| - t.integer "member_id", :null => false - t.integer "account_type_id" - t.datetime "paid_until" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end +ActiveRecord::Schema.define(:version => 20130518002942) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -29,6 +21,14 @@ ActiveRecord::Schema.define(:version => 20130518000339) do t.datetime "updated_at", :null => false end + create_table "accounts", :force => true do |t| + t.integer "member_id", :null => false + t.integer "account_type_id" + t.datetime "paid_until" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "authentications", :force => true do |t| t.integer "member_id", :null => false t.string "provider", :null => false diff --git a/spec/controllers/account_details_controller_spec.rb b/spec/controllers/account_details_controller_spec.rb deleted file mode 100644 index ad0bcef48..000000000 --- a/spec/controllers/account_details_controller_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'spec_helper' - -describe AccountDetailsController do - - login_member(:admin_member) - - def valid_attributes - { "paid_until" => Time.now } - end - - def create_account_detail - # account details are automatically created when you create a new - # member; creating them manually will just cause errors as only one is - # allowed. - - member = FactoryGirl.create(:member) - return member.account_detail - end - - describe "GET index" do - it "assigns all account_details as @account_details" do - account_detail = create_account_detail - get :index, {} - # can't match the whole list because it includes the one for the - # logged in admin member, sigh - assigns(:account_details).should include(account_detail) - assigns(:account_details).count.should eq 2 - end - end - - describe "GET show" do - it "assigns the requested account_detail as @account_detail" do - account_detail = create_account_detail - get :show, {:id => account_detail.to_param} - assigns(:account_detail).should eq(account_detail) - end - end - - describe "GET edit" do - it "assigns the requested account_detail as @account_detail" do - account_detail = create_account_detail - get :edit, {:id => account_detail.to_param} - assigns(:account_detail).should eq(account_detail) - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested account_detail" do - account_detail = create_account_detail - # Assuming there are no other account_detail in the database, this - # specifies that the AccountDetail created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - AccountDetail.any_instance.should_receive(:update_attributes).with({ "member_id" => "1" }) - put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "1" }} - end - - it "assigns the requested account_detail as @account_detail" do - account_detail = create_account_detail - put :update, {:id => account_detail.to_param, :account_detail => valid_attributes} - assigns(:account_detail).should eq(account_detail) - end - - it "redirects to the account_detail" do - account_detail = create_account_detail - put :update, {:id => account_detail.to_param, :account_detail => valid_attributes} - response.should redirect_to(account_detail) - end - end - - describe "with invalid params" do - it "assigns the account_detail as @account_detail" do - account_detail = create_account_detail - # Trigger the behavior that occurs when invalid params are submitted - AccountDetail.any_instance.stub(:save).and_return(false) - put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }} - assigns(:account_detail).should eq(account_detail) - end - - it "re-renders the 'edit' template" do - account_detail = create_account_detail - # Trigger the behavior that occurs when invalid params are submitted - AccountDetail.any_instance.stub(:save).and_return(false) - put :update, {:id => account_detail.to_param, :account_detail => { "member_id" => "invalid value" }} - response.should render_template("edit") - end - end - end - -end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb new file mode 100644 index 000000000..f04a8cc69 --- /dev/null +++ b/spec/controllers/accounts_controller_spec.rb @@ -0,0 +1,91 @@ +require 'spec_helper' + +describe AccountsController do + + login_member(:admin_member) + + def valid_attributes + { "paid_until" => Time.now } + end + + def create_account + # account details are automatically created when you create a new + # member; creating them manually will just cause errors as only one is + # allowed. + + member = FactoryGirl.create(:member) + return member.account + end + + describe "GET index" do + it "assigns all accounts as @accounts" do + account = create_account + get :index, {} + # can't match the whole list because it includes the one for the + # logged in admin member, sigh + assigns(:accounts).should include(account) + assigns(:accounts).count.should eq 2 + end + end + + describe "GET show" do + it "assigns the requested account as @account" do + account = create_account + get :show, {:id => account.to_param} + assigns(:account).should eq(account) + end + end + + describe "GET edit" do + it "assigns the requested account as @account" do + account = create_account + get :edit, {:id => account.to_param} + assigns(:account).should eq(account) + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested account" do + account = create_account + # Assuming there are no other account in the database, this + # specifies that the Account created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Account.any_instance.should_receive(:update_attributes).with({ "member_id" => "1" }) + put :update, {:id => account.to_param, :account => { "member_id" => "1" }} + end + + it "assigns the requested account as @account" do + account = create_account + put :update, {:id => account.to_param, :account => valid_attributes} + assigns(:account).should eq(account) + end + + it "redirects to the account" do + account = create_account + put :update, {:id => account.to_param, :account => valid_attributes} + response.should redirect_to(account) + end + end + + describe "with invalid params" do + it "assigns the account as @account" do + account = create_account + # Trigger the behavior that occurs when invalid params are submitted + Account.any_instance.stub(:save).and_return(false) + put :update, {:id => account.to_param, :account => { "member_id" => "invalid value" }} + assigns(:account).should eq(account) + end + + it "re-renders the 'edit' template" do + account = create_account + # Trigger the behavior that occurs when invalid params are submitted + Account.any_instance.stub(:save).and_return(false) + put :update, {:id => account.to_param, :account => { "member_id" => "invalid value" }} + response.should render_template("edit") + end + end + end + +end diff --git a/spec/factories/account_details.rb b/spec/factories/accounts.rb similarity index 100% rename from spec/factories/account_details.rb rename to spec/factories/accounts.rb diff --git a/spec/helpers/account_details_helper_spec.rb b/spec/helpers/account_details_helper_spec.rb deleted file mode 100644 index 4830dd76b..000000000 --- a/spec/helpers/account_details_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the AccountDetailsHelper. For example: -# -# describe AccountDetailsHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end -describe AccountDetailsHelper do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 722b52274..3d0cd04e3 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -246,17 +246,17 @@ describe Ability do context 'account details' do before(:each) do - @account_detail = @member.account_detail + @account = @member.account end context 'ordinary member' do it "can't read account details" do - @ability.should_not be_able_to(:read, @account_detail) + @ability.should_not be_able_to(:read, @account) end it "can't manage account details" do - @ability.should_not be_able_to(:create, AccountDetail) - @ability.should_not be_able_to(:update, @account_detail) - @ability.should_not be_able_to(:destroy, @account_detail) + @ability.should_not be_able_to(:create, Account) + @ability.should_not be_able_to(:update, @account) + @ability.should_not be_able_to(:destroy, @account) end end @@ -269,12 +269,12 @@ describe Ability do end it "can read account details" do - @admin_ability.should be_able_to(:read, @account_detail) + @admin_ability.should be_able_to(:read, @account) end it "can manage account details" do - @admin_ability.should be_able_to(:create, AccountDetail) - @admin_ability.should be_able_to(:update, @account_detail) - @admin_ability.should be_able_to(:destroy, @account_detail) + @admin_ability.should be_able_to(:create, Account) + @admin_ability.should be_able_to(:update, @account) + @admin_ability.should be_able_to(:destroy, @account) end end diff --git a/spec/models/account_detail_spec.rb b/spec/models/account_spec.rb similarity index 65% rename from spec/models/account_detail_spec.rb rename to spec/models/account_spec.rb index 94a786cd6..26d59116e 100644 --- a/spec/models/account_detail_spec.rb +++ b/spec/models/account_spec.rb @@ -1,16 +1,16 @@ require 'spec_helper' -describe AccountDetail do +describe Account do before(:each) do @member = FactoryGirl.create(:member) end it "auto-creates an account detail record when a member is created" do - @member.account_detail.should be_an_instance_of AccountDetail + @member.account.should be_an_instance_of Account end it "won't let you create two account details for the same member" do - @details = AccountDetail.new(:member_id => @member.id) + @details = Account.new(:member_id => @member.id) @details.should_not be_valid end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 109a3bdcb..24c1badc2 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -29,9 +29,9 @@ describe 'member' do @member.gardens.count.should == 1 end - it 'should have a account detail entry' do + it 'should have a accounts entry' do @member.save - @member.account_detail.should be_an_instance_of AccountDetail + @member.account.should be_an_instance_of Account end it "doesn't show email by default" do diff --git a/spec/routing/account_details_routing_spec.rb b/spec/routing/account_details_routing_spec.rb deleted file mode 100644 index 538fa8ddf..000000000 --- a/spec/routing/account_details_routing_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require "spec_helper" - -describe AccountDetailsController do - describe "routing" do - - it "routes to #index" do - get("/account_details").should route_to("account_details#index") - end - - it "routes to #new" do - get("/account_details/new").should route_to("account_details#new") - end - - it "routes to #show" do - get("/account_details/1").should route_to("account_details#show", :id => "1") - end - - it "routes to #edit" do - get("/account_details/1/edit").should route_to("account_details#edit", :id => "1") - end - - it "routes to #create" do - post("/account_details").should route_to("account_details#create") - end - - it "routes to #update" do - put("/account_details/1").should route_to("account_details#update", :id => "1") - end - - it "routes to #destroy" do - delete("/account_details/1").should route_to("account_details#destroy", :id => "1") - end - - end -end diff --git a/spec/views/account_details/edit.html.haml_spec.rb b/spec/views/account_details/edit.html.haml_spec.rb deleted file mode 100644 index df62bbac2..000000000 --- a/spec/views/account_details/edit.html.haml_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe "account_details/edit" do - before(:each) do - @member = FactoryGirl.create(:member) - @account_detail = assign(:account_detail, @member.account_detail) - end - - it "renders the edit account_detail form" do - render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => account_details_path(@account_detail), :method => "post" do - assert_select "input#account_detail_member_id", :name => "account_detail[member_id]" - assert_select "input#account_detail_account_type", :name => "account_detail[account_type]" - end - end -end diff --git a/spec/views/account_details/index.html.haml_spec.rb b/spec/views/account_details/index.html.haml_spec.rb deleted file mode 100644 index c2561eed8..000000000 --- a/spec/views/account_details/index.html.haml_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe "account_details/index" do - before(:each) do - @member = FactoryGirl.create(:member) - @account_detail = @member.account_detail - assign(:account_details, [@account_detail, @account_detail]) - end - - it "renders a list of account_details" do - render - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "tr>td", :text => @account_detail.member_id.to_s, :count => 2 - end -end diff --git a/spec/views/account_details/new.html.haml_spec.rb b/spec/views/account_details/new.html.haml_spec.rb deleted file mode 100644 index 05900eafe..000000000 --- a/spec/views/account_details/new.html.haml_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'spec_helper' - -describe "account_details/new" do - before(:each) do - @member = FactoryGirl.create(:member) - assign(:account_detail, @member.account_detail) - end - - it "renders new account_detail form" do - render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => account_details_path, :method => "post" do - assert_select "input#account_detail_member_id", :name => "account_detail[member_id]" - assert_select "input#account_detail_account_type", :name => "account_detail[account_type]" - end - end -end diff --git a/spec/views/accounts/edit.html.haml_spec.rb b/spec/views/accounts/edit.html.haml_spec.rb new file mode 100644 index 000000000..af3931dfa --- /dev/null +++ b/spec/views/accounts/edit.html.haml_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe "accounts/edit" do + before(:each) do + @member = FactoryGirl.create(:member) + @account = assign(:account, @member.account) + end + + it "renders the edit account form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => accounts_path(@account), :method => "post" do + assert_select "input#account_member_id", :name => "account[member_id]" + assert_select "input#account_account_type", :name => "account[account_type]" + end + end +end diff --git a/spec/views/accounts/index.html.haml_spec.rb b/spec/views/accounts/index.html.haml_spec.rb new file mode 100644 index 000000000..56e76b0ff --- /dev/null +++ b/spec/views/accounts/index.html.haml_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe "accounts/index" do + before(:each) do + @member = FactoryGirl.create(:member) + @account = @member.account + assign(:accounts, [@account, @account]) + end + + it "renders a list of accounts" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => @account.member_id.to_s, :count => 2 + end +end diff --git a/spec/views/accounts/new.html.haml_spec.rb b/spec/views/accounts/new.html.haml_spec.rb new file mode 100644 index 000000000..ab60cc346 --- /dev/null +++ b/spec/views/accounts/new.html.haml_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe "accounts/new" do + before(:each) do + @member = FactoryGirl.create(:member) + assign(:account, @member.account) + end + + it "renders new account form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => accounts_path, :method => "post" do + assert_select "input#account_member_id", :name => "account[member_id]" + assert_select "input#account_account_type", :name => "account[account_type]" + end + end +end diff --git a/spec/views/account_details/show.html.haml_spec.rb b/spec/views/accounts/show.html.haml_spec.rb similarity index 55% rename from spec/views/account_details/show.html.haml_spec.rb rename to spec/views/accounts/show.html.haml_spec.rb index e510602b8..cd8c0bd60 100644 --- a/spec/views/account_details/show.html.haml_spec.rb +++ b/spec/views/accounts/show.html.haml_spec.rb @@ -1,16 +1,16 @@ require 'spec_helper' -describe "account_details/show" do +describe "accounts/show" do before(:each) do @member = FactoryGirl.create(:member) - @account_detail = assign(:account_detail, @member.account_detail) + @account = assign(:account, @member.account) end it "renders attributes in

" do render # Run the generator again with the --webrat flag if you want to use webrat matchers - rendered.should contain @account_detail.member_id.to_s + rendered.should contain @account.member_id.to_s rendered.should contain 'Free account' - rendered.should contain @account_detail.paid_until.to_s + rendered.should contain @account.paid_until.to_s end end From fac1b9cc01a5d51fff7b0c1009883869b24b2f98 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 11:02:14 +1000 Subject: [PATCH 056/184] added is_paid? method to member --- app/models/member.rb | 11 +++++++++- spec/models/member_spec.rb | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/app/models/member.rb b/app/models/member.rb index 6f6686c90..874e2ba25 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -96,6 +96,16 @@ class Member < ActiveRecord::Base orders.where(:completed_at => nil).first end + def is_paid? + if account.account_type.is_permanent_paid + return true + elsif account.account_type.is_paid and account.paid_until >= Time.zone.now + return true + else + return false + end + end + protected def empty_unwanted_geocodes if self.location.to_s == '' @@ -104,5 +114,4 @@ class Member < ActiveRecord::Base end end - end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 24c1badc2..f62057875 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -256,4 +256,49 @@ describe 'member' do end end + context "paid accounts" do + before(:each) do + @member = FactoryGirl.create(:member) + end + + it "recognises a permanent paid account" do + @account_type = FactoryGirl.create(:account_type, + :is_paid => true, :is_permanent_paid => true) + @member.account.account_type = @account_type + @member.is_paid?.should be_true + end + + it "recognises a current paid account" do + @account_type = FactoryGirl.create(:account_type, + :is_paid => true, :is_permanent_paid => false) + @member.account.account_type = @account_type + @member.account.paid_until = Time.zone.now + 1.month + @member.is_paid?.should be_true + end + + it "recognises an expired paid account" do + @account_type = FactoryGirl.create(:account_type, + :is_paid => true, :is_permanent_paid => false) + @member.account.account_type = @account_type + @member.account.paid_until = Time.zone.now - 1.minute + @member.is_paid?.should be_false + end + + it "recognises a free account" do + @account_type = FactoryGirl.create(:account_type, + :is_paid => false, :is_permanent_paid => false) + @member.account.account_type = @account_type + @member.is_paid?.should be_false + end + + it "recognises a free account even with paid_until set" do + @account_type = FactoryGirl.create(:account_type, + :is_paid => false, :is_permanent_paid => false) + @member.account.account_type = @account_type + @member.account.paid_until = Time.zone.now + 1.month + @member.is_paid?.should be_false + end + + end + end From 75e29132030f2e60b50d76900a7f75c1ee61de97 Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 11:16:45 +1000 Subject: [PATCH 057/184] added method to update paid acct details after product purchase --- app/models/product.rb | 12 ++++++++++++ spec/models/product_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/app/models/product.rb b/app/models/product.rb index 031f59916..9f318107c 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -11,4 +11,16 @@ class Product < ActiveRecord::Base def to_s name end + + # when purchasing a product that gives you a paid account, this method + # does all the messing around to actually make sure the account is + # updated correctly -- account type, paid until, etc. + def update_account(member) + member.account.account_type = account_type + if paid_months + start_date = member.account.paid_until || Time.zone.now + member.account.paid_until = start_date + paid_months.months + end + end + end diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb index d8fc496a8..e56502413 100644 --- a/spec/models/product_spec.rb +++ b/spec/models/product_spec.rb @@ -6,4 +6,30 @@ describe Product do @product = FactoryGirl.create(:product) @product.to_s.should eq @product.name end + + context "update account" do + before(:each) do + @product = FactoryGirl.create(:product, + :paid_months => 3 + ) + @member = FactoryGirl.create(:member) + end + + it "sets account_type" do + @product.update_account(@member) + @member.account.account_type.should eq @product.account_type + end + + it "sets paid_until" do + @member.account.paid_until = nil # blank for now, as if never paid before + @product.update_account(@member) + + # stringify to avoid millisecond problems... + @member.account.paid_until.to_s.should eq (Time.zone.now + 3.months).to_s + + # and again to make sure it works for currently paid accounts + @product.update_account(@member) + @member.account.paid_until.to_s.should eq (Time.zone.now + 6.months).to_s + end + end end From 7c2fc5200997f538a8fa33c70d2885acc40cd2df Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 11:26:29 +1000 Subject: [PATCH 058/184] added update_account method to order --- app/models/order.rb | 8 ++++++++ app/models/product.rb | 4 +++- spec/models/order_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/models/order.rb b/app/models/order.rb index 2171857f5..eaa47e8a9 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -5,4 +5,12 @@ class Order < ActiveRecord::Base has_many :order_items, :dependent => :destroy default_scope order('created_at DESC') + + # when an order is completed, we update the member's account to mark + # them as paid, or whatever, based on what products they ordered + def update_account + order_items.each do |i| + i.product.update_account(member) + end + end end diff --git a/app/models/product.rb b/app/models/product.rb index 9f318107c..830880478 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -14,7 +14,9 @@ class Product < ActiveRecord::Base # when purchasing a product that gives you a paid account, this method # does all the messing around to actually make sure the account is - # updated correctly -- account type, paid until, etc. + # updated correctly -- account type, paid until, etc. Usually this is + # called by order.update_account, which loops through all order items + # and does this for each one. def update_account(member) member.account.account_type = account_type if paid_months diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index 57a878701..54d67d16f 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -17,4 +17,24 @@ describe Order do Order.all.should eq [@order2, @order] end + it 'updates the account details' do + @member = FactoryGirl.create(:member) + @order = FactoryGirl.create(:order, :member => @member) + @account_type = FactoryGirl.create(:account_type, :name => 'paid') + @product = FactoryGirl.create(:product, + :account_type => @account_type, + :paid_months => 3 + ) + @order_item = FactoryGirl.create(:order_item, + :order_id => @order.id, :product_id => @product.id) + + @member.account.account_type.should be_nil + @member.account.paid_until.should be_nil + + @order.update_account + + @member.account.account_type.should eq @account_type + @member.account.paid_until.should_not be_nil + end + end From c644753a9bfbc9a246ea74c610831e3f416e507a Mon Sep 17 00:00:00 2001 From: Skud Date: Sat, 18 May 2013 11:32:14 +1000 Subject: [PATCH 059/184] Call update_account when you complete an order. Also had to tweak product.update_account to actually save the account! --- app/controllers/orders_controller.rb | 3 ++- app/models/product.rb | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 428c1ef37..0da87512e 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -32,9 +32,10 @@ class OrdersController < ApplicationController @order = Order.find(params[:id]) @order.completed_at = Time.zone.now - # current_member.paid = true # or whatever @order.save + @order.update_account # apply paid account benefits, etc. + respond_to do |format| format.html # new.html.erb end diff --git a/app/models/product.rb b/app/models/product.rb index 830880478..dad488d59 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -23,6 +23,7 @@ class Product < ActiveRecord::Base start_date = member.account.paid_until || Time.zone.now member.account.paid_until = start_date + paid_months.months end + member.account.save end end From d601396741c4a5a19edb18cf96ee56c4fe3bac7a Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 20 May 2013 11:35:10 +1000 Subject: [PATCH 060/184] moved the update_order method from product to member --- app/models/member.rb | 16 ++++++++++++++++ app/models/order.rb | 2 +- app/models/product.rb | 14 -------------- spec/models/member_spec.rb | 26 ++++++++++++++++++++++++++ spec/models/product_spec.rb | 25 ------------------------- 5 files changed, 43 insertions(+), 40 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index 874e2ba25..1b6b04009 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -96,6 +96,22 @@ class Member < ActiveRecord::Base orders.where(:completed_at => nil).first end + # when purchasing a product that gives you a paid account, this method + # does all the messing around to actually make sure the account is + # updated correctly -- account type, paid until, etc. Usually this is + # called by order.update_account, which loops through all order items + # and does this for each one. + def update_account_after_purchase(product) + if product.account_type + account.account_type = product.account_type + end + if product.paid_months + start_date = account.paid_until || Time.zone.now + account.paid_until = start_date + product.paid_months.months + end + account.save + end + def is_paid? if account.account_type.is_permanent_paid return true diff --git a/app/models/order.rb b/app/models/order.rb index eaa47e8a9..f481ced3e 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -10,7 +10,7 @@ class Order < ActiveRecord::Base # them as paid, or whatever, based on what products they ordered def update_account order_items.each do |i| - i.product.update_account(member) + member.update_account_after_purchase(i.product) end end end diff --git a/app/models/product.rb b/app/models/product.rb index dad488d59..bc0d58fd1 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -12,18 +12,4 @@ class Product < ActiveRecord::Base name end - # when purchasing a product that gives you a paid account, this method - # does all the messing around to actually make sure the account is - # updated correctly -- account type, paid until, etc. Usually this is - # called by order.update_account, which loops through all order items - # and does this for each one. - def update_account(member) - member.account.account_type = account_type - if paid_months - start_date = member.account.paid_until || Time.zone.now - member.account.paid_until = start_date + paid_months.months - end - member.account.save - end - end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index f62057875..4d349b1a1 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -301,4 +301,30 @@ describe 'member' do end + context "update account" do + before(:each) do + @product = FactoryGirl.create(:product, + :paid_months => 3 + ) + @member = FactoryGirl.create(:member) + end + + it "sets account_type" do + @member.update_account_after_purchase(@product) + @member.account.account_type.should eq @product.account_type + end + + it "sets paid_until" do + @member.account.paid_until = nil # blank for now, as if never paid before + @member.update_account_after_purchase(@product) + + # stringify to avoid millisecond problems... + @member.account.paid_until.to_s.should eq (Time.zone.now + 3.months).to_s + + # and again to make sure it works for currently paid accounts + @member.update_account_after_purchase(@product) + @member.account.paid_until.to_s.should eq (Time.zone.now + 6.months).to_s + end + end + end diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb index e56502413..59339a8c9 100644 --- a/spec/models/product_spec.rb +++ b/spec/models/product_spec.rb @@ -7,29 +7,4 @@ describe Product do @product.to_s.should eq @product.name end - context "update account" do - before(:each) do - @product = FactoryGirl.create(:product, - :paid_months => 3 - ) - @member = FactoryGirl.create(:member) - end - - it "sets account_type" do - @product.update_account(@member) - @member.account.account_type.should eq @product.account_type - end - - it "sets paid_until" do - @member.account.paid_until = nil # blank for now, as if never paid before - @product.update_account(@member) - - # stringify to avoid millisecond problems... - @member.account.paid_until.to_s.should eq (Time.zone.now + 3.months).to_s - - # and again to make sure it works for currently paid accounts - @product.update_account(@member) - @member.account.paid_until.to_s.should eq (Time.zone.now + 6.months).to_s - end - end end From dad64d41fbdc8691d1e53acdaa5466693d587151 Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 20 May 2013 12:57:35 +1000 Subject: [PATCH 061/184] show account status/upgrade button in various places --- app/models/account_type.rb | 4 ++++ app/models/member.rb | 13 +++++++++---- app/views/home/index.html.haml | 8 ++++++++ app/views/members/show.html.haml | 16 ++++++++++++---- spec/views/home/index_spec.rb | 8 ++++++++ spec/views/members/show.html.haml_spec.rb | 10 +++++++++- 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/app/models/account_type.rb b/app/models/account_type.rb index 8aaa4f752..dc464707b 100644 --- a/app/models/account_type.rb +++ b/app/models/account_type.rb @@ -1,4 +1,8 @@ class AccountType < ActiveRecord::Base attr_accessible :is_paid, :is_permanent_paid, :name has_many :products + + def to_s + name + end end diff --git a/app/models/member.rb b/app/models/member.rb index 1b6b04009..aa3387a43 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -13,6 +13,7 @@ class Member < ActiveRecord::Base has_many :authentications has_many :orders has_one :account + has_one :account_type, :through => :account default_scope order("lower(login_name) asc") scope :confirmed, where('confirmed_at IS NOT NULL') @@ -113,10 +114,14 @@ class Member < ActiveRecord::Base end def is_paid? - if account.account_type.is_permanent_paid - return true - elsif account.account_type.is_paid and account.paid_until >= Time.zone.now - return true + if account.account_type # it might be nil if you've never had one + if account.account_type.is_permanent_paid + return true + elsif account.account_type.is_paid and account.paid_until >= Time.zone.now + return true + else + return false + end else return false end diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 7b33f1506..8819e544e 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -27,6 +27,14 @@ = link_to 'Add', new_garden_path, :class => 'btn btn-mini' .span4 + %p + Your account: + %strong + = current_member.account_type || "Free" + account + %br/ + - if current_member == current_member && !current_member.is_paid? + = link_to "Upgrade and Support Growstuff", shop_path, :class => 'btn btn-primary' - if current_member.has_role?(:admin) %p %b You are an ADMIN USER. diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index 44ec0aa5f..8da927ede 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -15,6 +15,14 @@ %strong Member since: = @member.created_at.to_s(:date) + %p + %strong Account type: + = @member.account_type || "Free" + account + + - if @member == current_member && !@member.is_paid? + = link_to "Upgrade", shop_path, :class => 'btn btn-primary btn-mini' + - if @twitter_auth || @flickr_auth || @member.show_email %h4 Contact @@ -42,12 +50,12 @@ = @member.location %br/ = link_to 'Find members near here', nearby_members_path(:location => @member.location) - %p - - if current_member == @member - %p - = link_to 'Edit Settings', "edit", :class => 'btn btn-mini' .span9 + %p + - if can? :update, @member + %p + = link_to 'Edit your profile', "edit", :class => 'btn btn-primary' .tabbable %ul.nav.nav-tabs - first_garden = true diff --git a/spec/views/home/index_spec.rb b/spec/views/home/index_spec.rb index e123927f5..49f9e5cf1 100644 --- a/spec/views/home/index_spec.rb +++ b/spec/views/home/index_spec.rb @@ -74,6 +74,14 @@ describe 'home/index.html.haml', :type => "view" do assert_select "a[href=#{url_for(@post)}]" end + it 'shows account type' do + rendered.should contain "Free account" + end + + it 'shows upgrade account button' do + rendered.should contain "Upgrade" + end + it 'shows admin status' do rendered.should contain "You are an ADMIN USER" end diff --git a/spec/views/members/show.html.haml_spec.rb b/spec/views/members/show.html.haml_spec.rb index aab5be98b..4c3c66e15 100644 --- a/spec/views/members/show.html.haml_spec.rb +++ b/spec/views/members/show.html.haml_spec.rb @@ -18,6 +18,10 @@ describe "members/show" do rendered.should contain @time.strftime("%B %d, %Y") end + it "shows account type" do + rendered.should contain "Free account" + end + it "contains a gravatar icon" do assert_select "img", :src => /gravatar\.com\/avatar/ end @@ -106,7 +110,11 @@ describe "members/show" do end it "contains an edit settings button" do - rendered.should contain "Edit Settings" + rendered.should contain "Edit your profile" + end + + it "asks you to upgrade your account" do + rendered.should contain "Upgrade" end it "contains no send message button" do From e588b312f9a3850a6eac32237f11918ed575f0c6 Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 20 May 2013 14:56:53 +1000 Subject: [PATCH 062/184] Added list of contributors I know git(hub) gives us much of this, but it doesn't credit the "copilot" from pairing, so this is a way to list *everyone* who's contributed to our code, even if they didn't do the actual commit with their own github account. --- CONTRIBUTORS.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000..176cbf63d --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,33 @@ +This is a list of contributors to Growstuff's codebase. We maintain +this list because we work in pairs, but Github only knows about the +person who actually does the commits. This gives credit to both members +of the pair. + +If you contribute, please add yourself to the bottom of the list and +submit the change with your pull request. + +- Alex Bayley / [Skud](https://github.com/Skud) +- Ricky Amianym / [amianym](https://github.com/amianym) +- Cesy / [cesy](https://github.com/cesy) +- Miles Gould / [pozorvlak](https://github.com/pozorvlak) +- Juliet Kemp / [julietk](https://github.com/julietk) +- Federico Mena Quintero / [federicomenaquintero](https://github.com/federicomenaquintero) +- Jay Springett / [thejaymo](https://github.com/thejaymo) +- Maia Sauren / [sauramaia](https://github.com/sauramaia) +- Joseph Caudle / [jcaudle](https://github.com/jcaudle) +- Norman Ancajas / [nbancajas](https://github.com/nbancajas) +- Jonathan "Duke" Leto / [leto](https://github.com/leto) +- Mackenzie Morgan / [maco](https://github.com/maco) +- Amy Hendrix / [sabreuse](https://github.com/sabreuse) +- CephLPod / [cephLpod](https://github.com/cephLpod/) +- Gemma Mason / [gemmaellen](https://github.com/gemmaellen) +- Courtney Webster / [phazel](https://github.com/phazel/) +- Cathy Sullivan / [cesullivan](https://github.com/cesullivan) +- Jared McCorkindale / [jmcorkindale](https://github.com/jmcorkindale) +- Florian Vallen / [flov](https://github.com/flov) +- Samuel Cochrane / [sj26](https://github.com/sj26) +- Ju Transcendancing / [transcendancing](https://github.com/transcendancing) +- Lillian Ryan / [attlebish](https://github.com/attlebish) +- Gary Traffanstedt / [blimey85](https://github.com/blimey85) +- Yaw Boakye [yawboakye](https://github.com/yawboakye) +- Ryan Clark / [IAMRYO](https://github.com/IAMRYO) From 1ad27fc8851ce2a2a400791c8a4cc22b1db077a1 Mon Sep 17 00:00:00 2001 From: Skud Date: Mon, 20 May 2013 15:00:29 +1000 Subject: [PATCH 063/184] Broke out "committers" and "contributors" I figured this would make it easier to show who's a good person to ask for help or information, while still crediting everyone who's contributed. --- CONTRIBUTORS.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 176cbf63d..9f8a3c74a 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -6,15 +6,20 @@ of the pair. If you contribute, please add yourself to the bottom of the list and submit the change with your pull request. +## Committers + - Alex Bayley / [Skud](https://github.com/Skud) -- Ricky Amianym / [amianym](https://github.com/amianym) - Cesy / [cesy](https://github.com/cesy) - Miles Gould / [pozorvlak](https://github.com/pozorvlak) +- Joseph Caudle / [jcaudle](https://github.com/jcaudle) + +## Contributors + +- Ricky Amianym / [amianym](https://github.com/amianym) - Juliet Kemp / [julietk](https://github.com/julietk) - Federico Mena Quintero / [federicomenaquintero](https://github.com/federicomenaquintero) - Jay Springett / [thejaymo](https://github.com/thejaymo) - Maia Sauren / [sauramaia](https://github.com/sauramaia) -- Joseph Caudle / [jcaudle](https://github.com/jcaudle) - Norman Ancajas / [nbancajas](https://github.com/nbancajas) - Jonathan "Duke" Leto / [leto](https://github.com/leto) - Mackenzie Morgan / [maco](https://github.com/maco) @@ -29,5 +34,5 @@ submit the change with your pull request. - Ju Transcendancing / [transcendancing](https://github.com/transcendancing) - Lillian Ryan / [attlebish](https://github.com/attlebish) - Gary Traffanstedt / [blimey85](https://github.com/blimey85) -- Yaw Boakye [yawboakye](https://github.com/yawboakye) +- Yaw Boakye / [yawboakye](https://github.com/yawboakye) - Ryan Clark / [IAMRYO](https://github.com/IAMRYO) From f955d14149f49c932e9b2206e5fa83596dc31af6 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 22 May 2013 10:57:20 +1000 Subject: [PATCH 064/184] Don't display shop if they already have a paid account This works fine in reality, but we can't figure out how to write tests for it. For some reason the @member object, which passes a test for is_paid?, doesn't get recognised as such when it's passed through to the view itself. Help? --- app/views/shop/index.html.haml | 7 ++++++- spec/factories/account_types.rb | 16 +++++++++++++++- spec/views/shop/index_spec.rb | 27 +++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 1133df8a9..d7f182529 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -13,7 +13,12 @@ you want to pay. Remember, your subscription supports an open source platform promoting sustainable lifestyles! -- if @order and @order.order_items.count > 0 +- if current_member && current_member.is_paid? + %h2 Thank you for supporting Growstuff + + %p You currently have a paid membership, and can't buy another one at this time. + +- elsif @order and @order.order_items.count > 0 %h2 Your current order %p diff --git a/spec/factories/account_types.rb b/spec/factories/account_types.rb index fa411ffbe..cfae7caf8 100644 --- a/spec/factories/account_types.rb +++ b/spec/factories/account_types.rb @@ -2,8 +2,22 @@ FactoryGirl.define do factory :account_type do - name "MyString" + name "Free" is_paid false is_permanent_paid false + + factory :paid_account_type do + name "Paid" + is_paid true + is_permanent_paid false + end + + factory :permanent_paid_account_type do + name "Permanent paid" + is_paid true + is_permanent_paid true + end + end + end diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index bef3ba4fb..dbd678199 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -28,6 +28,33 @@ describe 'shop/index.html.haml', :type => "view" do end end + context "is paid" do + before(:each) do + @member = FactoryGirl.create(:member) + @member.account.account_type = FactoryGirl.create(:paid_account_type) + @member.account.paid_until = Time.zone.now + 1.year + @member.save + controller.stub(:current_user) { @member } + end + + it "recognises the paid member" do + @member.is_paid?.should be_true + end + + it "tells you you have a paid membership" do + pending "can't set up a paid member for some reason" + render + rendered.should contain "You currently have a paid" + end + + it "doesn't show shop" do + pending "can't set up a paid member for some reason" + render + assert_select "form", false + end + + end + context "signed out" do before(:each) do controller.stub(:current_user) { nil } From 89069b4bd240396a8ffbeffd01553602702831f9 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 22 May 2013 11:22:10 +1000 Subject: [PATCH 065/184] don't allow multiple order items per order --- app/controllers/order_items_controller.rb | 2 +- app/models/order_item.rb | 2 ++ spec/controllers/order_items_controller_spec.rb | 10 +++++++--- spec/models/order_item_spec.rb | 10 ++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index ee7b02d49..ec6af6c48 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -29,7 +29,7 @@ class OrderItemsController < ApplicationController format.html { redirect_to @order_item.order, notice: 'Added item to your order.' } format.json { render json: @order_item, status: :created, location: @order_item } else - format.html { render action: "new" } + format.html { redirect_to shop_path, alert: 'There was a problem with your order' } format.json { render json: @order_item.errors, status: :unprocessable_entity } end end diff --git a/app/models/order_item.rb b/app/models/order_item.rb index c3f51def5..3a06097d1 100644 --- a/app/models/order_item.rb +++ b/app/models/order_item.rb @@ -6,6 +6,8 @@ class OrderItem < ActiveRecord::Base validate :price_must_be_greater_than_minimum + validates_uniqueness_of :order_id, :message => "may only have one item." + def price_must_be_greater_than_minimum @product = Product.find(product_id) if price < @product.min_price diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index 882e2f007..32c056a48 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -34,6 +34,7 @@ describe OrderItemsController do describe "with valid params" do it "creates a new OrderItem" do + @order = FactoryGirl.create(:order, :member => @member) expect { post :create, {:order_item => { :order_id => @order.id, @@ -44,6 +45,7 @@ describe OrderItemsController do end it "assigns a newly created order_item as @order_item" do + @order = FactoryGirl.create(:order, :member => @member) post :create, {:order_item => { :order_id => @order.id, :product_id => @product.id, @@ -53,7 +55,8 @@ describe OrderItemsController do assigns(:order_item).should be_persisted end - it "redirects to the created order_item" do + it "redirects to order" do + @order = FactoryGirl.create(:order, :member => @member) post :create, {:order_item => { :order_id => @order.id, :product_id => @product.id, @@ -74,6 +77,7 @@ describe OrderItemsController do }.to change(Order, :count).by(1) OrderItem.last.order.should be_an_instance_of Order end + end describe "with invalid params" do @@ -84,11 +88,11 @@ describe OrderItemsController do assigns(:order_item).should be_a_new(OrderItem) end - it "re-renders the 'new' template" do + it "sends you back to the shop" do # Trigger the behavior that occurs when invalid params are submitted OrderItem.any_instance.stub(:save).and_return(false) post :create, {:order_item => { "order_id" => "invalid value" }} - response.should render_template("new") + response.should redirect_to(shop_path) end end end diff --git a/spec/models/order_item_spec.rb b/spec/models/order_item_spec.rb index b4eea79da..e12cc9fbf 100644 --- a/spec/models/order_item_spec.rb +++ b/spec/models/order_item_spec.rb @@ -16,4 +16,14 @@ describe OrderItem do @order_item.should_not be_valid end + it "doesn't let you add two items to an order" do + @product = FactoryGirl.create(:product) + @order = FactoryGirl.create(:order) + @order_item = FactoryGirl.build(:order_item, :order => @order) + @order_item.should be_valid + @order_item.save + @order_item2 = FactoryGirl.build(:order_item, :order => @order) + @order_item2.should_not be_valid + end + end From 257db239fd3c585a3acf7377078924efaa195632 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 22 May 2013 11:25:48 +1000 Subject: [PATCH 066/184] remove unneeded edit/new actions from order_item --- app/controllers/order_items_controller.rb | 16 ------------- app/views/order_items/_form.html.haml | 22 ----------------- app/views/order_items/edit.html.haml | 7 ------ app/views/order_items/new.html.haml | 5 ---- .../order_items_controller_spec.rb | 14 ----------- spec/views/order_items/edit.html.haml_spec.rb | 24 ------------------- spec/views/order_items/new.html.haml_spec.rb | 24 ------------------- 7 files changed, 112 deletions(-) delete mode 100644 app/views/order_items/_form.html.haml delete mode 100644 app/views/order_items/edit.html.haml delete mode 100644 app/views/order_items/new.html.haml delete mode 100644 spec/views/order_items/edit.html.haml_spec.rb delete mode 100644 spec/views/order_items/new.html.haml_spec.rb diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index ec6af6c48..8d682c107 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -1,22 +1,6 @@ class OrderItemsController < ApplicationController load_and_authorize_resource - # GET /order_items/new - # GET /order_items/new.json - def new - @order_item = OrderItem.new - - respond_to do |format| - format.html # new.html.erb - format.json { render json: @order_item } - end - end - - # GET /order_items/1/edit - def edit - @order_item = OrderItem.find(params[:id]) - end - # POST /order_items # POST /order_items.json def create diff --git a/app/views/order_items/_form.html.haml b/app/views/order_items/_form.html.haml deleted file mode 100644 index ff2924af4..000000000 --- a/app/views/order_items/_form.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -= form_for @order_item do |f| - - if @order_item.errors.any? - #error_explanation - %h2= "#{pluralize(@order_item.errors.count, "error")} prohibited this order_item from being saved:" - %ul - - @order_item.errors.full_messages.each do |msg| - %li= msg - - .field - = f.label :order_id - = f.number_field :order_id - .field - = f.label :product_id - = f.number_field :product_id - .field - = f.label :price - = f.text_field :price - .field - = f.label :quantity - = f.number_field :quantity - .actions - = f.submit 'Save' diff --git a/app/views/order_items/edit.html.haml b/app/views/order_items/edit.html.haml deleted file mode 100644 index da759ba68..000000000 --- a/app/views/order_items/edit.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%h1 Editing order_item - -= render 'form' - -= link_to 'Show', @order_item -\| -= link_to 'Back', order_items_path diff --git a/app/views/order_items/new.html.haml b/app/views/order_items/new.html.haml deleted file mode 100644 index 2ba254512..000000000 --- a/app/views/order_items/new.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -%h1 New order_item - -= render 'form' - -= link_to 'Back', order_items_path diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index 32c056a48..cd2108f48 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -16,20 +16,6 @@ describe OrderItemsController do ) end - describe "GET new" do - it "assigns a new order_item as @order_item" do - get :new, {} - assigns(:order_item).should be_a_new(OrderItem) - end - end - - describe "GET edit" do - it "assigns the requested order_item as @order_item" do - get :edit, {:id => @order_item.to_param} - assigns(:order_item).should eq(@order_item) - end - end - describe "POST create" do describe "with valid params" do diff --git a/spec/views/order_items/edit.html.haml_spec.rb b/spec/views/order_items/edit.html.haml_spec.rb deleted file mode 100644 index 49dbc8005..000000000 --- a/spec/views/order_items/edit.html.haml_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe "order_items/edit" do - before(:each) do - @order_item = assign(:order_item, stub_model(OrderItem, - :order_id => 1, - :product_id => 1, - :price => "9.99", - :quantity => 1 - )) - end - - it "renders the edit order_item form" do - render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => order_items_path(@order_item), :method => "post" do - assert_select "input#order_item_order_id", :name => "order_item[order_id]" - assert_select "input#order_item_product_id", :name => "order_item[product_id]" - assert_select "input#order_item_price", :name => "order_item[price]" - assert_select "input#order_item_quantity", :name => "order_item[quantity]" - end - end -end diff --git a/spec/views/order_items/new.html.haml_spec.rb b/spec/views/order_items/new.html.haml_spec.rb deleted file mode 100644 index 70ed7cfa1..000000000 --- a/spec/views/order_items/new.html.haml_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' - -describe "order_items/new" do - before(:each) do - assign(:order_item, stub_model(OrderItem, - :order_id => 1, - :product_id => 1, - :price => "9.99", - :quantity => 1 - ).as_new_record) - end - - it "renders new order_item form" do - render - - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "form", :action => order_items_path, :method => "post" do - assert_select "input#order_item_order_id", :name => "order_item[order_id]" - assert_select "input#order_item_product_id", :name => "order_item[product_id]" - assert_select "input#order_item_price", :name => "order_item[price]" - assert_select "input#order_item_quantity", :name => "order_item[quantity]" - end - end -end From 98beebd697d574bb60ab94a451409496954b6d50 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 22 May 2013 11:51:38 +1000 Subject: [PATCH 067/184] fixed bug with non-int prices now if you choose to pay 33.33 it doesn't round to 33.00 also removed PUT and DELETE actions from order items (we don't want them for now, can reinstate later if/when we do.) --- app/controllers/order_items_controller.rb | 33 +------- .../order_items_controller_spec.rb | 84 ++++--------------- 2 files changed, 18 insertions(+), 99 deletions(-) diff --git a/app/controllers/order_items_controller.rb b/app/controllers/order_items_controller.rb index 8d682c107..631cfa5fc 100644 --- a/app/controllers/order_items_controller.rb +++ b/app/controllers/order_items_controller.rb @@ -4,8 +4,10 @@ class OrderItemsController < ApplicationController # POST /order_items # POST /order_items.json def create + if params[:order_item][:price] + params[:order_item][:price] = params[:order_item][:price].to_f * 100 # convert to cents + end @order_item = OrderItem.new(params[:order_item]) - @order_item.price = @order_item.price.to_f * 100 # convert to cents @order_item.order = current_member.current_order || Order.create(:member_id => current_member.id) respond_to do |format| @@ -18,33 +20,4 @@ class OrderItemsController < ApplicationController end end end - - # PUT /order_items/1 - # PUT /order_items/1.json - def update - @order_item = OrderItem.find(params[:id]) - @order_item.price = @order_item.price.to_f * 100 # convert to cents - - respond_to do |format| - if @order_item.update_attributes(params[:order_item]) - format.html { redirect_to @order_item.order, notice: 'Order item was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: "edit" } - format.json { render json: @order_item.errors, status: :unprocessable_entity } - end - end - end - - # DELETE /order_items/1 - # DELETE /order_items/1.json - def destroy - @order_item = OrderItem.find(params[:id]) - @order_item.destroy - - respond_to do |format| - format.html { redirect_to order_items_url } - format.json { head :no_content } - end - end end diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index cd2108f48..0ba6fbf43 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -66,6 +66,21 @@ describe OrderItemsController do end + describe "with non-int price" do + it "converts 3.33 to 333 cents" do + @order = FactoryGirl.create(:order, :member => @member) + @product = FactoryGirl.create(:product, :min_price => 1) + expect { + post :create, {:order_item => { + :order_id => @order.id, + :product_id => @product.id, + :price => 3.33 + }} + }.to change(OrderItem, :count).by(1) + OrderItem.last.price.should eq 333 + end + end + describe "with invalid params" do it "assigns a newly created but unsaved order_item as @order_item" do # Trigger the behavior that occurs when invalid params are submitted @@ -82,73 +97,4 @@ describe OrderItemsController do end end end - - describe "PUT update" do - - # we've said nobody can update an OrderItem; in future, we might - # want to delete all this code, but I wanted to wait just in case we - # needed it. - before(:each) { pending("we don't permit this (see app/model/ability.rb") } - - describe "with valid params" do - - it "updates the requested order_item" do - OrderItem.any_instance.should_receive(:update_attributes).with({ "order_id" => "1" }) - put :update, {:id => @order_item.to_param, :order_item => { "order_id" => "1" }} - end - - it "assigns the requested order_item as @order_item" do - put :update, {:id => @order_item.to_param, :order_item => { - :order_id => @order.id, - :product_id => @product.id, - :price => @product.min_price - }} - assigns(:order_item).should eq(@order_item) - end - - it "redirects to the order_item" do - put :update, {:id => @order_item.to_param, :order_item => { - :order_id => @order.id, - :product_id => @product.id, - :price => @product.min_price - }} - response.should redirect_to(@order_item.order) - end - end - - describe "with invalid params" do - it "assigns the order_item as @order_item" do - # Trigger the behavior that occurs when invalid params are submitted - OrderItem.any_instance.stub(:save).and_return(false) - put :update, {:id => @order_item.to_param, :order_item => { "order_id" => "invalid value" }} - assigns(:order_item).should eq(@order_item) - end - - it "re-renders the 'edit' template" do - # Trigger the behavior that occurs when invalid params are submitted - OrderItem.any_instance.stub(:save).and_return(false) - put :update, {:id => @order_item.to_param, :order_item => { "order_id" => "invalid value" }} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - # we've said nobody can update an OrderItem; in future, we might - # want to delete all this code, but I wanted to wait just in case we - # needed it. - before(:each) { pending("we don't permit this (see app/model/ability.rb") } - - it "destroys the requested order_item" do - expect { - delete :destroy, {:id => @order_item.to_param} - }.to change(OrderItem, :count).by(-1) - end - - it "redirects to the order_items list" do - delete :destroy, {:id => @order_item.to_param} - response.should redirect_to(order_items_url) - end - end - end From bc03a5c488650e9c49a8d34f6e6a045daf80266d Mon Sep 17 00:00:00 2001 From: Craig Read Date: Fri, 24 May 2013 11:58:40 +1000 Subject: [PATCH 068/184] Converted Posts RSS to HAML --- app/views/posts/index.rss.builder | 18 -------- app/views/posts/index.rss.haml | 15 +++++++ db/schema.rb | 41 ++++++------------- ...builder_spec.rb => index.rss.haml_spec.rb} | 2 +- 4 files changed, 29 insertions(+), 47 deletions(-) delete mode 100644 app/views/posts/index.rss.builder create mode 100644 app/views/posts/index.rss.haml rename spec/views/posts/{index.rss.builder_spec.rb => index.rss.haml_spec.rb} (89%) diff --git a/app/views/posts/index.rss.builder b/app/views/posts/index.rss.builder deleted file mode 100644 index 88025252c..000000000 --- a/app/views/posts/index.rss.builder +++ /dev/null @@ -1,18 +0,0 @@ -xml.instruct! :xml, :version => "1.0" -xml.rss :version => "2.0" do - xml.channel do - xml.title "#{Growstuff::Application.config.site_name} - Recent posts from all members" - xml.link posts_url - - for post in @recent_posts - xml.item do - xml.author post.author.login_name - xml.title post.subject - xml.description post.body - xml.pubDate post.created_at.to_s(:rfc822) - xml.link post_url(post) - xml.guid post_url(post) - end - end - end -end diff --git a/app/views/posts/index.rss.haml b/app/views/posts/index.rss.haml new file mode 100644 index 000000000..d54ea83da --- /dev/null +++ b/app/views/posts/index.rss.haml @@ -0,0 +1,15 @@ + +%rss{:version => 2.0} + %channel + %title #{Growstuff::Application.config.site_name} - Recent posts from all members + %link= posts_url + - @recent_posts.each do |post| + %item + %author= post.author.login_name + %title= post.subject + %description + :escaped_markdown + #{ strip_tags post.body } + %pubDate= post.created_at.to_s(:rfc822) + %link= post_url(post) + %guid= post_url(post) diff --git a/db/schema.rb b/db/schema.rb index d4bf0005f..c26161002 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130515122301) do +ActiveRecord::Schema.define(:version => 20130409162140) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -95,6 +95,11 @@ ActiveRecord::Schema.define(:version => 20130515122301) do t.string "location" t.float "latitude" t.float "longitude" + t.text "about_me" + t.string "full_name" + t.string "gardening_since" + t.string "wish_i_could_grow" + t.string "gardening_clothes" t.boolean "send_notification_email", :default => true end @@ -120,25 +125,13 @@ ActiveRecord::Schema.define(:version => 20130515122301) do t.datetime "updated_at", :null => false end - create_table "order_items", :force => true do |t| - t.integer "order_id" - t.integer "product_id" - t.integer "price" - t.integer "quantity" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "orders", :force => true do |t| - t.integer "member_id", :limit => 255, :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.datetime "completed_at" - end - - create_table "orders_products", :id => false, :force => true do |t| - t.integer "order_id" - t.integer "product_id" + create_table "payments", :force => true do |t| + t.integer "payer_id" + t.decimal "amount" + t.date "paid_period_begins" + t.date "paid_period_ends" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "plantings", :force => true do |t| @@ -168,14 +161,6 @@ ActiveRecord::Schema.define(:version => 20130515122301) do add_index "posts", ["created_at", "author_id"], :name => "index_updates_on_created_at_and_user_id" add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true - create_table "products", :force => true do |t| - t.string "name", :null => false - t.string "description", :null => false - t.integer "min_price", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - create_table "roles", :force => true do |t| t.string "name", :null => false t.text "description" diff --git a/spec/views/posts/index.rss.builder_spec.rb b/spec/views/posts/index.rss.haml_spec.rb similarity index 89% rename from spec/views/posts/index.rss.builder_spec.rb rename to spec/views/posts/index.rss.haml_spec.rb index f2b6c87e4..b839cf945 100644 --- a/spec/views/posts/index.rss.builder_spec.rb +++ b/spec/views/posts/index.rss.haml_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe 'posts/index.rss.builder', :type => "view" do +describe 'posts/index.rss.haml', :type => "view" do before(:each) do controller.stub(:current_user) { nil } @author = FactoryGirl.create(:member) From 9c2fed461ddd4415543063fe9c08d080b95b0d2b Mon Sep 17 00:00:00 2001 From: Craig Read Date: Fri, 24 May 2013 12:05:47 +1000 Subject: [PATCH 069/184] Removed redundant @recent_posts instance variable Not required, as we have pagination by default --- app/controllers/posts_controller.rb | 1 - app/views/posts/index.rss.haml | 2 +- spec/controllers/posts_controller_spec.rb | 1 - spec/views/posts/index.rss.haml_spec.rb | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 1ca2b5f94..6bec61061 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -5,7 +5,6 @@ class PostsController < ApplicationController def index @posts = Post.paginate(:page => params[:page]) - @recent_posts = Post.limit(100).order('created_at desc').all respond_to do |format| format.html # index.html.haml diff --git a/app/views/posts/index.rss.haml b/app/views/posts/index.rss.haml index d54ea83da..12402d0cf 100644 --- a/app/views/posts/index.rss.haml +++ b/app/views/posts/index.rss.haml @@ -3,7 +3,7 @@ %channel %title #{Growstuff::Application.config.site_name} - Recent posts from all members %link= posts_url - - @recent_posts.each do |post| + - @posts.each do |post| %item %author= post.author.login_name %title= post.subject diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index 5fe9c1ddd..f87f90fb9 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -13,7 +13,6 @@ describe PostsController do post = Post.create! valid_attributes get :index, {} assigns(:posts).should eq([post]) - assigns(:recent_posts).should eq([post]) end end diff --git a/spec/views/posts/index.rss.haml_spec.rb b/spec/views/posts/index.rss.haml_spec.rb index b839cf945..cd5f74c4d 100644 --- a/spec/views/posts/index.rss.haml_spec.rb +++ b/spec/views/posts/index.rss.haml_spec.rb @@ -4,7 +4,7 @@ describe 'posts/index.rss.haml', :type => "view" do before(:each) do controller.stub(:current_user) { nil } @author = FactoryGirl.create(:member) - assign(:recent_posts, [ + assign(:posts, [ FactoryGirl.build(:post, :id => 1, :author => @author), FactoryGirl.build(:post, :id => 2, :author => @author) ]) From d563f0ceae71185b72cff5a061b46fceb9f0e750 Mon Sep 17 00:00:00 2001 From: Craig Read Date: Fri, 24 May 2013 12:22:41 +1000 Subject: [PATCH 070/184] Converting crops RSS to HAML Added spec for crops RSS Removed redundant @new_crops instance variable (not needed as we now have pagination). --- app/controllers/crops_controller.rb | 1 - app/views/crops/index.rss.builder | 16 ---------------- app/views/crops/index.rss.haml | 11 +++++++++++ spec/controllers/crops_controller_spec.rb | 1 - spec/views/crops/index.rss.haml_spec.rb | 23 +++++++++++++++++++++++ 5 files changed, 34 insertions(+), 18 deletions(-) delete mode 100644 app/views/crops/index.rss.builder create mode 100644 app/views/crops/index.rss.haml create mode 100644 spec/views/crops/index.rss.haml_spec.rb diff --git a/app/controllers/crops_controller.rb b/app/controllers/crops_controller.rb index f0cfe2525..26a3de663 100644 --- a/app/controllers/crops_controller.rb +++ b/app/controllers/crops_controller.rb @@ -5,7 +5,6 @@ class CropsController < ApplicationController # GET /crops.json def index @crops = Crop.paginate(:page => params[:page]) - @new_crops = Crop.limit(20).order('created_at desc').all respond_to do |format| format.html # index.html.haml diff --git a/app/views/crops/index.rss.builder b/app/views/crops/index.rss.builder deleted file mode 100644 index 9410a6080..000000000 --- a/app/views/crops/index.rss.builder +++ /dev/null @@ -1,16 +0,0 @@ -xml.instruct! :xml, :version => "1.0" -xml.rss :version => "2.0" do - xml.channel do - xml.title "#{Growstuff::Application.config.site_name} - Recently added crops" - xml.link crops_url - - for crop in @new_crops - xml.item do - xml.title crop.system_name - xml.pubDate crop.created_at.to_s(:rfc822) - xml.link crop_url(crop) - xml.guid crop_url(crop) - end - end - end -end diff --git a/app/views/crops/index.rss.haml b/app/views/crops/index.rss.haml new file mode 100644 index 000000000..1fd02870e --- /dev/null +++ b/app/views/crops/index.rss.haml @@ -0,0 +1,11 @@ + +%rss{:version => 2.0} + %channel + %title #{Growstuff::Application.config.site_name} - Recently added crops + %link= crops_url + - @crops.each do |crop| + %item + %title= crop.system_name + %pubDate= crop.created_at.to_s(:rfc822) + %link= post_url(crop) + %guid= post_url(crop) \ No newline at end of file diff --git a/spec/controllers/crops_controller_spec.rb b/spec/controllers/crops_controller_spec.rb index db4c070bc..f56c7ee3b 100644 --- a/spec/controllers/crops_controller_spec.rb +++ b/spec/controllers/crops_controller_spec.rb @@ -16,7 +16,6 @@ describe CropsController do crop = Crop.create! valid_attributes get :index, {} assigns(:crops).should eq([crop]) - assigns(:new_crops).should eq([crop]) end end diff --git a/spec/views/crops/index.rss.haml_spec.rb b/spec/views/crops/index.rss.haml_spec.rb new file mode 100644 index 000000000..8790a7d80 --- /dev/null +++ b/spec/views/crops/index.rss.haml_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe 'crops/index.rss.haml' do + before(:each) do + controller.stub(:current_user) { nil } + @author = FactoryGirl.create(:member) + assign(:crops, [ + FactoryGirl.create(:tomato), + FactoryGirl.create(:maize) + ]) + render + end + + it 'shows RSS feed title' do + rendered.should contain "Recently added crops" + end + + it 'shows names of crops' do + rendered.should contain "Tomato" + rendered.should contain "Maize" + end + +end From 9437e6576340d83c1238bf76dfe1616d4f0c87fe Mon Sep 17 00:00:00 2001 From: Craig Read Date: Fri, 24 May 2013 12:35:08 +1000 Subject: [PATCH 071/184] Replaced plantings RSS with HAML Removed the redundant @recent_plantings variable and added pagination --- app/controllers/plantings_controller.rb | 3 +- app/views/plantings/index.html.haml | 10 +++++-- app/views/plantings/index.rss.builder | 18 ----------- app/views/plantings/index.rss.haml | 15 ++++++++++ spec/controllers/plantings_controller_spec.rb | 1 - spec/views/plantings/index.html.haml_spec.rb | 30 +++++++++++-------- ...builder_spec.rb => index.rss.haml_spec.rb} | 8 ++--- 7 files changed, 46 insertions(+), 39 deletions(-) delete mode 100644 app/views/plantings/index.rss.builder create mode 100644 app/views/plantings/index.rss.haml rename spec/views/plantings/{index.rss.builder_spec.rb => index.rss.haml_spec.rb} (58%) diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index ff9ebe886..7a7c61119 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -3,8 +3,7 @@ class PlantingsController < ApplicationController # GET /plantings # GET /plantings.json def index - @plantings = Planting.all - @recent_plantings = Planting.limit(100).order('created_at desc').all + @plantings = Planting.paginate(:page => params[:page]) respond_to do |format| format.html # index.html.erb diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index d359a342d..fd628410d 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -2,7 +2,13 @@ %p Here are the latest things planted by #{Growstuff::Application.config.site_name} members. -- @recent_plantings.each do |p| +%div.pagination + = page_entries_info @plantings, :model => "plantings" + = will_paginate @plantings + +- @plantings.each do |p| = render :partial => "plantings/thumbnail", :locals => { :planting => p } - +%div.pagination + = page_entries_info @plantings, :model => "plantings" + = will_paginate @plantings \ No newline at end of file diff --git a/app/views/plantings/index.rss.builder b/app/views/plantings/index.rss.builder deleted file mode 100644 index f203ca488..000000000 --- a/app/views/plantings/index.rss.builder +++ /dev/null @@ -1,18 +0,0 @@ -xml.instruct! :xml, :version => "1.0" -xml.rss :version => "2.0" do - xml.channel do - xml.title "#{Growstuff::Application.config.site_name} - Recent plantings from all members" - xml.link plantings_url - - for planting in @recent_plantings - xml.item do - xml.author planting.owner.login_name - xml.title planting.crop.system_name - xml.pubDate planting.created_at.to_s(:rfc822) - xml.description planting.description - xml.link planting_url(planting) - xml.guid planting_url(planting) - end - end - end -end diff --git a/app/views/plantings/index.rss.haml b/app/views/plantings/index.rss.haml new file mode 100644 index 000000000..d6ed95542 --- /dev/null +++ b/app/views/plantings/index.rss.haml @@ -0,0 +1,15 @@ + +%rss{:version => 2.0} + %channel + %title #{Growstuff::Application.config.site_name} - Recent plantings from all members + %link= plantings_url + - @plantings.each do |planting| + %item + %author= planting.owner.login_name + %title= planting.crop.system_name + %pubDate= planting.created_at.to_s(:rfc822) + %description + :escaped_markdown + #{ strip_tags planting.description } + %link= planting_url(planting) + %guid= planting_url(planting) diff --git a/spec/controllers/plantings_controller_spec.rb b/spec/controllers/plantings_controller_spec.rb index ff3fc9e36..fec82c7e8 100644 --- a/spec/controllers/plantings_controller_spec.rb +++ b/spec/controllers/plantings_controller_spec.rb @@ -16,7 +16,6 @@ describe PlantingsController do planting = Planting.create! valid_attributes get :index, {} assigns(:plantings).should eq([planting]) - assigns(:recent_plantings).should eq([planting]) end end diff --git a/spec/views/plantings/index.html.haml_spec.rb b/spec/views/plantings/index.html.haml_spec.rb index cb7cec5a5..16339f665 100644 --- a/spec/views/plantings/index.html.haml_spec.rb +++ b/spec/views/plantings/index.html.haml_spec.rb @@ -7,18 +7,24 @@ describe "plantings/index" do @garden = FactoryGirl.create(:garden, :owner => @member) @tomato = FactoryGirl.create(:tomato) @maize = FactoryGirl.create(:maize) - assign(:recent_plantings, [ - FactoryGirl.create(:planting, - :garden => @garden, - :crop => @tomato - ), - FactoryGirl.create(:planting, - :garden => @garden, - :crop => @maize, - :description => '', - :planted_at => Time.local(2013, 1, 13) - ) - ]) + page = 1 + per_page = 2 + total_entries = 2 + plantings = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + pager.replace([ + FactoryGirl.create(:planting, + :garden => @garden, + :crop => @tomato + ), + FactoryGirl.create(:planting, + :garden => @garden, + :crop => @maize, + :description => '', + :planted_at => Time.local(2013, 1, 13) + ) + ]) + end + assign(:plantings, plantings) render end diff --git a/spec/views/plantings/index.rss.builder_spec.rb b/spec/views/plantings/index.rss.haml_spec.rb similarity index 58% rename from spec/views/plantings/index.rss.builder_spec.rb rename to spec/views/plantings/index.rss.haml_spec.rb index abc847d45..a689707fc 100644 --- a/spec/views/plantings/index.rss.builder_spec.rb +++ b/spec/views/plantings/index.rss.haml_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe 'plantings/index.rss.builder', :type => "view" do +describe 'plantings/index.rss.haml' do before(:each) do controller.stub(:current_user) { nil } - assign(:recent_plantings, [ + assign(:plantings, [ FactoryGirl.create(:planting) ]) render @@ -13,8 +13,8 @@ describe 'plantings/index.rss.builder', :type => "view" do rendered.should contain "Recent plantings from all members" end - it 'shows content of posts' do - rendered.should contain "This is a *really* good plant." + it 'shows formatted content of posts' do + rendered.should contain "This is a really good plant." end end From 57f8575ab9316793ec1f7307e5bd437ab4386610 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 24 May 2013 12:54:57 +1000 Subject: [PATCH 072/184] removed unwanted @recent_comments, replaced w pagination --- .gitignore | 4 +++- app/controllers/comments_controller.rb | 3 +-- app/views/comments/index.html.haml | 10 +++++++++- app/views/plantings/index.html.haml | 2 +- spec/controllers/comments_controller_spec.rb | 1 - spec/views/comments/index.html.haml_spec.rb | 15 +++++++++++---- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 5b8787714..9b5a1cf4b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ *~ *.DS_Store credentials.sh -Pathogen: \ No newline at end of file +Pathogen: +custom_plan.rb +zeus.json diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index d6bc30bd1..969385361 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -2,8 +2,7 @@ class CommentsController < ApplicationController # GET /comments # GET /comments.json def index - @comments = Comment.all - @recent_comments = Comment.limit(100).order('created_at desc').all + @comments = Comment.paginate(:page => params[:page]) respond_to do |format| format.html # index.html.erb diff --git a/app/views/comments/index.html.haml b/app/views/comments/index.html.haml index a6a95d2d9..64915fc3c 100644 --- a/app/views/comments/index.html.haml +++ b/app/views/comments/index.html.haml @@ -1,7 +1,15 @@ = content_for :title, "Recent comments" -- @recent_comments.each do |comment| +%div.pagination + = page_entries_info @comments, :model => "comments" + = will_paginate @comments + +- @comments.each do |comment| %h2 Comment on = link_to comment.post.subject, comment.post = render :partial => "single", :locals => { :comment => comment } + +%div.pagination + = page_entries_info @comments, :model => "comments" + = will_paginate @comments diff --git a/app/views/plantings/index.html.haml b/app/views/plantings/index.html.haml index fd628410d..6e98960ad 100644 --- a/app/views/plantings/index.html.haml +++ b/app/views/plantings/index.html.haml @@ -11,4 +11,4 @@ %div.pagination = page_entries_info @plantings, :model => "plantings" - = will_paginate @plantings \ No newline at end of file + = will_paginate @plantings diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index 587d24407..ec76ec799 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -14,7 +14,6 @@ describe CommentsController do comment = Comment.create! valid_attributes get :index, {} assigns(:comments).should eq([comment]) - assigns(:recent_comments).should eq([comment]) end end diff --git a/spec/views/comments/index.html.haml_spec.rb b/spec/views/comments/index.html.haml_spec.rb index f2633c648..dac2dcc5b 100644 --- a/spec/views/comments/index.html.haml_spec.rb +++ b/spec/views/comments/index.html.haml_spec.rb @@ -3,10 +3,17 @@ require 'spec_helper' describe "comments/index" do before(:each) do controller.stub(:current_user) { nil } - assign(:recent_comments, [ - FactoryGirl.create(:comment), - FactoryGirl.create(:comment, :body => 'ROFL') - ]) + page = 1 + per_page = 2 + total_entries = 2 + comments = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + pager.replace([ + FactoryGirl.create(:comment), + FactoryGirl.create(:comment, :body => 'ROFL') + ]) + end + assign(:comments, comments) + render end it "renders a list of comments" do From 0d3588535ef0539fe9ddb3d890abe3f9b0c14a22 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 24 May 2013 13:29:17 +1000 Subject: [PATCH 073/184] Added RSS feed for all comments --- app/controllers/comments_controller.rb | 1 + app/views/comments/index.rss.haml | 22 +++++++++++++++ app/views/crops/index.rss.haml | 2 +- spec/controllers/comments_controller_spec.rb | 9 +++++++ spec/views/comments/index.rss.haml_spec.rb | 28 ++++++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 app/views/comments/index.rss.haml create mode 100644 spec/views/comments/index.rss.haml_spec.rb diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 969385361..c14b0123b 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -7,6 +7,7 @@ class CommentsController < ApplicationController respond_to do |format| format.html # index.html.erb format.json { render json: @comments } + format.rss { render :layout => false } end end diff --git a/app/views/comments/index.rss.haml b/app/views/comments/index.rss.haml new file mode 100644 index 000000000..7408684f9 --- /dev/null +++ b/app/views/comments/index.rss.haml @@ -0,0 +1,22 @@ + +%rss{:version => 2.0} + %channel + %title #{Growstuff::Application.config.site_name} - Recent comments on all posts + %link= comments_url + - @comments.each do |comment| + %item + %author= comment.author.login_name + %description + + :escaped +

+ Comment on + #{ link_to comment.post.subject, post_url(comment.post) } +

+ + :escaped_markdown + #{ strip_tags comment.body } + + %pubDate= comment.created_at.to_s(:rfc822) + %link= post_url(comment.post) + %guid= post_url(comment.post) diff --git a/app/views/crops/index.rss.haml b/app/views/crops/index.rss.haml index 1fd02870e..aabd178ac 100644 --- a/app/views/crops/index.rss.haml +++ b/app/views/crops/index.rss.haml @@ -8,4 +8,4 @@ %title= crop.system_name %pubDate= crop.created_at.to_s(:rfc822) %link= post_url(crop) - %guid= post_url(crop) \ No newline at end of file + %guid= post_url(crop) diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index ec76ec799..f247fc78f 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -17,6 +17,15 @@ describe CommentsController do end end + describe "GET RSS feed" do + it "returns an RSS feed" do + get :index, :format => "rss" + response.should be_success + response.should render_template("comments/index") + response.content_type.should eq("application/rss+xml") + end + end + describe "GET show" do it "assigns the requested comment as @comment" do comment = Comment.create! valid_attributes diff --git a/spec/views/comments/index.rss.haml_spec.rb b/spec/views/comments/index.rss.haml_spec.rb new file mode 100644 index 000000000..73bf99b99 --- /dev/null +++ b/spec/views/comments/index.rss.haml_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe 'comments/index.rss.haml' do + before(:each) do + controller.stub(:current_user) { nil } + @author = FactoryGirl.create(:member) + @post = FactoryGirl.create(:post) + assign(:comments, [ + FactoryGirl.create(:comment, :author => @author, :post => @post), + FactoryGirl.create(:comment, :author => @author, :post => @post) + ]) + render + end + + it 'shows RSS feed title' do + rendered.should contain "Recent comments on all posts" + end + + it 'escapes html for link to post' do + # it's then unescaped by 'render' so we don't actually look for < + rendered.should contain ' Date: Fri, 24 May 2013 13:43:06 +1000 Subject: [PATCH 074/184] Added RSS feed for comments on a single post --- app/controllers/posts_controller.rb | 4 ++++ app/views/posts/show.rss.haml | 22 ++++++++++++++++++ spec/controllers/posts_controller_spec.rb | 10 +++++++++ spec/views/posts/show.rss.haml_spec.rb | 27 +++++++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 app/views/posts/show.rss.haml create mode 100644 spec/views/posts/show.rss.haml_spec.rb diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 6bec61061..de7c80091 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -22,6 +22,10 @@ class PostsController < ApplicationController respond_to do |format| format.html # show.html.haml format.json { render json: @post } + format.rss { render( + :layout => false, + :locals => { :post => @post } + )} end end diff --git a/app/views/posts/show.rss.haml b/app/views/posts/show.rss.haml new file mode 100644 index 000000000..2b4362225 --- /dev/null +++ b/app/views/posts/show.rss.haml @@ -0,0 +1,22 @@ + +%rss{:version => 2.0} + %channel + %title #{Growstuff::Application.config.site_name} - Recent comments on #{@post.subject} + %link= post_url(@post) + - @post.comments.each do |comment| + %item + %author= comment.author.login_name + %description + + :escaped +

+ Comment on + #{ link_to @post.subject, post_url(@post) } +

+ + :escaped_markdown + #{ strip_tags comment.body } + + %pubDate= comment.created_at.to_s(:rfc822) + %link= post_url(@post) + %guid= post_url(@post) diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index f87f90fb9..bdae1ce48 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -33,6 +33,16 @@ describe PostsController do end end + describe "GET RSS feed for individual post" do + it "returns an RSS feed" do + post = Post.create! valid_attributes + get :show, { :format => "rss", :id => post.slug } + response.should be_success + response.should render_template("posts/show") + response.content_type.should eq("application/rss+xml") + end + end + describe "GET new" do it "assigns a new post as @post" do get :new, {} diff --git a/spec/views/posts/show.rss.haml_spec.rb b/spec/views/posts/show.rss.haml_spec.rb new file mode 100644 index 000000000..cfcf380e3 --- /dev/null +++ b/spec/views/posts/show.rss.haml_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe 'posts/show.rss.haml' do + before(:each) do + controller.stub(:current_user) { nil } + @author = FactoryGirl.create(:member) + @post = FactoryGirl.create(:post) + FactoryGirl.create(:comment, :author => @author, :post => @post) + FactoryGirl.create(:comment, :author => @author, :post => @post) + assign(:post, @post) + render + end + + it 'shows RSS feed title' do + rendered.should contain "Recent comments on #{@post.subject}" + end + + it 'escapes html for link to post' do + # it's then unescaped by 'render' so we don't actually look for < + rendered.should contain '
Date: Fri, 24 May 2013 14:36:58 +1000 Subject: [PATCH 075/184] Reordered comments: they are now DESC by default Added scope "post_order" to order them ASC for display on post page. Also tweaked post/comment views a bit. --- app/models/comment.rb | 3 +- app/views/comments/_form.html.haml | 5 -- app/views/comments/edit.html.haml | 4 + app/views/comments/new.html.haml | 6 ++ app/views/posts/_comments.html.haml | 6 +- spec/controllers/comments_controller_spec.rb | 2 +- spec/models/comment_spec.rb | 81 +++++++++++++------- spec/views/comments/new.html.haml_spec.rb | 8 +- 8 files changed, 73 insertions(+), 42 deletions(-) diff --git a/app/models/comment.rb b/app/models/comment.rb index ea2082bf9..f797848e0 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -3,7 +3,8 @@ class Comment < ActiveRecord::Base belongs_to :author, :class_name => 'Member' belongs_to :post - default_scope order("created_at asc") + default_scope order("created_at DESC") + scope :post_order, reorder("created_at ASC") # for display on post page after_create do recipient = self.post.author.id diff --git a/app/views/comments/_form.html.haml b/app/views/comments/_form.html.haml index 06b6d0f8b..3e3f6f66b 100644 --- a/app/views/comments/_form.html.haml +++ b/app/views/comments/_form.html.haml @@ -1,8 +1,3 @@ -= render :partial => "posts/single", :locals => { :post => @post || @comment.post, :subject => true } - -= render :partial => "posts/comments" - -%h2 Your comment = form_for @comment do |f| - if @comment.errors.any? #error_explanation diff --git a/app/views/comments/edit.html.haml b/app/views/comments/edit.html.haml index 7b07b546d..912c8a8b8 100644 --- a/app/views/comments/edit.html.haml +++ b/app/views/comments/edit.html.haml @@ -1,3 +1,7 @@ = content_for :title, "Editing comment" +%p + Editing comment on + = link_to @comment.post.subject, @comment.post + = render 'form' diff --git a/app/views/comments/new.html.haml b/app/views/comments/new.html.haml index 66393a209..c01ee755f 100644 --- a/app/views/comments/new.html.haml +++ b/app/views/comments/new.html.haml @@ -1,3 +1,9 @@ = content_for :title, "New comment" += render :partial => "posts/single", :locals => { :post => @post || @comment.post, :subject => true } + += render :partial => "posts/comments", :locals => {:post => @post || @comment.post} + +%p Your comment: + = render 'form' diff --git a/app/views/posts/_comments.html.haml b/app/views/posts/_comments.html.haml index c06124014..bf565debc 100644 --- a/app/views/posts/_comments.html.haml +++ b/app/views/posts/_comments.html.haml @@ -1,8 +1,8 @@ %a{:name => "comments"} -- if @comments +- if post.comments %h2 - =pluralize(@comments.length, "comment") - - @comments.each do |c| + =pluralize(post.comments.length, "comment") + - post.comments.post_order.each do |c| = render :partial => "comments/single", :locals => { :comment => c } - else diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index f247fc78f..128bcdd4e 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -71,7 +71,7 @@ describe CommentsController do old_comment = FactoryGirl.create(:comment, :post => post) comment = FactoryGirl.create(:comment, :post => post) get :edit, {:id => comment.to_param} - assigns(:comments).should eq([old_comment, comment]) + assigns(:comments).should eq([comment, old_comment]) end end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index 7bad161bf..d487d0b91 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -2,40 +2,65 @@ require 'spec_helper' describe Comment do - before(:each) do - @comment = FactoryGirl.create(:comment) + context "basic" do + before(:each) do + @comment = FactoryGirl.create(:comment) + end + + it "belongs to a post" do + @comment.post.should be_an_instance_of Post + end + + it "belongs to an author" do + @comment.author.should be_an_instance_of Member + end end - it "belongs to a post" do - @comment.post.should be_an_instance_of Post + context "notifications" do + before(:each) do + @comment = FactoryGirl.create(:comment) + end + + it "sends a notification when a comment is posted" do + expect { + FactoryGirl.create(:comment) + }.to change(Notification, :count).by(1) + end + + it "sets the notification fields" do + @c = FactoryGirl.create(:comment) + @n = Notification.first + @n.sender.should eq @c.author + @n.recipient.should eq @c.post.author + @n.subject.should match /commented on/ + @n.body.should eq @c.body + @n.post.should eq @c.post + end + + it "doesn't send notifications to yourself" do + @m = FactoryGirl.create(:member) + @p = FactoryGirl.create(:post, :author => @m) + expect { + FactoryGirl.create(:comment, :post => @p, :author => @m) + }.to change(Notification, :count).by(0) + end end - it "belongs to an author" do - @comment.author.should be_an_instance_of Member - end + context "ordering" do + before(:each) do + @m = FactoryGirl.create(:member) + @p = FactoryGirl.create(:post, :author => @m) + @c1 = FactoryGirl.create(:comment, :post => @p, :author => @m) + @c2 = FactoryGirl.create(:comment, :post => @p, :author => @m) + end - it "sends a notification when a comment is posted" do - expect { - FactoryGirl.create(:comment) - }.to change(Notification, :count).by(1) - end + it 'is in DESC order by default' do + Comment.all.should eq [@c2, @c1] + end - it "sets the notification fields" do - @c = FactoryGirl.create(:comment) - @n = Notification.first - @n.sender.should eq @c.author - @n.recipient.should eq @c.post.author - @n.subject.should match /commented on/ - @n.body.should eq @c.body - @n.post.should eq @c.post - end - - it "doesn't send notifications to yourself" do - @m = FactoryGirl.create(:member) - @p = FactoryGirl.create(:post, :author => @m) - expect { - FactoryGirl.create(:comment, :post => @p, :author => @m) - }.to change(Notification, :count).by(0) + it 'has a scope for ASC order for displaying on post page' do + Comment.post_order.should eq [@c1, @c2] + end end end diff --git a/spec/views/comments/new.html.haml_spec.rb b/spec/views/comments/new.html.haml_spec.rb index 90b5e1cb3..a27868a16 100644 --- a/spec/views/comments/new.html.haml_spec.rb +++ b/spec/views/comments/new.html.haml_spec.rb @@ -4,9 +4,9 @@ describe "comments/new" do before(:each) do controller.stub(:current_user) { nil } @post = FactoryGirl.create(:post) - @previous_comment = FactoryGirl.create(:comment, :post => @post) - assign(:comment, FactoryGirl.create(:comment, :post => @post)) - assign(:comments, [@previous_comment]) + @comment = FactoryGirl.create(:comment, :post => @post) + assign(:comment, @comment) + assign(:comments, [@comment]) render end @@ -15,7 +15,7 @@ describe "comments/new" do end it "shows previous comments" do - rendered.should contain @previous_comment.body + rendered.should contain @comment.body end it "shows the correct comment count" do From 18b92b133b95c3c35b406de44853f77c4fde4251 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 24 May 2013 14:43:45 +1000 Subject: [PATCH 076/184] Added titles to comment items in RSS --- app/views/comments/index.rss.haml | 1 + app/views/posts/show.rss.haml | 1 + spec/views/comments/index.rss.haml_spec.rb | 4 ++++ spec/views/posts/show.rss.haml_spec.rb | 4 ++++ 4 files changed, 10 insertions(+) diff --git a/app/views/comments/index.rss.haml b/app/views/comments/index.rss.haml index 7408684f9..5e4b227c5 100644 --- a/app/views/comments/index.rss.haml +++ b/app/views/comments/index.rss.haml @@ -5,6 +5,7 @@ %link= comments_url - @comments.each do |comment| %item + %title Comment by #{comment.author.login_name} on #{comment.post.subject} %author= comment.author.login_name %description diff --git a/app/views/posts/show.rss.haml b/app/views/posts/show.rss.haml index 2b4362225..4f260a038 100644 --- a/app/views/posts/show.rss.haml +++ b/app/views/posts/show.rss.haml @@ -5,6 +5,7 @@ %link= post_url(@post) - @post.comments.each do |comment| %item + %title Comment by #{comment.author.login_name} on #{comment.created_at.to_s} %author= comment.author.login_name %description diff --git a/spec/views/comments/index.rss.haml_spec.rb b/spec/views/comments/index.rss.haml_spec.rb index 73bf99b99..e5eeeac95 100644 --- a/spec/views/comments/index.rss.haml_spec.rb +++ b/spec/views/comments/index.rss.haml_spec.rb @@ -16,6 +16,10 @@ describe 'comments/index.rss.haml' do rendered.should contain "Recent comments on all posts" end + it 'shows item title' do + rendered.should contain "Comment by #{@author.login_name}" + end + it 'escapes html for link to post' do # it's then unescaped by 'render' so we don't actually look for < rendered.should contain ' Date: Tue, 28 May 2013 16:37:44 +1000 Subject: [PATCH 079/184] Roughly got Paypal working, after much messing around. Still need more tests for the orders_controller, and to store the token when it's returned. --- Gemfile | 2 ++ Gemfile.lock | 14 ++++++++++++ app/controllers/orders_controller.rb | 24 ++++++++++++++++++++ app/models/ability.rb | 2 ++ app/models/order.rb | 18 +++++++++++++++ app/views/orders/show.html.haml | 6 ++--- config/environments/development.rb | 11 +++++++++ config/environments/production.rb | 11 +++++++++ config/environments/staging.rb | 11 +++++++++ config/environments/test.rb | 7 ++++++ config/routes.rb | 3 +++ spec/models/order_spec.rb | 34 ++++++++++++++++++++++++++++ 12 files changed, 139 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 3802d0dde..d8d5911e2 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,8 @@ gem 'haml' gem 'cancan' +gem 'activemerchant' + # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' diff --git a/Gemfile.lock b/Gemfile.lock index 6493ad58b..8a2ad9de5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,6 +14,17 @@ GEM rack-cache (~> 1.2) rack-test (~> 0.6.1) sprockets (~> 2.2.1) + active_utils (1.0.5) + activesupport (>= 2.3.11) + i18n + activemerchant (1.32.1) + active_utils (>= 1.0.2) + activesupport (>= 2.3.14) + builder (>= 2.0.0) + i18n + json (>= 1.5.1) + money + nokogiri activemodel (3.2.13) activesupport (= 3.2.13) builder (~> 3.0.0) @@ -116,6 +127,8 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.21) + money (5.1.1) + i18n (~> 0.6.0) multi_json (1.7.1) net-scp (1.1.0) net-ssh (>= 2.6.5) @@ -232,6 +245,7 @@ PLATFORMS ruby DEPENDENCIES + activemerchant bluecloth bootstrap-datepicker-rails bundler (>= 1.1.5) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 0da87512e..2e1b481f3 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -28,6 +28,23 @@ class OrdersController < ApplicationController end end + # checkout with PayPal + def checkout + @order = Order.find(params[:id]) + + response = EXPRESS_GATEWAY.setup_purchase( + @order.total, + :items => @order.activemerchant_items, + :currency => Growstuff::Application.config.currency, + :no_shipping => true, + :ip => request.remote_ip, + :return_url => complete_order_url, + :cancel_return_url => shop_url + ) + redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token) + + end + def complete @order = Order.find(params[:id]) @@ -42,6 +59,13 @@ class OrdersController < ApplicationController end + def cancel + @order = Order.find(params[:id]) + respond_to do |format| + format.html { redirect_to shop_url, notice: 'Order was cancelled.' } + end + end + # DELETE /orders/1 def destroy @order = Order.find(params[:id]) diff --git a/app/models/ability.rb b/app/models/ability.rb index c3c59ee4d..cc403b32c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -67,6 +67,8 @@ class Ability can :create, Order can :read, Order, :member_id => member.id can :complete, Order, :member_id => member.id, :completed_at => nil + can :checkout, Order, :member_id => member.id, :completed_at => nil + can :cancel, Order, :member_id => member.id, :completed_at => nil can :destroy, Order, :member_id => member.id, :completed_at => nil can :create, OrderItem diff --git a/app/models/order.rb b/app/models/order.rb index f481ced3e..cf88e7b9f 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -6,6 +6,24 @@ class Order < ActiveRecord::Base default_scope order('created_at DESC') + # total price of an order + def total + order_items.to_a.sum(&:price) + end + + # return items in the format ActiveMerchant/PayPal want them + def activemerchant_items + items = [] + order_items.each do |i| + items.push({ + :name => i.product.name, + :quantity => i.quantity, + :amount => i.price + }) + end + return items + end + # when an order is completed, we update the member's account to mark # them as paid, or whatever, based on what products they ordered def update_account diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 1099a61e8..514c78acc 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -25,7 +25,6 @@ %th Price %th Quantity %th Subtotal - - total = 0 - @order.order_items.each do |i| %tr %td= i.product.name @@ -34,7 +33,6 @@ %td= i.quantity %td - subtotal = i.price * i.quantity - - total += subtotal = price_with_currency(subtotal) %tr @@ -44,14 +42,14 @@ %strong Total: %td %strong - = price_with_currency(total) + = price_with_currency(@order.total) %p - if can? :destroy, @order = link_to 'Delete this order', @order, method: :delete, | data: { confirm: 'Are you sure?' }, :class => 'btn' - if can? :complete, @order - = link_to 'Checkout', complete_order_path(@order), :class => 'btn btn-primary' + = link_to 'Checkout', checkout_order_path(@order), :class => 'btn btn-primary' %p = link_to "View other orders/order history", orders_path diff --git a/config/environments/development.rb b/config/environments/development.rb index 76d4a5ded..932c1ddc5 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -53,4 +53,15 @@ Growstuff::Application.configure do config.analytics_code = '' config.currency = 'AUD' end + + config.after_initialize do + ActiveMerchant::Billing::Base.mode = :test + paypal_options = { + :login => ENV['PAYPAL_USERNAME'], + :password => ENV['PAYPAL_PASSWORD'], + :signature => ENV['PAYPAL_SIGNATURE'] + } + ::STANDARD_GATEWAY = ActiveMerchant::Billing::PaypalGateway.new(paypal_options) + ::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options) + end end diff --git a/config/environments/production.rb b/config/environments/production.rb index a97654cc6..2cc74822a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -88,4 +88,15 @@ Growstuff::Application.configure do config.currency = 'AUD' end + config.after_initialize do + ActiveMerchant::Billing::Base.mode = :production + paypal_options = { + :login => ENV['PAYPAL_USERNAME'], + :password => ENV['PAYPAL_PASSWORD'], + :signature => ENV['PAYPAL_SIGNATURE'] + } + ::STANDARD_GATEWAY = ActiveMerchant::Billing::PaypalGateway.new(paypal_options) + ::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options) + end + end diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 1085a2ef0..9ed1a7ced 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -84,4 +84,15 @@ Growstuff::Application.configure do config.currency = 'AUD' end + config.after_initialize do + ActiveMerchant::Billing::Base.mode = :test + paypal_options = { + :login => ENV['PAYPAL_USERNAME'], + :password => ENV['PAYPAL_PASSWORD'], + :signature => ENV['PAYPAL_SIGNATURE'] + } + ::STANDARD_GATEWAY = ActiveMerchant::Billing::PaypalGateway.new(paypal_options) + ::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options) + end + end diff --git a/config/environments/test.rb b/config/environments/test.rb index 57e8c5d1a..e570d4630 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -46,6 +46,12 @@ Growstuff::Application.configure do config.currency = 'AUD' end + config.after_initialize do + ActiveMerchant::Billing::Base.mode = :test + ::STANDARD_GATEWAY = ActiveMerchant::Billing::BogusGateway.new + ::EXPRESS_GATEWAY = ActiveMerchant::Billing::BogusGateway.new + end + end Geocoder.configure(:lookup => :test) @@ -76,3 +82,4 @@ Geocoder::Lookup::Test.add_stub( } ] ) + diff --git a/config/routes.rb b/config/routes.rb index 6064228c0..3df532b51 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,7 +18,10 @@ Growstuff::Application.routes.draw do resources :account_types resources :accounts resources :orders + match 'orders/:id/checkout' => 'orders#checkout', :as => 'checkout_order' match 'orders/:id/complete' => 'orders#complete', :as => 'complete_order' + match 'orders/:id/cancel' => 'orders#cancel', :as => 'cancel_order' + resources :order_items resources :products diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index 54d67d16f..ffa3e2f12 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -37,4 +37,38 @@ describe Order do @member.account.paid_until.should_not be_nil end + it "totals the amount due" do + @member = FactoryGirl.create(:member) + @order = FactoryGirl.create(:order, :member => @member) + @product = FactoryGirl.create(:product, + :min_price => 1000 + ) + # we force an order to only have one item at present. Add more if wanted + # later. + @order_item1 = FactoryGirl.create(:order_item, + :order_id => @order.id, :product_id => @product.id, :price => 1111) + + @order.total.should eq 1111 + end + + it "formats order items for activemerchant" do + @member = FactoryGirl.create(:member) + @order = FactoryGirl.create(:order, :member => @member) + @product = FactoryGirl.create(:product, + :name => 'foo', + :min_price => 1000 + ) + # we force an order to only have one item at present. Add more if wanted + # later. + @order_item1 = FactoryGirl.create(:order_item, + :order_id => @order.id, :product_id => @product.id, :price => 1111, :quantity => 1) + + @order.activemerchant_items.should eq [{ + :name => 'foo', + :quantity => 1, + :amount => 1111 + }] + + end + end From 5e678d1dbd0a3dbacdaf2228e38d2d1d0abecd07 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 29 May 2013 13:22:17 +1000 Subject: [PATCH 080/184] default test paypal gateway details to 'dummy' or it fails --- config/environments/development.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 932c1ddc5..b76b3ffb4 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -57,9 +57,9 @@ Growstuff::Application.configure do config.after_initialize do ActiveMerchant::Billing::Base.mode = :test paypal_options = { - :login => ENV['PAYPAL_USERNAME'], - :password => ENV['PAYPAL_PASSWORD'], - :signature => ENV['PAYPAL_SIGNATURE'] + :login => ENV['PAYPAL_USERNAME'] || 'dummy', + :password => ENV['PAYPAL_PASSWORD'] || 'dummy', + :signature => ENV['PAYPAL_SIGNATURE'] || 'dummy' } ::STANDARD_GATEWAY = ActiveMerchant::Billing::PaypalGateway.new(paypal_options) ::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options) From b60790c8adfc9478adc431c9139ed8a5b1c13f81 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 29 May 2013 13:41:34 +1000 Subject: [PATCH 081/184] record paypal token/payer id on completion, for reference --- app/controllers/orders_controller.rb | 1 + app/models/order.rb | 9 +++++++++ .../20130529032813_add_express_token_to_orders.rb | 6 ++++++ db/schema.rb | 10 ++++++---- 4 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20130529032813_add_express_token_to_orders.rb diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index 2e1b481f3..efc21acaf 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -51,6 +51,7 @@ class OrdersController < ApplicationController @order.completed_at = Time.zone.now @order.save + @order.record_paypal_details(params[:token]) @order.update_account # apply paid account benefits, etc. respond_to do |format| diff --git a/app/models/order.rb b/app/models/order.rb index cf88e7b9f..6d79878f9 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -24,6 +24,14 @@ class Order < ActiveRecord::Base return items end + # record the paypal details for reference + def record_paypal_details(token) + self.paypal_express_token = token + details = EXPRESS_GATEWAY.details_for(token) + self.paypal_express_payer_id = details.payer_id + self.save + end + # when an order is completed, we update the member's account to mark # them as paid, or whatever, based on what products they ordered def update_account @@ -31,4 +39,5 @@ class Order < ActiveRecord::Base member.update_account_after_purchase(i.product) end end + end diff --git a/db/migrate/20130529032813_add_express_token_to_orders.rb b/db/migrate/20130529032813_add_express_token_to_orders.rb new file mode 100644 index 000000000..cf32be434 --- /dev/null +++ b/db/migrate/20130529032813_add_express_token_to_orders.rb @@ -0,0 +1,6 @@ +class AddExpressTokenToOrders < ActiveRecord::Migration + def change + add_column :orders, :paypal_express_token, :string + add_column :orders, :paypal_express_payer_id, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index cdb66dc42..c68bde39d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130518002942) do +ActiveRecord::Schema.define(:version => 20130529032813) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -147,10 +147,12 @@ ActiveRecord::Schema.define(:version => 20130518002942) do end create_table "orders", :force => true do |t| - t.integer "member_id", :limit => 255, :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.integer "member_id", :limit => 255, :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.datetime "completed_at" + t.string "paypal_express_token" + t.string "paypal_express_payer_id" end create_table "orders_products", :id => false, :force => true do |t| From 7a1eaeffeeac307dd631cebc551496e9d30e337a Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 29 May 2013 14:37:42 +1000 Subject: [PATCH 082/184] added admin controller and index view --- app/assets/javascripts/admin.js.coffee | 3 +++ app/controllers/admin_controller.rb | 8 ++++++++ app/helpers/admin_helper.rb | 2 ++ app/models/ability.rb | 22 +++------------------- app/views/admin/index.html.haml | 1 + config/routes.rb | 6 ++++++ spec/controllers/admin_controller_spec.rb | 5 +++++ spec/helpers/admin_helper_spec.rb | 15 +++++++++++++++ spec/models/ability_spec.rb | 2 +- 9 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 app/assets/javascripts/admin.js.coffee create mode 100644 app/controllers/admin_controller.rb create mode 100644 app/helpers/admin_helper.rb create mode 100644 app/views/admin/index.html.haml create mode 100644 spec/controllers/admin_controller_spec.rb create mode 100644 spec/helpers/admin_helper_spec.rb diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/admin.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb new file mode 100644 index 000000000..272e14eea --- /dev/null +++ b/app/controllers/admin_controller.rb @@ -0,0 +1,8 @@ +class AdminController < ApplicationController + def index + authorize! :manage, :all + respond_to do |format| + format.html # index.html.haml + end + end +end diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb new file mode 100644 index 000000000..d5c6d3555 --- /dev/null +++ b/app/helpers/admin_helper.rb @@ -0,0 +1,2 @@ +module AdminHelper +end diff --git a/app/models/ability.rb b/app/models/ability.rb index cc403b32c..4be2e10a5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -78,32 +78,16 @@ class Ability cannot :destroy, OrderItem, :order => { :member_id => member.id, :completed_at => nil } if member.has_role? :admin - # admin user roles (for authorization) - can :read, Role - can :manage, Role - # for now, only admins can create/edit forums - can :manage, Forum + can :read, :all + can :manage, :all - # admins can manage products - can :manage, Product - - # admins can read other people's orders... - can :read, Order - can :read, OrderItem - - # but they can't do anything to them, because orders are *history* + # can't change order history, because it's *history* cannot :create, Order cannot :complete, Order cannot :destroy, Order cannot :manage, OrderItem - # admins can read and manage members' account details (paid acct - # status, etc) - can :read, Account - can :manage, Account - can :read, AccountType - can :manage, AccountType end end diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml new file mode 100644 index 000000000..ef7530bca --- /dev/null +++ b/app/views/admin/index.html.haml @@ -0,0 +1 @@ +-content_for :title, 'Admin' diff --git a/config/routes.rb b/config/routes.rb index 3df532b51..aeea090df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,11 +89,17 @@ Growstuff::Application.routes.draw do # match ':controller(/:action(/:id))(.:format)' match '/policy/:action' => 'policy#:action' + match '/support' => 'support#index' match '/support/:action' => 'support#:action' + match '/about' => 'about#index' match '/about/:action' => 'about#:action' + match '/shop' => 'shop#index' match '/shop/:action' => 'shop#:action' + match '/admin' => 'admin#index' + match '/admin/:action' => 'admin#:action' + end diff --git a/spec/controllers/admin_controller_spec.rb b/spec/controllers/admin_controller_spec.rb new file mode 100644 index 000000000..587324b52 --- /dev/null +++ b/spec/controllers/admin_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe AdminController do + +end diff --git a/spec/helpers/admin_helper_spec.rb b/spec/helpers/admin_helper_spec.rb new file mode 100644 index 000000000..3870aa933 --- /dev/null +++ b/spec/helpers/admin_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the AdminHelper. For example: +# +# describe AdminHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe AdminHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 3d0cd04e3..527eaf908 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -238,7 +238,7 @@ describe Ability do end it "cannot delete orders" do - @admin_ability.should_not be_able_to(:delete, @order) + @admin_ability.should_not be_able_to(:destroy, @order) end end From cbca8c19a5bdd48f7b9cb07b06a44f31c846dd55 Mon Sep 17 00:00:00 2001 From: Skud Date: Wed, 29 May 2013 14:37:42 +1000 Subject: [PATCH 083/184] added admin controller and index view --- app/assets/javascripts/admin/orders.js.coffee | 3 ++ app/controllers/admin/orders_controller.rb | 45 +++++++++++++++++++ app/helpers/admin/orders_helper.rb | 2 + app/views/admin/index.html.haml | 12 +++++ app/views/admin/orders/_searchform.html.haml | 5 +++ app/views/admin/orders/index.html.haml | 3 ++ app/views/admin/orders/search.html.haml | 36 +++++++++++++++ app/views/home/index.html.haml | 10 ----- app/views/layouts/_header.html.haml | 2 + app/views/orders/show.html.haml | 8 ++++ config/routes.rb | 2 + .../admin/orders_controller_spec.rb | 5 +++ spec/helpers/admin/orders_helper_spec.rb | 15 +++++++ spec/views/admin/index_spec.rb | 17 +++++++ spec/views/home/index_spec.rb | 10 ----- 15 files changed, 155 insertions(+), 20 deletions(-) create mode 100644 app/assets/javascripts/admin/orders.js.coffee create mode 100644 app/controllers/admin/orders_controller.rb create mode 100644 app/helpers/admin/orders_helper.rb create mode 100644 app/views/admin/orders/_searchform.html.haml create mode 100644 app/views/admin/orders/index.html.haml create mode 100644 app/views/admin/orders/search.html.haml create mode 100644 spec/controllers/admin/orders_controller_spec.rb create mode 100644 spec/helpers/admin/orders_helper_spec.rb create mode 100644 spec/views/admin/index_spec.rb diff --git a/app/assets/javascripts/admin/orders.js.coffee b/app/assets/javascripts/admin/orders.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/admin/orders.js.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/app/controllers/admin/orders_controller.rb b/app/controllers/admin/orders_controller.rb new file mode 100644 index 000000000..fb94cf1ae --- /dev/null +++ b/app/controllers/admin/orders_controller.rb @@ -0,0 +1,45 @@ +class Admin::OrdersController < ApplicationController + def index + authorize! :manage, :all + respond_to do |format| + format.html # index.html.haml + end + end + + def search + @orders = nil + + if params[:search_text] + case params[:search_by] + when "member" + begin + @member = Member.find(params[:search_text]) + @orders = @member.orders + rescue ActiveRecord::RecordNotFound + flash[:alert] = "Couldn't find member with name #{params[:search_text]}" + end + when "order_id" + begin + @orders = [ Order.find(params[:search_text]) ] + rescue ActiveRecord::RecordNotFound + flash[:alert] = "Couldn't find order with id #{params[:search_text]}" + end + when "paypal_token" + @orders = [ Order.find_by_paypal_express_token(params[:search_text]) ] + if @orders.nil? + flash[:alert] = "Couldn't find order with paypal token #{params[:search_text]}" + end + when "paypal_payer_id" + @orders = [ Order.find_by_paypal_express_payer_id(params[:search_text]) ] + if @orders.nil? + flash[:alert] = "Couldn't find order with paypal payer id #{params[:search_text]}" + end + end + end + + respond_to do |format| + format.html # index.html.haml + end + + end +end diff --git a/app/helpers/admin/orders_helper.rb b/app/helpers/admin/orders_helper.rb new file mode 100644 index 000000000..863374ff6 --- /dev/null +++ b/app/helpers/admin/orders_helper.rb @@ -0,0 +1,2 @@ +module Admin::OrdersHelper +end diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index ef7530bca..0ce432ccd 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -1 +1,13 @@ -content_for :title, 'Admin' + +%h2 Manage + +%ul + %li= link_to "Account types", account_types_path + %li= link_to "Products", products_path + %li= link_to "Roles", roles_path + %li= link_to "Forums", forums_path + +%h2 Orders + +=render "admin/orders/searchform" diff --git a/app/views/admin/orders/_searchform.html.haml b/app/views/admin/orders/_searchform.html.haml new file mode 100644 index 000000000..8cbb7a91b --- /dev/null +++ b/app/views/admin/orders/_searchform.html.haml @@ -0,0 +1,5 @@ += form_tag(url_for(:controller => 'admin/orders', :action => 'search'), :method => :get, :class => 'form-inline') do + = label_tag :distance, "Search orders:", :class => 'control-label' + = text_field_tag :search_text + = select_tag :search_by, options_for_select({'Member' => 'member', 'Order ID' => 'order_id', 'Paypal Token' => 'paypal_token', 'Paypal Payer ID' => 'paypal_payer_id' }) + = submit_tag "Search", :class => 'btn btn-primary' diff --git a/app/views/admin/orders/index.html.haml b/app/views/admin/orders/index.html.haml new file mode 100644 index 000000000..42626c777 --- /dev/null +++ b/app/views/admin/orders/index.html.haml @@ -0,0 +1,3 @@ +-content_for :title, 'Admin Orders' + +=render "admin/orders/searchform" diff --git a/app/views/admin/orders/search.html.haml b/app/views/admin/orders/search.html.haml new file mode 100644 index 000000000..b553bec5d --- /dev/null +++ b/app/views/admin/orders/search.html.haml @@ -0,0 +1,36 @@ +-content_for :title, 'Search Orders' + +=render "admin/orders/searchform" + +- if @orders + %h2 + Found + = pluralize(@orders.count, "result") + + %table.table.table-striped + %tr + %th Member + %th Order number + %th Date completed + %th Items + %th + + - @orders.each do |order| + %tr + %td= link_to order.member.login_name, order.member + %td= order.id + %td + - if order.completed_at + = order.completed_at.to_s + - else + In progress + %td + - if order.order_items.count > 0 + - order.order_items.each do |o| + = o.quantity + x + = o.product.name + @ + = price_with_currency(o.price) + %br/ + %td= link_to 'Details', order, :class => 'btn btn-mini' diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 8819e544e..a9329bb05 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -35,16 +35,6 @@ %br/ - if current_member == current_member && !current_member.is_paid? = link_to "Upgrade and Support Growstuff", shop_path, :class => 'btn btn-primary' - - if current_member.has_role?(:admin) - %p - %b You are an ADMIN USER. - - - if current_member.forums.count > 0 - %p - %b Forums you administer: - %ul - - current_member.forums.each do |f| - %li= link_to f.name, f .row .span6 diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index da6b91970..622244364 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -13,6 +13,8 @@ %li= link_to "Posts", posts_path %li= link_to "Forums", forums_path %li= link_to "Shop", shop_path + -if member_signed_in? and current_member.has_role?(:admin) + %li= link_to "Admin", admin_path %li.divider-vertical - if member_signed_in? %li.dropdown< diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 514c78acc..8136c13ec 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -17,6 +17,14 @@ %strong Date completed: = @order.completed_at.to_s + - if current_member.has_role? :admin + %p + %strong Paypal Express token: + = @order.paypal_express_token + %p + %strong Paypal Express payer ID: + = @order.paypal_express_payer_id + %h2 Order items %table.table.table-striped diff --git a/config/routes.rb b/config/routes.rb index aeea090df..f69f07355 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -99,6 +99,8 @@ Growstuff::Application.routes.draw do match '/shop' => 'shop#index' match '/shop/:action' => 'shop#:action' + match '/admin/orders' => 'admin/orders#index' + match '/admin/orders/:action' => 'admin/orders#:action' match '/admin' => 'admin#index' match '/admin/:action' => 'admin#:action' diff --git a/spec/controllers/admin/orders_controller_spec.rb b/spec/controllers/admin/orders_controller_spec.rb new file mode 100644 index 000000000..b987ca78b --- /dev/null +++ b/spec/controllers/admin/orders_controller_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Admin::OrdersController do + +end diff --git a/spec/helpers/admin/orders_helper_spec.rb b/spec/helpers/admin/orders_helper_spec.rb new file mode 100644 index 000000000..a80e748a5 --- /dev/null +++ b/spec/helpers/admin/orders_helper_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +# Specs in this file have access to a helper object that includes +# the Admin::OrdersHelper. For example: +# +# describe Admin::OrdersHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# helper.concat_strings("this","that").should == "this that" +# end +# end +# end +describe Admin::OrdersHelper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/views/admin/index_spec.rb b/spec/views/admin/index_spec.rb new file mode 100644 index 000000000..d63f897ee --- /dev/null +++ b/spec/views/admin/index_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe 'admin/index.html.haml', :type => "view" do + before(:each) do + @member = FactoryGirl.create(:admin_member) + sign_in @member + controller.stub(:current_user) { @member } + render + end + + it "includes links to manage various things" do + assert_select "a", :href => account_types_path + assert_select "a", :href => products_path + assert_select "a", :href => roles_path + assert_select "a", :href => forums_path + end +end diff --git a/spec/views/home/index_spec.rb b/spec/views/home/index_spec.rb index 49f9e5cf1..585fc8d4f 100644 --- a/spec/views/home/index_spec.rb +++ b/spec/views/home/index_spec.rb @@ -46,8 +46,6 @@ describe 'home/index.html.haml', :type => "view" do @forum = FactoryGirl.create(:forum, :owner => @member) @post = FactoryGirl.create(:post, :author => @member) assign(:posts, [@post]) - @role = FactoryGirl.create(:admin) - @member.roles << @role render end @@ -82,13 +80,5 @@ describe 'home/index.html.haml', :type => "view" do rendered.should contain "Upgrade" end - it 'shows admin status' do - rendered.should contain "You are an ADMIN USER" - end - - it 'shows forum list' do - assert_select "a[href=#{url_for(@forum)}]", @forum.name - end - end end From 08c3ec7dec024ae49e8b6d1bf51c27989295ac19 Mon Sep 17 00:00:00 2001 From: Skud Date: Fri, 31 May 2013 12:15:23 +1000 Subject: [PATCH 084/184] Vendored the activemerchant gem to help with testing We needed a bogus paypal gateway as per http://infotrope.net/2013/05/31/testing-paypal-express-with-activemerchants-bogusgateway-and-how-to-make-it-work/ Tests now pass usefully. --- Gemfile | 9 +- Gemfile.lock | 28 +- app/controllers/orders_controller.rb | 7 +- config/environments/test.rb | 4 +- spec/controllers/orders_controller_spec.rb | 11 + vendor/gems/active_utils-1.0.5/.gitignore | 5 + vendor/gems/active_utils-1.0.5/Gemfile | 3 + vendor/gems/active_utils-1.0.5/MIT-LICENSE | 20 + vendor/gems/active_utils-1.0.5/README.md | 15 + vendor/gems/active_utils-1.0.5/Rakefile | 13 + .../active_utils-1.0.5/active_utils.gemspec | 26 + .../active_utils-1.0.5/lib/active_utils.rb | 20 + .../lib/active_utils/common/connection.rb | 147 + .../lib/active_utils/common/country.rb | 328 + .../lib/active_utils/common/error.rb | 26 + .../common/network_connection_retries.rb | 58 + .../lib/active_utils/common/post_data.rb | 24 + .../lib/active_utils/common/posts_data.rb | 69 + .../common/requires_parameters.rb | 16 + .../lib/active_utils/common/utils.rb | 20 + .../lib/active_utils/common/validateable.rb | 81 + .../lib/active_utils/version.rb | 3 + .../active_utils-1.0.5/lib/certs/cacert.pem | 7815 +++++++++++++++++ .../active_utils-1.0.5/test/test_helper.rb | 12 + .../test/unit/connection_test.rb | 149 + .../test/unit/country_code_test.rb | 31 + .../test/unit/country_test.rb | 68 + .../unit/network_connection_retries_test.rb | 127 + .../test/unit/post_data_test.rb | 50 + .../test/unit/posts_data_test.rb | 35 + .../test/unit/utils_test.rb | 7 + .../test/unit/validateable_test.rb | 59 + vendor/gems/activemerchant-1.33.0/CHANGELOG | 1143 +++ .../gems/activemerchant-1.33.0/CONTRIBUTORS | 402 + vendor/gems/activemerchant-1.33.0/MIT-LICENSE | 20 + vendor/gems/activemerchant-1.33.0/README.md | 221 + .../activemerchant-1.33.0/gem-public_cert.pem | 20 + .../lib/active_merchant.rb | 63 + .../lib/active_merchant/billing.rb | 9 + .../lib/active_merchant/billing/avs_result.rb | 98 + .../lib/active_merchant/billing/base.rb | 56 + .../lib/active_merchant/billing/check.rb | 69 + .../active_merchant/billing/credit_card.rb | 278 + .../billing/credit_card_formatting.rb | 21 + .../billing/credit_card_methods.rb | 143 + .../lib/active_merchant/billing/cvv_result.rb | 38 + .../active_merchant/billing/expiry_date.rb | 34 + .../lib/active_merchant/billing/gateway.rb | 177 + .../lib/active_merchant/billing/gateways.rb | 17 + .../billing/gateways/authorize_net.rb | 724 ++ .../billing/gateways/authorize_net_cim.rb | 956 ++ .../billing/gateways/balanced.rb | 467 + .../billing/gateways/banwire.rb | 105 + .../billing/gateways/barclays_epdq.rb | 314 + .../billing/gateways/beanstream.rb | 169 + .../gateways/beanstream/beanstream_core.rb | 393 + .../billing/gateways/beanstream_interac.rb | 54 + .../billing/gateways/blue_pay.rb | 503 ++ .../active_merchant/billing/gateways/bogus.rb | 142 + .../billing/gateways/braintree.rb | 19 + .../gateways/braintree/braintree_common.rb | 9 + .../billing/gateways/braintree_blue.rb | 401 + .../billing/gateways/braintree_orange.rb | 19 + .../billing/gateways/card_save.rb | 23 + .../billing/gateways/card_stream.rb | 225 + .../billing/gateways/card_stream_modern.rb | 155 + .../active_merchant/billing/gateways/cc5.rb | 156 + .../billing/gateways/certo_direct.rb | 277 + .../billing/gateways/cyber_source.rb | 614 ++ .../billing/gateways/data_cash.rb | 591 ++ .../billing/gateways/efsnet.rb | 230 + .../billing/gateways/elavon.rb | 312 + .../active_merchant/billing/gateways/epay.rb | 275 + .../billing/gateways/evo_ca.rb | 308 + .../active_merchant/billing/gateways/eway.rb | 225 + .../billing/gateways/eway_managed.rb | 291 + .../billing/gateways/eway_rapid.rb | 300 + .../active_merchant/billing/gateways/exact.rb | 218 + .../billing/gateways/fat_zebra.rb | 152 + .../billing/gateways/federated_canada.rb | 167 + .../billing/gateways/finansbank.rb | 22 + .../billing/gateways/first_pay.rb | 176 + .../billing/gateways/firstdata_e4.rb | 314 + .../billing/gateways/garanti.rb | 257 + .../active_merchant/billing/gateways/hdfc.rb | 207 + .../billing/gateways/ideal/ideal_base.rb | 249 + .../billing/gateways/ideal/ideal_rabobank.pem | 13 + .../billing/gateways/ideal/ideal_response.rb | 29 + .../billing/gateways/ideal_rabobank.rb | 66 + .../billing/gateways/inspire.rb | 221 + .../billing/gateways/instapay.rb | 163 + .../billing/gateways/iridium.rb | 262 + .../billing/gateways/itransact.rb | 448 + .../billing/gateways/jetpay.rb | 275 + .../billing/gateways/linkpoint.rb | 447 + .../active_merchant/billing/gateways/litle.rb | 540 ++ .../billing/gateways/merchant_e_solutions.rb | 176 + .../billing/gateways/merchant_ware.rb | 323 + .../billing/gateways/merchant_warrior.rb | 190 + .../billing/gateways/mercury.rb | 272 + .../billing/gateways/metrics_global.rb | 322 + .../active_merchant/billing/gateways/migs.rb | 265 + .../billing/gateways/migs/migs_codes.rb | 100 + .../billing/gateways/modern_payments.rb | 37 + .../billing/gateways/modern_payments_cim.rb | 219 + .../billing/gateways/moneris.rb | 244 + .../billing/gateways/moneris_us.rb | 208 + .../billing/gateways/nab_transact.rb | 269 + .../billing/gateways/net_registry.rb | 193 + .../billing/gateways/netaxept.rb | 181 + .../billing/gateways/netbilling.rb | 197 + .../billing/gateways/netpay.rb | 223 + .../active_merchant/billing/gateways/nmi.rb | 13 + .../active_merchant/billing/gateways/ogone.rb | 424 + .../billing/gateways/optimal_payment.rb | 297 + .../billing/gateways/orbital.rb | 633 ++ .../billing/gateways/orbital/avs_result.rb | 93 + .../orbital/orbital_soft_descriptors.rb | 46 + .../billing/gateways/pay_gate_xml.rb | 261 + .../billing/gateways/pay_junction.rb | 396 + .../billing/gateways/pay_secure.rb | 119 + .../billing/gateways/paybox_direct.rb | 196 + .../billing/gateways/payflow.rb | 268 + .../gateways/payflow/payflow_common_api.rb | 210 + .../payflow/payflow_express_response.rb | 39 + .../gateways/payflow/payflow_response.rb | 13 + .../billing/gateways/payflow_express.rb | 224 + .../billing/gateways/payflow_express_uk.rb | 15 + .../billing/gateways/payflow_uk.rb | 21 + .../billing/gateways/payment_express.rb | 340 + .../billing/gateways/paymill.rb | 179 + .../billing/gateways/paypal.rb | 106 + .../gateways/paypal/paypal_common_api.rb | 654 ++ .../paypal/paypal_express_response.rb | 61 + .../gateways/paypal/paypal_recurring_api.rb | 248 + .../billing/gateways/paypal_bogus.rb | 53 + .../billing/gateways/paypal_ca.rb | 13 + .../billing/gateways/paypal_digital_goods.rb | 43 + .../billing/gateways/paypal_express.rb | 222 + .../billing/gateways/paypal_express_common.rb | 30 + .../billing/gateways/paystation.rb | 199 + .../billing/gateways/payway.rb | 207 + .../active_merchant/billing/gateways/pin.rb | 165 + .../billing/gateways/plugnpay.rb | 294 + .../billing/gateways/psigate.rb | 227 + .../billing/gateways/psl_card.rb | 303 + .../active_merchant/billing/gateways/qbms.rb | 292 + .../billing/gateways/quantum.rb | 276 + .../billing/gateways/quickpay.rb | 335 + .../billing/gateways/realex.rb | 303 + .../billing/gateways/redsys.rb | 394 + .../active_merchant/billing/gateways/sage.rb | 152 + .../billing/gateways/sage/sage_bankcard.rb | 93 + .../billing/gateways/sage/sage_core.rb | 114 + .../gateways/sage/sage_virtual_check.rb | 102 + .../billing/gateways/sage_pay.rb | 324 + .../billing/gateways/sallie_mae.rb | 143 + .../billing/gateways/samurai.rb | 118 + .../billing/gateways/secure_net.rb | 329 + .../billing/gateways/secure_pay.rb | 28 + .../billing/gateways/secure_pay_au.rb | 279 + .../billing/gateways/secure_pay_tech.rb | 112 + .../billing/gateways/skip_jack.rb | 453 + .../billing/gateways/smart_ps.rb | 272 + .../billing/gateways/spreedly_core.rb | 233 + .../billing/gateways/stripe.rb | 255 + .../billing/gateways/trans_first.rb | 126 + .../billing/gateways/transax.rb | 23 + .../billing/gateways/transnational.rb | 239 + .../billing/gateways/trust_commerce.rb | 421 + .../billing/gateways/usa_epay.rb | 25 + .../billing/gateways/usa_epay_advanced.rb | 1501 ++++ .../billing/gateways/usa_epay_transaction.rb | 201 + .../billing/gateways/verifi.rb | 232 + .../billing/gateways/viaklix.rb | 189 + .../billing/gateways/vindicia.rb | 361 + .../billing/gateways/webpay.rb | 53 + .../billing/gateways/wirecard.rb | 313 + .../billing/gateways/worldpay.rb | 302 + .../active_merchant/billing/integrations.rb | 17 + .../billing/integrations/a1agregator.rb | 26 + .../integrations/a1agregator/helper.rb | 31 + .../integrations/a1agregator/notification.rb | 186 + .../integrations/a1agregator/status.rb | 38 + .../integrations/action_view_helper.rb | 73 + .../billing/integrations/authorize_net_sim.rb | 38 + .../integrations/authorize_net_sim/helper.rb | 228 + .../authorize_net_sim/notification.rb | 340 + .../billing/integrations/bogus.rb | 23 + .../billing/integrations/bogus/helper.rb | 17 + .../integrations/bogus/notification.rb | 11 + .../billing/integrations/bogus/return.rb | 10 + .../billing/integrations/chronopay.rb | 23 + .../billing/integrations/chronopay/helper.rb | 120 + .../integrations/chronopay/notification.rb | 158 + .../billing/integrations/chronopay/return.rb | 10 + .../billing/integrations/direc_pay.rb | 41 + .../billing/integrations/direc_pay/helper.rb | 200 + .../integrations/direc_pay/notification.rb | 76 + .../billing/integrations/direc_pay/return.rb | 32 + .../billing/integrations/direc_pay/status.rb | 37 + .../billing/integrations/directebanking.rb | 47 + .../integrations/directebanking/helper.rb | 90 + .../directebanking/notification.rb | 120 + .../integrations/directebanking/return.rb | 11 + .../billing/integrations/dotpay.rb | 22 + .../billing/integrations/dotpay/helper.rb | 77 + .../integrations/dotpay/notification.rb | 86 + .../billing/integrations/dotpay/return.rb | 11 + .../billing/integrations/dwolla.rb | 23 + .../billing/integrations/dwolla/common.rb | 21 + .../billing/integrations/dwolla/helper.rb | 40 + .../integrations/dwolla/notification.rb | 60 + .../billing/integrations/dwolla/return.rb | 46 + .../billing/integrations/e_payment_plans.rb | 48 + .../integrations/e_payment_plans/helper.rb | 34 + .../e_payment_plans/notification.rb | 84 + .../billing/integrations/easy_pay.rb | 30 + .../billing/integrations/easy_pay/common.rb | 40 + .../billing/integrations/easy_pay/helper.rb | 36 + .../integrations/easy_pay/notification.rb | 59 + .../billing/integrations/epay.rb | 21 + .../billing/integrations/epay/helper.rb | 55 + .../billing/integrations/epay/notification.rb | 110 + .../billing/integrations/first_data.rb | 38 + .../billing/integrations/first_data/helper.rb | 63 + .../integrations/first_data/notification.rb | 56 + .../billing/integrations/gestpay.rb | 25 + .../billing/integrations/gestpay/common.rb | 42 + .../billing/integrations/gestpay/helper.rb | 70 + .../integrations/gestpay/notification.rb | 85 + .../billing/integrations/gestpay/return.rb | 10 + .../billing/integrations/helper.rb | 117 + .../billing/integrations/hi_trust.rb | 27 + .../billing/integrations/hi_trust/helper.rb | 58 + .../integrations/hi_trust/notification.rb | 59 + .../billing/integrations/hi_trust/return.rb | 67 + .../billing/integrations/liqpay.rb | 30 + .../billing/integrations/liqpay/helper.rb | 43 + .../integrations/liqpay/notification.rb | 89 + .../billing/integrations/liqpay/return.rb | 83 + .../billing/integrations/maksuturva.rb | 86 + .../billing/integrations/maksuturva/helper.rb | 119 + .../integrations/maksuturva/notification.rb | 48 + .../billing/integrations/moneybookers.rb | 26 + .../integrations/moneybookers/helper.rb | 75 + .../integrations/moneybookers/notification.rb | 129 + .../billing/integrations/nochex.rb | 88 + .../billing/integrations/nochex/helper.rb | 68 + .../integrations/nochex/notification.rb | 94 + .../billing/integrations/nochex/return.rb | 10 + .../billing/integrations/notification.rb | 71 + .../billing/integrations/paxum.rb | 44 + .../billing/integrations/paxum/common.rb | 24 + .../billing/integrations/paxum/helper.rb | 42 + .../integrations/paxum/notification.rb | 33 + .../billing/integrations/pay_fast.rb | 70 + .../billing/integrations/pay_fast/common.rb | 42 + .../billing/integrations/pay_fast/helper.rb | 50 + .../integrations/pay_fast/notification.rb | 134 + .../billing/integrations/pay_fast/return.rb | 10 + .../billing/integrations/payflow_link.rb | 21 + .../integrations/payflow_link/helper.rb | 116 + .../integrations/payflow_link/notification.rb | 78 + .../billing/integrations/paypal.rb | 39 + .../billing/integrations/paypal/helper.rb | 119 + .../integrations/paypal/notification.rb | 227 + .../billing/integrations/paypal/return.rb | 10 + .../integrations/paypal_payments_advanced.rb | 20 + .../paypal_payments_advanced/helper.rb | 15 + .../billing/integrations/paysbuy.rb | 36 + .../billing/integrations/paysbuy/helper.rb | 15 + .../integrations/paysbuy/notification.rb | 28 + .../billing/integrations/payu_in.rb | 43 + .../billing/integrations/payu_in/helper.rb | 74 + .../integrations/payu_in/notification.rb | 167 + .../billing/integrations/payu_in/return.rb | 53 + .../billing/integrations/pxpay.rb | 31 + .../billing/integrations/pxpay/helper.rb | 111 + .../integrations/pxpay/notification.rb | 157 + .../billing/integrations/pxpay/return.rb | 25 + .../billing/integrations/quickpay.rb | 21 + .../billing/integrations/quickpay/helper.rb | 74 + .../integrations/quickpay/notification.rb | 137 + .../billing/integrations/rbkmoney.rb | 17 + .../billing/integrations/rbkmoney/helper.rb | 23 + .../integrations/rbkmoney/notification.rb | 91 + .../billing/integrations/return.rb | 42 + .../billing/integrations/robokassa.rb | 49 + .../billing/integrations/robokassa/common.rb | 19 + .../billing/integrations/robokassa/helper.rb | 50 + .../integrations/robokassa/notification.rb | 55 + .../billing/integrations/robokassa/return.rb | 17 + .../billing/integrations/sage_pay_form.rb | 37 + .../integrations/sage_pay_form/encryption.rb | 33 + .../integrations/sage_pay_form/helper.rb | 136 + .../sage_pay_form/notification.rb | 210 + .../integrations/sage_pay_form/return.rb | 31 + .../billing/integrations/two_checkout.rb | 44 + .../integrations/two_checkout/helper.rb | 91 + .../integrations/two_checkout/notification.rb | 139 + .../integrations/two_checkout/return.rb | 17 + .../billing/integrations/valitor.rb | 33 + .../billing/integrations/valitor/helper.rb | 86 + .../integrations/valitor/notification.rb | 13 + .../integrations/valitor/response_fields.rb | 97 + .../billing/integrations/valitor/return.rb | 13 + .../billing/integrations/verkkomaksut.rb | 20 + .../integrations/verkkomaksut/helper.rb | 87 + .../integrations/verkkomaksut/notification.rb | 59 + .../billing/integrations/web_pay.rb | 45 + .../billing/integrations/web_pay/common.rb | 50 + .../billing/integrations/web_pay/helper.rb | 68 + .../integrations/web_pay/notification.rb | 51 + .../billing/integrations/webmoney.rb | 43 + .../billing/integrations/webmoney/common.rb | 17 + .../billing/integrations/webmoney/helper.rb | 39 + .../integrations/webmoney/notification.rb | 43 + .../billing/integrations/world_pay.rb | 34 + .../billing/integrations/world_pay/helper.rb | 100 + .../integrations/world_pay/notification.rb | 160 + .../lib/active_merchant/billing/response.rb | 77 + .../lib/active_merchant/version.rb | 3 + .../lib/activemerchant.rb | 1 + .../lib/support/gateway_support.rb | 65 + .../lib/support/outbound_hosts.rb | 25 + .../lib/support/ssl_verify.rb | 93 + 327 files changed, 52355 insertions(+), 18 deletions(-) create mode 100644 vendor/gems/active_utils-1.0.5/.gitignore create mode 100644 vendor/gems/active_utils-1.0.5/Gemfile create mode 100644 vendor/gems/active_utils-1.0.5/MIT-LICENSE create mode 100644 vendor/gems/active_utils-1.0.5/README.md create mode 100644 vendor/gems/active_utils-1.0.5/Rakefile create mode 100644 vendor/gems/active_utils-1.0.5/active_utils.gemspec create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/connection.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/country.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/error.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/network_connection_retries.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/post_data.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/posts_data.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/requires_parameters.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/utils.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/common/validateable.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/active_utils/version.rb create mode 100644 vendor/gems/active_utils-1.0.5/lib/certs/cacert.pem create mode 100644 vendor/gems/active_utils-1.0.5/test/test_helper.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/connection_test.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/country_code_test.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/country_test.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/network_connection_retries_test.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/post_data_test.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/posts_data_test.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/utils_test.rb create mode 100644 vendor/gems/active_utils-1.0.5/test/unit/validateable_test.rb create mode 100644 vendor/gems/activemerchant-1.33.0/CHANGELOG create mode 100644 vendor/gems/activemerchant-1.33.0/CONTRIBUTORS create mode 100644 vendor/gems/activemerchant-1.33.0/MIT-LICENSE create mode 100644 vendor/gems/activemerchant-1.33.0/README.md create mode 100644 vendor/gems/activemerchant-1.33.0/gem-public_cert.pem create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/avs_result.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/base.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/check.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_formatting.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_methods.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/cvv_result.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/expiry_date.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateway.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net_cim.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/balanced.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/banwire.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/barclays_epdq.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream_interac.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/blue_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/bogus.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree/braintree_common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_blue.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_orange.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_save.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream_modern.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cc5.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/certo_direct.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cyber_source.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/data_cash.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/efsnet.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/elavon.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/epay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/evo_ca.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_managed.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_rapid.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/exact.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/fat_zebra.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/federated_canada.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/finansbank.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/first_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/firstdata_e4.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/garanti.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/hdfc.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_base.rb create mode 100755 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_response.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal_rabobank.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/inspire.rb create mode 100755 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/instapay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/iridium.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/itransact.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/jetpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/linkpoint.rb create mode 100755 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/litle.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_e_solutions.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_ware.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_warrior.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/mercury.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/metrics_global.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs/migs_codes.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments_cim.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris_us.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nab_transact.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/net_registry.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netaxept.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netbilling.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nmi.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ogone.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/optimal_payment.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/avs_result.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_gate_xml.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_junction.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_secure.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paybox_direct.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_response.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express_uk.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_uk.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payment_express.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paymill.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_bogus.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_ca.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_digital_goods.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express_common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paystation.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payway.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pin.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/plugnpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psigate.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psl_card.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/qbms.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quantum.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quickpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/realex.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/redsys.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_core.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sallie_mae.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/samurai.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_net.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_au.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_tech.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/skip_jack.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/smart_ps.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/spreedly_core.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/stripe.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trans_first.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transax.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transnational.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trust_commerce.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_advanced.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_transaction.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/verifi.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/viaklix.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/vindicia.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/webpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/wirecard.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/worldpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/status.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/action_view_helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/status.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/notification.rb create mode 100755 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in.rb create mode 100755 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/helper.rb create mode 100755 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/notification.rb create mode 100755 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/response_fields.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/return.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/common.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/helper.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/notification.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/response.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/active_merchant/version.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/activemerchant.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/support/gateway_support.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/support/outbound_hosts.rb create mode 100644 vendor/gems/activemerchant-1.33.0/lib/support/ssl_verify.rb diff --git a/Gemfile b/Gemfile index d8d5911e2..0a2115ddc 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,14 @@ gem 'haml' gem 'cancan' -gem 'activemerchant' +# 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' + # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' diff --git a/Gemfile.lock b/Gemfile.lock index 8a2ad9de5..9bc276930 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,15 @@ +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: @@ -14,17 +26,6 @@ GEM rack-cache (~> 1.2) rack-test (~> 0.6.1) sprockets (~> 2.2.1) - active_utils (1.0.5) - activesupport (>= 2.3.11) - i18n - activemerchant (1.32.1) - active_utils (>= 1.0.2) - activesupport (>= 2.3.14) - builder (>= 2.0.0) - i18n - json (>= 1.5.1) - money - nokogiri activemodel (3.2.13) activesupport (= 3.2.13) builder (~> 3.0.0) @@ -127,8 +128,6 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.21) - money (5.1.1) - i18n (~> 0.6.0) multi_json (1.7.1) net-scp (1.1.0) net-ssh (>= 2.6.5) @@ -245,7 +244,8 @@ PLATFORMS ruby DEPENDENCIES - activemerchant + active_utils (= 1.0.5)! + activemerchant (= 1.33.0)! bluecloth bootstrap-datepicker-rails bundler (>= 1.1.5) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index efc21acaf..f1ef42906 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -51,7 +51,12 @@ class OrdersController < ApplicationController @order.completed_at = Time.zone.now @order.save - @order.record_paypal_details(params[:token]) + if (params[:token]) + @order.record_paypal_details(params[:token]) + else + flash[:alert] = "PayPal didn't return a token for your order. Please notify support." + end + @order.update_account # apply paid account benefits, etc. respond_to do |format| diff --git a/config/environments/test.rb b/config/environments/test.rb index e570d4630..6718e2e84 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -48,8 +48,8 @@ Growstuff::Application.configure do config.after_initialize do ActiveMerchant::Billing::Base.mode = :test - ::STANDARD_GATEWAY = ActiveMerchant::Billing::BogusGateway.new - ::EXPRESS_GATEWAY = ActiveMerchant::Billing::BogusGateway.new + ::STANDARD_GATEWAY = ActiveMerchant::Billing::PaypalBogusGateway.new + ::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalBogusGateway.new end end diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb index 3d209f8e4..75e7b4346 100644 --- a/spec/controllers/orders_controller_spec.rb +++ b/spec/controllers/orders_controller_spec.rb @@ -32,6 +32,17 @@ describe OrdersController do end end + describe "GET checkout" do + it "redirects to Paypal" do + member = FactoryGirl.create(:member) + sign_in member + order = Order.create!(:member_id => member.id) + get :checkout, {:id => order.to_param} + response.status.should eq 302 + response.redirect_url.should match /paypal\.com/ + end + end + describe "GET complete" do it "assigns the requested order as @order" do member = FactoryGirl.create(:member) diff --git a/vendor/gems/active_utils-1.0.5/.gitignore b/vendor/gems/active_utils-1.0.5/.gitignore new file mode 100644 index 000000000..2ac8271fc --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/.gitignore @@ -0,0 +1,5 @@ +pkg/* +*.gem +.bundle +.DS_Store +Gemfile.lock diff --git a/vendor/gems/active_utils-1.0.5/Gemfile b/vendor/gems/active_utils-1.0.5/Gemfile new file mode 100644 index 000000000..c80ee3697 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/Gemfile @@ -0,0 +1,3 @@ +source "http://rubygems.org" + +gemspec diff --git a/vendor/gems/active_utils-1.0.5/MIT-LICENSE b/vendor/gems/active_utils-1.0.5/MIT-LICENSE new file mode 100644 index 000000000..29230683b --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/MIT-LICENSE @@ -0,0 +1,20 @@ +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. diff --git a/vendor/gems/active_utils-1.0.5/README.md b/vendor/gems/active_utils-1.0.5/README.md new file mode 100644 index 000000000..d942d0aab --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/README.md @@ -0,0 +1,15 @@ +# 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 diff --git a/vendor/gems/active_utils-1.0.5/Rakefile b/vendor/gems/active_utils-1.0.5/Rakefile new file mode 100644 index 000000000..0389bb0a8 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/Rakefile @@ -0,0 +1,13 @@ +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" diff --git a/vendor/gems/active_utils-1.0.5/active_utils.gemspec b/vendor/gems/active_utils-1.0.5/active_utils.gemspec new file mode 100644 index 000000000..6e887da9b --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/active_utils.gemspec @@ -0,0 +1,26 @@ +# -*- 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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils.rb new file mode 100644 index 000000000..86665aa77 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils.rb @@ -0,0 +1,20 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/connection.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/connection.rb new file mode 100644 index 000000000..8f3300159 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/connection.rb @@ -0,0 +1,147 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/country.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/country.rb new file mode 100644 index 000000000..a2ed3a310 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/country.rb @@ -0,0 +1,328 @@ +#!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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/error.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/error.rb new file mode 100644 index 000000000..36c5e38c7 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/error.rb @@ -0,0 +1,26 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/network_connection_retries.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/network_connection_retries.rb new file mode 100644 index 000000000..df53af3b8 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/network_connection_retries.rb @@ -0,0 +1,58 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/post_data.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/post_data.rb new file mode 100644 index 000000000..192d08e85 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/post_data.rb @@ -0,0 +1,24 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/posts_data.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/posts_data.rb new file mode 100644 index 000000000..584f19435 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/posts_data.rb @@ -0,0 +1,69 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/requires_parameters.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/requires_parameters.rb new file mode 100644 index 000000000..4e1f2e1aa --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/requires_parameters.rb @@ -0,0 +1,16 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/utils.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/utils.rb new file mode 100644 index 000000000..fddee5e06 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/utils.rb @@ -0,0 +1,20 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/common/validateable.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/validateable.rb new file mode 100644 index 000000000..8e1d2c909 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/common/validateable.rb @@ -0,0 +1,81 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/lib/active_utils/version.rb b/vendor/gems/active_utils-1.0.5/lib/active_utils/version.rb new file mode 100644 index 000000000..d4504aa02 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/active_utils/version.rb @@ -0,0 +1,3 @@ +module ActiveUtils + VERSION = "1.0.5" +end diff --git a/vendor/gems/active_utils-1.0.5/lib/certs/cacert.pem b/vendor/gems/active_utils-1.0.5/lib/certs/cacert.pem new file mode 100644 index 000000000..16494cbf9 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/lib/certs/cacert.pem @@ -0,0 +1,7815 @@ +## +## ca-bundle.crt -- Bundle of CA Root Certificates +## Converted by the service run by Daniel Stenberg +## URL: http://curl.haxx.se/docs/caextract.html +## Converted at: Wed Feb 28 04:14:50 UTC 2007 +CVS_ID "@(#) $RCSfile: certdata.txt,v $ $Revision: 1.37 $ $Date: 2005/04/18 16:08:07 $" +## +## This is a bundle of X.509 certificates of public Certificate Authorities +## (CA). These were automatically extracted from Mozilla's root certificates +## file (certdata.txt). This file can be found in the mozilla source tree: +## '/mozilla/security/nss/lib/ckfw/builtins/certdata.txt' +## +## This file was automatically converted and downloaded through the services +## provided by http://curl.haxx.se/docs/caextract.html +## +## +## The contents of this file are subject to the Mozilla Public License Version +## 1.1 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## http://www.mozilla.org/MPL/ +## +## Software distributed under the License is distributed on an "AS IS" basis, +## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +## for the specific language governing rights and limitations under the +## License. +## +## The Original Code is the Netscape security libraries. +## +## The Initial Developer of the Original Code is Netscape Communications +## Corporation. Portions created by Netscape are Copyright (C) 1994-2000 +## Netscape Communications Corporation. All Rights Reserved. +## +## Contributor(s): +## +## Alternatively, the contents of this file may be used under the terms of the +## GNU General Public License Version 2 or later (the "GPL"), in which case +## the provisions of the GPL are applicable instead of those above. If you +## wish to allow use of your version of this file only under the terms of the +## GPL and not to allow others to use your version of this file under the MPL, +## indicate your decision by deleting the provisions above and replace them +## with the notice and other provisions required by the GPL. If you do not +## delete the provisions above, a recipient may use your version of this file +## under either the MPL or the GPL. +## + +Verisign/RSA Secure Server CA +============================= + +MD5 Fingerprint=74:7B:82:03:43:F0:00:9E:6B:B3:EC:47:BF:85:A5:93 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 02:ad:66:7e:4e:45:fe:5e:57:6f:3c:98:19:5e:dd:c0 + Signature Algorithm: md2WithRSAEncryption + Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority + Validity + Not Before: Nov 9 00:00:00 1994 GMT + Not After : Jan 7 23:59:59 2010 GMT + Subject: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1000 bit) + Modulus (1000 bit): + 00:92:ce:7a:c1:ae:83:3e:5a:aa:89:83:57:ac:25: + 01:76:0c:ad:ae:8e:2c:37:ce:eb:35:78:64:54:03: + e5:84:40:51:c9:bf:8f:08:e2:8a:82:08:d2:16:86: + 37:55:e9:b1:21:02:ad:76:68:81:9a:05:a2:4b:c9: + 4b:25:66:22:56:6c:88:07:8f:f7:81:59:6d:84:07: + 65:70:13:71:76:3e:9b:77:4c:e3:50:89:56:98:48: + b9:1d:a7:29:1a:13:2e:4a:11:59:9c:1e:15:d5:49: + 54:2c:73:3a:69:82:b1:97:39:9c:6d:70:67:48:e5: + dd:2d:d6:c8:1e:7b + Exponent: 65537 (0x10001) + Signature Algorithm: md2WithRSAEncryption + 65:dd:7e:e1:b2:ec:b0:e2:3a:e0:ec:71:46:9a:19:11:b8:d3: + c7:a0:b4:03:40:26:02:3e:09:9c:e1:12:b3:d1:5a:f6:37:a5: + b7:61:03:b6:5b:16:69:3b:c6:44:08:0c:88:53:0c:6b:97:49: + c7:3e:35:dc:6c:b9:bb:aa:df:5c:bb:3a:2f:93:60:b6:a9:4b: + 4d:f2:20:f7:cd:5f:7f:64:7b:8e:dc:00:5c:d7:fa:77:ca:39: + 16:59:6f:0e:ea:d3:b5:83:7f:4d:4d:42:56:76:b4:c9:5f:04: + f8:38:f8:eb:d2:5f:75:5f:cd:7b:fc:e5:8e:80:7c:fc:50 +-----BEGIN CERTIFICATE----- +MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD +VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0 +MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UEBhMCVVMxIDAeBgNV +BAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2Vy +dmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUAA4GJ +ADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII +0haGN1XpsSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphI +uR2nKRoTLkoRWZweFdVJVCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZI +hvcNAQECBQADfgBl3X7hsuyw4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3 +YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc +1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== +-----END CERTIFICATE----- + +ABAecom (sub., Am. Bankers Assn.) Root CA +========================================= + +MD5 Fingerprint=41:B8:07:F7:A8:D1:09:EE:B4:9A:8E:70:4D:FC:1B:78 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + d0:1e:40:90:00:00:46:52:00:00:00:01:00:00:00:04 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=DC, L=Washington, O=ABA.ECOM, INC., CN=ABA.ECOM Root CA/emailAddress=admin@digsigtrust.com + Validity + Not Before: Jul 12 17:33:53 1999 GMT + Not After : Jul 9 17:33:53 2009 GMT + Subject: C=US, ST=DC, L=Washington, O=ABA.ECOM, INC., CN=ABA.ECOM Root CA/emailAddress=admin@digsigtrust.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b1:d3:11:e0:79:55:43:07:08:4c:cb:05:42:00: + e2:0d:83:46:3d:e4:93:ba:b6:06:d3:0d:59:bd:3e: + c1:ce:43:67:01:8a:21:a8:ef:bc:cc:d0:a2:cc:b0: + 55:96:53:84:66:05:00:da:44:49:80:d8:54:0a:a5: + 25:86:94:ed:63:56:ff:70:6c:a3:a1:19:d2:78:be: + 68:2a:44:5e:2f:cf:cc:18:5e:47:bc:3a:b1:46:3d: + 1e:f0:b9:2c:34:5f:8c:7c:4c:08:29:9d:40:55:eb: + 3c:7d:83:de:b5:f0:f7:8a:83:0e:a1:4c:b4:3a:a5: + b3:5f:5a:22:97:ec:19:9b:c1:05:68:fd:e6:b7:a9: + 91:94:2c:e4:78:48:24:1a:25:19:3a:eb:95:9c:39: + 0a:8a:cf:42:b2:f0:1c:d5:5f:fb:6b:ed:68:56:7b: + 39:2c:72:38:b0:ee:93:a9:d3:7b:77:3c:eb:71:03: + a9:38:4a:16:6c:89:2a:ca:da:33:13:79:c2:55:8c: + ed:9c:bb:f2:cb:5b:10:f8:2e:61:35:c6:29:4c:2a: + d0:2a:63:d1:65:59:b4:f8:cd:f9:f4:00:84:b6:57: + 42:85:9d:32:a8:f9:2a:54:fb:ff:78:41:bc:bd:71: + 28:f4:bb:90:bc:ff:96:34:04:e3:45:9e:a1:46:28: + 40:81 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:8 + Signature Algorithm: sha1WithRSAEncryption + 04:6f:25:86:e4:e6:96:27:b4:d9:42:c0:d0:c9:00:b1:7f:54: + 3e:87:b2:6d:24:a9:2f:0a:7e:fd:a4:44:b0:f8:54:07:bd:1b: + 9d:9d:ca:7b:50:24:7b:11:5b:49:a3:a6:bf:12:74:d5:89:b7: + b7:2f:98:64:25:14:b7:61:e9:7f:60:80:6b:d3:64:e8:ab:bd: + 1a:d6:51:fa:c0:b4:5d:77:1a:7f:64:08:5e:79:c6:05:4c:f1: + 7a:dd:4d:7d:ce:e6:48:7b:54:d2:61:92:81:d6:1b:d6:00:f0: + 0e:9e:28:77:a0:4d:88:c7:22:76:19:c3:c7:9e:1b:a6:77:78: + f8:5f:9b:56:d1:f0:f2:17:ac:8e:9d:59:e6:1f:fe:57:b6:d9: + 5e:e1:5d:9f:45:ec:61:68:19:41:e1:b2:20:26:fe:5a:30:76: + 24:ff:40:72:3c:79:9f:7c:22:48:ab:46:cd:db:b3:86:2c:8f: + bf:05:41:d3:c1:e3:14:e3:41:17:26:d0:7c:a7:71:4c:19:e8: + 4a:0f:72:58:31:7d:ec:60:7a:a3:22:28:bd:19:24:60:3f:3b: + 87:73:c0:6b:e4:cb:ae:b7:ab:25:43:b2:55:2d:7b:ab:06:0e: + 75:5d:34:e5:5d:73:6d:9e:b2:75:40:a5:59:c9:4f:31:71:88: + d9:88:7f:54 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIRANAeQJAAAEZSAAAAAQAAAAQwDQYJKoZIhvcNAQEFBQAw +gYkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJEQzETMBEGA1UEBxMKV2FzaGluZ3Rv +bjEXMBUGA1UEChMOQUJBLkVDT00sIElOQy4xGTAXBgNVBAMTEEFCQS5FQ09NIFJv +b3QgQ0ExJDAiBgkqhkiG9w0BCQEWFWFkbWluQGRpZ3NpZ3RydXN0LmNvbTAeFw05 +OTA3MTIxNzMzNTNaFw0wOTA3MDkxNzMzNTNaMIGJMQswCQYDVQQGEwJVUzELMAkG +A1UECBMCREMxEzARBgNVBAcTCldhc2hpbmd0b24xFzAVBgNVBAoTDkFCQS5FQ09N +LCBJTkMuMRkwFwYDVQQDExBBQkEuRUNPTSBSb290IENBMSQwIgYJKoZIhvcNAQkB +FhVhZG1pbkBkaWdzaWd0cnVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCx0xHgeVVDBwhMywVCAOINg0Y95JO6tgbTDVm9PsHOQ2cBiiGo77zM +0KLMsFWWU4RmBQDaREmA2FQKpSWGlO1jVv9wbKOhGdJ4vmgqRF4vz8wYXke8OrFG +PR7wuSw0X4x8TAgpnUBV6zx9g9618PeKgw6hTLQ6pbNfWiKX7BmbwQVo/ea3qZGU +LOR4SCQaJRk665WcOQqKz0Ky8BzVX/tr7WhWezkscjiw7pOp03t3POtxA6k4ShZs +iSrK2jMTecJVjO2cu/LLWxD4LmE1xilMKtAqY9FlWbT4zfn0AIS2V0KFnTKo+SpU ++/94Qby9cSj0u5C8/5Y0BONFnqFGKECBAgMBAAGjFjAUMBIGA1UdEwEB/wQIMAYB +Af8CAQgwDQYJKoZIhvcNAQEFBQADggEBAARvJYbk5pYntNlCwNDJALF/VD6Hsm0k +qS8Kfv2kRLD4VAe9G52dyntQJHsRW0mjpr8SdNWJt7cvmGQlFLdh6X9ggGvTZOir +vRrWUfrAtF13Gn9kCF55xgVM8XrdTX3O5kh7VNJhkoHWG9YA8A6eKHegTYjHInYZ +w8eeG6Z3ePhfm1bR8PIXrI6dWeYf/le22V7hXZ9F7GFoGUHhsiAm/lowdiT/QHI8 +eZ98IkirRs3bs4Ysj78FQdPB4xTjQRcm0HyncUwZ6EoPclgxfexgeqMiKL0ZJGA/ +O4dzwGvky663qyVDslUte6sGDnVdNOVdc22esnVApVnJTzFxiNmIf1Q= +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 1 +======================================= + +MD5 Fingerprint=25:7A:BA:83:2E:B6:A2:0B:DA:FE:F5:02:0F:08:D7:AD +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 913315222 (0x36701596) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=Digital Signature Trust Co., OU=DSTCA E1 + Validity + Not Before: Dec 10 18:10:23 1998 GMT + Not After : Dec 10 18:40:23 2018 GMT + Subject: C=US, O=Digital Signature Trust Co., OU=DSTCA E1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:a0:6c:81:a9:cf:34:1e:24:dd:fe:86:28:cc:de: + 83:2f:f9:5e:d4:42:d2:e8:74:60:66:13:98:06:1c: + a9:51:12:69:6f:31:55:b9:49:72:00:08:7e:d3:a5: + 62:44:37:24:99:8f:d9:83:48:8f:99:6d:95:13:bb: + 43:3b:2e:49:4e:88:37:c1:bb:58:7f:fe:e1:bd:f8: + bb:61:cd:f3:47:c0:99:a6:f1:f3:91:e8:78:7c:00: + cb:61:c9:44:27:71:69:55:4a:7e:49:4d:ed:a2:a3: + be:02:4c:00:ca:02:a8:ee:01:02:31:64:0f:52:2d: + 13:74:76:36:b5:7a:b4:2d:71 + Exponent: 3 (0x3) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/C=US/O=Digital Signature Trust Co./OU=DSTCA E1/CN=CRL1 + + X509v3 Private Key Usage Period: + Not Before: Dec 10 18:10:23 1998 GMT, Not After: Dec 10 18:10:23 2018 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:6A:79:7E:91:69:46:18:13:0A:02:77:A5:59:5B:60:98:25:0E:A2:F8 + + X509v3 Subject Key Identifier: + 6A:79:7E:91:69:46:18:13:0A:02:77:A5:59:5B:60:98:25:0E:A2:F8 + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0 +..V4.0.... + Signature Algorithm: sha1WithRSAEncryption + 22:12:d8:7a:1d:dc:81:06:b6:09:65:b2:87:c8:1f:5e:b4:2f: + e9:c4:1e:f2:3c:c1:bb:04:90:11:4a:83:4e:7e:93:b9:4d:42: + c7:92:26:a0:5c:34:9a:38:72:f8:fd:6b:16:3e:20:ee:82:8b: + 31:2a:93:36:85:23:88:8a:3c:03:68:d3:c9:09:0f:4d:fc:6c: + a4:da:28:72:93:0e:89:80:b0:7d:fe:80:6f:65:6d:18:33:97: + 8b:c2:6b:89:ee:60:3d:c8:9b:ef:7f:2b:32:62:73:93:cb:3c: + e3:7b:e2:76:78:45:bc:a1:93:04:bb:86:9f:3a:5b:43:7a:c3: + 8a:65 +-----BEGIN CERTIFICATE----- +MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV +UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL +EwhEU1RDQSBFMTAeFw05ODEyMTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJ +BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x +ETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCg +bIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJENySZ +j9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlV +Sn5JTe2io74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCG +SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx +JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI +RFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMTAxODEw +MjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFGp5 +fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i ++DAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG +SIb3DQEBBQUAA4GBACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lN +QseSJqBcNJo4cvj9axY+IO6CizEqkzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+ +gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4RbyhkwS7hp86W0N6w4pl +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 3 +======================================= + +MD5 Fingerprint=93:C2:8E:11:7B:D4:F3:03:19:BD:28:75:13:4A:45:4A +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 913232846 (0x366ed3ce) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=Digital Signature Trust Co., OU=DSTCA E2 + Validity + Not Before: Dec 9 19:17:26 1998 GMT + Not After : Dec 9 19:47:26 2018 GMT + Subject: C=US, O=Digital Signature Trust Co., OU=DSTCA E2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:bf:93:8f:17:92:ef:33:13:18:eb:10:7f:4e:16: + bf:ff:06:8f:2a:85:bc:5e:f9:24:a6:24:88:b6:03: + b7:c1:c3:5f:03:5b:d1:6f:ae:7e:42:ea:66:23:b8: + 63:83:56:fb:28:2d:e1:38:8b:b4:ee:a8:01:e1:ce: + 1c:b6:88:2a:22:46:85:fb:9f:a7:70:a9:47:14:3f: + ce:de:65:f0:a8:71:f7:4f:26:6c:8c:bc:c6:b5:ef: + de:49:27:ff:48:2a:7d:e8:4d:03:cc:c7:b2:52:c6: + 17:31:13:3b:b5:4d:db:c8:c4:f6:c3:0f:24:2a:da: + 0c:9d:e7:91:5b:80:cd:94:9d + Exponent: 3 (0x3) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/C=US/O=Digital Signature Trust Co./OU=DSTCA E2/CN=CRL1 + + X509v3 Private Key Usage Period: + Not Before: Dec 9 19:17:26 1998 GMT, Not After: Dec 9 19:17:26 2018 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:1E:82:4D:28:65:80:3C:C9:41:6E:AC:35:2E:5A:CB:DE:EE:F8:39:5B + + X509v3 Subject Key Identifier: + 1E:82:4D:28:65:80:3C:C9:41:6E:AC:35:2E:5A:CB:DE:EE:F8:39:5B + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0 +..V4.0.... + Signature Algorithm: sha1WithRSAEncryption + 47:8d:83:ad:62:f2:db:b0:9e:45:22:05:b9:a2:d6:03:0e:38: + 72:e7:9e:fc:7b:e6:93:b6:9a:a5:a2:94:c8:34:1d:91:d1:c5: + d7:f4:0a:25:0f:3d:78:81:9e:0f:b1:67:c4:90:4c:63:dd:5e: + a7:e2:ba:9f:f5:f7:4d:a5:31:7b:9c:29:2d:4c:fe:64:3e:ec: + b6:53:fe:ea:9b:ed:82:db:74:75:4b:07:79:6e:1e:d8:19:83: + 73:de:f5:3e:d0:b5:de:e7:4b:68:7d:43:2e:2a:20:e1:7e:a0: + 78:44:9e:08:f5:98:f9:c7:7f:1b:1b:d6:06:20:02:58:a1:c3: + a2:03 +-----BEGIN CERTIFICATE----- +MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV +UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL +EwhEU1RDQSBFMjAeFw05ODEyMDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJ +BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x +ETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC/ +k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGODVvso +LeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3o +TQPMx7JSxhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCG +SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx +JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI +RFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMDkxOTE3 +MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFB6C +TShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5 +WzAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG +SIb3DQEBBQUAA4GBAEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHR +xdf0CiUPPXiBng+xZ8SQTGPdXqfiup/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVL +B3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1mPnHfxsb1gYgAlihw6ID +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 2 +======================================= + +MD5 Fingerprint=6C:C9:A7:6E:47:F1:0C:E3:53:3B:78:4C:4D:C2:6A:C5 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + d0:1e:40:8b:00:00:02:7c:00:00:00:02:00:00:00:01 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=us, ST=Utah, L=Salt Lake City, O=Digital Signature Trust Co., OU=DSTCA X1, CN=DST RootCA X1/emailAddress=ca@digsigtrust.com + Validity + Not Before: Dec 1 18:18:55 1998 GMT + Not After : Nov 28 18:18:55 2008 GMT + Subject: C=us, ST=Utah, L=Salt Lake City, O=Digital Signature Trust Co., OU=DSTCA X1, CN=DST RootCA X1/emailAddress=ca@digsigtrust.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:d2:c6:26:b6:e7:a5:3d:c1:c4:68:d5:50:6f:53: + c5:6f:49:13:09:b8:af:2c:48:8d:14:6a:a3:17:5f: + 5a:f9:d3:2e:75:2f:d8:28:62:d1:93:2f:fc:4d:d4: + ab:87:e5:08:c7:99:e7:92:3f:75:bd:eb:25:b4:15: + c1:9b:19:3d:d2:44:8d:d7:74:20:6d:37:02:8f:69: + 93:5b:8a:c4:19:9d:f4:b2:0e:fc:16:6c:b9:b1:05: + 92:83:d1:85:2c:60:94:3e:45:55:a0:d9:ab:08:21: + e6:60:e8:3b:74:f2:99:50:51:68:d0:03:2d:b1:80: + be:a3:d8:52:b0:44:cd:43:4a:70:8e:58:85:95:e1: + 4e:2c:d6:2d:41:6f:d6:84:e7:c8:98:44:ca:47:db: + 2c:24:a5:69:26:cf:6b:b8:27:62:c3:f4:c9:7a:92: + 23:ed:13:67:82:ae:45:2e:45:e5:7e:72:3f:85:9d: + 94:62:10:e6:3c:91:a1:ad:77:00:e0:15:ec:f3:84: + 80:72:7a:8e:6e:60:97:c7:24:59:10:34:83:5b:e1: + a5:a4:69:b6:57:35:1c:78:59:c6:d3:2f:3a:73:67: + ee:94:ca:04:13:05:62:06:70:23:b3:f4:7c:ee:45: + d9:64:0b:5b:49:aa:a4:43:ce:26:c4:44:12:6c:b8: + dd:79 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + a2:37:b2:3f:69:fb:d7:86:79:54:49:31:95:33:2b:f3:d1:09: + 14:49:62:60:86:a5:b0:11:e2:50:c2:1d:06:57:3e:2d:e8:33: + 64:be:9b:aa:ad:5f:1b:4d:d4:99:95:a2:8b:9a:c9:62:72:b5: + 69:ea:d9:58:ab:35:ed:15:a2:43:d6:b6:bc:07:79:65:64:73: + 7d:d7:79:ca:7b:d5:5a:51:c6:e1:53:04:96:8d:38:cf:a3:17: + ac:39:71:6b:01:c3:8b:53:3c:63:e9:ee:79:c0:e4:be:92:32: + 64:7a:b3:1f:97:94:62:bd:ea:b2:20:15:95:fb:97:f2:78:2f: + 63:36:40:38:e3:46:0f:1d:dd:ac:95:ca:e7:4b:90:7b:b1:4b: + a9:d4:c5:eb:9a:da:aa:d5:a3:94:14:46:8d:2d:1f:f3:3a:d6: + 93:3a:f6:3e:79:fc:e8:e6:b0:75:ed:ee:3d:c9:70:c7:5d:aa: + 81:4b:46:25:1c:c7:6c:15:e3:95:4e:0f:aa:32:37:94:0a:17: + 24:92:13:84:58:d2:63:6f:2b:f7:e6:5b:62:0b:13:17:b0:0d: + 52:4c:fe:fe:6f:5c:e2:91:6e:1d:fd:a4:62:d7:68:fa:8e:7a: + 4f:d2:08:da:93:dc:f0:92:11:7a:d0:dc:72:93:0c:73:93:62: + 85:68:d0:f4 +-----BEGIN CERTIFICATE----- +MIID2DCCAsACEQDQHkCLAAACfAAAAAIAAAABMA0GCSqGSIb3DQEBBQUAMIGpMQsw +CQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMOU2FsdCBMYWtlIENp +dHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UE +CxMIRFNUQ0EgWDExFjAUBgNVBAMTDURTVCBSb290Q0EgWDExITAfBgkqhkiG9w0B +CQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTAeFw05ODEyMDExODE4NTVaFw0wODExMjgx +ODE4NTVaMIGpMQswCQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMO +U2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0 +IENvLjERMA8GA1UECxMIRFNUQ0EgWDExFjAUBgNVBAMTDURTVCBSb290Q0EgWDEx +ITAfBgkqhkiG9w0BCQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANLGJrbnpT3BxGjVUG9TxW9JEwm4ryxIjRRqoxdf +WvnTLnUv2Chi0ZMv/E3Uq4flCMeZ55I/db3rJbQVwZsZPdJEjdd0IG03Ao9pk1uK +xBmd9LIO/BZsubEFkoPRhSxglD5FVaDZqwgh5mDoO3TymVBRaNADLbGAvqPYUrBE +zUNKcI5YhZXhTizWLUFv1oTnyJhEykfbLCSlaSbPa7gnYsP0yXqSI+0TZ4KuRS5F +5X5yP4WdlGIQ5jyRoa13AOAV7POEgHJ6jm5gl8ckWRA0g1vhpaRptlc1HHhZxtMv +OnNn7pTKBBMFYgZwI7P0fO5F2WQLW0mqpEPOJsREEmy43XkCAwEAATANBgkqhkiG +9w0BAQUFAAOCAQEAojeyP2n714Z5VEkxlTMr89EJFEliYIalsBHiUMIdBlc+Legz +ZL6bqq1fG03UmZWii5rJYnK1aerZWKs17RWiQ9a2vAd5ZWRzfdd5ynvVWlHG4VME +lo04z6MXrDlxawHDi1M8Y+nuecDkvpIyZHqzH5eUYr3qsiAVlfuX8ngvYzZAOONG +Dx3drJXK50uQe7FLqdTF65raqtWjlBRGjS0f8zrWkzr2Pnn86Oawde3uPclwx12q +gUtGJRzHbBXjlU4PqjI3lAoXJJIThFjSY28r9+ZbYgsTF7ANUkz+/m9c4pFuHf2k +Ytdo+o56T9II2pPc8JIRetDccpMMc5NihWjQ9A== +-----END CERTIFICATE----- + +Digital Signature Trust Co. Global CA 4 +======================================= + +MD5 Fingerprint=CD:3B:3D:62:5B:09:B8:09:36:87:9E:12:2F:71:64:BA +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + d0:1e:40:8b:00:00:77:6d:00:00:00:01:00:00:00:04 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=us, ST=Utah, L=Salt Lake City, O=Digital Signature Trust Co., OU=DSTCA X2, CN=DST RootCA X2/emailAddress=ca@digsigtrust.com + Validity + Not Before: Nov 30 22:46:16 1998 GMT + Not After : Nov 27 22:46:16 2008 GMT + Subject: C=us, ST=Utah, L=Salt Lake City, O=Digital Signature Trust Co., OU=DSTCA X2, CN=DST RootCA X2/emailAddress=ca@digsigtrust.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:dc:75:f0:8c:c0:75:96:9a:c0:62:1f:26:f7:c4: + e1:9a:ea:e0:56:73:5b:99:cd:01:44:a8:08:b6:d5: + a7:da:1a:04:18:39:92:4a:78:a3:81:c2:f5:77:7a: + 50:b4:70:ff:9a:ab:c6:c7:ca:6e:83:4f:42:98:fb: + 26:0b:da:dc:6d:d6:a9:99:55:52:67:e9:28:03:92: + dc:e5:b0:05:9a:0f:15:f9:6b:59:72:56:f2:fa:39: + fc:aa:68:ee:0f:1f:10:83:2f:fc:9d:fa:17:96:dd: + 82:e3:e6:45:7d:c0:4b:80:44:1f:ed:2c:e0:84:fd: + 91:5c:92:54:69:25:e5:62:69:dc:e5:ee:00:52:bd: + 33:0b:ad:75:02:85:a7:64:50:2d:c5:19:19:30:c0: + 26:db:c9:d3:fd:2e:99:ad:59:b5:0b:4d:d4:41:ae: + 85:48:43:59:dc:b7:a8:e2:a2:de:c3:8f:d7:b8:a1: + 62:a6:68:50:52:e4:cf:31:a7:94:85:da:9f:46:32: + 17:56:e5:f2:eb:66:3d:12:ff:43:db:98:ef:77:cf: + cb:81:8d:34:b1:c6:50:4a:26:d1:e4:3e:41:50:af: + 6c:ae:22:34:2e:d5:6b:6e:83:ba:79:b8:76:65:48: + da:09:29:64:63:22:b9:fb:47:76:85:8c:86:44:cb: + 09:db + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + b5:36:0e:5d:e1:61:28:5a:11:65:c0:3f:83:03:79:4d:be:28: + a6:0b:07:02:52:85:cd:f8:91:d0:10:6c:b5:6a:20:5b:1c:90: + d9:30:3c:c6:48:9e:8a:5e:64:f9:a1:71:77:ef:04:27:1f:07: + eb:e4:26:f7:73:74:c9:44:18:1a:66:d3:e0:43:af:91:3b:d1: + cb:2c:d8:74:54:3a:1c:4d:ca:d4:68:cd:23:7c:1d:10:9e:45: + e9:f6:00:6e:a6:cd:19:ff:4f:2c:29:8f:57:4d:c4:77:92:be: + e0:4c:09:fb:5d:44:86:66:21:a8:b9:32:a2:56:d5:e9:8c:83: + 7c:59:3f:c4:f1:0b:e7:9d:ec:9e:bd:9c:18:0e:3e:c2:39:79: + 28:b7:03:0d:08:cb:c6:e7:d9:01:37:50:10:ec:cc:61:16:40: + d4:af:31:74:7b:fc:3f:31:a7:d0:47:73:33:39:1b:cc:4e:6a: + d7:49:83:11:06:fe:eb:82:58:33:32:4c:f0:56:ac:1e:9c:2f: + 56:9a:7b:c1:4a:1c:a5:fd:55:36:ce:fc:96:4d:f4:b0:f0:ec: + b7:6c:82:ed:2f:31:99:42:4c:a9:b2:0d:b8:15:5d:f1:df:ba: + c9:b5:4a:d4:64:98:b3:26:a9:30:c8:fd:a6:ec:ab:96:21:ad: + 7f:c2:78:b6 +-----BEGIN CERTIFICATE----- +MIID2DCCAsACEQDQHkCLAAB3bQAAAAEAAAAEMA0GCSqGSIb3DQEBBQUAMIGpMQsw +CQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMOU2FsdCBMYWtlIENp +dHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UE +CxMIRFNUQ0EgWDIxFjAUBgNVBAMTDURTVCBSb290Q0EgWDIxITAfBgkqhkiG9w0B +CQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTAeFw05ODExMzAyMjQ2MTZaFw0wODExMjcy +MjQ2MTZaMIGpMQswCQYDVQQGEwJ1czENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMO +U2FsdCBMYWtlIENpdHkxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0 +IENvLjERMA8GA1UECxMIRFNUQ0EgWDIxFjAUBgNVBAMTDURTVCBSb290Q0EgWDIx +ITAfBgkqhkiG9w0BCQEWEmNhQGRpZ3NpZ3RydXN0LmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANx18IzAdZaawGIfJvfE4Zrq4FZzW5nNAUSoCLbV +p9oaBBg5kkp4o4HC9Xd6ULRw/5qrxsfKboNPQpj7Jgva3G3WqZlVUmfpKAOS3OWw +BZoPFflrWXJW8vo5/Kpo7g8fEIMv/J36F5bdguPmRX3AS4BEH+0s4IT9kVySVGkl +5WJp3OXuAFK9MwutdQKFp2RQLcUZGTDAJtvJ0/0uma1ZtQtN1EGuhUhDWdy3qOKi +3sOP17ihYqZoUFLkzzGnlIXan0YyF1bl8utmPRL/Q9uY73fPy4GNNLHGUEom0eQ+ +QVCvbK4iNC7Va26Dunm4dmVI2gkpZGMiuftHdoWMhkTLCdsCAwEAATANBgkqhkiG +9w0BAQUFAAOCAQEAtTYOXeFhKFoRZcA/gwN5Tb4opgsHAlKFzfiR0BBstWogWxyQ +2TA8xkieil5k+aFxd+8EJx8H6+Qm93N0yUQYGmbT4EOvkTvRyyzYdFQ6HE3K1GjN +I3wdEJ5F6fYAbqbNGf9PLCmPV03Ed5K+4EwJ+11EhmYhqLkyolbV6YyDfFk/xPEL +553snr2cGA4+wjl5KLcDDQjLxufZATdQEOzMYRZA1K8xdHv8PzGn0EdzMzkbzE5q +10mDEQb+64JYMzJM8FasHpwvVpp7wUocpf1VNs78lk30sPDst2yC7S8xmUJMqbIN +uBVd8d+6ybVK1GSYsyapMMj9puyrliGtf8J4tg== +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority +======================================================= + +MD5 Fingerprint=97:60:E8:57:5F:D3:50:47:E5:43:0C:94:36:8A:B0:62 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + cd:ba:7f:56:f0:df:e4:bc:54:fe:22:ac:b3:72:aa:55 + Signature Algorithm: md2WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority + Validity + Not Before: Jan 29 00:00:00 1996 GMT + Not After : Aug 1 23:59:59 2028 GMT + Subject: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:e5:19:bf:6d:a3:56:61:2d:99:48:71:f6:67:de: + b9:8d:eb:b7:9e:86:80:0a:91:0e:fa:38:25:af:46: + 88:82:e5:73:a8:a0:9b:24:5d:0d:1f:cc:65:6e:0c: + b0:d0:56:84:18:87:9a:06:9b:10:a1:73:df:b4:58: + 39:6b:6e:c1:f6:15:d5:a8:a8:3f:aa:12:06:8d:31: + ac:7f:b0:34:d7:8f:34:67:88:09:cd:14:11:e2:4e: + 45:56:69:1f:78:02:80:da:dc:47:91:29:bb:36:c9: + 63:5c:c5:e0:d7:2d:87:7b:a1:b7:32:b0:7b:30:ba: + 2a:2f:31:aa:ee:a3:67:da:db + Exponent: 65537 (0x10001) + Signature Algorithm: md2WithRSAEncryption + 4c:3f:b8:8b:c6:68:df:ee:43:33:0e:5d:e9:a6:cb:07:84:4d: + 7a:33:ff:92:1b:f4:36:ad:d8:95:22:36:68:11:6c:7c:42:cc: + f3:9c:2e:c4:07:3f:14:b0:0f:4f:ff:90:92:76:f9:e2:bc:4a: + e9:8f:cd:a0:80:0a:f7:c5:29:f1:82:22:5d:b8:b1:dd:81:23: + a3:7b:25:15:46:30:79:16:f8:ea:05:4b:94:7f:1d:c2:1c:c8: + e3:b7:f4:10:40:3c:13:c3:5f:1f:53:e8:48:e4:86:b4:7b:a1: + 35:b0:7b:25:ba:b8:d3:8e:ab:3f:38:9d:00:34:00:98:f3:d1: + 71:94 +-----BEGIN CERTIFICATE----- +MIICPTCCAaYCEQDNun9W8N/kvFT+IqyzcqpVMA0GCSqGSIb3DQEBAgUAMF8xCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xh +c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05 +NjAxMjkwMDAwMDBaFw0yODA4MDEyMzU5NTlaMF8xCzAJBgNVBAYTAlVTMRcwFQYD +VQQKEw5WZXJpU2lnbiwgSW5jLjE3MDUGA1UECxMuQ2xhc3MgMSBQdWJsaWMgUHJp +bWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCBnzANBgkqhkiG9w0BAQEFAAOB +jQAwgYkCgYEA5Rm/baNWYS2ZSHH2Z965jeu3noaACpEO+jglr0aIguVzqKCbJF0N +H8xlbgyw0FaEGIeaBpsQoXPftFg5a27B9hXVqKg/qhIGjTGsf7A01480Z4gJzRQR +4k5FVmkfeAKA2txHkSm7NsljXMXg1y2He6G3MrB7MLoqLzGq7qNn2tsCAwEAATAN +BgkqhkiG9w0BAQIFAAOBgQBMP7iLxmjf7kMzDl3ppssHhE16M/+SG/Q2rdiVIjZo +EWx8QszznC7EBz8UsA9P/5CSdvnivErpj82ggAr3xSnxgiJduLHdgSOjeyUVRjB5 +FvjqBUuUfx3CHMjjt/QQQDwTw18fU+hI5Ia0e6E1sHslurjTjqs/OJ0ANACY89Fx +lA== +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority +======================================================= + +MD5 Fingerprint=B3:9C:25:B1:C3:2E:32:53:80:15:30:9D:4D:02:77:3E +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 2d:1b:fc:4a:17:8d:a3:91:eb:e7:ff:f5:8b:45:be:0b + Signature Algorithm: md2WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 2 Public Primary Certification Authority + Validity + Not Before: Jan 29 00:00:00 1996 GMT + Not After : Aug 1 23:59:59 2028 GMT + Subject: C=US, O=VeriSign, Inc., OU=Class 2 Public Primary Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:b6:5a:8b:a3:0d:6a:23:83:80:6b:cf:39:87:f4: + 21:13:33:06:4c:25:a2:ed:55:12:97:c5:a7:80:b9: + fa:83:c1:20:a0:fa:2f:15:0d:7c:a1:60:6b:7e:79: + 2c:fa:06:0f:3a:ae:f6:1b:6f:b1:d2:ff:2f:28:52: + 5f:83:7d:4b:c4:7a:b7:f8:66:1f:80:54:fc:b7:c2: + 8e:59:4a:14:57:46:d1:9a:93:be:41:91:03:bb:15: + 80:93:5c:eb:e7:cc:08:6c:3f:3e:b3:4a:fc:ff:4b: + 6c:23:d5:50:82:26:44:19:8e:23:c3:71:ea:19:24: + 47:04:9e:75:bf:c8:a6:00:1f + Exponent: 65537 (0x10001) + Signature Algorithm: md2WithRSAEncryption + 8a:1b:2b:fa:39:c1:74:d7:5e:d8:19:64:a2:58:4a:2d:37:e0: + 33:47:0f:ac:ed:f7:aa:db:1e:e4:8b:06:5c:60:27:ca:45:52: + ce:16:ef:3f:06:64:e7:94:68:7c:60:33:15:11:69:af:9d:62: + 8d:a3:03:54:6b:a6:be:e5:ee:05:18:60:04:bf:42:80:fd:d0: + a8:a8:1e:01:3b:f7:a3:5c:af:a3:dc:e6:26:80:23:3c:b8:44: + 74:f7:0a:ae:49:8b:61:78:cc:24:bf:88:8a:a7:0e:ea:73:19: + 41:fd:4d:03:f0:88:d1:e5:78:8d:a5:2a:4f:f6:97:0d:17:77: + ca:d8 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEC0b/EoXjaOR6+f/9YtFvgswDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAyIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAyIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQC2WoujDWojg4BrzzmH9CETMwZMJaLtVRKXxaeAufqDwSCg+i8VDXyh +YGt+eSz6Bg86rvYbb7HS/y8oUl+DfUvEerf4Zh+AVPy3wo5ZShRXRtGak75BkQO7 +FYCTXOvnzAhsPz6zSvz/S2wj1VCCJkQZjiPDceoZJEcEnnW/yKYAHwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBAIobK/o5wXTXXtgZZKJYSi034DNHD6zt96rbHuSLBlxg +J8pFUs4W7z8GZOeUaHxgMxURaa+dYo2jA1Rrpr7l7gUYYAS/QoD90KioHgE796Nc +r6Pc5iaAIzy4RHT3Cq5Ji2F4zCS/iIqnDupzGUH9TQPwiNHleI2lKk/2lw0Xd8rY +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority +======================================================= + +MD5 Fingerprint=10:FC:63:5D:F6:26:3E:0D:F3:25:BE:5F:79:CD:67:67 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 70:ba:e4:1d:10:d9:29:34:b6:38:ca:7b:03:cc:ba:bf + Signature Algorithm: md2WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority + Validity + Not Before: Jan 29 00:00:00 1996 GMT + Not After : Aug 1 23:59:59 2028 GMT + Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:c9:5c:59:9e:f2:1b:8a:01:14:b4:10:df:04:40: + db:e3:57:af:6a:45:40:8f:84:0c:0b:d1:33:d9:d9: + 11:cf:ee:02:58:1f:25:f7:2a:a8:44:05:aa:ec:03: + 1f:78:7f:9e:93:b9:9a:00:aa:23:7d:d6:ac:85:a2: + 63:45:c7:72:27:cc:f4:4c:c6:75:71:d2:39:ef:4f: + 42:f0:75:df:0a:90:c6:8e:20:6f:98:0f:f8:ac:23: + 5f:70:29:36:a4:c9:86:e7:b1:9a:20:cb:53:a5:85: + e7:3d:be:7d:9a:fe:24:45:33:dc:76:15:ed:0f:a2: + 71:64:4c:65:2e:81:68:45:a7 + Exponent: 65537 (0x10001) + Signature Algorithm: md2WithRSAEncryption + bb:4c:12:2b:cf:2c:26:00:4f:14:13:dd:a6:fb:fc:0a:11:84: + 8c:f3:28:1c:67:92:2f:7c:b6:c5:fa:df:f0:e8:95:bc:1d:8f: + 6c:2c:a8:51:cc:73:d8:a4:c0:53:f0:4e:d6:26:c0:76:01:57: + 81:92:5e:21:f1:d1:b1:ff:e7:d0:21:58:cd:69:17:e3:44:1c: + 9c:19:44:39:89:5c:dc:9c:00:0f:56:8d:02:99:ed:a2:90:45: + 4c:e4:bb:10:a4:3d:f0:32:03:0e:f1:ce:f8:e8:c9:51:8c:e6: + 62:9f:e6:9f:c0:7d:b7:72:9c:c9:36:3a:6b:9f:4e:a8:ff:64: + 0d:64 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority - G2 +============================================================ + +MD5 Fingerprint=DB:23:3D:F9:69:FA:4B:B9:95:80:44:73:5E:7D:41:83 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 4c:c7:ea:aa:98:3e:71:d3:93:10:f8:3d:3a:89:91:92 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Validity + Not Before: May 18 00:00:00 1998 GMT + Not After : Aug 1 23:59:59 2028 GMT + Subject: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:aa:d0:ba:be:16:2d:b8:83:d4:ca:d2:0f:bc:76: + 31:ca:94:d8:1d:93:8c:56:02:bc:d9:6f:1a:6f:52: + 36:6e:75:56:0a:55:d3:df:43:87:21:11:65:8a:7e: + 8f:bd:21:de:6b:32:3f:1b:84:34:95:05:9d:41:35: + eb:92:eb:96:dd:aa:59:3f:01:53:6d:99:4f:ed:e5: + e2:2a:5a:90:c1:b9:c4:a6:15:cf:c8:45:eb:a6:5d: + 8e:9c:3e:f0:64:24:76:a5:cd:ab:1a:6f:b6:d8:7b: + 51:61:6e:a6:7f:87:c8:e2:b7:e5:34:dc:41:88:ea: + 09:40:be:73:92:3d:6b:e7:75 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + a9:4f:c3:0d:c7:67:be:2c:cb:d9:a8:cd:2d:75:e7:7e:15:9e: + 3b:72:eb:7e:eb:5c:2d:09:87:d6:6b:6d:60:7c:e5:ae:c5:90: + 23:0c:5c:4a:d0:af:b1:5d:f3:c7:b6:0a:db:e0:15:93:0d:dd: + 03:bc:c7:76:8a:b5:dd:4f:c3:9b:13:75:b8:01:c0:e6:c9:5b: + 6b:a5:b8:89:dc:ac:a4:dd:72:ed:4e:a1:f7:4f:bc:06:d3:ea: + c8:64:74:7b:c2:95:41:9c:65:73:58:f1:90:9a:3c:6a:b1:98: + c9:c4:87:bc:cf:45:6d:45:e2:6e:22:3f:fe:bc:0f:31:5c:e8: + f2:d9 +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK +VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm +Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J +h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul +uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68 +DzFc6PLZ +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G2 +============================================================ + +MD5 Fingerprint=2D:BB:E5:25:D3:D1:65:82:3A:B7:0E:FA:E6:EB:E2:E1 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + b9:2f:60:cc:88:9f:a1:7a:46:09:b8:5b:70:6c:8a:af + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 2 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Validity + Not Before: May 18 00:00:00 1998 GMT + Not After : Aug 1 23:59:59 2028 GMT + Subject: C=US, O=VeriSign, Inc., OU=Class 2 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:a7:88:01:21:74:2c:e7:1a:03:f0:98:e1:97:3c: + 0f:21:08:f1:9c:db:97:e9:9a:fc:c2:04:06:13:be: + 5f:52:c8:cc:1e:2c:12:56:2c:b8:01:69:2c:cc:99: + 1f:ad:b0:96:ae:79:04:f2:13:39:c1:7b:98:ba:08: + 2c:e8:c2:84:13:2c:aa:69:e9:09:f4:c7:a9:02:a4: + 42:c2:23:4f:4a:d8:f0:0e:a2:fb:31:6c:c9:e6:6f: + 99:27:07:f5:e6:f4:4c:78:9e:6d:eb:46:86:fa:b9: + 86:c9:54:f2:b2:c4:af:d4:46:1c:5a:c9:15:30:ff: + 0d:6c:f5:2d:0e:6d:ce:7f:77 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 72:2e:f9:7f:d1:f1:71:fb:c4:9e:f6:c5:5e:51:8a:40:98:b8: + 68:f8:9b:1c:83:d8:e2:9d:bd:ff:ed:a1:e6:66:ea:2f:09:f4: + ca:d7:ea:a5:2b:95:f6:24:60:86:4d:44:2e:83:a5:c4:2d:a0: + d3:ae:78:69:6f:72:da:6c:ae:08:f0:63:92:37:e6:bb:c4:30: + 17:ad:77:cc:49:35:aa:cf:d8:8f:d1:be:b7:18:96:47:73:6a: + 54:22:34:64:2d:b6:16:9b:59:5b:b4:51:59:3a:b3:0b:14:f4: + 12:df:67:a0:f4:ad:32:64:5e:b1:46:72:27:8c:12:7b:c5:44: + b4:ae +-----BEGIN CERTIFICATE----- +MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns +YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y +aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe +Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj +IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx +KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM +HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw +DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC +AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji +nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX +rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn +jBJ7xUS0rg== +-----END CERTIFICATE----- + +GTE CyberTrust Root CA +====================== + +MD5 Fingerprint=C4:D7:F0:B2:A3:C5:7D:61:67:F0:04:CD:43:D3:BA:58 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 419 (0x1a3) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, O=GTE Corporation, CN=GTE CyberTrust Root + Validity + Not Before: Feb 23 23:01:00 1996 GMT + Not After : Feb 23 23:59:00 2006 GMT + Subject: C=US, O=GTE Corporation, CN=GTE CyberTrust Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:b8:e6:4f:ba:db:98:7c:71:7c:af:44:b7:d3:0f: + 46:d9:64:e5:93:c1:42:8e:c7:ba:49:8d:35:2d:7a: + e7:8b:bd:e5:05:31:59:c6:b1:2f:0a:0c:fb:9f:a7: + 3f:a2:09:66:84:56:1e:37:29:1b:87:e9:7e:0c:ca: + 9a:9f:a5:7f:f5:15:94:a3:d5:a2:46:82:d8:68:4c: + d1:37:15:06:68:af:bd:f8:b0:b3:f0:29:f5:95:5a: + 09:16:61:77:0a:22:25:d4:4f:45:aa:c7:bd:e5:96: + df:f9:d4:a8:8e:42:cc:24:c0:1e:91:27:4a:b5:6d: + 06:80:63:39:c4:a2:5e:38:03 + Exponent: 65537 (0x10001) + Signature Algorithm: md5WithRSAEncryption + 12:b3:75:c6:5f:1d:e1:61:55:80:00:d4:81:4b:7b:31:0f:23: + 63:e7:3d:f3:03:f9:f4:36:a8:bb:d9:e3:a5:97:4d:ea:2b:29: + e0:d6:6a:73:81:e6:c0:89:a3:d3:f1:e0:a5:a5:22:37:9a:63: + c2:48:20:b4:db:72:e3:c8:f6:d9:7c:be:b1:af:53:da:14:b4: + 21:b8:d6:d5:96:e3:fe:4e:0c:59:62:b6:9a:4a:f9:42:dd:8c: + 6f:81:a9:71:ff:f4:0a:72:6d:6d:44:0e:9d:f3:74:74:a8:d5: + 34:49:e9:5e:9e:e9:b4:7a:e1:e5:5a:1f:84:30:9c:d3:9f:a5: + 25:d8 +-----BEGIN CERTIFICATE----- +MIIB+jCCAWMCAgGjMA0GCSqGSIb3DQEBBAUAMEUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xHDAaBgNVBAMTE0dURSBDeWJlclRydXN0IFJv +b3QwHhcNOTYwMjIzMjMwMTAwWhcNMDYwMjIzMjM1OTAwWjBFMQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMRwwGgYDVQQDExNHVEUgQ3liZXJU +cnVzdCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC45k+625h8cXyv +RLfTD0bZZOWTwUKOx7pJjTUteueLveUFMVnGsS8KDPufpz+iCWaEVh43KRuH6X4M +ypqfpX/1FZSj1aJGgthoTNE3FQZor734sLPwKfWVWgkWYXcKIiXUT0Wqx73llt/5 +1KiOQswkwB6RJ0q1bQaAYznEol44AwIDAQABMA0GCSqGSIb3DQEBBAUAA4GBABKz +dcZfHeFhVYAA1IFLezEPI2PnPfMD+fQ2qLvZ46WXTeorKeDWanOB5sCJo9Px4KWl +IjeaY8JIILTbcuPI9tl8vrGvU9oUtCG41tWW4/5ODFlitppK+ULdjG+BqXH/9Apy +bW1EDp3zdHSo1TRJ6V6e6bR64eVaH4QwnNOfpSXY +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G2 +============================================================ + +MD5 Fingerprint=A2:33:9B:4C:74:78:73:D4:6C:E7:C1:F3:8D:CB:5C:E9 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 7d:d9:fe:07:cf:a8:1e:b7:10:79:67:fb:a7:89:34:c6 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Validity + Not Before: May 18 00:00:00 1998 GMT + Not After : Aug 1 23:59:59 2028 GMT + Subject: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:cc:5e:d1:11:5d:5c:69:d0:ab:d3:b9:6a:4c:99: + 1f:59:98:30:8e:16:85:20:46:6d:47:3f:d4:85:20: + 84:e1:6d:b3:f8:a4:ed:0c:f1:17:0f:3b:f9:a7:f9: + 25:d7:c1:cf:84:63:f2:7c:63:cf:a2:47:f2:c6:5b: + 33:8e:64:40:04:68:c1:80:b9:64:1c:45:77:c7:d8: + 6e:f5:95:29:3c:50:e8:34:d7:78:1f:a8:ba:6d:43: + 91:95:8f:45:57:5e:7e:c5:fb:ca:a4:04:eb:ea:97: + 37:54:30:6f:bb:01:47:32:33:cd:dc:57:9b:64:69: + 61:f8:9b:1d:1c:89:4f:5c:67 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 51:4d:cd:be:5c:cb:98:19:9c:15:b2:01:39:78:2e:4d:0f:67: + 70:70:99:c6:10:5a:94:a4:53:4d:54:6d:2b:af:0d:5d:40:8b: + 64:d3:d7:ee:de:56:61:92:5f:a6:c4:1d:10:61:36:d3:2c:27: + 3c:e8:29:09:b9:11:64:74:cc:b5:73:9f:1c:48:a9:bc:61:01: + ee:e2:17:a6:0c:e3:40:08:3b:0e:e7:eb:44:73:2a:9a:f1:69: + 92:ef:71:14:c3:39:ac:71:a7:91:09:6f:e4:71:06:b3:ba:59: + 57:26:79:00:f6:f8:0d:a2:33:30:28:d4:aa:58:a0:9d:9d:69: + 91:fd +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 +pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 +13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk +U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i +F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY +oJ2daZH9 +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G2 +============================================================ + +MD5 Fingerprint=26:6D:2C:19:98:B6:70:68:38:50:54:19:EC:90:34:60 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 32:88:8e:9a:d2:f5:eb:13:47:f8:7f:c4:20:37:25:f8 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 4 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Validity + Not Before: May 18 00:00:00 1998 GMT + Not After : Aug 1 23:59:59 2028 GMT + Subject: C=US, O=VeriSign, Inc., OU=Class 4 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:ba:f0:e4:cf:f9:c4:ae:85:54:b9:07:57:f9:8f: + c5:7f:68:11:f8:c4:17:b0:44:dc:e3:30:73:d5:2a: + 62:2a:b8:d0:cc:1c:ed:28:5b:7e:bd:6a:dc:b3:91: + 24:ca:41:62:3c:fc:02:01:bf:1c:16:31:94:05:97: + 76:6e:a2:ad:bd:61:17:6c:4e:30:86:f0:51:37:2a: + 50:c7:a8:62:81:dc:5b:4a:aa:c1:a0:b4:6e:eb:2f: + e5:57:c5:b1:2b:40:70:db:5a:4d:a1:8e:1f:bd:03: + 1f:d8:03:d4:8f:4c:99:71:bc:e2:82:cc:58:e8:98: + 3a:86:d3:86:38:f3:00:29:1f + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 85:8c:12:c1:a7:b9:50:15:7a:cb:3e:ac:b8:43:8a:dc:aa:dd: + 14:ba:89:81:7e:01:3c:23:71:21:88:2f:82:dc:63:fa:02:45: + ac:45:59:d7:2a:58:44:5b:b7:9f:81:3b:92:68:3d:e2:37:24: + f5:7b:6c:8f:76:35:96:09:a8:59:9d:b9:ce:23:ab:74:d6:83: + fd:32:73:27:d8:69:3e:43:74:f6:ae:c5:89:9a:e7:53:7c:e9: + 7b:f6:4b:f3:c1:65:83:de:8d:8a:9c:3c:88:8d:39:59:fc:aa: + 3f:22:8d:a1:c1:66:50:81:72:4c:ed:22:64:4f:4f:ca:80:91: + b6:29 +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEDKIjprS9esTR/h/xCA3JfgwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgNCBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgNCBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQC68OTP+cSuhVS5B1f5j8V/aBH4xBewRNzjMHPVKmIquNDM +HO0oW369atyzkSTKQWI8/AIBvxwWMZQFl3Zuoq29YRdsTjCG8FE3KlDHqGKB3FtK +qsGgtG7rL+VXxbErQHDbWk2hjh+9Ax/YA9SPTJlxvOKCzFjomDqG04Y48wApHwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAIWMEsGnuVAVess+rLhDityq3RS6iYF+ATwj +cSGIL4LcY/oCRaxFWdcqWERbt5+BO5JoPeI3JPV7bI92NZYJqFmduc4jq3TWg/0y +cyfYaT5DdPauxYma51N86Xv2S/PBZYPejYqcPIiNOVn8qj8ijaHBZlCBckztImRP +T8qAkbYp +-----END CERTIFICATE----- + +GlobalSign Root CA +================== + +MD5 Fingerprint=AB:BF:EA:E3:6B:29:A6:CC:A6:78:35:99:EF:AD:2B:80 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 02:00:00:00:00:00:d6:78:b7:94:05 + Signature Algorithm: md5WithRSAEncryption + Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA + Validity + Not Before: Sep 1 12:00:00 1998 GMT + Not After : Jan 28 12:00:00 2014 GMT + Subject: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:da:0e:e6:99:8d:ce:a3:e3:4f:8a:7e:fb:f1:8b: + 83:25:6b:ea:48:1f:f1:2a:b0:b9:95:11:04:bd:f0: + 63:d1:e2:67:66:cf:1c:dd:cf:1b:48:2b:ee:8d:89: + 8e:9a:af:29:80:65:ab:e9:c7:2d:12:cb:ab:1c:4c: + 70:07:a1:3d:0a:30:cd:15:8d:4f:f8:dd:d4:8c:50: + 15:1c:ef:50:ee:c4:2e:f7:fc:e9:52:f2:91:7d:e0: + 6d:d5:35:30:8e:5e:43:73:f2:41:e9:d5:6a:e3:b2: + 89:3a:56:39:38:6f:06:3c:88:69:5b:2a:4d:c5:a7: + 54:b8:6c:89:cc:9b:f9:3c:ca:e5:fd:89:f5:12:3c: + 92:78:96:d6:dc:74:6e:93:44:61:d1:8d:c7:46:b2: + 75:0e:86:e8:19:8a:d5:6d:6c:d5:78:16:95:a2:e9: + c8:0a:38:eb:f2:24:13:4f:73:54:93:13:85:3a:1b: + bc:1e:34:b5:8b:05:8c:b9:77:8b:b1:db:1f:20:91: + ab:09:53:6e:90:ce:7b:37:74:b9:70:47:91:22:51: + 63:16:79:ae:b1:ae:41:26:08:c8:19:2b:d1:46:aa: + 48:d6:64:2a:d7:83:34:ff:2c:2a:c1:6c:19:43:4a: + 07:85:e7:d3:7c:f6:21:68:ef:ea:f2:52:9f:7f:93: + 90:cf + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Subject Key Identifier: + 60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: md5WithRSAEncryption + ae:aa:9f:fc:b7:d2:cb:1f:5f:39:29:28:18:9e:34:c9:6c:4f: + 6f:1a:f0:64:a2:70:4a:4f:13:86:9b:60:28:9e:e8:81:49:98: + 7d:0a:bb:e5:b0:9d:3d:36:db:8f:05:51:ff:09:31:2a:1f:dd: + 89:77:9e:0f:2e:6c:95:04:ed:86:cb:b4:00:3f:84:02:4d:80: + 6a:2a:2d:78:0b:ae:6f:2b:a2:83:44:83:1f:cd:50:82:4c:24: + af:bd:f7:a5:b4:c8:5a:0f:f4:e7:47:5e:49:8e:37:96:fe:9a: + 88:05:3a:d9:c0:db:29:87:e6:19:96:47:a7:3a:a6:8c:8b:3c: + 77:fe:46:63:a7:53:da:21:d1:ac:7e:49:a2:4b:e6:c3:67:59: + 2f:b3:8a:0e:bb:2c:bd:a9:aa:42:7c:35:c1:d8:7f:d5:a7:31: + 3a:4e:63:43:39:af:08:b0:61:34:8c:d3:98:a9:43:34:f6:0f: + 87:29:3b:9d:c2:56:58:98:77:c3:f7:1b:ac:f6:9d:f8:3e:aa: + a7:54:45:f0:f5:f9:d5:31:65:fe:6b:58:9c:71:b3:1e:d7:52: + ea:32:17:fc:40:60:1d:c9:79:24:b2:f6:6c:fd:a8:66:0e:82: + dd:98:cb:da:c2:44:4f:2e:a0:7b:f2:f7:6b:2c:76:11:84:46: + 8a:78:a3:e3 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILAgAAAAAA1ni3lAUwDQYJKoZIhvcNAQEEBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0xNDAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIABjAdBgNVHQ4EFgQU +YHtmGkUNl8qJUC99BM00qP/8/UswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQQFAAOCAQEArqqf/LfSyx9fOSkoGJ40yWxPbxrwZKJwSk8ThptgKJ7ogUmYfQq7 +5bCdPTbbjwVR/wkxKh/diXeeDy5slQTthsu0AD+EAk2AaioteAuubyuig0SDH81Q +gkwkr733pbTIWg/050deSY43lv6aiAU62cDbKYfmGZZHpzqmjIs8d/5GY6dT2iHR +rH5Jokvmw2dZL7OKDrssvamqQnw1wdh/1acxOk5jQzmvCLBhNIzTmKlDNPYPhyk7 +ncJWWJh3w/cbrPad+D6qp1RF8PX51TFl/mtYnHGzHtdS6jIX/EBgHcl5JLL2bP2o +Zg6C3ZjL2sJETy6ge/L3ayx2EYRGinij4w== +-----END CERTIFICATE----- + +ValiCert Class 1 VA +=================== + +MD5 Fingerprint=65:58:AB:15:AD:57:6C:1E:A8:A7:B5:69:AC:BF:FF:EB +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 1 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com + Validity + Not Before: Jun 25 22:23:48 1999 GMT + Not After : Jun 25 22:23:48 2019 GMT + Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 1 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d8:59:82:7a:89:b8:96:ba:a6:2f:68:6f:58:2e: + a7:54:1c:06:6e:f4:ea:8d:48:bc:31:94:17:f0:f3: + 4e:bc:b2:b8:35:92:76:b0:d0:a5:a5:01:d7:00:03: + 12:22:19:08:f8:ff:11:23:9b:ce:07:f5:bf:69:1a: + 26:fe:4e:e9:d1:7f:9d:2c:40:1d:59:68:6e:a6:f8: + 58:b0:9d:1a:8f:d3:3f:f1:dc:19:06:81:a8:0e:e0: + 3a:dd:c8:53:45:09:06:e6:0f:70:c3:fa:40:a6:0e: + e2:56:05:0f:18:4d:fc:20:82:d1:73:55:74:8d:76: + 72:a0:1d:9d:1d:c0:dd:3f:71 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 50:68:3d:49:f4:2c:1c:06:94:df:95:60:7f:96:7b:17:fe:4f: + 71:ad:64:c8:dd:77:d2:ef:59:55:e8:3f:e8:8e:05:2a:21:f2: + 07:d2:b5:a7:52:fe:9c:b1:b6:e2:5b:77:17:40:ea:72:d6:23: + cb:28:81:32:c3:00:79:18:ec:59:17:89:c9:c6:6a:1e:71:c9: + fd:b7:74:a5:25:45:69:c5:48:ab:19:e1:45:8a:25:6b:19:ee: + e5:bb:12:f5:7f:f7:a6:8d:51:c3:f0:9d:74:b7:a9:3e:a0:a5: + ff:b6:49:03:13:da:22:cc:ed:71:82:2b:99:cf:3a:b7:f5:2d: + 72:c8 +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy +NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y +LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ +TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y +TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 +LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW +I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw +nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI +-----END CERTIFICATE----- + +ValiCert Class 2 VA +=================== + +MD5 Fingerprint=A9:23:75:9B:BA:49:36:6E:31:C2:DB:F2:E7:66:BA:87 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com + Validity + Not Before: Jun 26 00:19:54 1999 GMT + Not After : Jun 26 00:19:54 2019 GMT + Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:ce:3a:71:ca:e5:ab:c8:59:92:55:d7:ab:d8:74: + 0e:f9:ee:d9:f6:55:47:59:65:47:0e:05:55:dc:eb: + 98:36:3c:5c:53:5d:d3:30:cf:38:ec:bd:41:89:ed: + 25:42:09:24:6b:0a:5e:b3:7c:dd:52:2d:4c:e6:d4: + d6:7d:5a:59:a9:65:d4:49:13:2d:24:4d:1c:50:6f: + b5:c1:85:54:3b:fe:71:e4:d3:5c:42:f9:80:e0:91: + 1a:0a:5b:39:36:67:f3:3f:55:7c:1b:3f:b4:5f:64: + 73:34:e3:b4:12:bf:87:64:f8:da:12:ff:37:27:c1: + b3:43:bb:ef:7b:6e:2e:69:f7 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 3b:7f:50:6f:6f:50:94:99:49:62:38:38:1f:4b:f8:a5:c8:3e: + a7:82:81:f6:2b:c7:e8:c5:ce:e8:3a:10:82:cb:18:00:8e:4d: + bd:a8:58:7f:a1:79:00:b5:bb:e9:8d:af:41:d9:0f:34:ee:21: + 81:19:a0:32:49:28:f4:c4:8e:56:d5:52:33:fd:50:d5:7e:99: + 6c:03:e4:c9:4c:fc:cb:6c:ab:66:b3:4a:21:8c:e5:b5:0c:32: + 3e:10:b2:cc:6c:a1:dc:9a:98:4c:02:5b:f3:ce:b9:9e:a5:72: + 0e:4a:b7:3f:3c:e6:16:68:f8:be:ed:74:4c:bc:5b:d5:62:1f: + 43:dd +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy +NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY +dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 +WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS +v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v +UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu +IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC +W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +-----END CERTIFICATE----- + +RSA Root Certificate 1 +====================== + +MD5 Fingerprint=A2:6F:53:B7:EE:40:DB:4A:68:E7:FA:18:D9:10:4B:72 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 3 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com + Validity + Not Before: Jun 26 00:22:33 1999 GMT + Not After : Jun 26 00:22:33 2019 GMT + Subject: L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 3 Policy Validation Authority, CN=http://www.valicert.com//emailAddress=info@valicert.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:e3:98:51:96:1c:e8:d5:b1:06:81:6a:57:c3:72: + 75:93:ab:cf:9e:a6:fc:f3:16:52:d6:2d:4d:9f:35: + 44:a8:2e:04:4d:07:49:8a:38:29:f5:77:37:e7:b7: + ab:5d:df:36:71:14:99:8f:dc:c2:92:f1:e7:60:92: + 97:ec:d8:48:dc:bf:c1:02:20:c6:24:a4:28:4c:30: + 5a:76:6d:b1:5c:f3:dd:de:9e:10:71:a1:88:c7:5b: + 9b:41:6d:ca:b0:b8:8e:15:ee:ad:33:2b:cf:47:04: + 5c:75:71:0a:98:24:98:29:a7:49:59:a5:dd:f8:b7: + 43:62:61:f3:d3:e2:d0:55:3f + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 56:bb:02:58:84:67:08:2c:df:1f:db:7b:49:33:f5:d3:67:9d: + f4:b4:0a:10:b3:c9:c5:2c:e2:92:6a:71:78:27:f2:70:83:42: + d3:3e:cf:a9:54:f4:f1:d8:92:16:8c:d1:04:cb:4b:ab:c9:9f: + 45:ae:3c:8a:a9:b0:71:33:5d:c8:c5:57:df:af:a8:35:b3:7f: + 89:87:e9:e8:25:92:b8:7f:85:7a:ae:d6:bc:1e:37:58:2a:67: + c9:91:cf:2a:81:3e:ed:c6:39:df:c0:3e:19:9c:19:cc:13:4d: + 82:41:b5:8c:de:e0:3d:60:08:20:0f:45:7e:6b:a2:7f:a3:8c: + 15:ee +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy +NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD +cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs +2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY +JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE +Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ +n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A +PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary Certification Authority - G3 +============================================================ + +MD5 Fingerprint=B1:47:BC:18:57:D1:18:A0:78:2D:EC:71:E8:2A:95:73 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 8b:5b:75:56:84:54:85:0b:00:cf:af:38:48:ce:b1:a4 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 1 Public Primary Certification Authority - G3 + Validity + Not Before: Oct 1 00:00:00 1999 GMT + Not After : Jul 16 23:59:59 2036 GMT + Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 1 Public Primary Certification Authority - G3 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:dd:84:d4:b9:b4:f9:a7:d8:f3:04:78:9c:de:3d: + dc:6c:13:16:d9:7a:dd:24:51:66:c0:c7:26:59:0d: + ac:06:08:c2:94:d1:33:1f:f0:83:35:1f:6e:1b:c8: + de:aa:6e:15:4e:54:27:ef:c4:6d:1a:ec:0b:e3:0e: + f0:44:a5:57:c7:40:58:1e:a3:47:1f:71:ec:60:f6: + 6d:94:c8:18:39:ed:fe:42:18:56:df:e4:4c:49:10: + 78:4e:01:76:35:63:12:36:dd:66:bc:01:04:36:a3: + 55:68:d5:a2:36:09:ac:ab:21:26:54:06:ad:3f:ca: + 14:e0:ac:ca:ad:06:1d:95:e2:f8:9d:f1:e0:60:ff: + c2:7f:75:2b:4c:cc:da:fe:87:99:21:ea:ba:fe:3e: + 54:d7:d2:59:78:db:3c:6e:cf:a0:13:00:1a:b8:27: + a1:e4:be:67:96:ca:a0:c5:b3:9c:dd:c9:75:9e:eb: + 30:9a:5f:a3:cd:d9:ae:78:19:3f:23:e9:5c:db:29: + bd:ad:55:c8:1b:54:8c:63:f6:e8:a6:ea:c7:37:12: + 5c:a3:29:1e:02:d9:db:1f:3b:b4:d7:0f:56:47:81: + 15:04:4a:af:83:27:d1:c5:58:88:c1:dd:f6:aa:a7: + a3:18:da:68:aa:6d:11:51:e1:bf:65:6b:9f:96:76: + d1:3d + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + ab:66:8d:d7:b3:ba:c7:9a:b6:e6:55:d0:05:f1:9f:31:8d:5a: + aa:d9:aa:46:26:0f:71:ed:a5:ad:53:56:62:01:47:2a:44:e9: + fe:3f:74:0b:13:9b:b9:f4:4d:1b:b2:d1:5f:b2:b6:d2:88:5c: + b3:9f:cd:cb:d4:a7:d9:60:95:84:3a:f8:c1:37:1d:61:ca:e7: + b0:c5:e5:91:da:54:a6:ac:31:81:ae:97:de:cd:08:ac:b8:c0: + 97:80:7f:6e:72:a4:e7:69:13:95:65:1f:c4:93:3c:fd:79:8f: + 04:d4:3e:4f:ea:f7:9e:ce:cd:67:7c:4f:65:02:ff:91:85:54: + 73:c7:ff:36:f7:86:2d:ec:d0:5e:4f:ff:11:9f:72:06:d6:b8: + 1a:f1:4c:0d:26:65:e2:44:80:1e:c7:9f:e3:dd:e8:0a:da:ec: + a5:20:80:69:68:a1:4f:7e:e1:6b:cf:07:41:fa:83:8e:bc:38: + dd:b0:2e:11:b1:6b:b2:42:cc:9a:bc:f9:48:22:79:4a:19:0f: + b2:1c:3e:20:74:d9:6a:c3:be:f2:28:78:13:56:79:4f:6d:50: + ea:1b:b0:b5:57:b1:37:66:58:23:f3:dc:0f:df:0a:87:c4:ef: + 86:05:d5:38:14:60:99:a3:4b:de:06:96:71:2c:f2:db:b6:1f: + a4:ef:3f:ee +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4 +nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO +8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV +ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb +PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2 +6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr +n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a +qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4 +wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3 +ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs +pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4 +E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g== +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary Certification Authority - G3 +============================================================ + +MD5 Fingerprint=F8:BE:C4:63:22:C9:A8:46:74:8B:B8:1D:1E:4A:2B:F6 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 61:70:cb:49:8c:5f:98:45:29:e7:b0:a6:d9:50:5b:7a + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 2 Public Primary Certification Authority - G3 + Validity + Not Before: Oct 1 00:00:00 1999 GMT + Not After : Jul 16 23:59:59 2036 GMT + Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 2 Public Primary Certification Authority - G3 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:af:0a:0d:c2:d5:2c:db:67:b9:2d:e5:94:27:dd: + a5:be:e0:b0:4d:8f:b3:61:56:3c:d6:7c:c3:f4:cd: + 3e:86:cb:a2:88:e2:e1:d8:a4:69:c5:b5:e2:bf:c1: + a6:47:50:5e:46:39:8b:d5:96:ba:b5:6f:14:bf:10: + ce:27:13:9e:05:47:9b:31:7a:13:d8:1f:d9:d3:02: + 37:8b:ad:2c:47:f0:8e:81:06:a7:0d:30:0c:eb:f7: + 3c:0f:20:1d:dc:72:46:ee:a5:02:c8:5b:c3:c9:56: + 69:4c:c5:18:c1:91:7b:0b:d5:13:00:9b:bc:ef:c3: + 48:3e:46:60:20:85:2a:d5:90:b6:cd:8b:a0:cc:32: + dd:b7:fd:40:55:b2:50:1c:56:ae:cc:8d:77:4d:c7: + 20:4d:a7:31:76:ef:68:92:8a:90:1e:08:81:56:b2: + ad:69:a3:52:d0:cb:1c:c4:23:3d:1f:99:fe:4c:e8: + 16:63:8e:c6:08:8e:f6:31:f6:d2:fa:e5:76:dd:b5: + 1c:92:a3:49:cd:cd:01:cd:68:cd:a9:69:ba:a3:eb: + 1d:0d:9c:a4:20:a6:c1:a0:c5:d1:46:4c:17:6d:d2: + ac:66:3f:96:8c:e0:84:d4:36:ff:22:59:c5:f9:11: + 60:a8:5f:04:7d:f2:1a:f6:25:42:61:0f:c4:4a:b8: + 3e:89 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 34:26:15:3c:c0:8d:4d:43:49:1d:bd:e9:21:92:d7:66:9c:b7: + de:c5:b8:d0:e4:5d:5f:76:22:c0:26:f9:84:3a:3a:f9:8c:b5: + fb:ec:60:f1:e8:ce:04:b0:c8:dd:a7:03:8f:30:f3:98:df:a4: + e6:a4:31:df:d3:1c:0b:46:dc:72:20:3f:ae:ee:05:3c:a4:33: + 3f:0b:39:ac:70:78:73:4b:99:2b:df:30:c2:54:b0:a8:3b:55: + a1:fe:16:28:cd:42:bd:74:6e:80:db:27:44:a7:ce:44:5d:d4: + 1b:90:98:0d:1e:42:94:b1:00:2c:04:d0:74:a3:02:05:22:63: + 63:cd:83:b5:fb:c1:6d:62:6b:69:75:fd:5d:70:41:b9:f5:bf: + 7c:df:be:c1:32:73:22:21:8b:58:81:7b:15:91:7a:ba:e3:64: + 48:b0:7f:fb:36:25:da:95:d0:f1:24:14:17:dd:18:80:6b:46: + 23:39:54:f5:8e:62:09:04:1d:94:90:a6:9b:e6:25:e2:42:45: + aa:b8:90:ad:be:08:8f:a9:0b:42:18:94:cf:72:39:e1:b1:43: + e0:28:cf:b7:e7:5a:6c:13:6b:49:b3:ff:e3:18:7c:89:8b:33: + 5d:ac:33:d7:a7:f9:da:3a:55:c9:58:10:f9:aa:ef:5a:b6:cf: + 4b:4b:df:2a +-----BEGIN CERTIFICATE----- +MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy +aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s +IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp +Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV +BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp +Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu +Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g +Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt +IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU +J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO +JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY +wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o +koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN +qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E +Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe +xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u +7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU +sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI +sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP +cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary Certification Authority - G3 +============================================================ + +MD5 Fingerprint=CD:68:B6:A7:C7:C4:CE:75:E0:1D:4F:57:44:61:92:09 +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + 9b:7e:06:49:a3:3e:62:b9:d5:ee:90:48:71:29:ef:57 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G3 + Validity + Not Before: Oct 1 00:00:00 1999 GMT + Not After : Jul 16 23:59:59 2036 GMT + Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G3 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cb:ba:9c:52:fc:78:1f:1a:1e:6f:1b:37:73:bd: + f8:c9:6b:94:12:30:4f:f0:36:47:f5:d0:91:0a:f5: + 17:c8:a5:61:c1:16:40:4d:fb:8a:61:90:e5:76:20: + c1:11:06:7d:ab:2c:6e:a6:f5:11:41:8e:fa:2d:ad: + 2a:61:59:a4:67:26:4c:d0:e8:bc:52:5b:70:20:04: + 58:d1:7a:c9:a4:69:bc:83:17:64:ad:05:8b:bc:d0: + 58:ce:8d:8c:f5:eb:f0:42:49:0b:9d:97:27:67:32: + 6e:e1:ae:93:15:1c:70:bc:20:4d:2f:18:de:92:88: + e8:6c:85:57:11:1a:e9:7e:e3:26:11:54:a2:45:96: + 55:83:ca:30:89:e8:dc:d8:a3:ed:2a:80:3f:7f:79: + 65:57:3e:15:20:66:08:2f:95:93:bf:aa:47:2f:a8: + 46:97:f0:12:e2:fe:c2:0a:2b:51:e6:76:e6:b7:46: + b7:e2:0d:a6:cc:a8:c3:4c:59:55:89:e6:e8:53:5c: + 1c:ea:9d:f0:62:16:0b:a7:c9:5f:0c:f0:de:c2:76: + ce:af:f7:6a:f2:fa:41:a6:a2:33:14:c9:e5:7a:63: + d3:9e:62:37:d5:85:65:9e:0e:e6:53:24:74:1b:5e: + 1d:12:53:5b:c7:2c:e7:83:49:3b:15:ae:8a:68:b9: + 57:97 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 11:14:96:c1:ab:92:08:f7:3f:2f:c9:b2:fe:e4:5a:9f:64:de: + db:21:4f:86:99:34:76:36:57:dd:d0:15:2f:c5:ad:7f:15:1f: + 37:62:73:3e:d4:e7:5f:ce:17:03:db:35:fa:2b:db:ae:60:09: + 5f:1e:5f:8f:6e:bb:0b:3d:ea:5a:13:1e:0c:60:6f:b5:c0:b5: + 23:22:2e:07:0b:cb:a9:74:cb:47:bb:1d:c1:d7:a5:6b:cc:2f: + d2:42:fd:49:dd:a7:89:cf:53:ba:da:00:5a:28:bf:82:df:f8: + ba:13:1d:50:86:82:fd:8e:30:8f:29:46:b0:1e:3d:35:da:38: + 62:16:18:4a:ad:e6:b6:51:6c:de:af:62:eb:01:d0:1e:24:fe: + 7a:8f:12:1a:12:68:b8:fb:66:99:14:14:45:5c:ae:e7:ae:69: + 17:81:2b:5a:37:c9:5e:2a:f4:c6:e2:a1:5c:54:9b:a6:54:00: + cf:f0:f1:c1:c7:98:30:1a:3b:36:16:db:a3:6e:ea:fd:ad:b2: + c2:da:ef:02:47:13:8a:c0:f1:b3:31:ad:4f:1c:e1:4f:9c:af: + 0f:0c:9d:f7:78:0d:d8:f4:35:56:80:da:b7:6d:17:8f:9d:1e: + 81:64:e1:fe:c5:45:ba:ad:6b:b9:0a:7a:4e:4f:4b:84:ee:4b: + f1:7d:dd:11 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +Verisign Class 4 Public Primary Certification Authority - G3 +============================================================ + +MD5 Fingerprint=DB:C8:F2:27:2E:B1:EA:6A:29:23:5D:FE:56:3E:33:DF +Certificate: + Data: + Version: 1 (0x0) + Serial Number: + ec:a0:a7:8b:6e:75:6a:01:cf:c4:7c:cc:2f:94:5e:d7 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 4 Public Primary Certification Authority - G3 + Validity + Not Before: Oct 1 00:00:00 1999 GMT + Not After : Jul 16 23:59:59 2036 GMT + Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 4 Public Primary Certification Authority - G3 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ad:cb:a5:11:69:c6:59:ab:f1:8f:b5:19:0f:56: + ce:cc:b5:1f:20:e4:9e:26:25:4b:e0:73:65:89:59: + de:d0:83:e4:f5:0f:b5:bb:ad:f1:7c:e8:21:fc:e4: + e8:0c:ee:7c:45:22:19:76:92:b4:13:b7:20:5b:09: + fa:61:ae:a8:f2:a5:8d:85:c2:2a:d6:de:66:36:d2: + 9b:02:f4:a8:92:60:7c:9c:69:b4:8f:24:1e:d0:86: + 52:f6:32:9c:41:58:1e:22:bd:cd:45:62:95:08:6e: + d0:66:dd:53:a2:cc:f0:10:dc:54:73:8b:04:a1:46: + 33:33:5c:17:40:b9:9e:4d:d3:f3:be:55:83:e8:b1: + 89:8e:5a:7c:9a:96:22:90:3b:88:25:f2:d2:53:88: + 02:0c:0b:78:f2:e6:37:17:4b:30:46:07:e4:80:6d: + a6:d8:96:2e:e8:2c:f8:11:b3:38:0d:66:a6:9b:ea: + c9:23:5b:db:8e:e2:f3:13:8e:1a:59:2d:aa:02:f0: + ec:a4:87:66:dc:c1:3f:f5:d8:b9:f4:ec:82:c6:d2: + 3d:95:1d:e5:c0:4f:84:c9:d9:a3:44:28:06:6a:d7: + 45:ac:f0:6b:6a:ef:4e:5f:f8:11:82:1e:38:63:34: + 66:50:d4:3e:93:73:fa:30:c3:66:ad:ff:93:2d:97: + ef:03 + Exponent: 65537 (0x10001) + Signature Algorithm: sha1WithRSAEncryption + 8f:fa:25:6b:4f:5b:e4:a4:4e:27:55:ab:22:15:59:3c:ca:b5: + 0a:d4:4a:db:ab:dd:a1:5f:53:c5:a0:57:39:c2:ce:47:2b:be: + 3a:c8:56:bf:c2:d9:27:10:3a:b1:05:3c:c0:77:31:bb:3a:d3: + 05:7b:6d:9a:1c:30:8c:80:cb:93:93:2a:83:ab:05:51:82:02: + 00:11:67:6b:f3:88:61:47:5f:03:93:d5:5b:0d:e0:f1:d4:a1: + 32:35:85:b2:3a:db:b0:82:ab:d1:cb:0a:bc:4f:8c:5b:c5:4b: + 00:3b:1f:2a:82:a6:7e:36:85:dc:7e:3c:67:00:b5:e4:3b:52: + e0:a8:eb:5d:15:f9:c6:6d:f0:ad:1d:0e:85:b7:a9:9a:73:14: + 5a:5b:8f:41:28:c0:d5:e8:2d:4d:a4:5e:cd:aa:d9:ed:ce:dc: + d8:d5:3c:42:1d:17:c1:12:5d:45:38:c3:38:f3:fc:85:2e:83: + 46:48:b2:d7:20:5f:92:36:8f:e7:79:0f:98:5e:99:e8:f0:d0: + a4:bb:f5:53:bd:2a:ce:59:b0:af:6e:7f:6c:bb:d2:1e:00:b0: + 21:ed:f8:41:62:82:b9:d8:b2:c4:bb:46:50:f3:31:c5:8f:01: + a8:74:eb:f5:78:27:da:e7:f7:66:43:f3:9e:83:3e:20:aa:c3: + 35:60:91:ce +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 +GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ ++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd +U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm +NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY +ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ +ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 +CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq +g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c +2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ +bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +GTE CyberTrust Global Root +========================== + +MD5 Fingerprint=CA:3D:D3:68:F1:03:5C:D0:32:FA:B8:2B:59:E8:5A:DB +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 421 (0x1a5) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root + Validity + Not Before: Aug 13 00:29:00 1998 GMT + Not After : Aug 13 23:59:00 2018 GMT + Subject: C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:95:0f:a0:b6:f0:50:9c:e8:7a:c7:88:cd:dd:17: + 0e:2e:b0:94:d0:1b:3d:0e:f6:94:c0:8a:94:c7:06: + c8:90:97:c8:b8:64:1a:7a:7e:6c:3c:53:e1:37:28: + 73:60:7f:b2:97:53:07:9f:53:f9:6d:58:94:d2:af: + 8d:6d:88:67:80:e6:ed:b2:95:cf:72:31:ca:a5:1c: + 72:ba:5c:02:e7:64:42:e7:f9:a9:2c:d6:3a:0d:ac: + 8d:42:aa:24:01:39:e6:9c:3f:01:85:57:0d:58:87: + 45:f8:d3:85:aa:93:69:26:85:70:48:80:3f:12:15: + c7:79:b4:1f:05:2f:3b:62:99 + Exponent: 65537 (0x10001) + Signature Algorithm: md5WithRSAEncryption + 6d:eb:1b:09:e9:5e:d9:51:db:67:22:61:a4:2a:3c:48:77:e3: + a0:7c:a6:de:73:a2:14:03:85:3d:fb:ab:0e:30:c5:83:16:33: + 81:13:08:9e:7b:34:4e:df:40:c8:74:d7:b9:7d:dc:f4:76:55: + 7d:9b:63:54:18:e9:f0:ea:f3:5c:b1:d9:8b:42:1e:b9:c0:95: + 4e:ba:fa:d5:e2:7c:f5:68:61:bf:8e:ec:05:97:5f:5b:b0:d7: + a3:85:34:c4:24:a7:0d:0f:95:93:ef:cb:94:d8:9e:1f:9d:5c: + 85:6d:c7:aa:ae:4f:1f:22:b5:cd:95:ad:ba:a7:cc:f9:ab:0b: + 7a:7f +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +Entrust.net Secure Server CA +============================ + +MD5 Fingerprint=DF:F2:80:73:CC:F1:E6:61:73:FC:F5:42:E9:C5:7C:EE +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 927650371 (0x374ad243) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority + Validity + Not Before: May 25 16:09:40 1999 GMT + Not After : May 25 16:39:40 2019 GMT + Subject: C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:cd:28:83:34:54:1b:89:f3:0f:af:37:91:31:ff: + af:31:60:c9:a8:e8:b2:10:68:ed:9f:e7:93:36:f1: + 0a:64:bb:47:f5:04:17:3f:23:47:4d:c5:27:19:81: + 26:0c:54:72:0d:88:2d:d9:1f:9a:12:9f:bc:b3:71: + d3:80:19:3f:47:66:7b:8c:35:28:d2:b9:0a:df:24: + da:9c:d6:50:79:81:7a:5a:d3:37:f7:c2:4a:d8:29: + 92:26:64:d1:e4:98:6c:3a:00:8a:f5:34:9b:65:f8: + ed:e3:10:ff:fd:b8:49:58:dc:a0:de:82:39:6b:81: + b1:16:19:61:b9:54:b6:e6:43 + Exponent: 3 (0x3) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/C=US/O=Entrust.net/OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority/CN=CRL1 + URI:http://www.entrust.net/CRL/net1.crl + + X509v3 Private Key Usage Period: + Not Before: May 25 16:09:40 1999 GMT, Not After: May 25 16:09:40 2019 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A + + X509v3 Subject Key Identifier: + F0:17:62:13:55:3D:B3:FF:0A:00:6B:FB:50:84:97:F3:ED:62:D0:1A + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0 +..V4.0.... + Signature Algorithm: sha1WithRSAEncryption + 90:dc:30:02:fa:64:74:c2:a7:0a:a5:7c:21:8d:34:17:a8:fb: + 47:0e:ff:25:7c:8d:13:0a:fb:e4:98:b5:ef:8c:f8:c5:10:0d: + f7:92:be:f1:c3:d5:d5:95:6a:04:bb:2c:ce:26:36:65:c8:31: + c6:e7:ee:3f:e3:57:75:84:7a:11:ef:46:4f:18:f4:d3:98:bb: + a8:87:32:ba:72:f6:3c:e2:3d:9f:d7:1d:d9:c3:60:43:8c:58: + 0e:22:96:2f:62:a3:2c:1f:ba:ad:05:ef:ab:32:78:87:a0:54: + 73:19:b5:5c:05:f9:52:3e:6d:2d:45:0b:f7:0a:93:ea:ed:06: + f9:b2 +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +Entrust.net Secure Personal CA +============================== + +MD5 Fingerprint=0C:41:2F:13:5B:A0:54:F5:96:66:2D:7E:CD:0E:03:F4 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 939758062 (0x380391ee) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, O=Entrust.net, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab., OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Client Certification Authority + Validity + Not Before: Oct 12 19:24:30 1999 GMT + Not After : Oct 12 19:54:30 2019 GMT + Subject: C=US, O=Entrust.net, OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab., OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Client Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:c8:3a:99:5e:31:17:df:ac:27:6f:90:7b:e4:19: + ff:45:a3:34:c2:db:c1:a8:4f:f0:68:ea:84:fd:9f: + 75:79:cf:c1:8a:51:94:af:c7:57:03:47:64:9e:ad: + 82:1b:5a:da:7f:37:78:47:bb:37:98:12:96:ce:c6: + 13:7d:ef:d2:0c:30:51:a9:39:9e:55:f8:fb:b1:e7: + 30:de:83:b2:ba:3e:f1:d5:89:3b:3b:85:ba:aa:74: + 2c:fe:3f:31:6e:af:91:95:6e:06:d4:07:4d:4b:2c: + 56:47:18:04:52:da:0e:10:93:bf:63:90:9b:e1:df: + 8c:e6:02:a4:e6:4f:5e:f7:8b + Exponent: 3 (0x3) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/C=US/O=Entrust.net/OU=www.entrust.net/Client_CA_Info/CPS incorp. by ref. limits liab./OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Client Certification Authority/CN=CRL1 + URI:http://www.entrust.net/CRL/Client1.crl + + X509v3 Private Key Usage Period: + Not Before: Oct 12 19:24:30 1999 GMT, Not After: Oct 12 19:24:30 2019 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:C4:FB:9C:29:7B:97:CD:4C:96:FC:EE:5B:B3:CA:99:74:8B:95:EA:4C + + X509v3 Subject Key Identifier: + C4:FB:9C:29:7B:97:CD:4C:96:FC:EE:5B:B3:CA:99:74:8B:95:EA:4C + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0 +..V4.0.... + Signature Algorithm: md5WithRSAEncryption + 3f:ae:8a:f1:d7:66:03:05:9e:3e:fa:ea:1c:46:bb:a4:5b:8f: + 78:9a:12:48:99:f9:f4:35:de:0c:36:07:02:6b:10:3a:89:14: + 81:9c:31:a6:7c:b2:41:b2:6a:e7:07:01:a1:4b:f9:9f:25:3b: + 96:ca:99:c3:3e:a1:51:1c:f3:c3:2e:44:f7:b0:67:46:aa:92: + e5:3b:da:1c:19:14:38:30:d5:e2:a2:31:25:2e:f1:ec:45:38: + ed:f8:06:58:03:73:62:b0:10:31:8f:40:bf:64:e0:5c:3e:c5: + 4f:1f:da:12:43:ff:4c:e6:06:26:a8:9b:19:aa:44:3c:76:b2: + 5c:ec +-----BEGIN CERTIFICATE----- +MIIE7TCCBFagAwIBAgIEOAOR7jANBgkqhkiG9w0BAQQFADCByTELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MUgwRgYDVQQLFD93d3cuZW50cnVzdC5u +ZXQvQ2xpZW50X0NBX0luZm8vQ1BTIGluY29ycC4gYnkgcmVmLiBsaW1pdHMgbGlh +Yi4xJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV +BAMTKkVudHJ1c3QubmV0IENsaWVudCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw05OTEwMTIxOTI0MzBaFw0xOTEwMTIxOTU0MzBaMIHJMQswCQYDVQQGEwJVUzEU +MBIGA1UEChMLRW50cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9D +bGllbnRfQ0FfSW5mby9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjEl +MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMq +RW50cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0G +CSqGSIb3DQEBAQUAA4GLADCBhwKBgQDIOpleMRffrCdvkHvkGf9FozTC28GoT/Bo +6oT9n3V5z8GKUZSvx1cDR2SerYIbWtp/N3hHuzeYEpbOxhN979IMMFGpOZ5V+Pux +5zDeg7K6PvHViTs7hbqqdCz+PzFur5GVbgbUB01LLFZHGARS2g4Qk79jkJvh34zm +AqTmT173iwIBA6OCAeAwggHcMBEGCWCGSAGG+EIBAQQEAwIABzCCASIGA1UdHwSC +ARkwggEVMIHkoIHhoIHepIHbMIHYMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50 +cnVzdC5uZXQxSDBGBgNVBAsUP3d3dy5lbnRydXN0Lm5ldC9DbGllbnRfQ0FfSW5m +by9DUFMgaW5jb3JwLiBieSByZWYuIGxpbWl0cyBsaWFiLjElMCMGA1UECxMcKGMp +IDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQg +Q2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCyg +KqAohiZodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NSTC9DbGllbnQxLmNybDArBgNV +HRAEJDAigA8xOTk5MTAxMjE5MjQzMFqBDzIwMTkxMDEyMTkyNDMwWjALBgNVHQ8E +BAMCAQYwHwYDVR0jBBgwFoAUxPucKXuXzUyW/O5bs8qZdIuV6kwwHQYDVR0OBBYE +FMT7nCl7l81MlvzuW7PKmXSLlepMMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA +BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEEBQADgYEAP66K8ddmAwWePvrqHEa7 +pFuPeJoSSJn59DXeDDYHAmsQOokUgZwxpnyyQbJq5wcBoUv5nyU7lsqZwz6hURzz +wy5E97BnRqqS5TvaHBkUODDV4qIxJS7x7EU47fgGWANzYrAQMY9Av2TgXD7FTx/a +EkP/TOYGJqibGapEPHayXOw= +-----END CERTIFICATE----- + +Entrust.net Premium 2048 Secure Server CA +========================================= + +MD5 Fingerprint=BA:21:EA:20:D6:DD:DB:8F:C1:57:8B:40:AD:A1:FC:FC +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 946059622 (0x3863b966) + Signature Algorithm: sha1WithRSAEncryption + Issuer: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048) + Validity + Not Before: Dec 24 17:50:51 1999 GMT + Not After : Dec 24 18:20:51 2019 GMT + Subject: O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.net Certification Authority (2048) + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ad:4d:4b:a9:12:86:b2:ea:a3:20:07:15:16:64: + 2a:2b:4b:d1:bf:0b:4a:4d:8e:ed:80:76:a5:67:b7: + 78:40:c0:73:42:c8:68:c0:db:53:2b:dd:5e:b8:76: + 98:35:93:8b:1a:9d:7c:13:3a:0e:1f:5b:b7:1e:cf: + e5:24:14:1e:b1:81:a9:8d:7d:b8:cc:6b:4b:03:f1: + 02:0c:dc:ab:a5:40:24:00:7f:74:94:a1:9d:08:29: + b3:88:0b:f5:87:77:9d:55:cd:e4:c3:7e:d7:6a:64: + ab:85:14:86:95:5b:97:32:50:6f:3d:c8:ba:66:0c: + e3:fc:bd:b8:49:c1:76:89:49:19:fd:c0:a8:bd:89: + a3:67:2f:c6:9f:bc:71:19:60:b8:2d:e9:2c:c9:90: + 76:66:7b:94:e2:af:78:d6:65:53:5d:3c:d6:9c:b2: + cf:29:03:f9:2f:a4:50:b2:d4:48:ce:05:32:55:8a: + fd:b2:64:4c:0e:e4:98:07:75:db:7f:df:b9:08:55: + 60:85:30:29:f9:7b:48:a4:69:86:e3:35:3f:1e:86: + 5d:7a:7a:15:bd:ef:00:8e:15:22:54:17:00:90:26: + 93:bc:0e:49:68:91:bf:f8:47:d3:9d:95:42:c1:0e: + 4d:df:6f:26:cf:c3:18:21:62:66:43:70:d6:d5:c0: + 07:e1 + Exponent: 65537 (0x10001) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Authority Key Identifier: + keyid:55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70 + + X509v3 Subject Key Identifier: + 55:E4:81:D1:11:80:BE:D8:89:B9:08:A3:31:F9:A1:24:09:16:B9:70 + 1.2.840.113533.7.65.0: + 0...V5.0:4.0.... + Signature Algorithm: sha1WithRSAEncryption + 59:47:ac:21:84:8a:17:c9:9c:89:53:1e:ba:80:85:1a:c6:3c: + 4e:3e:b1:9c:b6:7c:c6:92:5d:18:64:02:e3:d3:06:08:11:61: + 7c:63:e3:2b:9d:31:03:70:76:d2:a3:28:a0:f4:bb:9a:63:73: + ed:6d:e5:2a:db:ed:14:a9:2b:c6:36:11:d0:2b:eb:07:8b:a5: + da:9e:5c:19:9d:56:12:f5:54:29:c8:05:ed:b2:12:2a:8d:f4: + 03:1b:ff:e7:92:10:87:b0:3a:b5:c3:9d:05:37:12:a3:c7:f4: + 15:b9:d5:a4:39:16:9b:53:3a:23:91:f1:a8:82:a2:6a:88:68: + c1:79:02:22:bc:aa:a6:d6:ae:df:b0:14:5f:b8:87:d0:dd:7c: + 7f:7b:ff:af:1c:cf:e6:db:07:ad:5e:db:85:9d:d0:2b:0d:33: + db:04:d1:e6:49:40:13:2b:76:fb:3e:e9:9c:89:0f:15:ce:18: + b0:85:78:21:4f:6b:4f:0e:fa:36:67:cd:07:f2:ff:08:d0:e2: + de:d9:bf:2a:af:b8:87:86:21:3c:04:ca:b7:94:68:7f:cf:3c: + e9:98:d7:38:ff:ec:c0:d9:50:f0:2e:4b:58:ae:46:6f:d0:2e: + c3:60:da:72:55:72:bd:4c:45:9e:61:ba:bf:84:81:92:03:d1: + d2:69:7c:c5 +-----BEGIN CERTIFICATE----- +MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy +MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA +vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G +CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA +WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo +oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ +h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18 +f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN +B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy +vUxFnmG6v4SBkgPR0ml8xQ== +-----END CERTIFICATE----- + +Baltimore CyberTrust Root +========================= + +MD5 Fingerprint=AC:B6:94:A5:9C:17:E0:D7:91:52:9B:B1:97:06:A6:E4 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 33554617 (0x20000b9) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root + Validity + Not Before: May 12 18:46:00 2000 GMT + Not After : May 12 23:59:00 2025 GMT + Subject: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:a3:04:bb:22:ab:98:3d:57:e8:26:72:9a:b5:79: + d4:29:e2:e1:e8:95:80:b1:b0:e3:5b:8e:2b:29:9a: + 64:df:a1:5d:ed:b0:09:05:6d:db:28:2e:ce:62:a2: + 62:fe:b4:88:da:12:eb:38:eb:21:9d:c0:41:2b:01: + 52:7b:88:77:d3:1c:8f:c7:ba:b9:88:b5:6a:09:e7: + 73:e8:11:40:a7:d1:cc:ca:62:8d:2d:e5:8f:0b:a6: + 50:d2:a8:50:c3:28:ea:f5:ab:25:87:8a:9a:96:1c: + a9:67:b8:3f:0c:d5:f7:f9:52:13:2f:c2:1b:d5:70: + 70:f0:8f:c0:12:ca:06:cb:9a:e1:d9:ca:33:7a:77: + d6:f8:ec:b9:f1:68:44:42:48:13:d2:c0:c2:a4:ae: + 5e:60:fe:b6:a6:05:fc:b4:dd:07:59:02:d4:59:18: + 98:63:f5:a5:63:e0:90:0c:7d:5d:b2:06:7a:f3:85: + ea:eb:d4:03:ae:5e:84:3e:5f:ff:15:ed:69:bc:f9: + 39:36:72:75:cf:77:52:4d:f3:c9:90:2c:b9:3d:e5: + c9:23:53:3f:1f:24:98:21:5c:07:99:29:bd:c6:3a: + ec:e7:6e:86:3a:6b:97:74:63:33:bd:68:18:31:f0: + 78:8d:76:bf:fc:9e:8e:5d:2a:86:a7:4d:90:dc:27: + 1a:39 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + E5:9D:59:30:82:47:58:CC:AC:FA:08:54:36:86:7B:3A:B5:04:4D:F0 + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:3 + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 85:0c:5d:8e:e4:6f:51:68:42:05:a0:dd:bb:4f:27:25:84:03: + bd:f7:64:fd:2d:d7:30:e3:a4:10:17:eb:da:29:29:b6:79:3f: + 76:f6:19:13:23:b8:10:0a:f9:58:a4:d4:61:70:bd:04:61:6a: + 12:8a:17:d5:0a:bd:c5:bc:30:7c:d6:e9:0c:25:8d:86:40:4f: + ec:cc:a3:7e:38:c6:37:11:4f:ed:dd:68:31:8e:4c:d2:b3:01: + 74:ee:be:75:5e:07:48:1a:7f:70:ff:16:5c:84:c0:79:85:b8: + 05:fd:7f:be:65:11:a3:0f:c0:02:b4:f8:52:37:39:04:d5:a9: + 31:7a:18:bf:a0:2a:f4:12:99:f7:a3:45:82:e3:3c:5e:f5:9d: + 9e:b5:c8:9e:7c:2e:c8:a4:9e:4e:08:14:4b:6d:fd:70:6d:6b: + 1a:63:bd:64:e6:1f:b7:ce:f0:f2:9f:2e:bb:1b:b7:f2:50:88: + 73:92:c2:e2:e3:16:8d:9a:32:02:ab:8e:18:dd:e9:10:11:ee: + 7e:35:ab:90:af:3e:30:94:7a:d0:33:3d:a7:65:0f:f5:fc:8e: + 9e:62:cf:47:44:2c:01:5d:bb:1d:b5:32:d2:47:d2:38:2e:d0: + fe:81:dc:32:6a:1e:b5:ee:3c:d5:fc:e7:81:1d:19:c3:24:42: + ea:63:39:a9 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +Equifax Secure Global eBusiness CA +================================== + +MD5 Fingerprint=8F:5D:77:06:27:C4:98:3C:5B:93:78:E7:D7:7D:9B:CC +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1 + Validity + Not Before: Jun 21 04:00:00 1999 GMT + Not After : Jun 21 04:00:00 2020 GMT + Subject: C=US, O=Equifax Secure Inc., CN=Equifax Secure Global eBusiness CA-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:ba:e7:17:90:02:65:b1:34:55:3c:49:c2:51:d5: + df:a7:d1:37:8f:d1:e7:81:73:41:52:60:9b:9d:a1: + 17:26:78:ad:c7:b1:e8:26:94:32:b5:de:33:8d:3a: + 2f:db:f2:9a:7a:5a:73:98:a3:5c:e9:fb:8a:73:1b: + 5c:e7:c3:bf:80:6c:cd:a9:f4:d6:2b:c0:f7:f9:99: + aa:63:a2:b1:47:02:0f:d4:e4:51:3a:12:3c:6c:8a: + 5a:54:84:70:db:c1:c5:90:cf:72:45:cb:a8:59:c0: + cd:33:9d:3f:a3:96:eb:85:33:21:1c:3e:1e:3e:60: + 6e:76:9c:67:85:c5:c8:c3:61 + Exponent: 65537 (0x10001) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Authority Key Identifier: + keyid:BE:A8:A0:74:72:50:6B:44:B7:C9:23:D8:FB:A8:FF:B3:57:6B:68:6C + + X509v3 Subject Key Identifier: + BE:A8:A0:74:72:50:6B:44:B7:C9:23:D8:FB:A8:FF:B3:57:6B:68:6C + Signature Algorithm: md5WithRSAEncryption + 30:e2:01:51:aa:c7:ea:5f:da:b9:d0:65:0f:30:d6:3e:da:0d: + 14:49:6e:91:93:27:14:31:ef:c4:f7:2d:45:f8:ec:c7:bf:a2: + 41:0d:23:b4:92:f9:19:00:67:bd:01:af:cd:e0:71:fc:5a:cf: + 64:c4:e0:96:98:d0:a3:40:e2:01:8a:ef:27:07:f1:65:01:8a: + 44:2d:06:65:75:52:c0:86:10:20:21:5f:6c:6b:0f:6c:ae:09: + 1c:af:f2:a2:18:34:c4:75:a4:73:1c:f1:8d:dc:ef:ad:f9:b3: + 76:b4:92:bf:dc:95:10:1e:be:cb:c8:3b:5a:84:60:19:56:94: + a9:55 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT +ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw +MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj +dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l +c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC +UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc +58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ +o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr +aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA +A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA +Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv +8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 1 +============================= + +MD5 Fingerprint=64:9C:EF:2E:44:FC:C6:8F:52:07:D0:51:73:8F:CB:3D +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, O=Equifax Secure Inc., CN=Equifax Secure eBusiness CA-1 + Validity + Not Before: Jun 21 04:00:00 1999 GMT + Not After : Jun 21 04:00:00 2020 GMT + Subject: C=US, O=Equifax Secure Inc., CN=Equifax Secure eBusiness CA-1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:ce:2f:19:bc:17:b7:77:de:93:a9:5f:5a:0d:17: + 4f:34:1a:0c:98:f4:22:d9:59:d4:c4:68:46:f0:b4: + 35:c5:85:03:20:c6:af:45:a5:21:51:45:41:eb:16: + 58:36:32:6f:e2:50:62:64:f9:fd:51:9c:aa:24:d9: + f4:9d:83:2a:87:0a:21:d3:12:38:34:6c:8d:00:6e: + 5a:a0:d9:42:ee:1a:21:95:f9:52:4c:55:5a:c5:0f: + 38:4f:46:fa:6d:f8:2e:35:d6:1d:7c:eb:e2:f0:b0: + 75:80:c8:a9:13:ac:be:88:ef:3a:6e:ab:5f:2a:38: + 62:02:b0:12:7b:fe:8f:a6:03 + Exponent: 65537 (0x10001) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Authority Key Identifier: + keyid:4A:78:32:52:11:DB:59:16:36:5E:DF:C1:14:36:40:6A:47:7C:4C:A1 + + X509v3 Subject Key Identifier: + 4A:78:32:52:11:DB:59:16:36:5E:DF:C1:14:36:40:6A:47:7C:4C:A1 + Signature Algorithm: md5WithRSAEncryption + 75:5b:a8:9b:03:11:e6:e9:56:4c:cd:f9:a9:4c:c0:0d:9a:f3: + cc:65:69:e6:25:76:cc:59:b7:d6:54:c3:1d:cd:99:ac:19:dd: + b4:85:d5:e0:3d:fc:62:20:a7:84:4b:58:65:f1:e2:f9:95:21: + 3f:f5:d4:7e:58:1e:47:87:54:3e:58:a1:b5:b5:f8:2a:ef:71: + e7:bc:c3:f6:b1:49:46:e2:d7:a0:6b:e5:56:7a:9a:27:98:7c: + 46:62:14:e7:c9:fc:6e:03:12:79:80:38:1d:48:82:8d:fc:17: + fe:2a:96:2b:b5:62:a6:a6:3d:bd:7f:92:59:cd:5a:2a:82:b2: + 37:79 +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT +ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw +MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j +LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo +RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu +WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw +Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD +AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK +eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM +zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ +WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN +/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +Equifax Secure eBusiness CA 2 +============================= + +MD5 Fingerprint=AA:BF:BF:64:97:DA:98:1D:6F:C6:08:3A:95:70:33:CA +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 930140085 (0x3770cfb5) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=Equifax Secure, OU=Equifax Secure eBusiness CA-2 + Validity + Not Before: Jun 23 12:14:45 1999 GMT + Not After : Jun 23 12:14:45 2019 GMT + Subject: C=US, O=Equifax Secure, OU=Equifax Secure eBusiness CA-2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:e4:39:39:93:1e:52:06:1b:28:36:f8:b2:a3:29: + c5:ed:8e:b2:11:bd:fe:eb:e7:b4:74:c2:8f:ff:05: + e7:d9:9d:06:bf:12:c8:3f:0e:f2:d6:d1:24:b2:11: + de:d1:73:09:8a:d4:b1:2c:98:09:0d:1e:50:46:b2: + 83:a6:45:8d:62:68:bb:85:1b:20:70:32:aa:40:cd: + a6:96:5f:c4:71:37:3f:04:f3:b7:41:24:39:07:1a: + 1e:2e:61:58:a0:12:0b:e5:a5:df:c5:ab:ea:37:71: + cc:1c:c8:37:3a:b9:97:52:a7:ac:c5:6a:24:94:4e: + 9c:7b:cf:c0:6a:d6:df:21:bd + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 CRL Distribution Points: + DirName:/C=US/O=Equifax Secure/OU=Equifax Secure eBusiness CA-2/CN=CRL1 + + X509v3 Private Key Usage Period: + Not After: Jun 23 12:14:45 2019 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:50:9E:0B:EA:AF:5E:B9:20:48:A6:50:6A:CB:FD:D8:20:7A:A7:82:76 + + X509v3 Subject Key Identifier: + 50:9E:0B:EA:AF:5E:B9:20:48:A6:50:6A:CB:FD:D8:20:7A:A7:82:76 + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0...V3.0c.... + Signature Algorithm: sha1WithRSAEncryption + 0c:86:82:ad:e8:4e:1a:f5:8e:89:27:e2:35:58:3d:29:b4:07: + 8f:36:50:95:bf:6e:c1:9e:eb:c4:90:b2:85:a8:bb:b7:42:e0: + 0f:07:39:df:fb:9e:90:b2:d1:c1:3e:53:9f:03:44:b0:7e:4b: + f4:6f:e4:7c:1f:e7:e2:b1:e4:b8:9a:ef:c3:bd:ce:de:0b:32: + 34:d9:de:28:ed:33:6b:c4:d4:d7:3d:12:58:ab:7d:09:2d:cb: + 70:f5:13:8a:94:a1:27:a4:d6:70:c5:6d:94:b5:c9:7d:9d:a0: + d2:c6:08:49:d9:66:9b:a6:d3:f4:0b:dc:c5:26:57:e1:91:30: + ea:cd +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj +dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0 +NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD +VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G +vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/ +BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX +MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl +IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw +NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq +y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy +0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1 +E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN +-----END CERTIFICATE----- + +Visa International Global Root 2 +================================ + +MD5 Fingerprint=35:48:95:36:4A:54:5A:72:96:8E:E0:64:CC:EF:2C:8C +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 798 (0x31e) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VISA, OU=Visa International Service Association, CN=GP Root 2 + Validity + Not Before: Aug 16 22:51:00 2000 GMT + Not After : Aug 15 23:59:00 2020 GMT + Subject: C=US, O=VISA, OU=Visa International Service Association, CN=GP Root 2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:a9:01:70:b5:aa:c4:40:f0:ab:6a:26:61:79:19: + 00:fc:bf:9b:37:59:0c:af:6f:64:1b:f8:da:95:94: + 24:69:33:11:70:ca:e3:56:74:a2:17:57:64:5c:20: + 06:e1:d6:ef:71:b7:3b:f7:ab:c1:69:d0:49:a4:b1: + 04:d7:f4:57:62:89:5c:b0:75:2d:17:24:69:e3:42: + 60:e4:ee:74:d6:ab:80:56:d8:88:28:e1:fb:6d:22: + fd:23:7c:46:73:4f:7e:54:73:1e:a8:2c:55:58:75: + b7:4c:f3:5a:45:a5:02:1a:fa:da:9d:c3:45:c3:22: + 5e:f3:8b:f1:60:29:d2:c7:5f:b4:0c:3a:51:83:ef: + 30:f8:d4:e7:c7:f2:fa:99:a3:22:50:be:f9:05:37: + a3:ad:ed:9a:c3:e6:ec:88:1b:b6:19:27:1b:38:8b: + 80:4d:ec:b9:c7:c5:89:cb:fc:1a:32:ed:23:f0:b5: + 01:58:f9:f6:8f:e0:85:a9:4c:09:72:39:12:db:b3: + f5:cf:4e:62:64:da:c6:19:15:3a:63:1d:e9:17:55: + a1:4c:22:3c:34:32:46:f8:65:57:ba:2b:ef:36:8c: + 6a:fa:d9:d9:44:f4:aa:dd:84:d7:0d:1c:b2:54:ac: + 32:85:b4:64:0d:de:41:bb:b1:34:c6:01:86:32:64: + d5:9f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 9E:7D:4B:34:BF:71:AD:C2:05:F6:03:75:80:CE:A9:4F:1A:C4:24:4C + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 21:a5:76:14:55:f9:ad:27:70:8f:3c:f4:d5:6c:c8:cc:0a:ab: + a3:98:0b:8a:06:23:c5:c9:61:db:99:07:69:35:26:31:fe:c7: + 2e:84:c2:99:61:d4:0d:e9:7d:2e:13:2b:7c:8e:85:b6:85:c7: + 4b:cf:35:b6:2c:47:3d:ce:29:2f:d8:6f:9f:89:1c:64:93:bf: + 08:bd:76:d0:90:8a:94:b3:7f:28:5b:6e:ac:4d:33:2c:ed:65: + dc:16:cc:e2:cd:ae:a4:3d:62:92:06:95:26:bf:df:b9:e4:20: + a6:73:6a:c1:be:f7:94:44:d6:4d:6f:2a:0b:6b:18:4d:74:10: + 36:68:6a:5a:c1:6a:a7:dd:36:29:8c:b8:30:8b:4f:21:3f:00: + 2e:54:30:07:3a:ba:8a:e4:c3:9e:ca:d8:b5:d8:7b:ce:75:45: + 66:07:f4:6d:2d:d8:7a:ca:e9:89:8a:f2:23:d8:2f:cb:6e:00: + 36:4f:fb:f0:2f:01:cc:0f:c0:22:65:f4:ab:e2:4e:61:2d:03: + 82:7d:91:16:b5:30:d5:14:de:5e:c7:90:fc:a1:fc:ab:10:af: + 5c:6b:70:a7:07:ef:29:86:e8:b2:25:c7:20:ff:26:dd:77:ef: + 79:44:14:c4:bd:dd:3b:c5:03:9b:77:23:ec:a0:ec:bb:5a:39: + b5:cc:ad:06 +-----BEGIN CERTIFICATE----- +MIIDgDCCAmigAwIBAgICAx4wDQYJKoZIhvcNAQEFBQAwYTELMAkGA1UEBhMCVVMx +DTALBgNVBAoTBFZJU0ExLzAtBgNVBAsTJlZpc2EgSW50ZXJuYXRpb25hbCBTZXJ2 +aWNlIEFzc29jaWF0aW9uMRIwEAYDVQQDEwlHUCBSb290IDIwHhcNMDAwODE2MjI1 +MTAwWhcNMjAwODE1MjM1OTAwWjBhMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklT +QTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRp +b24xEjAQBgNVBAMTCUdQIFJvb3QgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAKkBcLWqxEDwq2omYXkZAPy/mzdZDK9vZBv42pWUJGkzEXDK41Z0ohdX +ZFwgBuHW73G3O/erwWnQSaSxBNf0V2KJXLB1LRckaeNCYOTudNargFbYiCjh+20i +/SN8RnNPflRzHqgsVVh1t0zzWkWlAhr62p3DRcMiXvOL8WAp0sdftAw6UYPvMPjU +58fy+pmjIlC++QU3o63tmsPm7IgbthknGziLgE3sucfFicv8GjLtI/C1AVj59o/g +halMCXI5Etuz9c9OYmTaxhkVOmMd6RdVoUwiPDQyRvhlV7or7zaMavrZ2UT0qt2E +1w0cslSsMoW0ZA3eQbuxNMYBhjJk1Z8CAwEAAaNCMEAwHQYDVR0OBBYEFJ59SzS/ +ca3CBfYDdYDOqU8axCRMMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQAhpXYUVfmtJ3CPPPTVbMjMCqujmAuKBiPFyWHb +mQdpNSYx/scuhMKZYdQN6X0uEyt8joW2hcdLzzW2LEc9zikv2G+fiRxkk78IvXbQ +kIqUs38oW26sTTMs7WXcFsziza6kPWKSBpUmv9+55CCmc2rBvveURNZNbyoLaxhN +dBA2aGpawWqn3TYpjLgwi08hPwAuVDAHOrqK5MOeyti12HvOdUVmB/RtLdh6yumJ +ivIj2C/LbgA2T/vwLwHMD8AiZfSr4k5hLQOCfZEWtTDVFN5ex5D8ofyrEK9ca3Cn +B+8phuiyJccg/ybdd+95RBTEvd07xQObdyPsoOy7Wjm1zK0G +-----END CERTIFICATE----- + +beTRUSTed Root CA +================= + +MD5 Fingerprint=85:CA:76:5A:1B:D1:68:22:DC:A2:23:12:CA:C6:80:34 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 961510791 (0x394f7d87) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=WW, O=beTRUSTed, CN=beTRUSTed Root CAs, CN=beTRUSTed Root CA + Validity + Not Before: Jun 20 14:21:04 2000 GMT + Not After : Jun 20 13:21:04 2010 GMT + Subject: C=WW, O=beTRUSTed, CN=beTRUSTed Root CAs, CN=beTRUSTed Root CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:d4:b4:73:7a:13:0a:38:55:01:be:89:56:e1:94: + 9e:d4:be:5a:eb:4a:34:75:1b:61:29:c4:e1:ad:08: + 60:21:78:48:ff:b4:d0:fa:5e:41:8d:61:44:87:e8: + ed:c9:58:fa:fc:93:9a:df:4f:ea:3e:35:7d:f8:33: + 7a:e6:f1:d7:cd:6f:49:4b:3d:4f:2d:6e:0e:83:3a: + 18:78:77:a3:cf:e7:f4:4d:73:d8:9a:3b:1a:1d:be: + 95:53:cf:20:97:c2:cf:3e:24:52:6c:0c:8e:65:59: + c5:71:ff:62:09:8f:aa:c5:8f:cc:60:a0:73:4a:d7: + 38:3f:15:72:bf:a2:97:b7:70:e8:af:e2:7e:16:06: + 4c:f5:aa:64:26:72:07:25:ad:35:fc:18:b1:26:d7: + d8:ff:19:0e:83:1b:8c:dc:78:45:67:34:3d:f4:af: + 1c:8d:e4:6d:6b:ed:20:b3:67:9a:b4:61:cb:17:6f: + 89:35:ff:e7:4e:c0:32:12:e7:ee:ec:df:ff:97:30: + 74:ed:8d:47:8e:eb:b4:c3:44:e6:a7:4c:7f:56:43: + e8:b8:bc:b6:be:fa:83:97:e6:bb:fb:c4:b6:93:be: + 19:18:3e:8c:81:b9:73:88:16:f4:96:43:9c:67:73: + 17:90:d8:09:6e:63:ac:4a:b6:23:c4:01:a1:ad:a4: + e4:c5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.6334.1.0.0 + User Notice: + Explicit Text: Reliance on this certificate by any party assumes acceptance of the then applicable standard terms and conditions of use, and certification practice statement, which can be found at beTRUSTed's web site, https://www.beTRUSTed.com/vault/terms + CPS: https://www.beTRUSTed.com/vault/terms + + X509v3 CRL Distribution Points: + DirName:/O=beTRUSTed/C=WW + + X509v3 Subject Key Identifier: + 2A:B9:9B:69:2E:3B:9B:D8:CD:DE:2A:31:04:34:6B:CA:07:18:AB:67 + X509v3 Authority Key Identifier: + keyid:2A:B9:9B:69:2E:3B:9B:D8:CD:DE:2A:31:04:34:6B:CA:07:18:AB:67 + + X509v3 Key Usage: critical + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 79:61:db:a3:5e:6e:16:b1:ea:76:51:f9:cb:15:9b:cb:69:be: + e6:81:6b:9f:28:1f:65:3e:dd:11:85:92:d4:e8:41:bf:7e:33: + bd:23:e7:f1:20:bf:a4:b4:a6:19:01:c6:8c:8d:35:7c:65:a4: + 4f:09:a4:d6:d8:23:15:05:13:a7:43:79:af:db:a3:0e:9b:7b: + 78:1a:f3:04:86:5a:c6:f6:8c:20:47:38:49:50:06:9d:72:67: + 3a:f0:98:03:ad:96:67:44:fc:3f:10:0d:86:4d:e4:00:3b:29: + 7b:ce:3b:3b:99:86:61:25:40:84:dc:13:62:b7:fa:ca:59:d6: + 03:1e:d6:53:01:cd:6d:4c:68:55:40:e1:ee:6b:c7:2a:00:00: + 48:82:b3:0a:01:c3:60:2a:0c:f7:82:35:ee:48:86:96:e4:74: + d4:3d:ea:01:71:ba:04:75:40:a7:a9:7f:39:39:9a:55:97:29: + 65:ae:19:55:25:05:72:47:d3:e8:18:dc:b8:e9:af:43:73:01: + 12:74:a3:e1:5c:5f:15:5d:24:f3:f9:e4:f4:b6:67:67:12:e7: + 64:22:8a:f6:a5:41:a6:1c:b6:60:63:45:8a:10:b4:ba:46:10: + ae:41:57:65:6c:3f:23:10:3f:21:10:59:b7:e4:40:dd:26:0c: + 23:f6:aa:ae +-----BEGIN CERTIFICATE----- +MIIFLDCCBBSgAwIBAgIEOU99hzANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJX +VzESMBAGA1UEChMJYmVUUlVTVGVkMRswGQYDVQQDExJiZVRSVVNUZWQgUm9vdCBD +QXMxGjAYBgNVBAMTEWJlVFJVU1RlZCBSb290IENBMB4XDTAwMDYyMDE0MjEwNFoX +DTEwMDYyMDEzMjEwNFowWjELMAkGA1UEBhMCV1cxEjAQBgNVBAoTCWJlVFJVU1Rl +ZDEbMBkGA1UEAxMSYmVUUlVTVGVkIFJvb3QgQ0FzMRowGAYDVQQDExFiZVRSVVNU +ZWQgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANS0c3oT +CjhVAb6JVuGUntS+WutKNHUbYSnE4a0IYCF4SP+00PpeQY1hRIfo7clY+vyTmt9P +6j41ffgzeubx181vSUs9Ty1uDoM6GHh3o8/n9E1z2Jo7Gh2+lVPPIJfCzz4kUmwM +jmVZxXH/YgmPqsWPzGCgc0rXOD8Vcr+il7dw6K/ifhYGTPWqZCZyByWtNfwYsSbX +2P8ZDoMbjNx4RWc0PfSvHI3kbWvtILNnmrRhyxdviTX/507AMhLn7uzf/5cwdO2N +R47rtMNE5qdMf1ZD6Li8tr76g5fmu/vEtpO+GRg+jIG5c4gW9JZDnGdzF5DYCW5j +rEq2I8QBoa2k5MUCAwEAAaOCAfgwggH0MA8GA1UdEwEB/wQFMAMBAf8wggFZBgNV +HSAEggFQMIIBTDCCAUgGCisGAQQBsT4BAAAwggE4MIIBAQYIKwYBBQUHAgIwgfQa +gfFSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1 +bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0 +ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGFuZCBjZXJ0aWZpY2F0aW9uIHBy +YWN0aWNlIHN0YXRlbWVudCwgd2hpY2ggY2FuIGJlIGZvdW5kIGF0IGJlVFJVU1Rl +ZCdzIHdlYiBzaXRlLCBodHRwczovL3d3dy5iZVRSVVNUZWQuY29tL3ZhdWx0L3Rl +cm1zMDEGCCsGAQUFBwIBFiVodHRwczovL3d3dy5iZVRSVVNUZWQuY29tL3ZhdWx0 +L3Rlcm1zMDQGA1UdHwQtMCswKaAnoCWkIzAhMRIwEAYDVQQKEwliZVRSVVNUZWQx +CzAJBgNVBAYTAldXMB0GA1UdDgQWBBQquZtpLjub2M3eKjEENGvKBxirZzAfBgNV +HSMEGDAWgBQquZtpLjub2M3eKjEENGvKBxirZzAOBgNVHQ8BAf8EBAMCAf4wDQYJ +KoZIhvcNAQEFBQADggEBAHlh26Nebhax6nZR+csVm8tpvuaBa58oH2U+3RGFktTo +Qb9+M70j5/Egv6S0phkBxoyNNXxlpE8JpNbYIxUFE6dDea/bow6be3ga8wSGWsb2 +jCBHOElQBp1yZzrwmAOtlmdE/D8QDYZN5AA7KXvOOzuZhmElQITcE2K3+spZ1gMe +1lMBzW1MaFVA4e5rxyoAAEiCswoBw2AqDPeCNe5IhpbkdNQ96gFxugR1QKepfzk5 +mlWXKWWuGVUlBXJH0+gY3Ljpr0NzARJ0o+FcXxVdJPP55PS2Z2cS52QiivalQaYc +tmBjRYoQtLpGEK5BV2VsPyMQPyEQWbfkQN0mDCP2qq4= +-----END CERTIFICATE----- + +AddTrust Low-Value Services Root +================================ + +MD5 Fingerprint=1E:42:95:02:33:92:6B:B9:5F:C0:7F:DA:D6:B2:4B:FC +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, O=AddTrust AB, OU=AddTrust TTP Network, CN=AddTrust Class 1 CA Root + Validity + Not Before: May 30 10:38:31 2000 GMT + Not After : May 30 10:38:31 2020 GMT + Subject: C=SE, O=AddTrust AB, OU=AddTrust TTP Network, CN=AddTrust Class 1 CA Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:96:96:d4:21:49:60:e2:6b:e8:41:07:0c:de:c4: + e0:dc:13:23:cd:c1:35:c7:fb:d6:4e:11:0a:67:5e: + f5:06:5b:6b:a5:08:3b:5b:29:16:3a:e7:87:b2:34: + 06:c5:bc:05:a5:03:7c:82:cb:29:10:ae:e1:88:81: + bd:d6:9e:d3:fe:2d:56:c1:15:ce:e3:26:9d:15:2e: + 10:fb:06:8f:30:04:de:a7:b4:63:b4:ff:b1:9c:ae: + 3c:af:77:b6:56:c5:b5:ab:a2:e9:69:3a:3d:0e:33: + 79:32:3f:70:82:92:99:61:6d:8d:30:08:8f:71:3f: + a6:48:57:19:f8:25:dc:4b:66:5c:a5:74:8f:98:ae: + c8:f9:c0:06:22:e7:ac:73:df:a5:2e:fb:52:dc:b1: + 15:65:20:fa:35:66:69:de:df:2c:f1:6e:bc:30:db: + 2c:24:12:db:eb:35:35:68:90:cb:00:b0:97:21:3d: + 74:21:23:65:34:2b:bb:78:59:a3:d6:e1:76:39:9a: + a4:49:8e:8c:74:af:6e:a4:9a:a3:d9:9b:d2:38:5c: + 9b:a2:18:cc:75:23:84:be:eb:e2:4d:33:71:8e:1a: + f0:c2:f8:c7:1d:a2:ad:03:97:2c:f8:cf:25:c6:f6: + b8:24:31:b1:63:5d:92:7f:63:f0:25:c9:53:2e:1f: + bf:4d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 95:B1:B4:F0:94:B6:BD:C7:DA:D1:11:09:21:BE:C1:AF:49:FD:10:7B + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Authority Key Identifier: + keyid:95:B1:B4:F0:94:B6:BD:C7:DA:D1:11:09:21:BE:C1:AF:49:FD:10:7B + DirName:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Class 1 CA Root + serial:01 + + Signature Algorithm: sha1WithRSAEncryption + 2c:6d:64:1b:1f:cd:0d:dd:b9:01:fa:96:63:34:32:48:47:99: + ae:97:ed:fd:72:16:a6:73:47:5a:f4:eb:dd:e9:f5:d6:fb:45: + cc:29:89:44:5d:bf:46:39:3d:e8:ee:bc:4d:54:86:1e:1d:6c: + e3:17:27:43:e1:89:56:2b:a9:6f:72:4e:49:33:e3:72:7c:2a: + 23:9a:bc:3e:ff:28:2a:ed:a3:ff:1c:23:ba:43:57:09:67:4d: + 4b:62:06:2d:f8:ff:6c:9d:60:1e:d8:1c:4b:7d:b5:31:2f:d9: + d0:7c:5d:f8:de:6b:83:18:78:37:57:2f:e8:33:07:67:df:1e: + c7:6b:2a:95:76:ae:8f:57:a3:f0:f4:52:b4:a9:53:08:cf:e0: + 4f:d3:7a:53:8b:fd:bb:1c:56:36:f2:fe:b2:b6:e5:76:bb:d5: + 22:65:a7:3f:fe:d1:66:ad:0b:bc:6b:99:86:ef:3f:7d:f3:18: + 32:ca:7b:c6:e3:ab:64:46:95:f8:26:69:d9:55:83:7b:2c:96: + 07:ff:59:2c:44:a3:c6:e5:e9:a9:dc:a1:63:80:5a:21:5e:21: + cf:53:54:f0:ba:6f:89:db:a8:aa:95:cf:8b:e3:71:cc:1e:1b: + 20:44:08:c0:7a:b6:40:fd:c4:e4:35:e1:1d:16:1c:d0:bc:2b: + 8e:d6:71:d9 +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw +MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD +VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul +CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n +tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl +dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch +PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC ++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O +BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk +ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X +7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz +43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl +pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA +WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +Thawte Personal Basic CA +======================== + +MD5 Fingerprint=E6:0B:D2:C9:CA:2D:88:DB:1A:71:0E:4B:78:EB:02:41 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting, OU=Certification Services Division, CN=Thawte Personal Basic CA/emailAddress=personal-basic@thawte.com + Validity + Not Before: Jan 1 00:00:00 1996 GMT + Not After : Dec 31 23:59:59 2020 GMT + Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting, OU=Certification Services Division, CN=Thawte Personal Basic CA/emailAddress=personal-basic@thawte.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:bc:bc:93:53:6d:c0:50:4f:82:15:e6:48:94:35: + a6:5a:be:6f:42:fa:0f:47:ee:77:75:72:dd:8d:49: + 9b:96:57:a0:78:d4:ca:3f:51:b3:69:0b:91:76:17: + 22:07:97:6a:c4:51:93:4b:e0:8d:ef:37:95:a1:0c: + 4d:da:34:90:1d:17:89:97:e0:35:38:57:4a:c0:f4: + 08:70:e9:3c:44:7b:50:7e:61:9a:90:e3:23:d3:88: + 11:46:27:f5:0b:07:0e:bb:dd:d1:7f:20:0a:88:b9: + 56:0b:2e:1c:80:da:f1:e3:9e:29:ef:14:bd:0a:44: + fb:1b:5b:18:d1:bf:23:93:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: md5WithRSAEncryption + 2d:e2:99:6b:b0:3d:7a:89:d7:59:a2:94:01:1f:2b:dd:12:4b: + 53:c2:ad:7f:aa:a7:00:5c:91:40:57:25:4a:38:aa:84:70:b9: + d9:80:0f:a5:7b:5c:fb:73:c6:bd:d7:8a:61:5c:03:e3:2d:27: + a8:17:e0:84:85:42:dc:5e:9b:c6:b7:b2:6d:bb:74:af:e4:3f: + cb:a7:b7:b0:e0:5d:be:78:83:25:94:d2:db:81:0f:79:07:6d: + 4f:f4:39:15:5a:52:01:7b:de:32:d6:4d:38:f6:12:5c:06:50: + df:05:5b:bd:14:4b:a1:df:29:ba:3b:41:8d:f7:63:56:a1:df: + 22:b1 +-----BEGIN CERTIFICATE----- +MIIDITCCAoqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCByzELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD +VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT +ZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFBlcnNvbmFsIEJhc2lj +IENBMSgwJgYJKoZIhvcNAQkBFhlwZXJzb25hbC1iYXNpY0B0aGF3dGUuY29tMB4X +DTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgcsxCzAJBgNVBAYTAlpBMRUw +EwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UE +ChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2Vy +dmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQZXJzb25hbCBCYXNpYyBD +QTEoMCYGCSqGSIb3DQEJARYZcGVyc29uYWwtYmFzaWNAdGhhd3RlLmNvbTCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvLyTU23AUE+CFeZIlDWmWr5vQvoPR+53 +dXLdjUmbllegeNTKP1GzaQuRdhciB5dqxFGTS+CN7zeVoQxN2jSQHReJl+A1OFdK +wPQIcOk8RHtQfmGakOMj04gRRif1CwcOu93RfyAKiLlWCy4cgNrx454p7xS9CkT7 +G1sY0b8jkyECAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQF +AAOBgQAt4plrsD16iddZopQBHyvdEktTwq1/qqcAXJFAVyVKOKqEcLnZgA+le1z7 +c8a914phXAPjLSeoF+CEhULcXpvGt7Jtu3Sv5D/Lp7ew4F2+eIMllNLbgQ95B21P +9DkVWlIBe94y1k049hJcBlDfBVu9FEuh3ym6O0GN92NWod8isQ== +-----END CERTIFICATE----- + +AddTrust External Root +====================== + +MD5 Fingerprint=1D:35:54:04:85:78:B0:3F:42:42:4D:BF:20:73:0A:3F +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root + Validity + Not Before: May 30 10:48:38 2000 GMT + Not After : May 30 10:48:38 2020 GMT + Subject: C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b7:f7:1a:33:e6:f2:00:04:2d:39:e0:4e:5b:ed: + 1f:bc:6c:0f:cd:b5:fa:23:b6:ce:de:9b:11:33:97: + a4:29:4c:7d:93:9f:bd:4a:bc:93:ed:03:1a:e3:8f: + cf:e5:6d:50:5a:d6:97:29:94:5a:80:b0:49:7a:db: + 2e:95:fd:b8:ca:bf:37:38:2d:1e:3e:91:41:ad:70: + 56:c7:f0:4f:3f:e8:32:9e:74:ca:c8:90:54:e9:c6: + 5f:0f:78:9d:9a:40:3c:0e:ac:61:aa:5e:14:8f:9e: + 87:a1:6a:50:dc:d7:9a:4e:af:05:b3:a6:71:94:9c: + 71:b3:50:60:0a:c7:13:9d:38:07:86:02:a8:e9:a8: + 69:26:18:90:ab:4c:b0:4f:23:ab:3a:4f:84:d8:df: + ce:9f:e1:69:6f:bb:d7:42:d7:6b:44:e4:c7:ad:ee: + 6d:41:5f:72:5a:71:08:37:b3:79:65:a4:59:a0:94: + 37:f7:00:2f:0d:c2:92:72:da:d0:38:72:db:14:a8: + 45:c4:5d:2a:7d:b7:b4:d6:c4:ee:ac:cd:13:44:b7: + c9:2b:dd:43:00:25:fa:61:b9:69:6a:58:23:11:b7: + a7:33:8f:56:75:59:f5:cd:29:d7:46:b7:0a:2b:65: + b6:d3:42:6f:15:b2:b8:7b:fb:ef:e9:5d:53:d5:34: + 5a:27 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Authority Key Identifier: + keyid:AD:BD:98:7A:34:B4:26:F7:FA:C4:26:54:EF:03:BD:E0:24:CB:54:1A + DirName:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root + serial:01 + + Signature Algorithm: sha1WithRSAEncryption + b0:9b:e0:85:25:c2:d6:23:e2:0f:96:06:92:9d:41:98:9c:d9: + 84:79:81:d9:1e:5b:14:07:23:36:65:8f:b0:d8:77:bb:ac:41: + 6c:47:60:83:51:b0:f9:32:3d:e7:fc:f6:26:13:c7:80:16:a5: + bf:5a:fc:87:cf:78:79:89:21:9a:e2:4c:07:0a:86:35:bc:f2: + de:51:c4:d2:96:b7:dc:7e:4e:ee:70:fd:1c:39:eb:0c:02:51: + 14:2d:8e:bd:16:e0:c1:df:46:75:e7:24:ad:ec:f4:42:b4:85: + 93:70:10:67:ba:9d:06:35:4a:18:d3:2b:7a:cc:51:42:a1:7a: + 63:d1:e6:bb:a1:c5:2b:c2:36:be:13:0d:e6:bd:63:7e:79:7b: + a7:09:0d:40:ab:6a:dd:8f:8a:c3:f6:f6:8c:1a:42:05:51:d4: + 45:f5:9f:a7:62:21:68:15:20:43:3c:99:e7:7c:bd:24:d8:a9: + 91:17:73:88:3f:56:1b:31:38:18:b4:71:0f:9a:cd:c8:0e:9e: + 8e:2e:1b:e1:8c:98:83:cb:1f:31:f1:44:4c:c6:04:73:49:76: + 60:0f:c7:f8:bd:17:80:6b:2e:e9:cc:4c:0e:5a:9a:79:0f:20: + 0a:2e:d5:9e:63:26:1e:55:92:94:d8:82:17:5a:7b:d0:bc:c7: + 8f:4e:86:04 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +AddTrust Public Services Root +============================= + +MD5 Fingerprint=C1:62:3E:23:C5:82:73:9C:03:59:4B:2B:E9:77:49:7F +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, O=AddTrust AB, OU=AddTrust TTP Network, CN=AddTrust Public CA Root + Validity + Not Before: May 30 10:41:50 2000 GMT + Not After : May 30 10:41:50 2020 GMT + Subject: C=SE, O=AddTrust AB, OU=AddTrust TTP Network, CN=AddTrust Public CA Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:e9:1a:30:8f:83:88:14:c1:20:d8:3c:9b:8f:1b: + 7e:03:74:bb:da:69:d3:46:a5:f8:8e:c2:0c:11:90: + 51:a5:2f:66:54:40:55:ea:db:1f:4a:56:ee:9f:23: + 6e:f4:39:cb:a1:b9:6f:f2:7e:f9:5d:87:26:61:9e: + 1c:f8:e2:ec:a6:81:f8:21:c5:24:cc:11:0c:3f:db: + 26:72:7a:c7:01:97:07:17:f9:d7:18:2c:30:7d:0e: + 7a:1e:62:1e:c6:4b:c0:fd:7d:62:77:d3:44:1e:27: + f6:3f:4b:44:b3:b7:38:d9:39:1f:60:d5:51:92:73: + 03:b4:00:69:e3:f3:14:4e:ee:d1:dc:09:cf:77:34: + 46:50:b0:f8:11:f2:fe:38:79:f7:07:39:fe:51:92: + 97:0b:5b:08:5f:34:86:01:ad:88:97:eb:66:cd:5e: + d1:ff:dc:7d:f2:84:da:ba:77:ad:dc:80:08:c7:a7: + 87:d6:55:9f:97:6a:e8:c8:11:64:ba:e7:19:29:3f: + 11:b3:78:90:84:20:52:5b:11:ef:78:d0:83:f6:d5: + 48:90:d0:30:1c:cf:80:f9:60:fe:79:e4:88:f2:dd: + 00:eb:94:45:eb:65:94:69:40:ba:c0:d5:b4:b8:ba: + 7d:04:11:a8:eb:31:05:96:94:4e:58:21:8e:9f:d0: + 60:fd + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 81:3E:37:D8:92:B0:1F:77:9F:5C:B4:AB:73:AA:E7:F6:34:60:2F:FA + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Authority Key Identifier: + keyid:81:3E:37:D8:92:B0:1F:77:9F:5C:B4:AB:73:AA:E7:F6:34:60:2F:FA + DirName:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Public CA Root + serial:01 + + Signature Algorithm: sha1WithRSAEncryption + 03:f7:15:4a:f8:24:da:23:56:16:93:76:dd:36:28:b9:ae:1b: + b8:c3:f1:64:ba:20:18:78:95:29:27:57:05:bc:7c:2a:f4:b9: + 51:55:da:87:02:de:0f:16:17:31:f8:aa:79:2e:09:13:bb:af: + b2:20:19:12:e5:93:f9:4b:f9:83:e8:44:d5:b2:41:25:bf:88: + 75:6f:ff:10:fc:4a:54:d0:5f:f0:fa:ef:36:73:7d:1b:36:45: + c6:21:6d:b4:15:b8:4e:cf:9c:5c:a5:3d:5a:00:8e:06:e3:3c: + 6b:32:7b:f2:9f:f0:b6:fd:df:f0:28:18:48:f0:c6:bc:d0:bf: + 34:80:96:c2:4a:b1:6d:8e:c7:90:45:de:2f:67:ac:45:04:a3: + 7a:dc:55:92:c9:47:66:d8:1a:8c:c7:ed:9c:4e:9a:e0:12:bb: + b5:6a:4c:84:e1:e1:22:0d:87:00:64:fe:8c:7d:62:39:65:a6: + ef:42:b6:80:25:12:61:01:a8:24:13:70:00:11:26:5f:fa:35: + 50:c5:48:cc:06:47:e8:27:d8:70:8d:5f:64:e6:a1:44:26:5e: + 22:ec:92:cd:ff:42:9a:44:21:6d:5c:c5:e3:22:1d:5f:47:12: + e7:ce:5f:5d:fa:d8:aa:b1:33:2d:d9:76:f2:4e:3a:33:0c:2b: + b3:2d:90:06 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx +MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB +ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV +BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV +6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX +GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP +dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH +1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF +62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW +BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL +MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU +cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv +b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 +IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ +iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh +4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm +XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +AddTrust Qualified Certificates Root +==================================== + +MD5 Fingerprint=27:EC:39:47:CD:DA:5A:AF:E2:9A:01:65:21:A9:4C:BB +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, O=AddTrust AB, OU=AddTrust TTP Network, CN=AddTrust Qualified CA Root + Validity + Not Before: May 30 10:44:50 2000 GMT + Not After : May 30 10:44:50 2020 GMT + Subject: C=SE, O=AddTrust AB, OU=AddTrust TTP Network, CN=AddTrust Qualified CA Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:e4:1e:9a:fe:dc:09:5a:87:a4:9f:47:be:11:5f: + af:84:34:db:62:3c:79:78:b7:e9:30:b5:ec:0c:1c: + 2a:c4:16:ff:e0:ec:71:eb:8a:f5:11:6e:ed:4f:0d: + 91:d2:12:18:2d:49:15:01:c2:a4:22:13:c7:11:64: + ff:22:12:9a:b9:8e:5c:2f:08:cf:71:6a:b3:67:01: + 59:f1:5d:46:f3:b0:78:a5:f6:0e:42:7a:e3:7f:1b: + cc:d0:f0:b7:28:fd:2a:ea:9e:b3:b0:b9:04:aa:fd: + f6:c7:b4:b1:b8:2a:a0:fb:58:f1:19:a0:6f:70:25: + 7e:3e:69:4a:7f:0f:22:d8:ef:ad:08:11:9a:29:99: + e1:aa:44:45:9a:12:5e:3e:9d:6d:52:fc:e7:a0:3d: + 68:2f:f0:4b:70:7c:13:38:ad:bc:15:25:f1:d6:ce: + ab:a2:c0:31:d6:2f:9f:e0:ff:14:59:fc:84:93:d9: + 87:7c:4c:54:13:eb:9f:d1:2d:11:f8:18:3a:3a:de: + 25:d9:f7:d3:40:ed:a4:06:12:c4:3b:e1:91:c1:56: + 35:f0:14:dc:65:36:09:6e:ab:a4:07:c7:35:d1:c2: + 03:33:36:5b:75:26:6d:42:f1:12:6b:43:6f:4b:71: + 94:fa:34:1d:ed:13:6e:ca:80:7f:98:2f:6c:b9:65: + d8:e9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 39:95:8B:62:8B:5C:C9:D4:80:BA:58:0F:97:3F:15:08:43:CC:98:A7 + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Authority Key Identifier: + keyid:39:95:8B:62:8B:5C:C9:D4:80:BA:58:0F:97:3F:15:08:43:CC:98:A7 + DirName:/C=SE/O=AddTrust AB/OU=AddTrust TTP Network/CN=AddTrust Qualified CA Root + serial:01 + + Signature Algorithm: sha1WithRSAEncryption + 19:ab:75:ea:f8:8b:65:61:95:13:ba:69:04:ef:86:ca:13:a0: + c7:aa:4f:64:1b:3f:18:f6:a8:2d:2c:55:8f:05:b7:30:ea:42: + 6a:1d:c0:25:51:2d:a7:bf:0c:b3:ed:ef:08:7f:6c:3c:46:1a: + ea:18:43:df:76:cc:f9:66:86:9c:2c:68:f5:e9:17:f8:31:b3: + 18:c4:d6:48:7d:23:4c:68:c1:7e:bb:01:14:6f:c5:d9:6e:de: + bb:04:42:6a:f8:f6:5c:7d:e5:da:fa:87:eb:0d:35:52:67:d0: + 9e:97:76:05:93:3f:95:c7:01:e6:69:55:38:7f:10:61:99:c9: + e3:5f:a6:ca:3e:82:63:48:aa:e2:08:48:3e:aa:f2:b2:85:62: + a6:b4:a7:d9:bd:37:9c:68:b5:2d:56:7d:b0:b7:3f:a0:b1:07: + d6:e9:4f:dc:de:45:71:30:32:7f:1b:2e:09:f9:bf:52:a1:ee: + c2:80:3e:06:5c:2e:55:40:c1:1b:f5:70:45:b0:dc:5d:fa:f6: + 72:5a:77:d2:63:cd:cf:58:89:00:42:63:3f:79:39:d0:44:b0: + 82:6e:41:19:e8:dd:e0:c1:88:5a:d1:1e:71:93:1f:24:30:74: + e5:1e:a8:de:3c:27:37:7f:83:ae:9e:77:cf:f0:30:b1:ff:4b: + 99:e8:c6:a1 +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 +MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK +EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh +BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq +xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G +87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i +2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U +WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 +0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G +A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr +pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL +ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm +aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv +hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm +hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 +P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y +iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no +xqE= +-----END CERTIFICATE----- + +Verisign Class 1 Public Primary OCSP Responder +============================================== + +MD5 Fingerprint=7E:6F:3A:53:1B:7C:BE:B0:30:DB:43:1E:1E:94:89:B2 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 2b:68:d4:a3:46:9e:c5:3b:28:09:ab:38:5d:7f:27:20 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 1 Public Primary Certification Authority + Validity + Not Before: Aug 4 00:00:00 2000 GMT + Not After : Aug 3 23:59:59 2004 GMT + Subject: O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/RPA (c)00, CN=Class 1 Public Primary OCSP Responder + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:b9:ed:5e:7a:3a:77:5f:ce:5f:3a:52:fc:cd:64: + f7:71:b5:6f:6a:96:c6:59:92:55:94:5d:2f:5b:2e: + c1:11:ea:26:8a:cb:a7:81:3c:f6:5a:44:de:7a:13: + 2f:fd:5a:51:d9:7b:37:26:4a:c0:27:3f:04:03:6a: + 56:c1:83:2c:e1:6f:5b:a9:54:50:24:4a:c6:2e:7a: + 4c:a1:5b:37:54:24:21:31:1f:a1:78:18:76:a7:b1: + 70:da:22:d0:6a:fe:07:62:40:c6:f7:f6:9b:7d:0c: + 06:b8:4b:c7:28:e4:66:23:84:51:ef:46:b7:93:d8: + 81:33:cb:e5:36:ac:c6:e8:05 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DirName:/CN=OCSP 1-1 + X509v3 CRL Distribution Points: + URI:http://crl.verisign.com/pca1.crl + + X509v3 Extended Key Usage: + OCSP Signing + Authority Information Access: + OCSP - URI:http://ocsp.verisign.com/ocsp/status + + X509v3 Certificate Policies: + Policy: 2.16.840.1.113733.1.7.1.1 + CPS: https://www.verisign.com/RPA + + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha1WithRSAEncryption + 70:90:dd:b8:e4:be:53:17:7c:7f:02:e9:d5:f7:8b:99:93:31: + 60:8d:7e:e6:60:6b:24:ef:60:ac:d2:ce:91:de:80:6d:09:a4: + d3:b8:38:e5:44:ca:72:5e:0d:2d:c1:77:9c:bd:2c:03:78:29: + 8d:a4:a5:77:87:f5:f1:2b:26:ad:cc:07:6c:3a:54:5a:28:e0: + 09:f3:4d:0a:04:ca:d4:58:69:0b:a7:b3:f5:dd:01:a5:e7:dc: + f0:1f:ba:c1:5d:90:8d:b3:ea:4f:c1:11:59:97:6a:b2:2b:13: + b1:da:ad:97:a1:b3:b1:a0:20:5b:ca:32:ab:8d:cf:13:f0:1f: + 29:c3 +-----BEGIN CERTIFICATE----- +MIIDnjCCAwegAwIBAgIQK2jUo0aexTsoCas4XX8nIDANBgkqhkiG9w0BAQUFADBf +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT +LkNsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw +HhcNMDAwODA0MDAwMDAwWhcNMDQwODAzMjM1OTU5WjCBpzEXMBUGA1UEChMOVmVy +aVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5 +BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20v +UlBBIChjKTAwMS4wLAYDVQQDEyVDbGFzcyAxIFB1YmxpYyBQcmltYXJ5IE9DU1Ag +UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC57V56Ondfzl86 +UvzNZPdxtW9qlsZZklWUXS9bLsER6iaKy6eBPPZaRN56Ey/9WlHZezcmSsAnPwQD +albBgyzhb1upVFAkSsYuekyhWzdUJCExH6F4GHansXDaItBq/gdiQMb39pt9DAa4 +S8co5GYjhFHvRreT2IEzy+U2rMboBQIDAQABo4IBEDCCAQwwIAYDVR0RBBkwF6QV +MBMxETAPBgNVBAMTCE9DU1AgMS0xMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9j +cmwudmVyaXNpZ24uY29tL3BjYTEuY3JsMBMGA1UdJQQMMAoGCCsGAQUFBwMJMEIG +CCsGAQUFBwEBBDYwNDAyBggrBgEFBQcwAaYmFiRodHRwOi8vb2NzcC52ZXJpc2ln +bi5jb20vb2NzcC9zdGF0dXMwRAYDVR0gBD0wOzA5BgtghkgBhvhFAQcBATAqMCgG +CCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vUlBBMAkGA1UdEwQC +MAAwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBBQUAA4GBAHCQ3bjkvlMXfH8C6dX3 +i5mTMWCNfuZgayTvYKzSzpHegG0JpNO4OOVEynJeDS3Bd5y9LAN4KY2kpXeH9fEr +Jq3MB2w6VFoo4AnzTQoEytRYaQuns/XdAaXn3PAfusFdkI2z6k/BEVmXarIrE7Ha +rZehs7GgIFvKMquNzxPwHynD +-----END CERTIFICATE----- + +Verisign Class 2 Public Primary OCSP Responder +============================================== + +MD5 Fingerprint=F3:45:BD:10:96:0D:85:4B:EF:9F:11:62:34:A7:5E:B5 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 09:46:17:e6:1d:d8:d4:1c:a0:0c:a0:62:e8:79:8a:a7 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 2 Public Primary Certification Authority + Validity + Not Before: Aug 1 00:00:00 2000 GMT + Not After : Jul 31 23:59:59 2004 GMT + Subject: O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/RPA (c)00, CN=Class 2 Public Primary OCSP Responder + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d0:ca:63:31:61:7f:44:34:7c:05:7d:0b:3d:6a: + 90:cb:79:4b:77:0a:3f:4b:c7:23:e5:c0:62:2d:7e: + 9c:7e:3e:88:87:91:d0:ac:e8:4d:49:87:a2:96:90: + 8a:dd:04:a5:02:3f:8c:9b:e9:89:fe:62:a0:e2:5a: + bd:c8:dd:b4:78:e6:a5:42:93:08:67:01:c0:20:4d: + d7:5c:f4:5d:da:b3:e3:37:a6:52:1a:2c:4c:65:4d: + 8a:87:d9:a8:a3:f1:49:54:bb:3c:5c:80:51:68:c6: + fb:49:ff:0b:55:ab:15:dd:fb:9a:c1:b9:1d:74:0d: + b2:8c:44:5d:89:fc:9f:f9:83 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DirName:/CN=OCSP 1-2 + X509v3 CRL Distribution Points: + URI:http://crl.verisign.com/pca2.crl + + X509v3 Extended Key Usage: + OCSP Signing + Authority Information Access: + OCSP - URI:http://ocsp.verisign.com/ocsp/status + + X509v3 Certificate Policies: + Policy: 2.16.840.1.113733.1.7.1.1 + CPS: https://www.verisign.com/RPA + + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha1WithRSAEncryption + 1f:7d:09:6e:24:46:75:04:9c:f3:26:9b:e3:39:6e:17:ef:bc: + bd:a2:1b:d2:02:84:86:ab:d0:40:97:2c:c4:43:88:37:19:6b: + 22:a8:03:71:50:9d:20:dc:36:60:20:9a:73:2d:73:55:6c:58: + 9b:2c:c2:b4:34:2c:7a:33:42:ca:91:d9:e9:43:af:cf:1e:e0: + f5:c4:7a:ab:3f:72:63:1e:a9:37:e1:5b:3b:88:b3:13:86:82: + 90:57:cb:57:ff:f4:56:be:22:dd:e3:97:a8:e1:bc:22:43:c2: + dd:4d:db:f6:81:9e:92:14:9e:39:0f:13:54:de:82:d8:c0:5e: + 34:8d +-----BEGIN CERTIFICATE----- +MIIDnjCCAwegAwIBAgIQCUYX5h3Y1BygDKBi6HmKpzANBgkqhkiG9w0BAQUFADBf +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT +LkNsYXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw +HhcNMDAwODAxMDAwMDAwWhcNMDQwNzMxMjM1OTU5WjCBpzEXMBUGA1UEChMOVmVy +aVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5 +BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20v +UlBBIChjKTAwMS4wLAYDVQQDEyVDbGFzcyAyIFB1YmxpYyBQcmltYXJ5IE9DU1Ag +UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQymMxYX9ENHwF +fQs9apDLeUt3Cj9LxyPlwGItfpx+PoiHkdCs6E1Jh6KWkIrdBKUCP4yb6Yn+YqDi +Wr3I3bR45qVCkwhnAcAgTddc9F3as+M3plIaLExlTYqH2aij8UlUuzxcgFFoxvtJ +/wtVqxXd+5rBuR10DbKMRF2J/J/5gwIDAQABo4IBEDCCAQwwIAYDVR0RBBkwF6QV +MBMxETAPBgNVBAMTCE9DU1AgMS0yMDEGA1UdHwQqMCgwJqAkoCKGIGh0dHA6Ly9j +cmwudmVyaXNpZ24uY29tL3BjYTIuY3JsMBMGA1UdJQQMMAoGCCsGAQUFBwMJMEIG +CCsGAQUFBwEBBDYwNDAyBggrBgEFBQcwAaYmFiRodHRwOi8vb2NzcC52ZXJpc2ln +bi5jb20vb2NzcC9zdGF0dXMwRAYDVR0gBD0wOzA5BgtghkgBhvhFAQcBATAqMCgG +CCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vUlBBMAkGA1UdEwQC +MAAwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBBQUAA4GBAB99CW4kRnUEnPMmm+M5 +bhfvvL2iG9IChIar0ECXLMRDiDcZayKoA3FQnSDcNmAgmnMtc1VsWJsswrQ0LHoz +QsqR2elDr88e4PXEeqs/cmMeqTfhWzuIsxOGgpBXy1f/9Fa+It3jl6jhvCJDwt1N +2/aBnpIUnjkPE1TegtjAXjSN +-----END CERTIFICATE----- + +Verisign Class 3 Public Primary OCSP Responder +============================================== + +MD5 Fingerprint=7D:51:92:C9:76:83:98:16:DE:8C:B3:86:C4:7D:66:FB +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 2e:96:9e:bf:b6:62:6c:ec:7b:e9:73:cc:e3:6c:c1:84 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority + Validity + Not Before: Aug 4 00:00:00 2000 GMT + Not After : Aug 3 23:59:59 2004 GMT + Subject: O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/RPA (c)00, CN=Class 3 Public Primary OCSP Responder + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:f1:e4:08:0e:83:bb:75:e3:48:e5:b8:db:a6:f0: + b9:ab:e9:3c:62:c7:5e:35:5b:d0:02:54:11:d8:c9: + d1:56:b9:76:4b:b9:ab:7a:e6:cd:ba:f6:0c:04:d6: + 7e:d6:b0:0a:65:ac:4e:39:e3:f1:f7:2d:a3:25:39: + ef:b0:8b:cf:be:db:0c:5d:6e:70:f4:07:cd:70:f7: + 3a:c0:3e:35:16:ed:78:8c:43:cf:c2:26:2e:47:d6: + 86:7d:9c:f1:be:d6:67:0c:22:25:a4:ca:65:e6:1f: + 7a:78:28:2f:3f:05:db:04:21:bf:e1:45:66:fe:3c: + b7:82:ed:5a:b8:16:15:b9:55 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DirName:/CN=OCSP 1-3 + X509v3 CRL Distribution Points: + URI:http://crl.verisign.com/pca3.1.1.crl + + X509v3 Extended Key Usage: + OCSP Signing + Authority Information Access: + OCSP - URI:http://ocsp.verisign.com/ocsp/status + + X509v3 Certificate Policies: + Policy: 2.16.840.1.113733.1.7.1.1 + CPS: https://www.verisign.com/RPA + + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha1WithRSAEncryption + 02:f6:53:63:c0:a9:1e:f2:d0:8b:33:30:8f:48:9b:4c:b0:56: + b4:83:71:4a:be:dc:50:d8:f5:b6:e0:0b:db:bd:78:4f:e9:cf: + 09:34:da:29:49:9d:01:73:5a:91:91:82:54:2c:13:0a:d3:77: + 23:cf:37:fc:63:de:a7:e3:f6:b7:b5:69:45:28:49:c3:91:dc: + aa:47:1c:a9:88:99:2c:05:2a:8d:8d:8a:fa:62:e2:5a:b7:00: + 20:5d:39:c4:28:c2:cb:fc:9e:a8:89:ae:5b:3d:8e:12:ea:32: + b2:fc:eb:14:d7:09:15:1a:c0:cd:1b:d5:b5:15:4e:41:d5:96: + e3:4e +-----BEGIN CERTIFICATE----- +MIIDojCCAwugAwIBAgIQLpaev7ZibOx76XPM42zBhDANBgkqhkiG9w0BAQUFADBf +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT +LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw +HhcNMDAwODA0MDAwMDAwWhcNMDQwODAzMjM1OTU5WjCBpzEXMBUGA1UEChMOVmVy +aVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5 +BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20v +UlBBIChjKTAwMS4wLAYDVQQDEyVDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IE9DU1Ag +UmVzcG9uZGVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDx5AgOg7t140jl +uNum8Lmr6Txix141W9ACVBHYydFWuXZLuat65s269gwE1n7WsAplrE454/H3LaMl +Oe+wi8++2wxdbnD0B81w9zrAPjUW7XiMQ8/CJi5H1oZ9nPG+1mcMIiWkymXmH3p4 +KC8/BdsEIb/hRWb+PLeC7Vq4FhW5VQIDAQABo4IBFDCCARAwIAYDVR0RBBkwF6QV +MBMxETAPBgNVBAMTCE9DU1AgMS0zMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9j +cmwudmVyaXNpZ24uY29tL3BjYTMuMS4xLmNybDATBgNVHSUEDDAKBggrBgEFBQcD +CTBCBggrBgEFBQcBAQQ2MDQwMgYIKwYBBQUHMAGmJhYkaHR0cDovL29jc3AudmVy +aXNpZ24uY29tL29jc3Avc3RhdHVzMEQGA1UdIAQ9MDswOQYLYIZIAYb4RQEHAQEw +KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL1JQQTAJBgNV +HRMEAjAAMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0BAQUFAAOBgQAC9lNjwKke8tCL +MzCPSJtMsFa0g3FKvtxQ2PW24AvbvXhP6c8JNNopSZ0Bc1qRkYJULBMK03cjzzf8 +Y96n4/a3tWlFKEnDkdyqRxypiJksBSqNjYr6YuJatwAgXTnEKMLL/J6oia5bPY4S +6jKy/OsU1wkVGsDNG9W1FU5B1ZbjTg== +-----END CERTIFICATE----- + +Verisign Secure Server OCSP Responder +===================================== + +MD5 Fingerprint=2C:62:C3:D8:80:01:16:09:EA:59:EA:78:AB:10:43:F6 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + ff:45:d5:27:5d:24:fb:b3:c2:39:24:53:57:e1:4f:de + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=RSA Data Security, Inc., OU=Secure Server Certification Authority + Validity + Not Before: Aug 4 00:00:00 2000 GMT + Not After : Aug 3 23:59:59 2004 GMT + Subject: O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/RPA (c)00, CN=Secure Server OCSP Responder + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:b8:51:99:64:85:0e:ee:b3:0a:68:f0:bf:63:76: + 1d:53:f5:fc:a1:78:8c:33:ee:9f:f4:be:39:da:9b: + 0f:4d:47:a9:8f:20:e8:4b:44:bd:ce:cd:7b:90:d1: + 30:e8:90:c4:25:7b:89:28:de:bd:f6:93:1d:ff:b9: + ff:92:b5:a9:8d:e4:ae:cc:e2:c3:07:83:6a:a3:72: + 10:01:27:62:22:a6:35:26:39:2d:9e:cf:60:0c:fc: + 47:a4:d7:d0:42:78:a7:1d:6c:d0:cb:4f:15:a7:29: + 0a:b4:95:45:c4:b1:e7:5a:09:d7:39:95:d8:1d:35: + 9e:c2:bd:b3:5d:c1:0c:4b:1f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DirName:/CN=OCSP 1-4 + X509v3 CRL Distribution Points: + URI:http://crl.verisign.com/RSASecureServer-p.crl + + X509v3 Extended Key Usage: + OCSP Signing + Authority Information Access: + OCSP - URI:http://ocsp.verisign.com/ocsp/status + + X509v3 Certificate Policies: + Policy: 2.16.840.1.113733.1.7.1.1 + CPS: https://www.verisign.com/RPA + + X509v3 Basic Constraints: + CA:FALSE + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha1WithRSAEncryption + 00:b3:10:53:66:9c:49:93:2e:31:a0:02:42:d2:58:57:7e:66: + a1:fe:1b:8a:61:18:50:40:2c:1e:2b:41:a5:d6:db:ff:ac:08: + 1c:5a:05:6d:02:5c:2a:b6:96:4f:47:db:be:4e:db:ce:cc:ba: + 86:b8:18:ce:b1:12:91:5f:63:f7:f3:48:3e:cc:f1:4d:13:e4: + 6d:09:94:78:00:92:cb:a3:20:9d:06:0b:6a:a0:43:07:ce:d1: + 19:6c:8f:18:75:9a:9f:17:33:fd:a9:26:b8:e3:e2:de:c2:a8: + c4:5a:8a:7f:98:d6:07:06:6b:cc:56:9e:86:70:ce:d4:ef +-----BEGIN CERTIFICATE----- +MIIDnzCCAwygAwIBAgIRAP9F1SddJPuzwjkkU1fhT94wDQYJKoZIhvcNAQEFBQAw +XzELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMu +MS4wLAYDVQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MB4XDTAwMDgwNDAwMDAwMFoXDTA0MDgwMzIzNTk1OVowgZ4xFzAVBgNVBAoTDlZl +cmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTsw +OQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29t +L1JQQSAoYykwMDElMCMGA1UEAxMcU2VjdXJlIFNlcnZlciBPQ1NQIFJlc3BvbmRl +cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuFGZZIUO7rMKaPC/Y3YdU/X8 +oXiMM+6f9L452psPTUepjyDoS0S9zs17kNEw6JDEJXuJKN699pMd/7n/krWpjeSu +zOLDB4Nqo3IQASdiIqY1Jjktns9gDPxHpNfQQninHWzQy08VpykKtJVFxLHnWgnX +OZXYHTWewr2zXcEMSx8CAwEAAaOCAR0wggEZMCAGA1UdEQQZMBekFTATMREwDwYD +VQQDEwhPQ1NQIDEtNDA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vY3JsLnZlcmlz +aWduLmNvbS9SU0FTZWN1cmVTZXJ2ZXItcC5jcmwwEwYDVR0lBAwwCgYIKwYBBQUH +AwkwQgYIKwYBBQUHAQEENjA0MDIGCCsGAQUFBzABpiYWJGh0dHA6Ly9vY3NwLnZl +cmlzaWduLmNvbS9vY3NwL3N0YXR1czBEBgNVHSAEPTA7MDkGC2CGSAGG+EUBBwEB +MCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LnZlcmlzaWduLmNvbS9SUEEwCQYD +VR0TBAIwADALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQEFBQADfgAAsxBTZpxJky4x +oAJC0lhXfmah/huKYRhQQCweK0Gl1tv/rAgcWgVtAlwqtpZPR9u+TtvOzLqGuBjO +sRKRX2P380g+zPFNE+RtCZR4AJLLoyCdBgtqoEMHztEZbI8YdZqfFzP9qSa44+Le +wqjEWop/mNYHBmvMVp6GcM7U7w== +-----END CERTIFICATE----- + +Verisign Time Stamping Authority CA +=================================== + +MD5 Fingerprint=89:49:54:8C:C8:68:9A:83:29:EC:DC:06:73:21:AB:97 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 53:61:b2:60:ae:db:71:8e:a7:94:b3:13:33:f4:07:09 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority - G2, OU=(c) 1998 VeriSign, Inc. - For authorized use only, OU=VeriSign Trust Network + Validity + Not Before: Sep 26 00:00:00 2000 GMT + Not After : Sep 25 23:59:59 2010 GMT + Subject: O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)00, CN=VeriSign Time Stamping Authority CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d2:19:9d:67:c2:00:21:59:62:ce:b4:09:22:44: + 69:8a:f8:25:5a:db:ed:0d:b7:36:7e:4e:e0:bb:94: + 3e:90:25:87:c2:61:47:29:d9:bd:54:b8:63:cc:2c: + 7d:69:b4:33:36:f4:37:07:9a:c1:dd:40:54:fc:e0: + 78:9d:a0:93:b9:09:3d:23:51:7f:44:c2:14:74:db: + 0a:be:cb:c9:30:34:40:98:3e:d0:d7:25:10:81:94: + bd:07:4f:9c:d6:54:27:df:2e:a8:bf:cb:90:8c:8d: + 75:4b:bc:e2:e8:44:87:cd:e6:41:0a:25:6e:e8:f4: + 24:02:c5:52:0f:6e:ec:98:75 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:TRUE, pathlen:0 + X509v3 Certificate Policies: + Policy: 2.16.840.1.113733.1.7.23.1.3 + CPS: https://www.verisign.com/rpa + + X509v3 CRL Distribution Points: + URI:http://crl.verisign.com/pca3.crl + + X509v3 Key Usage: + Certificate Sign, CRL Sign + Authority Information Access: + OCSP - URI:http://ocsp.verisign.com/ocsp/status + + Signature Algorithm: sha1WithRSAEncryption + 82:70:68:95:df:b6:0d:c2:01:70:19:4a:d2:54:56:1e:ac:f2: + 45:4c:87:b8:f5:35:eb:78:4b:05:a9:c8:9d:3b:19:21:2e:70: + 34:4a:a2:f5:89:e0:15:75:45:e7:28:37:00:34:27:29:e8:37: + 4b:f2:ef:44:97:6b:17:51:1a:c3:56:9d:3c:1a:8a:f6:4a:46: + 46:37:8c:fa:cb:f5:64:5a:38:68:2e:1c:c3:ef:70:ce:b8:46: + 06:16:bf:f7:7e:e7:b5:a8:3e:45:ac:a9:25:75:22:7b:6f:3f: + b0:9c:94:e7:c7:73:ab:ac:1f:ee:25:9b:c0:16:ed:b7:ca:5b: + f0:14 +-----BEGIN CERTIFICATE----- +MIIDzTCCAzagAwIBAgIQU2GyYK7bcY6nlLMTM/QHCTANBgkqhkiG9w0BAQUFADCB +wTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTwwOgYDVQQL +EzNDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzIxOjA4BgNVBAsTMShjKSAxOTk4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1 +dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv +cmswHhcNMDAwOTI2MDAwMDAwWhcNMTAwOTI1MjM1OTU5WjCBpTEXMBUGA1UEChMO +VmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsx +OzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5j +b20vcnBhIChjKTAwMSwwKgYDVQQDEyNWZXJpU2lnbiBUaW1lIFN0YW1waW5nIEF1 +dGhvcml0eSBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0hmdZ8IAIVli +zrQJIkRpivglWtvtDbc2fk7gu5Q+kCWHwmFHKdm9VLhjzCx9abQzNvQ3B5rB3UBU +/OB4naCTuQk9I1F/RMIUdNsKvsvJMDRAmD7Q1yUQgZS9B0+c1lQn3y6ov8uQjI11 +S7zi6ESHzeZBCiVu6PQkAsVSD27smHUCAwEAAaOB3zCB3DAPBgNVHRMECDAGAQH/ +AgEAMEUGA1UdIAQ+MDwwOgYMYIZIAYb4RQEHFwEDMCowKAYIKwYBBQUHAgEWHGh0 +dHBzOi8vd3d3LnZlcmlzaWduLmNvbS9ycGEwMQYDVR0fBCowKDAmoCSgIoYgaHR0 +cDovL2NybC52ZXJpc2lnbi5jb20vcGNhMy5jcmwwCwYDVR0PBAQDAgEGMEIGCCsG +AQUFBwEBBDYwNDAyBggrBgEFBQcwAaYmFiRodHRwOi8vb2NzcC52ZXJpc2lnbi5j +b20vb2NzcC9zdGF0dXMwDQYJKoZIhvcNAQEFBQADgYEAgnBold+2DcIBcBlK0lRW +HqzyRUyHuPU163hLBanInTsZIS5wNEqi9YngFXVF5yg3ADQnKeg3S/LvRJdrF1Ea +w1adPBqK9kpGRjeM+sv1ZFo4aC4cw+9wzrhGBha/937ntag+RaypJXUie28/sJyU +58dzq6wf7iWbwBbtt8pb8BQ= +-----END CERTIFICATE----- + +Thawte Time Stamping CA +======================= + +MD5 Fingerprint=7F:66:7A:71:D3:EB:69:78:20:9A:51:14:9D:83:DA:20 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=ZA, ST=Western Cape, L=Durbanville, O=Thawte, OU=Thawte Certification, CN=Thawte Timestamping CA + Validity + Not Before: Jan 1 00:00:00 1997 GMT + Not After : Dec 31 23:59:59 2020 GMT + Subject: C=ZA, ST=Western Cape, L=Durbanville, O=Thawte, OU=Thawte Certification, CN=Thawte Timestamping CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d6:2b:58:78:61:45:86:53:ea:34:7b:51:9c:ed: + b0:e6:2e:18:0e:fe:e0:5f:a8:27:d3:b4:c9:e0:7c: + 59:4e:16:0e:73:54:60:c1:7f:f6:9f:2e:e9:3a:85: + 24:15:3c:db:47:04:63:c3:9e:c4:94:1a:5a:df:4c: + 7a:f3:d9:43:1d:3c:10:7a:79:25:db:90:fe:f0:51: + e7:30:d6:41:00:fd:9f:28:df:79:be:94:bb:9d:b6: + 14:e3:23:85:d7:a9:41:e0:4c:a4:79:b0:2b:1a:8b: + f2:f8:3b:8a:3e:45:ac:71:92:00:b4:90:41:98:fb: + 5f:ed:fa:b7:2e:8a:f8:88:37 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: md5WithRSAEncryption + 67:db:e2:c2:e6:87:3d:40:83:86:37:35:7d:1f:ce:9a:c3:0c: + 66:20:a8:ba:aa:04:89:86:c2:f5:10:08:0d:bf:cb:a2:05:8a: + d0:4d:36:3e:f4:d7:ef:69:c6:5e:e4:b0:94:6f:4a:b9:e7:de: + 5b:88:b6:7b:db:e3:27:e5:76:c3:f0:35:c1:cb:b5:27:9b:33: + 79:dc:90:a6:00:9e:77:fa:fc:cd:27:94:42:16:9c:d3:1c:68: + ec:bf:5c:dd:e5:a9:7b:10:0a:32:74:54:13:31:8b:85:03:84: + 91:b7:58:01:30:14:38:af:28:ca:fc:b1:50:19:19:09:ac:89: + 49:d3 +-----BEGIN CERTIFICATE----- +MIICoTCCAgqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzAN +BgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAd +BgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcgQ0EwHhcNOTcwMTAxMDAwMDAwWhcN +MjAxMjMxMjM1OTU5WjCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4g +Q2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsG +A1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1l +c3RhbXBpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANYrWHhhRYZT +6jR7UZztsOYuGA7+4F+oJ9O0yeB8WU4WDnNUYMF/9p8u6TqFJBU820cEY8OexJQa +Wt9MevPZQx08EHp5JduQ/vBR5zDWQQD9nyjfeb6Uu522FOMjhdepQeBMpHmwKxqL +8vg7ij5FrHGSALSQQZj7X+36ty6K+Ig3AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEEBQADgYEAZ9viwuaHPUCDhjc1fR/OmsMMZiCouqoEiYbC +9RAIDb/LogWK0E02PvTX72nGXuSwlG9KuefeW4i2e9vjJ+V2w/A1wcu1J5szedyQ +pgCed/r8zSeUQhac0xxo7L9c3eWpexAKMnRUEzGLhQOEkbdYATAUOK8oyvyxUBkZ +CayJSdM= +-----END CERTIFICATE----- + +Entrust.net Global Secure Server CA +=================================== + +MD5 Fingerprint=9D:66:6A:CC:FF:D5:F5:43:B4:BF:8C:16:D1:2B:A8:99 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 949686588 (0x389b113c) + Signature Algorithm: md5WithRSAEncryption + Issuer: O=Entrust.net, OU=www.entrust.net/SSL_CPS incorp. by ref. (limits liab.), OU=(c) 2000 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority + Validity + Not Before: Feb 4 17:20:00 2000 GMT + Not After : Feb 4 17:50:00 2020 GMT + Subject: O=Entrust.net, OU=www.entrust.net/SSL_CPS incorp. by ref. (limits liab.), OU=(c) 2000 Entrust.net Limited, CN=Entrust.net Secure Server Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:c7:c1:5f:4e:71:f1:ce:f0:60:86:0f:d2:58:7f: + d3:33:97:2d:17:a2:75:30:b5:96:64:26:2f:68:c3: + 44:ab:a8:75:e6:00:67:34:57:9e:65:c7:22:9b:73: + e6:d3:dd:08:0e:37:55:aa:25:46:81:6c:bd:fe:a8: + f6:75:57:57:8c:90:6c:4a:c3:3e:8b:4b:43:0a:c9: + 11:56:9a:9a:27:22:99:cf:55:9e:61:d9:02:e2:7c: + b6:7c:38:07:dc:e3:7f:4f:9a:b9:03:41:80:b6:75: + 67:13:0b:9f:e8:57:36:c8:5d:00:36:de:66:14:da: + 6e:76:1f:4f:37:8c:82:13:89 + Exponent: 65537 (0x10001) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/O=Entrust.net/OU=www.entrust.net/SSL_CPS incorp. by ref. (limits liab.)/OU=(c) 2000 Entrust.net Limited/CN=Entrust.net Secure Server Certification Authority/CN=CRL1 + + X509v3 Private Key Usage Period: + Not Before: Feb 4 17:20:00 2000 GMT, Not After: Feb 4 17:50:00 2020 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:CB:6C:C0:6B:E3:BB:3E:CB:FC:22:9C:FE:FB:8B:92:9C:B0:F2:6E:22 + + X509v3 Subject Key Identifier: + CB:6C:C0:6B:E3:BB:3E:CB:FC:22:9C:FE:FB:8B:92:9C:B0:F2:6E:22 + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0...V5.0:4.0.... + Signature Algorithm: md5WithRSAEncryption + 62:db:81:91:ce:c8:9a:77:42:2f:ec:bd:27:a3:53:0f:50:1b: + ea:4e:92:f0:a9:af:a9:a0:ba:48:61:cb:ef:c9:06:ef:1f:d5: + f4:ee:df:56:2d:e6:ca:6a:19:73:aa:53:be:92:b3:50:02:b6: + 85:26:72:63:d8:75:50:62:75:14:b7:b3:50:1a:3f:ca:11:00: + 0b:85:45:69:6d:b6:a5:ae:51:e1:4a:dc:82:3f:6c:8c:34:b2: + 77:6b:d9:02:f6:7f:0e:ea:65:04:f1:cd:54:ca:ba:c9:cc:e0: + 84:f7:c8:3e:11:97:d3:60:09:18:bc:05:ff:6c:89:33:f0:ec: + 15:0f +-----BEGIN CERTIFICATE----- +MIIElTCCA/6gAwIBAgIEOJsRPDANBgkqhkiG9w0BAQQFADCBujEUMBIGA1UEChML +RW50cnVzdC5uZXQxPzA9BgNVBAsUNnd3dy5lbnRydXN0Lm5ldC9TU0xfQ1BTIGlu +Y29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDIwMDAg +RW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJl +IFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMDAyMDQxNzIwMDBa +Fw0yMDAyMDQxNzUwMDBaMIG6MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDE/MD0GA1UE +CxQ2d3d3LmVudHJ1c3QubmV0L1NTTF9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMjAwMCBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHwV9OcfHO +8GCGD9JYf9Mzly0XonUwtZZkJi9ow0SrqHXmAGc0V55lxyKbc+bT3QgON1WqJUaB +bL3+qPZ1V1eMkGxKwz6LS0MKyRFWmponIpnPVZ5h2QLifLZ8OAfc439PmrkDQYC2 +dWcTC5/oVzbIXQA23mYU2m52H083jIITiQIDAQABo4IBpDCCAaAwEQYJYIZIAYb4 +QgEBBAQDAgAHMIHjBgNVHR8EgdswgdgwgdWggdKggc+kgcwwgckxFDASBgNVBAoT +C0VudHJ1c3QubmV0MT8wPQYDVQQLFDZ3d3cuZW50cnVzdC5uZXQvU1NMX0NQUyBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAyMDAw +IEVudHJ1c3QubmV0IExpbWl0ZWQxOjA4BgNVBAMTMUVudHJ1c3QubmV0IFNlY3Vy +ZSBTZXJ2ZXIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxDTALBgNVBAMTBENSTDEw +KwYDVR0QBCQwIoAPMjAwMDAyMDQxNzIwMDBagQ8yMDIwMDIwNDE3NTAwMFowCwYD +VR0PBAQDAgEGMB8GA1UdIwQYMBaAFMtswGvjuz7L/CKc/vuLkpyw8m4iMB0GA1Ud +DgQWBBTLbMBr47s+y/winP77i5KcsPJuIjAMBgNVHRMEBTADAQH/MB0GCSqGSIb2 +fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQQFAAOBgQBi24GRzsia +d0Iv7L0no1MPUBvqTpLwqa+poLpIYcvvyQbvH9X07t9WLebKahlzqlO+krNQAraF +JnJj2HVQYnUUt7NQGj/KEQALhUVpbbalrlHhStyCP2yMNLJ3a9kC9n8O6mUE8c1U +yrrJzOCE98g+EZfTYAkYvAX/bIkz8OwVDw== +-----END CERTIFICATE----- + +Thawte Personal Premium CA +========================== + +MD5 Fingerprint=3A:B2:DE:22:9A:20:93:49:F9:ED:C8:D2:8A:E7:68:0D +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting, OU=Certification Services Division, CN=Thawte Personal Premium CA/emailAddress=personal-premium@thawte.com + Validity + Not Before: Jan 1 00:00:00 1996 GMT + Not After : Dec 31 23:59:59 2020 GMT + Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting, OU=Certification Services Division, CN=Thawte Personal Premium CA/emailAddress=personal-premium@thawte.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:c9:66:d9:f8:07:44:cf:b9:8c:2e:f0:a1:ef:13: + 45:6c:05:df:de:27:16:51:36:41:11:6c:6c:3b:ed: + fe:10:7d:12:9e:e5:9b:42:9a:fe:60:31:c3:66:b7: + 73:3a:48:ae:4e:d0:32:37:94:88:b5:0d:b6:d9:f3: + f2:44:d9:d5:88:12:dd:76:4d:f2:1a:fc:6f:23:1e: + 7a:f1:d8:98:45:4e:07:10:ef:16:42:d0:43:75:6d: + 4a:de:e2:aa:c9:31:ff:1f:00:70:7c:66:cf:10:25: + 08:ba:fa:ee:00:e9:46:03:66:27:11:15:3b:aa:5b: + f2:98:dd:36:42:b2:da:88:75 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: md5WithRSAEncryption + 69:36:89:f7:34:2a:33:72:2f:6d:3b:d4:22:b2:b8:6f:9a:c5: + 36:66:0e:1b:3c:a1:b1:75:5a:e6:fd:35:d3:f8:a8:f2:07:6f: + 85:67:8e:de:2b:b9:e2:17:b0:3a:a0:f0:0e:a2:00:9a:df:f3: + 14:15:6e:bb:c8:85:5a:98:80:f9:ff:be:74:1d:3d:f3:fe:30: + 25:d1:37:34:67:fa:a5:71:79:30:61:29:72:c0:e0:2c:4c:fb: + 56:e4:3a:a8:6f:e5:32:59:52:db:75:28:50:59:0c:f8:0b:19: + e4:ac:d9:af:96:8d:2f:50:db:07:c3:ea:1f:ab:33:e0:f5:2b: + 31:89 +-----BEGIN CERTIFICATE----- +MIIDKTCCApKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBzzELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD +VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT +ZXJ2aWNlcyBEaXZpc2lvbjEjMCEGA1UEAxMaVGhhd3RlIFBlcnNvbmFsIFByZW1p +dW0gQ0ExKjAoBgkqhkiG9w0BCQEWG3BlcnNvbmFsLXByZW1pdW1AdGhhd3RlLmNv +bTAeFw05NjAxMDEwMDAwMDBaFw0yMDEyMzEyMzU5NTlaMIHPMQswCQYDVQQGEwJa +QTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xGjAY +BgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9u +IFNlcnZpY2VzIERpdmlzaW9uMSMwIQYDVQQDExpUaGF3dGUgUGVyc29uYWwgUHJl +bWl1bSBDQTEqMCgGCSqGSIb3DQEJARYbcGVyc29uYWwtcHJlbWl1bUB0aGF3dGUu +Y29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJZtn4B0TPuYwu8KHvE0Vs +Bd/eJxZRNkERbGw77f4QfRKe5ZtCmv5gMcNmt3M6SK5O0DI3lIi1DbbZ8/JE2dWI +Et12TfIa/G8jHnrx2JhFTgcQ7xZC0EN1bUre4qrJMf8fAHB8Zs8QJQi6+u4A6UYD +ZicRFTuqW/KY3TZCstqIdQIDAQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBBAUAA4GBAGk2ifc0KjNyL2071CKyuG+axTZmDhs8obF1Wub9NdP4qPIH +b4Vnjt4rueIXsDqg8A6iAJrf8xQVbrvIhVqYgPn/vnQdPfP+MCXRNzRn+qVxeTBh +KXLA4CxM+1bkOqhv5TJZUtt1KFBZDPgLGeSs2a+WjS9Q2wfD6h+rM+D1KzGJ +-----END CERTIFICATE----- + +Entrust.net Global Secure Personal CA +===================================== + +MD5 Fingerprint=9A:77:19:18:ED:96:CF:DF:1B:B7:0E:F5:8D:B9:88:2E +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 949941988 (0x389ef6e4) + Signature Algorithm: md5WithRSAEncryption + Issuer: O=Entrust.net, OU=www.entrust.net/GCCA_CPS incorp. by ref. (limits liab.), OU=(c) 2000 Entrust.net Limited, CN=Entrust.net Client Certification Authority + Validity + Not Before: Feb 7 16:16:40 2000 GMT + Not After : Feb 7 16:46:40 2020 GMT + Subject: O=Entrust.net, OU=www.entrust.net/GCCA_CPS incorp. by ref. (limits liab.), OU=(c) 2000 Entrust.net Limited, CN=Entrust.net Client Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:93:74:b4:b6:e4:c5:4b:d6:a1:68:7f:62:d5:ec: + f7:51:57:b3:72:4a:98:f5:d0:89:c9:ad:63:cd:4d: + 35:51:6a:84:d4:ad:c9:68:79:6f:b8:eb:11:db:87: + ae:5c:24:51:13:f1:54:25:84:af:29:2b:9f:e3:80: + e2:d9:cb:dd:c6:45:49:34:88:90:5e:01:97:ef:ea: + 53:a6:dd:fc:c1:de:4b:2a:25:e4:e9:35:fa:55:05: + 06:e5:89:7a:ea:a4:11:57:3b:fc:7c:3d:36:cd:67: + 35:6d:a4:a9:25:59:bd:66:f5:f9:27:e4:95:67:d6: + 3f:92:80:5e:f2:34:7d:2b:85 + Exponent: 65537 (0x10001) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/O=Entrust.net/OU=www.entrust.net/GCCA_CPS incorp. by ref. (limits liab.)/OU=(c) 2000 Entrust.net Limited/CN=Entrust.net Client Certification Authority/CN=CRL1 + + X509v3 Private Key Usage Period: + Not Before: Feb 7 16:16:40 2000 GMT, Not After: Feb 7 16:46:40 2020 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:84:8B:74:FD:C5:8D:C0:FF:27:6D:20:37:45:7C:FE:2D:CE:BA:D3:7D + + X509v3 Subject Key Identifier: + 84:8B:74:FD:C5:8D:C0:FF:27:6D:20:37:45:7C:FE:2D:CE:BA:D3:7D + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0...V5.0:4.0.... + Signature Algorithm: md5WithRSAEncryption + 4e:6f:35:80:3b:d1:8a:f5:0e:a7:20:cb:2d:65:55:d0:92:f4: + e7:84:b5:06:26:83:12:84:0b:ac:3b:b2:44:ee:bd:cf:40:db: + 20:0e:ba:6e:14:ea:30:e0:3b:62:7c:7f:8b:6b:7c:4a:a7:d5: + 35:3c:be:a8:5c:ea:4b:bb:93:8e:80:66:ab:0f:29:fd:4d:2d: + bf:1a:9b:0a:90:c5:ab:da:d1:b3:86:d4:2f:24:52:5c:7a:6d: + c6:f2:fe:e5:4d:1a:30:8c:90:f2:ba:d7:4a:3e:43:7e:d4:c8: + 50:1a:87:f8:4f:81:c7:76:0b:84:3a:72:9d:ce:65:66:97:ae: + 26:5e +-----BEGIN CERTIFICATE----- +MIIEgzCCA+ygAwIBAgIEOJ725DANBgkqhkiG9w0BAQQFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9HQ0NBX0NQUyBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAyMDAw +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENsaWVu +dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMDAyMDcxNjE2NDBaFw0yMDAy +MDcxNjQ2NDBaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0dDQ0FfQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDIwMDAgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2xpZW50IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTdLS25MVL1qFof2LV7PdRV7Ny +Spj10InJrWPNTTVRaoTUrcloeW+46xHbh65cJFET8VQlhK8pK5/jgOLZy93GRUk0 +iJBeAZfv6lOm3fzB3ksqJeTpNfpVBQbliXrqpBFXO/x8PTbNZzVtpKklWb1m9fkn +5JVn1j+SgF7yNH0rhQIDAQABo4IBnjCCAZowEQYJYIZIAYb4QgEBBAQDAgAHMIHd +BgNVHR8EgdUwgdIwgc+ggcyggcmkgcYwgcMxFDASBgNVBAoTC0VudHJ1c3QubmV0 +MUAwPgYDVQQLFDd3d3cuZW50cnVzdC5uZXQvR0NDQV9DUFMgaW5jb3JwLiBieSBy +ZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMjAwMCBFbnRydXN0Lm5l +dCBMaW1pdGVkMTMwMQYDVQQDEypFbnRydXN0Lm5ldCBDbGllbnQgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMjAwMDAy +MDcxNjE2NDBagQ8yMDIwMDIwNzE2NDY0MFowCwYDVR0PBAQDAgEGMB8GA1UdIwQY +MBaAFISLdP3FjcD/J20gN0V8/i3OutN9MB0GA1UdDgQWBBSEi3T9xY3A/ydtIDdF +fP4tzrrTfTAMBgNVHRMEBTADAQH/MB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4w +AwIEkDANBgkqhkiG9w0BAQQFAAOBgQBObzWAO9GK9Q6nIMstZVXQkvTnhLUGJoMS +hAusO7JE7r3PQNsgDrpuFOow4DtifH+La3xKp9U1PL6oXOpLu5OOgGarDyn9TS2/ +GpsKkMWr2tGzhtQvJFJcem3G8v7lTRowjJDyutdKPkN+1MhQGof4T4HHdguEOnKd +zmVml64mXg== +-----END CERTIFICATE----- + +AOL Time Warner Root Certification Authority 1 +============================================== + +MD5 Fingerprint=E7:7A:DC:B1:1F:6E:06:1F:74:6C:59:16:27:C3:4B:C0 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=AOL Time Warner Inc., OU=America Online Inc., CN=AOL Time Warner Root Certification Authority 1 + Validity + Not Before: May 29 06:00:00 2002 GMT + Not After : Nov 20 15:03:00 2037 GMT + Subject: C=US, O=AOL Time Warner Inc., OU=America Online Inc., CN=AOL Time Warner Root Certification Authority 1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:99:de:8f:c3:25:a3:69:34:e8:05:f7:74:b9:bf: + 5a:97:19:b9:2f:94:d2:93:e5:2d:89:ca:84:7c:3f: + 10:43:1b:8c:8b:7c:84:58:f8:24:7c:48:cf:2a:fd: + c0:15:d9:18:7e:84:1a:17:d3:db:9e:d7:ca:e4:d9: + d7:aa:58:51:87:f0:f0:8b:48:4e:e2:c2:c4:59:69: + 30:62:b6:30:a2:8c:0b:11:99:61:35:6d:7e:ef:c5: + b1:19:06:20:12:8e:42:e1:df:0f:96:10:52:a8:cf: + 9c:5f:95:14:d8:af:3b:75:0b:31:20:1f:44:2f:a2: + 62:41:b3:bb:18:21:db:ca:71:3c:8c:ec:b6:b9:0d: + 9f:ef:51:ef:4d:7b:12:f2:0b:0c:e1:ac:40:8f:77: + 7f:b0:ca:78:71:0c:5d:16:71:70:a2:d7:c2:3a:85: + cd:0e:9a:c4:e0:00:b0:d5:25:ea:dc:2b:e4:94:2d: + 38:9c:89:41:57:64:28:65:19:1c:b6:44:b4:c8:31: + 6b:8e:01:7b:76:59:25:7f:15:1c:84:08:7c:73:65: + 20:0a:a1:04:2e:1a:32:a8:9a:20:b1:9c:2c:21:59: + e7:fb:cf:ee:70:2d:08:ca:63:3e:2c:9b:93:19:6a: + a4:c2:97:ff:b7:86:57:88:85:6c:9e:15:16:2b:4d: + 2c:b3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + A1:36:30:16:CB:86:90:00:45:80:53:B1:8F:C8:D8:3D:7C:BE:5F:12 + X509v3 Authority Key Identifier: + keyid:A1:36:30:16:CB:86:90:00:45:80:53:B1:8F:C8:D8:3D:7C:BE:5F:12 + + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 8a:20:18:a5:be:b3:2f:b4:a6:84:00:40:30:29:fa:b4:14:73: + 4c:79:45:a7:f6:70:e0:e8:7e:64:1e:0a:95:7c:6a:61:c2:ef: + 4e:1f:be:ff:c9:99:1f:07:61:4a:e1:5d:4c:cd:ad:ee:d0:52: + 32:d9:59:32:bc:da:79:72:d6:7b:09:e8:02:81:35:d3:0a:df: + 11:1d:c9:79:a0:80:4d:fe:5a:d7:56:d6:ed:0f:2a:af:a7:18: + 75:33:0c:ea:c1:61:05:4f:6a:9a:89:f2:8d:b9:9f:2e:ef:b0: + 5f:5a:00:eb:be:ad:a0:f8:44:05:67:bc:cb:04:ef:9e:64:c5: + e9:c8:3f:05:bf:c6:2f:07:1c:c3:36:71:86:ca:38:66:4a:cd: + d6:b8:4b:c6:6c:a7:97:3b:fa:13:2d:6e:23:61:87:a1:63:42: + ac:c2:cb:97:9f:61:68:cf:2d:4c:04:9d:d7:25:4f:0a:0e:4d: + 90:8b:18:56:a8:93:48:57:dc:6f:ae:bd:9e:67:57:77:89:50: + b3:be:11:9b:45:67:83:86:19:87:d3:98:bd:08:1a:16:1f:58: + 82:0b:e1:96:69:05:4b:8e:ec:83:51:31:07:d5:d4:9f:ff:59: + 7b:a8:6e:85:cf:d3:4b:a9:49:b0:5f:b0:39:28:68:0e:73:dd: + 25:9a:de:12 +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMx +HTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNh +IE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyOTA2MDAwMFoXDTM3MTEyMDE1 +MDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wgVGltZSBXYXJuZXIg +SW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMuQU9M +IFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnej8Mlo2k06AX3dLm/WpcZuS+U +0pPlLYnKhHw/EEMbjIt8hFj4JHxIzyr9wBXZGH6EGhfT257XyuTZ16pYUYfw8ItI +TuLCxFlpMGK2MKKMCxGZYTVtfu/FsRkGIBKOQuHfD5YQUqjPnF+VFNivO3ULMSAf +RC+iYkGzuxgh28pxPIzstrkNn+9R7017EvILDOGsQI93f7DKeHEMXRZxcKLXwjqF +zQ6axOAAsNUl6twr5JQtOJyJQVdkKGUZHLZEtMgxa44Be3ZZJX8VHIQIfHNlIAqh +BC4aMqiaILGcLCFZ5/vP7nAtCMpjPiybkxlqpMKX/7eGV4iFbJ4VFitNLLMCAwEA +AaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUoTYwFsuGkABFgFOxj8jY +PXy+XxIwHwYDVR0jBBgwFoAUoTYwFsuGkABFgFOxj8jYPXy+XxIwDgYDVR0PAQH/ +BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQCKIBilvrMvtKaEAEAwKfq0FHNMeUWn +9nDg6H5kHgqVfGphwu9OH77/yZkfB2FK4V1Mza3u0FIy2VkyvNp5ctZ7CegCgTXT +Ct8RHcl5oIBN/lrXVtbtDyqvpxh1MwzqwWEFT2qaifKNuZ8u77BfWgDrvq2g+EQF +Z7zLBO+eZMXpyD8Fv8YvBxzDNnGGyjhmSs3WuEvGbKeXO/oTLW4jYYehY0KswsuX +n2Fozy1MBJ3XJU8KDk2QixhWqJNIV9xvrr2eZ1d3iVCzvhGbRWeDhhmH05i9CBoW +H1iCC+GWaQVLjuyDUTEH1dSf/1l7qG6Fz9NLqUmwX7A5KGgOc90lmt4S +-----END CERTIFICATE----- + +AOL Time Warner Root Certification Authority 2 +============================================== + +MD5 Fingerprint=01:5A:99:C3:D6:4F:A9:4B:3C:3B:B1:A3:AB:27:4C:BF +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=AOL Time Warner Inc., OU=America Online Inc., CN=AOL Time Warner Root Certification Authority 2 + Validity + Not Before: May 29 06:00:00 2002 GMT + Not After : Sep 28 23:43:00 2037 GMT + Subject: C=US, O=AOL Time Warner Inc., OU=America Online Inc., CN=AOL Time Warner Root Certification Authority 2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (4096 bit) + Modulus (4096 bit): + 00:b4:37:5a:08:16:99:14:e8:55:b1:1b:24:6b:fc: + c7:8b:e6:87:a9:89:ee:8b:99:cd:4f:40:86:a4:b6: + 4d:c9:d9:b1:dc:3c:4d:0d:85:4c:15:6c:46:8b:52: + 78:9f:f8:23:fd:67:f5:24:3a:68:5d:d0:f7:64:61: + 41:54:a3:8b:a5:08:d2:29:5b:9b:60:4f:26:83:d1: + 63:12:56:49:76:a4:16:c2:a5:9d:45:ac:8b:84:95: + a8:16:b1:ec:9f:ea:24:1a:ef:b9:57:5c:9a:24:21: + 2c:4d:0e:71:1f:a6:ac:5d:45:74:03:98:c4:54:8c: + 16:4a:41:77:86:95:75:0c:47:01:66:60:fc:15:f1: + 0f:ea:f5:14:78:c7:0e:d7:6e:81:1c:5e:bf:5e:e7: + 3a:2a:d8:97:17:30:7c:00:ad:08:9d:33:af:b8:99: + 61:80:8b:a8:95:7e:14:dc:12:6c:a4:d0:d8:ef:40: + 49:02:36:f9:6e:a9:d6:1d:96:56:04:b2:b3:2d:16: + 56:86:8f:d9:20:57:80:cd:67:10:6d:b0:4c:f0:da: + 46:b6:ea:25:2e:46:af:8d:b0:85:38:34:8b:14:26: + 82:2b:ac:ae:99:0b:8e:14:d7:52:bd:9e:69:c3:86: + 02:0b:ea:76:75:31:09:ce:33:19:21:85:43:e6:89: + 2d:9f:25:37:67:f1:23:6a:d2:00:6d:97:f9:9f:e7: + 29:ca:dd:1f:d7:06:ea:b8:c9:b9:09:21:9f:c8:3f: + 06:c5:d2:e9:12:46:00:4e:7b:08:eb:42:3d:2b:48: + 6e:9d:67:dd:4b:02:e4:44:f3:93:19:a5:27:ce:69: + 7a:be:67:d3:fc:50:a4:2c:ab:c3:6b:b9:e3:80:4c: + cf:05:61:4b:2b:dc:1b:b9:a6:d2:d0:aa:f5:2b:73: + fb:ce:90:35:9f:0c:52:1c:bf:5c:21:61:11:5b:15: + 4b:a9:24:51:fc:a4:5c:f7:17:9d:b0:d2:fa:07:e9: + 8f:56:e4:1a:8c:68:8a:04:d3:7c:5a:e3:9e:a2:a1: + ca:71:5b:a2:d4:a0:e7:29:85:5d:03:68:2a:4f:d2: + 06:d7:3d:f9:c3:03:2f:3f:65:f9:67:1e:47:40:d3: + 63:0f:e3:d5:8e:f9:85:ab:97:4c:b3:d7:26:eb:96: + 0a:94:de:85:36:9c:c8:7f:81:09:02:49:2a:0e:f5: + 64:32:0c:82:d1:ba:6a:82:1b:b3:4b:74:11:f3:8c: + 77:d6:9f:bf:dc:37:a4:a7:55:04:2f:d4:31:e8:d3: + 46:b9:03:7c:da:12:4e:59:64:b7:51:31:31:50:a0: + ca:1c:27:d9:10:2e:ad:d6:bd:10:66:2b:c3:b0:22: + 4a:12:5b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + 4F:69:6D:03:7E:9D:9F:07:18:43:BC:B7:10:4E:D5:BF:A9:C4:20:28 + X509v3 Authority Key Identifier: + keyid:4F:69:6D:03:7E:9D:9F:07:18:43:BC:B7:10:4E:D5:BF:A9:C4:20:28 + + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 3b:f3:ae:ca:e8:2e:87:85:fb:65:59:e7:ad:11:14:a5:57:bc: + 58:9f:24:12:57:bb:fb:3f:34:da:ee:ad:7a:2a:34:72:70:31: + 6b:c7:19:98:80:c9:82:de:37:77:5e:54:8b:8e:f2:ea:67:4f: + c9:74:84:91:56:09:d5:e5:7a:9a:81:b6:81:c2:ad:36:e4:f1: + 54:11:53:f3:34:45:01:26:c8:e5:1a:bc:34:44:21:de:ad:25: + fc:76:16:77:21:90:80:98:57:9d:4e:ea:ec:2f:aa:3c:14:7b: + 57:c1:7e:18:14:67:ee:24:c6:bd:ba:15:b0:d2:18:bd:b7:55: + 81:ac:53:c0:e8:dd:69:12:13:42:b7:02:b5:05:41:ca:79:50: + 6e:82:0e:71:72:93:46:e8:9d:0d:5d:bd:ae:ce:29:ad:63:d5: + 55:16:80:30:27:ff:76:ba:f7:b8:d6:4a:e3:d9:b5:f9:52:d0: + 4e:40:a9:c7:e5:c2:32:c7:aa:76:24:e1:6b:05:50:eb:c5:bf: + 0a:54:e5:b9:42:3c:24:fb:b7:07:9c:30:9f:79:5a:e6:e0:40: + 52:15:f4:fc:aa:f4:56:f9:44:97:87:ed:0e:65:72:5e:be:26: + fb:4d:a4:2d:08:07:de:d8:5c:a0:dc:81:33:99:18:25:11:77: + a7:eb:fd:58:09:2c:99:6b:1b:8a:f3:52:3f:1a:4d:48:60:f1: + a0:f6:33:02:53:8b:ed:25:09:b8:0d:2d:ed:97:73:ec:d7:96: + 1f:8e:60:0e:da:10:9b:2f:18:24:f6:a6:4d:0a:f9:3b:cb:75: + c2:cc:2f:ce:24:69:c9:0a:22:8e:59:a7:f7:82:0c:d7:d7:6b: + 35:9c:43:00:6a:c4:95:67:ba:9c:45:cb:b8:0e:37:f7:dc:4e: + 01:4f:be:0a:b6:03:d3:ad:8a:45:f7:da:27:4d:29:b1:48:df: + e4:11:e4:96:46:bd:6c:02:3e:d6:51:c8:95:17:01:15:a9:f2: + aa:aa:f2:bf:2f:65:1b:6f:d0:b9:1a:93:f5:8e:35:c4:80:87: + 3e:94:2f:66:e4:e9:a8:ff:41:9c:70:2a:4f:2a:39:18:95:1e: + 7e:fb:61:01:3c:51:08:2e:28:18:a4:16:0f:31:fd:3a:6c:23: + 93:20:76:e1:fd:07:85:d1:5b:3f:d2:1c:73:32:dd:fa:b9:f8: + 8c:cf:02:87:7a:9a:96:e4:ed:4f:89:8d:53:43:ab:0e:13:c0: + 01:15:b4:79:38:db:fc:6e:3d:9e:51:b6:b8:13:8b:67:cf:f9: + 7c:d9:22:1d:f6:5d:c5:1c:01:2f:98:e8:7a:24:18:bc:84:d7: + fa:dc:72:5b:f7:c1:3a:68 +-----BEGIN CERTIFICATE----- +MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMCVVMx +HTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNBbWVyaWNh +IE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAwMFoXDTM3MDkyODIz +NDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRBT0wgVGltZSBXYXJuZXIg +SW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUgSW5jLjE3MDUGA1UEAxMuQU9M +IFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ +7ouZzU9AhqS2TcnZsdw8TQ2FTBVsRotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilb +m2BPJoPRYxJWSXakFsKlnUWsi4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOY +xFSMFkpBd4aVdQxHAWZg/BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZ +YYCLqJV+FNwSbKTQ2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbq +JS5Gr42whTg0ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fx +I2rSAG2X+Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETz +kxmlJ85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh +EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNoKk/S +Btc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJKg71ZDIM +gtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1ExMVCgyhwn2RAu +rda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaAFE9pbQN+nZ8HGEO8txBO +1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAO/Ouyugu +h4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0cnAxa8cZmIDJgt43d15Ui47y6mdP +yXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRFASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q +7C+qPBR7V8F+GBRn7iTGvboVsNIYvbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKT +RuidDV29rs4prWPVVRaAMCf/drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/ +ClTluUI8JPu3B5wwn3la5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyB +M5kYJRF3p+v9WAksmWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQ +my8YJPamTQr5O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xO +AU++CrYD062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT +9Y41xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H +hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOLZ8/5 +fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg= +-----END CERTIFICATE----- + +beTRUSTed Root CA-Baltimore Implementation +========================================== + +MD5 Fingerprint=81:35:B9:FB:FB:12:CA:18:69:36:EB:AE:69:78:A1:F1 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1018510662 (0x3cb53d46) + Signature Algorithm: sha1WithRSAEncryption + Issuer: O=beTRUSTed, OU=beTRUSTed Root CAs, CN=beTRUSTed Root CA-Baltimore Implementation + Validity + Not Before: Apr 11 07:38:51 2002 GMT + Not After : Apr 11 07:38:51 2022 GMT + Subject: O=beTRUSTed, OU=beTRUSTed Root CAs, CN=beTRUSTed Root CA-Baltimore Implementation + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:bc:7e:c4:39:9c:8c:e3:d6:1c:86:ff:ca:62:ad: + e0:7f:30:45:7a:8e:1a:b3:b8:c7:f9:d1:36:ff:22: + f3:4e:6a:5f:84:10:fb:66:81:c3:94:79:31:d2:91: + e1:77:8e:18:2a:c3:14:de:51:f5:4f:a3:2b:bc:18: + 16:e2:b5:dd:79:de:22:f8:82:7e:cb:81:1f:fd:27: + 2c:8f:fa:97:64:22:8e:f8:ff:61:a3:9c:1b:1e:92: + 8f:c0:a8:09:df:09:11:ec:b7:7d:31:9a:1a:ea:83: + 21:06:3c:9f:ba:5c:ff:94:ea:6a:b8:c3:6b:55:34: + 4f:3d:32:1f:dd:81:14:e0:c4:3c:cd:9d:30:f8:30: + a9:97:d3:ee:cc:a3:d0:1f:5f:1c:13:81:d4:18:ab: + 94:d1:63:c3:9e:7f:35:92:9e:5f:44:ea:ec:f4:22: + 5c:b7:e8:3d:7d:a4:f9:89:a9:91:b2:2a:d9:eb:33: + 87:ee:a5:fd:e3:da:cc:88:e6:89:26:6e:c7:2b:82: + d0:5e:9d:59:db:14:ec:91:83:05:c3:5e:0e:c6:2a: + d0:04:dd:71:3d:20:4e:58:27:fc:53:fb:78:78:19: + 14:b2:fc:90:52:89:38:62:60:07:b4:a0:ec:ac:6b: + 50:d6:fd:b9:28:6b:ef:52:2d:3a:b2:ff:f1:01:40: + ac:37 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.6334.0.0.1.9.40.51377 + User Notice: + Explicit Text: Reliance on or use of this Certificate creates an acknowledgment and acceptance of the then applicable standard terms and conditions of use, the Certification Practice Statement and the Relying Party Agreement, which can be found at the beTRUSTed web site, http://www.betrusted.com/products_services/index.html + CPS: http://www.betrusted.com/products_services/index.html + + X509v3 Subject Key Identifier: + 45:3D:C3:A9:D1:DC:3F:24:56:98:1C:73:18:88:6A:FF:83:47:ED:B6 + X509v3 Authority Key Identifier: + keyid:45:3D:C3:A9:D1:DC:3F:24:56:98:1C:73:18:88:6A:FF:83:47:ED:B6 + + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 49:92:bc:a3:ee:ac:bd:fa:0d:c9:8b:79:86:1c:23:76:b0:80: + 59:77:fc:da:7f:b4:4b:df:c3:64:4b:6a:4e:0e:ad:f2:7d:59: + 77:05:ad:0a:89:73:b0:fa:bc:cb:dc:8d:00:88:8f:a6:a0:b2: + ea:ac:52:27:bf:a1:48:7c:97:10:7b:ba:ed:13:1d:9a:07:6e: + cb:31:62:12:e8:63:03:aa:7d:6d:e3:f8:1b:76:21:78:1b:9f: + 4b:43:8c:d3:49:86:f6:1b:5c:f6:2e:60:15:d3:e9:e3:7b:75: + 3f:d0:02:83:d0:18:82:41:cd:65:37:ea:8e:32:7e:bd:6b:99: + 5d:30:11:c8:db:48:54:1c:3b:e1:a7:13:d3:6a:48:93:f7:3d: + 8c:7f:05:e8:ce:f3:88:2a:63:04:b8:ea:7e:58:7c:01:7b:5b: + e1:c5:7d:ef:21:e0:8d:0e:5d:51:7d:b1:67:fd:a3:bd:38:36: + c6:f2:38:86:87:1a:96:68:60:46:fb:28:14:47:55:e1:a7:80: + 0c:6b:e2:ea:df:4d:7c:90:48:a0:36:bd:09:17:89:7f:c3:f2: + d3:9c:9c:e3:dd:c4:1b:dd:f5:b7:71:b3:53:05:89:06:d0:cb: + 4a:80:c1:c8:53:90:b5:3c:31:88:17:50:9f:c9:c4:0e:8b:d8: + a8:02:63:0d +-----BEGIN CERTIFICATE----- +MIIFajCCBFKgAwIBAgIEPLU9RjANBgkqhkiG9w0BAQUFADBmMRIwEAYDVQQKEwli +ZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEzMDEGA1UEAxMq +YmVUUlVTVGVkIFJvb3QgQ0EtQmFsdGltb3JlIEltcGxlbWVudGF0aW9uMB4XDTAy +MDQxMTA3Mzg1MVoXDTIyMDQxMTA3Mzg1MVowZjESMBAGA1UEChMJYmVUUlVTVGVk +MRswGQYDVQQLExJiZVRSVVNUZWQgUm9vdCBDQXMxMzAxBgNVBAMTKmJlVFJVU1Rl +ZCBSb290IENBLUJhbHRpbW9yZSBJbXBsZW1lbnRhdGlvbjCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALx+xDmcjOPWHIb/ymKt4H8wRXqOGrO4x/nRNv8i +805qX4QQ+2aBw5R5MdKR4XeOGCrDFN5R9U+jK7wYFuK13XneIviCfsuBH/0nLI/6 +l2Qijvj/YaOcGx6Sj8CoCd8JEey3fTGaGuqDIQY8n7pc/5TqarjDa1U0Tz0yH92B +FODEPM2dMPgwqZfT7syj0B9fHBOB1BirlNFjw55/NZKeX0Tq7PQiXLfoPX2k+Ymp +kbIq2eszh+6l/ePazIjmiSZuxyuC0F6dWdsU7JGDBcNeDsYq0ATdcT0gTlgn/FP7 +eHgZFLL8kFKJOGJgB7Sg7KxrUNb9uShr71ItOrL/8QFArDcCAwEAAaOCAh4wggIa +MA8GA1UdEwEB/wQFMAMBAf8wggG1BgNVHSAEggGsMIIBqDCCAaQGDysGAQQBsT4A +AAEJKIORMTCCAY8wggFIBggrBgEFBQcCAjCCAToaggE2UmVsaWFuY2Ugb24gb3Ig +dXNlIG9mIHRoaXMgQ2VydGlmaWNhdGUgY3JlYXRlcyBhbiBhY2tub3dsZWRnbWVu +dCBhbmQgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJk +IHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgdGhlIENlcnRpZmljYXRpb24g +UHJhY3RpY2UgU3RhdGVtZW50IGFuZCB0aGUgUmVseWluZyBQYXJ0eSBBZ3JlZW1l +bnQsIHdoaWNoIGNhbiBiZSBmb3VuZCBhdCB0aGUgYmVUUlVTVGVkIHdlYiBzaXRl +LCBodHRwOi8vd3d3LmJldHJ1c3RlZC5jb20vcHJvZHVjdHNfc2VydmljZXMvaW5k +ZXguaHRtbDBBBggrBgEFBQcCARY1aHR0cDovL3d3dy5iZXRydXN0ZWQuY29tL3By +b2R1Y3RzX3NlcnZpY2VzL2luZGV4Lmh0bWwwHQYDVR0OBBYEFEU9w6nR3D8kVpgc +cxiIav+DR+22MB8GA1UdIwQYMBaAFEU9w6nR3D8kVpgccxiIav+DR+22MA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEASZK8o+6svfoNyYt5hhwjdrCA +WXf82n+0S9/DZEtqTg6t8n1ZdwWtColzsPq8y9yNAIiPpqCy6qxSJ7+hSHyXEHu6 +7RMdmgduyzFiEuhjA6p9beP4G3YheBufS0OM00mG9htc9i5gFdPp43t1P9ACg9AY +gkHNZTfqjjJ+vWuZXTARyNtIVBw74acT02pIk/c9jH8F6M7ziCpjBLjqflh8AXtb +4cV97yHgjQ5dUX2xZ/2jvTg2xvI4hocalmhgRvsoFEdV4aeADGvi6t9NfJBIoDa9 +CReJf8Py05yc493EG931t3GzUwWJBtDLSoDByFOQtTwxiBdQn8nEDovYqAJjDQ== +-----END CERTIFICATE----- + +beTRUSTed Root CA - Entrust Implementation +========================================== + +MD5 Fingerprint=7D:86:90:8F:5B:F1:F2:40:C0:F7:3D:62:B5:A4:A9:3B +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1018515264 (0x3cb54f40) + Signature Algorithm: sha1WithRSAEncryption + Issuer: O=beTRUSTed, OU=beTRUSTed Root CAs, CN=beTRUSTed Root CA - Entrust Implementation + Validity + Not Before: Apr 11 08:24:27 2002 GMT + Not After : Apr 11 08:54:27 2022 GMT + Subject: O=beTRUSTed, OU=beTRUSTed Root CAs, CN=beTRUSTed Root CA - Entrust Implementation + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ba:f4:44:03:aa:12:6a:b5:43:ec:55:92:b6:30: + 7d:35:57:0c:db:f3:0d:27:6e:4c:f7:50:a8:9b:4e: + 2b:6f:db:f5:ad:1c:4b:5d:b3:a9:c1:fe:7b:44:eb: + 5b:a3:05:0d:1f:c5:34:2b:30:00:29:f1:78:40:b2: + a4:ff:3a:f4:01:88:17:7e:e6:d4:26:d3:ba:4c:ea: + 32:fb:43:77:97:87:23:c5:db:43:a3:f5:2a:a3:51: + 5e:e1:3b:d2:65:69:7e:55:15:9b:7a:e7:69:f7:44: + e0:57:b5:15:e8:66:60:0f:0d:03:fb:82:8e:a3:e8: + 11:7b:6c:be:c7:63:0e:17:93:df:cf:4b:ae:6e:73: + 75:e0:f3:aa:b9:a4:c0:09:1b:85:ea:71:29:88:41: + 32:f9:f0:2a:0e:6c:09:f2:74:6b:66:6c:52:13:1f: + 18:bc:d4:3e:f7:d8:6e:20:9e:ca:fe:fc:21:94:ee: + 13:28:4b:d7:5c:5e:0c:66:ee:e9:bb:0f:c1:34:b1: + 7f:08:76:f3:3d:26:70:c9:8b:25:1d:62:24:0c:ea: + 1c:75:4e:c0:12:e4:ba:13:1d:30:29:2d:56:33:05: + bb:97:59:7e:c6:49:4f:89:d7:2f:24:a8:b6:88:40: + b5:64:92:53:56:24:e4:a2:a0:85:b3:5e:90:b4:12: + 33:cd + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.6334.0.0.2.9.40.51377 + User Notice: + Explicit Text: Reliance on or use of this Certificate creates an acknowledgment and acceptance of the then applicable standard terms and conditions of use, the Certification Practice Statement and the Relying Party Agreement, which can be found at the beTRUSTed web site, https://www.betrusted.com/products_services/index.html + CPS: https://www.betrusted.com/products_services/index.html + + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/O=beTRUSTed/OU=beTRUSTed Root CAs/CN=beTRUSTed Root CA - Entrust Implementation/CN=CRL1 + + X509v3 Private Key Usage Period: + Not Before: Apr 11 08:24:27 2002 GMT, Not After: Apr 11 08:54:27 2022 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:7D:70:E5:AE:38:8B:06:3F:AA:1C:1A:8F:F9:CF:24:30:AA:84:84:16 + + X509v3 Subject Key Identifier: + 7D:70:E5:AE:38:8B:06:3F:AA:1C:1A:8F:F9:CF:24:30:AA:84:84:16 + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0...V6.0:4.0.... + Signature Algorithm: sha1WithRSAEncryption + 2a:b8:17:ce:1f:10:94:eb:b8:9a:b7:b9:5f:ec:da:f7:92:24: + ac:dc:92:3b:c7:20:8d:f2:99:e5:5d:38:a1:c2:34:ed:c5:13: + 59:5c:05:b5:2b:4f:61:9b:91:fb:41:fc:fc:d5:3c:4d:98:76: + 06:f5:81:7d:eb:dd:90:e6:d1:56:54:da:e3:2d:0c:9f:11:32: + 94:22:01:7a:f6:6c:2c:74:67:04:cc:a5:8f:8e:2c:b3:43:b5: + 94:a2:d0:7d:e9:62:7f:06:be:27:01:83:9e:3a:fd:8a:ee:98: + 43:4a:6b:d7:b5:97:3b:3a:bf:4f:6d:b4:63:fa:33:00:34:2e: + 2d:6d:96:c9:7b:ca:99:63:ba:be:f4:f6:30:a0:2d:98:96:e9: + 56:44:05:a9:44:a3:61:10:eb:82:a1:67:5d:bc:5d:27:75:aa: + 8a:28:36:2a:38:92:d9:dd:a4:5e:00:a5:cc:cc:7c:29:2a:de: + 28:90:ab:b7:e1:b6:ff:7d:25:0b:40:d8:aa:34:a3:2d:de:07: + eb:5f:ce:0a:dd:ca:7e:3a:7d:26:c1:62:68:3a:e6:2f:37:f3: + 81:86:21:c4:a9:64:aa:ef:45:36:d1:1a:66:7c:f8:e9:37:d6: + d6:61:be:a2:ad:48:e7:df:e6:74:fe:d3:6d:7d:d2:25:dc:ac: + 62:57:a9:f7 +-----BEGIN CERTIFICATE----- +MIIGUTCCBTmgAwIBAgIEPLVPQDANBgkqhkiG9w0BAQUFADBmMRIwEAYDVQQKEwli +ZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEzMDEGA1UEAxMq +YmVUUlVTVGVkIFJvb3QgQ0EgLSBFbnRydXN0IEltcGxlbWVudGF0aW9uMB4XDTAy +MDQxMTA4MjQyN1oXDTIyMDQxMTA4NTQyN1owZjESMBAGA1UEChMJYmVUUlVTVGVk +MRswGQYDVQQLExJiZVRSVVNUZWQgUm9vdCBDQXMxMzAxBgNVBAMTKmJlVFJVU1Rl +ZCBSb290IENBIC0gRW50cnVzdCBJbXBsZW1lbnRhdGlvbjCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALr0RAOqEmq1Q+xVkrYwfTVXDNvzDSduTPdQqJtO +K2/b9a0cS12zqcH+e0TrW6MFDR/FNCswACnxeECypP869AGIF37m1CbTukzqMvtD +d5eHI8XbQ6P1KqNRXuE70mVpflUVm3rnafdE4Fe1FehmYA8NA/uCjqPoEXtsvsdj +DheT389Lrm5zdeDzqrmkwAkbhepxKYhBMvnwKg5sCfJ0a2ZsUhMfGLzUPvfYbiCe +yv78IZTuEyhL11xeDGbu6bsPwTSxfwh28z0mcMmLJR1iJAzqHHVOwBLkuhMdMCkt +VjMFu5dZfsZJT4nXLySotohAtWSSU1Yk5KKghbNekLQSM80CAwEAAaOCAwUwggMB +MIIBtwYDVR0gBIIBrjCCAaowggGmBg8rBgEEAbE+AAACCSiDkTEwggGRMIIBSQYI +KwYBBQUHAgIwggE7GoIBN1JlbGlhbmNlIG9uIG9yIHVzZSBvZiB0aGlzIENlcnRp +ZmljYXRlIGNyZWF0ZXMgYW4gYWNrbm93bGVkZ21lbnQgYW5kIGFjY2VwdGFuY2Ug +b2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0 +aW9ucyBvZiB1c2UsIHRoZSBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dCBhbmQgdGhlIFJlbHlpbmcgUGFydHkgQWdyZWVtZW50LCB3aGljaCBjYW4gYmUg +Zm91bmQgYXQgdGhlIGJlVFJVU1RlZCB3ZWIgc2l0ZSwgaHR0cHM6Ly93d3cuYmV0 +cnVzdGVkLmNvbS9wcm9kdWN0c19zZXJ2aWNlcy9pbmRleC5odG1sMEIGCCsGAQUF +BwIBFjZodHRwczovL3d3dy5iZXRydXN0ZWQuY29tL3Byb2R1Y3RzX3NlcnZpY2Vz +L2luZGV4Lmh0bWwwEQYJYIZIAYb4QgEBBAQDAgAHMIGJBgNVHR8EgYEwfzB9oHug +eaR3MHUxEjAQBgNVBAoTCWJlVFJVU1RlZDEbMBkGA1UECxMSYmVUUlVTVGVkIFJv +b3QgQ0FzMTMwMQYDVQQDEypiZVRSVVNUZWQgUm9vdCBDQSAtIEVudHJ1c3QgSW1w +bGVtZW50YXRpb24xDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMjAwMjA0MTEw +ODI0MjdagQ8yMDIyMDQxMTA4NTQyN1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaA +FH1w5a44iwY/qhwaj/nPJDCqhIQWMB0GA1UdDgQWBBR9cOWuOIsGP6ocGo/5zyQw +qoSEFjAMBgNVHRMEBTADAQH/MB0GCSqGSIb2fQdBAAQQMA4bCFY2LjA6NC4wAwIE +kDANBgkqhkiG9w0BAQUFAAOCAQEAKrgXzh8QlOu4mre5X+za95IkrNySO8cgjfKZ +5V04ocI07cUTWVwFtStPYZuR+0H8/NU8TZh2BvWBfevdkObRVlTa4y0MnxEylCIB +evZsLHRnBMylj44ss0O1lKLQfelifwa+JwGDnjr9iu6YQ0pr17WXOzq/T220Y/oz +ADQuLW2WyXvKmWO6vvT2MKAtmJbpVkQFqUSjYRDrgqFnXbxdJ3Wqiig2KjiS2d2k +XgClzMx8KSreKJCrt+G2/30lC0DYqjSjLd4H61/OCt3Kfjp9JsFiaDrmLzfzgYYh +xKlkqu9FNtEaZnz46TfW1mG+oq1I59/mdP7TbX3SJdysYlep9w== +-----END CERTIFICATE----- + +beTRUSTed Root CA - RSA Implementation +====================================== + +MD5 Fingerprint=86:42:05:09:BC:A7:9D:EC:1D:F3:2E:0E:BA:D8:1D:D0 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 3b:59:c7:7b:cd:5b:57:9e:bd:37:52:ac:76:b4:aa:1a + Signature Algorithm: sha1WithRSAEncryption + Issuer: O=beTRUSTed, OU=beTRUSTed Root CAs, CN=beTRUSTed Root CA - RSA Implementation + Validity + Not Before: Apr 11 11:18:13 2002 GMT + Not After : Apr 12 11:07:25 2022 GMT + Subject: O=beTRUSTed, OU=beTRUSTed Root CAs, CN=beTRUSTed Root CA - RSA Implementation + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:e4:ba:34:30:09:8e:57:d0:b9:06:2c:6f:6e:24: + 80:22:bf:5d:43:a6:fa:4f:ac:82:e7:1c:68:70:85: + 1b:a3:6e:b5:aa:78:d9:6e:07:4b:3f:e9:df:f5:ea: + e8:54:a1:61:8a:0e:2f:69:75:18:b7:0c:e5:14:8d: + 71:6e:98:b8:55:fc:0c:95:d0:9b:6e:e1:2d:88:d4: + 3a:40:6b:92:f1:99:96:64:de:db:ff:78:f4:ee:96: + 1d:47:89:7c:d4:be:b9:88:77:23:3a:09:e6:04:9e: + 6d:aa:5e:d2:c8:bd:9a:4e:19:df:89:ea:5b:0e:7e: + c3:e4:b4:f0:e0:69:3b:88:0f:41:90:f8:d4:71:43: + 24:c1:8f:26:4b:3b:56:e9:ff:8c:6c:37:e9:45:ad: + 85:8c:53:c3:60:86:90:4a:96:c9:b3:54:b0:bb:17: + f0:1c:45:d9:d4:1b:19:64:56:0a:19:f7:cc:e1:ff: + 86:af:7e:58:5e:ac:7a:90:1f:c9:28:39:45:7b:a2: + b6:c7:9c:1f:da:85:d4:21:86:59:30:93:be:53:33: + 37:f6:ef:41:cf:33:c7:ab:72:6b:25:f5:f3:53:1b: + 0c:4c:2e:f1:75:4b:ef:a0:87:f7:fe:8a:15:d0:6c: + d5:cb:f9:68:53:b9:70:15:13:c2:f5:2e:fb:43:35: + 75:2d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:TRUE + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.6334.0.0.3.9.40.51377 + CPS: http://www.betrusted.com/products_services/index.html + User Notice: + Explicit Text: Reliance on or use of this Certificate creates an acknowledgment and acceptance of the then applicable standard terms and conditions of use, the Certification Practice Statement and the Relying Party Agreement, which can be found at the beTRUSTed web site, http://www.betrusted.com/products_services/index.html + + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:A9:EC:14:7E:F9:D9:43:CC:53:2B:14:AD:CF:F7:F0:59:89:41:CD:19 + + X509v3 Subject Key Identifier: + A9:EC:14:7E:F9:D9:43:CC:53:2B:14:AD:CF:F7:F0:59:89:41:CD:19 + Signature Algorithm: sha1WithRSAEncryption + db:97:b0:75:ea:0c:c4:c1:98:ca:56:05:c0:a8:ad:26:48:af: + 2d:20:e8:81:c7:b6:df:43:c1:2c:1d:75:4b:d4:42:8d:e7:7a: + a8:74:dc:66:42:59:87:b3:f5:69:6d:d9:a9:9e:b3:7d:1c:31: + c1:f5:54:e2:59:24:49:e5:ee:bd:39:a6:6b:8a:98:44:fb:9b: + d7:2a:83:97:34:2d:c7:7d:35:4c:2d:34:b8:3e:0d:c4:ec:88: + 27:af:9e:92:fd:50:61:82:a8:60:07:14:53:cc:65:13:c1:f6: + 47:44:69:d2:31:c8:a6:dd:2e:b3:0b:de:4a:8d:5b:3d:ab:0d: + c2:35:52:a2:56:37:cc:32:8b:28:85:42:9c:91:40:7a:70:2b: + 38:36:d5:e1:73:1a:1f:e5:fa:7e:5f:dc:d6:9c:3b:30:ea:db: + c0:5b:27:5c:d3:73:07:c1:c2:f3:4c:9b:6f:9f:1b:ca:1e:aa: + a8:38:33:09:58:b2:ae:fc:07:e8:36:dc:55:ba:2f:4f:40:fe: + 7a:bd:06:a6:81:c1:93:22:7c:86:11:0a:06:77:48:ae:35:b7: + 2f:32:9a:61:5e:8b:be:29:9f:29:24:88:56:39:2c:a8:d2:ab: + 96:03:5a:d4:48:9f:b9:40:84:0b:98:68:fb:01:43:d6:1b:e2: + 09:b1:97:1c +-----BEGIN CERTIFICATE----- +MIIFaDCCBFCgAwIBAgIQO1nHe81bV569N1KsdrSqGjANBgkqhkiG9w0BAQUFADBi +MRIwEAYDVQQKEwliZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENB +czEvMC0GA1UEAxMmYmVUUlVTVGVkIFJvb3QgQ0EgLSBSU0EgSW1wbGVtZW50YXRp +b24wHhcNMDIwNDExMTExODEzWhcNMjIwNDEyMTEwNzI1WjBiMRIwEAYDVQQKEwli +ZVRSVVNUZWQxGzAZBgNVBAsTEmJlVFJVU1RlZCBSb290IENBczEvMC0GA1UEAxMm +YmVUUlVTVGVkIFJvb3QgQ0EgLSBSU0EgSW1wbGVtZW50YXRpb24wggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkujQwCY5X0LkGLG9uJIAiv11DpvpPrILn +HGhwhRujbrWqeNluB0s/6d/16uhUoWGKDi9pdRi3DOUUjXFumLhV/AyV0Jtu4S2I +1DpAa5LxmZZk3tv/ePTulh1HiXzUvrmIdyM6CeYEnm2qXtLIvZpOGd+J6lsOfsPk +tPDgaTuID0GQ+NRxQyTBjyZLO1bp/4xsN+lFrYWMU8NghpBKlsmzVLC7F/AcRdnU +GxlkVgoZ98zh/4avflherHqQH8koOUV7orbHnB/ahdQhhlkwk75TMzf270HPM8er +cmsl9fNTGwxMLvF1S++gh/f+ihXQbNXL+WhTuXAVE8L1LvtDNXUtAgMBAAGjggIY +MIICFDAMBgNVHRMEBTADAQH/MIIBtQYDVR0gBIIBrDCCAagwggGkBg8rBgEEAbE+ +AAADCSiDkTEwggGPMEEGCCsGAQUFBwIBFjVodHRwOi8vd3d3LmJldHJ1c3RlZC5j +b20vcHJvZHVjdHNfc2VydmljZXMvaW5kZXguaHRtbDCCAUgGCCsGAQUFBwICMIIB +OhqCATZSZWxpYW5jZSBvbiBvciB1c2Ugb2YgdGhpcyBDZXJ0aWZpY2F0ZSBjcmVh +dGVzIGFuIGFja25vd2xlZGdtZW50IGFuZCBhY2NlcHRhbmNlIG9mIHRoZSB0aGVu +IGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5kIGNvbmRpdGlvbnMgb2YgdXNl +LCB0aGUgQ2VydGlmaWNhdGlvbiBQcmFjdGljZSBTdGF0ZW1lbnQgYW5kIHRoZSBS +ZWx5aW5nIFBhcnR5IEFncmVlbWVudCwgd2hpY2ggY2FuIGJlIGZvdW5kIGF0IHRo +ZSBiZVRSVVNUZWQgd2ViIHNpdGUsIGh0dHA6Ly93d3cuYmV0cnVzdGVkLmNvbS9w +cm9kdWN0c19zZXJ2aWNlcy9pbmRleC5odG1sMAsGA1UdDwQEAwIBBjAfBgNVHSME +GDAWgBSp7BR++dlDzFMrFK3P9/BZiUHNGTAdBgNVHQ4EFgQUqewUfvnZQ8xTKxSt +z/fwWYlBzRkwDQYJKoZIhvcNAQEFBQADggEBANuXsHXqDMTBmMpWBcCorSZIry0g +6IHHtt9DwSwddUvUQo3neqh03GZCWYez9Wlt2ames30cMcH1VOJZJEnl7r05pmuK +mET7m9cqg5c0Lcd9NUwtNLg+DcTsiCevnpL9UGGCqGAHFFPMZRPB9kdEadIxyKbd +LrML3kqNWz2rDcI1UqJWN8wyiyiFQpyRQHpwKzg21eFzGh/l+n5f3NacOzDq28Bb +J1zTcwfBwvNMm2+fG8oeqqg4MwlYsq78B+g23FW6L09A/nq9BqaBwZMifIYRCgZ3 +SK41ty8ymmFei74pnykkiFY5LKjSq5YDWtRIn7lAhAuYaPsBQ9Yb4gmxlxw= +-----END CERTIFICATE----- + +RSA Security 2048 v3 +==================== + +MD5 Fingerprint=77:0D:19:B1:21:FD:00:42:9C:3E:0C:A5:DD:0B:02:8E +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 0a:01:01:01:00:00:02:7c:00:00:00:0a:00:00:00:02 + Signature Algorithm: sha1WithRSAEncryption + Issuer: O=RSA Security Inc, OU=RSA Security 2048 V3 + Validity + Not Before: Feb 22 20:39:23 2001 GMT + Not After : Feb 22 20:39:23 2026 GMT + Subject: O=RSA Security Inc, OU=RSA Security 2048 V3 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b7:8f:55:71:d2:80:dd:7b:69:79:a7:f0:18:50: + 32:3c:62:67:f6:0a:95:07:dd:e6:1b:f3:9e:d9:d2: + 41:54:6b:ad:9f:7c:be:19:cd:fb:46:ab:41:68:1e: + 18:ea:55:c8:2f:91:78:89:28:fb:27:29:60:ff:df: + 8f:8c:3b:c9:49:9b:b5:a4:94:ce:01:ea:3e:b5:63: + 7b:7f:26:fd:19:dd:c0:21:bd:84:d1:2d:4f:46:c3: + 4e:dc:d8:37:39:3b:28:af:cb:9d:1a:ea:2b:af:21: + a5:c1:23:22:b8:b8:1b:5a:13:87:57:83:d1:f0:20: + e7:e8:4f:23:42:b0:00:a5:7d:89:e9:e9:61:73:94: + 98:71:26:bc:2d:6a:e0:f7:4d:f0:f1:b6:2a:38:31: + 81:0d:29:e1:00:c1:51:0f:4c:52:f8:04:5a:aa:7d: + 72:d3:b8:87:2a:bb:63:10:03:2a:b3:a1:4f:0d:5a: + 5e:46:b7:3d:0e:f5:74:ec:99:9f:f9:3d:24:81:88: + a6:dd:60:54:e8:95:36:3d:c6:09:93:9a:a3:12:80: + 00:55:99:19:47:bd:d0:a5:7c:c3:ba:fb:1f:f7:f5: + 0f:f8:ac:b9:b5:f4:37:98:13:18:de:85:5b:b7:0c: + 82:3b:87:6f:95:39:58:30:da:6e:01:68:17:22:cc: + c0:0b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:07:C3:51:30:A4:AA:E9:45:AE:35:24:FA:FF:24:2C:33:D0:B1:9D:8C + + X509v3 Subject Key Identifier: + 07:C3:51:30:A4:AA:E9:45:AE:35:24:FA:FF:24:2C:33:D0:B1:9D:8C + Signature Algorithm: sha1WithRSAEncryption + 5f:3e:86:76:6e:b8:35:3c:4e:36:1c:1e:79:98:bf:fd:d5:12: + 11:79:52:0e:ee:31:89:bc:dd:7f:f9:d1:c6:15:21:e8:8a:01: + 54:0d:3a:fb:54:b9:d6:63:d4:b1:aa:96:4d:a2:42:4d:d4:53: + 1f:8b:10:de:7f:65:be:60:13:27:71:88:a4:73:e3:84:63:d1: + a4:55:e1:50:93:e6:1b:0e:79:d0:67:bc:46:c8:bf:3f:17:0d: + 95:e6:c6:90:69:de:e7:b4:2f:de:95:7d:d0:12:3f:3d:3e:7f: + 4d:3f:14:68:f5:11:50:d5:c1:f4:90:a5:08:1d:31:60:ff:60: + 8c:23:54:0a:af:fe:a1:6e:c5:d1:7a:2a:68:78:cf:1e:82:0a: + 20:b4:1f:ad:e5:85:b2:6a:68:75:4e:ad:25:37:94:85:be:bd: + a1:d4:ea:b7:0c:4b:3c:9d:e8:12:00:f0:5f:ac:0d:e1:ac:70: + 63:73:f7:7f:79:9f:32:25:42:74:05:80:28:bf:bd:c1:24:96: + 58:15:b1:17:21:e9:89:4b:db:07:88:67:f4:15:ad:70:3e:2f: + 4d:85:3b:c2:b7:db:fe:98:68:23:89:e1:74:0f:de:f4:c5:84: + 63:29:1b:cc:cb:07:c9:00:a4:a9:d7:c2:22:4f:67:d7:77:ec: + 20:05:61:de +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6 +MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp +dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX +BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy +MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp +eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg +/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl +wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh +AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2 +PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu +AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR +MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc +HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/ +Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+ +f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO +rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch +6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3 +7CAFYd4= +-----END CERTIFICATE----- + +RSA Security 1024 v3 +==================== + +MD5 Fingerprint=3A:E5:50:B0:39:BE:C7:46:36:33:A1:FE:82:3E:8D:94 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 0a:01:01:01:00:00:02:7c:00:00:00:0b:00:00:00:02 + Signature Algorithm: sha1WithRSAEncryption + Issuer: O=RSA Security Inc, OU=RSA Security 1024 V3 + Validity + Not Before: Feb 22 21:01:49 2001 GMT + Not After : Feb 22 20:01:49 2026 GMT + Subject: O=RSA Security Inc, OU=RSA Security 1024 V3 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d5:dd:fe:66:09:cf:24:3c:3e:ae:81:4e:4e:8a: + c4:69:80:5b:59:3b:df:b9:4d:4c:ca:b5:2d:c3:27: + 2d:3c:af:00:42:6d:bc:28:a6:96:cf:7f:d7:58:ac: + 83:0a:a3:55:b5:7b:17:90:15:84:4c:8a:ee:26:99: + dc:58:ef:c7:38:a6:aa:af:d0:8e:42:c8:62:d7:ab: + ac:a9:fb:4a:7d:bf:ea:fe:12:4d:dd:ff:26:2d:6f: + 36:54:68:c8:d2:84:56:ee:92:53:61:09:b3:3f:39: + 9b:a8:c9:9b:bd:ce:9f:7e:d4:19:6a:16:29:18:be: + d7:3a:69:dc:25:5b:33:1a:51 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:C4:C0:1C:A4:07:94:FD:CD:4D:01:D4:54:DA:A5:0C:5F:DE:AE:05:5A + + X509v3 Subject Key Identifier: + C4:C0:1C:A4:07:94:FD:CD:4D:01:D4:54:DA:A5:0C:5F:DE:AE:05:5A + Signature Algorithm: sha1WithRSAEncryption + 3f:2d:6a:e3:26:43:95:7d:89:97:65:fb:75:e4:72:1d:46:57: + c4:61:6b:69:9f:12:9b:2c:d5:5a:e8:c0:a2:f0:43:95:e3:1f: + e9:76:cd:dc:eb:bc:93:a0:65:0a:c7:4d:4f:5f:a7:af:a2:46: + 14:b9:0c:f3:cc:bd:6a:6e:b7:9d:de:25:42:d0:54:ff:9e:68: + 73:63:dc:24:eb:22:bf:a8:72:f2:5e:00:e1:0d:4e:3a:43:6e: + 99:4e:3f:89:78:03:98:ca:f3:55:cc:9d:ae:8e:c1:aa:45:98: + fa:8f:1a:a0:8d:88:23:f1:15:41:0d:a5:46:3e:91:3f:8b:eb: + f7:71 +-----BEGIN CERTIFICATE----- +MIICXDCCAcWgAwIBAgIQCgEBAQAAAnwAAAALAAAAAjANBgkqhkiG9w0BAQUFADA6 +MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp +dHkgMTAyNCBWMzAeFw0wMTAyMjIyMTAxNDlaFw0yNjAyMjIyMDAxNDlaMDoxGTAX +BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAx +MDI0IFYzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDV3f5mCc8kPD6ugU5O +isRpgFtZO9+5TUzKtS3DJy08rwBCbbwoppbPf9dYrIMKo1W1exeQFYRMiu4mmdxY +78c4pqqv0I5CyGLXq6yp+0p9v+r+Ek3d/yYtbzZUaMjShFbuklNhCbM/OZuoyZu9 +zp9+1BlqFikYvtc6adwlWzMaUQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBTEwBykB5T9zU0B1FTapQxf3q4FWjAd +BgNVHQ4EFgQUxMAcpAeU/c1NAdRU2qUMX96uBVowDQYJKoZIhvcNAQEFBQADgYEA +Py1q4yZDlX2Jl2X7deRyHUZXxGFraZ8SmyzVWujAovBDleMf6XbN3Ou8k6BlCsdN +T1+nr6JGFLkM88y9am63nd4lQtBU/55oc2PcJOsiv6hy8l4A4Q1OOkNumU4/iXgD +mMrzVcydro7BqkWY+o8aoI2II/EVQQ2lRj6RP4vr93E= +-----END CERTIFICATE----- + +GeoTrust Global CA +================== + +MD5 Fingerprint=F7:75:AB:29:FB:51:4E:B7:77:5E:FF:05:3C:99:8E:F5 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 144470 (0x23456) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA + Validity + Not Before: May 21 04:00:00 2002 GMT + Not After : May 21 04:00:00 2022 GMT + Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:da:cc:18:63:30:fd:f4:17:23:1a:56:7e:5b:df: + 3c:6c:38:e4:71:b7:78:91:d4:bc:a1:d8:4c:f8:a8: + 43:b6:03:e9:4d:21:07:08:88:da:58:2f:66:39:29: + bd:05:78:8b:9d:38:e8:05:b7:6a:7e:71:a4:e6:c4: + 60:a6:b0:ef:80:e4:89:28:0f:9e:25:d6:ed:83:f3: + ad:a6:91:c7:98:c9:42:18:35:14:9d:ad:98:46:92: + 2e:4f:ca:f1:87:43:c1:16:95:57:2d:50:ef:89:2d: + 80:7a:57:ad:f2:ee:5f:6b:d2:00:8d:b9:14:f8:14: + 15:35:d9:c0:46:a3:7b:72:c8:91:bf:c9:55:2b:cd: + d0:97:3e:9c:26:64:cc:df:ce:83:19:71:ca:4e:e6: + d4:d5:7b:a9:19:cd:55:de:c8:ec:d2:5e:38:53:e5: + 5c:4f:8c:2d:fe:50:23:36:fc:66:e6:cb:8e:a4:39: + 19:00:b7:95:02:39:91:0b:0e:fe:38:2e:d1:1d:05: + 9a:f6:4d:3e:6f:0f:07:1d:af:2c:1e:8f:60:39:e2: + fa:36:53:13:39:d4:5e:26:2b:db:3d:a8:14:bd:32: + eb:18:03:28:52:04:71:e5:ab:33:3d:e1:38:bb:07: + 36:84:62:9c:79:ea:16:30:f4:5f:c0:2b:e8:71:6b: + e4:f9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E + X509v3 Authority Key Identifier: + keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E + + Signature Algorithm: sha1WithRSAEncryption + 35:e3:29:6a:e5:2f:5d:54:8e:29:50:94:9f:99:1a:14:e4:8f: + 78:2a:62:94:a2:27:67:9e:d0:cf:1a:5e:47:e9:c1:b2:a4:cf: + dd:41:1a:05:4e:9b:4b:ee:4a:6f:55:52:b3:24:a1:37:0a:eb: + 64:76:2a:2e:2c:f3:fd:3b:75:90:bf:fa:71:d8:c7:3d:37:d2: + b5:05:95:62:b9:a6:de:89:3d:36:7b:38:77:48:97:ac:a6:20: + 8f:2e:a6:c9:0c:c2:b2:99:45:00:c7:ce:11:51:22:22:e0:a5: + ea:b6:15:48:09:64:ea:5e:4f:74:f7:05:3e:c7:8a:52:0c:db: + 15:b4:bd:6d:9b:e5:c6:b1:54:68:a9:e3:69:90:b6:9a:a5:0f: + b8:b9:3f:20:7d:ae:4a:b5:b8:9c:e4:1d:b6:ab:e6:94:a5:c1: + c7:83:ad:db:f5:27:87:0e:04:6c:d5:ff:dd:a0:5d:ed:87:52: + b7:2b:15:02:ae:39:a6:6a:74:e9:da:c4:e7:bc:4d:34:1e:a9: + 5c:4d:33:5f:92:09:2f:88:66:5d:77:97:c7:1d:76:13:a9:d5: + e5:f1:16:09:11:35:d5:ac:db:24:71:70:2c:98:56:0b:d9:17: + b4:d1:e3:51:2b:5e:75:e8:d5:d0:dc:4f:34:ed:c2:05:66:80: + a1:cb:e6:33 +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +UTN-USER First-Network Applications +=================================== + +MD5 Fingerprint=BF:60:59:A3:5B:BA:F6:A7:76:42:DA:6F:1A:7B:50:CF +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 44:be:0c:8b:50:00:24:b4:11:d3:36:30:4b:c0:33:77 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Network Applications + Validity + Not Before: Jul 9 18:48:39 1999 GMT + Not After : Jul 9 18:57:49 2019 GMT + Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Network Applications + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b3:fb:91:a1:e4:36:55:85:ac:06:34:5b:a0:9a: + 58:b2:f8:b5:0f:05:77:83:ae:32:b1:76:92:68:ec: + 23:4a:c9:76:3f:e3:9c:b6:37:79:03:b9:ab:69:8d: + 07:25:b6:19:67:e4:b0:1b:18:73:61:4a:e8:7e:cd: + d3:2f:64:e3:a6:7c:0c:fa:17:80:a3:0d:47:89:4f: + 51:71:2f:ee:fc:3f:f9:b8:16:80:87:89:93:25:20: + 9a:43:82:69:24:76:28:59:35:a1:1d:c0:7f:83:06: + 64:16:20:2c:d3:49:a4:85:b4:c0:61:7f:51:08:f8: + 68:15:91:80:cb:a5:d5:ee:3b:3a:f4:84:04:5e:60: + 59:a7:8c:34:72:ee:b8:78:c5:d1:3b:12:4a:6f:7e: + 65:27:b9:a4:55:c5:b9:6f:43:a4:c5:1d:2c:99:c0: + 52:a4:78:4c:15:b3:40:98:08:6b:43:c6:01:b0:7a: + 7b:f5:6b:1c:22:3f:cb:ef:ff:a8:d0:3a:4b:76:15: + 9e:d2:d1:c6:2e:e3:db:57:1b:32:a2:b8:6f:e8:86: + a6:3f:70:ab:e5:70:92:ab:44:1e:40:50:fb:9c:a3: + 62:e4:6c:6e:a0:c8:de:e2:80:42:fa:e9:2f:e8:ce: + 32:04:8f:7c:8d:b7:1c:a3:35:3c:15:dd:9e:c3:ae: + 97:a5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: + Digital Signature, Non Repudiation, Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + FA:86:C9:DB:E0:BA:E9:78:F5:4B:A8:D6:15:DF:F0:D3:E1:6A:14:3C + X509v3 CRL Distribution Points: + URI:http://crl.usertrust.com/UTN-USERFirst-NetworkApplications.crl + + Signature Algorithm: sha1WithRSAEncryption + a4:f3:25:cc:d1:d4:91:83:22:d0:cc:32:ab:9b:96:4e:34:91: + 54:20:25:34:61:5f:2a:02:15:e1:8b:aa:ff:7d:64:51:cf:0a: + ff:bc:7d:d8:21:6a:78:cb:2f:51:6f:f8:42:1d:33:bd:eb:b5: + 7b:94:c3:c3:a9:a0:2d:df:d1:29:1f:1d:fe:8f:3f:bb:a8:45: + 2a:7f:d1:6e:55:24:e2:bb:02:fb:31:3f:be:e8:bc:ec:40:2b: + f8:01:d4:56:38:e4:ca:44:82:b5:61:20:21:67:65:f6:f0:0b: + e7:34:f8:a5:c2:9c:a3:5c:40:1f:85:93:95:06:de:4f:d4:27: + a9:b6:a5:fc:16:cd:73:31:3f:b8:65:27:cf:d4:53:1a:f0:ac: + 6e:9f:4f:05:0c:03:81:a7:84:29:c4:5a:bd:64:57:72:ad:3b: + cf:37:18:a6:98:c6:ad:06:b4:dc:08:a3:04:d5:29:a4:96:9a: + 12:67:4a:8c:60:45:9d:f1:23:9a:b0:00:9c:68:b5:98:50:d3: + ef:8e:2e:92:65:b1:48:3e:21:be:15:30:2a:0d:b5:0c:a3:6b: + 3f:ae:7f:57:f5:1f:96:7c:df:6f:dd:82:30:2c:65:1b:40:4a: + cd:68:b9:72:ec:71:76:ec:54:8e:1f:85:0c:01:6a:fa:a6:38: + ac:1f:c4:84 +-----BEGIN CERTIFICATE----- +MIIEZDCCA0ygAwIBAgIQRL4Mi1AAJLQR0zYwS8AzdzANBgkqhkiG9w0BAQUFADCB +ozELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VSRmlyc3Qt +TmV0d29yayBBcHBsaWNhdGlvbnMwHhcNOTkwNzA5MTg0ODM5WhcNMTkwNzA5MTg1 +NzQ5WjCBozELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0 +IExha2UgQ2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYD +VQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VS +Rmlyc3QtTmV0d29yayBBcHBsaWNhdGlvbnMwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCz+5Gh5DZVhawGNFugmliy+LUPBXeDrjKxdpJo7CNKyXY/45y2 +N3kDuatpjQclthln5LAbGHNhSuh+zdMvZOOmfAz6F4CjDUeJT1FxL+78P/m4FoCH +iZMlIJpDgmkkdihZNaEdwH+DBmQWICzTSaSFtMBhf1EI+GgVkYDLpdXuOzr0hARe +YFmnjDRy7rh4xdE7EkpvfmUnuaRVxblvQ6TFHSyZwFKkeEwVs0CYCGtDxgGwenv1 +axwiP8vv/6jQOkt2FZ7S0cYu49tXGzKiuG/ohqY/cKvlcJKrRB5AUPuco2LkbG6g +yN7igEL66S/ozjIEj3yNtxyjNTwV3Z7DrpelAgMBAAGjgZEwgY4wCwYDVR0PBAQD +AgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFPqGydvguul49Uuo1hXf8NPh +ahQ8ME8GA1UdHwRIMEYwRKBCoECGPmh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9V +VE4tVVNFUkZpcnN0LU5ldHdvcmtBcHBsaWNhdGlvbnMuY3JsMA0GCSqGSIb3DQEB +BQUAA4IBAQCk8yXM0dSRgyLQzDKrm5ZONJFUICU0YV8qAhXhi6r/fWRRzwr/vH3Y +IWp4yy9Rb/hCHTO967V7lMPDqaAt39EpHx3+jz+7qEUqf9FuVSTiuwL7MT++6Lzs +QCv4AdRWOOTKRIK1YSAhZ2X28AvnNPilwpyjXEAfhZOVBt5P1CeptqX8Fs1zMT+4 +ZSfP1FMa8Kxun08FDAOBp4QpxFq9ZFdyrTvPNximmMatBrTcCKME1SmklpoSZ0qM +YEWd8SOasACcaLWYUNPvji6SZbFIPiG+FTAqDbUMo2s/rn9X9R+WfN9v3YIwLGUb +QErNaLly7HF27FSOH4UMAWr6pjisH8SE +-----END CERTIFICATE----- + +Thawte Personal Freemail CA +=========================== + +MD5 Fingerprint=1E:74:C3:86:3C:0C:35:C5:3E:C2:7F:EF:3C:AA:3C:D9 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting, OU=Certification Services Division, CN=Thawte Personal Freemail CA/emailAddress=personal-freemail@thawte.com + Validity + Not Before: Jan 1 00:00:00 1996 GMT + Not After : Dec 31 23:59:59 2020 GMT + Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting, OU=Certification Services Division, CN=Thawte Personal Freemail CA/emailAddress=personal-freemail@thawte.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d4:69:d7:d4:b0:94:64:5b:71:e9:47:d8:0c:51: + b6:ea:72:91:b0:84:5e:7d:2d:0d:8f:7b:12:df:85: + 25:75:28:74:3a:42:2c:63:27:9f:95:7b:4b:ef:7e: + 19:87:1d:86:ea:a3:dd:b9:ce:96:64:1a:c2:14:6e: + 44:ac:7c:e6:8f:e8:4d:0f:71:1f:40:38:a6:00:a3: + 87:78:f6:f9:94:86:5e:ad:ea:c0:5e:76:eb:d9:14: + a3:5d:6e:7a:7c:0c:a5:4b:55:7f:06:19:29:7f:9e: + 9a:26:d5:6a:bb:38:24:08:6a:98:c7:b1:da:a3:98: + 91:fd:79:db:e5:5a:c4:1c:b9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: md5WithRSAEncryption + c7:ec:92:7e:4e:f8:f5:96:a5:67:62:2a:a4:f0:4d:11:60:d0: + 6f:8d:60:58:61:ac:26:bb:52:35:5c:08:cf:30:fb:a8:4a:96: + 8a:1f:62:42:23:8c:17:0f:f4:ba:64:9c:17:ac:47:29:df:9d: + 98:5e:d2:6c:60:71:5c:a2:ac:dc:79:e3:e7:6e:00:47:1f:b5: + 0d:28:e8:02:9d:e4:9a:fd:13:f4:a6:d9:7c:b1:f8:dc:5f:23: + 26:09:91:80:73:d0:14:1b:de:43:a9:83:25:f2:e6:9c:2f:15: + ca:fe:a6:ab:8a:07:75:8b:0c:dd:51:84:6b:e4:f8:d1:ce:77: + a2:81 +-----BEGIN CERTIFICATE----- +MIIDLTCCApagAwIBAgIBADANBgkqhkiG9w0BAQQFADCB0TELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYD +VQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBT +ZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBlcnNvbmFsIEZyZWVt +YWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1mcmVlbWFpbEB0aGF3dGUu +Y29tMB4XDTk2MDEwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgdExCzAJBgNVBAYT +AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEa +MBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBG +cmVlbWFpbCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhh +d3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1GnX1LCUZFtx6UfY +DFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyeflXtL734Zhx2G6qPduc6WZBrCFG5E +rHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56fAylS1V/Bhkpf56aJtVq +uzgkCGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zAN +BgkqhkiG9w0BAQQFAAOBgQDH7JJ+Tvj1lqVnYiqk8E0RYNBvjWBYYawmu1I1XAjP +MPuoSpaKH2JCI4wXD/S6ZJwXrEcp352YXtJsYHFcoqzceePnbgBHH7UNKOgCneSa +/RP0ptl8sfjcXyMmCZGAc9AUG95DqYMl8uacLxXK/qarigd1iwzdUYRr5PjRznei +gQ== +-----END CERTIFICATE----- + +America Online Root Certification Authority 1 +============================================= + +MD5 Fingerprint=14:F1:08:AD:9D:FA:64:E2:89:E7:1C:CF:A8:AD:7D:5E +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=America Online Inc., CN=America Online Root Certification Authority 1 + Validity + Not Before: May 28 06:00:00 2002 GMT + Not After : Nov 19 20:43:00 2037 GMT + Subject: C=US, O=America Online Inc., CN=America Online Root Certification Authority 1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:a8:2f:e8:a4:69:06:03:47:c3:e9:2a:98:ff:19: + a2:70:9a:c6:50:b2:7e:a5:df:68:4d:1b:7c:0f:b6: + 97:68:7d:2d:a6:8b:97:e9:64:86:c9:a3:ef:a0:86: + bf:60:65:9c:4b:54:88:c2:48:c5:4a:39:bf:14:e3: + 59:55:e5:19:b4:74:c8:b4:05:39:5c:16:a5:e2:95: + 05:e0:12:ae:59:8b:a2:33:68:58:1c:a6:d4:15:b7: + d8:9f:d7:dc:71:ab:7e:9a:bf:9b:8e:33:0f:22:fd: + 1f:2e:e7:07:36:ef:62:39:c5:dd:cb:ba:25:14:23: + de:0c:c6:3d:3c:ce:82:08:e6:66:3e:da:51:3b:16: + 3a:a3:05:7f:a0:dc:87:d5:9c:fc:72:a9:a0:7d:78: + e4:b7:31:55:1e:65:bb:d4:61:b0:21:60:ed:10:32: + 72:c5:92:25:1e:f8:90:4a:18:78:47:df:7e:30:37: + 3e:50:1b:db:1c:d3:6b:9a:86:53:07:b0:ef:ac:06: + 78:f8:84:99:fe:21:8d:4c:80:b6:0c:82:f6:66:70: + 79:1a:d3:4f:a3:cf:f1:cf:46:b0:4b:0f:3e:dd:88: + 62:b8:8c:a9:09:28:3b:7a:c7:97:e1:1e:e5:f4:9f: + c0:c0:ae:24:a0:c8:a1:d9:0f:d6:7b:26:82:69:32: + 3d:a7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + 00:AD:D9:A3:F6:79:F6:6E:74:A9:7F:33:3D:81:17:D7:4C:CF:33:DE + X509v3 Authority Key Identifier: + keyid:00:AD:D9:A3:F6:79:F6:6E:74:A9:7F:33:3D:81:17:D7:4C:CF:33:DE + + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 7c:8a:d1:1f:18:37:82:e0:b8:b0:a3:ed:56:95:c8:62:61:9c: + 05:a2:cd:c2:62:26:61:cd:10:16:d7:cc:b4:65:34:d0:11:8a: + ad:a8:a9:05:66:ef:74:f3:6d:5f:9d:99:af:f6:8b:fb:eb:52: + b2:05:98:a2:6f:2a:c5:54:bd:25:bd:5f:ae:c8:86:ea:46:2c: + c1:b3:bd:c1:e9:49:70:18:16:97:08:13:8c:20:e0:1b:2e:3a: + 47:cb:1e:e4:00:30:95:5b:f4:45:a3:c0:1a:b0:01:4e:ab:bd: + c0:23:6e:63:3f:80:4a:c5:07:ed:dc:e2:6f:c7:c1:62:f1:e3: + 72:d6:04:c8:74:67:0b:fa:88:ab:a1:01:c8:6f:f0:14:af:d2: + 99:cd:51:93:7e:ed:2e:38:c7:bd:ce:46:50:3d:72:e3:79:25: + 9d:9b:88:2b:10:20:dd:a5:b8:32:9f:8d:e0:29:df:21:74:86: + 82:db:2f:82:30:c6:c7:35:86:b3:f9:96:5f:46:db:0c:45:fd: + f3:50:c3:6f:c6:c3:48:ad:46:a6:e1:27:47:0a:1d:0e:9b:b6: + c2:77:7f:63:f2:e0:7d:1a:be:fc:e0:df:d7:c7:a7:6c:b0:f9: + ae:ba:3c:fd:74:b4:11:e8:58:0d:80:bc:d3:a8:80:3a:99:ed: + 75:cc:46:7b +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP +bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 +MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft +ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk +hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym +1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW +OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb +2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko +O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU +AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF +Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb +LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir +oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C +MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +America Online Root Certification Authority 2 +============================================= + +MD5 Fingerprint=D6:ED:3C:CA:E2:66:0F:AF:10:43:0D:77:9B:04:09:BF +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=America Online Inc., CN=America Online Root Certification Authority 2 + Validity + Not Before: May 28 06:00:00 2002 GMT + Not After : Sep 29 14:08:00 2037 GMT + Subject: C=US, O=America Online Inc., CN=America Online Root Certification Authority 2 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (4096 bit) + Modulus (4096 bit): + 00:cc:41:45:1d:e9:3d:4d:10:f6:8c:b1:41:c9:e0: + 5e:cb:0d:b7:bf:47:73:d3:f0:55:4d:dd:c6:0c:fa: + b1:66:05:6a:cd:78:b4:dc:02:db:4e:81:f3:d7:a7: + 7c:71:bc:75:63:a0:5d:e3:07:0c:48:ec:25:c4:03: + 20:f4:ff:0e:3b:12:ff:9b:8d:e1:c6:d5:1b:b4:6d: + 22:e3:b1:db:7f:21:64:af:86:bc:57:22:2a:d6:47: + 81:57:44:82:56:53:bd:86:14:01:0b:fc:7f:74:a4: + 5a:ae:f1:ba:11:b5:9b:58:5a:80:b4:37:78:09:33: + 7c:32:47:03:5c:c4:a5:83:48:f4:57:56:6e:81:36: + 27:18:4f:ec:9b:28:c2:d4:b4:d7:7c:0c:3e:0c:2b: + df:ca:04:d7:c6:8e:ea:58:4e:a8:a4:a5:18:1c:6c: + 45:98:a3:41:d1:2d:d2:c7:6d:8d:19:f1:ad:79:b7: + 81:3f:bd:06:82:27:2d:10:58:05:b5:78:05:b9:2f: + db:0c:6b:90:90:7e:14:59:38:bb:94:24:13:e5:d1: + 9d:14:df:d3:82:4d:46:f0:80:39:52:32:0f:e3:84: + b2:7a:43:f2:5e:de:5f:3f:1d:dd:e3:b2:1b:a0:a1: + 2a:23:03:6e:2e:01:15:87:5c:a6:75:75:c7:97:61: + be:de:86:dc:d4:48:db:bd:2a:bf:4a:55:da:e8:7d: + 50:fb:b4:80:17:b8:94:bf:01:3d:ea:da:ba:7c:e0: + 58:67:17:b9:58:e0:88:86:46:67:6c:9d:10:47:58: + 32:d0:35:7c:79:2a:90:a2:5a:10:11:23:35:ad:2f: + cc:e4:4a:5b:a7:c8:27:f2:83:de:5e:bb:5e:77:e7: + e8:a5:6e:63:c2:0d:5d:61:d0:8c:d2:6c:5a:21:0e: + ca:28:a3:ce:2a:e9:95:c7:48:cf:96:6f:1d:92:25: + c8:c6:c6:c1:c1:0c:05:ac:26:c4:d2:75:d2:e1:2a: + 67:c0:3d:5b:a5:9a:eb:cf:7b:1a:a8:9d:14:45:e5: + 0f:a0:9a:65:de:2f:28:bd:ce:6f:94:66:83:48:29: + d8:ea:65:8c:af:93:d9:64:9f:55:57:26:bf:6f:cb: + 37:31:99:a3:60:bb:1c:ad:89:34:32:62:b8:43:21: + 06:72:0c:a1:5c:6d:46:c5:fa:29:cf:30:de:89:dc: + 71:5b:dd:b6:37:3e:df:50:f5:b8:07:25:26:e5:bc: + b5:fe:3c:02:b3:b7:f8:be:43:c1:87:11:94:9e:23: + 6c:17:8a:b8:8a:27:0c:54:47:f0:a9:b3:c0:80:8c: + a0:27:eb:1d:19:e3:07:8e:77:70:ca:2b:f4:7d:76: + e0:78:67 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + 4D:45:C1:68:38:BB:73:A9:69:A1:20:E7:ED:F5:22:A1:23:14:D7:9E + X509v3 Authority Key Identifier: + keyid:4D:45:C1:68:38:BB:73:A9:69:A1:20:E7:ED:F5:22:A1:23:14:D7:9E + + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 67:6b:06:b9:5f:45:3b:2a:4b:33:b3:e6:1b:6b:59:4e:22:cc: + b9:b7:a4:25:c9:a7:c4:f0:54:96:0b:64:f3:b1:58:4f:5e:51: + fc:b2:97:7b:27:65:c2:e5:ca:e7:0d:0c:25:7b:62:e3:fa:9f: + b4:87:b7:45:46:af:83:a5:97:48:8c:a5:bd:f1:16:2b:9b:76: + 2c:7a:35:60:6c:11:80:97:cc:a9:92:52:e6:2b:e6:69:ed:a9: + f8:36:2d:2c:77:bf:61:48:d1:63:0b:b9:5b:52:ed:18:b0:43: + 42:22:a6:b1:77:ae:de:69:c5:cd:c7:1c:a1:b1:a5:1c:10:fb: + 18:be:1a:70:dd:c1:92:4b:be:29:5a:9d:3f:35:be:e5:7d:51: + f8:55:e0:25:75:23:87:1e:5c:dc:ba:9d:b0:ac:b3:69:db:17: + 83:c9:f7:de:0c:bc:08:dc:91:9e:a8:d0:d7:15:37:73:a5:35: + b8:fc:7e:c5:44:40:06:c3:eb:f8:22:80:5c:47:ce:02:e3:11: + 9f:44:ff:fd:9a:32:cc:7d:64:51:0e:eb:57:26:76:3a:e3:1e: + 22:3c:c2:a6:36:dd:19:ef:a7:fc:12:f3:26:c0:59:31:85:4c: + 9c:d8:cf:df:a4:cc:cc:29:93:ff:94:6d:76:5c:13:08:97:f2: + ed:a5:0b:4d:dd:e8:c9:68:0e:66:d3:00:0e:33:12:5b:bc:95: + e5:32:90:a8:b3:c6:6c:83:ad:77:ee:8b:7e:7e:b1:a9:ab:d3: + e1:f1:b6:c0:b1:ea:88:c0:e7:d3:90:e9:28:92:94:7b:68:7b: + 97:2a:0a:67:2d:85:02:38:10:e4:03:61:d4:da:25:36:c7:08: + 58:2d:a1:a7:51:af:30:0a:49:f5:a6:69:87:07:2d:44:46:76: + 8e:2a:e5:9a:3b:d7:18:a2:fc:9c:38:10:cc:c6:3b:d2:b5:17: + 3a:6f:fd:ae:25:bd:f5:72:59:64:b1:74:2a:38:5f:18:4c:df: + cf:71:04:5a:36:d4:bf:2f:99:9c:e8:d9:ba:b1:95:e6:02:4b: + 21:a1:5b:d5:c1:4f:8f:ae:69:6d:53:db:01:93:b5:5c:1e:18: + dd:64:5a:ca:18:28:3e:63:04:11:fd:1c:8d:00:0f:b8:37:df: + 67:8a:9d:66:a9:02:6a:91:ff:13:ca:2f:5d:83:bc:87:93:6c: + dc:24:51:16:04:25:66:fa:b3:d9:c2:ba:29:be:9a:48:38:82: + 99:f4:bf:3b:4a:31:19:f9:bf:8e:21:33:14:ca:4f:54:5f:fb: + ce:fb:8f:71:7f:fd:5e:19:a0:0f:4b:91:b8:c4:54:bc:06:b0: + 45:8f:26:91:a2:8e:fe:a9 +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP +bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 +MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft +ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC +206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci +KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 +JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 +BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e +Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B +PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 +Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq +Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ +o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 ++L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj +FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn +xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 +LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc +obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 +CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe +IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA +DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F +AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX +Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb +AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl +Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw +RY8mkaKO/qk= +-----END CERTIFICATE----- + +Visa eCommerce Root +=================== + +MD5 Fingerprint=FC:11:B8:D8:08:93:30:00:6D:23:F9:7E:EB:52:1E:02 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 13:86:35:4d:1d:3f:06:f2:c1:f9:65:05:d5:90:1c:62 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=VISA, OU=Visa International Service Association, CN=Visa eCommerce Root + Validity + Not Before: Jun 26 02:18:36 2002 GMT + Not After : Jun 24 00:16:12 2022 GMT + Subject: C=US, O=VISA, OU=Visa International Service Association, CN=Visa eCommerce Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:af:57:de:56:1e:6e:a1:da:60:b1:94:27:cb:17: + db:07:3f:80:85:4f:c8:9c:b6:d0:f4:6f:4f:cf:99: + d8:e1:db:c2:48:5c:3a:ac:39:33:c7:1f:6a:8b:26: + 3d:2b:35:f5:48:b1:91:c1:02:4e:04:96:91:7b:b0: + 33:f0:b1:14:4e:11:6f:b5:40:af:1b:45:a5:4a:ef: + 7e:b6:ac:f2:a0:1f:58:3f:12:46:60:3c:8d:a1:e0: + 7d:cf:57:3e:33:1e:fb:47:f1:aa:15:97:07:55:66: + a5:b5:2d:2e:d8:80:59:b2:a7:0d:b7:46:ec:21:63: + ff:35:ab:a5:02:cf:2a:f4:4c:fe:7b:f5:94:5d:84: + 4d:a8:f2:60:8f:db:0e:25:3c:9f:73:71:cf:94:df: + 4a:ea:db:df:72:38:8c:f3:96:bd:f1:17:bc:d2:ba: + 3b:45:5a:c6:a7:f6:c6:17:8b:01:9d:fc:19:a8:2a: + 83:16:b8:3a:48:fe:4e:3e:a0:ab:06:19:e9:53:f3: + 80:13:07:ed:2d:bf:3f:0a:3c:55:20:39:2c:2c:00: + 69:74:95:4a:bc:20:b2:a9:79:e5:18:89:91:a8:dc: + 1c:4d:ef:bb:7e:37:0b:5d:fe:39:a5:88:52:8c:00: + 6c:ec:18:7c:41:bd:f6:8b:75:77:ba:60:9d:84:e7: + fe:2d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Subject Key Identifier: + 15:38:83:0F:3F:2C:3F:70:33:1E:CD:46:FE:07:8C:20:E0:D7:C3:B7 + Signature Algorithm: sha1WithRSAEncryption + 5f:f1:41:7d:7c:5c:08:b9:2b:e0:d5:92:47:fa:67:5c:a5:13: + c3:03:21:9b:2b:4c:89:46:cf:59:4d:c9:fe:a5:40:b6:63:cd: + dd:71:28:95:67:11:cc:24:ac:d3:44:6c:71:ae:01:20:6b:03: + a2:8f:18:b7:29:3a:7d:e5:16:60:53:78:3c:c0:af:15:83:f7: + 8f:52:33:24:bd:64:93:97:ee:8b:f7:db:18:a8:6d:71:b3:f7: + 2c:17:d0:74:25:69:f7:fe:6b:3c:94:be:4d:4b:41:8c:4e:e2: + 73:d0:e3:90:22:73:43:cd:f3:ef:ea:73:ce:45:8a:b0:a6:49: + ff:4c:7d:9d:71:88:c4:76:1d:90:5b:1d:ee:fd:cc:f7:ee:fd: + 60:a5:b1:7a:16:71:d1:16:d0:7c:12:3c:6c:69:97:db:ae:5f: + 39:9a:70:2f:05:3c:19:46:04:99:20:36:d0:60:6e:61:06:bb: + 16:42:8c:70:f7:30:fb:e0:db:66:a3:00:01:bd:e6:2c:da:91: + 5f:a0:46:8b:4d:6a:9c:3d:3d:dd:05:46:fe:76:bf:a0:0a:3c: + e4:00:e6:27:b7:ff:84:2d:de:ba:22:27:96:10:71:eb:22:ed: + df:df:33:9c:cf:e3:ad:ae:8e:d4:8e:e6:4f:51:af:16:92:e0: + 5c:f6:07:0f +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr +MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl +cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw +CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h +dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l +cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h +2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E +lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV +ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq +299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t +vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL +dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF +AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR +zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3 +LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd +7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw +++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +TC TrustCenter, Germany, Class 2 CA +=================================== + +MD5 Fingerprint=B8:16:33:4C:4C:4C:F2:D8:D3:4D:06:B4:A6:5B:40:03 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1002 (0x3ea) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=DE, ST=Hamburg, L=Hamburg, O=TC TrustCenter for Security in Data Networks GmbH, OU=TC TrustCenter Class 2 CA/emailAddress=certificate@trustcenter.de + Validity + Not Before: Mar 9 11:59:59 1998 GMT + Not After : Jan 1 11:59:59 2011 GMT + Subject: C=DE, ST=Hamburg, L=Hamburg, O=TC TrustCenter for Security in Data Networks GmbH, OU=TC TrustCenter Class 2 CA/emailAddress=certificate@trustcenter.de + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:da:38:e8:ed:32:00:29:71:83:01:0d:bf:8c:01: + dc:da:c6:ad:39:a4:a9:8a:2f:d5:8b:5c:68:5f:50: + c6:62:f5:66:bd:ca:91:22:ec:aa:1d:51:d7:3d:b3: + 51:b2:83:4e:5d:cb:49:b0:f0:4c:55:e5:6b:2d:c7: + 85:0b:30:1c:92:4e:82:d4:ca:02:ed:f7:6f:be:dc: + e0:e3:14:b8:05:53:f2:9a:f4:56:8b:5a:9e:85:93: + d1:b4:82:56:ae:4d:bb:a8:4b:57:16:bc:fe:f8:58: + 9e:f8:29:8d:b0:7b:cd:78:c9:4f:ac:8b:67:0c:f1: + 9c:fb:fc:57:9b:57:5c:4f:0d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Netscape CA Policy Url: + http://www.trustcenter.de/guidelines + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + Signature Algorithm: md5WithRSAEncryption + 84:52:fb:28:df:ff:1f:75:01:bc:01:be:04:56:97:6a:74:42: + 24:31:83:f9:46:b1:06:8a:89:cf:96:2c:33:bf:8c:b5:5f:7a: + 72:a1:85:06:ce:86:f8:05:8e:e8:f9:25:ca:da:83:8c:06:ac: + eb:36:6d:85:91:34:04:36:f4:42:f0:f8:79:2e:0a:48:5c:ab: + cc:51:4f:78:76:a0:d9:ac:19:bd:2a:d1:69:04:28:91:ca:36: + 10:27:80:57:5b:d2:5c:f5:c2:5b:ab:64:81:63:74:51:f4:97: + bf:cd:12:28:f7:4d:66:7f:a7:f0:1c:01:26:78:b2:66:47:70: + 51:64 +-----BEGIN CERTIFICATE----- +MIIDXDCCAsWgAwIBAgICA+owDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRF +MRAwDgYDVQQIEwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFU +QyBUcnVzdENlbnRlciBmb3IgU2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJI +MSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBDbGFzcyAyIENBMSkwJwYJKoZIhvcN +AQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05ODAzMDkxMTU5NTla +Fw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFtYnVy +ZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9y +IFNlY3VyaXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1 +c3RDZW50ZXIgQ2xhc3MgMiBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVA +dHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANo46O0y +AClxgwENv4wB3NrGrTmkqYov1YtcaF9QxmL1Zr3KkSLsqh1R1z2zUbKDTl3LSbDw +TFXlay3HhQswHJJOgtTKAu33b77c4OMUuAVT8pr0VotanoWT0bSCVq5Nu6hLVxa8 +/vhYnvgpjbB7zXjJT6yLZwzxnPv8V5tXXE8NAgMBAAGjazBpMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3 +LnRydXN0Y2VudGVyLmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0G +CSqGSIb3DQEBBAUAA4GBAIRS+yjf/x91AbwBvgRWl2p0QiQxg/lGsQaKic+WLDO/ +jLVfenKhhQbOhvgFjuj5Jcrag4wGrOs2bYWRNAQ29ELw+HkuCkhcq8xRT3h2oNms +Gb0q0WkEKJHKNhAngFdb0lz1wlurZIFjdFH0l7/NEij3TWZ/p/AcASZ4smZHcFFk +-----END CERTIFICATE----- + +TC TrustCenter, Germany, Class 3 CA +=================================== + +MD5 Fingerprint=5F:94:4A:73:22:B8:F7:D1:31:EC:59:39:F7:8E:FE:6E +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1003 (0x3eb) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=DE, ST=Hamburg, L=Hamburg, O=TC TrustCenter for Security in Data Networks GmbH, OU=TC TrustCenter Class 3 CA/emailAddress=certificate@trustcenter.de + Validity + Not Before: Mar 9 11:59:59 1998 GMT + Not After : Jan 1 11:59:59 2011 GMT + Subject: C=DE, ST=Hamburg, L=Hamburg, O=TC TrustCenter for Security in Data Networks GmbH, OU=TC TrustCenter Class 3 CA/emailAddress=certificate@trustcenter.de + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:b6:b4:c1:35:05:2e:0d:8d:ec:a0:40:6a:1c:0e: + 27:a6:50:92:6b:50:1b:07:de:2e:e7:76:cc:e0:da: + fc:84:a8:5e:8c:63:6a:2b:4d:d9:4e:02:76:11:c1: + 0b:f2:8d:79:ca:00:b6:f1:b0:0e:d7:fb:a4:17:3d: + af:ab:69:7a:96:27:bf:af:33:a1:9a:2a:59:aa:c4: + b5:37:08:f2:12:a5:31:b6:43:f5:32:96:71:28:28: + ab:8d:28:86:df:bb:ee:e3:0c:7d:30:d6:c3:52:ab: + 8f:5d:27:9c:6b:c0:a3:e7:05:6b:57:49:44:b3:6e: + ea:64:cf:d2:8e:7a:50:77:77 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Digital Signature, Certificate Sign, CRL Sign + Netscape CA Policy Url: + http://www.trustcenter.de/guidelines + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + Signature Algorithm: md5WithRSAEncryption + 16:3d:c6:cd:c1:bb:85:71:85:46:9f:3e:20:8f:51:28:99:ec: + 2d:45:21:63:23:5b:04:bb:4c:90:b8:88:92:04:4d:bd:7d:01: + a3:3f:f6:ec:ce:f1:de:fe:7d:e5:e1:3e:bb:c6:ab:5e:0b:dd: + 3d:96:c4:cb:a9:d4:f9:26:e6:06:4e:9e:0c:a5:7a:ba:6e:c3: + 7c:82:19:d1:c7:b1:b1:c3:db:0d:8e:9b:40:7c:37:0b:f1:5d: + e8:fd:1f:90:88:a5:0e:4e:37:64:21:a8:4e:8d:b4:9f:f1:de: + 48:ad:d5:56:18:52:29:8b:47:34:12:09:d4:bb:92:35:ef:0f: + db:34 +-----BEGIN CERTIFICATE----- +MIIDXDCCAsWgAwIBAgICA+swDQYJKoZIhvcNAQEEBQAwgbwxCzAJBgNVBAYTAkRF +MRAwDgYDVQQIEwdIYW1idXJnMRAwDgYDVQQHEwdIYW1idXJnMTowOAYDVQQKEzFU +QyBUcnVzdENlbnRlciBmb3IgU2VjdXJpdHkgaW4gRGF0YSBOZXR3b3JrcyBHbWJI +MSIwIAYDVQQLExlUQyBUcnVzdENlbnRlciBDbGFzcyAzIENBMSkwJwYJKoZIhvcN +AQkBFhpjZXJ0aWZpY2F0ZUB0cnVzdGNlbnRlci5kZTAeFw05ODAzMDkxMTU5NTla +Fw0xMTAxMDExMTU5NTlaMIG8MQswCQYDVQQGEwJERTEQMA4GA1UECBMHSGFtYnVy +ZzEQMA4GA1UEBxMHSGFtYnVyZzE6MDgGA1UEChMxVEMgVHJ1c3RDZW50ZXIgZm9y +IFNlY3VyaXR5IGluIERhdGEgTmV0d29ya3MgR21iSDEiMCAGA1UECxMZVEMgVHJ1 +c3RDZW50ZXIgQ2xhc3MgMyBDQTEpMCcGCSqGSIb3DQEJARYaY2VydGlmaWNhdGVA +dHJ1c3RjZW50ZXIuZGUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALa0wTUF +Lg2N7KBAahwOJ6ZQkmtQGwfeLud2zODa/ISoXoxjaitN2U4CdhHBC/KNecoAtvGw +Dtf7pBc9r6tpepYnv68zoZoqWarEtTcI8hKlMbZD9TKWcSgoq40oht+77uMMfTDW +w1Krj10nnGvAo+cFa1dJRLNu6mTP0o56UHd3AgMBAAGjazBpMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgGGMDMGCWCGSAGG+EIBCAQmFiRodHRwOi8vd3d3 +LnRydXN0Y2VudGVyLmRlL2d1aWRlbGluZXMwEQYJYIZIAYb4QgEBBAQDAgAHMA0G +CSqGSIb3DQEBBAUAA4GBABY9xs3Bu4VxhUafPiCPUSiZ7C1FIWMjWwS7TJC4iJIE +Tb19AaM/9uzO8d7+feXhPrvGq14L3T2WxMup1Pkm5gZOngylerpuw3yCGdHHsbHD +2w2Om0B8NwvxXej9H5CIpQ5ON2QhqE6NtJ/x3kit1VYYUimLRzQSCdS7kjXvD9s0 +-----END CERTIFICATE----- + +Certum Root CA +============== + +MD5 Fingerprint=2C:8F:9F:66:1D:18:90:B1:47:26:9D:8E:86:82:8C:A9 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 65568 (0x10020) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=PL, O=Unizeto Sp. z o.o., CN=Certum CA + Validity + Not Before: Jun 11 10:46:39 2002 GMT + Not After : Jun 11 10:46:39 2027 GMT + Subject: C=PL, O=Unizeto Sp. z o.o., CN=Certum CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ce:b1:c1:2e:d3:4f:7c:cd:25:ce:18:3e:4f:c4: + 8c:6f:80:6a:73:c8:5b:51:f8:9b:d2:dc:bb:00:5c: + b1:a0:fc:75:03:ee:81:f0:88:ee:23:52:e9:e6:15: + 33:8d:ac:2d:09:c5:76:f9:2b:39:80:89:e4:97:4b: + 90:a5:a8:78:f8:73:43:7b:a4:61:b0:d8:58:cc:e1: + 6c:66:7e:9c:f3:09:5e:55:63:84:d5:a8:ef:f3:b1: + 2e:30:68:b3:c4:3c:d8:ac:6e:8d:99:5a:90:4e:34: + dc:36:9a:8f:81:88:50:b7:6d:96:42:09:f3:d7:95: + 83:0d:41:4b:b0:6a:6b:f8:fc:0f:7e:62:9f:67:c4: + ed:26:5f:10:26:0f:08:4f:f0:a4:57:28:ce:8f:b8: + ed:45:f6:6e:ee:25:5d:aa:6e:39:be:e4:93:2f:d9: + 47:a0:72:eb:fa:a6:5b:af:ca:53:3f:e2:0e:c6:96: + 56:11:6e:f7:e9:66:a9:26:d8:7f:95:53:ed:0a:85: + 88:ba:4f:29:a5:42:8c:5e:b6:fc:85:20:00:aa:68: + 0b:a1:1a:85:01:9c:c4:46:63:82:88:b6:22:b1:ee: + fe:aa:46:59:7e:cf:35:2c:d5:b6:da:5d:f7:48:33: + 14:54:b6:eb:d9:6f:ce:cd:88:d6:ab:1b:da:96:3b: + 1d:59 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + b8:8d:ce:ef:e7:14:ba:cf:ee:b0:44:92:6c:b4:39:3e:a2:84: + 6e:ad:b8:21:77:d2:d4:77:82:87:e6:20:41:81:ee:e2:f8:11: + b7:63:d1:17:37:be:19:76:24:1c:04:1a:4c:eb:3d:aa:67:6f: + 2d:d4:cd:fe:65:31:70:c5:1b:a6:02:0a:ba:60:7b:6d:58:c2: + 9a:49:fe:63:32:0b:6b:e3:3a:c0:ac:ab:3b:b0:e8:d3:09:51: + 8c:10:83:c6:34:e0:c5:2b:e0:1a:b6:60:14:27:6c:32:77:8c: + bc:b2:72:98:cf:cd:cc:3f:b9:c8:24:42:14:d6:57:fc:e6:26: + 43:a9:1d:e5:80:90:ce:03:54:28:3e:f7:3f:d3:f8:4d:ed:6a: + 0a:3a:93:13:9b:3b:14:23:13:63:9c:3f:d1:87:27:79:e5:4c: + 51:e3:01:ad:85:5d:1a:3b:b1:d5:73:10:a4:d3:f2:bc:6e:64: + f5:5a:56:90:a8:c7:0e:4c:74:0f:2e:71:3b:f7:c8:47:f4:69: + 6f:15:f2:11:5e:83:1e:9c:7c:52:ae:fd:02:da:12:a8:59:67: + 18:db:bc:70:dd:9b:b1:69:ed:80:ce:89:40:48:6a:0e:35:ca: + 29:66:15:21:94:2c:e8:60:2a:9b:85:4a:40:f3:6b:8a:24:ec: + 06:16:2c:73 +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E +jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo +ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI +ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu +Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg +AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 +HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA +uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa +TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg +xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q +CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x +O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs +6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +Comodo AAA Services root +======================== + +MD5 Fingerprint=49:79:04:B0:EB:87:19:AC:47:B0:BC:11:51:9B:74:D0 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=AAA Certificate Services + Validity + Not Before: Jan 1 00:00:00 2004 GMT + Not After : Dec 31 23:59:59 2028 GMT + Subject: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=AAA Certificate Services + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:be:40:9d:f4:6e:e1:ea:76:87:1c:4d:45:44:8e: + be:46:c8:83:06:9d:c1:2a:fe:18:1f:8e:e4:02:fa: + f3:ab:5d:50:8a:16:31:0b:9a:06:d0:c5:70:22:cd: + 49:2d:54:63:cc:b6:6e:68:46:0b:53:ea:cb:4c:24: + c0:bc:72:4e:ea:f1:15:ae:f4:54:9a:12:0a:c3:7a: + b2:33:60:e2:da:89:55:f3:22:58:f3:de:dc:cf:ef: + 83:86:a2:8c:94:4f:9f:68:f2:98:90:46:84:27:c7: + 76:bf:e3:cc:35:2c:8b:5e:07:64:65:82:c0:48:b0: + a8:91:f9:61:9f:76:20:50:a8:91:c7:66:b5:eb:78: + 62:03:56:f0:8a:1a:13:ea:31:a3:1e:a0:99:fd:38: + f6:f6:27:32:58:6f:07:f5:6b:b8:fb:14:2b:af:b7: + aa:cc:d6:63:5f:73:8c:da:05:99:a8:38:a8:cb:17: + 78:36:51:ac:e9:9e:f4:78:3a:8d:cf:0f:d9:42:e2: + 98:0c:ab:2f:9f:0e:01:de:ef:9f:99:49:f1:2d:df: + ac:74:4d:1b:98:b5:47:c5:e5:29:d1:f9:90:18:c7: + 62:9c:be:83:c7:26:7b:3e:8a:25:c7:c0:dd:9d:e6: + 35:68:10:20:9d:8f:d8:de:d2:c3:84:9c:0d:5e:e8: + 2f:c9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + A0:11:0A:23:3E:96:F1:07:EC:E2:AF:29:EF:82:A5:7F:D0:30:A4:B4 + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 CRL Distribution Points: + URI:http://crl.comodoca.com/AAACertificateServices.crl + URI:http://crl.comodo.net/AAACertificateServices.crl + + Signature Algorithm: sha1WithRSAEncryption + 08:56:fc:02:f0:9b:e8:ff:a4:fa:d6:7b:c6:44:80:ce:4f:c4: + c5:f6:00:58:cc:a6:b6:bc:14:49:68:04:76:e8:e6:ee:5d:ec: + 02:0f:60:d6:8d:50:18:4f:26:4e:01:e3:e6:b0:a5:ee:bf:bc: + 74:54:41:bf:fd:fc:12:b8:c7:4f:5a:f4:89:60:05:7f:60:b7: + 05:4a:f3:f6:f1:c2:bf:c4:b9:74:86:b6:2d:7d:6b:cc:d2:f3: + 46:dd:2f:c6:e0:6a:c3:c3:34:03:2c:7d:96:dd:5a:c2:0e:a7: + 0a:99:c1:05:8b:ab:0c:2f:f3:5c:3a:cf:6c:37:55:09:87:de: + 53:40:6c:58:ef:fc:b6:ab:65:6e:04:f6:1b:dc:3c:e0:5a:15: + c6:9e:d9:f1:59:48:30:21:65:03:6c:ec:e9:21:73:ec:9b:03: + a1:e0:37:ad:a0:15:18:8f:fa:ba:02:ce:a7:2c:a9:10:13:2c: + d4:e5:08:26:ab:22:97:60:f8:90:5e:74:d4:a2:9a:53:bd:f2: + a9:68:e0:a2:6e:c2:d7:6c:b1:a3:0f:9e:bf:eb:68:e7:56:f2: + ae:f2:e3:2b:38:3a:09:81:b5:6b:85:d7:be:2d:ed:3f:1a:b7: + b2:63:e2:f5:62:2c:82:d4:6a:00:41:50:f1:39:83:9f:95:e9: + 36:96:98:6e +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +Comodo Secure Services root +=========================== + +MD5 Fingerprint=D3:D9:BD:AE:9F:AC:67:24:B3:C8:1B:52:E1:B9:A9:BD +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=Secure Certificate Services + Validity + Not Before: Jan 1 00:00:00 2004 GMT + Not After : Dec 31 23:59:59 2028 GMT + Subject: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=Secure Certificate Services + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:c0:71:33:82:8a:d0:70:eb:73:87:82:40:d5:1d: + e4:cb:c9:0e:42:90:f9:de:34:b9:a1:ba:11:f4:25: + 85:f3:cc:72:6d:f2:7b:97:6b:b3:07:f1:77:24:91: + 5f:25:8f:f6:74:3d:e4:80:c2:f8:3c:0d:f3:bf:40: + ea:f7:c8:52:d1:72:6f:ef:c8:ab:41:b8:6e:2e:17: + 2a:95:69:0c:cd:d2:1e:94:7b:2d:94:1d:aa:75:d7: + b3:98:cb:ac:bc:64:53:40:bc:8f:ac:ac:36:cb:5c: + ad:bb:dd:e0:94:17:ec:d1:5c:d0:bf:ef:a5:95:c9: + 90:c5:b0:ac:fb:1b:43:df:7a:08:5d:b7:b8:f2:40: + 1b:2b:27:9e:50:ce:5e:65:82:88:8c:5e:d3:4e:0c: + 7a:ea:08:91:b6:36:aa:2b:42:fb:ea:c2:a3:39:e5: + db:26:38:ad:8b:0a:ee:19:63:c7:1c:24:df:03:78: + da:e6:ea:c1:47:1a:0b:0b:46:09:dd:02:fc:de:cb: + 87:5f:d7:30:63:68:a1:ae:dc:32:a1:ba:be:fe:44: + ab:68:b6:a5:17:15:fd:bd:d5:a7:a7:9a:e4:44:33: + e9:88:8e:fc:ed:51:eb:93:71:4e:ad:01:e7:44:8e: + ab:2d:cb:a8:fe:01:49:48:f0:c0:dd:c7:68:d8:92: + fe:3d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 3C:D8:93:88:C2:C0:82:09:CC:01:99:06:93:20:E9:9E:70:09:63:4F + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 CRL Distribution Points: + URI:http://crl.comodoca.com/SecureCertificateServices.crl + URI:http://crl.comodo.net/SecureCertificateServices.crl + + Signature Algorithm: sha1WithRSAEncryption + 87:01:6d:23:1d:7e:5b:17:7d:c1:61:32:cf:8f:e7:f3:8a:94: + 59:66:e0:9e:28:a8:5e:d3:b7:f4:34:e6:aa:39:b2:97:16:c5: + 82:6f:32:a4:e9:8c:e7:af:fd:ef:c2:e8:b9:4b:aa:a3:f4:e6: + da:8d:65:21:fb:ba:80:eb:26:28:85:1a:fe:39:8c:de:5b:04: + 04:b4:54:f9:a3:67:9e:41:fa:09:52:cc:05:48:a8:c9:3f:21: + 04:1e:ce:48:6b:fc:85:e8:c2:7b:af:7f:b7:cc:f8:5f:3a:fd: + 35:c6:0d:ef:97:dc:4c:ab:11:e1:6b:cb:31:d1:6c:fb:48:80: + ab:dc:9c:37:b8:21:14:4b:0d:71:3d:ec:83:33:6e:d1:6e:32: + 16:ec:98:c7:16:8b:59:a6:34:ab:05:57:2d:93:f7:aa:13:cb: + d2:13:e2:b7:2e:3b:cd:6b:50:17:09:68:3e:b5:26:57:ee:b6: + e0:b6:dd:b9:29:80:79:7d:8f:a3:f0:a4:28:a4:15:c4:85:f4: + 27:d4:6b:bf:e5:5c:e4:65:02:76:54:b4:e3:37:66:24:d3:19: + 61:c8:52:10:e5:8b:37:9a:b9:a9:f9:1d:bf:ea:99:92:61:96: + ff:01:cd:a1:5f:0d:bc:71:bc:0e:ac:0b:1d:47:45:1d:c1:ec: + 7c:ec:fd:29 +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp +ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow +fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV +BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM +cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S +HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 +CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk +3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz +6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV +HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv +Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw +Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww +DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 +5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI +gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ +aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl +izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= +-----END CERTIFICATE----- + +Comodo Trusted Services root +============================ + +MD5 Fingerprint=91:1B:3F:6E:CD:9E:AB:EE:07:FE:1F:71:D2:B3:61:27 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=Trusted Certificate Services + Validity + Not Before: Jan 1 00:00:00 2004 GMT + Not After : Dec 31 23:59:59 2028 GMT + Subject: C=GB, ST=Greater Manchester, L=Salford, O=Comodo CA Limited, CN=Trusted Certificate Services + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:df:71:6f:36:58:53:5a:f2:36:54:57:80:c4:74: + 08:20:ed:18:7f:2a:1d:e6:35:9a:1e:25:ac:9c:e5: + 96:7e:72:52:a0:15:42:db:59:dd:64:7a:1a:d0:b8: + 7b:dd:39:15:bc:55:48:c4:ed:3a:00:ea:31:11:ba: + f2:71:74:1a:67:b8:cf:33:cc:a8:31:af:a3:e3:d7: + 7f:bf:33:2d:4c:6a:3c:ec:8b:c3:92:d2:53:77:24: + 74:9c:07:6e:70:fc:bd:0b:5b:76:ba:5f:f2:ff:d7: + 37:4b:4a:60:78:f7:f0:fa:ca:70:b4:ea:59:aa:a3: + ce:48:2f:a9:c3:b2:0b:7e:17:72:16:0c:a6:07:0c: + 1b:38:cf:c9:62:b7:3f:a0:93:a5:87:41:f2:b7:70: + 40:77:d8:be:14:7c:e3:a8:c0:7a:8e:e9:63:6a:d1: + 0f:9a:c6:d2:f4:8b:3a:14:04:56:d4:ed:b8:cc:6e: + f5:fb:e2:2c:58:bd:7f:4f:6b:2b:f7:60:24:58:24: + ce:26:ef:34:91:3a:d5:e3:81:d0:b2:f0:04:02:d7: + 5b:b7:3e:92:ac:6b:12:8a:f9:e4:05:b0:3b:91:49: + 5c:b2:eb:53:ea:f8:9f:47:86:ee:bf:95:c0:c0:06: + 9f:d2:5b:5e:11:1b:f4:c7:04:35:29:d2:55:5c:e4: + ed:eb + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + C5:7B:58:BD:ED:DA:25:69:D2:F7:59:16:A8:B3:32:C0:7B:27:5B:F4 + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 CRL Distribution Points: + URI:http://crl.comodoca.com/TrustedCertificateServices.crl + URI:http://crl.comodo.net/TrustedCertificateServices.crl + + Signature Algorithm: sha1WithRSAEncryption + c8:93:81:3b:89:b4:af:b8:84:12:4c:8d:d2:f0:db:70:ba:57: + 86:15:34:10:b9:2f:7f:1e:b0:a8:89:60:a1:8a:c2:77:0c:50: + 4a:9b:00:8b:d8:8b:f4:41:e2:d0:83:8a:4a:1c:14:06:b0:a3: + 68:05:70:31:30:a7:53:9b:0e:e9:4a:a0:58:69:67:0e:ae:9d: + f6:a5:2c:41:bf:3c:06:6b:e4:59:cc:6d:10:f1:96:6f:1f:df: + f4:04:02:a4:9f:45:3e:c8:d8:fa:36:46:44:50:3f:82:97:91: + 1f:28:db:18:11:8c:2a:e4:65:83:57:12:12:8c:17:3f:94:36: + fe:5d:b0:c0:04:77:13:b8:f4:15:d5:3f:38:cc:94:3a:55:d0: + ac:98:f5:ba:00:5f:e0:86:19:81:78:2f:28:c0:7e:d3:cc:42: + 0a:f5:ae:50:a0:d1:3e:c6:a1:71:ec:3f:a0:20:8c:66:3a:89: + b4:8e:d4:d8:b1:4d:25:47:ee:2f:88:c8:b5:e1:05:45:c0:be: + 14:71:de:7a:fd:8e:7b:7d:4d:08:96:a5:12:73:f0:2d:ca:37: + 27:74:12:27:4c:cb:b6:97:e9:d9:ae:08:6d:5a:39:40:dd:05: + 47:75:6a:5a:21:b3:a3:18:cf:4e:f7:2e:57:b7:98:70:5e:c8: + c4:78:b0:62 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 +aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla +MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO +BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD +VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW +fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt +TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL +fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW +1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 +kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G +A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v +ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo +dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu +Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ +HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS +jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ +xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn +dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +IPS Chained CAs root +==================== + +MD5 Fingerprint=8D:72:51:DB:A0:3A:CF:20:77:DF:F2:65:06:5E:DF:EF +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA Chained CAs Certification Authority, CN=IPS CA Chained CAs Certification Authority/emailAddress=ips@mail.ips.es + Validity + Not Before: Dec 29 00:53:58 2001 GMT + Not After : Dec 27 00:53:58 2025 GMT + Subject: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA Chained CAs Certification Authority, CN=IPS CA Chained CAs Certification Authority/emailAddress=ips@mail.ips.es + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:dc:56:92:49:b2:94:20:bc:98:4f:50:eb:68:a4: + a7:49:0b:bf:d2:31:e8:c7:4f:c2:86:0b:fa:68:fd: + 43:5a:8a:f3:60:92:35:99:38:bb:4d:03:52:21:5b: + f0:37:99:35:e1:41:20:81:85:81:05:71:81:9d:b4: + 95:19:a9:5f:76:34:2e:63:37:35:57:8e:b4:1f:42: + 3f:15:5c:e1:7a:c1:5f:13:18:32:31:c9:ad:be:a3: + c7:83:66:1e:b9:9c:04:13:cb:69:c1:06:de:30:06: + bb:33:a3:b5:1f:f0:8f:6f:ce:ff:96:e8:54:be:66: + 80:ae:6b:db:41:84:36:a2:3d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + A1:AD:31:B1:F9:3E:E1:17:A6:C8:AB:34:FC:52:87:09:1E:62:52:41 + X509v3 Authority Key Identifier: + keyid:A1:AD:31:B1:F9:3E:E1:17:A6:C8:AB:34:FC:52:87:09:1E:62:52:41 + DirName:/C=ES/ST=Barcelona/L=Barcelona/O=IPS Internet publishing Services s.l./O=ips@mail.ips.es C.I.F. B-60929452/OU=IPS CA Chained CAs Certification Authority/CN=IPS CA Chained CAs Certification Authority/emailAddress=ips@mail.ips.es + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping, Microsoft Individual Code Signing, Microsoft Commercial Code Signing, Microsoft Trust List Signing, Microsoft Encrypted File System + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:ips@mail.ips.es + X509v3 Issuer Alternative Name: + email:ips@mail.ips.es + Netscape Comment: + Chained CA Certificate issued by http://www.ips.es/ + Netscape Base Url: + http://www.ips.es/ips2002/ + Netscape CA Revocation Url: + http://www.ips.es/ips2002/ips2002CAC.crl + Netscape Revocation Url: + http://www.ips.es/ips2002/revocationCAC.html? + Netscape Renewal Url: + http://www.ips.es/ips2002/renewalCAC.html? + Netscape CA Policy Url: + http://www.ips.es/ips2002/policyCAC.html + X509v3 CRL Distribution Points: + URI:http://www.ips.es/ips2002/ips2002CAC.crl + URI:http://wwwback.ips.es/ips2002/ips2002CAC.crl + + Authority Information Access: + OCSP - URI:http://ocsp.ips.es/ + + Signature Algorithm: sha1WithRSAEncryption + 44:72:30:9d:56:58:a2:41:1b:28:b7:95:e1:a6:1a:95:5f:a7: + 78:40:2b:ef:db:96:4a:fc:4c:71:63:d9:73:95:bd:02:e2:a2: + 06:c7:be:97:2a:93:80:34:86:03:fa:dc:d8:3d:1e:07:cd:1e: + 73:43:24:60:f5:1d:61:dc:dc:96:a0:bc:fb:1d:e3:e7:12:00: + 27:33:02:c0:c0:2b:53:3d:d8:6b:03:81:a3:db:d6:93:95:20: + ef:d3:96:7e:26:90:89:9c:26:9b:cd:6f:66:ab:ed:03:22:44: + 38:cc:59:bd:9f:db:f6:07:a2:01:7f:26:c4:63:f5:25:42:5e: + 62:bd +-----BEGIN CERTIFICATE----- +MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARwxCzAJBgNVBAYTAkVT +MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE +ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEzMDEGA1UECxMq +SVBTIENBIENoYWluZWQgQ0FzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMwMQYD +VQQDEypJUFMgQ0EgQ2hhaW5lZCBDQXMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx +HjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczAeFw0wMTEyMjkwMDUzNTha +Fw0yNTEyMjcwMDUzNThaMIIBHDELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNl +bG9uYTESMBAGA1UEBxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQg +cHVibGlzaGluZyBTZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMu +ZXMgQy5JLkYuICBCLTYwOTI5NDUyMTMwMQYDVQQLEypJUFMgQ0EgQ2hhaW5lZCBD +QXMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMzAxBgNVBAMTKklQUyBDQSBDaGFp +bmVkIENBcyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqGSIb3DQEJARYP +aXBzQG1haWwuaXBzLmVzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcVpJJ +spQgvJhPUOtopKdJC7/SMejHT8KGC/po/UNaivNgkjWZOLtNA1IhW/A3mTXhQSCB +hYEFcYGdtJUZqV92NC5jNzVXjrQfQj8VXOF6wV8TGDIxya2+o8eDZh65nAQTy2nB +Bt4wBrszo7Uf8I9vzv+W6FS+ZoCua9tBhDaiPQIDAQABo4IEQzCCBD8wHQYDVR0O +BBYEFKGtMbH5PuEXpsirNPxShwkeYlJBMIIBTgYDVR0jBIIBRTCCAUGAFKGtMbH5 +PuEXpsirNPxShwkeYlJBoYIBJKSCASAwggEcMQswCQYDVQQGEwJFUzESMBAGA1UE +CBMJQmFyY2Vsb25hMRIwEAYDVQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJ +bnRlcm5ldCBwdWJsaXNoaW5nIFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0Bt +YWlsLmlwcy5lcyBDLkkuRi4gIEItNjA5Mjk0NTIxMzAxBgNVBAsTKklQUyBDQSBD +aGFpbmVkIENBcyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAxMqSVBT +IENBIENoYWluZWQgQ0FzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZI +hvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8E +BQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMG +CCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYB +BAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMw +EYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlwcy5lczBC +BglghkgBhvhCAQ0ENRYzQ2hhaW5lZCBDQSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkg +aHR0cDovL3d3dy5pcHMuZXMvMCkGCWCGSAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlw +cy5lcy9pcHMyMDAyLzA3BglghkgBhvhCAQQEKhYoaHR0cDovL3d3dy5pcHMuZXMv +aXBzMjAwMi9pcHMyMDAyQ0FDLmNybDA8BglghkgBhvhCAQMELxYtaHR0cDovL3d3 +dy5pcHMuZXMvaXBzMjAwMi9yZXZvY2F0aW9uQ0FDLmh0bWw/MDkGCWCGSAGG+EIB +BwQsFipodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3JlbmV3YWxDQUMuaHRtbD8w +NwYJYIZIAYb4QgEIBCoWKGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcG9saWN5 +Q0FDLmh0bWwwbQYDVR0fBGYwZDAuoCygKoYoaHR0cDovL3d3dy5pcHMuZXMvaXBz +MjAwMi9pcHMyMDAyQ0FDLmNybDAyoDCgLoYsaHR0cDovL3d3d2JhY2suaXBzLmVz +L2lwczIwMDIvaXBzMjAwMkNBQy5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUF +BzABhhNodHRwOi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAERyMJ1W +WKJBGyi3leGmGpVfp3hAK+/blkr8THFj2XOVvQLiogbHvpcqk4A0hgP63Ng9HgfN +HnNDJGD1HWHc3JagvPsd4+cSACczAsDAK1M92GsDgaPb1pOVIO/Tln4mkImcJpvN +b2ar7QMiRDjMWb2f2/YHogF/JsRj9SVCXmK9 +-----END CERTIFICATE----- + +Thawte Server CA +================ + +MD5 Fingerprint=C5:70:C4:A2:ED:53:78:0C:C8:10:53:81:64:CB:D0:1D +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com + Validity + Not Before: Aug 1 00:00:00 1996 GMT + Not After : Dec 31 23:59:59 2020 GMT + Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d3:a4:50:6e:c8:ff:56:6b:e6:cf:5d:b6:ea:0c: + 68:75:47:a2:aa:c2:da:84:25:fc:a8:f4:47:51:da: + 85:b5:20:74:94:86:1e:0f:75:c9:e9:08:61:f5:06: + 6d:30:6e:15:19:02:e9:52:c0:62:db:4d:99:9e:e2: + 6a:0c:44:38:cd:fe:be:e3:64:09:70:c5:fe:b1:6b: + 29:b6:2f:49:c8:3b:d4:27:04:25:10:97:2f:e7:90: + 6d:c0:28:42:99:d7:4c:43:de:c3:f5:21:6d:54:9f: + 5d:c3:58:e1:c0:e4:d9:5b:b0:b8:dc:b4:7b:df:36: + 3a:c2:b5:66:22:12:d6:87:0d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: md5WithRSAEncryption + 07:fa:4c:69:5c:fb:95:cc:46:ee:85:83:4d:21:30:8e:ca:d9: + a8:6f:49:1a:e6:da:51:e3:60:70:6c:84:61:11:a1:1a:c8:48: + 3e:59:43:7d:4f:95:3d:a1:8b:b7:0b:62:98:7a:75:8a:dd:88: + 4e:4e:9e:40:db:a8:cc:32:74:b9:6f:0d:c6:e3:b3:44:0b:d9: + 8a:6f:9a:29:9b:99:18:28:3b:d1:e3:40:28:9a:5a:3c:d5:b5: + e7:20:1b:8b:ca:a4:ab:8d:e9:51:d9:e2:4c:2c:59:a9:da:b9: + b2:75:1b:f6:42:f2:ef:c7:f2:18:f9:89:bc:a3:ff:8a:23:2e: + 70:47 +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm +MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx +MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 +dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl +cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 +DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 +yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX +L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj +EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG +7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e +QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ +qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +IPS CLASE1 root +=============== + +MD5 Fingerprint=84:90:1D:95:30:49:56:FC:41:81:F0:45:D7:76:C4:6B +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASE1 Certification Authority, CN=IPS CA CLASE1 Certification Authority/emailAddress=ips@mail.ips.es + Validity + Not Before: Dec 29 00:59:38 2001 GMT + Not After : Dec 27 00:59:38 2025 GMT + Subject: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASE1 Certification Authority, CN=IPS CA CLASE1 Certification Authority/emailAddress=ips@mail.ips.es + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:e0:51:27:a7:0b:dd:af:d1:b9:43:5b:82:37:45: + 56:72:ef:9a:b6:c2:12:ef:2c:12:cc:76:f9:06:59: + af:5d:21:d4:d2:5a:b8:a0:d4:f3:6a:fd:ca:69:8d: + 66:48:f7:74:e6:ee:36:bd:e8:96:91:75:a6:71:28: + ca:e7:22:12:32:69:b0:3e:1e:6b:f4:50:52:62:62: + fd:63:3b:7d:7e:ec:ee:38:ea:62:f4:6c:a8:71:8d: + e1:e9:8b:c9:3f:c6:b5:cd:94:42:6f:dd:82:45:3c: + e8:df:09:e8:ef:0a:55:a9:56:47:61:4c:49:64:73: + 10:28:3f:ca:bf:09:ff:c6:2f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + EB:B3:19:79:F3:C1:A5:1C:AC:DC:BA:1F:66:A2:B2:9B:69:D0:78:08 + X509v3 Authority Key Identifier: + keyid:EB:B3:19:79:F3:C1:A5:1C:AC:DC:BA:1F:66:A2:B2:9B:69:D0:78:08 + DirName:/C=ES/ST=Barcelona/L=Barcelona/O=IPS Internet publishing Services s.l./O=ips@mail.ips.es C.I.F. B-60929452/OU=IPS CA CLASE1 Certification Authority/CN=IPS CA CLASE1 Certification Authority/emailAddress=ips@mail.ips.es + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping, Microsoft Individual Code Signing, Microsoft Commercial Code Signing, Microsoft Trust List Signing, Microsoft Encrypted File System + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:ips@mail.ips.es + X509v3 Issuer Alternative Name: + email:ips@mail.ips.es + Netscape Comment: + CLASE1 CA Certificate issued by http://www.ips.es/ + Netscape Base Url: + http://www.ips.es/ips2002/ + Netscape CA Revocation Url: + http://www.ips.es/ips2002/ips2002CLASE1.crl + Netscape Revocation Url: + http://www.ips.es/ips2002/revocationCLASE1.html? + Netscape Renewal Url: + http://www.ips.es/ips2002/renewalCLASE1.html? + Netscape CA Policy Url: + http://www.ips.es/ips2002/policyCLASE1.html + X509v3 CRL Distribution Points: + URI:http://www.ips.es/ips2002/ips2002CLASE1.crl + URI:http://wwwback.ips.es/ips2002/ips2002CLASE1.crl + + Authority Information Access: + OCSP - URI:http://ocsp.ips.es/ + + Signature Algorithm: sha1WithRSAEncryption + 2b:d0:eb:fd:da:c8:ca:59:6a:da:d3:cc:32:2e:c9:54:1b:8a: + 62:7e:15:2d:e9:d9:31:d3:2e:f4:27:23:ff:5b:ab:c5:4a:b6: + 72:40:ae:53:74:f4:bc:05:b4:c6:d9:c8:c9:77:fb:b7:f9:34: + 7f:78:00:f8:d6:a4:e4:52:3f:2c:4a:63:57:81:75:5a:8e:e8: + 8c:fb:02:c0:94:c6:29:ba:b3:dc:1c:e8:b2:af:d2:2e:62:5b: + 1a:a9:8e:0e:cc:c5:57:45:51:14:e9:4e:1c:88:a5:91:f4:a3: + f7:8e:51:c8:a9:be:86:33:3e:e6:2f:48:6e:af:54:90:4e:ad: + b1:25 +-----BEGIN CERTIFICATE----- +MIIH6jCCB1OgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARIxCzAJBgNVBAYTAkVT +MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE +ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEuMCwGA1UECxMl +SVBTIENBIENMQVNFMSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMl +SVBTIENBIENMQVNFMSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqGSIb3 +DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAwNTkzOFoXDTI1MTIyNzAw +NTkzOFowggESMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFyY2Vsb25hMRIwEAYD +VQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBwdWJsaXNoaW5n +IFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0BtYWlsLmlwcy5lcyBDLkkuRi4g +IEItNjA5Mjk0NTIxLjAsBgNVBAsTJUlQUyBDQSBDTEFTRTEgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxLjAsBgNVBAMTJUlQUyBDQSBDTEFTRTEgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxHjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4FEnpwvdr9G5Q1uCN0VWcu+atsIS7ywS +zHb5BlmvXSHU0lq4oNTzav3KaY1mSPd05u42veiWkXWmcSjK5yISMmmwPh5r9FBS +YmL9Yzt9fuzuOOpi9GyocY3h6YvJP8a1zZRCb92CRTzo3wno7wpVqVZHYUxJZHMQ +KD/Kvwn/xi8CAwEAAaOCBEowggRGMB0GA1UdDgQWBBTrsxl588GlHKzcuh9morKb +adB4CDCCAUQGA1UdIwSCATswggE3gBTrsxl588GlHKzcuh9morKbadB4CKGCARqk +ggEWMIIBEjELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNlbG9uYTESMBAGA1UE +BxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQgcHVibGlzaGluZyBT +ZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5JLkYuICBC +LTYwOTI5NDUyMS4wLAYDVQQLEyVJUFMgQ0EgQ0xBU0UxIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MS4wLAYDVQQDEyVJUFMgQ0EgQ0xBU0UxIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYD +VR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggr +BgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIB +FQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhC +AQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGB +D2lwc0BtYWlsLmlwcy5lczBBBglghkgBhvhCAQ0ENBYyQ0xBU0UxIENBIENlcnRp +ZmljYXRlIGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5lcy8wKQYJYIZIAYb4QgEC +BBwWGmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMDoGCWCGSAGG+EIBBAQtFito +dHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTEuY3JsMD8GCWCG +SAGG+EIBAwQyFjBodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25D +TEFTRTEuaHRtbD8wPAYJYIZIAYb4QgEHBC8WLWh0dHA6Ly93d3cuaXBzLmVzL2lw +czIwMDIvcmVuZXdhbENMQVNFMS5odG1sPzA6BglghkgBhvhCAQgELRYraHR0cDov +L3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lDTEFTRTEuaHRtbDBzBgNVHR8EbDBq +MDGgL6AthitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTEu +Y3JsMDWgM6Axhi9odHRwOi8vd3d3YmFjay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAy +Q0xBU0UxLmNybDAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9v +Y3NwLmlwcy5lcy8wDQYJKoZIhvcNAQEFBQADgYEAK9Dr/drIyllq2tPMMi7JVBuK +Yn4VLenZMdMu9Ccj/1urxUq2ckCuU3T0vAW0xtnIyXf7t/k0f3gA+Nak5FI/LEpj +V4F1Wo7ojPsCwJTGKbqz3Bzosq/SLmJbGqmODszFV0VRFOlOHIilkfSj945RyKm+ +hjM+5i9Ibq9UkE6tsSU= +-----END CERTIFICATE----- + +IPS CLASE3 root +=============== + +MD5 Fingerprint=42:76:97:68:CF:A6:B4:38:24:AA:A1:1B:F2:67:DE:CA +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASE3 Certification Authority, CN=IPS CA CLASE3 Certification Authority/emailAddress=ips@mail.ips.es + Validity + Not Before: Dec 29 01:01:44 2001 GMT + Not After : Dec 27 01:01:44 2025 GMT + Subject: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASE3 Certification Authority, CN=IPS CA CLASE3 Certification Authority/emailAddress=ips@mail.ips.es + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:ab:17:fe:0e:b0:c6:68:1b:53:f0:52:be:9f:fa: + da:fa:8b:13:04:bb:01:8f:32:d9:1f:8f:4d:ce:36: + 98:da:e4:00:44:8c:28:d8:13:44:2a:a4:6b:4e:17: + 24:42:9c:d3:88:a4:41:82:d6:23:fb:8b:c9:86:e5: + b9:a9:82:05:dc:f1:de:1f:e0:0c:99:55:98:f2:38: + ec:6c:9d:20:03:c0:ef:aa:a3:c6:64:04:51:2d:78: + 0d:a3:d2:a8:3a:d6:24:4c:e9:96:7a:18:ac:13:23: + 22:1b:7c:e8:31:11:b3:5f:09:aa:30:70:71:46:25: + 6b:49:71:80:2b:95:01:b2:1f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B8:93:FF:2E:CB:DC:2C:8E:A2:E7:7A:FE:36:51:21:A3:98:5B:0C:34 + X509v3 Authority Key Identifier: + keyid:B8:93:FF:2E:CB:DC:2C:8E:A2:E7:7A:FE:36:51:21:A3:98:5B:0C:34 + DirName:/C=ES/ST=Barcelona/L=Barcelona/O=IPS Internet publishing Services s.l./O=ips@mail.ips.es C.I.F. B-60929452/OU=IPS CA CLASE3 Certification Authority/CN=IPS CA CLASE3 Certification Authority/emailAddress=ips@mail.ips.es + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping, Microsoft Individual Code Signing, Microsoft Commercial Code Signing, Microsoft Trust List Signing, Microsoft Encrypted File System + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:ips@mail.ips.es + X509v3 Issuer Alternative Name: + email:ips@mail.ips.es + Netscape Comment: + CLASE3 CA Certificate issued by http://www.ips.es/ + Netscape Base Url: + http://www.ips.es/ips2002/ + Netscape CA Revocation Url: + http://www.ips.es/ips2002/ips2002CLASE3.crl + Netscape Revocation Url: + http://www.ips.es/ips2002/revocationCLASE3.html? + Netscape Renewal Url: + http://www.ips.es/ips2002/renewalCLASE3.html? + Netscape CA Policy Url: + http://www.ips.es/ips2002/policyCLASE3.html + X509v3 CRL Distribution Points: + URI:http://www.ips.es/ips2002/ips2002CLASE3.crl + URI:http://wwwback.ips.es/ips2002/ips2002CLASE3.crl + + Authority Information Access: + OCSP - URI:http://ocsp.ips.es/ + + Signature Algorithm: sha1WithRSAEncryption + 17:65:5c:99:95:43:03:27:af:26:e5:eb:d0:b3:17:23:f7:43: + aa:c7:f0:7d:ec:0f:c6:a9:ae:ae:96:0f:76:29:1c:e2:06:2d: + 7e:26:c5:3c:fa:a1:c1:81:ce:53:b0:42:d1:97:57:1a:17:7e: + a4:51:61:c6:ee:e9:5e:ef:05:ba:eb:bd:0f:a7:92:6f:d8:a3: + 06:68:29:8e:79:f5:ff:bf:f9:a7:af:e4:b1:ce:c2:d1:80:42: + 27:05:04:34:f8:c3:7f:16:78:23:0c:07:24:f2:46:47:ad:3b: + 54:d0:af:d5:31:b2:af:7d:c8:ea:e9:d4:56:d9:0e:13:b2:c5: + 45:50 +-----BEGIN CERTIFICATE----- +MIIH6jCCB1OgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARIxCzAJBgNVBAYTAkVT +MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE +ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEuMCwGA1UECxMl +SVBTIENBIENMQVNFMyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMl +SVBTIENBIENMQVNFMyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEeMBwGCSqGSIb3 +DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAxMDE0NFoXDTI1MTIyNzAx +MDE0NFowggESMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFyY2Vsb25hMRIwEAYD +VQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5ldCBwdWJsaXNoaW5n +IFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0BtYWlsLmlwcy5lcyBDLkkuRi4g +IEItNjA5Mjk0NTIxLjAsBgNVBAsTJUlQUyBDQSBDTEFTRTMgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxLjAsBgNVBAMTJUlQUyBDQSBDTEFTRTMgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkxHjAcBgkqhkiG9w0BCQEWD2lwc0BtYWlsLmlwcy5lczCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqxf+DrDGaBtT8FK+n/ra+osTBLsBjzLZ +H49NzjaY2uQARIwo2BNEKqRrThckQpzTiKRBgtYj+4vJhuW5qYIF3PHeH+AMmVWY +8jjsbJ0gA8DvqqPGZARRLXgNo9KoOtYkTOmWehisEyMiG3zoMRGzXwmqMHBxRiVr +SXGAK5UBsh8CAwEAAaOCBEowggRGMB0GA1UdDgQWBBS4k/8uy9wsjqLnev42USGj +mFsMNDCCAUQGA1UdIwSCATswggE3gBS4k/8uy9wsjqLnev42USGjmFsMNKGCARqk +ggEWMIIBEjELMAkGA1UEBhMCRVMxEjAQBgNVBAgTCUJhcmNlbG9uYTESMBAGA1UE +BxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJUFMgSW50ZXJuZXQgcHVibGlzaGluZyBT +ZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJpcHNAbWFpbC5pcHMuZXMgQy5JLkYuICBC +LTYwOTI5NDUyMS4wLAYDVQQLEyVJUFMgQ0EgQ0xBU0UzIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MS4wLAYDVQQDEyVJUFMgQ0EgQ0xBU0UzIENlcnRpZmljYXRpb24g +QXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYD +VR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggr +BgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIB +FQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhC +AQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGB +D2lwc0BtYWlsLmlwcy5lczBBBglghkgBhvhCAQ0ENBYyQ0xBU0UzIENBIENlcnRp +ZmljYXRlIGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5lcy8wKQYJYIZIAYb4QgEC +BBwWGmh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMDoGCWCGSAGG+EIBBAQtFito +dHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTMuY3JsMD8GCWCG +SAGG+EIBAwQyFjBodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25D +TEFTRTMuaHRtbD8wPAYJYIZIAYb4QgEHBC8WLWh0dHA6Ly93d3cuaXBzLmVzL2lw +czIwMDIvcmVuZXdhbENMQVNFMy5odG1sPzA6BglghkgBhvhCAQgELRYraHR0cDov +L3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lDTEFTRTMuaHRtbDBzBgNVHR8EbDBq +MDGgL6AthitodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJDTEFTRTMu +Y3JsMDWgM6Axhi9odHRwOi8vd3d3YmFjay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAy +Q0xBU0UzLmNybDAvBggrBgEFBQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9v +Y3NwLmlwcy5lcy8wDQYJKoZIhvcNAQEFBQADgYEAF2VcmZVDAyevJuXr0LMXI/dD +qsfwfewPxqmurpYPdikc4gYtfibFPPqhwYHOU7BC0ZdXGhd+pFFhxu7pXu8Fuuu9 +D6eSb9ijBmgpjnn1/7/5p6/ksc7C0YBCJwUENPjDfxZ4IwwHJPJGR607VNCv1TGy +r33I6unUVtkOE7LFRVA= +-----END CERTIFICATE----- + +IPS CLASEA1 root +================ + +MD5 Fingerprint=0C:F8:9E:17:FC:D4:03:BD:E6:8D:9B:3C:05:87:FE:84 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASEA1 Certification Authority, CN=IPS CA CLASEA1 Certification Authority/emailAddress=ips@mail.ips.es + Validity + Not Before: Dec 29 01:05:32 2001 GMT + Not After : Dec 27 01:05:32 2025 GMT + Subject: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASEA1 Certification Authority, CN=IPS CA CLASEA1 Certification Authority/emailAddress=ips@mail.ips.es + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:bb:30:d7:dc:d0:54:bd:35:4e:9f:c5:4c:82:ea: + d1:50:3c:47:98:fc:9b:69:9d:77:cd:6e:e0:3f:ee: + eb:32:5f:5f:9f:d2:d0:79:e5:95:73:44:21:32:e0: + 0a:db:9d:d7:ce:8d:ab:52:8b:2b:78:e0:9b:5b:7d: + f4:fd:6d:09:e5:ae:e1:6c:1d:07:23:a0:17:d1:f9: + 7d:a8:46:46:91:22:a8:b2:69:c6:ad:f7:f5:f5:94: + a1:30:94:bd:00:cc:44:7f:ee:c4:9e:c9:c1:e6:8f: + 0a:36:c1:fd:24:3d:01:a0:f5:7b:e2:7c:78:66:43: + 8b:4f:59:f2:9b:d9:fa:49:b3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 67:26:96:E7:A1:BF:D8:B5:03:9D:FE:3B:DC:FE:F2:8A:E6:15:DD:30 + X509v3 Authority Key Identifier: + keyid:67:26:96:E7:A1:BF:D8:B5:03:9D:FE:3B:DC:FE:F2:8A:E6:15:DD:30 + DirName:/C=ES/ST=Barcelona/L=Barcelona/O=IPS Internet publishing Services s.l./O=ips@mail.ips.es C.I.F. B-60929452/OU=IPS CA CLASEA1 Certification Authority/CN=IPS CA CLASEA1 Certification Authority/emailAddress=ips@mail.ips.es + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping, Microsoft Individual Code Signing, Microsoft Commercial Code Signing, Microsoft Trust List Signing, Microsoft Encrypted File System + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:ips@mail.ips.es + X509v3 Issuer Alternative Name: + email:ips@mail.ips.es + Netscape Comment: + CLASEA1 CA Certificate issued by http://www.ips.es/ + Netscape Base Url: + http://www.ips.es/ips2002/ + Netscape CA Revocation Url: + http://www.ips.es/ips2002/ips2002CLASEA1.crl + Netscape Revocation Url: + http://www.ips.es/ips2002/revocationCLASEA1.html? + Netscape Renewal Url: + http://www.ips.es/ips2002/renewalCLASEA1.html? + Netscape CA Policy Url: + http://www.ips.es/ips2002/policyCLASEA1.html + X509v3 CRL Distribution Points: + URI:http://www.ips.es/ips2002/ips2002CLASEA1.crl + URI:http://wwwback.ips.es/ips2002/ips2002CLASEA1.crl + + Authority Information Access: + OCSP - URI:http://ocsp.ips.es/ + + Signature Algorithm: sha1WithRSAEncryption + 7e:ba:8a:ac:80:00:84:15:0a:d5:98:51:0c:64:c5:9c:02:58: + 83:66:ca:ad:1e:07:cd:7e:6a:da:80:07:df:03:34:4a:1c:93: + c4:4b:58:20:35:36:71:ed:a2:0a:35:12:a5:a6:65:a7:85:69: + 0a:0e:e3:61:ee:ea:be:28:93:33:d5:ec:e8:be:c4:db:5f:7f: + a8:f9:63:31:c8:6b:96:e2:29:c2:5b:a0:e7:97:36:9d:77:5e: + 31:6b:fe:d3:a7:db:2a:db:db:96:8b:1f:66:de:b6:03:c0:2b: + b3:78:d6:55:07:e5:8f:39:50:de:07:23:72:e6:bd:20:14:4b: + b4:86 +-----BEGIN CERTIFICATE----- +MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARQxCzAJBgNVBAYTAkVT +MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE +ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0GA1UECxMm +SVBTIENBIENMQVNFQTEgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMT +JklQUyBDQSBDTEFTRUExIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZI +hvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwHhcNMDExMjI5MDEwNTMyWhcNMjUxMjI3 +MDEwNTMyWjCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ +BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp +bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G +LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTEgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUExIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMw +gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALsw19zQVL01Tp/FTILq0VA8R5j8 +m2mdd81u4D/u6zJfX5/S0HnllXNEITLgCtud186Nq1KLK3jgm1t99P1tCeWu4Wwd +ByOgF9H5fahGRpEiqLJpxq339fWUoTCUvQDMRH/uxJ7JweaPCjbB/SQ9AaD1e+J8 +eGZDi09Z8pvZ+kmzAgMBAAGjggRTMIIETzAdBgNVHQ4EFgQUZyaW56G/2LUDnf47 +3P7yiuYV3TAwggFGBgNVHSMEggE9MIIBOYAUZyaW56G/2LUDnf473P7yiuYV3TCh +ggEcpIIBGDCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ +BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp +bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G +LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTEgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUExIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOC +AQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUF +BwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYB +BAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglg +hkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1Ud +EgQTMBGBD2lwc0BtYWlsLmlwcy5lczBCBglghkgBhvhCAQ0ENRYzQ0xBU0VBMSBD +QSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkgaHR0cDovL3d3dy5pcHMuZXMvMCkGCWCG +SAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyLzA7BglghkgBhvhC +AQQELhYsaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyQ0xBU0VBMS5j +cmwwQAYJYIZIAYb4QgEDBDMWMWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmV2 +b2NhdGlvbkNMQVNFQTEuaHRtbD8wPQYJYIZIAYb4QgEHBDAWLmh0dHA6Ly93d3cu +aXBzLmVzL2lwczIwMDIvcmVuZXdhbENMQVNFQTEuaHRtbD8wOwYJYIZIAYb4QgEI +BC4WLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcG9saWN5Q0xBU0VBMS5odG1s +MHUGA1UdHwRuMGwwMqAwoC6GLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvaXBz +MjAwMkNMQVNFQTEuY3JsMDagNKAyhjBodHRwOi8vd3d3YmFjay5pcHMuZXMvaXBz +MjAwMi9pcHMyMDAyQ0xBU0VBMS5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUF +BzABhhNodHRwOi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAH66iqyA +AIQVCtWYUQxkxZwCWINmyq0eB81+atqAB98DNEock8RLWCA1NnHtogo1EqWmZaeF +aQoO42Hu6r4okzPV7Oi+xNtff6j5YzHIa5biKcJboOeXNp13XjFr/tOn2yrb25aL +H2betgPAK7N41lUH5Y85UN4HI3LmvSAUS7SG +-----END CERTIFICATE----- + +IPS CLASEA3 root +================ + +MD5 Fingerprint=06:F9:EB:EC:CC:56:9D:88:BA:90:F5:BA:B0:1A:E0:02 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASEA3 Certification Authority, CN=IPS CA CLASEA3 Certification Authority/emailAddress=ips@mail.ips.es + Validity + Not Before: Dec 29 01:07:50 2001 GMT + Not After : Dec 27 01:07:50 2025 GMT + Subject: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA CLASEA3 Certification Authority, CN=IPS CA CLASEA3 Certification Authority/emailAddress=ips@mail.ips.es + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:ee:80:00:f6:1a:64:2e:ad:6a:c8:83:b1:8b:a7: + ee:8f:d9:b6:db:cd:1b:bb:86:06:22:76:33:0c:12: + 6d:48:56:61:d2:dc:82:25:62:2f:9f:d2:69:30:65: + 03:42:23:58:bc:47:dc:6b:d6:75:5d:17:3c:e1:ff: + f2:58:67:79:a0:c1:81:b1:d4:56:a2:f2:8d:11:99: + fd:f6:7d:f1:c7:c4:5e:02:2a:9a:e2:4a:b5:13:8a: + 00:fd:8c:77:86:e6:d7:94:f5:20:75:2e:0e:4c:bf: + 74:c4:3f:81:3e:83:b4:a3:38:36:29:e7:e8:2a:f5: + 8c:88:41:aa:80:a6:e3:6c:ef + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 1E:9F:57:50:47:B6:61:93:39:D3:2C:FC:DA:5D:3D:05:75:B7:99:02 + X509v3 Authority Key Identifier: + keyid:1E:9F:57:50:47:B6:61:93:39:D3:2C:FC:DA:5D:3D:05:75:B7:99:02 + DirName:/C=ES/ST=Barcelona/L=Barcelona/O=IPS Internet publishing Services s.l./O=ips@mail.ips.es C.I.F. B-60929452/OU=IPS CA CLASEA3 Certification Authority/CN=IPS CA CLASEA3 Certification Authority/emailAddress=ips@mail.ips.es + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping, Microsoft Individual Code Signing, Microsoft Commercial Code Signing, Microsoft Trust List Signing, Microsoft Encrypted File System + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:ips@mail.ips.es + X509v3 Issuer Alternative Name: + email:ips@mail.ips.es + Netscape Comment: + CLASEA3 CA Certificate issued by http://www.ips.es/ + Netscape Base Url: + http://www.ips.es/ips2002/ + Netscape CA Revocation Url: + http://www.ips.es/ips2002/ips2002CLASEA3.crl + Netscape Revocation Url: + http://www.ips.es/ips2002/revocationCLASEA3.html? + Netscape Renewal Url: + http://www.ips.es/ips2002/renewalCLASEA3.html? + Netscape CA Policy Url: + http://www.ips.es/ips2002/policyCLASEA3.html + X509v3 CRL Distribution Points: + URI:http://www.ips.es/ips2002/ips2002CLASEA3.crl + URI:http://wwwback.ips.es/ips2002/ips2002CLASEA3.crl + + Authority Information Access: + OCSP - URI:http://ocsp.ips.es/ + + Signature Algorithm: sha1WithRSAEncryption + 4a:3d:20:47:1a:da:89:f4:7a:2b:31:79:ec:01:c0:cc:01:f5: + d6:c1:fc:c8:c3:f3:50:02:51:90:58:2a:9f:e7:35:09:5b:30: + 0a:81:00:25:47:af:d4:0f:0e:9e:60:26:a8:95:a7:83:08:df: + 2d:ac:e9:0e:f7:9c:c8:9f:cb:93:45:f1:ba:6a:c6:67:51:4a: + 69:4f:6b:fe:7d:0b:2f:52:29:c2:50:ad:24:44:ed:23:b3:48: + cb:44:40:c1:03:95:0c:0a:78:06:12:01:f5:91:31:2d:49:8d: + bb:3f:45:4e:2c:e0:e8:cd:b5:c9:14:15:0c:e3:07:83:9b:26: + 75:ef +-----BEGIN CERTIFICATE----- +MIIH9zCCB2CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCARQxCzAJBgNVBAYTAkVT +MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE +ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjEvMC0GA1UECxMm +SVBTIENBIENMQVNFQTMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxLzAtBgNVBAMT +JklQUyBDQSBDTEFTRUEzIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4wHAYJKoZI +hvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMwHhcNMDExMjI5MDEwNzUwWhcNMjUxMjI3 +MDEwNzUwWjCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ +BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp +bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G +LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTMgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUEzIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXMw +gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAO6AAPYaZC6tasiDsYun7o/ZttvN +G7uGBiJ2MwwSbUhWYdLcgiViL5/SaTBlA0IjWLxH3GvWdV0XPOH/8lhneaDBgbHU +VqLyjRGZ/fZ98cfEXgIqmuJKtROKAP2Md4bm15T1IHUuDky/dMQ/gT6DtKM4Ninn +6Cr1jIhBqoCm42zvAgMBAAGjggRTMIIETzAdBgNVHQ4EFgQUHp9XUEe2YZM50yz8 +2l09BXW3mQIwggFGBgNVHSMEggE9MIIBOYAUHp9XUEe2YZM50yz82l09BXW3mQKh +ggEcpIIBGDCCARQxCzAJBgNVBAYTAkVTMRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQ +BgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UEChMlSVBTIEludGVybmV0IHB1Ymxpc2hp +bmcgU2VydmljZXMgcy5sLjErMCkGA1UEChQiaXBzQG1haWwuaXBzLmVzIEMuSS5G +LiAgQi02MDkyOTQ1MjEvMC0GA1UECxMmSVBTIENBIENMQVNFQTMgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkxLzAtBgNVBAMTJklQUyBDQSBDTEFTRUEzIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOC +AQAwDAYDVR0TBAUwAwEB/zAMBgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUF +BwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYB +BAGCNwIBFQYKKwYBBAGCNwIBFgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglg +hkgBhvhCAQEEBAMCAAcwGgYDVR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1Ud +EgQTMBGBD2lwc0BtYWlsLmlwcy5lczBCBglghkgBhvhCAQ0ENRYzQ0xBU0VBMyBD +QSBDZXJ0aWZpY2F0ZSBpc3N1ZWQgYnkgaHR0cDovL3d3dy5pcHMuZXMvMCkGCWCG +SAGG+EIBAgQcFhpodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyLzA7BglghkgBhvhC +AQQELhYsaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyQ0xBU0VBMy5j +cmwwQAYJYIZIAYb4QgEDBDMWMWh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcmV2 +b2NhdGlvbkNMQVNFQTMuaHRtbD8wPQYJYIZIAYb4QgEHBDAWLmh0dHA6Ly93d3cu +aXBzLmVzL2lwczIwMDIvcmVuZXdhbENMQVNFQTMuaHRtbD8wOwYJYIZIAYb4QgEI +BC4WLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvcG9saWN5Q0xBU0VBMy5odG1s +MHUGA1UdHwRuMGwwMqAwoC6GLGh0dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvaXBz +MjAwMkNMQVNFQTMuY3JsMDagNKAyhjBodHRwOi8vd3d3YmFjay5pcHMuZXMvaXBz +MjAwMi9pcHMyMDAyQ0xBU0VBMy5jcmwwLwYIKwYBBQUHAQEEIzAhMB8GCCsGAQUF +BzABhhNodHRwOi8vb2NzcC5pcHMuZXMvMA0GCSqGSIb3DQEBBQUAA4GBAEo9IEca +2on0eisxeewBwMwB9dbB/MjD81ACUZBYKp/nNQlbMAqBACVHr9QPDp5gJqiVp4MI +3y2s6Q73nMify5NF8bpqxmdRSmlPa/59Cy9SKcJQrSRE7SOzSMtEQMEDlQwKeAYS +AfWRMS1Jjbs/RU4s4OjNtckUFQzjB4ObJnXv +-----END CERTIFICATE----- + +IPS Servidores root +=================== + +MD5 Fingerprint=7B:B5:08:99:9A:8C:18:BF:85:27:7D:0E:AE:DA:B2:AB +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 0 (0x0) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=ES, ST=BARCELONA, L=BARCELONA, O=IPS Seguridad CA, OU=Certificaciones, CN=IPS SERVIDORES/emailAddress=ips@mail.ips.es + Validity + Not Before: Jan 1 23:21:07 1998 GMT + Not After : Dec 29 23:21:07 2009 GMT + Subject: C=ES, ST=BARCELONA, L=BARCELONA, O=IPS Seguridad CA, OU=Certificaciones, CN=IPS SERVIDORES/emailAddress=ips@mail.ips.es + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:ac:4f:52:74:9f:39:ea:8e:dc:25:c4:bc:98:5d: + 98:64:24:09:3c:21:b3:cc:19:b5:8e:94:8e:87:d1: + f8:37:3e:a1:c8:2d:58:a4:80:35:5b:a1:75:6c:1d: + 45:0c:1f:61:63:6a:5e:6f:9b:0a:4c:c1:c8:b8:61: + 23:35:81:ff:fe:ac:78:70:2d:68:e1:3a:07:98:95: + 02:54:dd:cd:23:b7:80:53:d7:c8:37:45:72:06:24: + 12:ba:13:61:21:8a:6e:75:28:e0:c5:0f:34:fd:36: + d8:45:7f:e1:b8:36:ef:b3:e1:c6:20:8e:e8:b4:38: + bc:e1:3e:f6:11:de:8c:9d:01 + Exponent: 65537 (0x10001) + Signature Algorithm: md5WithRSAEncryption + 2c:f3:c3:79:58:24:de:c6:3b:d1:e0:42:69:b8:ee:64:b3:3d: + 62:01:b9:b3:84:df:23:7d:dd:98:cf:10:a9:fe:00:d8:22:96: + 05:13:07:54:57:c5:a7:de:cb:d9:b8:88:42:f6:99:db:14:77: + 1f:b6:fe:25:3d:e1:a2:3e:03:a9:81:d2:2d:6c:47:f5:96:46: + 8c:22:ab:c8:cc:0d:0e:97:5e:8b:41:b4:3b:c4:0a:06:40:1d: + dd:46:f4:01:dd:ba:82:2e:3c:3d:78:70:9e:7c:18:d0:ab:f8: + b8:77:07:46:71:f1:ca:0b:63:5c:6a:f9:72:94:d5:01:4f:a0: + db:42 +-----BEGIN CERTIFICATE----- +MIICtzCCAiACAQAwDQYJKoZIhvcNAQEEBQAwgaMxCzAJBgNVBAYTAkVTMRIwEAYD +VQQIEwlCQVJDRUxPTkExEjAQBgNVBAcTCUJBUkNFTE9OQTEZMBcGA1UEChMQSVBT +IFNlZ3VyaWRhZCBDQTEYMBYGA1UECxMPQ2VydGlmaWNhY2lvbmVzMRcwFQYDVQQD +Ew5JUFMgU0VSVklET1JFUzEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwuaXBzLmVz +MB4XDTk4MDEwMTIzMjEwN1oXDTA5MTIyOTIzMjEwN1owgaMxCzAJBgNVBAYTAkVT +MRIwEAYDVQQIEwlCQVJDRUxPTkExEjAQBgNVBAcTCUJBUkNFTE9OQTEZMBcGA1UE +ChMQSVBTIFNlZ3VyaWRhZCBDQTEYMBYGA1UECxMPQ2VydGlmaWNhY2lvbmVzMRcw +FQYDVQQDEw5JUFMgU0VSVklET1JFUzEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwu +aXBzLmVzMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCsT1J0nznqjtwlxLyY +XZhkJAk8IbPMGbWOlI6H0fg3PqHILVikgDVboXVsHUUMH2Fjal5vmwpMwci4YSM1 +gf/+rHhwLWjhOgeYlQJU3c0jt4BT18g3RXIGJBK6E2Ehim51KODFDzT9NthFf+G4 +Nu+z4cYgjui0OLzhPvYR3oydAQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBACzzw3lY +JN7GO9HgQmm47mSzPWIBubOE3yN93ZjPEKn+ANgilgUTB1RXxafey9m4iEL2mdsU +dx+2/iU94aI+A6mB0i1sR/WWRowiq8jMDQ6XXotBtDvECgZAHd1G9AHduoIuPD14 +cJ58GNCr+Lh3B0Zx8coLY1xq+XKU1QFPoNtC +-----END CERTIFICATE----- + +IPS Timestamping root +===================== + +MD5 Fingerprint=2E:03:FD:C5:F5:D7:2B:94:64:C1:BE:89:31:F1:16:9B +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA Timestamping Certification Authority, CN=IPS CA Timestamping Certification Authority/emailAddress=ips@mail.ips.es + Validity + Not Before: Dec 29 01:10:18 2001 GMT + Not After : Dec 27 01:10:18 2025 GMT + Subject: C=ES, ST=Barcelona, L=Barcelona, O=IPS Internet publishing Services s.l., O=ips@mail.ips.es C.I.F. B-60929452, OU=IPS CA Timestamping Certification Authority, CN=IPS CA Timestamping Certification Authority/emailAddress=ips@mail.ips.es + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:bc:b8:ee:56:a5:9a:8c:e6:36:c9:c2:62:a0:66: + 81:8d:1a:d5:7a:d2:73:9f:0e:84:64:ba:95:b4:90: + a7:78:af:ca:fe:54:61:5b:ce:b2:20:57:01:ae:44: + 92:43:10:38:11:f7:68:fc:17:40:a5:68:27:32:3b: + c4:a7:e6:42:71:c5:99:ef:76:ff:2b:95:24:f5:49: + 92:18:68:ca:00:b5:a4:5a:2f:6e:cb:d6:1b:2c:0d: + 54:67:6b:7a:29:a1:58:ab:a2:5a:00:d6:5b:bb:18: + c2:df:f6:1e:13:56:76:9b:a5:68:e2:98:ce:c6:03: + 8a:34:db:4c:83:41:a6:a9:a3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 8B:D0:10:50:09:81:F2:9D:09:D5:0E:60:78:03:22:A2:3F:C8:CA:66 + X509v3 Authority Key Identifier: + keyid:8B:D0:10:50:09:81:F2:9D:09:D5:0E:60:78:03:22:A2:3F:C8:CA:66 + DirName:/C=ES/ST=Barcelona/L=Barcelona/O=IPS Internet publishing Services s.l./O=ips@mail.ips.es C.I.F. B-60929452/OU=IPS CA Timestamping Certification Authority/CN=IPS CA Timestamping Certification Authority/emailAddress=ips@mail.ips.es + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + X509v3 Key Usage: + Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping, Microsoft Individual Code Signing, Microsoft Commercial Code Signing, Microsoft Trust List Signing, Microsoft Encrypted File System + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:ips@mail.ips.es + X509v3 Issuer Alternative Name: + email:ips@mail.ips.es + Netscape Comment: + Timestamping CA Certificate issued by http://www.ips.es/ + Netscape Base Url: + http://www.ips.es/ips2002/ + Netscape CA Revocation Url: + http://www.ips.es/ips2002/ips2002Timestamping.crl + Netscape Revocation Url: + http://www.ips.es/ips2002/revocationTimestamping.html? + Netscape Renewal Url: + http://www.ips.es/ips2002/renewalTimestamping.html? + Netscape CA Policy Url: + http://www.ips.es/ips2002/policyTimestamping.html + X509v3 CRL Distribution Points: + URI:http://www.ips.es/ips2002/ips2002Timestamping.crl + URI:http://wwwback.ips.es/ips2002/ips2002Timestamping.crl + + Authority Information Access: + OCSP - URI:http://ocsp.ips.es/ + + Signature Algorithm: sha1WithRSAEncryption + 65:ba:c1:cc:00:1a:95:91:ca:e9:6c:3a:bf:3a:1e:14:08:7c: + fb:83:ee:6b:62:51:d3:33:91:b5:60:79:7e:04:d8:5d:79:37: + e8:c3:5b:b0:c4:67:2d:68:5a:b2:5f:0e:0a:fa:cd:3f:3a:45: + a1:ea:36:cf:26:1e:a7:11:28:c5:94:8f:84:4c:53:08:c5:93: + b3:fc:e2:7f:f5:8d:f3:b1:a9:85:5f:88:de:91:96:ee:17:5b: + ae:a5:ea:70:65:78:2c:21:64:01:95:ce:ce:4c:3e:50:f4:b6: + 59:cb:63:8d:b6:bd:18:d4:87:4a:5f:dc:ef:e9:56:f0:0a:0c: + e8:75 +-----BEGIN CERTIFICATE----- +MIIIODCCB6GgAwIBAgIBADANBgkqhkiG9w0BAQUFADCCAR4xCzAJBgNVBAYTAkVT +MRIwEAYDVQQIEwlCYXJjZWxvbmExEjAQBgNVBAcTCUJhcmNlbG9uYTEuMCwGA1UE +ChMlSVBTIEludGVybmV0IHB1Ymxpc2hpbmcgU2VydmljZXMgcy5sLjErMCkGA1UE +ChQiaXBzQG1haWwuaXBzLmVzIEMuSS5GLiAgQi02MDkyOTQ1MjE0MDIGA1UECxMr +SVBTIENBIFRpbWVzdGFtcGluZyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTE0MDIG +A1UEAxMrSVBTIENBIFRpbWVzdGFtcGluZyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eTEeMBwGCSqGSIb3DQEJARYPaXBzQG1haWwuaXBzLmVzMB4XDTAxMTIyOTAxMTAx +OFoXDTI1MTIyNzAxMTAxOFowggEeMQswCQYDVQQGEwJFUzESMBAGA1UECBMJQmFy +Y2Vsb25hMRIwEAYDVQQHEwlCYXJjZWxvbmExLjAsBgNVBAoTJUlQUyBJbnRlcm5l +dCBwdWJsaXNoaW5nIFNlcnZpY2VzIHMubC4xKzApBgNVBAoUImlwc0BtYWlsLmlw +cy5lcyBDLkkuRi4gIEItNjA5Mjk0NTIxNDAyBgNVBAsTK0lQUyBDQSBUaW1lc3Rh +bXBpbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxNDAyBgNVBAMTK0lQUyBDQSBU +aW1lc3RhbXBpbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHjAcBgkqhkiG9w0B +CQEWD2lwc0BtYWlsLmlwcy5lczCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +vLjuVqWajOY2ycJioGaBjRrVetJznw6EZLqVtJCneK/K/lRhW86yIFcBrkSSQxA4 +Efdo/BdApWgnMjvEp+ZCccWZ73b/K5Uk9UmSGGjKALWkWi9uy9YbLA1UZ2t6KaFY +q6JaANZbuxjC3/YeE1Z2m6Vo4pjOxgOKNNtMg0GmqaMCAwEAAaOCBIAwggR8MB0G +A1UdDgQWBBSL0BBQCYHynQnVDmB4AyKiP8jKZjCCAVAGA1UdIwSCAUcwggFDgBSL +0BBQCYHynQnVDmB4AyKiP8jKZqGCASakggEiMIIBHjELMAkGA1UEBhMCRVMxEjAQ +BgNVBAgTCUJhcmNlbG9uYTESMBAGA1UEBxMJQmFyY2Vsb25hMS4wLAYDVQQKEyVJ +UFMgSW50ZXJuZXQgcHVibGlzaGluZyBTZXJ2aWNlcyBzLmwuMSswKQYDVQQKFCJp +cHNAbWFpbC5pcHMuZXMgQy5JLkYuICBCLTYwOTI5NDUyMTQwMgYDVQQLEytJUFMg +Q0EgVGltZXN0YW1waW5nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTQwMgYDVQQD +EytJUFMgQ0EgVGltZXN0YW1waW5nIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MR4w +HAYJKoZIhvcNAQkBFg9pcHNAbWFpbC5pcHMuZXOCAQAwDAYDVR0TBAUwAwEB/zAM +BgNVHQ8EBQMDB/+AMGsGA1UdJQRkMGIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYKKwYBBAGCNwIBFQYKKwYBBAGCNwIB +FgYKKwYBBAGCNwoDAQYKKwYBBAGCNwoDBDARBglghkgBhvhCAQEEBAMCAAcwGgYD +VR0RBBMwEYEPaXBzQG1haWwuaXBzLmVzMBoGA1UdEgQTMBGBD2lwc0BtYWlsLmlw +cy5lczBHBglghkgBhvhCAQ0EOhY4VGltZXN0YW1waW5nIENBIENlcnRpZmljYXRl +IGlzc3VlZCBieSBodHRwOi8vd3d3Lmlwcy5lcy8wKQYJYIZIAYb4QgECBBwWGmh0 +dHA6Ly93d3cuaXBzLmVzL2lwczIwMDIvMEAGCWCGSAGG+EIBBAQzFjFodHRwOi8v +d3d3Lmlwcy5lcy9pcHMyMDAyL2lwczIwMDJUaW1lc3RhbXBpbmcuY3JsMEUGCWCG +SAGG+EIBAwQ4FjZodHRwOi8vd3d3Lmlwcy5lcy9pcHMyMDAyL3Jldm9jYXRpb25U +aW1lc3RhbXBpbmcuaHRtbD8wQgYJYIZIAYb4QgEHBDUWM2h0dHA6Ly93d3cuaXBz +LmVzL2lwczIwMDIvcmVuZXdhbFRpbWVzdGFtcGluZy5odG1sPzBABglghkgBhvhC +AQgEMxYxaHR0cDovL3d3dy5pcHMuZXMvaXBzMjAwMi9wb2xpY3lUaW1lc3RhbXBp +bmcuaHRtbDB/BgNVHR8EeDB2MDegNaAzhjFodHRwOi8vd3d3Lmlwcy5lcy9pcHMy +MDAyL2lwczIwMDJUaW1lc3RhbXBpbmcuY3JsMDugOaA3hjVodHRwOi8vd3d3YmFj +ay5pcHMuZXMvaXBzMjAwMi9pcHMyMDAyVGltZXN0YW1waW5nLmNybDAvBggrBgEF +BQcBAQQjMCEwHwYIKwYBBQUHMAGGE2h0dHA6Ly9vY3NwLmlwcy5lcy8wDQYJKoZI +hvcNAQEFBQADgYEAZbrBzAAalZHK6Ww6vzoeFAh8+4Pua2JR0zORtWB5fgTYXXk3 +6MNbsMRnLWhasl8OCvrNPzpFoeo2zyYepxEoxZSPhExTCMWTs/zif/WN87GphV+I +3pGW7hdbrqXqcGV4LCFkAZXOzkw+UPS2Wctjjba9GNSHSl/c7+lW8AoM6HU= +-----END CERTIFICATE----- + +QuoVadis Root CA +================ + +MD5 Fingerprint=27:DE:36:FE:72:B7:00:03:00:9D:F4:F0:1E:6C:04:24 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 985026699 (0x3ab6508b) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=BM, O=QuoVadis Limited, OU=Root Certification Authority, CN=QuoVadis Root Certification Authority + Validity + Not Before: Mar 19 18:33:33 2001 GMT + Not After : Mar 17 18:33:33 2021 GMT + Subject: C=BM, O=QuoVadis Limited, OU=Root Certification Authority, CN=QuoVadis Root Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:bf:61:b5:95:53:ba:57:fc:fa:f2:67:0b:3a:1a: + df:11:80:64:95:b4:d1:bc:cd:7a:cf:f6:29:96:2e: + 24:54:40:24:38:f7:1a:85:dc:58:4c:cb:a4:27:42: + 97:d0:9f:83:8a:c3:e4:06:03:5b:00:a5:51:1e:70: + 04:74:e2:c1:d4:3a:ab:d7:ad:3b:07:18:05:8e:fd: + 83:ac:ea:66:d9:18:1b:68:8a:f5:57:1a:98:ba:f5: + ed:76:3d:7c:d9:de:94:6a:3b:4b:17:c1:d5:8f:bd: + 65:38:3a:95:d0:3d:55:36:4e:df:79:57:31:2a:1e: + d8:59:65:49:58:20:98:7e:ab:5f:7e:9f:e9:d6:4d: + ec:83:74:a9:c7:6c:d8:ee:29:4a:85:2a:06:14:f9: + 54:e6:d3:da:65:07:8b:63:37:12:d7:d0:ec:c3:7b: + 20:41:44:a3:ed:cb:a0:17:e1:71:65:ce:1d:66:31: + f7:76:01:19:c8:7d:03:58:b6:95:49:1d:a6:12:26: + e8:c6:0c:76:e0:e3:66:cb:ea:5d:a6:26:ee:e5:cc: + 5f:bd:67:a7:01:27:0e:a2:ca:54:c5:b1:7a:95:1d: + 71:1e:4a:29:8a:03:dc:6a:45:c1:a4:19:5e:6f:36: + cd:c3:a2:b0:b7:fe:5c:38:e2:52:bc:f8:44:43:e6: + 90:bb + Exponent: 65537 (0x10001) + X509v3 extensions: + Authority Information Access: + OCSP - URI:https://ocsp.quovadisoffshore.com + + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.8024.0.1 + User Notice: + Explicit Text: Reliance on the QuoVadis Root Certificate by any party assumes acceptance of the then applicable standard terms and conditions of use, certification practices, and the QuoVadis Certificate Policy. + CPS: http://www.quovadis.bm + + X509v3 Subject Key Identifier: + 8B:4B:6D:ED:D3:29:B9:06:19:EC:39:39:A9:F0:97:84:6A:CB:EF:DF + X509v3 Authority Key Identifier: + keyid:8B:4B:6D:ED:D3:29:B9:06:19:EC:39:39:A9:F0:97:84:6A:CB:EF:DF + DirName:/C=BM/O=QuoVadis Limited/OU=Root Certification Authority/CN=QuoVadis Root Certification Authority + serial:3A:B6:50:8B + + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 8a:d4:14:b5:fe:f4:9a:92:a7:19:d4:a4:7e:72:18:8f:d9:68: + 7c:52:24:dd:67:6f:39:7a:c4:aa:5e:3d:e2:58:b0:4d:70:98: + 84:61:e8:1b:e3:69:18:0e:ce:fb:47:50:a0:4e:ff:f0:24:1f: + bd:b2:ce:f5:27:fc:ec:2f:53:aa:73:7b:03:3d:74:6e:e6:16: + 9e:eb:a5:2e:c4:bf:56:27:50:2b:62:ba:be:4b:1c:3c:55:5c: + 41:1d:24:be:82:20:47:5d:d5:44:7e:7a:16:68:df:7d:4d:51: + 70:78:57:1d:33:1e:fd:02:99:9c:0c:cd:0a:05:4f:c7:bb:8e: + a4:75:fa:4a:6d:b1:80:8e:09:56:b9:9c:1a:60:fe:5d:c1:d7: + 7a:dc:11:78:d0:d6:5d:c1:b7:d5:ad:32:99:03:3a:8a:cc:54: + 25:39:31:81:7b:13:22:51:ba:46:6c:a1:bb:9e:fa:04:6c:49: + 26:74:8f:d2:73:eb:cc:30:a2:e6:ea:59:22:87:f8:97:f5:0e: + fd:ea:cc:92:a4:16:c4:52:18:ea:21:ce:b1:f1:e6:84:81:e5: + ba:a9:86:28:f2:43:5a:5d:12:9d:ac:1e:d9:a8:e5:0a:6a:a7: + 7f:a0:87:29:cf:f2:89:4d:d4:ec:c5:e2:e6:7a:d0:36:23:8a: + 4a:74:36:f9 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----END CERTIFICATE----- + +Security Communication Root CA +============================== + +MD5 Fingerprint=F1:BC:63:6A:54:E0:B5:27:F5:CD:E7:1A:E3:4D:6E:4A +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=JP, O=SECOM Trust.net, OU=Security Communication RootCA1 + Validity + Not Before: Sep 30 04:20:49 2003 GMT + Not After : Sep 30 04:20:49 2023 GMT + Subject: C=JP, O=SECOM Trust.net, OU=Security Communication RootCA1 + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b3:b3:fe:7f:d3:6d:b1:ef:16:7c:57:a5:0c:6d: + 76:8a:2f:4b:bf:64:fb:4c:ee:8a:f0:f3:29:7c:f5: + ff:ee:2a:e0:e9:e9:ba:5b:64:22:9a:9a:6f:2c:3a: + 26:69:51:05:99:26:dc:d5:1c:6a:71:c6:9a:7d:1e: + 9d:dd:7c:6c:c6:8c:67:67:4a:3e:f8:71:b0:19:27: + a9:09:0c:a6:95:bf:4b:8c:0c:fa:55:98:3b:d8:e8: + 22:a1:4b:71:38:79:ac:97:92:69:b3:89:7e:ea:21: + 68:06:98:14:96:87:d2:61:36:bc:6d:27:56:9e:57: + ee:c0:c0:56:fd:32:cf:a4:d9:8e:c2:23:d7:8d:a8: + f3:d8:25:ac:97:e4:70:38:f4:b6:3a:b4:9d:3b:97: + 26:43:a3:a1:bc:49:59:72:4c:23:30:87:01:58:f6: + 4e:be:1c:68:56:66:af:cd:41:5d:c8:b3:4d:2a:55: + 46:ab:1f:da:1e:e2:40:3d:db:cd:7d:b9:92:80:9c: + 37:dd:0c:96:64:9d:dc:22:f7:64:8b:df:61:de:15: + 94:52:15:a0:7d:52:c9:4b:a8:21:c9:c6:b1:ed:cb: + c3:95:60:d1:0f:f0:ab:70:f8:df:cb:4d:7e:ec:d6: + fa:ab:d9:bd:7f:54:f2:a5:e9:79:fa:d9:d6:76:24: + 28:73 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + A0:73:49:99:68:DC:85:5B:65:E3:9B:28:2F:57:9F:BD:33:BC:07:48 + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 68:40:a9:a8:bb:e4:4f:5d:79:b3:05:b5:17:b3:60:13:eb:c6: + 92:5d:e0:d1:d3:6a:fe:fb:be:9b:6d:bf:c7:05:6d:59:20:c4: + 1c:f0:b7:da:84:58:02:63:fa:48:16:ef:4f:a5:0b:f7:4a:98: + f2:3f:9e:1b:ad:47:6b:63:ce:08:47:eb:52:3f:78:9c:af:4d: + ae:f8:d5:4f:cf:9a:98:2a:10:41:39:52:c4:dd:d9:9b:0e:ef: + 93:01:ae:b2:2e:ca:68:42:24:42:6c:b0:b3:3a:3e:cd:e9:da: + 48:c4:15:cb:e9:f9:07:0f:92:50:49:8a:dd:31:97:5f:c9:e9: + 37:aa:3b:59:65:97:94:32:c9:b3:9f:3e:3a:62:58:c5:49:ad: + 62:0e:71:a5:32:aa:2f:c6:89:76:43:40:13:13:67:3d:a2:54: + 25:10:cb:f1:3a:f2:d9:fa:db:49:56:bb:a6:fe:a7:41:35:c3: + e0:88:61:c9:88:c7:df:36:10:22:98:59:ea:b0:4a:fb:56:16: + 73:6e:ac:4d:f7:22:a1:4f:ad:1d:7a:2d:45:27:e5:30:c1:5e: + f2:da:13:cb:25:42:51:95:47:03:8c:6c:21:cc:74:42:ed:53: + ff:33:8b:8f:0f:57:01:16:2f:cf:a6:ee:c9:70:22:14:bd:fd: + be:6c:0b:03 +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +Sonera Class 1 Root CA +====================== + +MD5 Fingerprint=33:B7:84:F5:5F:27:D7:68:27:DE:14:DE:12:2A:ED:6F +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 36 (0x24) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=FI, O=Sonera, CN=Sonera Class1 CA + Validity + Not Before: Apr 6 10:49:13 2001 GMT + Not After : Apr 6 10:49:13 2021 GMT + Subject: C=FI, O=Sonera, CN=Sonera Class1 CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b5:89:1f:2b:4f:67:0a:79:ff:c5:1e:f8:7f:3c: + ed:d1:7e:da:b0:cd:6d:2f:36:ac:34:c6:db:d9:64: + 17:08:63:30:33:22:8a:4c:ee:8e:bb:0f:0d:42:55: + c9:9d:2e:a5:ef:f7:a7:8c:c3:ab:b9:97:cb:8e:ef: + 3f:15:67:a8:82:72:63:53:0f:41:8c:7d:10:95:24: + a1:5a:a5:06:fa:92:57:9d:fa:a5:01:f2:75:e9:1f: + bc:56:26:52:4e:78:19:65:58:55:03:58:c0:14:ae: + 8c:7c:55:5f:70:5b:77:23:06:36:97:f3:24:b5:9a: + 46:95:e4:df:0d:0b:05:45:e5:d1:f2:1d:82:bb:c6: + 13:e0:fe:aa:7a:fd:69:30:94:f3:d2:45:85:fc:f2: + 32:5b:32:de:e8:6c:5d:1f:cb:a4:22:74:b0:80:8e: + 5d:94:f7:06:00:4b:a9:d4:5e:2e:35:50:09:f3:80: + 97:f4:0c:17:ae:39:d8:5f:cd:33:c1:1c:ca:89:c2: + 22:f7:45:12:ed:5e:12:93:9d:63:ab:82:2e:b9:eb: + 42:41:44:cb:4a:1a:00:82:0d:9e:f9:8b:57:3e:4c: + c7:17:ed:2c:8b:72:33:5f:72:7a:38:56:d5:e6:d9: + ae:05:1a:1d:75:45:b1:cb:a5:25:1c:12:57:36:fd: + 22:37 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + 47:E2:0C:8B:F6:53:88:52 + X509v3 Key Usage: + Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 8b:1a:b2:c9:5d:61:b4:e1:b9:2b:b9:53:d1:b2:85:9d:77:8e: + 16:ee:11:3d:db:c2:63:d9:5b:97:65:fb:12:67:d8:2a:5c:b6: + ab:e5:5e:c3:b7:16:2f:c8:e8:ab:1d:8a:fd:ab:1a:7c:d5:5f: + 63:cf:dc:b0:dd:77:b9:a8:e6:d2:22:38:87:07:14:d9:ff:be: + 56:b5:fd:07:0e:3c:55:ca:16:cc:a7:a6:77:37:fb:db:5c:1f: + 4e:59:06:87:a3:03:43:f5:16:ab:b7:84:bd:4e:ef:9f:31:37: + f0:46:f1:40:b6:d1:0c:a5:64:f8:63:5e:21:db:55:4e:4f:31: + 76:9c:10:61:8e:b6:53:3a:a3:11:be:af:6d:7c:1e:bd:ae:2d: + e2:0c:69:c7:85:53:68:a2:61:ba:c5:3e:b4:79:54:78:9e:0a: + c7:02:be:62:d1:11:82:4b:65:2f:91:5a:c2:a8:87:b1:56:68: + 94:79:f9:25:f7:c1:d5:ae:1a:b8:bb:3d:8f:a9:8a:38:15:f7: + 73:d0:5a:60:d1:80:b0:f0:dc:d5:50:cd:4e:ee:92:48:69:ed: + b2:23:1e:30:cc:c8:94:c8:b6:f5:3b:86:7f:3f:a6:2e:9f:f6: + 3e:2c:b5:92:96:3e:df:2c:93:8a:ff:81:8c:0f:0f:59:21:19: + 57:bd:55:9a +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAx +MDQwNjEwNDkxM1oXDTIxMDQwNjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMSBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H887dF+2rDNbS82rDTG +29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9EJUk +oVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk +3w0LBUXl0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBL +qdReLjVQCfOAl/QMF6452F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIIN +nvmLVz5MxxftLItyM19yejhW1ebZrgUaHXVFsculJRwSVzb9IjcCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZTiFIwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE928Jj2VuX +ZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0H +DjxVyhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VO +TzF2nBBhjrZTOqMRvq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2Uv +kVrCqIexVmiUefkl98HVrhq4uz2PqYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4w +zMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9ZIRlXvVWa +-----END CERTIFICATE----- + +Sonera Class 2 Root CA +====================== + +MD5 Fingerprint=A3:EC:75:0F:2E:88:DF:FA:48:01:4E:0B:5C:48:6F:FB +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 29 (0x1d) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=FI, O=Sonera, CN=Sonera Class2 CA + Validity + Not Before: Apr 6 07:29:40 2001 GMT + Not After : Apr 6 07:29:40 2021 GMT + Subject: C=FI, O=Sonera, CN=Sonera Class2 CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:90:17:4a:35:9d:ca:f0:0d:96:c7:44:fa:16:37: + fc:48:bd:bd:7f:80:2d:35:3b:e1:6f:a8:67:a9:bf: + 03:1c:4d:8c:6f:32:47:d5:41:68:a4:13:04:c1:35: + 0c:9a:84:43:fc:5c:1d:ff:89:b3:e8:17:18:cd:91: + 5f:fb:89:e3:ea:bf:4e:5d:7c:1b:26:d3:75:79:ed: + e6:84:e3:57:e5:ad:29:c4:f4:3a:28:e7:a5:7b:84: + 36:69:b3:fd:5e:76:bd:a3:2d:99:d3:90:4e:23:28: + 7d:18:63:f1:54:3b:26:9d:76:5b:97:42:b2:ff:ae: + f0:4e:ec:dd:39:95:4e:83:06:7f:e7:49:40:c8:c5: + 01:b2:54:5a:66:1d:3d:fc:f9:e9:3c:0a:9e:81:b8: + 70:f0:01:8b:e4:23:54:7c:c8:ae:f8:90:1e:00:96: + 72:d4:54:cf:61:23:bc:ea:fb:9d:02:95:d1:b6:b9: + 71:3a:69:08:3f:0f:b4:e1:42:c7:88:f5:3f:98:a8: + a7:ba:1c:e0:71:71:ef:58:57:81:50:7a:5c:6b:74: + 46:0e:83:03:98:c3:8e:a8:6e:f2:76:32:6e:27:83: + c2:73:f3:dc:18:e8:b4:93:ea:75:44:6b:04:60:20: + 71:57:87:9d:f3:be:a0:90:23:3d:8a:24:e1:da:21: + db:c3 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + 4A:A0:AA:58:84:D3:5E:3C + X509v3 Key Usage: + Certificate Sign, CRL Sign + Signature Algorithm: sha1WithRSAEncryption + 5a:ce:87:f9:16:72:15:57:4b:1d:d9:9b:e7:a2:26:30:ec:93: + 67:df:d6:2d:d2:34:af:f7:38:a5:ce:ab:16:b9:ab:2f:7c:35: + cb:ac:d0:0f:b4:4c:2b:fc:80:ef:6b:8c:91:5f:36:76:f7:db: + b3:1b:19:ea:f4:b2:11:fd:61:71:44:bf:28:b3:3a:1d:bf:b3: + 43:e8:9f:bf:dc:31:08:71:b0:9d:8d:d6:34:47:32:90:c6:65: + 24:f7:a0:4a:7c:04:73:8f:39:6f:17:8c:72:b5:bd:4b:c8:7a: + f8:7b:83:c3:28:4e:9c:09:ea:67:3f:b2:67:04:1b:c3:14:da: + f8:e7:49:24:91:d0:1d:6a:fa:61:39:ef:6b:e7:21:75:06:07: + d8:12:b4:21:20:70:42:71:81:da:3c:9a:36:be:a6:5b:0d:6a: + 6c:9a:1f:91:7b:f9:f9:ef:42:ba:4e:4e:9e:cc:0c:8d:94:dc: + d9:45:9c:5e:ec:42:50:63:ae:f4:5d:c4:b1:12:dc:ca:3b:a8: + 2e:9d:14:5a:05:75:b7:ec:d7:63:e2:ba:35:b6:04:08:91:e8: + da:9d:9c:f6:66:b5:18:ac:0a:a6:54:26:34:33:d2:1b:c1:d4: + 7f:1a:3a:8e:0b:aa:32:6e:db:fc:4f:25:9f:d9:32:c7:96:5a: + 70:ac:df:4c +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----END CERTIFICATE----- + +Thawte Premium Server CA +======================== + +MD5 Fingerprint=06:9F:69:79:16:66:90:02:1B:8C:8C:A2:C3:07:6F:3A +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com + Validity + Not Before: Aug 1 00:00:00 1996 GMT + Not After : Dec 31 23:59:59 2020 GMT + Subject: C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:d2:36:36:6a:8b:d7:c2:5b:9e:da:81:41:62:8f: + 38:ee:49:04:55:d6:d0:ef:1c:1b:95:16:47:ef:18: + 48:35:3a:52:f4:2b:6a:06:8f:3b:2f:ea:56:e3:af: + 86:8d:9e:17:f7:9e:b4:65:75:02:4d:ef:cb:09:a2: + 21:51:d8:9b:d0:67:d0:ba:0d:92:06:14:73:d4:93: + cb:97:2a:00:9c:5c:4e:0c:bc:fa:15:52:fc:f2:44: + 6e:da:11:4a:6e:08:9f:2f:2d:e3:f9:aa:3a:86:73: + b6:46:53:58:c8:89:05:bd:83:11:b8:73:3f:aa:07: + 8d:f4:42:4d:e7:40:9d:1c:37 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + Signature Algorithm: md5WithRSAEncryption + 26:48:2c:16:c2:58:fa:e8:16:74:0c:aa:aa:5f:54:3f:f2:d7: + c9:78:60:5e:5e:6e:37:63:22:77:36:7e:b2:17:c4:34:b9:f5: + 08:85:fc:c9:01:38:ff:4d:be:f2:16:42:43:e7:bb:5a:46:fb: + c1:c6:11:1f:f1:4a:b0:28:46:c9:c3:c4:42:7d:bc:fa:ab:59: + 6e:d5:b7:51:88:11:e3:a4:85:19:6b:82:4c:a4:0c:12:ad:e9: + a4:ae:3f:f1:c3:49:65:9a:8c:c5:c8:3e:25:b7:94:99:bb:92: + 32:71:07:f0:86:5e:ed:50:27:a6:0d:a6:23:f9:bb:cb:a6:07: + 14:42 +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +Staat der Nederlanden Root CA +============================= + +MD5 Fingerprint=60:84:7C:5A:CE:DB:0C:D4:CB:A7:E9:FE:02:C6:A9:C0 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 10000010 (0x98968a) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=NL, O=Staat der Nederlanden, CN=Staat der Nederlanden Root CA + Validity + Not Before: Dec 17 09:23:49 2002 GMT + Not After : Dec 16 09:15:38 2015 GMT + Subject: C=NL, O=Staat der Nederlanden, CN=Staat der Nederlanden Root CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:98:d2:b5:51:11:7a:81:a6:14:98:71:6d:be:cc: + e7:13:1b:d6:27:0e:7a:b3:6a:18:1c:b6:61:5a:d5: + 61:09:bf:de:90:13:c7:67:ee:dd:f3:da:c5:0c:12: + 9e:35:55:3e:2c:27:88:40:6b:f7:dc:dd:22:61:f5: + c2:c7:0e:f5:f6:d5:76:53:4d:8f:8c:bc:18:76:37: + 85:9d:e8:ca:49:c7:d2:4f:98:13:09:a2:3e:22:88: + 9c:7f:d6:f2:10:65:b4:ee:5f:18:d5:17:e3:f8:c5: + fd:e2:9d:a2:ef:53:0e:85:77:a2:0f:e1:30:47:ee: + 00:e7:33:7d:44:67:1a:0b:51:e8:8b:a0:9e:50:98: + 68:34:52:1f:2e:6d:01:f2:60:45:f2:31:eb:a9:31: + 68:29:bb:7a:41:9e:c6:19:7f:94:b4:51:39:03:7f: + b2:de:a7:32:9b:b4:47:8e:6f:b4:4a:ae:e5:af:b1: + dc:b0:1b:61:bc:99:72:de:e4:89:b7:7a:26:5d:da: + 33:49:5b:52:9c:0e:f5:8a:ad:c3:b8:3d:e8:06:6a: + c2:d5:2a:0b:6c:7b:84:bd:56:05:cb:86:65:92:ec: + 44:2b:b0:8e:b9:dc:70:0b:46:da:ad:bc:63:88:39: + fa:db:6a:fe:23:fa:bc:e4:48:f4:67:2b:6a:11:10: + 21:49 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:TRUE + X509v3 Certificate Policies: + Policy: 2.5.29.32.0 + CPS: http://www.pkioverheid.nl/policies/root-policy + + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Subject Key Identifier: + A8:7D:EB:BC:63:A4:74:13:74:00:EC:96:E0:D3:34:C1:2C:BF:6C:F8 + Signature Algorithm: sha1WithRSAEncryption + 05:84:87:55:74:36:61:c1:bb:d1:d4:c6:15:a8:13:b4:9f:a4: + fe:bb:ee:15:b4:2f:06:0c:29:f2:a8:92:a4:61:0d:fc:ab:5c: + 08:5b:51:13:2b:4d:c2:2a:61:c8:f8:09:58:fc:2d:02:b2:39: + 7d:99:66:81:bf:6e:5c:95:45:20:6c:e6:79:a7:d1:d8:1c:29: + fc:c2:20:27:51:c8:f1:7c:5d:34:67:69:85:11:30:c6:00:d2: + d7:f3:d3:7c:b6:f0:31:57:28:12:82:73:e9:33:2f:a6:55:b4: + 0b:91:94:47:9c:fa:bb:7a:42:32:e8:ae:7e:2d:c8:bc:ac:14: + bf:d9:0f:d9:5b:fc:c1:f9:7a:95:e1:7d:7e:96:fc:71:b0:c2: + 4c:c8:df:45:34:c9:ce:0d:f2:9c:64:08:d0:3b:c3:29:c5:b2: + ed:90:04:c1:b1:29:91:c5:30:6f:c1:a9:72:33:cc:fe:5d:16: + 17:2c:11:69:e7:7e:fe:c5:83:08:df:bc:dc:22:3a:2e:20:69: + 23:39:56:60:67:90:8b:2e:76:39:fb:11:88:97:f6:7c:bd:4b: + b8:20:16:67:05:8d:e2:3b:c1:72:3f:94:95:37:c7:5d:b9:9e: + d8:93:a1:17:8f:ff:0c:66:15:c1:24:7c:32:7c:03:1d:3b:a1: + 58:45:32:93 +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO +TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy +MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk +ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn +ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71 +9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO +hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U +tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o +BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh +SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww +OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv +cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA +7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k +/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm +eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6 +u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy +7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR +iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== +-----END CERTIFICATE----- + +TDC Internet Root CA +==================== + +MD5 Fingerprint=91:F4:03:55:20:A1:F8:63:2C:62:DE:AC:FB:61:1C:8E +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 986490188 (0x3acca54c) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=DK, O=TDC Internet, OU=TDC Internet Root CA + Validity + Not Before: Apr 5 16:33:17 2001 GMT + Not After : Apr 5 17:03:17 2021 GMT + Subject: C=DK, O=TDC Internet, OU=TDC Internet Root CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:c4:b8:40:bc:91:d5:63:1f:d7:99:a0:8b:0c:40: + 1e:74:b7:48:9d:46:8c:02:b2:e0:24:5f:f0:19:13: + a7:37:83:6b:5d:c7:8e:f9:84:30:ce:1a:3b:fa:fb: + ce:8b:6d:23:c6:c3:6e:66:9f:89:a5:df:e0:42:50: + 67:fa:1f:6c:1e:f4:d0:05:d6:bf:ca:d6:4e:e4:68: + 60:6c:46:aa:1c:5d:63:e1:07:86:0e:65:00:a7:2e: + a6:71:c6:bc:b9:81:a8:3a:7d:1a:d2:f9:d1:ac:4b: + cb:ce:75:af:dc:7b:fa:81:73:d4:fc:ba:bd:41:88: + d4:74:b3:f9:5e:38:3a:3c:43:a8:d2:95:4e:77:6d: + 13:0c:9d:8f:78:01:b7:5a:20:1f:03:37:35:e2:2c: + db:4b:2b:2c:78:b9:49:db:c4:d0:c7:9c:9c:e4:8a: + 20:09:21:16:56:66:ff:05:ec:5b:e3:f0:cf:ab:24: + 24:5e:c3:7f:70:7a:12:c4:d2:b5:10:a0:b6:21:e1: + 8d:78:69:55:44:69:f5:ca:96:1c:34:85:17:25:77: + e2:f6:2f:27:98:78:fd:79:06:3a:a2:d6:5a:43:c1: + ff:ec:04:3b:ee:13:ef:d3:58:5a:ff:92:eb:ec:ae: + da:f2:37:03:47:41:b6:97:c9:2d:0a:41:22:bb:bb: + e6:a7 + Exponent: 65537 (0x10001) + X509v3 extensions: + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/C=DK/O=TDC Internet/OU=TDC Internet Root CA/CN=CRL1 + + X509v3 Private Key Usage Period: + Not Before: Apr 5 16:33:17 2001 GMT, Not After: Apr 5 17:03:17 2021 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:6C:64:01:C7:FD:85:6D:AC:C8:DA:9E:50:08:85:08:B5:3C:56:A8:50 + + X509v3 Subject Key Identifier: + 6C:64:01:C7:FD:85:6D:AC:C8:DA:9E:50:08:85:08:B5:3C:56:A8:50 + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0...V5.0:4.0.... + Signature Algorithm: sha1WithRSAEncryption + 4e:43:cc:d1:dd:1d:10:1b:06:7f:b7:a4:fa:d3:d9:4d:fb:23: + 9f:23:54:5b:e6:8b:2f:04:28:8b:b5:27:6d:89:a1:ec:98:69: + dc:e7:8d:26:83:05:79:74:ec:b4:b9:a3:97:c1:35:00:fd:15: + da:39:81:3a:95:31:90:de:97:e9:86:a8:99:77:0c:e5:5a:a0: + 84:ff:12:16:ac:6e:b8:8d:c3:7b:92:c2:ac:2e:d0:7d:28:ec: + b6:f3:60:38:69:6f:3e:d8:04:55:3e:9e:cc:55:d2:ba:fe:bb: + 47:04:d7:0a:d9:16:0a:34:29:f5:58:13:d5:4f:cf:8f:56:4b: + b3:1e:ee:d3:98:79:da:08:1e:0c:6f:b8:f8:16:27:ef:c2:6f: + 3d:f6:a3:4b:3e:0e:e4:6d:6c:db:3b:41:12:9b:bd:0d:47:23: + 7f:3c:4a:d0:af:c0:af:f6:ef:1b:b5:15:c4:eb:83:c4:09:5f: + 74:8b:d9:11:fb:c2:56:b1:3c:f8:70:ca:34:8d:43:40:13:8c: + fd:99:03:54:79:c6:2e:ea:86:a1:f6:3a:d4:09:bc:f4:bc:66: + cc:3d:58:d0:57:49:0a:ee:25:e2:41:ee:13:f9:9b:38:34:d1: + 00:f5:7e:e7:94:1d:fc:69:03:62:b8:99:05:05:3d:6b:78:12: + bd:b0:6f:65 +-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE +SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg +Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV +BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl +cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA +vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu +Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a +0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1 +4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN +eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD +R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG +A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu +dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME +Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3 +WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw +HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ +KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO +Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX +wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ +2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89 +9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0 +jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38 +aQNiuJkFBT1reBK9sG9l +-----END CERTIFICATE----- + +TDC OCES Root CA +================ + +MD5 Fingerprint=93:7F:90:1C:ED:84:67:17:A4:65:5F:9B:CB:30:02:97 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1044954564 (0x3e48bdc4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=DK, O=TDC, CN=TDC OCES CA + Validity + Not Before: Feb 11 08:39:30 2003 GMT + Not After : Feb 11 09:09:30 2037 GMT + Subject: C=DK, O=TDC, CN=TDC OCES CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ac:62:f6:61:20:b2:cf:c0:c6:85:d7:e3:79:e6: + cc:ed:f2:39:92:a4:97:2e:64:a3:84:5b:87:9c:4c: + fd:a4:f3:c4:5f:21:bd:56:10:eb:db:2e:61:ec:93: + 69:e3:a3:cc:bd:99:c3:05:fc:06:b8:ca:36:1c:fe: + 90:8e:49:4c:c4:56:9a:2f:56:bc:cf:7b:0c:f1:6f: + 47:a6:0d:43:4d:e2:e9:1d:39:34:cd:8d:2c:d9:12: + 98:f9:e3:e1:c1:4a:7c:86:38:c4:a9:c4:61:88:d2: + 5e:af:1a:26:4d:d5:e4:a0:22:47:84:d9:64:b7:19: + 96:fc:ec:19:e4:b2:97:26:4e:4a:4c:cb:8f:24:8b: + 54:18:1c:48:61:7b:d5:88:68:da:5d:b5:ea:cd:1a: + 30:c1:80:83:76:50:aa:4f:d1:d4:dd:38:f0:ef:16: + f4:e1:0c:50:06:bf:ea:fb:7a:49:a1:28:2b:1c:f6: + fc:15:32:a3:74:6a:8f:a9:c3:62:29:71:31:e5:3b: + a4:60:17:5e:74:e6:da:13:ed:e9:1f:1f:1b:d1:b2: + 68:73:c6:10:34:75:46:10:10:e3:90:00:76:40:cb: + 8b:b7:43:09:21:ff:ab:4e:93:c6:58:e9:a5:82:db: + 77:c4:3a:99:b1:72:95:49:04:f0:b7:2b:fa:7b:59: + 8e:dd + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Certificate Policies: + Policy: 1.2.208.169.1.1.1 + CPS: http://www.certifikat.dk/repository + User Notice: + Organization: TDC + Number: 1 + Explicit Text: Certifikater fra denne CA udstedes under OID 1.2.208.169.1.1.1. Certificates from this CA are issued under OID 1.2.208.169.1.1.1. + + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 CRL Distribution Points: + DirName:/C=DK/O=TDC/CN=TDC OCES CA/CN=CRL1 + URI:http://crl.oces.certifikat.dk/oces.crl + + X509v3 Private Key Usage Period: + Not Before: Feb 11 08:39:30 2003 GMT, Not After: Feb 11 09:09:30 2037 GMT + X509v3 Authority Key Identifier: + keyid:60:B5:85:EC:56:64:7E:12:19:27:67:1D:50:15:4B:73:AE:3B:F9:12 + + X509v3 Subject Key Identifier: + 60:B5:85:EC:56:64:7E:12:19:27:67:1D:50:15:4B:73:AE:3B:F9:12 + 1.2.840.113533.7.65.0: + 0...V6.0:4.0.... + Signature Algorithm: sha1WithRSAEncryption + 0a:ba:26:26:46:d3:73:a8:09:f3:6b:0b:30:99:fd:8a:e1:57: + 7a:11:d3:b8:94:d7:09:10:6e:a3:b1:38:03:d1:b6:f2:43:41: + 29:62:a7:72:d8:fb:7c:05:e6:31:70:27:54:18:4e:8a:7c:4e: + e5:d1:ca:8c:78:88:cf:1b:d3:90:8b:e6:23:f8:0b:0e:33:43: + 7d:9c:e2:0a:19:8f:c9:01:3e:74:5d:74:c9:8b:1c:03:e5:18: + c8:01:4c:3f:cb:97:05:5d:98:71:a6:98:6f:b6:7c:bd:37:7f: + be:e1:93:25:6d:6f:f0:0a:ad:17:18:e1:03:bc:07:29:c8:ad: + 26:e8:f8:61:f0:fd:21:09:7e:9a:8e:a9:68:7d:48:62:72:bd: + 00:ea:01:99:b8:06:82:51:81:4e:f1:f5:b4:91:54:b9:23:7a: + 00:9a:9f:5d:8d:e0:3c:64:b9:1a:12:92:2a:c7:82:44:72:39: + dc:e2:3c:c6:d8:55:f5:15:4e:c8:05:0e:db:c6:d0:62:a6:ec: + 15:b4:b5:02:82:db:ac:8c:a2:81:f0:9b:99:31:f5:20:20:a8: + 88:61:0a:07:9f:94:fc:d0:d7:1b:cc:2e:17:f3:04:27:76:67: + eb:54:83:fd:a4:90:7e:06:3d:04:a3:43:2d:da:fc:0b:62:ea: + 2f:5f:62:53 +-----BEGIN CERTIFICATE----- +MIIFGTCCBAGgAwIBAgIEPki9xDANBgkqhkiG9w0BAQUFADAxMQswCQYDVQQGEwJE +SzEMMAoGA1UEChMDVERDMRQwEgYDVQQDEwtUREMgT0NFUyBDQTAeFw0wMzAyMTEw +ODM5MzBaFw0zNzAyMTEwOTA5MzBaMDExCzAJBgNVBAYTAkRLMQwwCgYDVQQKEwNU +REMxFDASBgNVBAMTC1REQyBPQ0VTIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEArGL2YSCyz8DGhdfjeebM7fI5kqSXLmSjhFuHnEz9pPPEXyG9VhDr +2y5h7JNp46PMvZnDBfwGuMo2HP6QjklMxFaaL1a8z3sM8W9Hpg1DTeLpHTk0zY0s +2RKY+ePhwUp8hjjEqcRhiNJerxomTdXkoCJHhNlktxmW/OwZ5LKXJk5KTMuPJItU +GBxIYXvViGjaXbXqzRowwYCDdlCqT9HU3Tjw7xb04QxQBr/q+3pJoSgrHPb8FTKj +dGqPqcNiKXEx5TukYBdedObaE+3pHx8b0bJoc8YQNHVGEBDjkAB2QMuLt0MJIf+r +TpPGWOmlgtt3xDqZsXKVSQTwtyv6e1mO3QIDAQABo4ICNzCCAjMwDwYDVR0TAQH/ +BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwgewGA1UdIASB5DCB4TCB3gYIKoFQgSkB +AQEwgdEwLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2VydGlmaWthdC5kay9yZXBv +c2l0b3J5MIGdBggrBgEFBQcCAjCBkDAKFgNUREMwAwIBARqBgUNlcnRpZmlrYXRl +ciBmcmEgZGVubmUgQ0EgdWRzdGVkZXMgdW5kZXIgT0lEIDEuMi4yMDguMTY5LjEu +MS4xLiBDZXJ0aWZpY2F0ZXMgZnJvbSB0aGlzIENBIGFyZSBpc3N1ZWQgdW5kZXIg +T0lEIDEuMi4yMDguMTY5LjEuMS4xLjARBglghkgBhvhCAQEEBAMCAAcwgYEGA1Ud +HwR6MHgwSKBGoESkQjBAMQswCQYDVQQGEwJESzEMMAoGA1UEChMDVERDMRQwEgYD +VQQDEwtUREMgT0NFUyBDQTENMAsGA1UEAxMEQ1JMMTAsoCqgKIYmaHR0cDovL2Ny +bC5vY2VzLmNlcnRpZmlrYXQuZGsvb2Nlcy5jcmwwKwYDVR0QBCQwIoAPMjAwMzAy +MTEwODM5MzBagQ8yMDM3MDIxMTA5MDkzMFowHwYDVR0jBBgwFoAUYLWF7FZkfhIZ +J2cdUBVLc647+RIwHQYDVR0OBBYEFGC1hexWZH4SGSdnHVAVS3OuO/kSMB0GCSqG +SIb2fQdBAAQQMA4bCFY2LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEACrom +JkbTc6gJ82sLMJn9iuFXehHTuJTXCRBuo7E4A9G28kNBKWKnctj7fAXmMXAnVBhO +inxO5dHKjHiIzxvTkIvmI/gLDjNDfZziChmPyQE+dF10yYscA+UYyAFMP8uXBV2Y +caaYb7Z8vTd/vuGTJW1v8AqtFxjhA7wHKcitJuj4YfD9IQl+mo6paH1IYnK9AOoB +mbgGglGBTvH1tJFUuSN6AJqfXY3gPGS5GhKSKseCRHI53OI8xthV9RVOyAUO28bQ +YqbsFbS1AoLbrIyigfCbmTH1ICCoiGEKB5+U/NDXG8wuF/MEJ3Zn61SD/aSQfgY9 +BKNDLdr8C2LqL19iUw== +-----END CERTIFICATE----- + +UTN DATACorp SGC Root CA +======================== + +MD5 Fingerprint=B3:A5:3E:77:21:6D:AC:4A:C0:C9:FB:D5:41:3D:CA:06 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 44:be:0c:8b:50:00:21:b4:11:d3:2a:68:06:a9:ad:69 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC + Validity + Not Before: Jun 24 18:57:21 1999 GMT + Not After : Jun 24 19:06:30 2019 GMT + Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:df:ee:58:10:a2:2b:6e:55:c4:8e:bf:2e:46:09: + e7:e0:08:0f:2e:2b:7a:13:94:1b:bd:f6:b6:80:8e: + 65:05:93:00:1e:bc:af:e2:0f:8e:19:0d:12:47:ec: + ac:ad:a3:fa:2e:70:f8:de:6e:fb:56:42:15:9e:2e: + 5c:ef:23:de:21:b9:05:76:27:19:0f:4f:d6:c3:9c: + b4:be:94:19:63:f2:a6:11:0a:eb:53:48:9c:be:f2: + 29:3b:16:e8:1a:a0:4c:a6:c9:f4:18:59:68:c0:70: + f2:53:00:c0:5e:50:82:a5:56:6f:36:f9:4a:e0:44: + 86:a0:4d:4e:d6:47:6e:49:4a:cb:67:d7:a6:c4:05: + b9:8e:1e:f4:fc:ff:cd:e7:36:e0:9c:05:6c:b2:33: + 22:15:d0:b4:e0:cc:17:c0:b2:c0:f4:fe:32:3f:29: + 2a:95:7b:d8:f2:a7:4e:0f:54:7c:a1:0d:80:b3:09: + 03:c1:ff:5c:dd:5e:9a:3e:bc:ae:bc:47:8a:6a:ae: + 71:ca:1f:b1:2a:b8:5f:42:05:0b:ec:46:30:d1:72: + 0b:ca:e9:56:6d:f5:ef:df:78:be:61:ba:b2:a5:ae: + 04:4c:bc:a8:ac:69:15:97:bd:ef:eb:b4:8c:bf:35: + f8:d4:c3:d1:28:0e:5c:3a:9f:70:18:33:20:77:c4: + a2:af + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: + Digital Signature, Non Repudiation, Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + 53:32:D1:B3:CF:7F:FA:E0:F1:A0:5D:85:4E:92:D2:9E:45:1D:B4:4F + X509v3 CRL Distribution Points: + URI:http://crl.usertrust.com/UTN-DATACorpSGC.crl + + X509v3 Extended Key Usage: + TLS Web Server Authentication, Microsoft Server Gated Crypto, Netscape Server Gated Crypto + Signature Algorithm: sha1WithRSAEncryption + 27:35:97:00:8a:8b:28:bd:c6:33:30:1e:29:fc:e2:f7:d5:98: + d4:40:bb:60:ca:bf:ab:17:2c:09:36:7f:50:fa:41:dc:ae:96: + 3a:0a:23:3e:89:59:c9:a3:07:ed:1b:37:ad:fc:7c:be:51:49: + 5a:de:3a:0a:54:08:16:45:c2:99:b1:87:cd:8c:68:e0:69:03: + e9:c4:4e:98:b2:3b:8c:16:b3:0e:a0:0c:98:50:9b:93:a9:70: + 09:c8:2c:a3:8f:df:02:e4:e0:71:3a:f1:b4:23:72:a0:aa:01: + df:df:98:3e:14:50:a0:31:26:bd:28:e9:5a:30:26:75:f9:7b: + 60:1c:8d:f3:cd:50:26:6d:04:27:9a:df:d5:0d:45:47:29:6b: + 2c:e6:76:d9:a9:29:7d:32:dd:c9:36:3c:bd:ae:35:f1:11:9e: + 1d:bb:90:3f:12:47:4e:8e:d7:7e:0f:62:73:1d:52:26:38:1c: + 18:49:fd:30:74:9a:c4:e5:22:2f:d8:c0:8d:ed:91:7a:4c:00: + 8f:72:7f:5d:da:dd:1b:8b:45:6b:e7:dd:69:97:a8:c5:56:4c: + 0f:0c:f6:9f:7a:91:37:f6:97:82:e0:dd:71:69:ff:76:3f:60: + 4d:3c:cf:f7:99:f9:c6:57:f4:c9:55:39:78:ba:2c:79:c9:a6: + 88:2b:f4:08 +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB +kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw +IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD +VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu +dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 +E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ +D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK +4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq +lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW +bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB +o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT +MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js +LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr +BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB +AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj +j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH +KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv +2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 +mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +UTN USERFirst Email Root CA +=========================== + +MD5 Fingerprint=D7:34:3D:EF:1D:27:09:28:E1:31:02:5B:13:2B:DD:F7 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 44:be:0c:8b:50:00:24:b4:11:d3:36:25:25:67:c9:89 + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Client Authentication and Email + Validity + Not Before: Jul 9 17:28:50 1999 GMT + Not After : Jul 9 17:36:58 2019 GMT + Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Client Authentication and Email + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b2:39:85:a4:f2:7d:ab:41:3b:62:46:37:ae:cd: + c1:60:75:bc:39:65:f9:4a:1a:47:a2:b9:cc:48:cc: + 6a:98:d5:4d:35:19:b9:a4:42:e5:ce:49:e2:8a:2f: + 1e:7c:d2:31:07:c7:4e:b4:83:64:9d:2e:29:d5:a2: + 64:c4:85:bd:85:51:35:79:a4:4e:68:90:7b:1c:7a: + a4:92:a8:17:f2:98:15:f2:93:cc:c9:a4:32:95:bb: + 0c:4f:30:bd:98:a0:0b:8b:e5:6e:1b:a2:46:fa:78: + bc:a2:6f:ab:59:5e:a5:2f:cf:ca:da:6d:aa:2f:eb: + ac:a1:b3:6a:aa:b7:2e:67:35:8b:79:e1:1e:69:88: + e2:e6:46:cd:a0:a5:ea:be:0b:ce:76:3a:7a:0e:9b: + ea:fc:da:27:5b:3d:73:1f:22:e6:48:61:c6:4c:f3: + 69:b1:a8:2e:1b:b6:d4:31:20:2c:bc:82:8a:8e:a4: + 0e:a5:d7:89:43:fc:16:5a:af:1d:71:d7:11:59:da: + ba:87:0d:af:fa:f3:e1:c2:f0:a4:c5:67:8c:d6:d6: + 54:3a:de:0a:a4:ba:03:77:b3:65:c8:fd:1e:d3:74: + 62:aa:18:ca:68:93:1e:a1:85:7e:f5:47:65:cb:f8: + 4d:57:28:74:d2:34:ff:30:b6:ee:f6:62:30:14:8c: + 2c:eb + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: + Digital Signature, Non Repudiation, Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + 89:82:67:7D:C4:9D:26:70:00:4B:B4:50:48:7C:DE:3D:AE:04:6E:7D + X509v3 CRL Distribution Points: + URI:http://crl.usertrust.com/UTN-USERFirst-ClientAuthenticationandEmail.crl + + X509v3 Extended Key Usage: + TLS Web Client Authentication, E-mail Protection + Signature Algorithm: sha1WithRSAEncryption + b1:6d:61:5d:a6:1a:7f:7c:ab:4a:e4:30:fc:53:6f:25:24:c6: + ca:ed:e2:31:5c:2b:0e:ee:ee:61:55:6f:04:3e:cf:39:de:c5: + 1b:49:94:e4:eb:20:4c:b4:e6:9e:50:2e:72:d9:8d:f5:aa:a3: + b3:4a:da:56:1c:60:97:80:dc:82:a2:ad:4a:bd:8a:2b:ff:0b: + 09:b4:c6:d7:20:04:45:e4:cd:80:01:ba:ba:2b:6e:ce:aa:d7: + 92:fe:e4:af:eb:f4:26:1d:16:2a:7f:6c:30:95:37:2f:33:12: + ac:7f:dd:c7:d1:11:8c:51:98:b2:d0:a3:91:d0:ad:f6:9f:9e: + 83:93:1e:1d:42:b8:46:af:6b:66:f0:9b:7f:ea:e3:03:02:e5: + 02:51:c1:aa:d5:35:9d:72:40:03:89:ba:31:1d:c5:10:68:52: + 9e:df:a2:85:c5:5c:08:a6:78:e6:53:4f:b1:e8:b7:d3:14:9e: + 93:a6:c3:64:e3:ac:7e:71:cd:bc:9f:e9:03:1b:cc:fb:e9:ac: + 31:c1:af:7c:15:74:02:99:c3:b2:47:a6:c2:32:61:d7:c7:6f: + 48:24:51:27:a1:d5:87:55:f2:7b:8f:98:3d:16:9e:ee:75:b6: + f8:d0:8e:f2:f3:c6:ae:28:5b:a7:f0:f3:36:17:fc:c3:05:d3: + ca:03:4a:54 +-----BEGIN CERTIFICATE----- +MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB +rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt +Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa +Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV +BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l +dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE +AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B +YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9 +hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l +L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm +SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM +1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws +6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw +Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50 +aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u +7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0 +xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ +rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim +eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk +USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ= +-----END CERTIFICATE----- + +UTN USERFirst Hardware Root CA +============================== + +MD5 Fingerprint=4C:56:41:E5:0D:BB:2B:E8:CA:A3:ED:18:08:AD:43:39 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 44:be:0c:8b:50:00:24:b4:11:d3:36:2a:fe:65:0a:fd + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware + Validity + Not Before: Jul 9 18:10:42 1999 GMT + Not After : Jul 9 18:19:22 2019 GMT + Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b1:f7:c3:38:3f:b4:a8:7f:cf:39:82:51:67:d0: + 6d:9f:d2:ff:58:f3:e7:9f:2b:ec:0d:89:54:99:b9: + 38:99:16:f7:e0:21:79:48:c2:bb:61:74:12:96:1d: + 3c:6a:72:d5:3c:10:67:3a:39:ed:2b:13:cd:66:eb: + 95:09:33:a4:6c:97:b1:e8:c6:ec:c1:75:79:9c:46: + 5e:8d:ab:d0:6a:fd:b9:2a:55:17:10:54:b3:19:f0: + 9a:f6:f1:b1:5d:b6:a7:6d:fb:e0:71:17:6b:a2:88: + fb:00:df:fe:1a:31:77:0c:9a:01:7a:b1:32:e3:2b: + 01:07:38:6e:c3:a5:5e:23:bc:45:9b:7b:50:c1:c9: + 30:8f:db:e5:2b:7a:d3:5b:fb:33:40:1e:a0:d5:98: + 17:bc:8b:87:c3:89:d3:5d:a0:8e:b2:aa:aa:f6:8e: + 69:88:06:c5:fa:89:21:f3:08:9d:69:2e:09:33:9b: + 29:0d:46:0f:8c:cc:49:34:b0:69:51:bd:f9:06:cd: + 68:ad:66:4c:bc:3e:ac:61:bd:0a:88:0e:c8:df:3d: + ee:7c:04:4c:9d:0a:5e:6b:91:d6:ee:c7:ed:28:8d: + ab:4d:87:89:73:d0:6e:a4:d0:1e:16:8b:14:e1:76: + 44:03:7f:63:ac:e4:cd:49:9c:c5:92:f4:ab:32:a1: + 48:5b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: + Digital Signature, Non Repudiation, Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45 + X509v3 CRL Distribution Points: + URI:http://crl.usertrust.com/UTN-USERFirst-Hardware.crl + + X509v3 Extended Key Usage: + TLS Web Server Authentication, IPSec End System, IPSec Tunnel, IPSec User + Signature Algorithm: sha1WithRSAEncryption + 47:19:0f:de:74:c6:99:97:af:fc:ad:28:5e:75:8e:eb:2d:67: + ee:4e:7b:2b:d7:0c:ff:f6:de:cb:55:a2:0a:e1:4c:54:65:93: + 60:6b:9f:12:9c:ad:5e:83:2c:eb:5a:ae:c0:e4:2d:f4:00:63: + 1d:b8:c0:6c:f2:cf:49:bb:4d:93:6f:06:a6:0a:22:b2:49:62: + 08:4e:ff:c8:c8:14:b2:88:16:5d:e7:01:e4:12:95:e5:45:34: + b3:8b:69:bd:cf:b4:85:8f:75:51:9e:7d:3a:38:3a:14:48:12: + c6:fb:a7:3b:1a:8d:0d:82:40:07:e8:04:08:90:a1:89:cb:19: + 50:df:ca:1c:01:bc:1d:04:19:7b:10:76:97:3b:ee:90:90:ca: + c4:0e:1f:16:6e:75:ef:33:f8:d3:6f:5b:1e:96:e3:e0:74:77: + 74:7b:8a:a2:6e:2d:dd:76:d6:39:30:82:f0:ab:9c:52:f2:2a: + c7:af:49:5e:7e:c7:68:e5:82:81:c8:6a:27:f9:27:88:2a:d5: + 58:50:95:1f:f0:3b:1c:57:bb:7d:14:39:62:2b:9a:c9:94:92: + 2a:a3:22:0c:ff:89:26:7d:5f:23:2b:47:d7:15:1d:a9:6a:9e: + 51:0d:2a:51:9e:81:f9:d4:3b:5e:70:12:7f:10:32:9c:1e:bb: + 9d:f8:66:a8 +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== +-----END CERTIFICATE----- + +UTN USERFirst Object Root CA +============================ + +MD5 Fingerprint=A7:F2:E4:16:06:41:11:50:30:6B:9C:E3:B4:9C:B0:C9 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 44:be:0c:8b:50:00:24:b4:11:d3:36:2d:e0:b3:5f:1b + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Object + Validity + Not Before: Jul 9 18:31:20 1999 GMT + Not After : Jul 9 18:40:36 2019 GMT + Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Object + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ce:aa:81:3f:a3:a3:61:78:aa:31:00:55:95:11: + 9e:27:0f:1f:1c:df:3a:9b:82:68:30:c0:4a:61:1d: + f1:2f:0e:fa:be:79:f7:a5:23:ef:55:51:96:84:cd: + db:e3:b9:6e:3e:31:d8:0a:20:67:c7:f4:d9:bf:94: + eb:47:04:3e:02:ce:2a:a2:5d:87:04:09:f6:30:9d: + 18:8a:97:b2:aa:1c:fc:41:d2:a1:36:cb:fb:3d:91: + ba:e7:d9:70:35:fa:e4:e7:90:c3:9b:a3:9b:d3:3c: + f5:12:99:77:b1:b7:09:e0:68:e6:1c:b8:f3:94:63: + 88:6a:6a:fe:0b:76:c9:be:f4:22:e4:67:b9:ab:1a: + 5e:77:c1:85:07:dd:0d:6c:bf:ee:06:c7:77:6a:41: + 9e:a7:0f:d7:fb:ee:94:17:b7:fc:85:be:a4:ab:c4: + 1c:31:dd:d7:b6:d1:e4:f0:ef:df:16:8f:b2:52:93: + d7:a1:d4:89:a1:07:2e:bf:e1:01:12:42:1e:1a:e1: + d8:95:34:db:64:79:28:ff:ba:2e:11:c2:e5:e8:5b: + 92:48:fb:47:0b:c2:6c:da:ad:32:83:41:f3:a5:e5: + 41:70:fd:65:90:6d:fa:fa:51:c4:f9:bd:96:2b:19: + 04:2c:d3:6d:a7:dc:f0:7f:6f:83:65:e2:6a:ab:87: + 86:75 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: + Digital Signature, Non Repudiation, Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + DA:ED:64:74:14:9C:14:3C:AB:DD:99:A9:BD:5B:28:4D:8B:3C:C9:D8 + X509v3 CRL Distribution Points: + URI:http://crl.usertrust.com/UTN-USERFirst-Object.crl + + X509v3 Extended Key Usage: + Code Signing, Time Stamping, Microsoft Encrypted File System + Signature Algorithm: sha1WithRSAEncryption + 08:1f:52:b1:37:44:78:db:fd:ce:b9:da:95:96:98:aa:55:64: + 80:b5:5a:40:dd:21:a5:c5:c1:f3:5f:2c:4c:c8:47:5a:69:ea: + e8:f0:35:35:f4:d0:25:f3:c8:a6:a4:87:4a:bd:1b:b1:73:08: + bd:d4:c3:ca:b6:35:bb:59:86:77:31:cd:a7:80:14:ae:13:ef: + fc:b1:48:f9:6b:25:25:2d:51:b6:2c:6d:45:c1:98:c8:8a:56: + 5d:3e:ee:43:4e:3e:6b:27:8e:d0:3a:4b:85:0b:5f:d3:ed:6a: + a7:75:cb:d1:5a:87:2f:39:75:13:5a:72:b0:02:81:9f:be:f0: + 0f:84:54:20:62:6c:69:d4:e1:4d:c6:0d:99:43:01:0d:12:96: + 8c:78:9d:bf:50:a2:b1:44:aa:6a:cf:17:7a:cf:6f:0f:d4:f8: + 24:55:5f:f0:34:16:49:66:3e:50:46:c9:63:71:38:31:62:b8: + 62:b9:f3:53:ad:6c:b5:2b:a2:12:aa:19:4f:09:da:5e:e7:93: + c6:8e:14:08:fe:f0:30:80:18:a0:86:85:4d:c8:7d:d7:8b:03: + fe:6e:d5:f7:9d:16:ac:92:2c:a0:23:e5:9c:91:52:1f:94:df: + 17:94:73:c3:b3:c1:c1:71:05:20:00:78:bd:13:52:1d:a8:3e: + cd:00:1f:c8 +-----BEGIN CERTIFICATE----- +MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCB +lTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAbBgNVBAMTFFVUTi1VU0VSRmlyc3Qt +T2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAzNlowgZUxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAc +BgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3 +dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicP +HxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x2AogZ8f02b+U60cEPgLO +KqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQw5ujm9M89RKZd7G3CeBo +5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vulBe3/IW+ +pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehb +kkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUC +AwEAAaOBrzCBrDALBgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov +L2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDApBgNV +HSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQwDQYJKoZIhvcN +AQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw +NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXB +mMiKVl0+7kNOPmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU +4U3GDZlDAQ0Slox4nb9QorFEqmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK5 +81OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCGhU3IfdeLA/5u1fedFqySLKAj5ZyR +Uh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g= +-----END CERTIFICATE----- + +Camerfirma Chambers of Commerce Root +==================================== + +MD5 Fingerprint=B0:01:EE:14:D9:AF:29:18:94:76:8E:F1:69:33:2A:84 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=EU, O=AC Camerfirma SA CIF A82743287, OU=http://www.chambersign.org, CN=Chambers of Commerce Root + Validity + Not Before: Sep 30 16:13:43 2003 GMT + Not After : Sep 30 16:13:44 2037 GMT + Subject: C=EU, O=AC Camerfirma SA CIF A82743287, OU=http://www.chambersign.org, CN=Chambers of Commerce Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b7:36:55:e5:a5:5d:18:30:e0:da:89:54:91:fc: + c8:c7:52:f8:2f:50:d9:ef:b1:75:73:65:47:7d:1b: + 5b:ba:75:c5:fc:a1:88:24:fa:2f:ed:ca:08:4a:39: + 54:c4:51:7a:b5:da:60:ea:38:3c:81:b2:cb:f1:bb: + d9:91:23:3f:48:01:70:75:a9:05:2a:ad:1f:71:f3: + c9:54:3d:1d:06:6a:40:3e:b3:0c:85:ee:5c:1b:79: + c2:62:c4:b8:36:8e:35:5d:01:0c:23:04:47:35:aa: + 9b:60:4e:a0:66:3d:cb:26:0a:9c:40:a1:f4:5d:98: + bf:71:ab:a5:00:68:2a:ed:83:7a:0f:a2:14:b5:d4: + 22:b3:80:b0:3c:0c:5a:51:69:2d:58:18:8f:ed:99: + 9e:f1:ae:e2:95:e6:f6:47:a8:d6:0c:0f:b0:58:58: + db:c3:66:37:9e:9b:91:54:33:37:d2:94:1c:6a:48: + c9:c9:f2:a5:da:a5:0c:23:f7:23:0e:9c:32:55:5e: + 71:9c:84:05:51:9a:2d:fd:e6:4e:2a:34:5a:de:ca: + 40:37:67:0c:54:21:55:77:da:0a:0c:cc:97:ae:80: + dc:94:36:4a:f4:3e:ce:36:13:1e:53:e4:ac:4e:3a: + 05:ec:db:ae:72:9c:38:8b:d0:39:3b:89:0a:3e:77: + fe:75 + Exponent: 3 (0x3) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:12 + X509v3 CRL Distribution Points: + URI:http://crl.chambersign.org/chambersroot.crl + + X509v3 Subject Key Identifier: + E3:94:F5:B1:4D:E9:DB:A1:29:5B:57:8B:4D:76:06:76:E1:D1:A2:8A + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:chambersroot@chambersign.org + X509v3 Issuer Alternative Name: + email:chambersroot@chambersign.org + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.17326.10.3.1 + CPS: http://cps.chambersign.org/cps/chambersroot.html + + Signature Algorithm: sha1WithRSAEncryption + 0c:41:97:c2:1a:86:c0:22:7c:9f:fb:90:f3:1a:d1:03:b1:ef: + 13:f9:21:5f:04:9c:da:c9:a5:8d:27:6c:96:87:91:be:41:90: + 01:72:93:e7:1e:7d:5f:f6:89:c6:5d:a7:40:09:3d:ac:49:45: + 45:dc:2e:8d:30:68:b2:09:ba:fb:c3:2f:cc:ba:0b:df:3f:77: + 7b:46:7d:3a:12:24:8e:96:8f:3c:05:0a:6f:d2:94:28:1d:6d: + 0c:c0:2e:88:22:d5:d8:cf:1d:13:c7:f0:48:d7:d7:05:a7:cf: + c7:47:9e:3b:3c:34:c8:80:4f:d4:14:bb:fc:0d:50:f7:fa:b3: + ec:42:5f:a9:dd:6d:c8:f4:75:cf:7b:c1:72:26:b1:01:1c:5c: + 2c:fd:7a:4e:b4:01:c5:05:57:b9:e7:3c:aa:05:d9:88:e9:07: + 46:41:ce:ef:41:81:ae:58:df:83:a2:ae:ca:d7:77:1f:e7:00: + 3c:9d:6f:8e:e4:32:09:1d:4d:78:34:78:34:3c:94:9b:26:ed: + 4f:71:c6:19:7a:bd:20:22:48:5a:fe:4b:7d:03:b7:e7:58:be: + c6:32:4e:74:1e:68:dd:a8:68:5b:b3:3e:ee:62:7d:d9:80:e8: + 0a:75:7a:b7:ee:b4:65:9a:21:90:e0:aa:d0:98:bc:38:b5:73: + 3c:8b:f8:dc +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn +MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL +ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg +b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa +MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB +ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw +IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B +AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb +unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d +BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq +7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3 +0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX +roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG +A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j +aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p +26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA +BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud +EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN +BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB +AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd +p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi +1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc +XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0 +eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu +tGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +Camerfirma Global Chambersign Root +================================== + +MD5 Fingerprint=C5:E6:7B:BF:06:D0:4F:43:ED:C4:7A:65:8A:FB:6B:19 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=EU, O=AC Camerfirma SA CIF A82743287, OU=http://www.chambersign.org, CN=Global Chambersign Root + Validity + Not Before: Sep 30 16:14:18 2003 GMT + Not After : Sep 30 16:14:18 2037 GMT + Subject: C=EU, O=AC Camerfirma SA CIF A82743287, OU=http://www.chambersign.org, CN=Global Chambersign Root + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:a2:70:a2:d0:9f:42:ae:5b:17:c7:d8:7d:cf:14: + 83:fc:4f:c9:a1:b7:13:af:8a:d7:9e:3e:04:0a:92: + 8b:60:56:fa:b4:32:2f:88:4d:a1:60:08:f4:b7:09: + 4e:a0:49:2f:49:d6:d3:df:9d:97:5a:9f:94:04:70: + ec:3f:59:d9:b7:cc:66:8b:98:52:28:09:02:df:c5: + 2f:84:8d:7a:97:77:bf:ec:40:9d:25:72:ab:b5:3f: + 32:98:fb:b7:b7:fc:72:84:e5:35:87:f9:55:fa:a3: + 1f:0e:6f:2e:28:dd:69:a0:d9:42:10:c6:f8:b5:44: + c2:d0:43:7f:db:bc:e4:a2:3c:6a:55:78:0a:77:a9: + d8:ea:19:32:b7:2f:fe:5c:3f:1b:ee:b1:98:ec:ca: + ad:7a:69:45:e3:96:0f:55:f6:e6:ed:75:ea:65:e8: + 32:56:93:46:89:a8:25:8a:65:06:ee:6b:bf:79:07: + d0:f1:b7:af:ed:2c:4d:92:bb:c0:a8:5f:a7:67:7d: + 04:f2:15:08:70:ac:92:d6:7d:04:d2:33:fb:4c:b6: + 0b:0b:fb:1a:c9:c4:8d:03:a9:7e:5c:f2:50:ab:12: + a5:a1:cf:48:50:a5:ef:d2:c8:1a:13:fa:b0:7f:b1: + 82:1c:77:6a:0f:5f:dc:0b:95:8f:ef:43:7e:e6:45: + 09:25 + Exponent: 3 (0x3) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:12 + X509v3 CRL Distribution Points: + URI:http://crl.chambersign.org/chambersignroot.crl + + X509v3 Subject Key Identifier: + 43:9C:36:9F:B0:9E:30:4D:C6:CE:5F:AD:10:AB:E5:03:A5:FA:A9:14 + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + X509v3 Subject Alternative Name: + email:chambersignroot@chambersign.org + X509v3 Issuer Alternative Name: + email:chambersignroot@chambersign.org + X509v3 Certificate Policies: + Policy: 1.3.6.1.4.1.17326.10.1.1 + CPS: http://cps.chambersign.org/cps/chambersignroot.html + + Signature Algorithm: sha1WithRSAEncryption + 3c:3b:70:91:f9:04:54:27:91:e1:ed:ed:fe:68:7f:61:5d:e5: + 41:65:4f:32:f1:18:05:94:6a:1c:de:1f:70:db:3e:7b:32:02: + 34:b5:0c:6c:a1:8a:7c:a5:f4:8f:ff:d4:d8:ad:17:d5:2d:04: + d1:3f:58:80:e2:81:59:88:be:c0:e3:46:93:24:fe:90:bd:26: + a2:30:2d:e8:97:26:57:35:89:74:96:18:f6:15:e2:af:24:19: + 56:02:02:b2:ba:0f:14:ea:c6:8a:66:c1:86:45:55:8b:be:92: + be:9c:a4:04:c7:49:3c:9e:e8:29:7a:89:d7:fe:af:ff:68:f5: + a5:17:90:bd:ac:99:cc:a5:86:57:09:67:46:db:d6:16:c2:46: + f1:e4:a9:50:f5:8f:d1:92:15:d3:5f:3e:c6:00:49:3a:6e:58: + b2:d1:d1:27:0d:25:c8:32:f8:20:11:cd:7d:32:33:48:94:54: + 4c:dd:dc:79:c4:30:9f:eb:8e:b8:55:b5:d7:88:5c:c5:6a:24: + 3d:b2:d3:05:03:51:c6:07:ef:cc:14:72:74:3d:6e:72:ce:18: + 28:8c:4a:a0:77:e5:09:2b:45:44:47:ac:b7:67:7f:01:8a:05: + 5a:93:be:a1:c1:ff:f8:e7:0e:67:a4:47:49:76:5d:75:90:1a: + f5:26:8f:f0 +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn +MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL +ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo +YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9 +MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy +NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G +A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA +A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0 +Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s +QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV +eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795 +B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh +z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T +AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i +ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w +TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH +MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD +VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE +VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B +AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM +bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi +ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG +VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c +ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/ +AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +NetLock Notary (Class A) Root +============================= + +MD5 Fingerprint=86:38:6D:5E:49:63:6C:85:5C:DB:6D:DC:94:B7:D0:F7 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 259 (0x103) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=HU, ST=Hungary, L=Budapest, O=NetLock Halozatbiztonsagi Kft., OU=Tanusitvanykiadok, CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado + Validity + Not Before: Feb 24 23:14:47 1999 GMT + Not After : Feb 19 23:14:47 2019 GMT + Subject: C=HU, ST=Hungary, L=Budapest, O=NetLock Halozatbiztonsagi Kft., OU=Tanusitvanykiadok, CN=NetLock Kozjegyzoi (Class A) Tanusitvanykiado + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:bc:74:8c:0f:bb:4c:f4:37:1e:a9:05:82:d8:e6: + e1:6c:70:ea:78:b5:6e:d1:38:44:0d:a8:83:ce:5d: + d2:d6:d5:81:c5:d4:4b:e7:5b:94:70:26:db:3b:9d: + 6a:4c:62:f7:71:f3:64:d6:61:3b:3d:eb:73:a3:37: + d9:cf:ea:8c:92:3b:cd:f7:07:dc:66:74:97:f4:45: + 22:dd:f4:5c:e0:bf:6d:f3:be:65:33:e4:15:3a:bf: + db:98:90:55:38:c4:ed:a6:55:63:0b:b0:78:04:f4: + e3:6e:c1:3f:8e:fc:51:78:1f:92:9e:83:c2:fe:d9: + b0:a9:c9:bc:5a:00:ff:a9:a8:98:74:fb:f6:2c:3e: + 15:39:0d:b6:04:55:a8:0e:98:20:42:b3:b1:25:ad: + 7e:9a:6f:5d:53:b1:ab:0c:fc:eb:e0:f3:7a:b3:a8: + b3:ff:46:f6:63:a2:d8:3a:98:7b:b6:ac:85:ff:b0: + 25:4f:74:63:e7:13:07:a5:0a:8f:05:f7:c0:64:6f: + 7e:a7:27:80:96:de:d4:2e:86:60:c7:6b:2b:5e:73: + 7b:17:e7:91:3f:64:0c:d8:4b:22:34:2b:9b:32:f2: + 48:1f:9f:a1:0a:84:7a:e2:c2:ad:97:3d:8e:d5:c1: + f9:56:a3:50:e9:c6:b4:fa:98:a2:ee:95:e6:2a:03: + 8c:df + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:4 + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + Netscape Comment: + FIGYELEM! Ezen tanusitvany a NetLock Kft. Altalanos Szolgaltatasi Felteteleiben leirt eljarasok alapjan keszult. A hitelesites folyamatat a NetLock Kft. termekfelelosseg-biztositasa vedi. A digitalis alairas elfogadasanak feltetele az eloirt ellenorzesi eljaras megtetele. Az eljaras leirasa megtalalhato a NetLock Kft. Internet honlapjan a https://www.netlock.net/docs cimen vagy kerheto az ellenorzes@netlock.net e-mail cimen. IMPORTANT! The issuance and the use of this certificate is subject to the NetLock CPS available at https://www.netlock.net/docs or by e-mail at cps@netlock.net. + Signature Algorithm: md5WithRSAEncryption + 48:24:46:f7:ba:56:6f:fa:c8:28:03:40:4e:e5:31:39:6b:26: + 6b:53:7f:db:df:df:f3:71:3d:26:c0:14:0e:c6:67:7b:23:a8: + 0c:73:dd:01:bb:c6:ca:6e:37:39:55:d5:c7:8c:56:20:0e:28: + 0a:0e:d2:2a:a4:b0:49:52:c6:38:07:fe:be:0a:09:8c:d1:98: + cf:ca:da:14:31:a1:4f:d2:39:fc:0f:11:2c:43:c3:dd:ab:93: + c7:55:3e:47:7c:18:1a:00:dc:f3:7b:d8:f2:7f:52:6c:20:f4: + 0b:5f:69:52:f4:ee:f8:b2:29:60:eb:e3:49:31:21:0d:d6:b5: + 10:41:e2:41:09:6c:e2:1a:9a:56:4b:77:02:f6:a0:9b:9a:27: + 87:e8:55:29:71:c2:90:9f:45:78:1a:e1:15:64:3d:d0:0e:d8: + a0:76:9f:ae:c5:d0:2e:ea:d6:0f:56:ec:64:7f:5a:9b:14:58: + 01:27:7e:13:50:c7:6b:2a:e6:68:3c:bf:5c:a0:0a:1b:e1:0e: + 7a:e9:e2:80:c3:e9:e9:f6:fd:6c:11:9e:d0:e5:28:27:2b:54: + 32:42:14:82:75:e6:4a:f0:2b:66:75:63:8c:a2:fb:04:3e:83: + 0e:9b:36:f0:18:e4:26:20:c3:8c:f0:28:07:ad:3c:17:66:88: + b5:fd:b6:88 +-----BEGIN CERTIFICATE----- +MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV +MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe +TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0 +dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB +KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0 +N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC +dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu +MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL +b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD +zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi +3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8 +WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY +Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi +NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC +ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4 +QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0 +YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz +aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu +IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm +ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg +ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs +amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv +IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3 +Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6 +ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1 +YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg +dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs +b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G +CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO +xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP +0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ +QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk +f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK +8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI +-----END CERTIFICATE----- + +Equifax Secure CA +================= + +MD5 Fingerprint=67:CB:9D:C0:13:24:8A:82:9B:B2:17:1E:D1:1B:EC:D4 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 903804111 (0x35def4cf) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority + Validity + Not Before: Aug 22 16:41:51 1998 GMT + Not After : Aug 22 16:41:51 2018 GMT + Subject: C=US, O=Equifax, OU=Equifax Secure Certificate Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:c1:5d:b1:58:67:08:62:ee:a0:9a:2d:1f:08:6d: + 91:14:68:98:0a:1e:fe:da:04:6f:13:84:62:21:c3: + d1:7c:ce:9f:05:e0:b8:01:f0:4e:34:ec:e2:8a:95: + 04:64:ac:f1:6b:53:5f:05:b3:cb:67:80:bf:42:02: + 8e:fe:dd:01:09:ec:e1:00:14:4f:fc:fb:f0:0c:dd: + 43:ba:5b:2b:e1:1f:80:70:99:15:57:93:16:f1:0f: + 97:6a:b7:c2:68:23:1c:cc:4d:59:30:ac:51:1e:3b: + af:2b:d6:ee:63:45:7b:c5:d9:5f:50:d2:e3:50:0f: + 3a:88:e7:bf:14:fd:e0:c7:b9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 CRL Distribution Points: + DirName:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority/CN=CRL1 + + X509v3 Private Key Usage Period: + Not After: Aug 22 16:41:51 2018 GMT + X509v3 Key Usage: + Certificate Sign, CRL Sign + X509v3 Authority Key Identifier: + keyid:48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4 + + X509v3 Subject Key Identifier: + 48:E6:68:F9:2B:D2:B2:95:D7:47:D8:23:20:10:4F:33:98:90:9F:D4 + X509v3 Basic Constraints: + CA:TRUE + 1.2.840.113533.7.65.0: + 0...V3.0c.... + Signature Algorithm: sha1WithRSAEncryption + 58:ce:29:ea:fc:f7:de:b5:ce:02:b9:17:b5:85:d1:b9:e3:e0: + 95:cc:25:31:0d:00:a6:92:6e:7f:b6:92:63:9e:50:95:d1:9a: + 6f:e4:11:de:63:85:6e:98:ee:a8:ff:5a:c8:d3:55:b2:66:71: + 57:de:c0:21:eb:3d:2a:a7:23:49:01:04:86:42:7b:fc:ee:7f: + a2:16:52:b5:67:67:d3:40:db:3b:26:58:b2:28:77:3d:ae:14: + 77:61:d6:fa:2a:66:27:a0:0d:fa:a7:73:5c:ea:70:f1:94:21: + 65:44:5f:fa:fc:ef:29:68:a9:a2:87:79:ef:79:ef:4f:ac:07: + 77:38 +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy +dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 +MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx +dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f +BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A +cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ +MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw +ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj +IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y +7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh +1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 +-----END CERTIFICATE----- + +NetLock Business (Class B) Root +=============================== + +MD5 Fingerprint=39:16:AA:B9:6A:41:E1:14:69:DF:9E:6C:3B:72:DC:B6 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 105 (0x69) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=HU, L=Budapest, O=NetLock Halozatbiztonsagi Kft., OU=Tanusitvanykiadok, CN=NetLock Uzleti (Class B) Tanusitvanykiado + Validity + Not Before: Feb 25 14:10:22 1999 GMT + Not After : Feb 20 14:10:22 2019 GMT + Subject: C=HU, L=Budapest, O=NetLock Halozatbiztonsagi Kft., OU=Tanusitvanykiadok, CN=NetLock Uzleti (Class B) Tanusitvanykiado + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:b1:ea:04:ec:20:a0:23:c2:8f:38:60:cf:c7:46: + b3:d5:1b:fe:fb:b9:99:9e:04:dc:1c:7f:8c:4a:81: + 98:ee:a4:d4:ca:8a:17:b9:22:7f:83:0a:75:4c:9b: + c0:69:d8:64:39:a3:ed:92:a3:fd:5b:5c:74:1a:c0: + 47:ca:3a:69:76:9a:ba:e2:44:17:fc:4c:a3:d5:fe: + b8:97:88:af:88:03:89:1f:a4:f2:04:3e:c8:07:0b: + e6:f9:b3:2f:7a:62:14:09:46:14:ca:64:f5:8b:80: + b5:62:a8:d8:6b:d6:71:93:2d:b3:bf:09:54:58:ed: + 06:eb:a8:7b:dc:43:b1:a1:69 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:4 + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + Netscape Comment: + FIGYELEM! Ezen tanusitvany a NetLock Kft. Altalanos Szolgaltatasi Felteteleiben leirt eljarasok alapjan keszult. A hitelesites folyamatat a NetLock Kft. termekfelelosseg-biztositasa vedi. A digitalis alairas elfogadasanak feltetele az eloirt ellenorzesi eljaras megtetele. Az eljaras leirasa megtalalhato a NetLock Kft. Internet honlapjan a https://www.netlock.net/docs cimen vagy kerheto az ellenorzes@netlock.net e-mail cimen. IMPORTANT! The issuance and the use of this certificate is subject to the NetLock CPS available at https://www.netlock.net/docs or by e-mail at cps@netlock.net. + Signature Algorithm: md5WithRSAEncryption + 04:db:ae:8c:17:af:f8:0e:90:31:4e:cd:3e:09:c0:6d:3a:b0: + f8:33:4c:47:4c:e3:75:88:10:97:ac:b0:38:15:91:c6:29:96: + cc:21:c0:6d:3c:a5:74:cf:d8:82:a5:39:c3:65:e3:42:70:bb: + 22:90:e3:7d:db:35:76:e1:a0:b5:da:9f:70:6e:93:1a:30:39: + 1d:30:db:2e:e3:7c:b2:91:b2:d1:37:29:fa:b9:d6:17:5c:47: + 4f:e3:1d:38:eb:9f:d5:7b:95:a8:28:9e:15:4a:d1:d1:d0:2b: + 00:97:a0:e2:92:36:2b:63:ac:58:01:6b:33:29:50:86:83:f1: + 01:48 +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD +EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 +OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G +A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh +Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l +dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK +gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX +iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc +Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E +BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G +SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu +b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh +bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv +Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln +aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 +IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph +biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo +ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP +UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj +YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA +bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 +sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa +n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS +NitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +NetLock Express (Class C) Root +============================== + +MD5 Fingerprint=4F:EB:F1:F0:70:C2:80:63:5D:58:9F:DA:12:3C:A9:C4 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 104 (0x68) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=HU, L=Budapest, O=NetLock Halozatbiztonsagi Kft., OU=Tanusitvanykiadok, CN=NetLock Expressz (Class C) Tanusitvanykiado + Validity + Not Before: Feb 25 14:08:11 1999 GMT + Not After : Feb 20 14:08:11 2019 GMT + Subject: C=HU, L=Budapest, O=NetLock Halozatbiztonsagi Kft., OU=Tanusitvanykiadok, CN=NetLock Expressz (Class C) Tanusitvanykiado + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (1024 bit) + Modulus (1024 bit): + 00:eb:ec:b0:6c:61:8a:23:25:af:60:20:e3:d9:9f: + fc:93:0b:db:5d:8d:b0:a1:b3:40:3a:82:ce:fd:75: + e0:78:32:03:86:5a:86:95:91:ed:53:fa:9d:40:fc: + e6:e8:dd:d9:5b:7a:03:bd:5d:f3:3b:0c:c3:51:79: + 9b:ad:55:a0:e9:d0:03:10:af:0a:ba:14:42:d9:52: + 26:11:22:c7:d2:20:cc:82:a4:9a:a9:fe:b8:81:76: + 9d:6a:b7:d2:36:75:3e:b1:86:09:f6:6e:6d:7e:4e: + b7:7a:ec:ae:71:84:f6:04:33:08:25:32:eb:74:ac: + 16:44:c6:e4:40:93:1d:7f:ad + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:TRUE, pathlen:4 + X509v3 Key Usage: critical + Certificate Sign, CRL Sign + Netscape Cert Type: + SSL CA, S/MIME CA, Object Signing CA + Netscape Comment: + FIGYELEM! Ezen tanusitvany a NetLock Kft. Altalanos Szolgaltatasi Felteteleiben leirt eljarasok alapjan keszult. A hitelesites folyamatat a NetLock Kft. termekfelelosseg-biztositasa vedi. A digitalis alairas elfogadasanak feltetele az eloirt ellenorzesi eljaras megtetele. Az eljaras leirasa megtalalhato a NetLock Kft. Internet honlapjan a https://www.netlock.net/docs cimen vagy kerheto az ellenorzes@netlock.net e-mail cimen. IMPORTANT! The issuance and the use of this certificate is subject to the NetLock CPS available at https://www.netlock.net/docs or by e-mail at cps@netlock.net. + Signature Algorithm: md5WithRSAEncryption + 10:ad:7f:d7:0c:32:80:0a:d8:86:f1:79:98:b5:ad:d4:cd:b3: + 36:c4:96:48:c1:5c:cd:9a:d9:05:2e:9f:be:50:eb:f4:26:14: + 10:2d:d4:66:17:f8:9e:c1:27:fd:f1:ed:e4:7b:4b:a0:6c:b5: + ab:9a:57:70:a6:ed:a0:a4:ed:2e:f5:fd:fc:bd:fe:4d:37:08: + 0c:bc:e3:96:83:22:f5:49:1b:7f:4b:2b:b4:54:c1:80:7c:99: + 4e:1d:d0:8c:ee:d0:ac:e5:92:fa:75:56:fe:64:a0:13:8f:b8: + b8:16:9d:61:05:67:80:c8:d0:d8:a5:07:02:34:98:04:8d:33: + 04:d4 +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD +EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X +DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw +DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u +c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr +TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA +OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC +2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW +RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P +AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW +ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 +YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz +b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO +ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB +IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs +b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s +YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg +a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g +SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 +aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg +YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg +Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY +ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g +pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 +Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +XRamp Global CA Root +==================== + +MD5 Fingerprint=A1:0B:44:B3:CA:10:D8:00:6E:9D:0F:D8:0F:92:0A:D1 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 50:94:6c:ec:18:ea:d5:9c:4d:d5:97:ef:75:8f:a0:ad + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, OU=www.xrampsecurity.com, O=XRamp Security Services Inc, CN=XRamp Global Certification Authority + Validity + Not Before: Nov 1 17:14:04 2004 GMT + Not After : Jan 1 05:37:19 2035 GMT + Subject: C=US, OU=www.xrampsecurity.com, O=XRamp Security Services Inc, CN=XRamp Global Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:98:24:1e:bd:15:b4:ba:df:c7:8c:a5:27:b6:38: + 0b:69:f3:b6:4e:a8:2c:2e:21:1d:5c:44:df:21:5d: + 7e:23:74:fe:5e:7e:b4:4a:b7:a6:ad:1f:ae:e0:06: + 16:e2:9b:5b:d9:67:74:6b:5d:80:8f:29:9d:86:1b: + d9:9c:0d:98:6d:76:10:28:58:e4:65:b0:7f:4a:98: + 79:9f:e0:c3:31:7e:80:2b:b5:8c:c0:40:3b:11:86: + d0:cb:a2:86:36:60:a4:d5:30:82:6d:d9:6e:d0:0f: + 12:04:33:97:5f:4f:61:5a:f0:e4:f9:91:ab:e7:1d: + 3b:bc:e8:cf:f4:6b:2d:34:7c:e2:48:61:1c:8e:f3: + 61:44:cc:6f:a0:4a:a9:94:b0:4d:da:e7:a9:34:7a: + 72:38:a8:41:cc:3c:94:11:7d:eb:c8:a6:8c:b7:86: + cb:ca:33:3b:d9:3d:37:8b:fb:7a:3e:86:2c:e7:73: + d7:0a:57:ac:64:9b:19:eb:f4:0f:04:08:8a:ac:03: + 17:19:64:f4:5a:25:22:8d:34:2c:b2:f6:68:1d:12: + 6d:d3:8a:1e:14:da:c4:8f:a6:e2:23:85:d5:7a:0d: + bd:6a:e0:e9:ec:ec:17:bb:42:1b:67:aa:25:ed:45: + 83:21:fc:c1:c9:7c:d5:62:3e:fa:f2:c5:2d:d3:fd: + d4:65 + Exponent: 65537 (0x10001) + X509v3 extensions: + 1.3.6.1.4.1.311.20.2: + ...C.A + X509v3 Key Usage: + Digital Signature, Certificate Sign, CRL Sign + X509v3 Basic Constraints: critical + CA:TRUE + X509v3 Subject Key Identifier: + C6:4F:A2:3D:06:63:84:09:9C:CE:62:E4:04:AC:8D:5C:B5:E9:B6:1B + X509v3 CRL Distribution Points: + URI:http://crl.xrampsecurity.com/XGCA.crl + + 1.3.6.1.4.1.311.21.1: + ... + Signature Algorithm: sha1WithRSAEncryption + 91:15:39:03:01:1b:67:fb:4a:1c:f9:0a:60:5b:a1:da:4d:97: + 62:f9:24:53:27:d7:82:64:4e:90:2e:c3:49:1b:2b:9a:dc:fc: + a8:78:67:35:f1:1d:f0:11:bd:b7:48:e3:10:f6:0d:df:3f:d2: + c9:b6:aa:55:a4:48:ba:02:db:de:59:2e:15:5b:3b:9d:16:7d: + 47:d7:37:ea:5f:4d:76:12:36:bb:1f:d7:a1:81:04:46:20:a3: + 2c:6d:a9:9e:01:7e:3f:29:ce:00:93:df:fd:c9:92:73:89:89: + 64:9e:e7:2b:e4:1c:91:2c:d2:b9:ce:7d:ce:6f:31:99:d3:e6: + be:d2:1e:90:f0:09:14:79:5c:23:ab:4d:d2:da:21:1f:4d:99: + 79:9d:e1:cf:27:9f:10:9b:1c:88:0d:b0:8a:64:41:31:b8:0e: + 6c:90:24:a4:9b:5c:71:8f:ba:bb:7e:1c:1b:db:6a:80:0f:21: + bc:e9:db:a6:b7:40:f4:b2:8b:a9:b1:e4:ef:9a:1a:d0:3d:69: + 99:ee:a8:28:a3:e1:3c:b3:f0:b2:11:9c:cf:7c:40:e6:dd:e7: + 43:7d:a2:d8:3a:b5:a9:8d:f2:34:99:c4:d4:10:e1:06:fd:09: + 84:10:3b:ee:c4:4c:f4:ec:27:7c:42:c2:74:7c:82:8a:09:c9: + b4:03:25:bc +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +Go Daddy Class 2 CA +=================== + +MD5 Fingerprint=91:DE:06:25:AB:DA:FD:32:17:0C:BB:25:17:2A:84:67 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority + Validity + Not Before: Jun 29 17:06:20 2004 GMT + Not After : Jun 29 17:06:20 2034 GMT + Subject: C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:de:9d:d7:ea:57:18:49:a1:5b:eb:d7:5f:48:86: + ea:be:dd:ff:e4:ef:67:1c:f4:65:68:b3:57:71:a0: + 5e:77:bb:ed:9b:49:e9:70:80:3d:56:18:63:08:6f: + da:f2:cc:d0:3f:7f:02:54:22:54:10:d8:b2:81:d4: + c0:75:3d:4b:7f:c7:77:c3:3e:78:ab:1a:03:b5:20: + 6b:2f:6a:2b:b1:c5:88:7e:c4:bb:1e:b0:c1:d8:45: + 27:6f:aa:37:58:f7:87:26:d7:d8:2d:f6:a9:17:b7: + 1f:72:36:4e:a6:17:3f:65:98:92:db:2a:6e:5d:a2: + fe:88:e0:0b:de:7f:e5:8d:15:e1:eb:cb:3a:d5:e2: + 12:a2:13:2d:d8:8e:af:5f:12:3d:a0:08:05:08:b6: + 5c:a5:65:38:04:45:99:1e:a3:60:60:74:c5:41:a5: + 72:62:1b:62:c5:1f:6f:5f:1a:42:be:02:51:65:a8: + ae:23:18:6a:fc:78:03:a9:4d:7f:80:c3:fa:ab:5a: + fc:a1:40:a4:ca:19:16:fe:b2:c8:ef:5e:73:0d:ee: + 77:bd:9a:f6:79:98:bc:b1:07:67:a2:15:0d:dd:a0: + 58:c6:44:7b:0a:3e:62:28:5f:ba:41:07:53:58:cf: + 11:7e:38:74:c5:f8:ff:b5:69:90:8f:84:74:ea:97: + 1b:af + Exponent: 3 (0x3) + X509v3 extensions: + X509v3 Subject Key Identifier: + D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3 + X509v3 Authority Key Identifier: + keyid:D2:C4:B0:D2:91:D4:4C:11:71:B3:61:CB:3D:A1:FE:DD:A8:6A:D4:E3 + DirName:/C=US/O=The Go Daddy Group, Inc./OU=Go Daddy Class 2 Certification Authority + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 32:4b:f3:b2:ca:3e:91:fc:12:c6:a1:07:8c:8e:77:a0:33:06: + 14:5c:90:1e:18:f7:08:a6:3d:0a:19:f9:87:80:11:6e:69:e4: + 96:17:30:ff:34:91:63:72:38:ee:cc:1c:01:a3:1d:94:28:a4: + 31:f6:7a:c4:54:d7:f6:e5:31:58:03:a2:cc:ce:62:db:94:45: + 73:b5:bf:45:c9:24:b5:d5:82:02:ad:23:79:69:8d:b8:b6:4d: + ce:cf:4c:ca:33:23:e8:1c:88:aa:9d:8b:41:6e:16:c9:20:e5: + 89:9e:cd:3b:da:70:f7:7e:99:26:20:14:54:25:ab:6e:73:85: + e6:9b:21:9d:0a:6c:82:0e:a8:f8:c2:0c:fa:10:1e:6c:96:ef: + 87:0d:c4:0f:61:8b:ad:ee:83:2b:95:f8:8e:92:84:72:39:eb: + 20:ea:83:ed:83:cd:97:6e:08:bc:eb:4e:26:b6:73:2b:e4:d3: + f6:4c:fe:26:71:e2:61:11:74:4a:ff:57:1a:87:0f:75:48:2e: + cf:51:69:17:a0:02:12:61:95:d5:d1:40:b2:10:4c:ee:c4:ac: + 10:43:a6:a5:9e:0a:d5:95:62:9a:0d:cf:88:82:c5:32:0c:e4: + 2b:9f:45:e6:0d:9f:28:9c:b1:b9:2a:5a:57:ad:37:0f:af:1d: + 7f:db:bd:9f +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +Starfield Class 2 CA +==================== + +MD5 Fingerprint=32:4A:4B:BB:C8:63:69:9B:BE:74:9A:C6:DD:1D:46:24 +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 0 (0x0) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority + Validity + Not Before: Jun 29 17:39:16 2004 GMT + Not After : Jun 29 17:39:16 2034 GMT + Subject: C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b7:32:c8:fe:e9:71:a6:04:85:ad:0c:11:64:df: + ce:4d:ef:c8:03:18:87:3f:a1:ab:fb:3c:a6:9f:f0: + c3:a1:da:d4:d8:6e:2b:53:90:fb:24:a4:3e:84:f0: + 9e:e8:5f:ec:e5:27:44:f5:28:a6:3f:7b:de:e0:2a: + f0:c8:af:53:2f:9e:ca:05:01:93:1e:8f:66:1c:39: + a7:4d:fa:5a:b6:73:04:25:66:eb:77:7f:e7:59:c6: + 4a:99:25:14:54:eb:26:c7:f3:7f:19:d5:30:70:8f: + af:b0:46:2a:ff:ad:eb:29:ed:d7:9f:aa:04:87:a3: + d4:f9:89:a5:34:5f:db:43:91:82:36:d9:66:3c:b1: + b8:b9:82:fd:9c:3a:3e:10:c8:3b:ef:06:65:66:7a: + 9b:19:18:3d:ff:71:51:3c:30:2e:5f:be:3d:77:73: + b2:5d:06:6c:c3:23:56:9a:2b:85:26:92:1c:a7:02: + b3:e4:3f:0d:af:08:79:82:b8:36:3d:ea:9c:d3:35: + b3:bc:69:ca:f5:cc:9d:e8:fd:64:8d:17:80:33:6e: + 5e:4a:5d:99:c9:1e:87:b4:9d:1a:c0:d5:6e:13:35: + 23:5e:df:9b:5f:3d:ef:d6:f7:76:c2:ea:3e:bb:78: + 0d:1c:42:67:6b:04:d8:f8:d6:da:6f:8b:f2:44:a0: + 01:ab + Exponent: 3 (0x3) + X509v3 extensions: + X509v3 Subject Key Identifier: + BF:5F:B7:D1:CE:DD:1F:86:F4:5B:55:AC:DC:D7:10:C2:0E:A9:88:E7 + X509v3 Authority Key Identifier: + keyid:BF:5F:B7:D1:CE:DD:1F:86:F4:5B:55:AC:DC:D7:10:C2:0E:A9:88:E7 + DirName:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority + serial:00 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 05:9d:3f:88:9d:d1:c9:1a:55:a1:ac:69:f3:f3:59:da:9b:01: + 87:1a:4f:57:a9:a1:79:09:2a:db:f7:2f:b2:1e:cc:c7:5e:6a: + d8:83:87:a1:97:ef:49:35:3e:77:06:41:58:62:bf:8e:58:b8: + 0a:67:3f:ec:b3:dd:21:66:1f:c9:54:fa:72:cc:3d:4c:40:d8: + 81:af:77:9e:83:7a:bb:a2:c7:f5:34:17:8e:d9:11:40:f4:fc: + 2c:2a:4d:15:7f:a7:62:5d:2e:25:d3:00:0b:20:1a:1d:68:f9: + 17:b8:f4:bd:8b:ed:28:59:dd:4d:16:8b:17:83:c8:b2:65:c7: + 2d:7a:a5:aa:bc:53:86:6d:dd:57:a4:ca:f8:20:41:0b:68:f0: + f4:fb:74:be:56:5d:7a:79:f5:f9:1d:85:e3:2d:95:be:f5:71: + 90:43:cc:8d:1f:9a:00:0a:87:29:e9:55:22:58:00:23:ea:e3: + 12:43:29:5b:47:08:dd:8c:41:6a:65:06:a8:e5:21:aa:41:b4: + 95:21:95:b9:7d:d1:34:ab:13:d6:ad:bc:dc:e2:3d:39:cd:bd: + 3e:75:70:a1:18:59:03:c9:22:b4:8f:9c:d5:5e:2a:d7:a5:b6: + d4:0a:6d:f8:b7:40:11:46:9a:1f:79:0e:62:bf:0f:97:ec:e0: + 2f:1f:17:94 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- diff --git a/vendor/gems/active_utils-1.0.5/test/test_helper.rb b/vendor/gems/active_utils-1.0.5/test/test_helper.rb new file mode 100644 index 000000000..8f91c69fe --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/test_helper.rb @@ -0,0 +1,12 @@ +#!/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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/connection_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/connection_test.rb new file mode 100644 index 000000000..7213bf0c0 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/connection_test.rb @@ -0,0 +1,149 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/country_code_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/country_code_test.rb new file mode 100644 index 000000000..ce60adb44 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/country_code_test.rb @@ -0,0 +1,31 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/country_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/country_test.rb new file mode 100644 index 000000000..62fcb761b --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/country_test.rb @@ -0,0 +1,68 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/network_connection_retries_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/network_connection_retries_test.rb new file mode 100644 index 000000000..325ea0dbb --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/network_connection_retries_test.rb @@ -0,0 +1,127 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/post_data_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/post_data_test.rb new file mode 100644 index 000000000..dd22426f8 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/post_data_test.rb @@ -0,0 +1,50 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/posts_data_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/posts_data_test.rb new file mode 100644 index 000000000..e0d8b23d5 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/posts_data_test.rb @@ -0,0 +1,35 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/utils_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/utils_test.rb new file mode 100644 index 000000000..6a78ea4f9 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/utils_test.rb @@ -0,0 +1,7 @@ +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 diff --git a/vendor/gems/active_utils-1.0.5/test/unit/validateable_test.rb b/vendor/gems/active_utils-1.0.5/test/unit/validateable_test.rb new file mode 100644 index 000000000..d0cf67c76 --- /dev/null +++ b/vendor/gems/active_utils-1.0.5/test/unit/validateable_test.rb @@ -0,0 +1,59 @@ +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 diff --git a/vendor/gems/activemerchant-1.33.0/CHANGELOG b/vendor/gems/activemerchant-1.33.0/CHANGELOG new file mode 100644 index 000000000..17ad36b74 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/CHANGELOG @@ -0,0 +1,1143 @@ += ActiveMerchant CHANGELOG + +== Version 1.33.0 (May 30, 2013) + +* Netaxept: Completely revamped to use the "M" service type [rbjordan3, ntalbott] +* Litle: Void authorizations via an auth reversal [jrust] +* Add RBK Money integration [england] +* Direcpay: Update test url [ashish-d] +* PayPal Express gateway: Add support for creating billing agreements [fabiokr] +* PayPal Express gateway: Add reference authorizations [fabiokr] +* Add Cardstream Modern gateway [ExxKA] +* Pin: Fix special headers [duff] +* PayPal Express gateway: Remember the billing agreement id as Response#authorization [duff] +* PayPal Express gateway: Allow an amount of 0 [duff] +* PayPal Express gateway: Reduce parameter requirements [duff] +* Quickpay integration: Update notification parser to handle API v6 [larspind] +* Sage gateway: Deprecate #credit call [duff] +* Update notification generator to better match current notification class [lulalala] +* Paymill gateway: Change .com -> .de [louiskearns] +* Quickpay integration: Fix v6 response parsing [larspind] +* First Data e4: Add TransArmor store/tokenization support [gabetax] +* MerchantWarrior: Format expiration month/year correctly [klebervirgilio] +* Add iconv for ActiveSupport 2.3 under Ruby 2.0 [sanemat] +* Add Transnational gateway [bvandenbos] +* Authorize.Net: Add Check as payment method [andrunix] +* Merchant e-Solutions: Add ref number and recurring support [carlaares] +* Bogus gateway: Add authorization to purchase response [hron] +* Bluepay gateway: Fix Check support; general cleanup [ntalbott] +* Dwolla: Fix security issues and enable guest checkout [capablemonkey, schonfeld] +* SagePay gateway: Per-transaction 3D-secure selection [ExxKA] +* Barclays ePDQ: Handle incorrectly encoded response [jordanwheeler, aprofeit] +* Orbital: Bug fixes; add CustomerEmail, Retry Logic, Managed Billing, and Destination Address [juicedM3 +* Distinguish invalid vs empty issue_numbers on CreditCards [drasch] +* Float Gemfiles to latest Rails [sanemat] +* USA ePay Advanced: Fix Check support [RyanScottLewis] +* Authorize.Net: Match up Check fields better with eCheck.Net requirements [ntalbott] +* Bluepay: Updated to bp20post api [cagerton, melari] +* Net Registry: Deprecate credit method [jduff] +* Sage: Don't include T_customer_number unless it is numeric [melari] +* Auth.net: Don't include cust_id unless it is numeric [melari] +* Epay: Deprecate credit method [melari] +* New PayU.in Integration [PayU, melari] + +== Version 1.32.1 (April 4, 2013) + +* CC5 and Garanti: Remove $KCODE modifications [melari] +* Paymill: Add support for store [ntalbott] +* USA ePay: Fix misspelling of "Aduth" [joelvh, ntalbott] +* Orbital: Fix nil address values throwing exceptions during truncation [melari] + +== Version 1.32.0 (April 1, 2013) + +* Optimal: Submit shipping address with requests [jduff] +* Iridium: Enable reference transactions for authorize [ntalbott] +* Stripe: Add authorize and capture methods [melari] +* Pin: Add a default description if none is specified to fix failures [melari] +* Litle: Add support for passing optional fields in token based transactions [forest] +* Add Finansbank gateway [scamurcuoglu] +* Paymill: Use .com instead of .de for save card url [besi] +* Worldpay integration: Use more robust endpoint urls [nashbridges] +* Braintree Blue: Return CC token in transaction hash [cyu] +* Robokassa: Fix signature for empty amount [ukolovda] +* Worldpay gateway: Fix error messages for some failures [duff] +* Worldpay gateway: Allow settled payments to be refunded [dougal] +* Spreedly: Update urls and terminology [duff] +* Make card brand error more user friendly [oggy] +* DataCash: Update test Mastercard number [jamesshipton] +* DataCash: Update test response fixtures [jamesshipton] +* Pin: Add Pin.js card token support [nagash] +* PayPal Express gateway: Fix error when no address information is in response [pierre] +* Ogone: Use BYPSP for ALIASOPERATION [ntalbott] +* Paymill: Handle error storing card [duff] +* SagePay integration: Add referrer field [melari] +* Pin: Add extra headers [duff] +* Paymill: Add support for store [ntalbott] +* USA ePay Advanced: Fix typo in message credit card data options [joelvh] + +== Version 1.31.1 (February 25, 2013) + +* Cybersource: Bug fixes [natejgreene, jduff] + +== Version 1.31.0 (February 20, 2013) + +* Worldpay: XML encoding is required to be ISO-8859-1 [dougal] +* Worldpay: Add card code for more supported card types [dougal] +* Ogone: Add action option [pwoestelandt] +* PayPal Express gateway: Add support for BuyerEmailOptInEnable [chrisrbnelson] +* Add Paymill gateway [duff] +* Add EVO Canada gateway [alexdunae] +* Fixed credit card and check interface, used correct method for checking payment type [jduff] + +== Version 1.30.0 (February 13, 2013) + +* Add FirstData Global Gateway e4 [frobcode] +* PaymentExpress: Add support for optional fields: ClientType and TxnData [moklett] +* PaymentExpress: Limit MerchantReference/description to 64 chars [moklett] +* Wirecard: description must be no more than 32 characters [moklett] +* Litle: Add support for passing a token to the authorize and purchase methods [forest] +* PayPal Common: Allow searching for transactions by ProfileID [aq1018] +* Add Spreedly Core gateway [duff] +* eWay Gateway: Return proper value for authorization [duff] +* eWay Gateway: Add support for refunds [duff] +* Quickpay: Add support for protocols 5 & 6 [twarberg] +* Banwire gateway: Handle JSON::ParserError [duff] +* Balanced gateway: Fix unspecified marketplace [duff] +* QBMS gateway: Allow partial addresses [duff] +* Authorize.Net CIM: Allow omitting card expiration date [shanebonham] +* Authorize.Net CIM: Add support for extraOptions to createCustomerProfileTransaction [tpiekos] +* Add NETPAY gateway [samlown] +* Balanced gateway: Add amount to the refund method signature [ntalbott] +* Orbital gateway: Fix void method signature [aprofeit, ntalbott] +* Eway Managed: Add 'query_customer' API as #retrieve [cdaloisio] +* NetPay: Fix the signature for void [duff] +* Cybersource: Add check support [bowmande] +* Moneris: Use a capture of $0 for void [ntalbott] +* PayPal Express integration: Fix received_at time zone [ntalbott] +* NAB Transact: Add refund capability [nagash] +* Stripe: Add support for application_fee [duff] +* SagePay: Add support for GiftAidPayment [duff] +* Wirecard: Add support for partial captures [richardblair] +* Add Pin gateway [madpilot] +* Balanced: Added support for on_behalf_of_uri to capture [cwise] +* Litle: Add support for passing an order_source [forest] +* Add Merchant Warrior gateway [pronix, Fodoj, ntalbott] +* Use v4 of the MerchantWare API for voiding transactions [melari] +* Add support for Authorize.net in CA and GB [melari] +* Send customer's IP to Beanstream for fraud review [melari] + +== Version 1.29.3 (December 7, 2012) + +* Braintree Blue: Better wiredump_device support [ntalbott] +* Braintree: Store sets vault id as authorization [ntalbott] +* WorldPay: Fix currencies without fractions like JPY and HUF by rounding down amount [Soleone] + +== Version 1.29.2 (December 7, 2012) + +* Moneris: fix issue with the default options not being merged [jduff] +* Sage Pay: Make 0000 default post code for everyone if missing [BlakeMesdag] + +== Version 1.29.1 (December 5, 2012) + +* Add eWay Rapid 3.0 gateway [ntalbott] +* Fix AVS responses missing attributes [jduff] + +== Version 1.29.0 (November 30, 2012) + +* Authorize.Net gateway: Support description and order_id for capture [ntalbott] +* Add Mercury gateway [adr1anx, opendining] +* Webmoney integration: Add gross, item_id, and amount accessors to notification [fr33z3] +* Fix running tests under ActiveSupport 2.3.14 [ntalbott] +* SagePay Form integration: Remove dependency on ActiveSupport's String#truncate [ntalbott] +* Elavon gateway: Add support for the sales tax parameter [stevestmartin] +* Add WebPay gateway [keikubo] +* iTransact gateway: make void API consistent [frobcode] +* Stripe gateway: Pass city in add_address [npverni] +* Paypal gateway: Add option to change the outstanding balance of a recurring billing profile [mattwhite] +* Mercury gateway: Fix authorizations API [ntalbott] +* Mercury gateway: Support refund properly [ntalbott] +* Braintree Blue gateway: Add support for the recurring flag [ntalbott] +* Add HDFC gateway [ntalbott] +* HDFC gateway: Add more supported currencies [ntalbott] +* HDFC gateway: Add support for passing ECI value [ntalbott] +* HDFC gateway: Allow setting test mode via options [ntalbott] +* HDFC gateway: Pass phone number in UDF3 [ntalbott] +* HDFC gateway: Fix unescaped '&' entity in XML [ntalbott] +* HDFC gateway: More robust entity fixing [ntalbott] +* Add A1Agregator integration [england] +* Refactored handling of #test? and @options [ntalbott] +* Realex gateway: Realex gateway: Fix billing address format [ntalbott] +* Orbital gateway: handle custom AVS response codes [jonm-okc] +* Orbital gateway: Fix infinite connection retry [jonm-okc, ntalbott] +* PayPal integration: Add support for MassPay IPN notifications [damonmorgan] +* Orbital gateway: Fix status of void result [jonm-okc] +* Add Redsys gateway [samlown] +* Cybersource gateway: Add support for subscription credit [fabiokr] +* Use Thor for generators [ntalbott] +* Psigate gateway: Add void support [samuelreh] +* Add Liqpay integration [beorc] +* Paypal Express gateway: Add shipping accessor to response [v-fedorov] + +== Version 1.28.0 (August 10, 2012) + +* PayPal Express: support non standard locale codes [Soleone] +* Litle: allow setting test mode per transaction [jduff] +* Add Banwire gateway [acolin] +* Authorize.Net CIM gateway: Move cardCode after order to comply with the XSD [davetron5000] +* Add WebMoney integration [Mehonoshin] +* EasyPay: Make symmetric with other integrations [nashby] +* Add Paysbuy integration [divineforest] +* Bogus gateway: Use last digit for pass/fail [mipearson] +* Elavon gateway: Separate from Viaklix, implement refund & void [duff] +* Orbital gateway: Update to API version 5.6 and add support for profile requests [rbarazi] + +== Version 1.27.0 (August 10, 2012) + +* Add First Data integration [courtland] +* Add WebPay integration [nashby] +* Add Suomen Maksuturva integration [akonan] +* Payway gateway: Fix card storage [BenZhang] +* Payflow Pro gateway: Add MaxFailPayments support [gregwinn] +* Add Paxum integration [Mehonoshin] +* Add Balanced gateway [mjallday] +* Plug'n Pay gateway: Add tests for partial capture [csaunders] +* Braintree Blue gateway: Add credit card details to responses [dougbradbury] +* PayPal gateway: Support for 'Full' refund type [kurenn] +* Worldpay: fix refund [jduff/omh] +* Add PxPay offsite integration [boourns] +* Wirecard: always capture 'authorization' transaction [ntalbott] +* Add rake task to verify ssl certs [boourns] + +== Version 1.26.0 (July 6, 2012) + +* Orbital gateway: fix broken requests by ensuring the order of XML elements matches their DTD [Soleone] +* CyberSource gateway: clean up formatting [ntalbott] +* Netbilling gateway: Add refund/credit/void support [ntalbott] +* Add PayGate XML gateway [rubyisbeautiful] +* Add PayWay gateway [BenZhang] +* PayWay gateway: Tweaks to make more ActiveMerchant like [ntalbott] +* Netbilling gateway: Fix error handling [ntalbott] +* Netbilling gateway: Add refund/credit/void support [zenom, ntalbott] + +== Version 1.25.0 (July 3, 2012) + +* eWAY gateway: Add support for Diners Club cards [Soleone] +* Orbital gateway: Never send country code for orders outside of US, CA and GB [Soleone] +* Add EasyPay integration [nashby] +* Updating LitleOnline requirement to 8.13.2 to take advantage of better validation and get bugfix for Username [GregDrake] +* USAepay gateway: Add description support [ntalbott] +* Add Paypal Payments Advanced integration [csaunders] +* Authorize.Net gateway: Improve #refund docs [neerajdotname] +* Wirecard gateway: Fix for missing address hash [ntalbott] +* Clean up requires of RubyGems and JSON gems. Rename remote Litle test to match naming conventions [codyfauser] +* Cybersource gateway: Fix updating address only [fabiokr] +* Cybersource gateway: Move email requirement [fabiokr] +* Add the Metrics Global gateway [DanKnox] +* Braintree Blue gateway: Support wiredump_device [moklett] +* Add Fat Zebra gateway [amasses] +* Braintree Blue gateway: Always pass CVV on update [shayfrendt] +* eWAY gateway: Update docs. Require address [juggler] +* Cybersource gateway: Add support for subscriptions [fabiokr] + +== Version 1.24.0 (June 8, 2012) + +* PayPal gateway: Support for incomplete captures [mbulat] +* Moneris gateway: Add support for vault [kenzie] +* NAB Transact gateway: Add support for card descriptors [nagash] +* SagePayForm: truncate long description fields [jnormore] +* Paybox Direct: treat all response codes besides '00000' as failures +[Soleone] +* Deprecate CreditCard#type method in favor of CreditCard#brand [jduff] +* Cybersource gateway: Add subscriptions support [fabiokr, jaredmoody] +* eWay gateway: Improved docs, and more accurate required parameters [juggler] +* Braintree Blue gateway: Always pass CVV on card update [shayfrendt] +* Add Fat Zebra gateway [amasses] +* Braintree Blue gateway: Add support for wiredump_device [moklett] +* Add Metrics Global gateway [DanKnox] +* Cybersource gateway: Do not require email address for subscription operations [fabiokr] +* Cybersource gateway: Fix passing only an address when updating a subscription [fabiokr] +* Wirecard gateway: Fix for missing address; general cleanup [ntalbott] +* Authorize.Net gateway: Document ability to just pass the last four to #refund [neerajdotname] +* Add EasyPay integration [nashby] + +== Version 1.23.0 (May 23, 2012) + +* Add Litle gateway [GregDrake] +* PaymentExpress gateway: add support for BillingId and DpsBillingId for token [mikel] +* 2checkout integration: Add ability to auto settle [craigchristenson] +* 2checkout integration: Switch default mode to single page [craigchristenson] +* Cybersource: Revert - Add retrieve method to pull details on a +stored card [jduff] +* Cybersource: Revert - Add recurring payment support [jduff] +* PaymentExpress: add Cvc2Presence flag when submitting verification +value [jduff] +* SecurePayAU: fix CreditCard check [jduff] +* Barclays: fix order capture [csaunders/ntalbott/jduff] + +== Version 1.22.0 (May 17, 2012) + +* Remove version restriction for money gem [ylansegal] +* Add iTransact XML gateway [motske] +* PayPal Express Gateway: add options[:landing_page] [markus] +* USA ePay: Fix handling of AVS [duff] +* Ogone: Add store method to create an alias without making a purchase [joelcogen] +* Spelling fix: purcahse -> purchase [mnoack] +* ePay: Added more useful results for authorization errors [Dennis O'Connor] +* Add Robokassa integration [nashby] +* PayPal Gateway: Add recurring API [dscataglini] +* Braintree: Add support for :verify_card option on store [brentmc79] +* Moneris: cannot void a preauthorization [eddanger] +* Add Moneris US gateway [eddanger] +* Add Dotpay integration [kacperix] +* Payflow: Add description, comment and comment2 tags [ksnyder] +* Dotpay: Fix field mapping [kacperix] +* Authorize.Net CIM: Optionally add 'order' details to transactions [pote] +* Braintree: Allow including billing address when storing a customer [brentmc79] +* PayPal Gateway: Refactored PaymentDetails & PaymentDetailsItem common code [dscataglini] +* Viaklix/Elavon: Separate "demo accounts" from "test transactions" [mltsy] +* PayPal Gateway: Add transaction_details, balance, authorize_transaction, and manage_pending_transaction API calls [dscataglini] +* PayPal Gateway: Add support for TransactionSearch & DoReferenceTransaction [dscataglini] +* Cybersource: Add recurring payment support [jaredmoory] +* Tidy up gateway lists [ashokak] +* Paybox: remove Iconv usage [ntalbott] +* Dotpay: Add amount mapping, pin setter, and support for test? [kacperix] +* Braintree Blue: Make address country map to alpha2 [ntalbott] +* Use GB as the alpha2 country code for the UK [ntalbott] +* Realex: Handle XML response with unescaped ampersand [ntalbott] +* Add Vindicia gateway [steved555] +* Payment Express: use %w[] for country list [parndt] +* Braintree Blue: Match remote test up with change to :country [braintreeps] +* PayPal Integration: Fix received_at method time parsing [subbarao] +* Add MiGS Gateway [mnoack, nagash] +* Quickpay integration: Fix payment_service_for helper [TheMaster] +* Braintree Blue gateway: Improve update method [brentmc79] +* 2checkout integration: Add mode mapping & line items helper [AlexanderZaytsev] +* USA ePay Advanced gateway: Fix expiration date format. [cctalbott] +* Add ePay integration [ePay] +* 2checkout integration: Add support for single page payment routine [AlexanderZaytsev] +* Ogone: Add support for 3D Secure [rymai, ZenCocoon] +* Stripe gateway: Remove authorize and capture methods since they are not supported [jduff] +* Stripe gateway: default test to false if no livemode parameter is specified [jduff] +* Paybox Direct gateway: 'card absent' and 'do not honour' should be considered failures, not fraudulent [jduff] +* Add Verkkomaksut integration [akonan] +* Remove trailing spaces from generator templates [akonan] +* Payflow gateway: Allow modification of RetryNumDays [jrust] +* Payflow gateway: Don't auto-set start_date on modification [jrust] +* Bluepay gateway: Add ACH & recurring support [jslingerland] +* Orbital gateway: Don't send AVS address details for any country besides US, CA, GB and UK [Soleone] +* Payflow Express gateway: Better amount handling [jduff] +* Barclays gateway: Allow American Express [duff] +* Ogone gateway: Remove duplicated method [ntalbott] +* Cybersource gateway: Add retrieve method to pull details on a stored card [fabiokr] + +== Version 1.21.0 (March 7, 2012) + +* Stripe: Add support for passing IP [collision] +* Merchant e-Solutions: pass expiration date when purchasing with a stored credit card [chrisyoung] +* Braintree: Fix passing custom processor ids to old accounts [maxsilver] +* Authorize.net CIM: Add validation mode option to create_customer_profile_request [jwood] +* eWay Managed: Include transaction number in response params [jamsi] +* Fix various hash ordering issues exposed by Ruby 1.8 [ntalbott] +* Authorize.Net CIM: Add WEB echeck type [deathbob] +* Move Braintree from the gemspec to the Gemfile [ntalbott] +* Add CertoDirect gateway [hron] +* Authorize.Net CIM: Add option for setting a custom delimiter [bmorton] +* Authorize.Net CIM: Add 3.1 response fields [bmorton] +* Authorize.Net CIM: Misc fixes and doc improvements [bmorton] +* Authorize.Net CIM: Fix error when order is blank [KeeperPat] +* Beanstream: Add recurring payments support [castiglione] +* Make ePay password optional [ePay] +* Quickpay: skip testmode if transaction provided [brentmc79] +* Payflow: add additional fields [thorstadt] +* Authorize.Net CIM: Add get_customer_profile_ids [howaboutwe] +* PayPal Express: Add support for BrandName and Custom fields [exviva] +* Payflow: Handle dates with leading zeros [jcoleman] +* Authorize.Net CIM: Add CCV code support & improve tests [tgarnett] +* Add Authorize.Net SIM integration [courtland & rdp] +* Secure Pay AU: Handle periodic payments [tommeier] +* Viaklix: Add discover as a supported card type [waelchatila] +* Improvements to testing infrastructure for integrations [jduff] +* Add NAB Transact (AU) Gateway [tommeier] +* PayPal Express: Add Support for Reference Transactions using BAIDs [kenmazaika] +* Authorize.Net CIM: Add support for optional refund fields [nilmethod] +* SecurePayTech: Fix EnableCSC parameter so CVV codes are checked. [tlconnor] +* SecurePayTech: Add remote tests for CSC checking. [tlconnor] +* Samurai: Add option to retain payment methods once stored [brentmc79] +* PayPal Express Gateway: Add support for Digital Goods / Micropayments [kenmazaika] + + +== Version 1.20.4 (February 22, 2012) + +* Fix json dependency + +== Version 1.20.3 (February 7, 2012) + +* Various fixes to support Ruby 1.9 [wisq] +* SkipJack: Fix partial capture [jduff] +* Optimal Payments: submit region when outside North America [jduff] +* USA ePay: Add void and refund support [duff/ntalbott] +* Add first digits to credit card [codyfauser] +* Orbital: fixes to authentication and order id [Soleone] +* Stripe: fixes to purchase method [duff/ntalbott] + +== Version 1.20.2 (January 16, 2012) + +* Remove authorize/capture support for Stripe [gdb] + +== Version 1.20.1 (December 22, 2011) + +* PayflowExpressUk: Fix parsing street2 from response [odorcicd] +* AuthorizeNet: Support tracking id [odorcicd] +* SagePay Form: Map billing address to shipping address [jduff] + +== Version 1.20.0 (November 14, 2011) + +* Add support for USA ePay Advanced SOAP interface [matthewcalebsmith/jduff] +* Beanstram: fix purchase with Secure Profile [pitr/jduff] +* Orbital: various fixes [Soleone] +* Add Samuari gateway by Fee Fighters [jkrall/odorcicd] +* Lock money gem to 3.7.1 or less since newer versions break in 1.9 [jduff] +* Braintree: handle gateway rejected transactions gracefully [braintreeps/jduff] +* Ogone: support different signature encryptors, custom currency and eci [ZenCocoon/rymai/jduff] +* Payflow Link: use secure token [jduff] +* Added refund method to Exact, Pay Junction and Skip Jack gateways [jduff] +* Elavon: added test url [kylekeesling/jduff] +* Fix redundent errors when credit card is expired [castiglione/jduff] +* Two Checkout: update service url [vampirechicken/jduff] + +== Version 1.18.1 (September 23, 2011) + +* Braintree: allow setting merchant_account_id on initialize [jduff] +* Realex: only send letters and numbers in shipping code field [Soleone] + +== Version 1.18.0 (September 23, 2011) + +* NoChex: Update the URL that payment requests are posted to [caseywhalen/jduff] +* QBMS: fixed test mode check [Soleone] +* Realex: encode avs info with shipping address [Soleone] +* Add Dwolla offsite gateway [armsteadj1/jduff] +* Eway: pass email, customer, description and options to store [moklett/tobi] +* New dependency: active_utils gem [odorcicd] +* Optimal Payments: fix test mode check [jduff] + +== Version 1.17.0 (August 23, 2011) + +* Add Payflow Link integration [jduff] +* Add CardSave gateway [MrJaba/jduff]] +* Quickpay: Support protocal version 4 and fraud parameters [anderslemke/jduff] +* Authorize.net: Add status_recurring [mm1/jduff] +* Paypal Express: Support specifying :items with purchase [sivabudh/jduff] +* ePay: Add Sweden and Norway to supported countries [ePay/jduff] +* Brainreee: Support passing merchant_account_id parameter [braintreeps/jduff] +* Paypal Express: Remove deprecated Address field in favor of ShipToAddress[jduff] +* Add Optimal Payments gateway [jamie/jduff] +* Documentation improvements [dasch/nhemsley/jstorimer/jduff] +* Authorize.Net: Pass through first name, last name, and zip for refunds. [ntalbott] + +== Version 1.16.0 (July 18, 2011) + +* Bogus: Support referenced transactions for #authorize, #purchase, #recurring and +#credit [dasch/jduff] +* Payment Express: Update gateway url [bayan/titanous] +* Moneybookers: Send country and account_name if provided [Soleone] +* Moneris: Add Diners Club and Discover [Soleone] +* Cybersource: add auth_reversal support [jeberly/titanous] +* WorldPay: Update endpoint URLs for offsite gateway [Soleone] +* Worldpay: Add JCB and add Maestro [Soleone] +* Authorize.net: Add Diners Club and JCB [Soleone] +* Quickpay: Add testmode for subscribe and authorize [dasch/jduff] +* Orbital: fix handling of phone numbers. [ntalbott] +* Braintree: Add Diners Club [cody] +* Add ePaymentPlans offsite payment [robertomiranda/Soleone] +* Add Stripe gateway [boucher/titanous] +* Add Paystation gateway [nikz/jduff] +* Bump minimum ActiveSupport version to 2.3.11 [titanous] +* Use securerandom from stdlib not active_support [phlipper/jduff] + +== Version 1.15.0 (May 12, 2011) + +* DirecPay: Fix address to not include address2 twice in some cases [Soleone] +* DirecPay: Send company if available [Soleone] +* Realex: Fix hash signature [ntalbott/Soleone] +* SecurePay AU: Update remote tests [ntalbott] +* SecurePay AU: Fix method arity for #capture, #refund, #credit and #void [Soleone] +* Barclays ePDQ: Make response parsing more robust [Soleone] +* Payflow Express: Add line item support [wolframarnold] +* Payflow Express: Add comment field support [wolframarnold] +* Payflow: Add more optional fields [wolframarnold] +* Beanstream/Paypal: Fix CREDIT_DEPRECATION_MESSAGE errors [Jonathan Rudenberg] +* BraintreeBlue: Return a hash instead of a transaction object [braintreeps] +* BraintreeBlue: Return proper AVS/CVV values [braintreeps] +* Bogus: Add #recurring [trwomey] +* Make Validateable compatible with ActiveModel [CodeMonkeySteve] +* Add DirectEBanking offsite gateway [Gerwin Brunner/Soleone] +* ActiveSupport 3.1 beta support [cgriego] + +== Version 1.14.0 (Apr 29, 2011) + +* SagePayForm: Implement #cancelled? for Return. [wisq] +* Add #cancelled? to Integrations::Return [wisq] +* Bogus gateway: Add refund support and better tests [wisq] +* Beanstream: Add support for storing cards [duffomelia] +* eWay: Add support for storing cards [duffomelia] +* Add validation mode to update profile request [Ken Miller] +* Authorize.net CIM: Add oldLiveMode [ntalbott] +* Authorize.net CIM: Add extra transaction types [Ken Miller] +* JetPay: gateway tweaks [ntalbott] +* Deprecate a bunch more #credit methods [ntalbott] +* RealEx: Add authorize/capture/credit/void [ntalbott] +* SecurePay AU: Add authorize/capture/credit/void [ntalbott] +* PayPal Express: Make response parsing more robust [ntalbott] +* Test deprecation warnings; add deprecation line numbers [ntabott] +* Add Orbital direct gateway [ntalbott] +* Add WorldPay direct gateway [ntalbott] + +== Version 1.13.0 (Apr 19, 2011) + +* Add a Gemfile for optional bundler support [ssoroka] +* Stop using has_rdoc= when rubygems version is 1.7.0 or greater, since it's deprecated [ssoroka] +* Add tax field to braintree [wisq] +* Quickpay: Also add Sweden as supported country [Soleone] +* Adding refund method for gateways that are using the credit method for referenced based refunds, added deprecation worning to the credit method [John Duff] +* Return the Braintree transaction id in the response for void and refund transaction calls [John Duff] +* PayPal Express: Extract phone number from address if no contact phone was sent [Soleone] +* Unify all offsite gateways that verify the signature of Returns or Notifications by always using the #acknowledge method and calling the secret :credential2 [Soleone] +* Valitor: Change name of credential for Return and Notification from :password to :credential2 in symmetry with the other Integrations [Soleone] +* Moneybookers: Add support for tracking token [Soleone] +* Moneybookers: Require credential when creating Notifications instead of adding an argument to #acknowledge [Soleone] +* Moneybookers: Fix Notification to return correct status [Soleone] +* Support default Return class for all Integrations that don't use returns [Soleone] +* Add support for passing additional options when creating a Notification to all Integrations [Soleone] +* Update BraintreeBlue#refund to have consistent method signature [Jonathan Rudenberg] +* Add rails/init.rb for gem campatability in Rails [Rūdolfs Ošiņš] +* Fix Paypal Express response parser [Jonathan Rudenberg] +* Braintree/Transax: Add tax field [wisq] + +== Version 1.12.1 (Mar 21, 2011) + +* Ogone: Make sure response.params is a real Hash [Soleone] +* WorldPay: Fix service_url in production mode [Soleone] + +== Version 1.12.0 (Mar 1, 2011) + +* DirecPay: Send phone number as mobile phone by default [Soleone] +* Support sending line items for PayPal Express transactions [Jonathan Rudenberg] +* Update PayPal Express XML format to latest version [Jonathan Rudenberg] +* Fix custom image header for PayPal Express [mwagg] +* Add InvoiceID and OrderDescription to PayPal Express Authorize and Capture [cody] +* Add Moneybookers integration [Alex Diakov] +* Add QBMS (Quickbooks Merchant Services) gateway [ntalbott] +* Add NMI gateway [ntalbott] +* Make fully compatible with Rails 2 & 3, and Ruby 1.8 & 1.9 [ntalbott] +* Authorize.Net: Only return AVS message for AVS-related reason codes. [ntalbott] +* Add Federated Canada gateway [ntalbott] +* Garanti: Fix text normalization for nil values [Selem Delul] +* Valitor: Always send amount without any decimal places [Soleone] +* Add WorldPay integration [Soleone] + +== Version 1.11.0 (Feb 11, 2011) + +* Bump dependency for activesupport from 2.3.2 to 2.3.8 [Soleone] +* Garanti: Normalize text in xml fields for non-standard characters [Selem Delul] +* Garanti: Make sure order number does not contain illegal characters [Soleone] +* Fix ActionView tests for ActiveSupport 3.0.4 [Soleone] +* DirecPay: Make address information editable by default [Soleone] +* Fix ePDQ credit to expect and handle full authorization [Nathaniel Talbott] +* Add Barclays ePDQ Gateway [Nathaniel Talbott] +* Add default fixture for Garanti and don't use fixture for Garanti [cody] +* Add cms param for ePay [ePay] +* Add Valitor Integration [Nathaniel Talbott] + +== Version 1.10.0 (Jan 20, 2011) + +* PayPal Express: Support returning payer phone number [Soleone] +* Fix ePay to correctly send order number [Soleone] +* Add BluePay Gateway [Nathaniel Talbott] +* Add Quantum Gateway [Joshua Lippiner] +* Add iDEAL/Rabobank gateway [Jonathan Rudenberg] +* SagePayForm: Added send_email_confirmation (default false) to enable confirmation emails [wisq] + +== Version 1.9.4 (Jan 5, 2011) + +* Update Garanti gateway to integrate with new API [Selem Delul] + +== Version 1.9.3 (December 17, 2010) + +* Fix BBS Netaxept to change transaction type from C (for MOTO: mail order telephone order) to M (for credit card orders) [Soleone] +* Fix Iridium and ePay to work with any object that responds to credit card methods not only ActiveMerchant::CreditCard objects + +== Version 1.9.2 (December 9, 2010) + +* Add support for PayPal mobile payments [wisq] +* Add ePay gateway [ePay, Jonathan Rudenberg] +* Allow access to the raw HTTP response [Jonathan Rudenberg] + +== Version 1.9.1 (November 24, 2010) + +* PayPal Express and PayPal Pro: Send JPY currency correctly without decimals [Soleone] +* Netaxept: Make sure password (token) is URL escaped and update remote tests for updated server behavior [Soleone] +* DirecPay: Add support for additional options in Return class and add convenience method to get transaction status update [Soleone] +* Add new alias credit_card.brand for credit_card.type and handle the brand correctly in Netaxept [Soleone] +* Iridium: Do not depend on ExpiryDate class for credit_card [Soleone] +* PayFlow: Use same timeout of 60 seconds in HTTP header and XML for all requests [Soleone] +* PayPal Website Payments Pro CA no longer supports American Express cards [Soleone] +* Updated BIN ranges for Discover to match recent documents [kaunartist] + +== Version 1.9.0 (October 14, 2010) + +* Add support for DirecPay gateway [Soleone] +* Add SagePay Form integration gateway [wisq] +* Allow Return class to include a Notification for gateways that treat the direct response as a notification [wisq] +* Add support for PayboxDirect gateway [Donald Piret] +* Add support for SecureNet gateway [Kal] +* Add support for the Inspire gateway [ryan r. smith] + +== Version 1.8.0 (September 24, 2010) + +* PayPal Express: Add support for billing agreements [Nathaniel Talbott] +* Allow comparing countries [Nathaniel Talbott] +* Iridium: Fix country handling [Nathaniel Talbott] +* Iridium: Fix missing billing address [Nathaniel Talbott] +* Iridium: Do not pass CV2 if not present [Nathaniel Talbott] +* Add Iridium support [Phil Smy] +* Add Netaxept support [Nathaniel Talbott] +* PaymentExpress: Use Card Holder Help Text for the response message [Nathaniel Talbott] +* Sort the country name list [Duff OMelia] + +== Version 1.7.3 (September 14, 2010) + +* Fix SagePay special handling for Japanese YEN currency to not send fractional amounts [Soleone] + +== Version 1.7.2 (August 27, 2010) + +* Update Braintree integration to play nicely with the braintree 2.5.0 gem [Soleone] +* Fix SagePay to not send fractional amounts for Japanese YEN currency [Soleone] + +== Version 1.7.1 (July 28, 2010) + +* Pull in only the necessary components of Active Support. Enables use of ActiveMerchant with Rails 3 [railsjedi] + +== Version 1.7.0 (July 9, 2010) + +* Add support for new Braintree Blue Gateway (using the braintree gem) [Braintree] + +== Version 1.6.0 (July 6, 2010) + +* Add a task rake gateways:hosts to get a list of all outbound hosts and ports [cody] +* Fix test failure in chronopay helper in Ruby 1.9.1 [cody] +* Fix timezone issue in credit card test. [cody] +* Fix failing unit test for Garanti gateway [cody] +* Fix failing CyberSource remote test [Patrick Joyce] +* Support for Garanti Sanal Pos: Turkish bank and billing gateway [Selem Delul] +* Add deprecation note for Money objects to Bogus gateway [Soleone] +* Updated test URL for Merchant eSolutions and added valid remote test credentials [Soleone] +* Add new error class for SSL certificate problems in connection class [Soleone] +* Update valid_month and valid_expiry_year to coerce string arguments to integers [cody] +* Add support for displaying credit cards with PayPal Express. Use the :allow_guest_checkout => true option when setting up the transaction [Edward Ocampo-Gooding] +* Use card_brand method for checking for checks in Sage and Beanstream [cody] +* Add JCB and Diners Club to LinkPoint [Soleone] + +== Version 1.5.1 (February 14, 2010) + +* Cleanup Rakefile, add gemspec and prepare for 1.5.1 release [cody] +* Update copyright dates [cody] +* Work around SkipJack bug by reversing the order of the query params [Soleone] +* Fix uppercase character in autoload of 2Checkout's Notification class [Edward Ocampo-Gooding] +* Detect language used in Chronopay integration based on billing address country [Soleone] +* Better handle international addresses in BeanstreamGateway [Soleone] + +== Version 1.5.0 (February 2, 2010) + +* Fix Gestpay notification to avoid Ruby 1.9 warnings [cody] +* Fix Chronopay notification time parsing for Ruby 1.9 [Joe Van Dyk] +* Set default currency of Braintree to USD [cody] +* Fix QuickPay helper for Ruby 1.9 compat [cody] +* Use String#each_line instead of collect in PaySecureGateway for Ruby 1.9 compat [cody] +* Use String#each_line instead of to_a in SagePayGateway for Ruby 1.9 compat [cody] +* Don't return an array when finding the country code. Fixes issue with Ruby 1.9 [cody] +* Fix custom assertions for Ruby 1.9 [cody] +* Deprecate Money objects [cody] +* Update JCB rejex to catch all valid PANs [pjhyett] +* Remove old TransaXGateway constant [cody] +* Remove old ProtxGateway constant [cody] +* Remove old BrainTree constant [cody] +* Remove AuthorizedNet constant [cody] +* SecurePay changed their delimeter from % to ,. Update gateway to handle changes [Soleone] +* Fix documentation typo in base.rb [mig-hub] +* Add capture test to Linkpoint [Dusty Doris] +* Fix bug in Linkpoint with ternary operator and Ruby 1.9.1 [Dusty Doris] +* Add currency and processor options to Braintree gateway [cbillen] +* Unify API to always look for billing_address/address hash inside of options [stopdropandrew] +* Fix bug with Modern Payments Gateway where failure authorizations appeared to be successful [cody] +* Fix Modern Payments Gateway [cody] +* Use basic SkipJack host for all non-authorization transactions. Fix status method [cody] +* Strip non alpha numeric chars out of MerchantWare order number [cody] +* Parse complete response of Authorize.net CIM gateway [Patrick Joyce] +* Update to PayPal Sandbox URL for testing Payflow Pro Express Checkout. See Express Checkout for Payflow Pro guide [cody] +* Provide a C_STATE value of "Outside United States" for SageGateway when processing international customers [cody] +* PayPal Website Payments Pro Canada supports Amex [cody] +* Add line item support for LinkpointGateway. [Tony Primerano] +* Add support for SallieMae gateway [iamjwc] +* Add support for the JetPay gateway [Phil Ripperger, Peter Williams, cody] +* Add support for advanced SkipJack processors. Pass :advanced => true when constructing gateway [cody] +* Support test option in AuthorizeNetCimGateway [Tim] +* Improve Ogone error messages [cody] +* Add support for :test => true option to OgoneGateway [cody] +* Bump PayPal Version to 59.0 [cody] +* Add amex support to eWay gateway [cody] +* Change Payflow header X-VPS-Timeout -> X-VPS-Client-Timeout [cody] +* Fix typo preventing OgoneGateway from working in production [Nicolas Jacobeus] +* Add support for the Elavon MyVirtualMerchant gateway [jstorimer] +* Fix recurring transactions in Ogone gateway [cody] +* Fix money formatting for Ogone gateway [cody] +* Tweak Ogone gateway to use ActiveMerchant conventions for reference transactions [cody, jstorimer] +* Add support for the Ogone DirectLink payment gateway [Nicolas Jacobeus] +* Add support for the Antigua based FirstPay payment gateway [Phil R] +* Add support for PayPal reference transactions [kevin, John, Rahsun McAfee] +* Add support for the MerchantWARE payment gateway [cody] +* Rename Protx to SagePay [jstorimer] +* Allow test mode for eWay gateway [Duff OMelia] +* Don't use Time.parse for the ExpiryDate [cody] +* Add support for CVV code to Authorize.net CIM [Guy Naor] +* Add shipping address to Authorize.net [Eric Tarn] +* Don't setup the logger by default [cody] +* Refactor ActiveMerchant::Connection out of the PostsData module. Add support for logging and wiredumping requests [cody] +* Assume a valid load path when running tests [cody] +* Use SHIPTOSTREET2 element instead of STREET2 element for Payflow Express Uk address [cody] +* Clean up the test helper [cody] +* Fix DataCash unit test that was making a remote call [cody] +* Don't check Request#test? for remote PaymentExpress tests because their test environment has changed [cody] +* Update Instapay gateway to support capture and add address, order, and invoice fields. Add support for CVV and AVS response [cody] +* Add support for Instapay gateway [brahma] +* Cleanup PaymentExpress reference purchases and turn on AVS [cody] +* Add reference purchases and authorizations to PaymentExpress [mocra] +* Add support for Merchant e-Solutions Gateway [Zac Williams, Robby Russell] +* Fix Braintree unit test [cody] +* Add support for checks to SmartPs gateways [jvoohris] +* Extract SmartPs for Braintree and Transax [mmangino] +* Ruby 1.9 compatibility [bschwartz] +* Update Payflow Express to handle Street2 element [James MacAulay] +* Fix typo in Protx DeliveryState field [cody] +* Ignore Wirecard state unless it is 2 characters [Cody] +* Update Wirecard to make country and state processing more robust [Soleone] +* Update ProTX to use the latest v2.23 protocol [Tekin] + +== Version 1.4.2 (April 24, 2009) + +* Fix typo in Authorize.net CIM [infused] +* Add missing ISO countries [Edward Ocampo-Gooding] +* Add support for Guernsey to country.rb [cody] +* Add American Express to the MonerisGateway [cody] +* Use :words_connector instead of connector in RequiresParameters [cody] +* Fixed CreditCard not validating start_month and start_year when set as string [Tekin] +* Update PostsData to support get requests [cody] +* Fix broken Quickpay remote test [cody] +* Update Quickpay gateway to v3. Add support for offsite integration for Danish Dankort cards [Lars Pind] +* Use default partner id when passed in :partner is blank with PayflowGateway [cody] +* Remove PayflowGateway.certification_id [cody] +* Set Response#test? to true in TrustCommerce gateway when using the demo account in production [cody] +* Correctly set Sage.supported_countries [cody] +* Add BogusGateway#void [Donald Ball] +* Fix PSL gateway capturing [cody] +* Fix failed Visa debit purchases with PSL gateway start date info is present [cody] +* Support personal fixtures file on Windows [cody] +* Clearer variable naming for BraintreeGateway#authorize [Jonathan S. Katz] +* Fix brittle Authorize.net tests [cody] +* Add support for Authorize.net duplicate window [Seamus Abshere] +* Return transaction id for PayPal refunds [jxtps435] +* Allow storage of e-checks with BraintreeGateway [jimiray] +* Add test URL to PayJunction gateway [boomtowndesigngroup] +* More robust parsing for Wirecard gateway [Soleone] +* Pass the issue number to CardStream verbatim and update test card numbers [Soleone] + +== Version 1.4.1 (December 9, 2008) + +* Update CardStream URL. Note that you will also need to update your login id. [cody] + +== Version 1.4.0 (November 27, 2008) + +* Return failed authorization when SkipJack purchase fails [Tron, cody] +* Update README [cody] +* Add metadata to Authorize.net CIM gateway [cody] +* Make ActionViewHelper compatible with changes to concat method in ActionPack [cody] +* Remove PayPal and Payflow Name-Value gateways. PayPal is no longer terminating the Payflow XML API. [cody] +* Don't directly use the inflector in the action view helper [cody] +* Work around Rails Inflector change [cody] +* Add configurable timeouts to PostsData [Michael Koziarski] +* Add valid_sender? method to gateway integrations [Soleone] +* Fix PayPal error parsing [cody] +* Fix MIT-LICENSE [cody] +* Add a payment gateway for Website Payments Pro Canada [cody] +* Fix shipping amount option in Sage gateway [Darrick Wiebe] +* Improved message and error message handling [Soleone] +* Get Wirecard working in the Live environment [Soleone] +* Remove dead code in PayPal Common API files [cody] +* Use the PayPal short error message if the long message is empty [cody] +* Fix unit tests when being run by Cruise Control [cody] +* Add support for PayPal Fraud Review Response [cody] +* Add testing support for German Wirecard Gateway [Soleone] +* Specify required version of ActiveSupport [cody] +* Make ssl_strict a superclass_delegating_accessor so the entire application's validation of SSL certs can be disabled in the event of certificate problem. [cody] +* Make Gateway.application_id a superclass_delegating_accessor so it can be set from outside the subclass definition [cody] +* Add Discover to the list of supported card types for Braintree [cody] +* Add support for Modern Payments gateway [Jeremy Nicoll, cody] +* Add support for EFT/ACH and Interac Online to the BeanstreamGateway [cody] +* Document the SageGateway [cody] +* Add support for echecks with SageGateway. [cody] +* Handle all successful SecurePay AU response codes [cody] +* Get SageGateway working with real test account. Improve test suite. [cody] +* Unify TrustCommerce, Payment Express, and Braintree CC storage [benjamin.curtis] +* Update to use new Payflow Pro URLs [cody] +* Fix missing Content-Type header for Ruby 1.8.4 [cody] +* Fix Authorize.Net CIM response.message [patrick.t.joyce] +* Add JCB and Diners Club as supported cards to SageGateway [cody] +* Add CA country code to Sage gateway's supported countries [cody] +* Add support for Sage Payment Solutions gateway [cody] +* Add test mode detection to Beanstream [cody] +* Add support for Beanstream payment gateway [xiaobozz] +* Add support for PayPal NV Pair API. Will be used to replace the usage of the PayPal SOAP API in ActiveMerchant in the future [Greg Furmanek, cody] +* Protx does support UK Maestro [cody] +* Add tests for length of UK Maestro cards [cody] +* Return all the error messages and codes from paypal responses [cody] +* Fail hard when attempting to capture without a credit card with NetRegistry [cody] +* Add support for the order fields to the create_customer_profile_transaction in Authorize.net CIM. [Patrick T. Joyce] +* Strip invalid characters and limit lengths of Protx customer data [Simon Russell] +* Fix empty start or end dates in Protx [Simon Russell] +* Add support for Authorize.net CIM [Patrick T. Joyce, Ian Lotinsky] +* Add option to skip order review to all PayPal Express gateways [garret.alfert, cody] +* Add capturing partial amounts, fix issue number formatting, fix authorization string when nil values returned, fix parsing of multiple '=' characters, simplify message_from [Simon Russell] +* Fix StartDate in ProtxGatewy [cody] +* Add support for refunds and continuous authority references to DataCashGateway [joel.chippindale] +* Fix gross in HiTrust notification. Don't use Money object in Verifi gateway [cody] +* Initial implementation of Payflow Name-Value API [Greg Furmanek] +* Add support for CyberSource credits [mjuneja] + +== Version 1.3.2 (February 24, 2008) + +* Actually fix the bug by adding extdata element to Payflow Requests [cody] +* Fix bug with adding name to Payflow requests [cody] +* Gateways will now look for CreditCard#brand before looking for CreditCard#type [cody] +* Make before_validate in CreditCard more clear [keith_du...@mac.com, cody] +* Don't send full Australian state names to PayPal [cody] +* Return last_digits that are less than 4 characters long [cody] +* Fix Bug with Authorize.Net ARB Remote Test [patrick.t.joyce] +* Add support for forcing test mode on Secure Pay AU gateway [cody] +* Update Secure Pay Au to meet specs for MessageInfo elements [cody] +* Add support for the Australian Secure Pay payment gateway [cody] +* Allow LinkPoint cancellations for recurring billing. [yanagimoto.shin] +* Add support for Åland Islands to the country list [cody] + +== Version 1.3.1 (January 28, 2008) + +* Rename BrainTreeGateway to BraintreeGateway, but keep alias to old naming for backwards compatibility [cody] + +== Version 1.3.0 (January 28, 2008) + +* Remove attr_readers for url and response from Gateway [cody] +* Remove @url from EfsnetGateway [cody] +* Remove @response instance variable in QuickpayGateway. [cody] +* Remove @response instance variable in PsigateGateway. Don't use billing address for shipping [cody] +* Remove @response instance variable in PaypalGateway. Don't use billing address for shipping. [cody] +* Remove @response instance variable in PayflowGateway [cody] +* Remove @response instance variable in MonerisGateway [cody] +* Remove @response instance variable and don't use billing address for shipping address in LinkpointGateway [cody] +* Remove @response instance variable from ExactGateway [cody] +* Remove @response instance variable from EwayGateway [cody] +* Remove @response instance variable from EfsnetGateway [cody] +* Remove @response instance variable from DataCashGateway [cody] +* Don't use billing_address for shipping_address in CyberSourceGateway [cody] +* Remove @response instance variable from CardStreamGateway [cody] +* Remove @response instance variable from BrainTreeGateway [cody] +* Remove unused deal_with_cc method from BogusGateway [cody] +* Remove test_result_from_cc_number completely from ActiveMerchant [cody] +* Don't use billing_address for shipping_address in Realex [cody] +* Update Realex to add support for cvv data. remove test_result_from_cc_number. [cody] +* Update Protx to add support for avs and cvv data. remove test_result_from_cc_number. [cody] +* Include ActiveMerchant::Utils module in test_helper and use generate_unique_id from the module instead of generate_order_id. [cody] +* Update SecurePay tests to check for avs and cvv data. [cody] +* Update SkipJack to add support for avs and cvv data. remove test_result_from_cc_number. [cody] +* Move generate_unique_id to its own module [cody] +* Update Viaklix to add support for avs and cvv data. remove test_result_from_cc_number. Truncate fields to avoid failure [cody] +* Update PSL Card Gateway to add support for avs and cvv data. remove test_result_from_cc_number. [cody] +* Update PlugNPayGateway to support avs and cvv data. Remove test_result_from_cc_number. [cody] +* Update PaymentExpressGateway to remove test_result_from_cc_number. [cody] +* Update PaySecure to remove test_result_from_cc_number. [cody] +* Update NetbillingGateway to support avs and cvv data. Remove test_result_from_cc_number. [cody] +* Replace all usage of :address with :billing_address in test cases [cody] +* Remove sensitive data from NetRegistryGateway responses. Refactor gateway and tests. Remove test_result_from_cc_number. [cody] +* Update VerifiGateway to support avs and cvv data. Remove test_result_from_cc_number. [cody] +* Small refactoring of UsaEpayGateway [cody] +* Update UsaEpayGateway to support avs and cvv data. Remove test_result_from_cc_number. [cody] +* Update TrustCommerce docs now that the gateway falls back to SSL post when tclink isn't available [cody] +* Change ARB to use correct :address1 key for addresses [cody] +* No need for specialized recurring response for Authorize.net recurring billing [cody] +* Update TransFirst to support avs and cvv data. Remove test_result_from_cc_number. [cody] +* Maintain backwards compatibility with :address option for now in the Payflow gateways [cody] +* Remove test_result_from_cc_number from SecurePayTech. Improve unit test coverage [cody] +* Fix email option in PayflowGateway. Remove support for :address option. :billing_address and :shipping_address must now be passed in separately. [cody] +* Make Bogus gateway's credit() method behave like capture [cody] +* Add update and delete methods to update and delete records stored in the vault. [benjamin.curtis] +* Add support for recurring_inquiry() to the PayflowGateway [dave.my...@contentfree.com] +* Add support for Authorize.net Automated Recurring Billing (ARB) [vkurnavenkov, forestcarlisle, ianlotin...@hotmail.com, patrick.t.joyce] +* Fix laser card regex [ladislav.martincik] +* Cleanup whitepace in README [patrick.t.joyce] +* Update ExactGateway to support avs and cvv data. Remove test_result_from_cc_number. [cody] +* Remove test_result_from_cc_number from eWay gateway. [cody] +* Remove duplicate attr_reader definitions from all gateways [cody] +* Remove useless tests raising Error [cody] +* Update gateway templates [cody] +* Fix Authorize.net test where authorize() was being called instead of purchase(). Perform some cleanup of the tests [ianlotin...@hotmail.com, cody] +* Improve Authorize.net documentation based on the DataCashGateway docs [patrick.t.joyce] +* Update EfsnetGateway to support avs and cvv data. Remove test_result_from_cc_number. [cody] +* Remove test_result_from_cc_number from DataCash. Improve unit test coverage [cody] +* Update CyberSourceGateway to support avs and cvv results. Remove test_result_from_cc_number. [cody] +* Remove match information from CVVResult [cody] +* Remove Response#card_data. The application has access to the information anyway [cody] +* Return the last 4 digits of the card number from the Response instead of the masked number [cody] +* Actually use the shipping address in TrustCommerce [cody] +* Update TrustCommerceGateway to support avs and cvv results. Remove test_result_from_cc_number. Automatically fallback to SSL POST if the TCLink library is not available. Add additional customer information to the requests. [cody] +* Fix remote CardStreamGateway tests [cody] +* Map merchant AVS codes to street and postal match codes [cody] +* Update CardStreamGateway to support avs and cvv data [cody] +* Remove merchant_data hash. Add additional CVV codes [cody] +* Update QuickpayGateway to support merchant_data hash. Remove test_result_from_cc_number. [cody] +* Update LinkpointGateway to support merchant_data hash. Remove test_result_from_cc_number. [cody] +* Update PsigateGateway to support merchant_data hash. Remove test_result_from_cc_number. [cody] +* Update MonerisGateway to support merchant_data hash. Remove test_result_from_cc_number. [cody] +* Remove AVS Message and CVV2 Message from params hash in Authorize.net [cody] +* Update BrainTreeGateway to support merchant_data hash [cody] +* Update PaypalGateway to support merchant_data hash [cody] +* Update Payflow to support merchant_data hash [cody] +* Add card data to PayJunction response. PayJunction does not return the CVV or AVS result codes. Remote test_result_from_cc_number from PayJunction. [cody] +* Rename CCVResult to CVVResult to be more aligned with ActiveMerchant's usage of the term verification value [cody] +* Remove test_result_from_cc_number from Authorize.net in favour of mocking [cody] +* Add merchant_data hash, which contains all of the card_data, avs_result, and ccv_result. [cody] +* Add CCVResult for the Card Code Verification Result. Update Authorize.net to use the new class [cody] +* Rename AVSResult#match_type AVSResult#match [cody] +* Rename AVS::Result class to AVSResult [cody] +* Convert Authorize.net gateway to use the new AVS module [cody] +* Add AVS data to the Response object [cody] +* Fix credentials for remote Authorize.net TEST MODE test [cody] +* Add AVS module and AVS::Result class [cody] +* Update base gateway class RDOC [cody] +* Update the README with the latest list of supported gateways. Update the example in the README to include the verification value, which is now required by the credit card object by default. [cody] +* Handle the return from 2Checkout [cody] +* Automatically determine the credit card type when a type is not provided [cody] +* Revert to initial implementation of LUHN algorithm because it all fits in one simple method [cody] +* Remove unused api_cert_chain.crt file [cody] +* Update PaypalGateway, and PaypalExpressGateway to send requests to the correct endpoints when using API signatures [cody] +* Successful return code for HiTRUST is actually 00 [cody] +* Make ActiveMerchant::Billing::Error a subclass of ActiveMerchant::ActiveMerchantError [cody] +* Handle the return from the offsite payment gateways [cody] +* Default HiTRUST order description to "Store purchase" [cody] +* Fix HiTRUST field names [cody] +* Add support for passing in the locale code [georg.fr...@meandevel.com] +* Add support for the Offsite payment gateway HiTRUST [cody] +* Accept SuccessWithWarning as success [cody] +* Add a link to the LinkPoint staging server docs in remote_linkpoint_test.rb [cody] +* Update Discover regex [cody] +* Match full pan range of Maestro cards from 12 - 19 digits in length [cody] +* Fix errors on base of CreditCard [josh.bassett] +* Update product to use Rubigen instead of stolen Rails generator [cody] +* Mimic directory structure of unit tests in remote tests [cody] +* Restructure the location of the remote tests [cody] +* Ensure DataCash order_id is limited to 30 characters [cody] +* Return the pretty messages from PayJunction based on the return code [cody] +* make CreditCard.require_verification_value = true the default [cody] +* Use existing credit_card helper in credit card tests [cody] +* Return the authrorization number of the original transaction in the SkipJack gateway [cody] +* Update format of line items given to the gateway. Cleanup and uncomment unit tests [cody] +* Add support for the SkipJack gateway [Bill Bereza, cody] +* Make the bogus gateway easier to test by moving messages into constants [cody] +* Add retry logic when connection has been refused for all gateways. Enable safe retries of all connection failures with the PayflowGateway, as it has a unique request header. [cody] +* Catch Timeout::Error when posting data [cody] +* Change order of loading ActionPack for tests since assert_success defined in ActionController::Assertions::DeprecatedAssertions inteferes with ActiveMerchant's definition [cody] +* Catch Errno::ETIMEDOUT and extend open and read timeouts to 60 seconds [cody] +* Add address2 to the billing address of Viaklix transactions [cody] +* Improve Psigate generic error message [cody] +* Fix small errors in Psigate documentation [cody] +* Add Response#fraud_review? query method to the response. Allows gateways to indicate that a payment is pending review by the fraud service [cody] +* Handle Errno::ECONNRESET when posting data [cody] +* Fix broken USA ePay URL [cody] +* Update RequiresParameters to support HashWithIndifferentAccess [cody] +* Add support for SecurePayTech payment gateway [Jasper Bryant-Greene] +* Detect when test credentials are being used with PayJunction [cody] +* Update documentation about TrustCommerce void [cody] +* Add void to TrustCommerce [jesse.c.scott] +* Add support for echecks to the BrainTree gateway [Jeremy Voorhis] +* Fix before_validate and validate methods in CreditCard [rick.denatale] +* Add support for Netbilling payment gateway [cody] +* Pass in N/A for unknown states when a country is present in PaypalGateway [cody] +* Strip non alpha chars from order_id in Payflow gateway, as Paymentech Tampa can't handle them [cody] +* Add support for the PaySecure payment gateway [cody] +* Add support for descriptions in Authorize.net credits [shiva.kaul] +* Great cleanup and improvement of CreditCard code, tests, and docs [James Herdman] + +== Version 1.2.1 + +* Fix remote PayPal tests [cody] + +== Version 1.2.0 + +* Update Linkpoint tests to remove useless pem file [cody] +* Use symbols for CreditCard error messages, since errors have indifferent access [cody] +* Improve CreditCard error messages [George Ogata] +* Change deny to assert_false, and deny_success to assert_failure. Remove Gateway.gateway, as it is available from Base [cody] +* Improve documentation, and test coverage [James Herdman] +* Refactor MonerisGateway, improve test coverage and documentation [James Herdman] +* Add support for crediting to Moneris [James Herdman] +* Send state N/A in Payflow when the state is blank. Fixes UK PayPal Express payments when not providing a state [cody] +* Load remote test credentials from a fixtures file. ActiveMerchant will look for a custom file ~/.active_merchant/fixtures.yml. If the file exists it will be loaded instead of the default fixtures provided by ActiveMerchant. This makes development easier, and removes the risk of committing non-public test account credentials to subversion. [cody] +* Add support for password protected pem files [cody] +* Add support for Concord Efsnet payment gateway [snacktime] +* Fix dependency loading for gateways that are subclasses [cody] +* Add Braintree payment gateway [Michael J. Mangino] +* Add support for PayPal API signatures [Benjamin Curtis, cody] +* Add payment gateway Viaklix [Sal Scotto, cody] +* Add Australian payment gateway NetRegistry [George Ogata] +* Take order email from the options hash instead of the address for CyberSource [cody] +* Use an array for LineItems when calculating tax in CyberSource gateway [cody] +* Add CyberSource gateway [Matt Margolis] +* Sanitize Protx order id [cody] +* Fix support for electron in Protx [cody] +* Add support for Protx [shiftx, cody] +* Use undef_method with a single argument in SecurePay to prevent JRuby from choking on it. [jonathan.l.bartlett] +* Default address_override to 0 for PayPal Website Payments Standard payments. [cody] +* Enhance credit card error messages [manfred] +* Use HashWithIndifferentAccess for CreditCard for compatibility with Rails applications [michael.j.mangino] +* Fix nil exception when no response reason text is found in Authorize.net [cody] +* Add support for PayJunction [Matt Sanders] +* Change billing_address to shipping_address in PayPal Integration helper, as billing_address was incorrect. Addresses passed to billing_address for the PayPal helper will no longer be added to the form. This will break existing code, as the address will not be passed. +* Remove switch patterns from card detection that were eliminated on July 1, 2007 [cody] +* Format the issue number in Payflow requests to always be 2 digits [cody] +* Move application_id to Gateway and Helper class respectively [cody] +* Improve TrustCommerce documentation [cody] +* Add credit to Payflow [cody] +* Add support for the Plug 'N Pay gateway [ryan.norbauer, cody] +* Add support for ItemTotal, Shipping, Handling, and Tax amounts in the PayPal Express and PayPal gateways [baldwindavid, cody] +* Add page customization options to the PaypalExpress, and PayflowExpress gateways [ cpjolicoeur, cody] +* Add Verifi gateway [Paul Hepworth] +* Add a PayflowResponse object with a profile_id accessor method. Return the correct authorization number on recurring actions [cody] +* Add support for an initial transaction with recurring payments [findchris, cody] +* Add support for email receipts to recurring Payflow Payments [Rick Olson] +* Ensure the ButtonSource isn't too long [cody] +* Add ButtonSource to Paypal and PaypalExpress gateways [cody] +* Rename application to application_id and place it on Base, so it can be set once and forgotten about [cody] +* Add ButtonSource field to PayflowExpress gateway [cody] +* Add a field for the bn to the PayPal helper [cody] +* Add remote secure pay test and correctly define test? [cody] +* Undefine unsupported methods from SecurePay [cody] +* Enhance the TransFirst error message for declined transactions [cody] +* Add initial support for TransFirst gateway [cody] +* Deprecate certification_id in Payflow gateways [cody] +* Work around required PayPal state fields for countries that don't require states [cody] +* Add metadata to SecurePay gateway [cody] +* Add initial support for the SecurePay gateway using the Authorize.net translator [cody] +* Add the homepage_url and display_name accessors to each gateway [cody] +* Remove Money dependency from main gateways. Cleanup tests. Add supported_countries class accessor which returns an array of 2 digit iso country codes for which countries the gateway supports accounts in [cody] +* Add American Express card to Psigate [cody] +* Send N/A to PayPal in the PayPal Helper when we don't know the UK county [cody] +* Actually pass the amount of the capture through to Payflow [cody] +* Update ExactGateway test and test mode [cody] +* Remove unused method in PslCardGateway [cody] +* Add updated credit card tests [cody] +* Update and test PslCardGateway [cody] +* Add Laser card type [cody] +* Update Nochex documentation [cody] +* Sanitize the Realex order_id [cody] +* Add support for Irish Realex payment gateway [John Ward, cody] +* Move credit_card helper method to the test_helper [cody] +* Update PayflowExpressResponse to match the interface of the PayflowExpressResponse. Add :no_shipping and :address_override options to PayflowExpress [cody] +* Add a currency option to the Payflow and Paypal gateways [cody] +* PaypalExpress should use the shipping address, not the billing address [cody] +* Allow overriding the user with Payflow so that a vendor and user can be provided when making requests [cody] +* PayPal DirectPayment API requires a UK County to be sent as the state or province. Return N/A as the state when one isn't provided to ensure that PayPal doesn't reject the payment [cody] +* Add ability to perform reference transactions with Payflow [Al Evans, cody] +* Enhance recurring Payflow tests and recurring_inquiry [Al Evans] +* Add recurring payments to Payflow [Rick Olson] +* Improve the error message generated by requires! [cody] +* Update credit card regular expressions and update Quickpay gateway with tests for new cards [cody] +* Add support for token based payments to PaymentExpress [Nik Wakelin] +* Refactor default_currency to the base gateway class [cody] +* Clean unsupported characters from the Quickpay ordernum [cody] +* Call the :sale and :authorization in QuickpayGateway [cody] +* Add Danish gateway Quickpay [cody] +* Remove redundant hash brackets from generator template [cody] +* Add additional options to the PayPal Website Payments Standard Helper [Rick Olson] +* Move generate_unique_id method to Gateway class so other gateways can also use it [cody] +* Allow notification name / value pairs to have a . in the name like checkout.x = 400 [cody] +* Fix PaypalExpressGateway#purchase to have the same method signature as other gateways [cody] +* Cargo cult off the rails unique id generator instead of UUID library [cody] +* Add uuid-1.0.3 for generating random request UUIDs [cody] +* Remove mock_methods and http mock from the library [cody] +* PaypalExpress cannot setup a payment for 0 dollars. If the amount is zero then setup a payment for $1. [cody] +* Small changes to PslCard gateway [cody] +* Fix Money dependency with PslCard gateway [cody] +* Add PslCard payment gateway [MoneySpyder http://moneyspyder.co.uk] +* Scrub the card number, expiry, and CVV code from the response [cody] +* Use test? query for checking test mode [cody] +* Add support for the E-xact Payment Gateway [James Edward Gray II, cody] +* Fix partially broken method of dealing with phone numbers in the PayPal Helper [cody] +* Update remote tests for PaymentExpress [cody] +* Add Content-Type header to PaymentExpress post [cody] +* Use DECLINED as the message for declined transactions in the PaymentExpress remote tests [cody] +* Add JCB as a supported card type for the PaymentExpressGateway [cody] +* Rename DpsGateway to PaymentExpressGateway [cody] +* Add DPS Payment Express gateway (NZ) [dgjones, cody] +* Remove duplicate and incorrect expdate method from Authorize.net [cody] +* Allow authorization and purchase using a billing_id retrieved from TrustCommerce citadel [jesse.c.scott] +* Don't return a frozen string from CreditCard.type? [cody] +* Update remote Psigate test to ensure using a verification value doesn't break anything [cody] +* Update remote Moneris test to ensure using a verification value doesn't break anything [cody] +* Fix Solo issue number with CardStream gateway and improve test coverage [cody] +* Add CardStream gateway [Jonah Fox, Thomas Nichols, cody] +* Verify Peer in PayPal notifications and add account method [cody] + +== Version 1.1.0 + +* Add unique_id option to PayPal mass payments [Haig] +* Fix expiry date in USA ePay [cody] +* Fix PayPal Payments Pro UK with Switch & Solo cards [cody] +* Add reauthorization to PaypalGateway and PaypalExpressGateway [dorrenchen] +* Update DataCash tests and format merchant reference number to meet DataCash's requirements [MoneySpyder, cody] +* Add Datacash gateway [MoneySpyder, cody] +* VERIFY_PEER on all SSL requests [cody] +* Add support for 2Checkout [cody] + +== Version 1.0.3 + +* Add support for PayPal mass payments to the PaypalGateway and the PaypalExpressGateway [Brandon Keepers] +* Add a credit method to Authorize.net [cody] + +== Version 1.0.2 + +* Add support for OrderDescription, Payer, and InvoiceID fields in PaypalGateway [cody] + +== Version 1.0.1 + +* Add support for crediting to PayPal [cody, Haig] + +== Version 1.0.0 + +* Add discover to list of supported card types for Authorize.net +* Fix Psigate crediting [sean.alien8@gmail.com] +* Fix dependency loading of tests +* Add methods for storing credit cards to the Bogus gateway [Jim Kane] +* Fix bugs in expiration dates. [Jim Kane] +* Fixed bugs related to authorized.net [Rick Olson] +* Linkpoint is now a full featured backend for active merchant [Ryan Heneise] +* Added linkpoint support [Ryan Heneise] +* Added trust commerce gateway [Hans Friedrich] +* Removed shipping stuff until there is time to implement it properly +* The library now rejects money amounts which are not either cents as integer or a Money object +* Moneris now uses the same layout as the authorized.net plugin +* Added authorized.net +* Changed default to :test mode. Set to production with ActiveMerchant::Billing::Base.gateway_mode = :production +* More refactoring +* Refactored a bit so that there is space for billing and shipping area. None of the shipping aids are fleshed out yet. Needs more work. +* Added Moneris support +* Credit card in memory object resembling a AR object +* Credit card validation methods as static methods of the credit card object + +== PlanetArgon fork for integrating Merchant eSolutions gateway + diff --git a/vendor/gems/activemerchant-1.33.0/CONTRIBUTORS b/vendor/gems/activemerchant-1.33.0/CONTRIBUTORS new file mode 100644 index 000000000..58bd51f33 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/CONTRIBUTORS @@ -0,0 +1,402 @@ +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 + +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) diff --git a/vendor/gems/activemerchant-1.33.0/MIT-LICENSE b/vendor/gems/activemerchant-1.33.0/MIT-LICENSE new file mode 100644 index 000000000..783e60724 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/MIT-LICENSE @@ -0,0 +1,20 @@ +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. diff --git a/vendor/gems/activemerchant-1.33.0/README.md b/vendor/gems/activemerchant-1.33.0/README.md new file mode 100644 index 000000000..67e157567 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/README.md @@ -0,0 +1,221 @@ +# 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) diff --git a/vendor/gems/activemerchant-1.33.0/gem-public_cert.pem b/vendor/gems/activemerchant-1.33.0/gem-public_cert.pem new file mode 100644 index 000000000..c2588d5c2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/gem-public_cert.pem @@ -0,0 +1,20 @@ +-----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----- diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant.rb new file mode 100644 index 000000000..aff818bcb --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant.rb @@ -0,0 +1,63 @@ +#-- +# 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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing.rb new file mode 100644 index 000000000..ee144b1f3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing.rb @@ -0,0 +1,9 @@ +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' \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/avs_result.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/avs_result.rb new file mode 100644 index 000000000..527c3efa1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/avs_result.rb @@ -0,0 +1,98 @@ +#!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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/base.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/base.rb new file mode 100644 index 000000000..7ee06986b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/base.rb @@ -0,0 +1,56 @@ +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 + # * bogus: BogusGateway - Does nothing (for testing) + # * moneris: MonerisGateway + # * authorize_net: AuthorizeNetGateway + # * trust_commerce: 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 + # * bogus: Bogus - Does nothing (for testing) + # * chronopay: Chronopay + # * paypal: 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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/check.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/check.rb new file mode 100644 index 000000000..79e99d2ee --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/check.rb @@ -0,0 +1,69 @@ +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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card.rb new file mode 100644 index 000000000..953d79849 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card.rb @@ -0,0 +1,278 @@ +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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_formatting.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_formatting.rb new file mode 100644 index 000000000..126207b14 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_formatting.rb @@ -0,0 +1,21 @@ +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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_methods.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_methods.rb new file mode 100644 index 000000000..fef4bf16c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/credit_card_methods.rb @@ -0,0 +1,143 @@ +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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/cvv_result.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/cvv_result.rb new file mode 100644 index 000000000..edd2d5086 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/cvv_result.rb @@ -0,0 +1,38 @@ +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 \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/expiry_date.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/expiry_date.rb new file mode 100644 index 000000000..4ab1fb00a --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/expiry_date.rb @@ -0,0 +1,34 @@ +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 \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateway.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateway.rb new file mode 100644 index 000000000..976e175cb --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateway.rb @@ -0,0 +1,177 @@ +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: + # + # * purchase(money, creditcard, options = {}) + # * authorize(money, creditcard, options = {}) + # * capture(money, authorization, options = {}) + # * void(identification, options = {}) + # * credit(money, identification, options = {}) + # + # Some gateways include features for recurring billing + # + # * recurring(money, creditcard, options = {}) + # + # Some gateways also support features for storing credit cards: + # + # * store(creditcard, options = {}) + # * unstore(identification, options = {}) + # + # === Gateway Options + # The options hash consists of the following options: + # + # * :order_id - The order number + # * :ip - The IP address of the customer making the purchase + # * :customer - The name, customer number, or other information that identifies the customer + # * :invoice - The invoice number + # * :merchant - The name or description of the merchant offering the product + # * :description - A description of the transaction + # * :email - The email address of the customer + # * :currency - 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. + # * :billing_address - A hash containing the billing address of the customer. + # * :shipping_address - A hash containing the shipping address of the customer. + # + # The :billing_address, and :shipping_address hashes can have the following keys: + # + # * :name - The full name of the customer. + # * :company - The company name of the customer. + # * :address1 - The primary street address of the customer. + # * :address2 - Additional line of address information. + # * :city - The city of the customer. + # * :state - 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. + # * :country - 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. + # * :zip - The zip or postal code of the customer. + # * :phone - 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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways.rb new file mode 100644 index 000000000..a9bebd1b8 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways.rb @@ -0,0 +1,17 @@ +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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net.rb new file mode 100644 index 000000000..9ab415a14 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net.rb @@ -0,0 +1,724 @@ +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 + # + # * :login -- The Authorize.Net API Login ID (REQUIRED) + # * :password -- The Authorize.Net Transaction Key. (REQUIRED) + # * :test -- +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 + # + # * money -- The amount to be authorized as an Integer value in cents. + # * paysource -- The CreditCard or Check details for the transaction. + # * options -- 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 + # + # * money -- The amount to be purchased as an Integer value in cents. + # * paysource -- The CreditCard or Check details for the transaction. + # * options -- 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 + # + # * money -- The amount to be captured as an Integer value in cents. + # * authorization -- 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 + # + # * authorization - 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 + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * identification -- The ID of the original transaction against which the refund is being issued. + # * options -- A hash of parameters. + # + # ==== Options + # + # * :card_number -- 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. + # * :first_name -- The first name of the account being refunded. + # * :last_name -- The last name of the account being refunded. + # * :zip -- 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 + # + # * money -- The amount to be charged to the customer at each interval as an Integer value in cents. + # * creditcard -- The CreditCard details for the transaction. + # * options -- A hash of parameters. + # + # ==== Options + # + # * :interval -- A hash containing information about the interval of time between payments. Must + # contain the keys :length and :unit. :unit can be either :months or :days. + # If :unit is :months then :length must be an integer between 1 and 12 inclusive. + # If :unit is :days then :length 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) + # * :duration -- A hash containing keys for the :start_date the subscription begins (also the date the + # initial billing occurs) and the total number of billing :occurences 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 + # + # * options -- A hash of parameters. + # + # ==== Options + # + # * :subscription_id -- A string containing the :subscription_id 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 + # + # * subscription_id -- 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 + # + # * subscription_id -- A string containing the +subscription_id+ of the recurring payment already in place + # for a given credit card. (REQUIRED) + def status_recurring(subscription_id) + request = build_recurring_request(:status, :subscription_id => subscription_id) + recurring_commit(:status, request) + end + + private + + def commit(action, money, parameters) + parameters[:amount] = amount(money) unless action == 'VOID' + + # Only activate the test_request when the :test option is passed in + parameters[:test_request] = @options[:test] ? 'TRUE' : 'FALSE' + + url = test? ? self.test_url : self.live_url + data = ssl_post url, post_data(action, parameters) + + response = parse(data) + response[:action] = action + + message = message_from(response) + + # Return the response. The authorization can be taken out of the transaction_id + # Test Mode on/off is something we have to parse from the response text. + # It usually looks something like this + # + # (TESTMODE) Successful Sale + test_mode = test? || message =~ /TESTMODE/ + + Response.new(success?(response), message, response, + :test => test_mode, + :authorization => response[:transaction_id], + :fraud_review => fraud_review?(response), + :avs_result => { :code => response[:avs_result_code] }, + :cvv_result => response[:card_code] + ) + end + + def success?(response) + response[:response_code] == APPROVED + end + + def fraud_review?(response) + response[:response_code] == FRAUD_REVIEW + end + + def parse(body) + fields = split(body) + + results = { + :response_code => fields[RESPONSE_CODE].to_i, + :response_reason_code => fields[RESPONSE_REASON_CODE], + :response_reason_text => fields[RESPONSE_REASON_TEXT], + :avs_result_code => fields[AVS_RESULT_CODE], + :transaction_id => fields[TRANSACTION_ID], + :card_code => fields[CARD_CODE_RESPONSE_CODE] + } + results + end + + def post_data(action, parameters = {}) + post = {} + + post[:version] = API_VERSION + post[:login] = @options[:login] + post[:tran_key] = @options[:password] + post[:relay_response] = "FALSE" + post[:type] = action + post[:delim_data] = "TRUE" + post[:delim_char] = "," + post[:encap_char] = "$" + post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant" + + request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + request + end + + def add_currency_code(post, money, options) + post[:currency_code] = options[:currency] || currency(money) + end + + def add_invoice(post, options) + post[:invoice_num] = options[:order_id] + post[:description] = options[:description] + end + + def add_creditcard(post, creditcard) + post[:card_num] = creditcard.number + post[:card_code] = creditcard.verification_value if creditcard.verification_value? + post[:exp_date] = expdate(creditcard) + post[:first_name] = creditcard.first_name + post[:last_name] = creditcard.last_name + end + + def add_payment_source(params, source, options={}) + if card_brand(source) == "check" + add_check(params, source, options) + else + add_creditcard(params, source) + end + end + + def add_check(post, check, options) + post[:method] = "ECHECK" + post[:bank_name] = check.bank_name + post[:bank_aba_code] = check.routing_number + post[:bank_acct_num] = check.account_number + post[:bank_acct_type] = check.account_type + post[:echeck_type] = "WEB" + post[:bank_acct_name] = check.name + post[:bank_check_number] = check.number if check.number.present? + post[:recurring_billing] = (options[:recurring] ? "TRUE" : "FALSE") + end + + def add_customer_data(post, options) + if options.has_key? :email + post[:email] = options[:email] + post[:email_customer] = false + end + + if options.has_key? :customer + post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil + end + + if options.has_key? :ip + post[:customer_ip] = options[:ip] + end + end + + # x_duplicate_window won't be sent by default, because sending it changes the response. + # "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent." + # (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf + def add_duplicate_window(post) + unless duplicate_window.nil? + post[:duplicate_window] = duplicate_window + end + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:address] = address[:address1].to_s + post[:company] = address[:company].to_s + post[:phone] = address[:phone].to_s + post[:zip] = address[:zip].to_s + post[:city] = address[:city].to_s + post[:country] = address[:country].to_s + post[:state] = address[:state].blank? ? 'n/a' : address[:state] + end + + if address = options[:shipping_address] + post[:ship_to_first_name] = address[:first_name].to_s + post[:ship_to_last_name] = address[:last_name].to_s + post[:ship_to_address] = address[:address1].to_s + post[:ship_to_company] = address[:company].to_s + post[:ship_to_phone] = address[:phone].to_s + post[:ship_to_zip] = address[:zip].to_s + post[:ship_to_city] = address[:city].to_s + post[:ship_to_country] = address[:country].to_s + post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state] + end + end + + # Make a ruby type out of the response string + def normalize(field) + case field + when "true" then true + when "false" then false + when "" then nil + when "null" then nil + else field + end + end + + def message_from(results) + if results[:response_code] == DECLINED + return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code]) + if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code]) + return AVSResult.messages[ results[:avs_result_code] ] + end + end + + (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '') + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}#{year[-2..-1]}" + end + + def split(response) + response[1..-2].split(/\$,\$/) + end + + # ARB + + # Builds recurring billing request + def build_recurring_request(action, options = {}) + unless RECURRING_ACTIONS.include?(action) + raise StandardError, "Invalid Automated Recurring Billing Action: #{action}" + end + + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.tag!("#{RECURRING_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_ARB_NAMESPACE) do + add_arb_merchant_authentication(xml) + # Merchant-assigned reference ID for the request + xml.tag!('refId', options[:ref_id]) if options[:ref_id] + send("build_arb_#{action}_subscription_request", xml, options) + end + end + + # Contains the merchant’s payment gateway account authentication information + def add_arb_merchant_authentication(xml) + xml.tag!('merchantAuthentication') do + xml.tag!('name', @options[:login]) + xml.tag!('transactionKey', @options[:password]) + end + end + + # Builds body for ARBCreateSubscriptionRequest + def build_arb_create_subscription_request(xml, options) + # Subscription + add_arb_subscription(xml, options) + + xml.target! + end + + # Builds body for ARBUpdateSubscriptionRequest + def build_arb_update_subscription_request(xml, options) + xml.tag!('subscriptionId', options[:subscription_id]) + # Adds Subscription + add_arb_subscription(xml, options) + + xml.target! + end + + # Builds body for ARBCancelSubscriptionRequest + def build_arb_cancel_subscription_request(xml, options) + xml.tag!('subscriptionId', options[:subscription_id]) + + xml.target! + end + + # Builds body for ARBGetSubscriptionStatusRequest + def build_arb_status_subscription_request(xml, options) + xml.tag!('subscriptionId', options[:subscription_id]) + + xml.target! + end + + # Adds subscription information + def add_arb_subscription(xml, options) + xml.tag!('subscription') do + # Merchant-assigned name for the subscription (optional) + xml.tag!('name', options[:subscription_name]) if options[:subscription_name] + # Contains information about the payment schedule + add_arb_payment_schedule(xml, options) + # The amount to be billed to the customer + # for each payment in the subscription + xml.tag!('amount', amount(options[:amount])) if options[:amount] + if trial = options[:trial] + # The amount to be charged for each payment during a trial period (conditional) + xml.tag!('trialAmount', amount(trial[:amount])) if trial[:amount] + end + # Contains either the customer’s credit card + # or bank account payment information + add_arb_payment(xml, options) + # Contains order information (optional) + add_arb_order(xml, options) + # Contains information about the customer + add_arb_customer(xml, options) + # Contains the customer's billing address information + add_arb_address(xml, 'billTo', options[:billing_address]) + # Contains the customer's shipping address information (optional) + add_arb_address(xml, 'shipTo', options[:shipping_address]) + end + end + + # Adds information about the interval of time between payments + def add_arb_interval(xml, options) + interval = options[:interval] + return unless interval + xml.tag!('interval') do + # The measurement of time, in association with the Interval Unit, + # that is used to define the frequency of the billing occurrences + xml.tag!('length', interval[:length]) + # The unit of time, in association with the Interval Length, + # between each billing occurrence + xml.tag!('unit', interval[:unit].to_s) + end + end + + # Adds information about the subscription duration + def add_arb_duration(xml, options) + duration = options[:duration] + return unless duration + # The date the subscription begins + # (also the date the initial billing occurs) + xml.tag!('startDate', duration[:start_date]) if duration[:start_date] + # Number of billing occurrences or payments for the subscription + xml.tag!('totalOccurrences', duration[:occurrences]) if duration[:occurrences] + end + + def add_arb_payment_schedule(xml, options) + return unless options[:interval] || options[:duration] + xml.tag!('paymentSchedule') do + # Contains information about the interval of time between payments + add_arb_interval(xml, options) + add_arb_duration(xml, options) + if trial = options[:trial] + # Number of billing occurrences or payments in the trial period (optional) + xml.tag!('trialOccurrences', trial[:occurrences]) if trial[:occurrences] + end + end + end + + # Adds customer's credit card or bank account payment information + def add_arb_payment(xml, options) + return unless options[:credit_card] || options[:bank_account] + xml.tag!('payment') do + # Contains the customer’s credit card information + add_arb_credit_card(xml, options) + # Contains the customer’s bank account information + add_arb_bank_account(xml, options) + end + end + + # Adds customer’s credit card information + # Note: This element should only be included + # when the payment method is credit card. + def add_arb_credit_card(xml, options) + credit_card = options[:credit_card] + return unless credit_card + xml.tag!('creditCard') do + # The credit card number used for payment of the subscription + xml.tag!('cardNumber', credit_card.number) + # The expiration date of the credit card used for the subscription + xml.tag!('expirationDate', arb_expdate(credit_card)) + end + end + + # Adds customer’s bank account information + # Note: This element should only be included + # when the payment method is bank account. + def add_arb_bank_account(xml, options) + bank_account = options[:bank_account] + return unless bank_account + xml.tag!('bankAccount') do + # The type of bank account used for payment of the subscription + xml.tag!('accountType', bank_account[:account_type]) + # The routing number of the customer’s bank + xml.tag!('routingNumber', bank_account[:routing_number]) + # The bank account number used for payment of the subscription + xml.tag!('accountNumber', bank_account[:account_number]) + # The full name of the individual associated + # with the bank account number + xml.tag!('nameOfAccount', bank_account[:name_of_account]) + # The full name of the individual associated + # with the bank account number (optional) + xml.tag!('bankName', bank_account[:bank_name]) if bank_account[:bank_name] + # The type of electronic check transaction used for the subscription + xml.tag!('echeckType', bank_account[:echeck_type]) + end + end + + # Adds order information (optional) + def add_arb_order(xml, options) + order = options[:order] + return unless order + xml.tag!('order') do + # Merchant-assigned invoice number for the subscription (optional) + xml.tag!('invoiceNumber', order[:invoice_number]) + # Description of the subscription (optional) + xml.tag!('description', order[:description]) + end + end + + # Adds information about the customer + def add_arb_customer(xml, options) + customer = options[:customer] + return unless customer + xml.tag!('customer') do + xml.tag!('type', customer[:type]) if customer[:type] + xml.tag!('id', customer[:id]) if customer[:id] + xml.tag!('email', customer[:email]) if customer[:email] + xml.tag!('phoneNumber', customer[:phone_number]) if customer[:phone_number] + xml.tag!('faxNumber', customer[:fax_number]) if customer[:fax_number] + add_arb_drivers_license(xml, options) + xml.tag!('taxId', customer[:tax_id]) if customer[:tax_id] + end + end + + # Adds the customer's driver's license information (conditional) + def add_arb_drivers_license(xml, options) + return unless customer = options[:customer] + return unless drivers_license = customer[:drivers_license] + xml.tag!('driversLicense') do + # The customer's driver's license number + xml.tag!('number', drivers_license[:number]) + # The customer's driver's license state + xml.tag!('state', drivers_license[:state]) + # The customer's driver's license date of birth + xml.tag!('dateOfBirth', drivers_license[:date_of_birth]) + end + end + + # Adds address information + def add_arb_address(xml, container_name, address) + return if address.blank? + xml.tag!(container_name) do + xml.tag!('firstName', address[:first_name]) + xml.tag!('lastName', address[:last_name]) + xml.tag!('company', address[:company]) + xml.tag!('address', address[:address1]) + xml.tag!('city', address[:city]) + xml.tag!('state', address[:state]) + xml.tag!('zip', address[:zip]) + xml.tag!('country', address[:country]) + end + end + + def arb_expdate(credit_card) + sprintf('%04d-%02d', credit_card.year, credit_card.month) + end + + def recurring_commit(action, request) + url = test? ? arb_test_url : arb_live_url + xml = ssl_post(url, request, "Content-Type" => "text/xml") + + response = recurring_parse(action, xml) + + message = response[:message] || response[:text] + test_mode = test? || message =~ /Test Mode/ + success = response[:result_code] == 'Ok' + + Response.new(success, message, response, + :test => test_mode, + :authorization => response[:subscription_id] + ) + end + + def recurring_parse(action, xml) + response = {} + xml = REXML::Document.new(xml) + root = REXML::XPath.first(xml, "//#{RECURRING_ACTIONS[action]}Response") || + REXML::XPath.first(xml, "//ErrorResponse") + if root + root.elements.to_a.each do |node| + recurring_parse_element(response, node) + end + end + + response + end + + def recurring_parse_element(response, node) + if node.has_elements? + node.elements.each{|e| recurring_parse_element(response, e) } + else + response[node.name.underscore.to_sym] = node.text + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net_cim.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net_cim.rb new file mode 100644 index 000000000..26bcf32f5 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/authorize_net_cim.rb @@ -0,0 +1,956 @@ +# -*- 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 + # + # * :login -- The Authorize.Net API Login ID (REQUIRED) + # * :password -- The Authorize.Net Transaction Key. (REQUIRED) + # * :test -- +true+ or +false+. If true, perform transactions against the test server. + # Otherwise, perform transactions against the production server. + # * :delimiter -- 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 + # + # * :profile -- A hash containing at least one of the CONDITIONAL profile options below (REQUIRED) + # + # ==== Profile + # + # * :email -- Email address associated with the customer profile (CONDITIONAL) + # * :description -- Description of the customer or customer profile (CONDITIONAL) + # * :merchant_customer_id -- Merchant assigned ID for the customer (CONDITIONAL) + # * :payment_profile -- A hash containing the elements of the new payment profile (optional) + # + # ==== Payment Profile + # + # * :payment -- 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer the payment profile will be added to. (REQUIRED) + # * :payment_profile -- A hash containing the elements of the new payment profile (REQUIRED) + # + # ==== Payment Profile + # + # * :payment -- 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer the payment profile will be added to. (REQUIRED) + # * :address -- 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 + # + # * :customer_profile_id -- 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer with the payment profile to be deleted. (REQUIRED) + # * :customer_payment_profile_id -- 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer with the payment profile to be deleted. (REQUIRED) + # * :customer_address_id -- 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 + # + # * :customer_profile_id -- 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer with the payment profile to be retrieved. (REQUIRED) + # * :customer_payment_profile_id -- 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer with the payment profile to be retrieved. (REQUIRED) + # * :customer_address_id -- 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 :payment_profile 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 + # + # * :profile -- A hash containing the values the Customer Profile should be updated to. (REQUIRED) + # + # ==== Profile + # + # * :customer_profile_id -- 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 :payment_profile 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer with the payment profile to be updated. (REQUIRED) + # * :payment_profile -- A hash containing the values the Customer Payment Profile should be updated to. (REQUIRED) + # + # ==== Payment Profile + # + # * :customer_payment_profile_id -- 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 :address 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 + # + # * :customer_profile_id -- The Customer Profile ID of the customer with the payment profile to be updated. (REQUIRED) + # * :address -- A hash containing the values the Customer Shipping Address should be updated to. (REQUIRED) + # + # ==== Address + # + # * :customer_address_id -- 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 params['direct_response'] + # + # ==== Options + # + # * :transaction -- A hash containing information on the transaction that is being requested. (REQUIRED) + # + # ==== Transaction + # + # * :type -- The type of transaction. Can be either :auth_only, :capture_only, :auth_capture, :prior_auth_capture, :refund or :void. (REQUIRED) + # * :amount -- 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) + # + # * :customer_profile_id -- 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) + # + # * :customer_payment_profile_id -- 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) + # + # * :trans_id -- 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) + # + # * :card_code -- CVV/CCV code (OPTIONAL) + # - :type = (:void, :refund, :prior_auth_capture) (NOT USED) + # - :type = (:auth_only, :capture_only, :auth_capture) (OPTIONAL) + # + # * :customer_shipping_address_id -- 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 + # * :credit_card_number_masked -- (CONDITIONAL - requied for credit card refunds is :customer_profile_id AND :customer_payment_profile_id are missing) + # * :bank_routing_number_masked && :bank_account_number_masked -- (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 params['direct_response'] + # + # ==== Options + # + # * :transaction -- A hash containing information on the transaction that is being requested. (REQUIRED) + # + # ==== Transaction + # + # * :amount -- The total amount to be refunded (REQUIRED) + # + # * :customer_profile_id -- The Customer Profile ID of the customer to use in this transaction. (CONDITIONAL :customer_payment_profile_id must be included if used) + # * :customer_payment_profile_id -- The Customer Payment Profile ID of the Customer Payment Profile to use in this transaction. (CONDITIONAL :customer_profile_id must be included if used) + # + # * :credit_card_number_masked -- 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) + # + # * :bank_routing_number_masked -- The last four gidits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked) + # * :bank_account_number_masked -- The last four digis of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked) + # + # * :tax - A hash containing tax information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) + # * :duty - A hash containting duty information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) + # * :shipping - A hash containing shipping information for the refund (OPTIONAL - :amount, :name (31 characters), :description (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 params['direct_response'] + # + # ==== Options + # + # * :transaction -- A hash containing information on the transaction that is being requested. (REQUIRED) + # + # ==== Transaction + # + # * :trans_id -- The payment gateway assigned transaction id of the original transaction. (REQUIRED) + # * :customer_profile_id -- The Customer Profile ID of the customer to use in this transaction. + # * :customer_payment_profile_id -- The Customer Payment Profile ID of the Customer Payment Profile to use in this transaction. + # * :customer_shipping_address_id -- 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 params['direct_response'] + # + # ==== Options + # + # * :customer_profile_id -- The Customer Profile ID of the customer to use in this transaction. (REQUIRED) + # * :customer_payment_profile_id -- The Customer Payment Profile ID of the Customer Payment Profile to be verified. (REQUIRED) + # * :customer_address_id -- The Customer Address ID of the Customer Shipping Address to be verified. (OPTIONAL) + # * :card_code -- If the payment profile is a credit card, the CCV/CVV code to validate with (OPTIONAL) + # * :validation_mode -- :live or :test In Test Mode, only field validation is performed. (REQUIRED + # In Live Mode, a transaction is generated and submitted to the processor with the amount of $0.01. If successful, the transaction is immediately voided. (REQUIRED) + def validate_customer_payment_profile(options) + requires!(options, :customer_profile_id, :customer_payment_profile_id, :validation_mode) + + request = build_request(:validate_customer_payment_profile, options) + commit(:validate_customer_payment_profile, request) + end + + private + + def expdate(credit_card) + if credit_card.year.present? && credit_card.month.present? + sprintf('%04d-%02d', credit_card.year, credit_card.month) + else + 'XXXX' + end + end + + def build_request(action, options = {}) + unless CIM_ACTIONS.include?(action) + raise StandardError, "Invalid Customer Information Manager Action: #{action}" + end + + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.tag!("#{CIM_ACTIONS[action]}Request", :xmlns => AUTHORIZE_NET_CIM_NAMESPACE) do + add_merchant_authentication(xml) + # Merchant-assigned reference ID for the request + xml.tag!('refId', options[:ref_id]) if options[:ref_id] + # Order options + add_order(xml, options[:order]) if options[:order] + send("build_#{action}_request", xml, options) + end + end + + # Contains the merchant’s payment gateway account authentication information + def add_merchant_authentication(xml) + xml.tag!('merchantAuthentication') do + xml.tag!('name', @options[:login]) + xml.tag!('transactionKey', @options[:password]) + end + end + + def build_create_customer_profile_request(xml, options) + add_profile(xml, options[:profile]) + + xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode] + + if options.has_key?(:payment_profile) + xml.tag!('paymentProfile') do + add_payment_profile(xml, options[:payment_profile]) + end + end + + xml.target! + end + + def build_create_customer_payment_profile_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + + xml.tag!('paymentProfile') do + add_payment_profile(xml, options[:payment_profile]) + end + + xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode] + + xml.target! + end + + def build_create_customer_shipping_address_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + + xml.tag!('address') do + add_address(xml, options[:address]) + end + + xml.target! + end + + def build_delete_customer_profile_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.target! + end + + def build_delete_customer_payment_profile_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id]) + xml.target! + end + + def build_delete_customer_shipping_address_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.tag!('customerAddressId', options[:customer_address_id]) + xml.target! + end + + def build_get_customer_profile_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.target! + end + + def build_get_customer_profile_ids_request(xml, options) + xml.target! + end + + def build_get_customer_payment_profile_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id]) + xml.target! + end + + def build_get_customer_shipping_address_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.tag!('customerAddressId', options[:customer_address_id]) + xml.target! + end + + def build_update_customer_profile_request(xml, options) + add_profile(xml, options[:profile], true) + + xml.target! + end + + def build_update_customer_payment_profile_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + + xml.tag!('paymentProfile') do + add_payment_profile(xml, options[:payment_profile]) + end + + xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode] + + xml.target! + end + + def build_update_customer_shipping_address_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + + xml.tag!('address') do + add_address(xml, options[:address]) + end + + xml.target! + end + + def build_create_customer_profile_transaction_request(xml, options) + options[:extra_options] ||= {} + options[:extra_options].merge!('x_test_request' => 'TRUE') if @options[:test] + + add_transaction(xml, options[:transaction]) + tag_unless_blank(xml, 'extraOptions', format_extra_options(options[:extra_options])) + + xml.target! + end + + def build_validate_customer_payment_profile_request(xml, options) + xml.tag!('customerProfileId', options[:customer_profile_id]) + xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id]) + xml.tag!('customerShippingAddressId', options[:customer_address_id]) if options[:customer_address_id] + tag_unless_blank(xml, 'cardCode', options[:card_code]) + xml.tag!('validationMode', CIM_VALIDATION_MODES[options[:validation_mode]]) if options[:validation_mode] + + xml.target! + end + + # :merchant_customer_id (Optional) + # :description (Optional) + # :email (Optional) + # :payment_profiles (Optional) + def add_profile(xml, profile, update = false) + xml.tag!('profile') do + # Merchant assigned ID for the customer. Up to 20 characters. (optional) + xml.tag!('merchantCustomerId', profile[:merchant_customer_id]) if profile[:merchant_customer_id] + # Description of the customer. Up to 255 Characters (optional) + xml.tag!('description', profile[:description]) if profile[:description] + # Email Address for the customer. Up to 255 Characters (optional) + xml.tag!('email', profile[:email]) if profile[:email] + + if update + xml.tag!('customerProfileId', profile[:customer_profile_id]) + else + add_payment_profiles(xml, profile[:payment_profiles]) if profile[:payment_profiles] + add_ship_to_list(xml, profile[:ship_to_list]) if profile[:ship_to_list] + end + end + end + + def add_transaction(xml, transaction) + unless CIM_TRANSACTION_TYPES.include?(transaction[:type]) + raise StandardError, "Invalid Customer Information Manager Transaction Type: #{transaction[:type]}" + end + + xml.tag!('transaction') do + xml.tag!(CIM_TRANSACTION_TYPES[transaction[:type]]) do + # The amount to be billed to the customer + case transaction[:type] + when :void + tag_unless_blank(xml,'customerProfileId', transaction[:customer_profile_id]) + tag_unless_blank(xml,'customerPaymentProfileId', transaction[:customer_payment_profile_id]) + tag_unless_blank(xml,'customerShippingAddressId', transaction[:customer_shipping_address_id]) + xml.tag!('transId', transaction[:trans_id]) + when :refund + #TODO - add lineItems field + xml.tag!('amount', transaction[:amount]) + tag_unless_blank(xml, 'customerProfileId', transaction[:customer_profile_id]) + tag_unless_blank(xml, 'customerPaymentProfileId', transaction[:customer_payment_profile_id]) + tag_unless_blank(xml, 'customerShippingAddressId', transaction[:customer_shipping_address_id]) + tag_unless_blank(xml, 'creditCardNumberMasked', transaction[:credit_card_number_masked]) + tag_unless_blank(xml, 'bankRoutingNumberMasked', transaction[:bank_routing_number_masked]) + tag_unless_blank(xml, 'bankAccountNumberMasked', transaction[:bank_account_number_masked]) + xml.tag!('transId', transaction[:trans_id]) + add_tax(xml, transaction[:tax]) if transaction[:tax] + add_duty(xml, transaction[:duty]) if transaction[:duty] + add_shipping(xml, transaction[:shipping]) if transaction[:shipping] + when :prior_auth_capture + xml.tag!('amount', transaction[:amount]) + xml.tag!('transId', transaction[:trans_id]) + else + xml.tag!('amount', transaction[:amount]) + xml.tag!('customerProfileId', transaction[:customer_profile_id]) + xml.tag!('customerPaymentProfileId', transaction[:customer_payment_profile_id]) + xml.tag!('approvalCode', transaction[:approval_code]) if transaction[:type] == :capture_only + end + add_order(xml, transaction[:order]) if transaction[:order].present? + unless [:void,:refund,:prior_auth_capture].include?(transaction[:type]) + tag_unless_blank(xml, 'cardCode', transaction[:card_code]) + end + end + end + end + + def add_tax(xml, tax) + xml.tag!('tax') do + xml.tag!('amount', tax[:amount]) if tax[:amount] + xml.tag!('name', tax[:name]) if tax[:name] + xml.tag!('description', tax[:description]) if tax[:description] + end + end + + def add_duty(xml, duty) + xml.tag!('duty') do + xml.tag!('amount', duty[:amount]) if duty[:amount] + xml.tag!('name', duty[:name]) if duty[:name] + xml.tag!('description', duty[:description]) if duty[:description] + end + end + + def add_shipping(xml, shipping) + xml.tag!('shipping') do + xml.tag!('amount', shipping[:amount]) if shipping[:amount] + xml.tag!('name', shipping[:name]) if shipping[:name] + xml.tag!('description', shipping[:description]) if shipping[:description] + end + end + + def add_order(xml, order) + xml.tag!('order') do + xml.tag!('invoiceNumber', order[:invoice_number]) if order[:invoice_number] + xml.tag!('description', order[:description]) if order[:description] + xml.tag!('purchaseOrderNumber', order[:purchase_order_number]) if order[:purchase_order_number] + end + end + + def add_payment_profiles(xml, payment_profiles) + xml.tag!('paymentProfiles') do + add_payment_profile(xml, payment_profiles) + end + end + + # :customer_type => 'individual or business', # Optional + # :bill_to => @address, + # :payment => @payment + def add_payment_profile(xml, payment_profile) + # 'individual' or 'business' (optional) + xml.tag!('customerType', payment_profile[:customer_type]) if payment_profile[:customer_type] + + if payment_profile[:bill_to] + xml.tag!('billTo') do + add_address(xml, payment_profile[:bill_to]) + end + end + + if payment_profile[:payment] + xml.tag!('payment') do + add_credit_card(xml, payment_profile[:payment][:credit_card]) if payment_profile[:payment].has_key?(:credit_card) + add_bank_account(xml, payment_profile[:payment][:bank_account]) if payment_profile[:payment].has_key?(:bank_account) + add_drivers_license(xml, payment_profile[:payment][:drivers_license]) if payment_profile[:payment].has_key?(:drivers_license) + # This element is only required for Wells Fargo SecureSource eCheck.Net merchants + # The customer's Social Security Number or Tax ID + xml.tag!('taxId', payment_profile[:payment]) if payment_profile[:payment].has_key?(:tax_id) + end + end + + xml.tag!('customerPaymentProfileId', payment_profile[:customer_payment_profile_id]) if payment_profile[:customer_payment_profile_id] + end + + def add_ship_to_list(xml, ship_to_list) + xml.tag!('shipToList') do + add_address(xml, ship_to_list) + end + end + + def add_address(xml, address) + xml.tag!('firstName', address[:first_name]) + xml.tag!('lastName', address[:last_name]) + xml.tag!('company', address[:company]) + xml.tag!('address', address[:address1]) if address[:address1] + xml.tag!('address', address[:address]) if address[:address] + xml.tag!('city', address[:city]) + xml.tag!('state', address[:state]) + xml.tag!('zip', address[:zip]) + xml.tag!('country', address[:country]) + xml.tag!('phoneNumber', address[:phone_number]) if address[:phone_number] + xml.tag!('faxNumber', address[:fax_number]) if address[:fax_number] + + xml.tag!('customerAddressId', address[:customer_address_id]) if address[:customer_address_id] + end + + # Adds customer’s credit card information + # Note: This element should only be included + # when the payment method is credit card. + def add_credit_card(xml, credit_card) + return unless credit_card + xml.tag!('creditCard') do + # The credit card number used for payment of the subscription + xml.tag!('cardNumber', credit_card.number) + # The expiration date of the credit card used for the subscription + xml.tag!('expirationDate', expdate(credit_card)) + # Note that Authorize.net does not save CVV codes as part of the + # payment profile. Any transactions/validations after the payment + # profile is created that wish to use CVV verification must pass + # the CVV code to authorize.net again. + xml.tag!('cardCode', credit_card.verification_value) if credit_card.verification_value? + end + end + + # Adds customer’s bank account information + # Note: This element should only be included + # when the payment method is bank account. + def add_bank_account(xml, bank_account) + raise StandardError, "Invalid Bank Account Type: #{bank_account[:account_type]}" unless BANK_ACCOUNT_TYPES.include?(bank_account[:account_type]) + raise StandardError, "Invalid eCheck Type: #{bank_account[:echeck_type]}" unless ECHECK_TYPES.include?(bank_account[:echeck_type]) + + xml.tag!('bankAccount') do + # The type of bank account + xml.tag!('accountType', BANK_ACCOUNT_TYPES[bank_account[:account_type]]) + # The routing number of the customer’s bank + xml.tag!('routingNumber', bank_account[:routing_number]) + # The bank account number + xml.tag!('accountNumber', bank_account[:account_number]) + # The full name of the individual associated + # with the bank account number + xml.tag!('nameOnAccount', bank_account[:name_on_account]) + # The type of electronic check transaction + xml.tag!('echeckType', ECHECK_TYPES[bank_account[:echeck_type]]) + # The full name of the individual associated + # with the bank account number (optional) + xml.tag!('bankName', bank_account[:bank_name]) if bank_account[:bank_name] + end + end + + # Adds customer’s driver's license information + # Note: This element is only required for + # Wells Fargo SecureSource eCheck.Net merchants + def add_drivers_license(xml, drivers_license) + xml.tag!('driversLicense') do + # The state of the customer's driver's license + # A valid two character state code + xml.tag!('state', drivers_license[:state]) + # The customer’s driver's license number + xml.tag!('number', drivers_license[:number]) + # The date of birth listed on the customer's driver's license + # YYYY-MM-DD + xml.tag!('dateOfBirth', drivers_license[:date_of_birth]) + end + end + + def commit(action, request) + url = test? ? test_url : live_url + xml = ssl_post(url, request, "Content-Type" => "text/xml") + + response_params = parse(action, xml) + + message = response_params['messages']['message']['text'] + test_mode = test? || message =~ /Test Mode/ + success = response_params['messages']['result_code'] == 'Ok' + response_params['direct_response'] = parse_direct_response(response_params['direct_response']) if response_params['direct_response'] + transaction_id = response_params['direct_response']['transaction_id'] if response_params['direct_response'] + + Response.new(success, message, response_params, + :test => test_mode, + :authorization => transaction_id || response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil) + ) + end + + def tag_unless_blank(xml, tag_name, data) + xml.tag!(tag_name, data) unless data.blank? || data.nil? + end + + def format_extra_options(options) + options.map{ |k, v| "#{k}=#{v}" }.join(',') unless options.nil? + end + + def parse_direct_response(params) + delimiter = @options[:delimiter] || ',' + direct_response = {'raw' => params} + direct_response_fields = params.split(delimiter) + direct_response.merge( + { + 'response_code' => direct_response_fields[0], + 'response_subcode' => direct_response_fields[1], + 'response_reason_code' => direct_response_fields[2], + 'message' => direct_response_fields[3], + 'approval_code' => direct_response_fields[4], + 'avs_response' => direct_response_fields[5], + 'transaction_id' => direct_response_fields[6], + 'invoice_number' => direct_response_fields[7], + 'order_description' => direct_response_fields[8], + 'amount' => direct_response_fields[9], + 'method' => direct_response_fields[10], + 'transaction_type' => direct_response_fields[11], + 'customer_id' => direct_response_fields[12], + 'first_name' => direct_response_fields[13], + 'last_name' => direct_response_fields[14], + 'company' => direct_response_fields[15], + 'address' => direct_response_fields[16], + 'city' => direct_response_fields[17], + 'state' => direct_response_fields[18], + 'zip_code' => direct_response_fields[19], + 'country' => direct_response_fields[20], + 'phone' => direct_response_fields[21], + 'fax' => direct_response_fields[22], + 'email_address' => direct_response_fields[23], + 'ship_to_first_name' => direct_response_fields[24], + 'ship_to_last_name' => direct_response_fields[25], + 'ship_to_company' => direct_response_fields[26], + 'ship_to_address' => direct_response_fields[27], + 'ship_to_city' => direct_response_fields[28], + 'ship_to_state' => direct_response_fields[29], + 'ship_to_zip_code' => direct_response_fields[30], + 'ship_to_country' => direct_response_fields[31], + 'tax' => direct_response_fields[32], + 'duty' => direct_response_fields[33], + 'freight' => direct_response_fields[34], + 'tax_exempt' => direct_response_fields[35], + 'purchase_order_number' => direct_response_fields[36], + 'md5_hash' => direct_response_fields[37], + 'card_code' => direct_response_fields[38], + 'cardholder_authentication_verification_response' => direct_response_fields[39], + # The following direct response fields are only available in version 3.1 of the + # transaction response. Check your merchant account settings for details. + 'account_number' => direct_response_fields[50] || '', + 'card_type' => direct_response_fields[51] || '', + 'split_tender_id' => direct_response_fields[52] || '', + 'requested_amount' => direct_response_fields[53] || '', + 'balance_on_card' => direct_response_fields[54] || '', + } + ) + end + + def parse(action, xml) + xml = REXML::Document.new(xml) + root = REXML::XPath.first(xml, "//#{CIM_ACTIONS[action]}Response") || + REXML::XPath.first(xml, "//ErrorResponse") + if root + response = parse_element(root) + end + + response + end + + def parse_element(node) + if node.has_elements? + response = {} + node.elements.each{ |e| + key = e.name.underscore + value = parse_element(e) + if response.has_key?(key) + if response[key].is_a?(Array) + response[key].push(value) + else + response[key] = [response[key], value] + end + else + response[key] = parse_element(e) + end + } + else + response = node.text + end + + response + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/balanced.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/balanced.rb new file mode 100644 index 000000000..a768fb38d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/balanced.rb @@ -0,0 +1,467 @@ +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 + # + # * :login -- 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 + # + # * money -- The amount to be authorized as an Integer value in cents. + # * credit_card -- A hash of credit card details for this + # transaction or the URI of a card previously stored in Balanced. + # * options -- A hash of optional parameters. + # + # ==== Options + # + # If you are passing a new credit card you must pass one of these two + # parameters + # + # * email -- the email address of user associated with this + # purchase. + # * account_uri -- `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 + # + # * money -- The amount to be purchased as an Integer value in cents. + # * credit_card -- A hash of credit card details for this + # transaction or the URI of a card previously stored in Balanced. + # * options -- A hash of optional parameters. + # + # ==== Options + # + # If you are passing a new credit card you must pass one of these two + # parameters + # + # * email -- the email address of user associated with this + # purchase. + # * account_uri -- `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 + # + # * money -- The amount to be captured as an Integer value in + # cents. If omitted the full amount of the original authorization + # transaction will be captured. + # * authorization -- The uri of an authorization returned from + # an authorize request. + # + # ==== Options + # + # * description -- 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 + # + # * authorization -- 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 + # + # * debit_uri -- The uri of the original transaction against + # which the refund is being issued. + # * options -- A hash of parameters. + # + # ==== Options + # + # * `:amount` -- 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 + # + # * credit_card -- + 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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/banwire.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/banwire.rb new file mode 100644 index 000000000..b7b63adde --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/banwire.rb @@ -0,0 +1,105 @@ +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 diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/barclays_epdq.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/barclays_epdq.rb new file mode 100644 index 000000000..5d686df86 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/barclays_epdq.rb @@ -0,0 +1,314 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BarclaysEpdqGateway < Gateway + self.test_url = 'https://secure2.mde.epdq.co.uk:11500' + self.live_url = 'https://secure2.epdq.co.uk:11500' + + self.supported_countries = ['GB'] + self.default_currency = 'GBP' + self.supported_cardtypes = [:visa, :master, :american_express, :maestro, :switch ] + self.money_format = :cents + self.homepage_url = 'http://www.barclaycard.co.uk/business/accepting-payments/epdq-mpi/' + self.display_name = 'Barclays ePDQ' + + def initialize(options = {}) + requires!(options, :login, :password, :client_id) + super + end + + def authorize(money, creditcard, options = {}) + document = Document.new(self, @options) do + add_order_form(options[:order_id]) do + add_consumer(options) do + add_creditcard(creditcard) + end + add_transaction(:PreAuth, money) + end + end + + commit(document) + end + + def purchase(money, creditcard, options = {}) + # disable fraud checks if this is a repeat order: + if options[:payment_number] && (options[:payment_number] > 1) + no_fraud = true + else + no_fraud = options[:no_fraud] + end + document = Document.new(self, @options, :no_fraud => no_fraud) do + add_order_form(options[:order_id], options[:group_id]) do + add_consumer(options) do + add_creditcard(creditcard) + end + add_transaction(:Auth, money, options) + end + end + commit(document) + end + + # authorization is your unique order ID, not the authorization + # code returned by ePDQ + def capture(money, authorization, options = {}) + document = Document.new(self, @options) do + add_order_form(authorization) do + add_transaction(:PostAuth, money) + end + end + + commit(document) + end + + # authorization is your unique order ID, not the authorization + # code returned by ePDQ + def credit(money, creditcard_or_authorization, options = {}) + if creditcard_or_authorization.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, creditcard_or_authorization, options) + else + credit_new_order(money, creditcard_or_authorization, options) + end + end + + def refund(money, authorization, options = {}) + credit_existing_order(money, authorization, options) + end + + def void(authorization, options = {}) + document = Document.new(self, @options) do + add_order_form(authorization) do + add_transaction(:Void) + end + end + + commit(document) + end + + private + def credit_new_order(money, creditcard, options) + document = Document.new(self, @options) do + add_order_form do + add_consumer(options) do + add_creditcard(creditcard) + end + add_transaction(:Credit, money) + end + end + + commit(document) + end + + def credit_existing_order(money, authorization, options) + order_id, _ = authorization.split(":") + document = Document.new(self, @options) do + add_order_form(order_id) do + add_transaction(:Credit, money) + end + end + + commit(document) + end + + def parse(body) + parser = Parser.new(body) + response = parser.parse + Response.new(response[:success], response[:message], response, + :test => test?, + :authorization => response[:authorization], + :avs_result => response[:avsresponse], + :cvv_result => response[:cvv_result], + :order_id => response[:order_id], + :raw_response => response[:raw_response] + ) + end + + def commit(document) + url = (test? ? self.test_url : self.live_url) + data = ssl_post(url, document.to_xml) + parse(data) + end + + class Parser + def initialize(response) + @response = response + end + + def parse + require 'iconv' unless String.method_defined?(:encode) + if String.method_defined?(:encode) + doc = REXML::Document.new(@response.encode("UTF-8", "ISO-8859-1")) + else + ic = Iconv.new('UTF-8', 'ISO-8859-1') + doc = REXML::Document.new(ic.iconv(@response)) + end + + auth_type = find(doc, "//Transaction/Type").to_s + + message = find(doc, "//Message/Text") + if message.blank? + message = find(doc, "//Transaction/CardProcResp/CcReturnMsg") + end + + case auth_type + when 'Credit', 'Void' + success = find(doc, "//CcReturnMsg") == "Approved." + else + success = find(doc, "//Transaction/AuthCode").present? + end + + { + :success => success, + :message => message, + :transaction_id => find(doc, "//Transaction/Id"), + :avs_result => find(doc, "//Transaction/AvsRespCode"), + :cvv_result => find(doc, "//Transaction/Cvv2Resp"), + :authorization => find(doc, "//OrderFormDoc/Id"), + :raw_response => @response + } + end + + def find(doc, xpath) + REXML::XPath.first(doc, xpath).try(:text) + end + end + + class Document + attr_reader :type, :xml + + PAYMENT_INTERVALS = { + :days => 'D', + :months => 'M' + } + + EPDQ_CARD_TYPES = { + :visa => 1, + :master => 2, + :switch => 9, + :maestro => 10, + } + + def initialize(gateway, options = {}, document_options = {}, &block) + @gateway = gateway + @options = options + @document_options = document_options + @xml = Builder::XmlMarkup.new(:indent => 2) + build(&block) + end + + def to_xml + @xml.target! + end + + def build(&block) + xml.instruct!(:xml, :version => '1.0') + xml.EngineDocList do + xml.DocVersion "1.0" + xml.EngineDoc do + xml.ContentType "OrderFormDoc" + xml.User do + xml.Name(@options[:login]) + xml.Password(@options[:password]) + xml.ClientId({ :DataType => "S32" }, @options[:client_id]) + end + xml.Instructions do + if @document_options[:no_fraud] + xml.Pipeline "PaymentNoFraud" + else + xml.Pipeline "Payment" + end + end + instance_eval(&block) + end + end + end + + def add_order_form(order_id=nil, group_id=nil, &block) + xml.OrderFormDoc do + xml.Mode 'P' + xml.Id(order_id) if order_id + xml.GroupId(group_id) if group_id + instance_eval(&block) + end + end + + def add_consumer(options=nil, &block) + xml.Consumer do + if options + xml.Email(options[:email]) if options[:email] + billing_address = options[:billing_address] || options[:address] + if billing_address + xml.BillTo do + xml.Location do + xml.Address do + xml.Street1 billing_address[:address1] + xml.Street2 billing_address[:address2] + xml.City billing_address[:city] + xml.StateProv billing_address[:state] + xml.PostalCode billing_address[:zip] + xml.Country billing_address[:country_code] + end + end + end + end + end + instance_eval(&block) + end + end + + def add_creditcard(creditcard) + xml.PaymentMech do + xml.CreditCard do + xml.Type({ :DataType => 'S32' }, EPDQ_CARD_TYPES[creditcard.brand.to_sym]) + xml.Number creditcard.number + xml.Expires({ :DataType => 'ExpirationDate', :Locale => 826 }, format_expiry_date(creditcard)) + if creditcard.verification_value.present? + xml.Cvv2Indicator 1 + xml.Cvv2Val creditcard.verification_value + else + xml.Cvv2Indicator 5 + end + xml.IssueNum(creditcard.issue_number) if creditcard.issue_number.present? + end + end + end + + def add_transaction(auth_type, amount = nil, options = {}) + @auth_type = auth_type + xml.Transaction do + xml.Type @auth_type.to_s + if options[:payment_number] && options[:payment_number] > 1 + xml.CardholderPresentCode({ :DataType => 'S32' }, 8) + else + xml.CardholderPresentCode({ :DataType => 'S32' }, 7) + end + if options[:payment_number] + xml.PaymentNumber({ :DataType => 'S32' }, options[:payment_number]) + end + if options[:total_payments] + xml.TotalNumberPayments({ :DataType => 'S32' }, options[:total_payments]) + end + if amount + xml.CurrentTotals do + xml.Totals do + xml.Total({ :DataType => 'Money', :Currency => 826 }, amount) + end + end + end + end + end + + # date must be formatted MM/YY + def format_expiry_date(creditcard) + month_str = "%02d" % creditcard.month + if match = creditcard.year.to_s.match(/^\d{2}(\d{2})$/) + year_str = "%02d" % match[1].to_i + else + year_str = "%02d" % creditcard.year + end + "#{month_str}/#{year_str}" + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream.rb new file mode 100644 index 000000000..edc3bb11f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream.rb @@ -0,0 +1,169 @@ +require File.dirname(__FILE__) + '/beanstream/beanstream_core' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This class implements the Canadian {Beanstream}[http://www.beanstream.com] payment gateway. + # It is also named TD Canada Trust Online Mart payment gateway. + # To learn more about the specification of Beanstream gateway, please read the OM_Direct_Interface_API.pdf, + # which you can get from your Beanstream account or get from me by email. + # + # == Supported transaction types by Beanstream: + # * +P+ - Purchase + # * +PA+ - Pre Authorization + # * +PAC+ - Pre Authorization Completion + # + # == Secure Payment Profiles: + # BeanStream supports payment profiles (vaults). This allows you to store cc information with BeanStream and process subsequent transactions with a customer id. + # Secure Payment Profiles must be enabled on your account (must be done over the phone). + # Your API Access Passcode must be set in Administration => account settings => order settings. + # To learn more about storing credit cards with the Beanstream gateway, please read the BEAN_Payment_Profiles.pdf (I had to phone BeanStream to request it.) + # + # == Notes + # * Adding of order products information is not implemented. + # * Ensure that country and province data is provided as a code such as "CA", "US", "QC". + # * login is the Beanstream merchant ID, username and password should be enabled in your Beanstream account and passed in using the :user and :password options. + # * Test your app with your true merchant id and test credit card information provided in the api pdf document. + # * Beanstream does not allow Payment Profiles to be deleted with their API. The accounts are 'closed', but have to be deleted manually. + # + # Example authorization (Beanstream PA transaction type): + # + # twenty = 2000 + # gateway = BeanstreamGateway.new( + # :login => '100200000', + # :user => 'xiaobozz', + # :password => 'password' + # ) + # + # credit_card = CreditCard.new( + # :number => '4030000010001234', + # :month => 8, + # :year => 2011, + # :first_name => 'xiaobo', + # :last_name => 'zzz', + # :verification_value => 137 + # ) + # response = gateway.authorize(twenty, credit_card, + # :order_id => '1234', + # :billing_address => { + # :name => 'xiaobo zzz', + # :phone => '555-555-5555', + # :address1 => '1234 Levesque St.', + # :address2 => 'Apt B', + # :city => 'Montreal', + # :state => 'QC', + # :country => 'CA', + # :zip => 'H2C1X8' + # }, + # :email => 'xiaobozzz@example.com', + # :subtotal => 800, + # :shipping => 100, + # :tax1 => 100, + # :tax2 => 100, + # :custom => 'reference one' + # ) + class BeanstreamGateway < Gateway + include BeanstreamCore + + def authorize(money, source, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_source(post, source) + add_address(post, options) + add_transaction_type(post, :authorization) + add_customer_ip(post, options) + commit(post) + end + + def purchase(money, source, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_source(post, source) + add_address(post, options) + add_transaction_type(post, purchase_action(source)) + add_customer_ip(post, options) + commit(post) + end + + def void(authorization, options = {}) + reference, amount, type = split_auth(authorization) + + post = {} + add_reference(post, reference) + add_original_amount(post, amount) + add_transaction_type(post, void_action(type)) + commit(post) + end + + def recurring(money, source, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, source) + add_address(post, options) + add_transaction_type(post, purchase_action(source)) + add_recurring_type(post, options) + commit(post) + end + + def update_recurring(amount, source, options = {}) + post = {} + add_recurring_amount(post, amount) + add_recurring_invoice(post, options) + add_credit_card(post, source) + add_address(post, options) + add_recurring_operation_type(post, :update) + add_recurring_service(post, options) + recurring_commit(post) + end + + def cancel_recurring(options = {}) + post = {} + add_recurring_operation_type(post, :cancel) + add_recurring_service(post, options) + recurring_commit(post) + end + + def interac + @interac ||= BeanstreamInteracGateway.new(@options) + end + + # To match the other stored-value gateways, like TrustCommerce, + # store and unstore need to be defined + def store(credit_card, options = {}) + post = {} + add_address(post, options) + add_credit_card(post, credit_card) + add_secure_profile_variables(post,options) + commit(post, true) + end + + #can't actually delete a secure profile with the supplicaed API. This function sets the status of the profile to closed (C). + #Closed profiles will have to removed manually. + def delete(vault_id) + update(vault_id, false, {:status => "C"}) + end + + alias_method :unstore, :delete + + # Update the values (such as CC expiration) stored at + # the gateway. The CC number must be supplied in the + # CreditCard object. + def update(vault_id, credit_card, options = {}) + post = {} + add_address(post, options) + add_credit_card(post, credit_card) + options.merge!({:vault_id => vault_id, :operation => secure_profile_action(:modify)}) + add_secure_profile_variables(post,options) + commit(post, true) + end + + private + def build_response(*args) + Response.new(*args) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb new file mode 100644 index 000000000..79ca7c5a6 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb @@ -0,0 +1,393 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module BeanstreamCore + RECURRING_URL = 'https://www.beanstream.com/scripts/recurring_billing.asp' + SECURE_PROFILE_URL = 'https://www.beanstream.com/scripts/payment_profile.asp' + + SP_SERVICE_VERSION = '1.1' + + TRANSACTIONS = { + :authorization => 'PA', + :purchase => 'P', + :capture => 'PAC', + :refund => 'R', + :void => 'VP', + :check_purchase => 'D', + :check_refund => 'C', + :void_purchase => 'VP', + :void_refund => 'VR' + } + + PROFILE_OPERATIONS = { + :new => 'N', + :modify => 'M' + } + + CVD_CODES = { + '1' => 'M', + '2' => 'N', + '3' => 'I', + '4' => 'S', + '5' => 'U', + '6' => 'P' + } + + AVS_CODES = { + '0' => 'R', + '5' => 'I', + '9' => 'I' + } + + PERIODS = { + :days => 'D', + :weeks => 'W', + :months => 'M', + :years => 'Y' + } + + PERIODICITIES = { + :daily => [:days, 1], + :weekly => [:weeks, 1], + :biweekly => [:weeks, 2], + :monthly => [:months, 1], + :bimonthly => [:months, 2], + :yearly => [:years, 1] + } + + RECURRING_OPERATION = { + :update => 'M', + :cancel => 'C' + } + + def self.included(base) + base.default_currency = 'CAD' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + base.supported_countries = ['CA'] + + # The card types supported by the payment gateway + base.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + # The homepage URL of the gateway + base.homepage_url = 'http://www.beanstream.com/' + base.live_url = 'https://www.beanstream.com/scripts/process_transaction.asp' + + # The name of the gateway + base.display_name = 'Beanstream.com' + end + + # Only :login is required by default, + # which is the merchant's merchant ID. If you'd like to perform void, + # capture or refund transactions then you'll also need to add a username + # and password to your account under administration -> account settings -> + # order settings -> Use username/password validation + def initialize(options = {}) + requires!(options, :login) + super + end + + def capture(money, authorization, options = {}) + reference, amount, type = split_auth(authorization) + + post = {} + add_amount(post, money) + add_reference(post, reference) + add_transaction_type(post, :capture) + commit(post) + end + + def refund(money, source, options = {}) + post = {} + reference, amount, type = split_auth(source) + add_reference(post, reference) + add_transaction_type(post, refund_action(type)) + add_amount(post, money) + commit(post) + end + + def credit(money, source, options = {}) + deprecated Gateway::CREDIT_DEPRECATION_MESSAGE + refund(money, source, options) + end + + private + def purchase_action(source) + if source.is_a?(Check) + :check_purchase + else + :purchase + end + end + + def add_customer_ip(post, options) + post[:customerIP] = options[:ip] if options[:ip] + end + + def void_action(original_transaction_type) + (original_transaction_type == TRANSACTIONS[:refund]) ? :void_refund : :void_purchase + end + + def refund_action(type) + (type == TRANSACTIONS[:check_purchase]) ? :check_refund : :refund + end + + def secure_profile_action(type) + PROFILE_OPERATIONS[type] || PROFILE_OPERATIONS[:new] + end + + def split_auth(string) + string.split(";") + end + + def add_amount(post, money) + post[:trnAmount] = amount(money) + end + + def add_original_amount(post, amount) + post[:trnAmount] = amount + end + + def add_reference(post, reference) + post[:adjId] = reference + end + + def add_address(post, options) + prepare_address_for_non_american_countries(options) + + if billing_address = options[:billing_address] || options[:address] + post[:ordName] = billing_address[:name] + post[:ordEmailAddress] = options[:email] + post[:ordPhoneNumber] = billing_address[:phone] + post[:ordAddress1] = billing_address[:address1] + post[:ordAddress2] = billing_address[:address2] + post[:ordCity] = billing_address[:city] + post[:ordProvince] = billing_address[:state] + post[:ordPostalCode] = billing_address[:zip] + post[:ordCountry] = billing_address[:country] + end + if shipping_address = options[:shipping_address] + post[:shipName] = shipping_address[:name] + post[:shipEmailAddress] = options[:email] + post[:shipPhoneNumber] = shipping_address[:phone] + post[:shipAddress1] = shipping_address[:address1] + post[:shipAddress2] = shipping_address[:address2] + post[:shipCity] = shipping_address[:city] + post[:shipProvince] = shipping_address[:state] + post[:shipPostalCode] = shipping_address[:zip] + post[:shipCountry] = shipping_address[:country] + post[:shippingMethod] = shipping_address[:shipping_method] + post[:deliveryEstimate] = shipping_address[:delivery_estimate] + end + end + + def prepare_address_for_non_american_countries(options) + [ options[:billing_address], options[:shipping_address] ].compact.each do |address| + unless ['US', 'CA'].include?(address[:country]) + address[:state] = '--' + address[:zip] = '000000' unless address[:zip] + end + end + end + + def add_invoice(post, options) + post[:trnOrderNumber] = options[:order_id] + post[:trnComments] = options[:description] + post[:ordItemPrice] = amount(options[:subtotal]) + post[:ordShippingPrice] = amount(options[:shipping]) + post[:ordTax1Price] = amount(options[:tax1] || options[:tax]) + post[:ordTax2Price] = amount(options[:tax2]) + post[:ref1] = options[:custom] + end + + def add_credit_card(post, credit_card) + if credit_card + post[:trnCardOwner] = credit_card.name + post[:trnCardNumber] = credit_card.number + post[:trnExpMonth] = format(credit_card.month, :two_digits) + post[:trnExpYear] = format(credit_card.year, :two_digits) + post[:trnCardCvd] = credit_card.verification_value + end + end + + def add_check(post, check) + # The institution number of the consumer’s financial institution. Required for Canadian dollar EFT transactions. + post[:institutionNumber] = check.institution_number + + # The bank transit number of the consumer’s bank account. Required for Canadian dollar EFT transactions. + post[:transitNumber] = check.transit_number + + # The routing number of the consumer’s bank account. Required for US dollar EFT transactions. + post[:routingNumber] = check.routing_number + + # The account number of the consumer’s bank account. Required for both Canadian and US dollar EFT transactions. + post[:accountNumber] = check.account_number + end + + def add_secure_profile_variables(post, options = {}) + post[:serviceVersion] = SP_SERVICE_VERSION + post[:responseFormat] = 'QS' + post[:cardValidation] = (options[:cardValidation].to_i == 1) || '0' + + post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new) + post[:customerCode] = options[:billing_id] || options[:vault_id] || false + post[:status] = options[:status] + end + + def add_recurring_amount(post, money) + post[:amount] = amount(money) + end + + def add_recurring_invoice(post, options) + post[:rbApplyTax1] = options[:apply_tax1] + post[:rbApplyTax2] = options[:apply_tax2] + end + + def add_recurring_operation_type(post, operation) + post[:operationType] = RECURRING_OPERATION[operation] + end + + def add_recurring_service(post, options) + post[:serviceVersion] = '1.0' + post[:merchantId] = @options[:login] + post[:passCode] = @options[:recurring_api_key] + post[:rbAccountId] = options[:account_id] + end + + def add_recurring_type(post, options) + # XXX requires! + post[:trnRecurring] = 1 + period, increment = interval(options) + post[:rbBillingPeriod] = PERIODS[period] + post[:rbBillingIncrement] = increment + + if options.include? :start_date + post[:rbCharge] = 0 + post[:rbFirstBilling] = options[:start_date].strftime('%m%d%Y') + end + + if count = options[:occurrences] || options[:payments] + post[:rbExpiry] = (options[:start_date] || Date.current).advance(period => count).strftime('%m%d%Y') + end + end + + def interval(options) + if options.include? :periodicity + requires!(options, [:periodicity, *PERIODICITIES.keys]) + PERIODICITIES[options[:periodicity]] + elsif options.include? :interval + interval = options[:interval] + if interval.respond_to? :parts + parts = interval.parts + raise ArgumentError.new("Cannot recur with mixed interval (#{interval}). Use only one of: days, weeks, months or years") if parts.length > 1 + parts.first + elsif interval.kind_of? Hash + requires!(interval, :unit) + unit, length = interval.values_at(:unit, :length) + length ||= 1 + [unit, length] + end + end + end + + def parse(body) + results = {} + if !body.nil? + body.split(/&/).each do |pair| + key, val = pair.split(/\=/) + results[key.to_sym] = val.nil? ? nil : CGI.unescape(val) + end + end + + # Clean up the message text if there is any + if results[:messageText] + results[:messageText].gsub!(/
  • /, "") + results[:messageText].gsub!(/(\.)?
    /, ". ") + results[:messageText].strip! + end + + results + end + + def recurring_parse(data) + REXML::Document.new(data).root.elements.to_a.inject({}) do |response, element| + response[element.name.to_sym] = element.text + response + end + end + + def commit(params, use_profile_api = false) + post(post_data(params,use_profile_api),use_profile_api) + end + + def recurring_commit(params) + recurring_post(post_data(params, false)) + end + + def post(data, use_profile_api=nil) + response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : self.live_url), data)) + response[:customer_vault_id] = response[:customerCode] if response[:customerCode] + build_response(success?(response), message_from(response), response, + :test => test? || response[:authCode] == "TEST", + :authorization => authorization_from(response), + :cvv_result => CVD_CODES[response[:cvdId]], + :avs_result => { :code => (AVS_CODES.include? response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] } + ) + end + + def recurring_post(data) + response = recurring_parse(ssl_post(RECURRING_URL, data)) + build_response(recurring_success?(response), recurring_message_from(response), response) + end + + def authorization_from(response) + "#{response[:trnId]};#{response[:trnAmount]};#{response[:trnType]}" + end + + def message_from(response) + response[:messageText] || response[:responseMessage] + end + + def recurring_message_from(response) + response[:message] + end + + def success?(response) + response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1' + end + + def recurring_success?(response) + response[:code] == '1' + end + + def add_source(post, source) + if source.is_a?(String) or source.is_a?(Integer) + post[:customerCode] = source + else + card_brand(source) == "check" ? add_check(post, source) : add_credit_card(post, source) + end + end + + def add_transaction_type(post, action) + post[:trnType] = TRANSACTIONS[action] + end + + def post_data(params, use_profile_api) + params[:requestType] = 'BACKEND' + if use_profile_api + params[:merchantId] = @options[:login] + params[:passCode] = @options[:secure_profile_api_key] + else + params[:username] = @options[:user] if @options[:user] + params[:password] = @options[:password] if @options[:password] + params[:merchant_id] = @options[:login] + end + params[:vbvEnabled] = '0' + params[:scEnabled] = '0' + + params.reject{|k, v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream_interac.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream_interac.rb new file mode 100644 index 000000000..2caa3b7a7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/beanstream_interac.rb @@ -0,0 +1,54 @@ +require File.dirname(__FILE__) + '/beanstream/beanstream_core' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BeanstreamInteracResponse < Response + def redirect + params['pageContents'] + end + end + + class BeanstreamInteracGateway < Gateway + include BeanstreamCore + + # Confirm a transaction posted back from the bank to Beanstream. + # Confirming a transaction does not require any credentials, + # and in an application with many merchants sharing a funded + # URL the application may not yet know which merchant the + # post back is for until the response of the confirmation is + # received, which contains the order number. + def self.confirm(transaction) + gateway = new(:login => '') + gateway.confirm(transaction) + end + + def purchase(money, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_address(post, options) + add_interac_details(post, options) + add_transaction_type(post, :purchase) + commit(post) + end + + # Confirm a transaction posted back from the bank to Beanstream. + def confirm(transaction) + post(transaction) + end + + private + + def add_interac_details(post, options) + address = options[:billing_address] || options[:address] || {} + post[:trnCardOwner] = address[:name] + post[:paymentMethod] = 'IO' + end + + def build_response(*args) + BeanstreamInteracResponse.new(*args) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/blue_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/blue_pay.rb new file mode 100644 index 000000000..1a8d169a0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/blue_pay.rb @@ -0,0 +1,503 @@ +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BluePayGateway < Gateway + class_attribute :rebilling_url, :ignore_http_status + + self.live_url = 'https://secure.bluepay.com/interfaces/bp20post' + self.rebilling_url = 'https://secure.bluepay.com/interfaces/bp20rebadmin' + + self.ignore_http_status = true + + CARD_CODE_ERRORS = %w( N S ) + AVS_ERRORS = %w( A E N R W Z ) + AVS_REASON_CODES = %w(27 45) + + FRAUD_REVIEW_STATUSES = %w( E 0 ) + + FIELD_MAP = { + 'TRANS_ID' => :transaction_id, + 'STATUS' => :response_code, + 'AVS' => :avs_result_code, + 'CVV2'=> :card_code, + 'AUTH_CODE' => :authorization, + 'MESSAGE' => :message, + 'REBID' => :rebid, + 'TRANS_TYPE' => :trans_type, + 'PAYMENT_ACCOUNT_MASK' => :acct_mask, + 'CARD_TYPE' => :card_type, + } + + REBILL_FIELD_MAP = { + 'REBILL_ID' => :rebill_id, + 'ACCOUNT_ID'=> :account_id, + 'USER_ID' => :user_id, + 'TEMPLATE_ID' => :template_id, + 'STATUS' => :status, + 'CREATION_DATE' => :creation_date, + 'NEXT_DATE' => :next_date, + 'LAST_DATE' => :last_date, + 'SCHED_EXPR' => :schedule, + 'CYCLES_REMAIN' => :cycles_remain, + 'REB_AMOUNT' => :rebill_amount, + 'NEXT_AMOUNT' => :next_amount, + 'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc. + } + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.homepage_url = 'http://www.bluepay.com/' + self.display_name = 'BluePay' + self.money_format = :dollars + + # Creates a new BluepayGateway + # + # The gateway requires that a valid Account ID and Secret Key be passed + # in the +options+ hash. + # + # ==== Options + # + # * :account_id -- The BluePay gateway Account ID (REQUIRED) + # * :secret_key -- The BluePay gateway Secret Key (REQUIRED) + # * :test -- set to true for TEST mode or false for LIVE mode + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Performs an authorization, which reserves the funds on the customer's credit card. This does not actually take funds from the customer + # This is referred to an AUTH transaction in BluePay + # + # ==== Parameters + # + # * money -- The amount to be authorized as an Integer value in cents. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # * options -- A hash of optional parameters. + def authorize(money, payment_object, options = {}) + post = {} + add_payment_method(post, payment_object) + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + add_rebill(post, options) if options[:rebill] + add_duplicate_override(post, options) + post[:TRANS_TYPE] = 'AUTH' + commit('AUTH_ONLY', money, post) + end + + # Perform a purchase, which is essentially an authorization and capture in a single operation. + # This is referred to a SALE transaction in BluePay + # + # ==== Parameters + # + # * money -- The amount to be purchased as an Integer value in cents. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # * options -- A hash of optional parameters., + def purchase(money, payment_object, options = {}) + post = {} + add_payment_method(post, payment_object) + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + add_rebill(post, options) if options[:rebill] + add_duplicate_override(post, options) + post[:TRANS_TYPE] = 'SALE' + commit('AUTH_CAPTURE', money, post) + end + + # Captures the funds from an authorize transaction. + # This is referred to a CAPTURE transaction in BluePay + # + # ==== Parameters + # + # * money -- The amount to be captured as an Integer value in cents. + # * identification -- The Master ID, or token, returned from the previous authorize transaction. + def capture(money, identification, options = {}) + post = {} + add_address(post, options) + add_customer_data(post, options) + post[:MASTER_ID] = identification + post[:TRANS_TYPE] = 'CAPTURE' + commit('PRIOR_AUTH_CAPTURE', money, post) + end + + # Void a previous transaction + # This is referred to a VOID transaction in BluePay + # + # ==== Parameters + # + # * identification - The Master ID, or token, returned from a previous authorize transaction. + def void(identification, options = {}) + post = {} + post[:MASTER_ID] = identification + post[:TRANS_TYPE] = 'VOID' + commit('VOID', nil, post) + end + + # Performs a credit. + # + # This transaction indicates that money should flow from the merchant to the customer. + # + # ==== Parameters + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # If the payment_object is a token, then the transaction type will reverse a previous capture or purchase transaction, returning the funds to the customer. If the amount is nil, a full credit will be processed. This is referred to a REFUND transaction in BluePay. + # If the payment_object is either a CreditCard or Check object, then the transaction type will be an unmatched credit placing funds in the specified account. This is referred to a CREDIT transaction in BluePay. + # * options -- A hash of parameters. + def refund(money, identification, options = {}) + if(identification && !identification.kind_of?(String)) + deprecated "refund should only be used to refund a referenced transaction" + return credit(money, identification, options) + end + + post = {} + post[:PAYMENT_ACCOUNT] = '' + post[:MASTER_ID] = identification + post[:TRANS_TYPE] = 'REFUND' + post[:NAME1] = (options[:first_name] ? options[:first_name] : "") + post[:NAME2] = options[:last_name] if options[:last_name] + post[:ZIP] = options[:zip] if options[:zip] + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + commit('CREDIT', money, post) + end + + def credit(money, payment_object, options = {}) + if(payment_object && payment_object.kind_of?(String)) + deprecated "credit should only be used to credit a payment method" + return refund(money, payment_object, options) + end + + post = {} + post[:PAYMENT_ACCOUNT] = '' + add_payment_method(post, payment_object) + post[:TRANS_TYPE] = 'CREDIT' + + post[:NAME1] = (options[:first_name] ? options[:first_name] : "") + post[:NAME2] = options[:last_name] if options[:last_name] + post[:ZIP] = options[:zip] if options[:zip] + add_invoice(post, options) + add_address(post, options) + add_customer_data(post, options) + commit('CREDIT', money, post) + end + + # Create a new recurring payment. + # + # ==== Parameters + # + # * money -- The amount to charge the customer at the time of the recurring payment setup, in cents. Set to zero if you do not want the customer to be charged at this time. + # * payment_object -- This can either be one of three things: + # A CreditCard object, + # A Check object, + # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction. + # * options -- A hash of optional parameters., + + # ==== Options + # + # * :rebill_start_date is a string that tells the gateway when to start the rebill. (REQUIRED) + # Has two valid formats: + # "YYYY-MM-DD HH:MM:SS" Hours, minutes, and seconds are optional. + # "XX UNITS" Relative date as explained below. Marked from the time of the + # transaction (i.e.: 10 DAYS, 1 MONTH, 1 YEAR) + # * :rebill_expression is the period of time in-between rebillings. (REQUIRED) + # It uses the same "XX UNITS" format as rebill_start_date, explained above. + # Optional parameters include: + # * rebill_cycles: Number of times to rebill. Don't send or set to nil for infinite rebillings (or + # until canceled). + # * rebill_amount: Amount to rebill. Defaults to amount of transaction for rebillings. + # + # For example, to charge the customer $19.95 now and then charge $39.95 in 60 days every 3 months for 5 times, the options hash would be as follows: + # :rebill_start_date => '60 DAYS', + # :rebill_expression => '3 MONTHS', + # :rebill_cycles => '5', + # :rebill_amount => '39.95' + # A money object of 1995 cents would be passed into the 'money' parameter. + def recurring(money, payment_object, options = {}) + requires!(options, :rebill_start_date, :rebill_expression) + options[:rebill] = true + if money + purchase(money, payment_object, options) + else + authorize(money, payment_object, options) + end + end + + # View a recurring payment + # + # This will pull data associated with a current recurring billing + # + # ==== Parameters + # + # * rebill_id -- A string containing the rebill_id of the recurring billing that is already active (REQUIRED) + def status_recurring(rebill_id) + post = {} + requires!(rebill_id) + post[:REBILL_ID] = rebill_id + post[:TRANS_TYPE] = 'GET' + commit('rebill', 'nil', post) + end + + # Update a recurring payment's details. + # + # This transaction updates an existing recurring billing + # + # ==== Options + # + # * :rebill_id -- The 12 digit rebill ID used to update a particular rebilling cycle. (REQUIRED) + # * :rebill_amount -- A string containing the new rebilling amount. + # * :rebill_next_date -- A string containing the new rebilling next date. + # * :rebill_expression -- A string containing the new rebilling expression. + # * :rebill_cycles -- A string containing the new rebilling cycles. + # * :rebill_next_amount -- A string containing the next rebilling amount to charge the customer. This ONLY affects the next scheduled charge; all other rebillings will continue at the regular (rebill_amount) amount. + # Take a look above at the recurring_payment method for similar examples on how to use. + def update_recurring(options = {}) + post = {} + requires!(options, :rebill_id) + post[:REBILL_ID] = options[:rebill_id] + post[:TRANS_TYPE] = 'SET' + post[:REB_AMOUNT] = amount(options[:rebill_amount]) if options[:rebill_amount] + post[:NEXT_DATE] = options[:rebill_next_date] + post[:REB_EXPR] = options[:rebill_expression] + post[:REB_CYCLES] = options[:rebill_cycles] + post[:NEXT_AMOUNT] = options[:rebill_next_amount] + commit('rebill', 'nil', post) + end + + # Cancel a recurring payment. + # + # This transaction cancels an existing recurring billing. + # + # ==== Parameters + # + # * rebill_id -- A string containing the rebill_id of the recurring billing that you wish to cancel/stop (REQUIRED) + def cancel_recurring(rebill_id) + post = {} + requires!(rebill_id) + post[:REBILL_ID] = rebill_id + post[:TRANS_TYPE] = 'SET' + post[:STATUS] = 'stopped' + commit('rebill', 'nil', post) + end + + private + + def commit(action, money, fields) + fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill') + fields[:MODE] = (test? ? 'TEST' : 'LIVE') + fields[:ACCOUNT_ID] = @options[:login] + + if action == 'rebill' + url = rebilling_url + fields[:TAMPER_PROOF_SEAL] = calc_rebill_tps(fields) + else + url = live_url + fields[:TAMPER_PROOF_SEAL] = calc_tps(amount(money), fields) + end + parse(ssl_post(url, post_data(action, fields))) + end + + def parse_recurring(response_fields, opts={}) # expected status? + parsed = {} + response_fields.each do |k,v| + mapped_key = REBILL_FIELD_MAP.include?(k) ? REBILL_FIELD_MAP[k] : k + parsed[mapped_key] = v + end + + success = parsed[:status] != 'error' + message = parsed[:status] + + Response.new(success, message, parsed, + :test => test?, + :authorization => parsed[:rebill_id]) + end + + def parse(body) + # The bp20api has max one value per form field. + response_fields = Hash[CGI::parse(body).map{|k,v| [k.upcase,v.first]}] + + if response_fields.include? "REBILL_ID" + return parse_recurring(response_fields) + end + + parsed = {} + response_fields.each do |k,v| + mapped_key = FIELD_MAP.include?(k) ? FIELD_MAP[k] : k + parsed[mapped_key] = v + end + + # normalize message + message = message_from(parsed) + success = parsed[:response_code] == '1' + Response.new(success, message, parsed, + :test => test?, + :authorization => (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]), + :fraud_review => FRAUD_REVIEW_STATUSES.include?(parsed[:response_code]), + :avs_result => { :code => parsed[:avs_result_code] }, + :cvv_result => parsed[:card_code] + ) + end + + def message_from(parsed) + message = parsed[:message] + if(parsed[:response_code].to_i == 2) + if CARD_CODE_ERRORS.include?(parsed[:card_code]) + message = CVVResult.messages[parsed[:card_code]] + elsif AVS_ERRORS.include?(parsed[:avs_result_code]) + message = AVSResult.messages[ parsed[:avs_result_code] ] + else + message = message.chomp('.') + end + elsif message == "Missing ACCOUNT_ID" + message = "The merchant login ID or password is invalid" + elsif message =~ /Approved/ + message = "This transaction has been approved" + elsif message =~ /Expired/ + message = "The credit card has expired" + end + message + end + + def add_invoice(post, options) + post[:ORDER_ID] = options[:order_id] + post[:INVOICE_ID] = options[:invoice] + post[:invoice_num] = options[:order_id] + post[:MEMO] = options[:description] + post[:description] = options[:description] + end + + def add_payment_method(post, payment_object) + post[:MASTER_ID] = '' + case payment_object + when String + post[:MASTER_ID] = payment_object + when Check + add_check(post, payment_object) + else + add_creditcard(post, payment_object) + end + end + + def add_creditcard(post, creditcard) + post[:PAYMENT_TYPE] = 'CREDIT' + post[:PAYMENT_ACCOUNT] = creditcard.number + post[:CARD_CVV2] = creditcard.verification_value + post[:CARD_EXPIRE] = expdate(creditcard) + post[:NAME1] = creditcard.first_name + post[:NAME2] = creditcard.last_name + end + + CHECK_ACCOUNT_TYPES = { + "checking" => "C", + "savings" => "S" + } + + def add_check(post, check) + post[:PAYMENT_TYPE] = 'ACH' + post[:PAYMENT_ACCOUNT] = [CHECK_ACCOUNT_TYPES[check.account_type], check.routing_number, check.account_number].join(":") + post[:NAME1] = check.first_name + post[:NAME2] = check.last_name + end + + def add_customer_data(post, options) + post[:EMAIL] = options[:email] + post[:CUSTOM_ID] = options[:customer] + end + + def add_duplicate_override(post, options) + post[:DUPLICATE_OVERRIDE] = options[:duplicate_override] + end + + def add_address(post, options) + if address = (options[:shipping_address] || options[:billing_address] || options[:address]) + post[:NAME1] = address[:first_name] + post[:NAME2] = address[:last_name] + post[:ADDR1] = address[:address1] + post[:ADDR2] = address[:address2] + post[:COMPANY_NAME] = address[:company] + post[:PHONE] = address[:phone] + post[:CITY] = address[:city] + post[:STATE] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:ZIP] = address[:zip] + post[:COUNTRY] = address[:country] + end + end + + def add_rebill(post, options) + post[:DO_REBILL] = '1' + post[:REB_AMOUNT] = amount(options[:rebill_amount]) + post[:REB_FIRST_DATE] = options[:rebill_start_date] + post[:REB_EXPR] = options[:rebill_expression] + post[:REB_CYCLES] = options[:rebill_cycles] + end + + def post_data(action, parameters = {}) + post = {} + post[:version] = '1' + post[:login] = '' + post[:tran_key] = '' + post[:relay_response] = "FALSE" + post[:type] = action + post[:delim_data] = "TRUE" + post[:delim_char] = "," + post[:encap_char] = "$" + post[:card_num] = '4111111111111111' + post[:exp_date] = '1212' + post[:solution_ID] = application_id if(application_id && application_id != "ActiveMerchant") + post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def expdate(creditcard) + year = format(creditcard.year, :two_digits) + month = format(creditcard.month, :two_digits) + + "#{month}#{year}" + end + + def calc_tps(amount, post) + post[:NAME1] ||= '' + Digest::MD5.hexdigest( + [ + @options[:password], + @options[:login], + post[:TRANS_TYPE], + amount, + post[:MASTER_ID], + post[:NAME1], + post[:PAYMENT_ACCOUNT] + ].join("") + ) + end + + def calc_rebill_tps(post) + Digest::MD5.hexdigest( + [ + @options[:password], + @options[:login], + post[:TRANS_TYPE], + post[:REBILL_ID] + ].join("") + ) + end + + def handle_response(response) + if ignore_http_status || (200...300).include?(response.code.to_i) + return response.body + end + raise ResponseError.new(response) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/bogus.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/bogus.rb new file mode 100644 index 000000000..3a23de6e8 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/bogus.rb @@ -0,0 +1,142 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # Bogus Gateway + class BogusGateway < Gateway + AUTHORIZATION = '53433' + + SUCCESS_MESSAGE = "Bogus Gateway: Forced success" + FAILURE_MESSAGE = "Bogus Gateway: Forced failure" + ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error" + CREDIT_ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error" + UNSTORE_ERROR_MESSAGE = "Bogus Gateway: Use trans_id ending in 1 for success, 2 for exception and anything else for error" + CAPTURE_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success" + VOID_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success" + REFUND_ERROR_MESSAGE = "Bogus Gateway: Use trans_id number ending in 1 for exception, 2 for error and anything else for success" + + self.supported_countries = ['US'] + self.supported_cardtypes = [:bogus] + self.homepage_url = 'http://example.com' + self.display_name = 'Bogus' + + def authorize(money, credit_card_or_reference, options = {}) + money = amount(money) + case normalize(credit_card_or_reference) + when /1$/ + Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION ) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true) + else + raise Error, ERROR_MESSAGE + end + end + + def purchase(money, credit_card_or_reference, options = {}) + money = amount(money) + case normalize(credit_card_or_reference) + when /1$/, AUTHORIZATION + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true) + else + raise Error, ERROR_MESSAGE + end + end + + def recurring(money, credit_card_or_reference, options = {}) + money = amount(money) + case normalize(credit_card_or_reference) + when /1$/ + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true) + else + raise Error, ERROR_MESSAGE + end + end + + def credit(money, credit_card_or_reference, options = {}) + if credit_card_or_reference.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + return refund(money, credit_card_or_reference, options) + end + + money = amount(money) + case normalize(credit_card_or_reference) + when /1$/ + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true ) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true) + else + raise Error, CREDIT_ERROR_MESSAGE + end + end + + def refund(money, reference, options = {}) + money = amount(money) + case reference + when /1$/ + raise Error, REFUND_ERROR_MESSAGE + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true) + else + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) + end + end + + def capture(money, reference, options = {}) + money = amount(money) + case reference + when /1$/ + raise Error, CAPTURE_ERROR_MESSAGE + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true) + else + Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) + end + end + + def void(reference, options = {}) + case reference + when /1$/ + raise Error, VOID_ERROR_MESSAGE + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:authorization => reference, :error => FAILURE_MESSAGE }, :test => true) + else + Response.new(true, SUCCESS_MESSAGE, {:authorization => reference}, :test => true) + end + end + + def store(credit_card_or_reference, options = {}) + case normalize(credit_card_or_reference) + when /1$/ + Response.new(true, SUCCESS_MESSAGE, {:billingid => '1'}, :test => true, :authorization => AUTHORIZATION) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:billingid => nil, :error => FAILURE_MESSAGE }, :test => true) + else + raise Error, ERROR_MESSAGE + end + end + + def unstore(reference, options = {}) + case reference + when /1$/ + Response.new(true, SUCCESS_MESSAGE, {}, :test => true) + when /2$/ + Response.new(false, FAILURE_MESSAGE, {:error => FAILURE_MESSAGE },:test => true) + else + raise Error, UNSTORE_ERROR_MESSAGE + end + end + + private + + def normalize(credit_card_or_reference) + if credit_card_or_reference.respond_to?(:number) + credit_card_or_reference.number + else + credit_card_or_reference.to_s + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree.rb new file mode 100644 index 000000000..6167d6be6 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/braintree/braintree_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BraintreeGateway < Gateway + include BraintreeCommon + + self.abstract_class = true + + def self.new(options={}) + if options.has_key?(:login) + BraintreeOrangeGateway.new(options) + else + BraintreeBlueGateway.new(options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree/braintree_common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree/braintree_common.rb new file mode 100644 index 000000000..a4d08f583 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree/braintree_common.rb @@ -0,0 +1,9 @@ +module BraintreeCommon + def self.included(base) + base.supported_countries = ['US'] + base.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + base.homepage_url = 'http://www.braintreepaymentsolutions.com' + base.display_name = 'Braintree' + base.default_currency = 'USD' + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_blue.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_blue.rb new file mode 100644 index 000000000..78f225773 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -0,0 +1,401 @@ +require File.dirname(__FILE__) + '/braintree/braintree_common' + +begin + require "braintree" +rescue LoadError + raise "Could not load the braintree gem. Use `gem install braintree` to install it." +end + +raise "Need braintree gem 2.x.y. Run `gem install braintree --version '~>2.0'` to get the correct version." unless Braintree::Version::Major == 2 + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # For more information on the Braintree Gateway please visit their + # {Developer Portal}[https://www.braintreepayments.com/developers] + # + # ==== About this implementation + # + # This implementation leverages the Braintree-authored ruby gem: + # https://github.com/braintree/braintree_ruby + # + # ==== Debugging Information + # + # Setting an ActiveMerchant +wiredump_device+ will automatically + # configure the Braintree logger (via the Braintree gem's + # configuration) when the BraintreeBlueGateway is instantiated. + # Additionally, the log level will be set to +DEBUG+. Therefore, + # all you have to do is set the +wiredump_device+ and you'll + # get your debug output from your HTTP interactions with the + # remote gateway. (Don't enable this in production.) + # + # For example: + # + # ActiveMerchant::Billing::BraintreeBlueGateway.wiredump_device = Logger.new(STDOUT) + # # => # + # + # Braintree::Configuration.logger + # # => (some other logger, created by default by the gem) + # + # Braintree::Configuration.logger.level + # # => 1 (INFO) + # + # ActiveMerchant::Billing::BraintreeBlueGateway.new(:merchant_id => 'x', :public_key => 'x', :private_key => 'x') + # + # Braintree::Configuration.logger + # # => # + # + # Braintree::Configuration.logger.level + # # => 0 (DEBUG) + # + # Alternatively, you can avoid setting the +wiredump_device+ + # and set +Braintree::Configuration.logger+ and/or + # +Braintree::Configuration.logger.level+ directly. + class BraintreeBlueGateway < Gateway + include BraintreeCommon + + self.display_name = 'Braintree (Blue Platform)' + + def initialize(options = {}) + requires!(options, :merchant_id, :public_key, :private_key) + @merchant_account_id = options[:merchant_account_id] + + super + + Braintree::Configuration.merchant_id = options[:merchant_id] + Braintree::Configuration.public_key = options[:public_key] + Braintree::Configuration.private_key = options[:private_key] + Braintree::Configuration.environment = (options[:environment] || (test? ? :sandbox : :production)).to_sym + Braintree::Configuration.custom_user_agent = "ActiveMerchant #{ActiveMerchant::VERSION}" + + if wiredump_device + Braintree::Configuration.logger = ((Logger === wiredump_device) ? wiredump_device : Logger.new(wiredump_device)) + Braintree::Configuration.logger.level = Logger::DEBUG + else + Braintree::Configuration.logger.level = Logger::WARN + end + end + + def authorize(money, credit_card_or_vault_id, options = {}) + create_transaction(:sale, money, credit_card_or_vault_id, options) + end + + def capture(money, authorization, options = {}) + commit do + result = Braintree::Transaction.submit_for_settlement(authorization, amount(money).to_s) + Response.new(result.success?, message_from_result(result)) + end + end + + def purchase(money, credit_card_or_vault_id, options = {}) + authorize(money, credit_card_or_vault_id, options.merge(:submit_for_settlement => true)) + end + + def credit(money, credit_card_or_vault_id, options = {}) + create_transaction(:credit, money, credit_card_or_vault_id, options) + end + + def refund(*args) + # legacy signature: #refund(transaction_id, options = {}) + # new signature: #refund(money, transaction_id, options = {}) + money, transaction_id, _ = extract_refund_args(args) + money = amount(money).to_s if money + + commit do + result = Braintree::Transaction.refund(transaction_id, money) + Response.new(result.success?, message_from_result(result), + {:braintree_transaction => (transaction_hash(result.transaction) if result.success?)}, + {:authorization => (result.transaction.id if result.success?)} + ) + end + end + + def void(authorization, options = {}) + commit do + result = Braintree::Transaction.void(authorization) + Response.new(result.success?, message_from_result(result), + {:braintree_transaction => (transaction_hash(result.transaction) if result.success?)}, + {:authorization => (result.transaction.id if result.success?)} + ) + end + end + + def store(creditcard, options = {}) + commit do + parameters = { + :first_name => creditcard.first_name, + :last_name => creditcard.last_name, + :email => options[:email], + :credit_card => { + :number => creditcard.number, + :cvv => creditcard.verification_value, + :expiration_month => creditcard.month.to_s.rjust(2, "0"), + :expiration_year => creditcard.year.to_s + } + } + result = Braintree::Customer.create(merge_credit_card_options(parameters, options)) + Response.new(result.success?, message_from_result(result), + { + :braintree_customer => (customer_hash(result.customer) if result.success?), + :customer_vault_id => (result.customer.id if result.success?) + }, + :authorization => (result.customer.id if result.success?) + ) + end + end + + def update(vault_id, creditcard, options = {}) + braintree_credit_card = nil + commit do + braintree_credit_card = Braintree::Customer.find(vault_id).credit_cards.detect { |cc| cc.default? } + return Response.new(false, 'Braintree::NotFoundError') if braintree_credit_card.nil? + + options.merge!(:update_existing_token => braintree_credit_card.token) + credit_card_params = merge_credit_card_options({ + :credit_card => { + :number => creditcard.number, + :cvv => creditcard.verification_value, + :expiration_month => creditcard.month.to_s.rjust(2, "0"), + :expiration_year => creditcard.year.to_s + } + }, options)[:credit_card] + + result = Braintree::Customer.update(vault_id, + :first_name => creditcard.first_name, + :last_name => creditcard.last_name, + :email => options[:email], + :credit_card => credit_card_params + ) + Response.new(result.success?, message_from_result(result), + :braintree_customer => (customer_hash(Braintree::Customer.find(vault_id)) if result.success?), + :customer_vault_id => (result.customer.id if result.success?) + ) + end + end + + def unstore(customer_vault_id) + commit do + Braintree::Customer.delete(customer_vault_id) + Response.new(true, "OK") + end + end + alias_method :delete, :unstore + + private + + def merge_credit_card_options(parameters, options) + valid_options = {} + options.each do |key, value| + valid_options[key] = value if [:update_existing_token, :verify_card, :verification_merchant_account_id].include?(key) + end + + parameters[:credit_card] ||= {} + parameters[:credit_card].merge!(:options => valid_options) + parameters[:credit_card][:billing_address] = map_address(options[:billing_address]) if options[:billing_address] + parameters + end + + def map_address(address) + return {} if address.nil? + mapped = { + :street_address => address[:address1], + :extended_address => address[:address2], + :company => address[:company], + :locality => address[:city], + :region => address[:state], + :postal_code => address[:zip], + } + if(address[:country] || address[:country_code_alpha2]) + mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2]) + elsif address[:country_name] + mapped[:country_name] = address[:country_name] + elsif address[:country_code_alpha3] + mapped[:country_code_alpha3] = address[:country_code_alpha3] + elsif address[:country_code_numeric] + mapped[:country_code_numeric] = address[:country_code_numeric] + end + mapped + end + + def commit(&block) + yield + rescue Braintree::BraintreeError => ex + Response.new(false, ex.class.to_s) + end + + def message_from_result(result) + if result.success? + "OK" + elsif result.errors.size == 0 && result.credit_card_verification + "Processor declined: #{result.credit_card_verification.processor_response_text} (#{result.credit_card_verification.processor_response_code})" + else + result.errors.map { |e| "#{e.message} (#{e.code})" }.join(" ") + end + end + + def create_transaction(transaction_type, money, credit_card_or_vault_id, options) + transaction_params = create_transaction_parameters(money, credit_card_or_vault_id, options) + + commit do + result = Braintree::Transaction.send(transaction_type, transaction_params) + response_params, response_options, avs_result, cvv_result = {}, {}, {}, {} + if result.success? + response_params[:braintree_transaction] = transaction_hash(result.transaction) + response_params[:customer_vault_id] = result.transaction.customer_details.id + response_options[:authorization] = result.transaction.id + end + if result.transaction + response_options[:avs_result] = { + :code => nil, :message => nil, + :street_match => result.transaction.avs_street_address_response_code, + :postal_match => result.transaction.avs_postal_code_response_code + } + response_options[:cvv_result] = result.transaction.cvv_response_code + if result.transaction.status == "gateway_rejected" + message = "Transaction declined - gateway rejected" + else + message = "#{result.transaction.processor_response_code} #{result.transaction.processor_response_text}" + end + else + message = message_from_result(result) + end + response = Response.new(result.success?, message, response_params, response_options) + response.cvv_result['message'] = '' + response + end + end + + def extract_refund_args(args) + options = args.extract_options! + + # money, transaction_id, options + if args.length == 1 # legacy signature + return nil, args[0], options + elsif args.length == 2 + return args[0], args[1], options + else + raise ArgumentError, "wrong number of arguments (#{args.length} for 2)" + end + end + + def customer_hash(customer) + credit_cards = customer.credit_cards.map do |cc| + { + "bin" => cc.bin, + "expiration_date" => cc.expiration_date, + "token" => cc.token, + "last_4" => cc.last_4, + "card_type" => cc.card_type, + "masked_number" => cc.masked_number + } + end + + { + "email" => customer.email, + "first_name" => customer.first_name, + "last_name" => customer.last_name, + "credit_cards" => credit_cards, + "id" => customer.id + } + end + + def transaction_hash(transaction) + if transaction.vault_customer + vault_customer = { + } + vault_customer["credit_cards"] = transaction.vault_customer.credit_cards.map do |cc| + { + "bin" => cc.bin + } + end + else + vault_customer = nil + end + + customer_details = { + "id" => transaction.customer_details.id, + "email" => transaction.customer_details.email + } + + billing_details = { + "street_address" => transaction.billing_details.street_address, + "extended_address" => transaction.billing_details.extended_address, + "company" => transaction.billing_details.company, + "locality" => transaction.billing_details.locality, + "region" => transaction.billing_details.region, + "postal_code" => transaction.billing_details.postal_code, + "country_name" => transaction.billing_details.country_name, + } + + shipping_details = { + "street_address" => transaction.shipping_details.street_address, + "extended_address" => transaction.shipping_details.extended_address, + "company" => transaction.shipping_details.company, + "locality" => transaction.shipping_details.locality, + "region" => transaction.shipping_details.region, + "postal_code" => transaction.shipping_details.postal_code, + "country_name" => transaction.shipping_details.country_name, + } + credit_card_details = { + "masked_number" => transaction.credit_card_details.masked_number, + "bin" => transaction.credit_card_details.bin, + "last_4" => transaction.credit_card_details.last_4, + "card_type" => transaction.credit_card_details.card_type, + "token" => transaction.credit_card_details.token + } + + { + "order_id" => transaction.order_id, + "status" => transaction.status, + "credit_card_details" => credit_card_details, + "customer_details" => customer_details, + "billing_details" => billing_details, + "shipping_details" => shipping_details, + "vault_customer" => vault_customer, + "merchant_account_id" => transaction.merchant_account_id + } + end + + def create_transaction_parameters(money, credit_card_or_vault_id, options) + parameters = { + :amount => amount(money).to_s, + :order_id => options[:order_id], + :customer => { + :id => options[:store] == true ? "" : options[:store], + :email => options[:email] + }, + :options => { + :store_in_vault => options[:store] ? true : false, + :submit_for_settlement => options[:submit_for_settlement] + } + } + + if merchant_account_id = (options[:merchant_account_id] || @merchant_account_id) + parameters[:merchant_account_id] = merchant_account_id + end + + if options[:recurring] + parameters[:recurring] = true + end + + if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer) + parameters[:customer_id] = credit_card_or_vault_id + else + parameters[:customer].merge!( + :first_name => credit_card_or_vault_id.first_name, + :last_name => credit_card_or_vault_id.last_name + ) + parameters[:credit_card] = { + :number => credit_card_or_vault_id.number, + :cvv => credit_card_or_vault_id.verification_value, + :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, "0"), + :expiration_year => credit_card_or_vault_id.year.to_s + } + end + parameters[:billing] = map_address(options[:billing_address]) if options[:billing_address] + parameters[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address] + parameters + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_orange.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_orange.rb new file mode 100644 index 000000000..16e24beea --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/braintree_orange.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/smart_ps.rb' +require File.dirname(__FILE__) + '/braintree/braintree_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class BraintreeOrangeGateway < SmartPs + include BraintreeCommon + + self.display_name = 'Braintree (Orange Platform)' + + self.live_url = self.test_url = 'https://secure.braintreepaymentgateway.com/api/transact.php' + + def add_processor(post, options) + post[:processor_id] = options[:processor] unless options[:processor].nil? + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_save.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_save.rb new file mode 100644 index 000000000..7bd9ee8e4 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_save.rb @@ -0,0 +1,23 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CardSaveGateway < IridiumGateway + #CardSave lets you handle failovers on payments by providing 3 gateways in case one happens to be down + #URLS = ['https://gw1.cardsaveonlinepayments.com:4430/','https://gw2.cardsaveonlinepayments.com:4430/','https://gw3.cardsaveonlinepayments.com:4430/'] + + self.money_format = :cents + self.default_currency = 'GBP' + self.supported_cardtypes = [ :visa, :switch, :maestro, :master, :solo, :american_express, :jcb ] + self.supported_countries = [ 'GB' ] + self.homepage_url = 'http://www.cardsave.net/' + self.display_name = 'CardSave' + + def initialize(options={}) + super + @test_url = 'https://gw1.cardsaveonlinepayments.com:4430/' + @live_url = 'https://gw1.cardsaveonlinepayments.com:4430/' + end + + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream.rb new file mode 100644 index 000000000..30dc8897f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream.rb @@ -0,0 +1,225 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # + # CardStream supports the following credit cards, which are auto-detected by + # the gateway based on the card number used: + # * AM American Express + # * Diners Club + # * Electron + # * JCB + # * UK Maestro + # * Maestro International + # * Mastercard + # * Solo + # * Style + # * Switch + # * Visa Credit + # * Visa Debit + # * Visa Purchasing + # + class CardStreamGateway < Gateway + self.live_url = self.test_url = 'https://gateway.cardstream.com/process.ashx' + + self.money_format = :cents + self.default_currency = 'GBP' + self.supported_countries = ['GB'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :solo, :switch] + self.homepage_url = 'http://www.cardstream.com/' + self.display_name = 'CardStream' + + APPROVED = '00' + + CURRENCY_CODES = { + "AUD"=> '036', + "CAD"=> '124', + "CZK"=> '203', + "DKK"=> '208', + "HKD"=> '344', + "ICK"=> '352', + "JPY"=> '392', + "NOK"=> '578', + "SGD"=> '702', + "SEK"=> '752', + "CHF"=> '756', + "GBP"=> '826', + "USD"=> '840', + "EUR"=> '978' + } + + TRANSACTIONS = { + :purchase => 'ESALE_KEYED', + :refund => 'EREFUND_KEYED', + :authorization => 'ESALE_KEYED' + } + + CVV_CODE = { + '0' => 'U', + '1' => 'P', + '2' => 'M', + '4' => 'N' + } + + # 0 - No additional information available. + # 1 - Postcode not checked. + # 2 - Postcode matched. + # 4 - Postcode not matched. + # 8 - Postcode partially matched. + AVS_POSTAL_MATCH = { + "0" => nil, + "1" => nil, + "2" => "Y", + "4" => "N", + "8" => "N" + } + + # 0 - No additional information available. + # 1 - Address numeric not checked. + # 2 - Address numeric matched. + # 4 - Address numeric not matched. + # 8 - Address numeric partially matched. + AVS_STREET_MATCH = { + "0" => nil, + "1" => nil, + "2" => "Y", + "4" => "N", + "8" => "N" + } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card, options = {}) + requires!(options, :order_id) + + post = {} + + add_amount(post, money, options) + add_invoice(post, money, credit_card, options) + add_credit_card(post, credit_card) + add_address(post, options) + add_customer_data(post, options) + + commit(:purchase, post) + end + + private + + def add_amount(post, money, options) + add_pair(post, :Amount, amount(money), :required => true) + add_pair(post, :CurrencyCode, currency_code(options[:currency] || currency(money)), :required => true) + end + + def add_customer_data(post, options) + add_pair(post, :BillingEmail, options[:email]) + add_pair(post, :BillingPhoneNumber, options[:phone]) + end + + def add_address(post, options) + address = options[:billing_address] || options[:address] + + return if address.nil? + + add_pair(post, :BillingStreet, address[:address1]) + add_pair(post, :BillingHouseNumber, address[:address2]) + add_pair(post, :BillingCity, address[:city]) + add_pair(post, :BillingState, address[:state]) + add_pair(post, :BillingPostCode, address[:zip]) + end + + def add_invoice(post, money, credit_card, options) + add_pair(post, :TransactionUnique, options[:order_id], :required => true) + add_pair(post, :OrderDesc, options[:description] || options[:order_id], :required => true) + + if [ 'american_express', 'diners_club' ].include?(card_brand(credit_card).to_s) + add_pair(post, :AEIT1Quantity, 1) + add_pair(post, :AEIT1Description, (options[:description] || options[:order_id]).slice(0, 15)) + add_pair(post, :AEIT1GrossValue, amount(money)) + end + end + + def add_credit_card(post, credit_card) + add_pair(post, :CardName, credit_card.name, :required => true) + add_pair(post, :CardNumber, credit_card.number, :required => true) + + add_pair(post, :ExpiryDateMM, format(credit_card.month, :two_digits), :required => true) + add_pair(post, :ExpiryDateYY, format(credit_card.year, :two_digits), :required => true) + + if requires_start_date_or_issue_number?(credit_card) + add_pair(post, :StartDateMM, format(credit_card.start_month, :two_digits)) + add_pair(post, :StartDateYY, format(credit_card.start_year, :two_digits)) + + add_pair(post, :IssueNumber, credit_card.issue_number) + end + + add_pair(post, :CV2, credit_card.verification_value) + end + + def commit(action, parameters) + response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) + + Response.new(response[:response_code] == APPROVED, message_from(response), response, + :test => test?, + :authorization => response[:cross_reference], + :cvv_result => CVV_CODE[ response[:avscv2_response_code].to_s[0, 1] ], + :avs_result => { + :street_match => AVS_STREET_MATCH[ response[:avscv2_response_code].to_s[2, 1] ], + :postal_match => AVS_POSTAL_MATCH[ response[:avscv2_response_code].to_s[1, 1] ] + } + ) + end + + def message_from(results) + results[:response_code] == APPROVED ? "APPROVED" : results[:message] + end + + def post_data(action, parameters = {}) + parameters.update( + :MerchantPassword => @options[:password], + :MerchantID => @options[:login], + :MessageType => TRANSACTIONS[action], + :CallBack => "disable", + :DuplicateDelay => "0", + :EchoCardType => "YES", + :EchoAmount => "YES", + :EchoAVSCV2ResponseCode => "YES", + :ReturnAVSCV2Message => "YES", + :CountryCode => '826' # 826 for UK based merchant + ) + + add_pair(parameters, :Dispatch, action == :authorization ? "LATER" : "NOW") + + parameters.collect { |key, value| "VP#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + # VPCrossReference + # The value in VPCrossReference on a success transaction will contain + # a unique reference that you may use to run future transactions. + # Please note that cross reference transactions must come a static IP + # addressed that has been pre-registered with Cardstream. To + # register an IP address please send it to support@cardstream.com + # with your Cardstream issued merchant ID and it will be added to + # your account. + def parse(body) + result = {} + pairs = body.split("&") + pairs.each do |pair| + a = pair.split("=") + result[a[0].gsub(/^VP/,'').underscore.to_sym] = a[1] + end + + result + end + + def currency_code(currency) + CURRENCY_CODES[currency] + end + + def add_pair(post, key, value, options = {}) + post[key] = value if !value.blank? || options[:required] + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream_modern.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream_modern.rb new file mode 100644 index 000000000..1d3393fa2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/card_stream_modern.rb @@ -0,0 +1,155 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CardStreamModernGateway < Gateway + self.test_url = self.live_url = 'https://gateway.cardstream.com/direct/' + self.money_format = :cents + self.default_currency = 'GBP' + self.supported_countries = ['GB'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :solo, :switch] + self.homepage_url = 'http://www.cardstream.com/' + self.display_name = 'CardStream' + + def initialize(options = {}) + requires!(options, :login) + if(options[:threeDSRequired]) + @threeDSRequired = options[:threeDSRequired] + else + @threeDSRequired = 'N' + end + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_amount(post, money, options) + add_invoice(post, creditcard, money, options) + add_creditcard(post, creditcard) + add_address(post, creditcard, options) + add_customer_data(post, options) + commit('PREAUTH', post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_amount(post, money, options) + add_invoice(post, creditcard, money, options) + add_creditcard(post, creditcard) + add_address(post, creditcard, options) + add_customer_data(post, options) + commit('SALE', post) + end + + def capture(money, authorization, options = {}) + post = {} + add_pair(post, :xref, authorization) + add_amount(post, money, options) + commit('SALE', post) + end + + def refund(money, authorization, options = {}) + post = {} + add_pair(post, :xref, authorization) + add_amount(post, money, options) + commit('REFUND', post) + end + + def void(authorization, options = {}) + post = {} + add_pair(post, :xref, authorization) + commit('REFUND', post) + end + + private + + def add_amount(post, money, options) + add_pair(post, :amount, amount(money), :required => true) + add_pair(post, :currencyCode, options[:currency] || self.default_currency) + end + + def add_customer_data(post, options) + address = options[:billing_address] || options[:address] + add_pair(post, :customerPostCode, address[:zip]) + add_pair(post, :customerEmail, options[:email]) + add_pair(post, :customerPhone, options[:phone]) + end + + def add_address(post, creditcard, options) + address = options[:billing_address] || options[:address] + + return if address.nil? + + add_pair(post, :customerAddress, address[:address1] + " " + (address[:address2].nil? ? "" : address[:address2]) ) + add_pair(post, :customerPostCode, address[:zip]) + end + + def add_invoice(post, credit_card, money, options) + add_pair(post, :transactionUnique, options[:order_id], :required => true) + add_pair(post, :orderRef, options[:description] || options[:order_id], :required => true) + if [ 'american_express', 'diners_club' ].include?(card_brand(credit_card).to_s) + add_pair(post, :item1Quantity, 1) + add_pair(post, :item1Description, (options[:description] || options[:order_id]).slice(0, 15)) + add_pair(post, :item1GrossValue, amount(money)) + end + end + + def add_creditcard(post, credit_card) + add_pair(post, :customerName, credit_card.name, :required => true) + add_pair(post, :cardNumber, credit_card.number, :required => true) + + add_pair(post, :cardExpiryMonth, format(credit_card.month, :two_digits), :required => true) + add_pair(post, :cardExpiryYear, format(credit_card.year, :two_digits), :required => true) + + if requires_start_date_or_issue_number?(credit_card) + add_pair(post, :cardStartMonth, format(credit_card.start_month, :two_digits)) + add_pair(post, :cardStartYear, format(credit_card.start_year, :two_digits)) + + add_pair(post, :cardIssueNumber, credit_card.issue_number) + end + + add_pair(post, :cardCVV, credit_card.verification_value) + end + + def parse(body) + result = {} + pairs = body.split("&") + pairs.each do |pair| + a = pair.split("=") + result[a[0].to_sym] = CGI.unescape(a[1]) + end + result + end + + def commit(action, parameters) + response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) + + Response.new(response[:responseCode] == "0", + response[:responseCode] == "0" ? "APPROVED" : response[:responseMessage], + response, + :test => test?, + :authorization => response[:xref], + :avs_result => { + :street_match => response[:addressCheck], + :postal_match => response[:postcodeCheck], + }, + :cvv_result => response[:cv2Check] + ) + end + + def post_data(action, parameters = {}) + parameters.update( + :merchantID => @options[:login], + :action => action, + :type => '1', #Ecommerce + :countryCode => self.supported_countries[0], + :threeDSRequired => @threeDSRequired #Disable 3d secure by default + ) + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def add_pair(post, key, value, options = {}) + post[key] = value if !value.blank? || options[:required] + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cc5.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cc5.rb new file mode 100644 index 000000000..4d315243c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cc5.rb @@ -0,0 +1,156 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # CC5 API is used by many banks in Turkey. Extend this base class to provide + # concrete implementations. + class CC5Gateway < Gateway + self.default_currency = 'TRY' + + CURRENCY_CODES = { + 'TRY' => 949, + 'YTL' => 949, + 'TRL' => 949, + 'TL' => 949, + 'USD' => 840, + 'EUR' => 978, + 'GBP' => 826, + 'JPY' => 392 + } + + def initialize(options = {}) + requires!(options, :login, :password, :client_id) + super + end + + def purchase(money, creditcard, options = {}) + commit(build_sale_request('Auth', money, creditcard, options)) + end + + def authorize(money, creditcard, options = {}) + commit(build_sale_request('PreAuth', money, creditcard, options)) + end + + def capture(money, authorization, options = {}) + commit(build_capture_request(money, authorization, options)) + end + + protected + + def build_sale_request(type, money, creditcard, options = {}) + requires!(options, :order_id) + + xml = Builder::XmlMarkup.new :indent => 2 + + xml.tag! 'CC5Request' do + add_login_tags(xml) + xml.tag! 'OrderId', options[:order_id] + xml.tag! 'Type', type + xml.tag! 'Number', creditcard.number + xml.tag! 'Expires', [format(creditcard.month, :two_digits), format(creditcard.year, :two_digits)].join('/') + xml.tag! 'Cvv2Val', creditcard.verification_value + add_amount_tags(money, options, xml) + xml.tag! 'Email', options[:email] if options[:email] + + if(address = (options[:billing_address] || options[:address])) + xml.tag! 'BillTo' do + add_address(xml, address) + end + xml.tag! 'ShipTo' do + add_address(xml, address) + end + end + + end + + xml.target! + end + + def build_capture_request(money, authorization, options = {}) + xml = Builder::XmlMarkup.new :indent => 2 + + xml.tag! 'CC5Request' do + add_login_tags(xml) + xml.tag! 'OrderId', authorization + xml.tag! 'Type', 'PostAuth' + add_amount_tags(money, options, xml) + end + end + + def add_address(xml, address) + xml.tag! 'Name', normalize(address[:name]) + xml.tag! 'Street1', normalize(address[:address1]) + xml.tag! 'Street2', normalize(address[:address2]) if address[:address2] + xml.tag! 'City', normalize(address[:city]) + xml.tag! 'PostalCode', address[:zip] + xml.tag! 'Country', normalize(address[:country]) + xml.tag! 'Company', normalize(address[:company]) + xml.tag! 'TelVoice', address[:phone].to_s.gsub(/[^0-9]/, '') if address[:phone] + end + + def add_login_tags(xml) + xml.tag! 'Name', @options[:login] + xml.tag! 'Password', @options[:password] + xml.tag! 'ClientId', @options[:client_id] + xml.tag! 'Mode', (test? ? 'T' : 'P') + end + + def add_amount_tags(money, options, xml) + xml.tag! 'Total', amount(money) + xml.tag! 'Currency', currency_code(options[:currency] || currency(money)) + end + + def currency_code(currency) + (CURRENCY_CODES[currency] || CURRENCY_CODES[default_currency]) + end + + def commit(request) + raw_response = ssl_post((test? ? self.test_url : self.live_url), "DATA=" + request) + + response = parse(raw_response) + + success = success?(response) + + Response.new( + success, + (success ? 'Approved' : "Declined (Reason: #{response[:proc_return_code]} - #{response[:err_msg]})"), + response, + :test => test?, + :authorization => response[:order_id] + ) + end + + def parse(body) + xml = REXML::Document.new(body) + + response = {} + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each{|element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def success?(response) + (response[:response] == "Approved") + end + + def normalize(text) + return unless text + + if ActiveSupport::Inflector.method(:transliterate).arity == -2 + ActiveSupport::Inflector.transliterate(text,'') + elsif RUBY_VERSION >= '1.9' + text.gsub(/[^\x00-\x7F]+/, '') + else + ActiveSupport::Inflector.transliterate(text).to_s + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/certo_direct.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/certo_direct.rb new file mode 100644 index 000000000..a77fc780a --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/certo_direct.rb @@ -0,0 +1,277 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class CertoDirectGateway < Gateway + self.live_url = self.test_url = "https://secure.certodirect.com/gateway/process/v2" + + self.supported_countries = [ + "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", "GB" + ] + + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.certodirect.com/' + self.display_name = 'CertoDirect' + + # Creates a new CertoDirectGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- The CertoDirect Shop ID (REQUIRED) + # * :password -- The CertoDirect Shop Password. (REQUIRED) + # * :test -- +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 + + # Perform a purchase, which is essentially an authorization and capture in a single operation. + # + # ==== Parameters + # + # * money -- The amount to be purchased as an Integer value in cents. + # * credit_card -- The CreditCard details for the transaction. + # * options -- A hash of optional parameters. + def purchase(money, credit_card, options = {}) + requires!(options, :email, :currency, :ip, :description) + + commit(build_sale_request(money, credit_card, options)) + end + + # Refund a transaction. + # + # This transaction indicates to the gateway that + # money should flow from the merchant to the customer. + # + # ==== Parameters + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * identification -- The ID of the original order against which the refund is being issued. + # * options -- A hash of parameters. + def refund(money, identification, options = {}) + requires!(options, :reason) + + commit(build_refund_request(money, identification, options)) + end + + # Performs an authorization, which reserves the funds on the customer's credit card, but does not + # charge the card. + # + # ==== Parameters + # + # * money -- The amount to be authorized as an Integer value in cents. + # * credit_card -- The CreditCard details for the transaction. + # * options -- A hash of optional parameters. + def authorize(money, credit_card, options = {}) + requires!(options, :email, :currency, :ip, :description) + + commit(build_authorize_request(money, credit_card, options)) + end + + # Captures the funds from an authorized transaction. + # + # ==== Parameters + # + # * money -- The amount to be captured as an Integer value in cents. + # * identification -- The authorization returned from the previous authorize request. + def capture(money, identification, options = {}) + commit(build_capture_request(money, identification)) + end + + # Void a previous transaction + # + # ==== Parameters + # + # * money -- The amount to be captured as an Integer value in cents. + # * identification - The authorization returned from the previous authorize request. + def void(money, identification, options = {}) + commit(build_void_request(money, identification)) + end + + # Create a recurring payment. + # + # ==== Parameters + # + # * options -- A hash of parameters. + # + # ==== Options + # + def recurring(identification, options={}) + commit(build_recurring_request(identification, options)) + end + + + private + + def commit(request_xml) + begin + response = Hash.from_xml(ssl_post(self.live_url, request_xml, headers)) + Response.new(success?(response), + message(response), + response, + :test => test?, + :authorization => authorization(response)) + rescue ResponseError => e + raise e unless e.response.code == '403' + response = Hash.from_xml(e.response.body)['response'] + Response.new(false, message(response), {}, :test => test?) + end + end + + def build_sale_request(money, credit_card, options) + build_request_xml('Sale') do |xml| + add_order(xml, money, credit_card, options) + end + end + + def build_authorize_request(money, credit_card, options) + build_request_xml('Authorize') do |xml| + add_order(xml, money, credit_card, options) + end + end + + def build_refund_request(money, identification, options) + build_request_xml('Refund') do |xml| + add_reference_info(xml, money, identification, options) + xml.tag! 'reason', options[:reason] + end + end + + def build_capture_request(money, identification) + build_request_xml('Capture') do |xml| + add_reference_info(xml, money, identification, options) + end + end + + def build_void_request(money, identification) + build_request_xml('Void') do |xml| + add_reference_info(xml, money, identification, options) + end + end + + def build_recurring_request(identification, options) + build_request_xml('Sale') do |xml| + xml.tag! 'order' do |xml| + xml.tag!('test', 'true') if test? + xml.tag! 'initial_order_id', identification, :type => 'integer' + + add_order_details(xml, options[:amount], options) if has_any_order_details_key?(options) + add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address] + add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address] + end + end + end + + def build_request_xml(type, &block) + xml = Builder::XmlMarkup.new(:indent => 2) + xml.tag! 'transaction' do + xml.tag! 'type', type + yield(xml) + end + xml.target! + end + + def add_order(xml, money, credit_card, options) + xml.tag! 'order' do + xml.tag!('test', 'true') if test? + + xml.tag!('return_url', options[:return_url]) if options[:return_url] + xml.tag!('cancel_url', options[:cancel_url]) if options[:cancel_url] + + xml.tag! 'payment_method_type', 'CreditCard' + xml.tag! 'payment_method' do + xml.tag! 'number', credit_card.number + xml.tag! 'exp_month', "%02i" % credit_card.month + xml.tag! 'exp_year', credit_card.year + xml.tag! 'holder', credit_card.name + xml.tag! 'verification_value', credit_card.verification_value + end + + add_order_details(xml, money, options) + add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address] + add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address] + end + end + + def add_order_details(xml, money, options) + xml.tag! 'details' do + xml.tag!('amount', localized_amount(money, options[:currency]), :type => 'decimal') if money + xml.tag!('currency', options[:currency]) if options[:currency] + xml.tag!('email', options[:email]) if options[:email] + xml.tag!('ip', options[:ip]) if options[:ip] + xml.tag!('shipping', options[:shipping], :type => 'decimal') if options[:shipping] + xml.tag!('description', options[:description]) if options[:description] + end + end + + def add_reference_info(xml, money, identification, options) + xml.tag! 'order_id', identification, :type => 'integer' + xml.tag! 'amount', localized_amount(money, options[:currency]), :type => 'decimal' + end + + def add_address(xml, address_type, address) + xml.tag! address_type do + xml.tag! 'address', address[:address1] + xml.tag! 'city', address[:city] + xml.tag! 'country', address[:country] + xml.tag! 'first_name', address[:first_name] + xml.tag! 'last_name', address[:last_name] + xml.tag! 'state', address[:state] + xml.tag! 'phone', address[:phone] + xml.tag! 'zip', address[:zip] + end + end + + def has_any_order_details_key?(options) + [ :currency, :amount, :email, :ip, :shipping, :description ].any? do |key| + options.has_key?(key) + end + end + + def success?(response) + %w(completed forwarding).include?(state(response)) and + status(response) == 'success' + end + + def error?(response) + response['errors'] + end + + def state(response) + response["transaction"].try(:[], "state") + end + + def status(response) + response['transaction'].try(:[], 'response').try(:[], 'status') + end + + def authorization(response) + error?(response) ? nil : response["transaction"]["order"]['id'].to_s + end + + def message(response) + return response['errors'].join('; ') if error?(response) + + if state(response) == 'completed' + response["transaction"]["response"]["message"] + else + response['transaction']['message'] + end + end + + def headers + { 'authorization' => basic_auth, + 'Accept' => 'application/xml', + 'Content-Type' => 'application/xml' } + end + + def basic_auth + 'Basic ' + ["#{@options[:login]}:#{@options[:password]}"].pack('m').delete("\r\n") + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cyber_source.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cyber_source.rb new file mode 100644 index 000000000..d9110cd47 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/cyber_source.rb @@ -0,0 +1,614 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # See the remote and mocked unit test files for example usage. Pay special + # attention to the contents of the options hash. + # + # Initial setup instructions can be found in + # http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf + # + # Debugging + # If you experience an issue with this gateway be sure to examine the + # transaction information from a general transaction search inside the + # CyberSource Business Center for the full error messages including field + # names. + # + # Important Notes + # * For checks you can purchase and store. + # * AVS and CVV only work against the production server. You will always + # get back X for AVS and no response for CVV against the test server. + # * Nexus is the list of states or provinces where you have a physical + # presence. Nexus is used to calculate tax. Leave blank to tax everyone. + # * If you want to calculate VAT for overseas customers you must supply a + # registration number in the options hash as vat_reg_number. + # * productCode is a value in the line_items hash that is used to tell + # CyberSource what kind of item you are selling. It is used when + # calculating tax/VAT. + # * All transactions use dollar values. + class CyberSourceGateway < Gateway + self.test_url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor' + self.live_url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor' + + XSD_VERSION = "1.69" + + # visa, master, american_express, discover + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.homepage_url = 'http://www.cybersource.com' + self.display_name = 'CyberSource' + + # map credit card to the CyberSource expected representation + @@credit_card_codes = { + :visa => '001', + :master => '002', + :american_express => '003', + :discover => '004' + } + + # map response codes to something humans can read + @@response_codes = { + :r100 => "Successful transaction", + :r101 => "Request is missing one or more required fields" , + :r102 => "One or more fields contains invalid data", + :r150 => "General failure", + :r151 => "The request was received but a server time-out occurred", + :r152 => "The request was received, but a service timed out", + :r200 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check", + :r201 => "The issuing bank has questions about the request", + :r202 => "Expired card", + :r203 => "General decline of the card", + :r204 => "Insufficient funds in the account", + :r205 => "Stolen or lost card", + :r207 => "Issuing bank unavailable", + :r208 => "Inactive card or card not authorized for card-not-present transactions", + :r209 => "American Express Card Identifiction Digits (CID) did not match", + :r210 => "The card has reached the credit limit", + :r211 => "Invalid card verification number", + :r221 => "The customer matched an entry on the processor's negative file", + :r230 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check", + :r231 => "Invalid account number", + :r232 => "The card type is not accepted by the payment processor", + :r233 => "General decline by the processor", + :r234 => "A problem exists with your CyberSource merchant configuration", + :r235 => "The requested amount exceeds the originally authorized amount", + :r236 => "Processor failure", + :r237 => "The authorization has already been reversed", + :r238 => "The authorization has already been captured", + :r239 => "The requested transaction amount must match the previous transaction amount", + :r240 => "The card type sent is invalid or does not correlate with the credit card number", + :r241 => "The request ID is invalid", + :r242 => "You requested a capture, but there is no corresponding, unused authorization record.", + :r243 => "The transaction has already been settled or reversed", + :r244 => "The bank account number failed the validation check", + :r246 => "The capture or credit is not voidable because the capture or credit information has already been submitted to your processor", + :r247 => "You requested a credit for a capture that was previously voided", + :r250 => "The request was received, but a time-out occurred with the payment processor", + :r254 => "Your CyberSource account is prohibited from processing stand-alone refunds", + :r255 => "Your CyberSource account is not configured to process the service in the country you specified" + } + + # These are the options that can be used when creating a new CyberSource + # Gateway object. + # + # :login => your username + # + # :password => the transaction key you generated in the Business Center + # + # :test => true sets the gateway to test mode + # + # :vat_reg_number => your VAT registration number + # + # :nexus => "WI CA QC" sets the states/provinces where you have a physical + # presense for tax purposes + # + # :ignore_avs => true don't want to use AVS so continue processing even + # if AVS would have failed + # + # :ignore_cvv => true don't want to use CVV so continue processing even + # if CVV would have failed + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Request an authorization for an amount from CyberSource + # + # You must supply an :order_id in the options hash + def authorize(money, creditcard_or_reference, options = {}) + requires!(options, :order_id) + setup_address_hash(options) + commit(build_auth_request(money, creditcard_or_reference, options), options ) + end + + def auth_reversal(money, identification, options = {}) + commit(build_auth_reversal_request(money, identification, options), options) + end + + # Capture an authorization that has previously been requested + def capture(money, authorization, options = {}) + setup_address_hash(options) + commit(build_capture_request(money, authorization, options), options) + end + + # Purchase is an auth followed by a capture + # You must supply an order_id in the options hash + def purchase(money, payment_method_or_reference, options = {}) + requires!(options, :order_id) + setup_address_hash(options) + commit(build_purchase_request(money, payment_method_or_reference, options), options) + end + + def void(identification, options = {}) + commit(build_void_request(identification, options), options) + end + + def refund(money, identification, options = {}) + commit(build_refund_request(money, identification, options), options) + end + + # Adds credit to a subscription (stand alone credit). + def credit(money, reference, options = {}) + requires!(options, :order_id) + commit(build_credit_request(money, reference, options), options) + end + + # Stores a customer subscription/profile with type "on-demand". + # To charge the card while creating a profile, pass + # options[:setup_fee] => money + def store(payment_method, options = {}) + requires!(options, :order_id) + setup_address_hash(options) + commit(build_create_subscription_request(payment_method, options), options) + end + + # Updates a customer subscription/profile + def update(reference, creditcard, options = {}) + requires!(options, :order_id) + setup_address_hash(options) + commit(build_update_subscription_request(reference, creditcard, options), options) + end + + # Removes a customer subscription/profile + def unstore(reference, options = {}) + requires!(options, :order_id) + commit(build_delete_subscription_request(reference, options), options) + end + + # Retrieves a customer subscription/profile + def retrieve(reference, options = {}) + requires!(options, :order_id) + commit(build_retrieve_subscription_request(reference, options), options) + end + + # CyberSource requires that you provide line item information for tax + # calculations. If you do not have prices for each item or want to + # simplify the situation then pass in one fake line item that costs the + # subtotal of the order + # + # The line_item hash goes in the options hash and should look like + # + # :line_items => [ + # { + # :declared_value => '1', + # :quantity => '2', + # :code => 'default', + # :description => 'Giant Walrus', + # :sku => 'WA323232323232323' + # }, + # { + # :declared_value => '6', + # :quantity => '1', + # :code => 'default', + # :description => 'Marble Snowcone', + # :sku => 'FAKE1232132113123' + # } + # ] + # + # This functionality is only supported by this particular gateway may + # be changed at any time + def calculate_tax(creditcard, options) + requires!(options, :line_items) + setup_address_hash(options) + commit(build_tax_calculation_request(creditcard, options), options) + end + + private + + # Create all address hash key value pairs so that we still function if we + # were only provided with one or two of them + def setup_address_hash(options) + options[:billing_address] = options[:billing_address] || options[:address] || {} + options[:shipping_address] = options[:shipping_address] || {} + end + + def build_auth_request(money, creditcard_or_reference, options) + xml = Builder::XmlMarkup.new :indent => 2 + add_payment_method_or_subscription(xml, money, creditcard_or_reference, options) + add_auth_service(xml) + add_business_rules_data(xml) + xml.target! + end + + def build_tax_calculation_request(creditcard, options) + xml = Builder::XmlMarkup.new :indent => 2 + add_address(xml, creditcard, options[:billing_address], options, false) + add_address(xml, creditcard, options[:shipping_address], options, true) + add_line_item_data(xml, options) + add_purchase_data(xml, 0, false, options) + add_tax_service(xml) + add_business_rules_data(xml) + xml.target! + end + + def build_capture_request(money, authorization, options) + order_id, request_id, request_token = authorization.split(";") + options[:order_id] = order_id + + xml = Builder::XmlMarkup.new :indent => 2 + add_purchase_data(xml, money, true, options) + add_capture_service(xml, request_id, request_token) + add_business_rules_data(xml) + xml.target! + end + + def build_purchase_request(money, payment_method_or_reference, options) + xml = Builder::XmlMarkup.new :indent => 2 + add_payment_method_or_subscription(xml, money, payment_method_or_reference, options) + if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check' + add_check_service(xml) + else + add_purchase_service(xml, options) + add_business_rules_data(xml) + end + xml.target! + end + + def build_void_request(identification, options) + order_id, request_id, request_token = identification.split(";") + options[:order_id] = order_id + + xml = Builder::XmlMarkup.new :indent => 2 + add_void_service(xml, request_id, request_token) + xml.target! + end + + def build_auth_reversal_request(money, identification, options) + order_id, request_id, request_token = identification.split(";") + options[:order_id] = order_id + xml = Builder::XmlMarkup.new :indent => 2 + add_purchase_data(xml, money, true, options) + add_auth_reversal_service(xml, request_id, request_token) + xml.target! + end + + def build_refund_request(money, identification, options) + order_id, request_id, request_token = identification.split(";") + options[:order_id] = order_id + + xml = Builder::XmlMarkup.new :indent => 2 + add_purchase_data(xml, money, true, options) + add_credit_service(xml, request_id, request_token) + + xml.target! + end + + def build_credit_request(money, reference, options) + xml = Builder::XmlMarkup.new :indent => 2 + + add_purchase_data(xml, money, true, options) + add_subscription(xml, options, reference) + add_credit_service(xml) + + xml.target! + end + + def build_create_subscription_request(payment_method, options) + options[:subscription] = (options[:subscription] || {}).merge(:frequency => "on-demand", :amount => 0, :automatic_renew => false) + + xml = Builder::XmlMarkup.new :indent => 2 + add_address(xml, payment_method, options[:billing_address], options) + add_purchase_data(xml, options[:setup_fee] || 0, true, options) + if card_brand(payment_method) == 'check' + add_check(xml, payment_method) + add_check_payment_method(xml) + add_check_service(xml, options) if options[:setup_fee] + else + add_creditcard(xml, payment_method) + add_creditcard_payment_method(xml) + add_purchase_service(xml, options) if options[:setup_fee] + end + add_subscription(xml, options) + add_subscription_create_service(xml, options) + add_business_rules_data(xml) + xml.target! + end + + def build_update_subscription_request(reference, creditcard, options) + xml = Builder::XmlMarkup.new :indent => 2 + add_address(xml, creditcard, options[:billing_address], options) unless options[:billing_address].blank? + add_purchase_data(xml, options[:setup_fee], true, options) unless options[:setup_fee].blank? + add_creditcard(xml, creditcard) if creditcard + add_creditcard_payment_method(xml) if creditcard + add_subscription(xml, options, reference) + add_subscription_update_service(xml, options) + add_business_rules_data(xml) + xml.target! + end + + def build_delete_subscription_request(reference, options) + xml = Builder::XmlMarkup.new :indent => 2 + add_subscription(xml, options, reference) + add_subscription_delete_service(xml, options) + xml.target! + end + + def build_retrieve_subscription_request(reference, options) + xml = Builder::XmlMarkup.new :indent => 2 + add_subscription(xml, options, reference) + add_subscription_retrieve_service(xml, options) + xml.target! + end + + def add_business_rules_data(xml) + xml.tag! 'businessRules' do + xml.tag!('ignoreAVSResult', 'true') if @options[:ignore_avs] + xml.tag!('ignoreCVResult', 'true') if @options[:ignore_cvv] + end + end + + def add_line_item_data(xml, options) + options[:line_items].each_with_index do |value, index| + xml.tag! 'item', {'id' => index} do + xml.tag! 'unitPrice', amount(value[:declared_value]) + xml.tag! 'quantity', value[:quantity] + xml.tag! 'productCode', value[:code] || 'shipping_only' + xml.tag! 'productName', value[:description] + xml.tag! 'productSKU', value[:sku] + end + end + end + + def add_merchant_data(xml, options) + xml.tag! 'merchantID', @options[:login] + xml.tag! 'merchantReferenceCode', options[:order_id] + xml.tag! 'clientLibrary' ,'Ruby Active Merchant' + xml.tag! 'clientLibraryVersion', VERSION + xml.tag! 'clientEnvironment' , RUBY_PLATFORM + end + + def add_purchase_data(xml, money = 0, include_grand_total = false, options={}) + xml.tag! 'purchaseTotals' do + xml.tag! 'currency', options[:currency] || currency(money) + xml.tag!('grandTotalAmount', amount(money)) if include_grand_total + end + end + + def add_address(xml, payment_method, address, options, shipTo = false) + requires!(options, :email) + + xml.tag! shipTo ? 'shipTo' : 'billTo' do + xml.tag! 'firstName', payment_method.first_name if payment_method + xml.tag! 'lastName', payment_method.last_name if payment_method + xml.tag! 'street1', address[:address1] + xml.tag! 'street2', address[:address2] unless address[:address2].blank? + xml.tag! 'city', address[:city] + xml.tag! 'state', address[:state] + xml.tag! 'postalCode', address[:zip] + xml.tag! 'country', address[:country] + xml.tag! 'company', address[:company] unless address[:company].blank? + xml.tag! 'companyTaxID', address[:companyTaxID] unless address[:company_tax_id].blank? + xml.tag! 'phoneNumber', address[:phone_number] unless address[:phone_number].blank? + xml.tag! 'email', options[:email] + xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank? + xml.tag! 'driversLicenseState', options[:drivers_license_state] unless options[:drivers_license_state].blank? + end + end + + def add_creditcard(xml, creditcard) + xml.tag! 'card' do + xml.tag! 'accountNumber', creditcard.number + xml.tag! 'expirationMonth', format(creditcard.month, :two_digits) + xml.tag! 'expirationYear', format(creditcard.year, :four_digits) + xml.tag!('cvNumber', creditcard.verification_value) unless (@options[:ignore_cvv] || creditcard.verification_value.blank? ) + xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym] + end + end + + def add_check(xml, check) + xml.tag! 'check' do + xml.tag! 'accountNumber', check.account_number + xml.tag! 'accountType', check.account_type[0] + xml.tag! 'bankTransitNumber', check.routing_number + end + end + + def add_tax_service(xml) + xml.tag! 'taxService', {'run' => 'true'} do + xml.tag!('nexus', @options[:nexus]) unless @options[:nexus].blank? + xml.tag!('sellerRegistration', @options[:vat_reg_number]) unless @options[:vat_reg_number].blank? + end + end + + def add_auth_service(xml) + xml.tag! 'ccAuthService', {'run' => 'true'} + end + + def add_capture_service(xml, request_id, request_token) + xml.tag! 'ccCaptureService', {'run' => 'true'} do + xml.tag! 'authRequestID', request_id + xml.tag! 'authRequestToken', request_token + end + end + + def add_purchase_service(xml, options) + xml.tag! 'ccAuthService', {'run' => 'true'} + xml.tag! 'ccCaptureService', {'run' => 'true'} + end + + def add_void_service(xml, request_id, request_token) + xml.tag! 'voidService', {'run' => 'true'} do + xml.tag! 'voidRequestID', request_id + xml.tag! 'voidRequestToken', request_token + end + end + + def add_auth_reversal_service(xml, request_id, request_token) + xml.tag! 'ccAuthReversalService', {'run' => 'true'} do + xml.tag! 'authRequestID', request_id + xml.tag! 'authRequestToken', request_token + end + end + + def add_credit_service(xml, request_id = nil, request_token = nil) + xml.tag! 'ccCreditService', {'run' => 'true'} do + xml.tag! 'captureRequestID', request_id if request_id + xml.tag! 'captureRequestToken', request_token if request_token + end + end + + def add_check_service(xml) + xml.tag! 'ecDebitService', {'run' => 'true'} + end + + def add_subscription_create_service(xml, options) + xml.tag! 'paySubscriptionCreateService', {'run' => 'true'} + end + + def add_subscription_update_service(xml, options) + xml.tag! 'paySubscriptionUpdateService', {'run' => 'true'} + end + + def add_subscription_delete_service(xml, options) + xml.tag! 'paySubscriptionDeleteService', {'run' => 'true'} + end + + def add_subscription_retrieve_service(xml, options) + xml.tag! 'paySubscriptionRetrieveService', {'run' => 'true'} + end + + def add_subscription(xml, options, reference = nil) + options[:subscription] ||= {} + + xml.tag! 'recurringSubscriptionInfo' do + if reference + _, subscription_id, _ = reference.split(";") + xml.tag! 'subscriptionID', subscription_id + end + + xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status] + xml.tag! 'amount', options[:subscription][:amount] if options[:subscription][:amount] + xml.tag! 'numberOfPayments', options[:subscription][:occurrences] if options[:subscription][:occurrences] + xml.tag! 'automaticRenew', options[:subscription][:automatic_renew] if options[:subscription][:automatic_renew] + xml.tag! 'frequency', options[:subscription][:frequency] if options[:subscription][:frequency] + xml.tag! 'startDate', options[:subscription][:start_date].strftime("%Y%m%d") if options[:subscription][:start_date] + xml.tag! 'endDate', options[:subscription][:end_date].strftime("%Y%m%d") if options[:subscription][:end_date] + xml.tag! 'approvalRequired', options[:subscription][:approval_required] || false + xml.tag! 'event', options[:subscription][:event] if options[:subscription][:event] + xml.tag! 'billPayment', options[:subscription][:bill_payment] if options[:subscription][:bill_payment] + end + end + + def add_creditcard_payment_method(xml) + xml.tag! 'subscription' do + xml.tag! 'paymentMethod', "credit card" + end + end + + def add_check_payment_method(xml) + xml.tag! 'subscription' do + xml.tag! 'paymentMethod', "check" + end + end + + def add_payment_method_or_subscription(xml, money, payment_method_or_reference, options) + if payment_method_or_reference.is_a?(String) + add_purchase_data(xml, money, true, options) + add_subscription(xml, options, payment_method_or_reference) + elsif card_brand(payment_method_or_reference) == 'check' + add_address(xml, payment_method_or_reference, options[:billing_address], options) + add_purchase_data(xml, money, true, options) + add_check(xml, payment_method_or_reference) + else + add_address(xml, payment_method_or_reference, options[:billing_address], options) + add_purchase_data(xml, money, true, options) + add_creditcard(xml, payment_method_or_reference) + end + end + + # Where we actually build the full SOAP request using builder + def build_request(body, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do + xml.tag! 's:Header' do + xml.tag! 'wsse:Security', {'s:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'} do + xml.tag! 'wsse:UsernameToken' do + xml.tag! 'wsse:Username', @options[:login] + xml.tag! 'wsse:Password', @options[:password], 'Type' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText' + end + end + end + xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do + xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{XSD_VERSION}"} do + add_merchant_data(xml, options) + xml << body + end + end + end + xml.target! + end + + # Contact CyberSource, make the SOAP request, and parse the reply into a + # Response object + def commit(request, options) + response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(request, options))) + + success = response[:decision] == "ACCEPT" + message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message] + authorization = success ? [ options[:order_id], response[:requestID], response[:requestToken] ].compact.join(";") : nil + + Response.new(success, message, response, + :test => test?, + :authorization => authorization, + :avs_result => { :code => response[:avsCode] }, + :cvv_result => response[:cvCode] + ) + end + + # Parse the SOAP response + # Technique inspired by the Paypal Gateway + def parse(xml) + reply = {} + xml = REXML::Document.new(xml) + if root = REXML::XPath.first(xml, "//c:replyMessage") + root.elements.to_a.each do |node| + case node.name + when 'c:reasonCode' + reply[:message] = reply(node.text) + else + parse_element(reply, node) + end + end + elsif root = REXML::XPath.first(xml, "//soap:Fault") + parse_element(reply, root) + reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}" + end + return reply + end + + def parse_element(reply, node) + if node.has_elements? + node.elements.each{|e| parse_element(reply, e) } + else + if node.parent.name =~ /item/ + parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '') + reply[(parent + '_' + node.name).to_sym] = node.text + else + reply[node.name.to_sym] = node.text + end + end + return reply + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/data_cash.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/data_cash.rb new file mode 100644 index 000000000..2c62302dd --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/data_cash.rb @@ -0,0 +1,591 @@ +module ActiveMerchant + module Billing + class DataCashGateway < Gateway + self.default_currency = 'GBP' + self.supported_countries = ['GB'] + + # From the DataCash docs; Page 13, the following cards are + # usable: + # American Express, ATM, Carte Blanche, Diners Club, Discover, + # EnRoute, GE Capital, JCB, Laser, Maestro, Mastercard, Solo, + # Switch, Visa, Visa Delta, VISA Electron, Visa Purchasing + # + # Note continuous authority is only supported for :visa, :master and :american_express card types + self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro, :switch, :solo, :laser ] + + self.homepage_url = 'http://www.datacash.com/' + self.display_name = 'DataCash' + + # Datacash server URLs + self.test_url = 'https://testserver.datacash.com/Transaction' + self.live_url = 'https://mars.transaction.datacash.com/Transaction' + + # Different Card Transaction Types + AUTH_TYPE = 'auth' + CANCEL_TYPE = 'cancel' + FULFILL_TYPE = 'fulfill' + PRE_TYPE = 'pre' + REFUND_TYPE = 'refund' + TRANSACTION_REFUND_TYPE = 'txn_refund' + + # Constant strings for use in the ExtendedPolicy complex element for + # CV2 checks + POLICY_ACCEPT = 'accept' + POLICY_REJECT = 'reject' + + # Datacash success code + DATACASH_SUCCESS = '1' + + # Creates a new DataCashGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- The Datacash account login. + # * :password -- The Datacash account password. + # * :test => +true+ or +false+ -- Use the test or live Datacash url. + # + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Perform a purchase, which is essentially an authorization and capture in a single operation. + # + # ==== Parameters + # * money The amount to be authorized as an Integer value in cents. + # * authorization_or_credit_card:: The continuous authority reference or CreditCard details for the transaction. + # * options A hash of optional parameters. + # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation) + # * :set_up_continuous_authority + # Set to true to set up a recurring historic transaction account be set up. + # Only supported for :visa, :master and :american_express card types + # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions. + # * :address:: billing address for card + # + # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one + def purchase(money, authorization_or_credit_card, options = {}) + requires!(options, :order_id) + + if authorization_or_credit_card.is_a?(String) + request = build_purchase_or_authorization_request_with_continuous_authority_reference_request(AUTH_TYPE, money, authorization_or_credit_card, options) + else + request = build_purchase_or_authorization_request_with_credit_card_request(AUTH_TYPE, money, authorization_or_credit_card, options) + end + + commit(request) + end + + # Performs an authorization, which reserves the funds on the customer's credit card, but does not + # charge the card. + # + # ==== Parameters + # + # * money The amount to be authorized as an Integer value in cents. + # * authorization_or_credit_card:: The continuous authority reference or CreditCard details for the transaction. + # * options A hash of optional parameters. + # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation) + # * :set_up_continuous_authority:: + # Set to true to set up a recurring historic transaction account be set up. + # Only supported for :visa, :master and :american_express card types + # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions. + # * :address:: billing address for card + # + # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one + def authorize(money, authorization_or_credit_card, options = {}) + requires!(options, :order_id) + + if authorization_or_credit_card.is_a?(String) + request = build_purchase_or_authorization_request_with_continuous_authority_reference_request(AUTH_TYPE, money, authorization_or_credit_card, options) + else + request = build_purchase_or_authorization_request_with_credit_card_request(PRE_TYPE, money, authorization_or_credit_card, options) + end + + commit(request) + end + + # Captures the funds from an authorized transaction. + # + # ==== Parameters + # + # * money -- The amount to be captured as anInteger value in cents. + # * authorization -- The authorization returned from the previous authorize request. + def capture(money, authorization, options = {}) + commit(build_void_or_capture_request(FULFILL_TYPE, money, authorization, options)) + end + + # Void a previous transaction + # + # ==== Parameters + # + # * authorization - The authorization returned from the previous authorize request. + def void(authorization, options = {}) + request = build_void_or_capture_request(CANCEL_TYPE, nil, authorization, options) + + commit(request) + end + + # Refund to a card + # + # ==== Parameters + # + # * money The amount to be refunded as an Integer value in cents. Set to nil for a full refund on existing transaction. + # * reference_or_credit_card The credit card you want to refund OR the datacash_reference for the existing transaction you are refunding + # * options Are ignored when refunding via reference to an existing transaction, otherwise + # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation) + # * :address:: billing address for card + def credit(money, reference_or_credit_card, options = {}) + if reference_or_credit_card.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, reference_or_credit_card) + else + request = build_refund_request(money, reference_or_credit_card, options) + commit(request) + end + end + + def refund(money, reference, options = {}) + commit(build_transaction_refund_request(money, reference)) + end + + private + # Create the xml document for a 'cancel' or 'fulfill' transaction. + # + # Final XML should look like: + # + # + # 99000001 + # ****** + # + # + # + # 25.00 + # + # + # 4900200000000001 + # A6 + # fulfill + # + # + # + # + # Parameters: + # * type must be FULFILL_TYPE or CANCEL_TYPE + # * money - optional - Integer value in cents + # * authorization - the Datacash authorization from a previous succesful authorize transaction + # * options + # * order_id - A unique reference for the transaction + # + # Returns: + # -Builder xml document + # + def build_void_or_capture_request(type, money, authorization, options) + reference, auth_code, ca_reference = authorization.to_s.split(';') + + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + + xml.tag! :Transaction do + xml.tag! :HistoricTxn do + xml.tag! :reference, reference + xml.tag! :authcode, auth_code + xml.tag! :method, type + end + + if money + xml.tag! :TxnDetails do + xml.tag! :merchantreference, format_reference_number(options[:order_id]) + xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + end + end + end + end + xml.target! + end + + # Create the xml document for an 'auth' or 'pre' transaction with a credit card + # + # Final XML should look like: + # + # + # + # 99000000 + # ******* + # + # + # + # 123456 + # 10.00 + # + # + # + # 4444********1111 + # 03/04 + # + # Flat 7 + # 89 Jumble + # Street + # Mytown + # AV12FR + # 123 + # + # + # + # + # + # + # + # auth + # + # + # + # + # Parameters: + # -type must be 'auth' or 'pre' + # -money - A money object with the price and currency + # -credit_card - The credit_card details to use + # -options: + # :order_id is the merchant reference number + # :billing_address is the billing address for the cc + # :address is the delivery address + # + # Returns: + # -xml: Builder document containing the markup + # + def build_purchase_or_authorization_request_with_credit_card_request(type, money, credit_card, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + + xml.tag! :Transaction do + if options[:set_up_continuous_authority] + xml.tag! :ContAuthTxn, :type => 'setup' + end + xml.tag! :CardTxn do + xml.tag! :method, type + add_credit_card(xml, credit_card, options[:billing_address]) + end + xml.tag! :TxnDetails do + xml.tag! :merchantreference, format_reference_number(options[:order_id]) + xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + end + end + end + xml.target! + end + + # Create the xml document for an 'auth' or 'pre' transaction with + # continuous authorization + # + # Final XML should look like: + # + # + # + # + # + # 3851231 + # cont_auth + # 18.50 + # + # + # 4500200040925092 + # auth + # + # + # + # 99000001 + # mypasswd + # + # + # + # Parameters: + # -type must be 'auth' or 'pre' + # -money - A money object with the price and currency + # -authorization - The authorization containing a continuous authority reference previously set up on a credit card + # -options: + # :order_id is the merchant reference number + # + # Returns: + # -xml: Builder document containing the markup + # + def build_purchase_or_authorization_request_with_continuous_authority_reference_request(type, money, authorization, options) + reference, auth_code, ca_reference = authorization.to_s.split(';') + raise ArgumentError, "The continuous authority reference is required for continuous authority transactions" if ca_reference.blank? + + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + xml.tag! :Transaction do + xml.tag! :ContAuthTxn, :type => 'historic' + xml.tag! :HistoricTxn do + xml.tag! :reference, ca_reference + xml.tag! :method, type + end + xml.tag! :TxnDetails do + xml.tag! :merchantreference, format_reference_number(options[:order_id]) + xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money) + xml.tag! :capturemethod, 'cont_auth' + end + end + end + xml.target! + end + + # Create the xml document for a full or partial refund transaction with + # + # Final XML should look like: + # + # + # + # 99000001 + # ******* + # + # + # + # txn_refund + # 12345678 + # + # + # 10.00 + # + # + # + # + def build_transaction_refund_request(money, reference) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + xml.tag! :Transaction do + xml.tag! :HistoricTxn do + xml.tag! :reference, reference + xml.tag! :method, TRANSACTION_REFUND_TYPE + end + unless money.nil? + xml.tag! :TxnDetails do + xml.tag! :amount, amount(money) + end + end + end + end + xml.target! + end + + # Create the xml document for a full or partial refund with + # + # Final XML should look like: + # + # + # + # 99000001 + # ***** + # + # + # + # + # 633300*********1 + # 04/06 + # 01/04 + # + # refund + # + # + # 1000001 + # 95.99 + # + # + # + def build_refund_request(money, credit_card, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! :Request do + add_authentication(xml) + xml.tag! :Transaction do + xml.tag! :CardTxn do + xml.tag! :method, REFUND_TYPE + add_credit_card(xml, credit_card, options[:billing_address]) + end + xml.tag! :TxnDetails do + xml.tag! :merchantreference, format_reference_number(options[:order_id]) + xml.tag! :amount, amount(money) + end + end + end + xml.target! + end + + + # Adds the authentication element to the passed builder xml doc + # + # Parameters: + # -xml: Builder document that is being built up + # + # Returns: + # -none: The results is stored in the passed xml document + # + def add_authentication(xml) + xml.tag! :Authentication do + xml.tag! :client, @options[:login] + xml.tag! :password, @options[:password] + end + end + + # Add credit_card details to the passed XML Builder doc + # + # Parameters: + # -xml: Builder document that is being built up + # -credit_card: ActiveMerchant::Billing::CreditCard object + # -billing_address: Hash containing all of the billing address details + # + # Returns: + # -none: The results is stored in the passed xml document + # + def add_credit_card(xml, credit_card, address) + + xml.tag! :Card do + + # DataCash calls the CC number 'pan' + xml.tag! :pan, credit_card.number + xml.tag! :expirydate, format_date(credit_card.month, credit_card.year) + + # optional values - for Solo etc + if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s) + + xml.tag! :issuenumber, credit_card.issue_number unless credit_card.issue_number.blank? + + if !credit_card.start_month.blank? && !credit_card.start_year.blank? + xml.tag! :startdate, format_date(credit_card.start_month, credit_card.start_year) + end + end + + xml.tag! :Cv2Avs do + xml.tag! :cv2, credit_card.verification_value if credit_card.verification_value? + if address + xml.tag! :street_address1, address[:address1] unless address[:address1].blank? + xml.tag! :street_address2, address[:address2] unless address[:address2].blank? + xml.tag! :street_address3, address[:address3] unless address[:address3].blank? + xml.tag! :street_address4, address[:address4] unless address[:address4].blank? + xml.tag! :postcode, address[:zip] unless address[:zip].blank? + end + + # The ExtendedPolicy defines what to do when the passed data + # matches, or not... + # + # All of the following elements MUST be present for the + # xml to be valid (or can drop the ExtendedPolicy and use + # a predefined one + xml.tag! :ExtendedPolicy do + xml.tag! :cv2_policy, + :notprovided => POLICY_REJECT, + :notchecked => POLICY_REJECT, + :matched => POLICY_ACCEPT, + :notmatched => POLICY_REJECT, + :partialmatch => POLICY_REJECT + xml.tag! :postcode_policy, + :notprovided => POLICY_ACCEPT, + :notchecked => POLICY_ACCEPT, + :matched => POLICY_ACCEPT, + :notmatched => POLICY_REJECT, + :partialmatch => POLICY_ACCEPT + xml.tag! :address_policy, + :notprovided => POLICY_ACCEPT, + :notchecked => POLICY_ACCEPT, + :matched => POLICY_ACCEPT, + :notmatched => POLICY_REJECT, + :partialmatch => POLICY_ACCEPT + end + end + end + end + + # Send the passed data to DataCash for processing + # + # Parameters: + # -request: The XML data that is to be sent to Datacash + # + # Returns: + # - ActiveMerchant::Billing::Response object + # + def commit(request) + response = parse(ssl_post(test? ? self.test_url : self.live_url, request)) + + Response.new(response[:status] == DATACASH_SUCCESS, response[:reason], response, + :test => test?, + :authorization => "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}" + ) + end + + # Returns a date string in the format Datacash expects + # + # Parameters: + # -month: integer, the month + # -year: integer, the year + # + # Returns: + # -String: date in MM/YY format + # + def format_date(month, year) + "#{format(month,:two_digits)}/#{format(year, :two_digits)}" + end + + # Parse the datacash response and create a Response object + # + # Parameters: + # -body: The XML returned from Datacash + # + # Returns: + # -a hash with all of the values returned in the Datacash XML response + # + def parse(body) + + response = {} + xml = REXML::Document.new(body) + root = REXML::XPath.first(xml, "//Response") + + root.elements.to_a.each do |node| + parse_element(response, node) + end + + response + end + + # Parse an xml element + # + # Parameters: + # -response: The hash that the values are being returned in + # -node: The node that is currently being read + # + # Returns: + # - none (results are stored in the passed hash) + def parse_element(response, node) + if node.has_elements? + node.elements.each{|e| parse_element(response, e) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def format_reference_number(number) + number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, "0").first(30) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/efsnet.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/efsnet.rb new file mode 100644 index 000000000..fcf28d8e0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/efsnet.rb @@ -0,0 +1,230 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + + class EfsnetGateway < Gateway + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.concordefsnet.com/' + self.display_name = 'Efsnet' + + self.test_url = 'https://testefsnet.concordebiz.com/efsnet.dll' + self.live_url = 'https://efsnet.concordebiz.com/efsnet.dll' + + # login is your Store ID + # password is your Store Key + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard, options = {}) + request = build_credit_card_request(money, creditcard, options) + commit(:credit_card_authorize, request) + end + + def purchase(money, creditcard, options = {}) + request = build_credit_card_request(money, creditcard, options) + commit(:credit_card_charge, request) + end + + def capture(money, identification, options = {}) + request = build_refund_or_settle_request(money, identification, options) + commit(:credit_card_settle, request) + end + + def credit(money, identification_or_credit_card, options = {}) + if identification_or_credit_card.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + # Perform authorization reversal + refund(money, identification_or_credit_card, options) + else + # Perform credit + request = build_credit_card_request(money, identification_or_credit_card, options) + commit(:credit_card_credit, request) + end + end + + def refund(money, reference, options = {}) + # Perform authorization reversal + request = build_refund_or_settle_request(money, reference, options) + commit(:credit_card_refund, request) + end + + def void(identification, options = {}) + requires!(options, :order_id) + original_transaction_id, original_transaction_amount = identification.split(";") + commit(:void_transaction, {:reference_number => format_reference_number(options[:order_id]), :transaction_id => original_transaction_id}) + end + + def voice_authorize(money, authorization_code, creditcard, options = {}) + options[:authorization_number] = authorization_code + request = build_credit_card_request(money, creditcard, options) + commit(:credit_card_voice_authorize, request) + end + + def force(money, authorization_code, creditcard, options = {}) + options[:authorization_number] = authorization_code + request = build_credit_card_request(money, creditcard, options) + commit(:credit_card_capture, request) + end + + def system_check + commit(:system_check, {}) + end + + private + + def build_refund_or_settle_request(money, identification, options = {}) + original_transaction_id, original_transaction_amount = identification.split(";") + + requires!(options, :order_id) + + post = { + :reference_number => format_reference_number(options[:order_id]), + :transaction_amount => amount(money), + :original_transaction_amount => original_transaction_amount, + :original_transaction_id => original_transaction_id, + :client_ip_address => options[:ip] + } + end + + def build_credit_card_request(money, creditcard, options = {}) + requires!(options, :order_id) + + post = { + :reference_number => format_reference_number(options[:order_id]), + :authorization_number => options[:authorization_number], + :transaction_amount => amount(money), + :client_ip_address => options[:ip] + + } + add_creditcard(post,creditcard) + add_address(post,options) + post + end + + def format_reference_number(number) + number.to_s.slice(0,12) + end + + def add_address(post,options) + if address = options[:billing_address] || options[:address] + if address[:address2] + post[:billing_address] = address[:address1].to_s << ' ' << address[:address2].to_s + else + post[:billing_address] = address[:address1].to_s + end + post[:billing_city] = address[:city].to_s + post[:billing_state] = address[:state].blank? ? 'n/a' : address[:state] + post[:billing_postal_code] = address[:zip].to_s + post[:billing_country] = address[:country].to_s + end + + if address = options[:shipping_address] + if address[:address2] + post[:shipping_address] = address[:address1].to_s << ' ' << address[:address2].to_s + else + post[:shipping_address] = address[:address1].to_s + end + post[:shipping_city] = address[:city].to_s + post[:shipping_state] = address[:state].blank? ? 'n/a' : address[:state] + post[:shipping_postal_code] = address[:zip].to_s + post[:shipping_country] = address[:country].to_s + end + end + + def add_creditcard(post, creditcard) + post[:billing_name] = creditcard.name if creditcard.name + post[:account_number] = creditcard.number + post[:card_verification_value] = creditcard.verification_value if creditcard.verification_value? + post[:expiration_month] = sprintf("%.2i", creditcard.month) + post[:expiration_year] = sprintf("%.4i", creditcard.year)[-2..-1] + end + + + def commit(action, parameters) + response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters), 'Content-Type' => 'text/xml')) + + Response.new(success?(response), message_from(response[:result_message]), response, + :test => test?, + :authorization => authorization_from(response, parameters), + :avs_result => { :code => response[:avs_response_code] }, + :cvv_result => response[:cvv_response_code] + ) + end + + def success?(response) + response[:response_code] == '0' + end + + def authorization_from(response, params) + [ response[:transaction_id], params[:transaction_amount] ].compact.join(';') + end + + def parse(xml) + response = {} + + xml = REXML::Document.new(xml) + + xml.elements.each('//Reply//TransactionReply/*') do |node| + + response[node.name.underscore.to_sym] = normalize(node.text) + + end unless xml.root.nil? + + response + end + + def post_data(action, parameters = {}) + xml = REXML::Document.new("") + root = xml.add_element("Request") + root.attributes["StoreID"] = options[:login] + root.attributes["StoreKey"] = options[:password] + root.attributes["ApplicationID"] = 'ot 1.0' + transaction = root.add_element(action.to_s.camelize) + + actions[action].each do |key| + transaction.add_element(key).text = parameters[key.underscore.to_sym] unless parameters[key.underscore.to_sym].blank? + end + + xml.to_s + end + + def message_from(message) + return 'Unspecified error' if message.blank? + message.gsub(/[^\w]/, ' ').split.join(" ").capitalize + 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 actions + ACTIONS + end + + CREDIT_CARD_FIELDS = %w(AuthorizationNumber ClientIpAddress BillingAddress BillingCity BillingState BillingPostalCode BillingCountry BillingName CardVerificationValue ExpirationMonth ExpirationYear ReferenceNumber TransactionAmount AccountNumber ) + + ACTIONS = { + :credit_card_authorize => CREDIT_CARD_FIELDS, + :credit_card_charge => CREDIT_CARD_FIELDS, + :credit_card_voice_authorize => CREDIT_CARD_FIELDS, + :credit_card_capture => CREDIT_CARD_FIELDS, + :credit_card_credit => CREDIT_CARD_FIELDS + ["OriginalTransactionAmount"], + :credit_card_refund => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), + :void_transaction => %w(ReferenceNumber TransactionID), + :credit_card_settle => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress), + :system_check => %w(SystemCheck), + } + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/elavon.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/elavon.rb new file mode 100644 index 000000000..ddc4559b9 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/elavon.rb @@ -0,0 +1,312 @@ +require File.dirname(__FILE__) + '/viaklix' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # = Elavon Virtual Merchant Gateway + # + # == Example use: + # + # gateway = ActiveMerchant::Billing::ElavonGateway.new( + # :login => "my_virtual_merchant_id", + # :password => "my_virtual_merchant_pin", + # :user => "my_virtual_merchant_user_id" # optional + # ) + # + # # set up credit card obj as in main ActiveMerchant example + # creditcard = ActiveMerchant::Billing::CreditCard.new( + # :type => 'visa', + # :number => '41111111111111111', + # :month => 10, + # :year => 2011, + # :first_name => 'Bob', + # :last_name => 'Bobsen' + # ) + # + # # run request + # response = gateway.purchase(1000, creditcard) # authorize and capture 10 USD + # + # puts response.success? # Check whether the transaction was successful + # puts response.message # Retrieve the message returned by Elavon + # puts response.authorization # Retrieve the unique transaction ID returned by Elavon + # + class ElavonGateway < Gateway + class_attribute :test_url, :live_url, :delimiter, :actions + + self.test_url = 'https://demo.myvirtualmerchant.com/VirtualMerchantDemo/process.do' + self.live_url = 'https://www.myvirtualmerchant.com/VirtualMerchant/process.do' + + self.display_name = 'Elavon MyVirtualMerchant' + self.supported_countries = ['US', 'CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.elavon.com/' + + self.delimiter = "\n" + self.actions = { + :purchase => 'CCSALE', + :credit => 'CCCREDIT', + :refund => 'CCRETURN', + :authorize => 'CCAUTHONLY', + :capture => 'CCFORCE', + :void => 'CCVOID' + } + + # Initialize the Gateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- Merchant ID + # * :password -- PIN + # * :user -- Specify a subuser of the account (optional) + # * :test => +true+ or +false+ -- Force test transactions + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Make a purchase + def purchase(money, creditcard, options = {}) + form = {} + add_salestax(form, options) + add_invoice(form, options) + add_creditcard(form, creditcard) + add_address(form, options) + add_customer_data(form, options) + add_test_mode(form, options) + commit(:purchase, money, form) + end + + # Authorize a credit card for a given amount. + # + # ==== Parameters + # * money - The amount to be authorized as an Integer value in cents. + # * credit_card - The CreditCard details for the transaction. + # * options + # * :billing_address - The billing address for the cardholder. + def authorize(money, creditcard, options = {}) + form = {} + add_salestax(form, options) + add_invoice(form, options) + add_creditcard(form, creditcard) + add_address(form, options) + add_customer_data(form, options) + add_test_mode(form, options) + commit(:authorize, money, form) + end + + # Capture authorized funds from a credit card. + # + # ==== Parameters + # * money - The amount to be captured as an Integer value in cents. + # * authorization - The approval code returned from the initial authorization. + # * options + # * :credit_card - The CreditCard details from the initial transaction (required). + def capture(money, authorization, options = {}) + requires!(options, :credit_card) + + form = {} + add_salestax(form, options) + add_approval_code(form, authorization) + add_invoice(form, options) + add_creditcard(form, options[:credit_card]) + add_customer_data(form, options) + add_test_mode(form, options) + commit(:capture, money, form) + end + + # Refund a transaction. + # + # This transaction indicates to the gateway that + # money should flow from the merchant to the customer. + # + # ==== Parameters + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * identification -- The ID of the original transaction against which the refund is being issued. + # * options -- A hash of parameters. + def refund(money, identification, options = {}) + form = {} + add_txn_id(form, identification) + add_test_mode(form, options) + commit(:refund, money, form) + end + + # Void a previous transaction + # + # ==== Parameters + # + # * authorization - The authorization returned from the previous request. + def void(identification, options = {}) + form = {} + add_txn_id(form, identification) + add_test_mode(form, options) + commit(:void, nil, form) + end + + # Make a credit to a card. Use the refund method if you'd like to credit using + # previous transaction + # + # ==== Parameters + # * money - The amount to be credited as an Integer value in cents. + # * creditcard - The credit card to be credited. + # * options + def credit(money, creditcard, options = {}) + if creditcard.is_a?(String) + raise ArgumentError, "Reference credits are not supported. Please supply the original credit card or use the #refund method." + end + + form = {} + add_invoice(form, options) + add_creditcard(form, creditcard) + add_address(form, options) + add_customer_data(form, options) + add_test_mode(form, options) + commit(:credit, money, form) + end + + + private + def add_invoice(form,options) + form[:invoice_number] = (options[:order_id] || options[:invoice]).to_s.slice(0, 10) + form[:description] = options[:description].to_s.slice(0, 255) + end + + def add_approval_code(form, authorization) + form[:approval_code] = authorization.split(';').first + end + + def add_txn_id(form, authorization) + form[:txn_id] = authorization.split(';').last + end + + def authorization_from(response) + [response['approval_code'], response['txn_id']].join(';') + end + + def add_creditcard(form, creditcard) + form[:card_number] = creditcard.number + form[:exp_date] = expdate(creditcard) + + if creditcard.verification_value? + add_verification_value(form, creditcard) + end + + form[:first_name] = creditcard.first_name.to_s.slice(0, 20) + form[:last_name] = creditcard.last_name.to_s.slice(0, 30) + end + + def add_verification_value(form, creditcard) + form[:cvv2cvc2] = creditcard.verification_value + form[:cvv2cvc2_indicator] = '1' + end + + def add_customer_data(form, options) + form[:email] = options[:email].to_s.slice(0, 100) unless options[:email].blank? + form[:customer_code] = options[:customer].to_s.slice(0, 10) unless options[:customer].blank? + end + + def add_salestax(form, options) + form[:salestax] = options[:tax] if options[:tax].present? + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + "#{month}#{year[2..3]}" + end + + def add_address(form,options) + billing_address = options[:billing_address] || options[:address] + + if billing_address + form[:avs_address] = billing_address[:address1].to_s.slice(0, 30) + form[:address2] = billing_address[:address2].to_s.slice(0, 30) + form[:avs_zip] = billing_address[:zip].to_s.slice(0, 10) + form[:city] = billing_address[:city].to_s.slice(0, 30) + form[:state] = billing_address[:state].to_s.slice(0, 10) + form[:company] = billing_address[:company].to_s.slice(0, 50) + form[:phone] = billing_address[:phone].to_s.slice(0, 20) + form[:country] = billing_address[:country].to_s.slice(0, 50) + end + + if shipping_address = options[:shipping_address] + first_name, last_name = parse_first_and_last_name(shipping_address[:name]) + form[:ship_to_first_name] = first_name.to_s.slice(0, 20) + form[:ship_to_last_name] = last_name.to_s.slice(0, 30) + form[:ship_to_address1] = shipping_address[:address1].to_s.slice(0, 30) + form[:ship_to_address2] = shipping_address[:address2].to_s.slice(0, 30) + form[:ship_to_city] = shipping_address[:city].to_s.slice(0, 30) + form[:ship_to_state] = shipping_address[:state].to_s.slice(0, 10) + form[:ship_to_company] = shipping_address[:company].to_s.slice(0, 50) + form[:ship_to_country] = shipping_address[:country].to_s.slice(0, 50) + form[:ship_to_zip] = shipping_address[:zip].to_s.slice(0, 10) + end + end + + def parse_first_and_last_name(value) + name = value.to_s.split(' ') + + last_name = name.pop || '' + first_name = name.join(' ') + [ first_name, last_name ] + end + + def add_test_mode(form, options) + form[:test_mode] = 'TRUE' if options[:test_mode] + end + + def message_from(response) + success?(response) ? response['result_message'] : response['errorMessage'] + end + + def success?(response) + !response.has_key?('errorMessage') + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) + parameters[:transaction_type] = self.actions[action] + + response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)) ) + + Response.new(response['result'] == '0', message_from(response), response, + :test => @options[:test] || test?, + :authorization => authorization_from(response), + :avs_result => { :code => response['avs_response'] }, + :cvv_result => response['cvv2_response'] + ) + end + + def post_data(parameters) + result = preamble + result.merge!(parameters) + result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def preamble + result = { + 'merchant_id' => @options[:login], + 'pin' => @options[:password], + 'show_form' => 'false', + 'result_format' => 'ASCII' + } + + result['user_id'] = @options[:user] unless @options[:user].blank? + result + end + + def parse(msg) + resp = {} + msg.split(self.delimiter).collect{|li| + key, value = li.split("=") + resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip + } + resp + end + + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/epay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/epay.rb new file mode 100644 index 000000000..ff242f9d9 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/epay.rb @@ -0,0 +1,275 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class EpayGateway < Gateway + API_HOST = 'ssl.ditonlinebetalingssystem.dk' + self.live_url = 'https://' + API_HOST + '/remote/payment' + + self.default_currency = 'DKK' + self.money_format = :cents + self.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, + :american_express, :diners_club, :jcb, :maestro] + self.supported_countries = ['DK', 'SE', 'NO'] + self.homepage_url = 'http://epay.dk/' + self.display_name = 'ePay' + + CURRENCY_CODES = { + :ADP => '020', :AED => '784', :AFA => '004', :ALL => '008', :AMD => '051', + :ANG => '532', :AOA => '973', :ARS => '032', :AUD => '036', :AWG => '533', + :AZM => '031', :BAM => '977', :BBD => '052', :BDT => '050', :BGL => '100', + :BGN => '975', :BHD => '048', :BIF => '108', :BMD => '060', :BND => '096', + :BOB => '068', :BOV => '984', :BRL => '986', :BSD => '044', :BTN => '064', + :BWP => '072', :BYR => '974', :BZD => '084', :CAD => '124', :CDF => '976', + :CHF => '756', :CLF => '990', :CLP => '152', :CNY => '156', :COP => '170', + :CRC => '188', :CUP => '192', :CVE => '132', :CYP => '196', :CZK => '203', + :DJF => '262', :DKK => '208', :DOP => '214', :DZD => '012', :ECS => '218', + :ECV => '983', :EEK => '233', :EGP => '818', :ERN => '232', :ETB => '230', + :EUR => '978', :FJD => '242', :FKP => '238', :GBP => '826', :GEL => '981', + :GHC => '288', :GIP => '292', :GMD => '270', :GNF => '324', :GTQ => '320', + :GWP => '624', :GYD => '328', :HKD => '344', :HNL => '340', :HRK => '191', + :HTG => '332', :HUF => '348', :IDR => '360', :ILS => '376', :INR => '356', + :IQD => '368', :IRR => '364', :ISK => '352', :JMD => '388', :JOD => '400', + :JPY => '392', :KES => '404', :KGS => '417', :KHR => '116', :KMF => '174', + :KPW => '408', :KRW => '410', :KWD => '414', :KYD => '136', :KZT => '398', + :LAK => '418', :LBP => '422', :LKR => '144', :LRD => '430', :LSL => '426', + :LTL => '440', :LVL => '428', :LYD => '434', :MAD => '504', :MDL => '498', + :MGF => '450', :MKD => '807', :MMK => '104', :MNT => '496', :MOP => '446', + :MRO => '478', :MTL => '470', :MUR => '480', :MVR => '462', :MWK => '454', + :MXN => '484', :MXV => '979', :MYR => '458', :MZM => '508', :NAD => '516', + :NGN => '566', :NIO => '558', :NOK => '578', :NPR => '524', :NZD => '554', + :OMR => '512', :PAB => '590', :PEN => '604', :PGK => '598', :PHP => '608', + :PKR => '586', :PLN => '985', :PYG => '600', :QAR => '634', :ROL => '642', + :RUB => '643', :RUR => '810', :RWF => '646', :SAR => '682', :SBD => '090', + :SCR => '690', :SDD => '736', :SEK => '752', :SGD => '702', :SHP => '654', + :SIT => '705', :SKK => '703', :SLL => '694', :SOS => '706', :SRG => '740', + :STD => '678', :SVC => '222', :SYP => '760', :SZL => '748', :THB => '764', + :TJS => '972', :TMM => '795', :TND => '788', :TOP => '776', :TPE => '626', + :TRL => '792', :TRY => '949', :TTD => '780', :TWD => '901', :TZS => '834', + :UAH => '980', :UGX => '800', :USD => '840', :UYU => '858', :UZS => '860', + :VEB => '862', :VND => '704', :VUV => '548', :XAF => '950', :XCD => '951', + :XOF => '952', :XPF => '953', :YER => '886', :YUM => '891', :ZAR => '710', + :ZMK => '894', :ZWD => '716' + } + + # login: merchant number + # password: referrer url (for authorize authentication) + def initialize(options = {}) + requires!(options, :login) + super + end + + def authorize(money, credit_card_or_reference, options = {}) + post = {} + + add_amount(post, money, options) + add_invoice(post, options) + add_creditcard_or_reference(post, credit_card_or_reference) + add_instant_capture(post, false) + + commit(:authorize, post) + end + + def purchase(money, credit_card_or_reference, options = {}) + post = {} + + add_amount(post, money, options) + add_creditcard_or_reference(post, credit_card_or_reference) + add_invoice(post, options) + add_instant_capture(post, true) + + commit(:authorize, post) + end + + def capture(money, authorization, options = {}) + post = {} + + add_reference(post, authorization) + add_amount_without_currency(post, money) + + commit(:capture, post) + end + + def void(identification, options = {}) + post = {} + + add_reference(post, identification) + + commit(:void, post) + end + + def refund(money, identification, options = {}) + post = {} + + add_amount_without_currency(post, money) + add_reference(post, identification) + + commit(:credit, post) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + private + + def add_amount(post, money, options) + post[:amount] = amount(money) + post[:currency] = CURRENCY_CODES[(options[:currency] || currency(money)).to_sym] + end + + def add_amount_without_currency(post, money) + post[:amount] = amount(money) + end + + def add_reference(post, identification) + post[:transaction] = identification + end + + def add_invoice(post, options) + post[:orderid] = format_order_number(options[:order_id]) + end + + def add_creditcard(post, credit_card) + post[:cardno] = credit_card.number + post[:cvc] = credit_card.verification_value + post[:expmonth] = credit_card.month + post[:expyear] = credit_card.year + end + + def add_creditcard_or_reference(post, credit_card_or_reference) + if credit_card_or_reference.respond_to?(:number) + add_creditcard(post, credit_card_or_reference) + else + add_reference(post, credit_card_or_reference.to_s) + end + end + + def add_instant_capture(post, option) + post[:instantcapture] = option ? 1 : 0 + end + + def commit(action, params) + response = send("do_#{action}", params) + + if action == :authorize + Response.new response['accept'].to_i == 1, + response['errortext'], + response, + :test => test?, + :authorization => response['tid'] + else + Response.new response['result'] == 'true', + messages(response['epay'], response['pbs']), + response, + :test => test?, + :authorization => params[:transaction] + end + end + + def messages(epay, pbs = nil) + response = "ePay: #{epay}" + response << " PBS: #{pbs}" if pbs + return response + end + + def soap_post(method, params) + data = xml_builder(params, method) + headers = make_headers(data, method) + REXML::Document.new(ssl_post('https://' + API_HOST + '/remote/payment.asmx', data, headers)) + end + + def do_authorize(params) + headers = {} + headers['Referer'] = (options[:password] || "activemerchant.org") + + response = raw_ssl_request(:post, 'https://' + API_HOST + '/auth/default.aspx', authorize_post_data(params), headers) + + # Authorize gives the response back by redirecting with the values in + # the URL query + if location = response['Location'] + query = CGI::parse(URI.parse(location.gsub(' ', '%20')).query) + else + return { + 'accept' => '0', + 'errortext' => 'ePay did not respond as expected. Please try again.', + 'response_code' => response.code, + 'response_message' => response.message + } + end + + result = {} + query.each_pair do |k,v| + result[k] = v.is_a?(Array) && v.size == 1 ? v[0] : v # make values like ['v'] into 'v' + end + result + end + + def do_capture(params) + response = soap_post('capture', params) + { + 'result' => response.elements['//captureResponse/captureResult'].text, + 'pbs' => response.elements['//captureResponse/pbsResponse'].text, + 'epay' => response.elements['//captureResponse/epayresponse'].text + } + end + + def do_credit(params) + response = soap_post('credit', params) + { + 'result' => response.elements['//creditResponse/creditResult'].text, + 'pbs' => response.elements['//creditResponse/pbsresponse'].text, + 'epay' => response.elements['//creditResponse/epayresponse'].text + } + end + + def do_void(params) + response = soap_post('delete', params) + { + 'result' => response.elements['//deleteResponse/deleteResult'].text, + 'epay' => response.elements['//deleteResponse/epayresponse'].text + } + end + + def make_headers(data, soap_call) + { + 'Content-Type' => 'text/xml; charset=utf-8', + 'Host' => API_HOST, + 'Content-Length' => data.size.to_s, + 'SOAPAction' => self.live_url + '/' + soap_call + } + end + + def xml_builder(params, soap_call) + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct! + xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } do + xml.tag! 'soap:Body' do + xml.tag! soap_call, { 'xmlns' => self.live_url } do + xml.tag! 'merchantnumber', @options[:login] + xml.tag! 'transactionid', params[:transaction] + xml.tag! 'amount', params[:amount].to_s if soap_call != 'delete' + end + end + end + xml.target! + end + + def authorize_post_data(params = {}) + params[:language] = '2' + params[:cms] = 'activemerchant' + params[:accepturl] = 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?accept=1' + params[:declineurl] = 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?decline=1' + params[:merchantnumber] = @options[:login] + + params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + # Limited to 20 digits max + def format_order_number(number) + number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20] + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/evo_ca.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/evo_ca.rb new file mode 100644 index 000000000..c2f76883e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/evo_ca.rb @@ -0,0 +1,308 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # === EVO Canada payment gateway. + # + # EVO returns two different identifiers for most transactions, the + # +authcode+ and the +transactionid+. Since +transactionid+ is used more + # often (i.e. for {#capture}, {#refund}, {#void} and {#update}) we store it in the + # Response#authorization attribute. The +authcode+ from the merchant + # account is accessible via {Response#params}. + # + # Two different but related response messages are also returned from EVO. + # The message indicated by EVO's response_code parameter is returned as + # {Response#message} (Those messages can be seen in the {MESSAGES} hash.) + # The other, shorter message is available via {Response#params}. + # + # It's recommended to save the contents of the {Response#params} in your + # transaction log for future reference. + # + # === Sample Use + # + # gateway = ActiveMerchant::Billing::EvoCaGateway.new(username: 'demo', password: 'password') + # + # response = gateway.authorize(1000, credit_card, options) + # + # puts response.authorization # the transactionid + # puts response.params['authcode'] # the authcode from the merchant account + # puts response.message # the 'pretty' response message + # puts response.params['responsetext'] # the 'terse' response message + # + # gateway.capture(1000, response.authorization) + # gateway.update(response.authorization, shipping_carrier: 'fedex') + # gateway.refund(500, response.authorization) + # + class EvoCaGateway < Gateway + self.test_url = 'https://secure.evoepay.com/api/transact.php' + self.live_url = 'https://secure.evoepay.com/api/transact.php' + + self.supported_countries = ['CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover] + self.money_format = :dollars + self.homepage_url = 'http://www.evocanada.com/' + self.display_name = 'EVO Canada' + + APPROVED, DECLINED, ERROR = 1, 2, 3 + + MESSAGES = { + 100 => 'Transaction was approved', + 200 => 'Transaction was declined by processor', + 201 => 'Do not honor', + 202 => 'Insufficient funds', + 203 => 'Over limit', + 204 => 'Transaction not allowed', + 220 => 'Incorrect payment data', + 221 => 'No such card issuer', + 222 => 'No card number on file with issuer', + 223 => 'Expired card', + 224 => 'Invalid expiration date', + 225 => 'Invalid card security code', + 240 => 'Call issuer for futher information', + 250 => 'Pick up card', + 251 => 'Lost card', + 252 => 'Stolen card', + 253 => 'Fraudulant card', + 260 => 'Declined with further instructions available', + 261 => 'Declined - stop all recurring payments', + 262 => 'Declined - stop this recurring program', + 263 => 'Declined - updated cardholder data available', + 264 => 'Declined - retry in a few days', + 300 => 'Transaction was rejected by gateway', + 400 => 'Transaction error returned by processor', + 410 => 'Invalid merchant configuration', + 411 => 'Merchant account is inactive', + 420 => 'Communication error', + 421 => 'Communication error with issuer', + 430 => 'Duplicate transaction at processor', + 440 => 'Processor format error', + 441 => 'Invalid transaction information', + 460 => 'Processor feature not available', + 461 => 'Unsupported card type' + } + + # This gateway requires that a valid username and password be passed + # in the +options+ hash. + # + # === Required Options + # + # * :username + # * :password + def initialize(options = {}) + requires!(options, :username, :password) + super + end + + # Transaction sales are submitted and immediately flagged for settlement. + # These transactions will automatically be settled. + # + # Payment source can be either a {CreditCard} or {Check}. + # + # === Additional Options + # In addition to the standard options, this gateway supports + # + # * :tracking_number - Shipping tracking number + # * :shipping_carrier - ups/fedex/dhl/usps + # * :po_number - Purchase order + # * :tax - Tax amount + # * :shipping - Shipping cost + def purchase(money, credit_card_or_check, options = {}) + post = {} + add_invoice(post, options) + add_order(post, options) + add_paymentmethod(post, credit_card_or_check) + add_address(post, options) + add_customer_data(post, options) + commit('sale', money, post) + end + + # Transaction authorizations are authorized immediately but are not + # flagged for settlement. These transactions must be flagged for + # settlement using the _capture_ transaction type. Authorizations + # typically remain activate for three to seven business days. + # + # Payment source must be a {CreditCard}. + def authorize(money, credit_card, options = {}) + post = {} + add_invoice(post, options) + add_order(post, options) + add_paymentmethod(post, credit_card) + add_address(post, options) + add_customer_data(post, options) + commit('auth', money, post) + end + + # Transaction captures flag existing _authorizations_ for settlement. Only + # authorizations can be captured. Captures can be submitted for an amount + # equal to or less than the original authorization. + # + # The authorization parameter is the transaction ID, retrieved + # from Response#authorization. See EvoCaGateway#purchase for the + # options. + def capture(money, authorization, options = {}) + post = { + :amount => amount(money), + :transactionid => authorization + } + add_order(post, options) + commit('capture', money, post) + end + + # Transaction refunds will reverse a previously settled transaction. If + # the transaction has not been settled, it must be _voided_ instead of + # refunded. + # + # The identification parameter is the transaction ID, retrieved + # from {Response#authorization}. + def refund(money, identification) + post = {:transactionid => identification} + commit('refund', money, post) + end + + # Transaction credits apply a negative amount to the cardholder's card. + # In most situations credits are disabled as transaction refunds should + # be used instead. + # + # Note that this is different from a {#refund} (which is usually what + # you'll be looking for). + def credit(money, credit_card, options = {}) + post = {} + add_invoice(post, options) + add_order(post, options) + add_paymentmethod(post, credit_card) + add_address(post, options) + add_customer_data(post, options) + commit('credit', money, post) + end + + # Transaction voids will cancel an existing sale or captured + # authorization. In addition, non-captured authorizations can be voided to + # prevent any future capture. Voids can only occur if the transaction has + # not been settled. + # + # The identification parameter is the transaction ID, retrieved + # from {Response#authorization}. + def void(identification) + post = {:transactionid => identification} + commit('void', nil, post) + end + + # Transaction updates can be used to update previous transactions with + # specific order information, such as a tracking number and shipping + # carrier. See EvoCaGateway#purchase for options. + # + # The identification parameter is the transaction ID, retrieved + # from {Response#authorization}. + def update(identification, options) + post = {:transactionid => identification} + add_order(post, options) + commit('update', nil, post) + end + + private + + def add_customer_data(post, options) + post[:email] = options[:email] + post[:ipaddress] = options[:ip] + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:firstname] = address[:first_name] + post[:lastname] = address[:last_name] + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:company] = address[:company] + post[:phone] = address[:phone] + post[:city] = address[:city] + post[:state] = address[:state] + post[:zip] = address[:zip] + post[:country] = address[:country] + end + + if address = options[:shipping_address] + post[:shipping_firstname] = address[:first_name] + post[:shipping_lastname] = address[:last_name] + post[:shipping_address1] = address[:address1] + post[:shipping_address2] = address[:address2] + post[:shipping_company] = address[:company] + post[:shipping_zip] = address[:zip] + post[:shipping_city] = address[:city] + post[:shipping_state] = address[:state] + post[:shipping_country] = address[:country] + end + end + + def add_order(post, options) + post[:orderid] = options[:order_id] + post[:tracking_number] = options[:tracking_number] + post[:shipping_carrier] = options[:shipping_carrier] + end + + def add_invoice(post, options) + post[:orderdescription] = options[:description] + post[:ponumber] = options[:po_number] + post[:shipping] = amount(options[:shipping]) + post[:tax] = amount(options[:tax]) + end + + def add_paymentmethod(post, payment) + if card_brand(payment)=='check' + post[:payment] = 'check' + post[:checkname] = payment.name + post[:checkaba] = payment.routing_number + post[:checkaccount] = payment.account_number + post[:account_holder_type] = payment.account_holder_type + post[:account_type] = payment.account_type + else + post[:payment] = 'creditcard' + post[:ccnumber] = payment.number + post[:ccexp] = "#{format(payment.month, :two_digits)}#{format(payment.year, :two_digits)}" + post[:cvv] = payment.verification_value + end + end + + def parse(body) + fields = {} + CGI::parse(body).each do |k, v| + fields[k.to_s] = v.kind_of?(Array) ? v[0] : v + end + fields + end + + def success?(response) + response['response'].to_i == APPROVED + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) unless action == 'void' + + data = ssl_post self.live_url, post_data(action, parameters) + response = parse(data) + message = message_from(response) + + Response.new(success?(response), message, response, + :test => test?, + :authorization => response['transactionid'], + :avs_result => { :code => response['avsresponse'] }, + :cvv_result => response['cvvresponse'] + ) + end + + def message_from(response) + MESSAGES.fetch(response['response_code'].to_i, false) || response['message'] + end + + def post_data(action, parameters = {}) + post = {:type => action} + + if test? + post[:username] = 'demo' + post[:password] = 'password' + else + post[:username] = options[:username] + post[:password] = options[:password] + end + post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" unless value.nil? }.compact.join("&") + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway.rb new file mode 100644 index 000000000..e21a1c071 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway.rb @@ -0,0 +1,225 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # Public: For more information on the Eway Gateway please visit their + # {Developers Area}[http://www.eway.com.au/developers/api/direct-payments] + class EwayGateway < Gateway + self.live_url = 'https://www.eway.com.au' + + self.money_format = :cents + self.supported_countries = ['AU'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.homepage_url = 'http://www.eway.com.au/' + self.display_name = 'eWAY' + + # Public: Create a new Eway Gateway. + # options - A hash of options: + # :login - Your Customer ID. + # :password - Your XML Refund Password that you + # specified on the Eway site. (optional) + def initialize(options = {}) + requires!(options, :login) + super + end + + def purchase(money, creditcard, options = {}) + requires_address!(options) + + post = {} + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_id(post) + add_invoice_data(post, options) + add_non_optional_data(post) + add_amount(post, money) + post[:CustomerEmail] = options[:email] + + commit(purchase_url(post[:CVN]), money, post) + end + + def refund(money, authorization, options={}) + post = {} + + add_customer_id(post) + add_amount(post, money) + add_non_optional_data(post) + post[:OriginalTrxnNumber] = authorization + post[:RefundPassword] = @options[:password] + post[:CardExpiryMonth] = nil + post[:CardExpiryYear] = nil + + commit(refund_url, money, post) + end + + private + def requires_address!(options) + raise ArgumentError.new("Missing eWay required parameters: address or billing_address") unless (options.has_key?(:address) or options.has_key?(:billing_address)) + end + + def add_creditcard(post, creditcard) + post[:CardNumber] = creditcard.number + post[:CardExpiryMonth] = sprintf("%.2i", creditcard.month) + post[:CardExpiryYear] = sprintf("%.4i", creditcard.year)[-2..-1] + post[:CustomerFirstName] = creditcard.first_name + post[:CustomerLastName] = creditcard.last_name + post[:CardHoldersName] = creditcard.name + + post[:CVN] = creditcard.verification_value if creditcard.verification_value? + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:CustomerAddress] = [ address[:address1], address[:address2], address[:city], address[:state], address[:country] ].compact.join(', ') + post[:CustomerPostcode] = address[:zip] + end + end + + def add_customer_id(post) + post[:CustomerID] = @options[:login] + end + + def add_invoice_data(post, options) + post[:CustomerInvoiceRef] = options[:order_id] + post[:CustomerInvoiceDescription] = options[:description] + end + + def add_amount(post, money) + post[:TotalAmount] = amount(money) + end + + def add_non_optional_data(post) + post[:Option1] = nil + post[:Option2] = nil + post[:Option3] = nil + post[:TrxnNumber] = nil + end + + def commit(url, money, parameters) + raw_response = ssl_post(url, post_data(parameters)) + response = parse(raw_response) + + Response.new(success?(response), + message_from(response[:ewaytrxnerror]), + response, + :authorization => response[:ewaytrxnnumber], + :test => test? + ) + end + + def success?(response) + response[:ewaytrxnstatus] == "True" + end + + def parse(xml) + response = {} + xml = REXML::Document.new(xml) + xml.elements.each('//ewayResponse/*') do |node| + response[node.name.downcase.to_sym] = normalize(node.text) + end unless xml.root.nil? + + response + end + + def post_data(parameters = {}) + xml = REXML::Document.new + root = xml.add_element("ewaygateway") + + parameters.each do |key, value| + root.add_element("eway#{key}").text = value + end + xml.to_s + end + + def message_from(message) + return '' if message.blank? + MESSAGES[message[0,2]] || message + 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 purchase_url(cvn) + suffix = test? ? 'xmltest/testpage.asp' : 'xmlpayment.asp' + gateway_part = cvn ? 'gateway_cvn' : 'gateway' + "#{live_url}/#{gateway_part}/#{suffix}" + end + + def refund_url + suffix = test? ? 'xmltest/refund_test.asp' : 'xmlpaymentrefund.asp' + "#{live_url}/gateway/#{suffix}" + end + + MESSAGES = { + "00" => "Transaction Approved", + "01" => "Refer to Issuer", + "02" => "Refer to Issuer, special", + "03" => "No Merchant", + "04" => "Pick Up Card", + "05" => "Do Not Honour", + "06" => "Error", + "07" => "Pick Up Card, Special", + "08" => "Honour With Identification", + "09" => "Request In Progress", + "10" => "Approved For Partial Amount", + "11" => "Approved, VIP", + "12" => "Invalid Transaction", + "13" => "Invalid Amount", + "14" => "Invalid Card Number", + "15" => "No Issuer", + "16" => "Approved, Update Track 3", + "19" => "Re-enter Last Transaction", + "21" => "No Action Taken", + "22" => "Suspected Malfunction", + "23" => "Unacceptable Transaction Fee", + "25" => "Unable to Locate Record On File", + "30" => "Format Error", + "31" => "Bank Not Supported By Switch", + "33" => "Expired Card, Capture", + "34" => "Suspected Fraud, Retain Card", + "35" => "Card Acceptor, Contact Acquirer, Retain Card", + "36" => "Restricted Card, Retain Card", + "37" => "Contact Acquirer Security Department, Retain Card", + "38" => "PIN Tries Exceeded, Capture", + "39" => "No Credit Account", + "40" => "Function Not Supported", + "41" => "Lost Card", + "42" => "No Universal Account", + "43" => "Stolen Card", + "44" => "No Investment Account", + "51" => "Insufficient Funds", + "52" => "No Cheque Account", + "53" => "No Savings Account", + "54" => "Expired Card", + "55" => "Incorrect PIN", + "56" => "No Card Record", + "57" => "Function Not Permitted to Cardholder", + "58" => "Function Not Permitted to Terminal", + "59" => "Suspected Fraud", + "60" => "Acceptor Contact Acquirer", + "61" => "Exceeds Withdrawal Limit", + "62" => "Restricted Card", + "63" => "Security Violation", + "64" => "Original Amount Incorrect", + "66" => "Acceptor Contact Acquirer, Security", + "67" => "Capture Card", + "75" => "PIN Tries Exceeded", + "82" => "CVV Validation Error", + "90" => "Cutoff In Progress", + "91" => "Card Issuer Unavailable", + "92" => "Unable To Route Transaction", + "93" => "Cannot Complete, Violation Of The Law", + "94" => "Duplicate Transaction", + "96" => "System Error" + } + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_managed.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_managed.rb new file mode 100644 index 000000000..2dcae353a --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_managed.rb @@ -0,0 +1,291 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class EwayManagedGateway < Gateway + self.test_url = 'https://www.eway.com.au/gateway/ManagedPaymentService/test/managedCreditCardPayment.asmx' + self.live_url = 'https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['AU'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master] + + self.default_currency = 'AUD' + + #accepted money format + self.money_format = :cents + + # The homepage URL of the gateway + self.homepage_url = 'http://www.eway.com.au/' + + # The name of the gateway + self.display_name = 'eWay Managed Payments' + + def initialize(options = {}) + requires!(options, :login, :username, :password) + + # eWay returns 500 code for faults, which AM snaffles. + # So, we tell it to allow them. + options[:ignore_http_status] = true + super + end + + # add a new customer CC to your eway account and return unique ManagedCustomerID + # supports storing details required by eway see "add_creditcard" and "add_address" + def store(creditcard, options = {}) + post = {} + + # Handle our required fields + requires!(options, :billing_address) + + # Handle eWay specific required fields. + billing_address = options[:billing_address] + eway_requires!(billing_address) + + add_creditcard(post, creditcard) + add_address(post, billing_address) + add_misc_fields(post, options) + + commit("CreateCustomer", post) + end + + def update(billing_id, creditcard, options={}) + post = {} + + # Handle our required fields + requires!(options, :billing_address) + + # Handle eWay specific required fields. + billing_address = options[:billing_address] + eway_requires!(billing_address) + + post[:managedCustomerID]=billing_id + add_creditcard(post, creditcard) + add_address(post, billing_address) + add_misc_fields(post, options) + + commit("UpdateCustomer", post) + end + + # Process a payment in the given amount against the stored credit card given by billing_id + # + # ==== Parameters + # + # * money -- The amount to be purchased as an Integer value in cents. + # * billing_id -- The eWay provided card/customer token to charge (managedCustomerID) + # * options -- A hash of optional parameters. + # + # ==== Options + # + # * :order_id -- The order number, passed to eWay as the "Invoice Reference" + # * :invoice -- The invoice number, passed to eWay as the "Invoice Reference" unless :order_id is also given + # * :description -- A description of the payment, passed to eWay as the "Invoice Description" + def purchase(money, billing_id, options={}) + post = {} + post[:managedCustomerID] = billing_id.to_s + post[:amount]=money + add_invoice(post, options) + + commit("ProcessPayment", post) + end + + # Get customer's stored credit card details given by billing_id + # + # ==== Parameters + # + # * billing_id -- The eWay provided card/customer token to charge (managedCustomerID) + def retrieve(billing_id) + post = {} + post[:managedCustomerID] = billing_id.to_s + + commit("QueryCustomer", post) + end + + # TODO: eWay API also provides QueryPayment + + private + + def eway_requires!(hash) + raise ArgumentError.new("Missing eWay required parameter in `billing_address`: title") unless hash.has_key?(:title) + raise ArgumentError.new("Missing eWay required parameter in `billing_address`: country") unless hash.has_key?(:country) + end + + def add_address(post, address) + post[:Address] = address[:address1].to_s + post[:Phone] = address[:phone].to_s + post[:PostCode] = address[:zip].to_s + post[:Suburb] = address[:city].to_s + post[:Country] = address[:country].to_s.downcase + post[:State] = address[:state].to_s + post[:Mobile] = address[:mobile].to_s + post[:Fax] = address[:fax].to_s + end + + def add_misc_fields(post, options) + post[:CustomerRef]=options[:billing_address][:customer_ref] || options[:customer] + post[:Title]=options[:billing_address][:title] + post[:Company]=options[:billing_address][:company] + post[:JobDesc]=options[:billing_address][:job_desc] + post[:Email]=options[:billing_address][:email] || options[:email] + post[:URL]=options[:billing_address][:url] + post[:Comments]=options[:description] + end + + def add_invoice(post, options) + post[:invoiceReference] = options[:order_id] || options[:invoice] + post[:invoiceDescription] = options[:description] + end + + + # add credit card details to be stored by eway. NOTE eway requires "title" field + def add_creditcard(post, creditcard) + post[:CCNumber] = creditcard.number + post[:CCExpiryMonth] = sprintf("%.2i", creditcard.month) + post[:CCExpiryYear] = sprintf("%.4i", creditcard.year)[-2..-1] + post[:CCNameOnCard] = creditcard.name + post[:FirstName] = creditcard.first_name + post[:LastName] = creditcard.last_name + end + + def parse(body) + reply = {} + xml = REXML::Document.new(body) + if root = REXML::XPath.first(xml, "//soap:Fault") then + reply=parse_fault(root) + else + if root = REXML::XPath.first(xml, '//ProcessPaymentResponse/ewayResponse') then + # Successful payment + reply=parse_purchase(root) + else + if root = REXML::XPath.first(xml, '//QueryCustomerResult') then + reply=parse_query_customer(root) + else + if root = REXML::XPath.first(xml, '//CreateCustomerResult') then + reply[:message]='OK' + reply[:CreateCustomerResult]=root.text + reply[:success]=true + else + if root = REXML::XPath.first(xml, '//UpdateCustomerResult') then + if root.text.downcase == 'true' then + reply[:message]='OK' + reply[:success]=true + else + # ERROR: This state should never occur. If there is a problem, + # a soap:Fault will be returned. The presence of this + # element always means a success. + raise StandardError, "Unexpected \"false\" in UpdateCustomerResult" + end + else + # ERROR: This state should never occur currently. We have handled + # responses for all the methods which we support. + raise StandardError, "Unexpected response" + end + end + end + end + end + return reply + end + + def parse_fault(node) + reply={} + reply[:message]=REXML::XPath.first(node, '//soap:Reason/soap:Text').text + reply[:success]=false + reply + end + + def parse_purchase(node) + reply={} + reply[:message]=REXML::XPath.first(node, '//ewayTrxnError').text + reply[:success]=(REXML::XPath.first(node, '//ewayTrxnStatus').text == 'True') + reply[:auth_code]=REXML::XPath.first(node, '//ewayAuthCode').text + reply[:transaction_number]=REXML::XPath.first(node, '//ewayTrxnNumber').text + reply + end + + def parse_query_customer(node) + reply={} + reply[:message]='OK' + reply[:success]=true + reply[:CCNumber]=REXML::XPath.first(node, '//CCNumber').text + reply[:CCName]=REXML::XPath.first(node, '//CCName').text + reply[:CCExpiryMonth]=REXML::XPath.first(node, '//CCExpiryMonth').text + reply[:CCExpiryYear]=REXML::XPath.first(node, '//CCExpiryYear').text + reply + end + + def commit(action, post) + raw = begin + ssl_post(test? ? self.test_url : self.live_url, soap_request(post, action), 'Content-Type' => 'application/soap+xml; charset=utf-8') + rescue ResponseError => e + e.response.body + end + response = parse(raw) + + EwayResponse.new(response[:success], response[:message], response, + :test => test?, + :authorization => response[:auth_code] + ) + end + + # Where we build the full SOAP 1.2 request using builder + def soap_request(arguments, action) + # eWay demands all fields be sent, but contain an empty string if blank + post = case action + when 'QueryCustomer' + arguments + when 'ProcessPayment' + default_payment_fields.merge(arguments) + when 'CreateCustomer' + default_customer_fields.merge(arguments) + when 'UpdateCustomer' + default_customer_fields.merge(arguments) + end + + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! 'soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'} do + xml.tag! 'soap12:Header' do + xml.tag! 'eWAYHeader', {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do + xml.tag! 'eWAYCustomerID', @options[:login] + xml.tag! 'Username', @options[:username] + xml.tag! 'Password', @options[:password] + end + end + xml.tag! 'soap12:Body' do |x| + x.tag! "#{action}", {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do |y| + post.each do |key, value| + y.tag! "#{key}", "#{value}" + end + end + end + end + xml.target! + end + + def default_customer_fields + hash={} + %w( CustomerRef Title FirstName LastName Company JobDesc Email Address Suburb State PostCode Country Phone Mobile Fax URL Comments CCNumber CCNameOnCard CCExpiryMonth CCExpiryYear ).each do |field| + hash[field.to_sym]='' + end + return hash + end + + def default_payment_fields + hash={} + %w( managedCustomerID amount invoiceReference invoiceDescription ).each do |field| + hash[field.to_sym]='' + end + return hash + end + + class EwayResponse < Response + # add a method to response so we can easily get the eway token "ManagedCustomerID" + def token + @params['CreateCustomerResult'] + end + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_rapid.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_rapid.rb new file mode 100644 index 000000000..628f91889 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/eway_rapid.rb @@ -0,0 +1,300 @@ +require "nokogiri" +require "cgi" + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class EwayRapidGateway < Gateway + self.test_url = "https://api.sandbox.ewaypayments.com/" + self.live_url = "https://api.ewaypayments.com/" + + self.money_format = :cents + self.supported_countries = ["AU"] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.homepage_url = "http://www.eway.com.au/" + self.display_name = "eWAY Rapid 3.0" + self.default_currency = "AUD" + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Public: Run a purchase transaction. Treats the Rapid 3.0 transparent + # redirect as an API endpoint in order to conform to the standard + # ActiveMerchant #purchase API. + # + # amount - The monetary amount of the transaction in cents. + # options - A standard ActiveMerchant options hash: + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :currency - Three letter currency code for the + # transaction (default: "AUD") + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :shipping_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/Shopify/active_merchant") + # + # Returns an ActiveMerchant::Billing::Response object + def purchase(amount, payment_method, options={}) + MultiResponse.new.tap do |r| + # Rather than follow the redirect, we detect the 302 and capture the + # token out of the Location header in the run_purchase step. But we + # still need a placeholder url to pass to eWay, and that is what + # example.com is used for here. + r.process{setup_purchase(amount, options.merge(:redirect_url => "http://example.com/"))} + r.process{run_purchase(r.authorization, payment_method, r.params["formactionurl"])} + r.process{status(r.authorization)} + end + end + + # Public: Acquire the token necessary to run a transparent redirect. + # + # amount - The monetary amount of the transaction in cents. + # options - A supplemented ActiveMerchant options hash: + # :redirect_url - The url to return the customer to after + # the transparent redirect is completed + # (required). + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :currency - Three letter currency code for the + # transaction (default: "AUD") + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :shipping_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/Shopify/active_merchant") + # + # Returns an EwayRapidResponse object, which conforms to the + # ActiveMerchant::Billing::Response API, but also exposes #form_url. + def setup_purchase(amount, options={}) + requires!(options, :redirect_url) + request = build_xml_request("CreateAccessCodeRequest") do |doc| + add_metadata(doc, options) + add_invoice(doc, amount, options) + add_customer_data(doc, options) + end + + commit(url_for("CreateAccessCode"), request) + end + + # Public: Retrieve the status of a transaction. + # + # identification - The Eway Rapid 3.0 access code for the transaction + # (returned as the response.authorization by + # #setup_purchase). + # + # Returns an EwayRapidResponse object. + def status(identification) + request = build_xml_request("GetAccessCodeResultRequest") do |doc| + doc.AccessCode identification + end + commit(url_for("GetAccessCodeResult"), request) + end + + private + + def run_purchase(identification, payment_method, endpoint) + post = { + "accesscode" => identification + } + add_credit_card(post, payment_method) + + commit_form(endpoint, build_form_request(post)) + end + + def add_metadata(doc, options) + doc.RedirectUrl(options[:redirect_url]) + doc.CustomerIP options[:ip] if options[:ip] + doc.Method "ProcessPayment" + doc.DeviceID(options[:application_id] || application_id) + end + + def add_invoice(doc, money, options) + doc.Payment do + doc.TotalAmount amount(money) + doc.InvoiceReference options[:order_id] + doc.InvoiceDescription options[:description] + currency_code = (options[:currency] || currency(money) || default_currency) + doc.CurrencyCode currency_code + end + end + + def add_customer_data(doc, options) + doc.Customer do + add_address(doc, (options[:billing_address] || options[:address]), {:email => options[:email]}) + end + doc.ShippingAddress do + add_address(doc, options[:shipping_address], {:skip_company => true}) + end + end + + def add_address(doc, address, options={}) + return unless address + if name = address[:name] + parts = name.split(/\s+/) + doc.FirstName parts.shift if parts.size > 1 + doc.LastName parts.join(" ") + end + doc.CompanyName address[:company] unless options[:skip_company] + doc.Street1 address[:address1] + doc.Street2 address[:address2] + doc.City address[:city] + doc.State address[:state] + doc.PostalCode address[:zip] + doc.Country address[:country] + doc.Phone address[:phone] + doc.Fax address[:fax] + doc.Email options[:email] + end + + def add_credit_card(post, credit_card) + post["cardname"] = credit_card.name + post["cardnumber"] = credit_card.number + post["cardexpirymonth"] = credit_card.month + post["cardexpiryyear"] = credit_card.year + post["cardcvn"] = credit_card.verification_value + end + + def build_xml_request(root) + builder = Nokogiri::XML::Builder.new + builder.__send__(root) do |doc| + yield(doc) + end + builder.to_xml + end + + def build_form_request(post) + request = [] + post.each do |key, value| + request << "EWAY_#{key.upcase}=#{CGI.escape(value.to_s)}" + end + request.join("&") + end + + def url_for(action) + (test? ? test_url : live_url) + action + ".xml" + end + + def commit(url, request, form_post=false) + headers = { + "Authorization" => ("Basic " + Base64.strict_encode64(@options[:login].to_s + ":" + @options[:password].to_s).chomp), + "Content-Type" => "text/xml" + } + + raw = parse(ssl_post(url, request, headers)) + + succeeded = success?(raw) + EwayRapidResponse.new( + succeeded, + message_from(succeeded, raw), + raw, + :authorization => authorization_from(raw), + :test => test?, + :avs_result => avs_result_from(raw), + :cvv_result => cvv_result_from(raw) + ) + rescue ActiveMerchant::ResponseError => e + return EwayRapidResponse.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) + end + + def commit_form(url, request) + http_response = raw_ssl_request(:post, url, request) + + success = (http_response.code.to_s == "302") + message = (success ? "Succeeded" : http_response.body) + if success + authorization = CGI.unescape(http_response["Location"].split("=").last) + end + Response.new(success, message, {:location => http_response["Location"]}, :authorization => authorization, :test => test?) + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.root.xpath("*").each do |node| + if (node.elements.size == 0) + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name.downcase}_#{childnode.name.downcase}" + response[name.to_sym] = childnode.text + end + end + end unless doc.root.nil? + + response + end + + def success?(response) + if response[:errors] + false + elsif response[:transactionstatus] + (response[:transactionstatus] == "true") + else + true + end + end + + def message_from(succeeded, response) + if response[:errors] + response[:errors] + elsif response[:responsecode] + ActiveMerchant::Billing::EwayGateway::MESSAGES[response[:responsecode]] + elsif response[:responsemessage] + response[:responsemessage] + elsif succeeded + "Succeeded" + else + "Failed" + end + end + + def authorization_from(response) + response[:accesscode] + end + + def avs_result_from(response) + code = case response[:verification_address] + when "Valid" + "M" + when "Invalid" + "N" + else + "I" + end + {:code => code} + end + + def cvv_result_from(response) + case response[:verification_cvn] + when "Valid" + "M" + when "Invalid" + "N" + else + "P" + end + end + + class EwayRapidResponse < ActiveMerchant::Billing::Response + def form_url + params["formactionurl"] + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/exact.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/exact.rb new file mode 100644 index 000000000..8b6f04ec2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/exact.rb @@ -0,0 +1,218 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ExactGateway < Gateway + self.live_url = self.test_url = 'https://secure2.e-xact.com/vplug-in/transaction/rpc-enc/service.asmx' + + API_VERSION = "8.5" + + TEST_LOGINS = [ {:login => "A00049-01", :password => "test1"}, + {:login => "A00427-01", :password => "testus"} ] + + TRANSACTIONS = { :sale => "00", + :authorization => "01", + :capture => "32", + :credit => "34" } + + + ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' + } + + SEND_AND_COMMIT_ATTRIBUTES = { 'xmlns:n1' => "http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Request", + 'env:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' + } + + SEND_AND_COMMIT_SOURCE_ATTRIBUTES = { 'xmlns:n2' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes', + 'xsi:type' => 'n2:Transaction' + } + + POST_HEADERS = { 'soapAction' => "http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/SendAndCommit", + 'Content-Type' => 'text/xml' + } + + SUCCESS = "true" + + SENSITIVE_FIELDS = [ :verification_str2, :expiry_date, :card_number ] + + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover] + self.supported_countries = ['CA', 'US'] + self.homepage_url = 'http://www.e-xact.com' + self.display_name = 'E-xact' + + def initialize(options = {}) + requires!(options, :login, :password) + + super + end + + def authorize(money, credit_card, options = {}) + commit(:authorization, build_sale_or_authorization_request(money, credit_card, options)) + end + + def purchase(money, credit_card, options = {}) + commit(:sale, build_sale_or_authorization_request(money, credit_card, options)) + end + + def capture(money, authorization, options = {}) + commit(:capture, build_capture_or_credit_request(money, authorization, options)) + end + + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def refund(money, authorization, options = {}) + commit(:credit, build_capture_or_credit_request(money, authorization, options)) + end + + private + def build_request(action, body) + xml = Builder::XmlMarkup.new + + xml.instruct! + xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do + xml.tag! 'env:Body' do + xml.tag! 'n1:SendAndCommit', SEND_AND_COMMIT_ATTRIBUTES do + xml.tag! 'SendAndCommitSource', SEND_AND_COMMIT_SOURCE_ATTRIBUTES do + add_credentials(xml) + add_transaction_type(xml, action) + xml << body + end + end + end + end + xml.target! + end + + def build_sale_or_authorization_request(money, credit_card, options) + xml = Builder::XmlMarkup.new + + add_amount(xml, money) + add_credit_card(xml, credit_card) + add_customer_data(xml, options) + add_invoice(xml, options) + + xml.target! + end + + def build_capture_or_credit_request(money, identification, options) + xml = Builder::XmlMarkup.new + + add_identification(xml, identification) + add_amount(xml, money) + add_customer_data(xml, options) + + xml.target! + end + + def add_credentials(xml) + xml.tag! 'ExactID', @options[:login] + xml.tag! 'Password', @options[:password] + end + + def add_transaction_type(xml, action) + xml.tag! 'Transaction_Type', TRANSACTIONS[action] + end + + def add_identification(xml, identification) + authorization_num, transaction_tag = identification.split(';') + + xml.tag! 'Authorization_Num', authorization_num + xml.tag! 'Transaction_Tag', transaction_tag + end + + def add_amount(xml, money) + xml.tag! 'DollarAmount', amount(money) + end + + def add_credit_card(xml, credit_card) + xml.tag! 'Card_Number', credit_card.number + xml.tag! 'Expiry_Date', expdate(credit_card) + xml.tag! 'CardHoldersName', credit_card.name + + if credit_card.verification_value? + xml.tag! 'CVD_Presence_Ind', '1' + xml.tag! 'VerificationStr2', credit_card.verification_value + end + end + + def add_customer_data(xml, options) + xml.tag! 'Customer_Ref', options[:customer] + xml.tag! 'Client_IP', options[:ip] + xml.tag! 'Client_Email', options[:email] + end + + def add_address(xml, options) + if address = options[:billing_address] || options[:address] + xml.tag! 'ZipCode', address[:zip] + end + end + + def add_invoice(xml, options) + xml.tag! 'Reference_No', options[:order_id] + xml.tag! 'Reference_3', options[:description] + end + + def expdate(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" + end + + def commit(action, request) + response = parse(ssl_post(self.live_url, build_request(action, request), POST_HEADERS)) + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => authorization_from(response), + :avs_result => { :code => response[:avs] }, + :cvv_result => response[:cvv2] + ) + end + + def successful?(response) + response[:transaction_approved] == SUCCESS + end + + def authorization_from(response) + if response[:authorization_num] && response[:transaction_tag] + "#{response[:authorization_num]};#{response[:transaction_tag]}" + else + '' + end + end + + def message_from(response) + if response[:faultcode] && response[:faultstring] + response[:faultstring] + elsif response[:error_number] != '0' + response[:error_description] + else + result = response[:exact_message] || '' + result << " - #{response[:bank_message]}" unless response[:bank_message].blank? + result + end + end + + def parse(xml) + response = {} + xml = REXML::Document.new(xml) + + if root = REXML::XPath.first(xml, "//types:TransactionResult") + parse_elements(response, root) + elsif root = REXML::XPath.first(xml, "//soap:Fault") + parse_elements(response, root) + end + + response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) } + end + + def parse_elements(response, root) + root.elements.to_a.each do |node| + response[node.name.gsub(/EXact/, 'Exact').underscore.to_sym] = (node.text || '').strip + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/fat_zebra.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/fat_zebra.rb new file mode 100644 index 000000000..735f93153 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/fat_zebra.rb @@ -0,0 +1,152 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class FatZebraGateway < Gateway + self.live_url = "https://gateway.fatzebra.com.au/v1.0" + self.test_url = "https://gateway.sandbox.fatzebra.com.au/v1.0" + + self.supported_countries = ['AU'] + self.default_currency = 'AUD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :jcb] + + self.homepage_url = 'https://www.fatzebra.com.au/' + self.display_name = 'Fat Zebra' + + # Setup a new instance of the gateway. + # + # The options hash should include :username and :token + # You can find your username and token at https://dashboard.fatzebra.com.au + # Under the Your Account section + def initialize(options = {}) + requires!(options, :username) + requires!(options, :token) + @username = options[:username] + @token = options[:token] + super + end + + # To create a purchase on a credit card use: + # + # purchase(money, creditcard , { ... }) + # + # To charge a tokenized card + # + # purchase(money, {:token => "abzy87u", :cvv => "123"}, { ... }}) + def purchase(money, creditcard, options = {}) + post = {} + + add_amount(post, money, options) + add_creditcard(post, creditcard, options) + post[:reference] = options[:order_id] + post[:customer_ip] = options[:ip] + + commit(:post, 'purchases', post) + end + + # Refund a transaction + # + # amount - Integer - the amount to refund + # txn_id - String - the original transaction to be refunded + # reference - String - your transaction reference + def refund(money, txn_id, reference) + post = {} + + post[:amount] = money + post[:transaction_id] = txn_id + post[:reference] = reference + + commit(:post, "refunds", post) + end + + # Tokenize a credit card + def store(creditcard) + post = {} + add_creditcard(post, creditcard) + + commit(:post, "credit_cards", post) + end + + private + # Add the money details to the request + def add_amount(post, money, options) + post[:amount] = money + end + + # Add the credit card details to the request + def add_creditcard(post, creditcard, options = {}) + if creditcard.respond_to?(:number) + post[:card_number] = creditcard.number + post[:card_expiry] = "#{creditcard.month}/#{creditcard.year}" + post[:cvv] = creditcard.verification_value if creditcard.verification_value? + post[:card_holder] = creditcard.name if creditcard.name + else + post[:card_token] = creditcard[:token] + post[:cvv] = creditcard[:cvv] + end + end + + # Post the data to the gateway + def commit(method, uri, parameters=nil) + raw_response = response = nil + success = false + begin + raw_response = ssl_request(method, get_url(uri), parameters.to_json, headers) + response = parse(raw_response) + success = response["successful"] && (response["response"]["successful"] || response["response"]["token"]) + rescue ResponseError => e + if e.response.code == "401" + return Response.new(false, "Invalid Login") + end + + raw_response = e.response.body + response = parse(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + + message = response["response"]["message"] + unless response["successful"] + # There is an error, so we will show that instead + message = response["errors"].empty? ? "Unknown Error" : response["errors"].join(", ") + end + + Response.new(success, + message, + response, + :test => response.has_key?("test") ? response["test"] : false, + :authorization => response["response"]["id"] || response["response"]["token"]) + end + + # Parse the returned JSON, if parse errors are raised then return a detailed error. + def parse(response) + begin + JSON.parse(response) + rescue JSON::ParserError + msg = 'Invalid JSON response received from Fat Zebra. Please contact support@fatzebra.com.au if you continue to receive this message.' + msg += " (The raw response returned by the API was #{response.inspect})" + { + "successful" => false, + "response" => {}, + "errors" => [msg] + } + end + end + + # Build the URL based on the AM mode and the URI + def get_url(uri) + base = test? ? self.test_url : self.live_url + base + "/" + uri + end + + # Builds the auth and U-A headers for the request + def headers + { + "Authorization" => "Basic " + Base64.strict_encode64(@username.to_s + ":" + @token.to_s).strip, + "User-Agent" => "Fat Zebra v1.0/ActiveMerchant #{ActiveMerchant::VERSION}" + } + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/federated_canada.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/federated_canada.rb new file mode 100644 index 000000000..d08e87a44 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/federated_canada.rb @@ -0,0 +1,167 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class FederatedCanadaGateway < Gateway + # Same URL for both test and live, testing is done by using the test username (demo) and password (password). + self.live_url = self.test_url = 'https://secure.federatedgateway.com/api/transact.php' + + APPROVED, DECLINED, ERROR = 1, 2, 3 + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['CA'] + + self.default_currency = 'CAD' + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.federatedcanada.com/' + + # The name of the gateway + self.display_name = 'Federated Canada' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + commit('sale', money, post) + end + + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + commit('auth', money, post) + end + + def capture(money, authorization, options = {}) + options[:transactionid] = authorization + commit('capture', money, options) + end + + def void(authorization, options = {}) + options[:transactionid] = authorization + commit('void', nil, options) + end + + def refund(money, authorization, options = {}) + commit('refund', money, options.merge(:transactionid => authorization)) + end + + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + private + + def add_customer_data(post, options) + post[:firstname] = options[:first_name] + post[:lastname] = options[:last_name] + + post[:email] = options[:email] + end + + def add_address(post, options) + if address = (options[:billing_address] || options[:address]) + post[:company] = address[:company] + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:city] = address[:city] + post[:state] = address[:state] + post[:zip] = address[:zip] + post[:country] = address[:country] + post[:phone] = address[:phone] + end + if address = options[:shipping_address] + post[:shipping_firstname] = address[:first_name] + post[:shipping_lastname] = address[:last_name] + post[:shipping_company] = address[:company] + post[:shipping_address1] = address[:address1] + post[:shipping_address2] = address[:address2] + post[:shipping_city] = address[:city] + post[:shipping_state] = address[:state] + post[:shipping_zip] = address[:zip] + post[:shipping_country] = address[:country] + post[:shipping_email] = address[:email] + end + end + + def add_invoice(post, options) + post[:orderid] = options[:order_id] + post[:orderdescription] = options[:description] + end + + def add_creditcard(post, creditcard) + post[:ccnumber] = creditcard.number + post[:ccexp] = expdate(creditcard) + post[:cvv] = creditcard.verification_value + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + "#{month}#{year[-2..-1]}" + end + + def parse(body) + body.split('&').inject({}) do |memo, x| + k, v = x.split('=') + memo[k] = v + memo + end + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) + data = ssl_post(self.live_url, post_data(action, parameters)) + response = parse(data) + message = message_from(response) + test_mode = test? + + Response.new(success?(response), message, response, + :test => test?, + :authorization => response['transactionid'], + :avs_result => {:code => response['avsresponse']}, + :cvv_result => response['cvvresponse'] + ) + end + + def success?(response) + response['response'] == '1' + end + + def test? + (@options[:login].eql?('demo')) && (@options[:password].eql?('password')) + end + + def message_from(response) + case response['response'].to_i + when APPROVED + "Transaction Approved" + when DECLINED + "Transaction Declined" + else + "Error in transaction data or system error" + end + end + + def post_data(action, parameters = {}) + parameters[:type] = action + parameters[:username] = @options[:login] + parameters[:password] = @options[:password] + parameters.map{|k, v| "#{k}=#{CGI.escape(v.to_s)}"}.join('&') + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/finansbank.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/finansbank.rb new file mode 100644 index 000000000..a81cecd54 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/finansbank.rb @@ -0,0 +1,22 @@ +require File.dirname(__FILE__) + '/cc5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class FinansbankGateway < CC5Gateway + self.live_url = self.test_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US', 'TR'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master] + + # The homepage URL of the gateway + self.homepage_url = 'https://www.fbwebpos.com/' + + # The name of the gateway + self.display_name = 'Finansbank WebPOS' + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/first_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/first_pay.rb new file mode 100644 index 000000000..b356dddb7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/first_pay.rb @@ -0,0 +1,176 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class FirstPayGateway < Gateway + class FirstPayPostData < PostData + # Fields that will be sent even if they are blank + self.required_fields = [ :action, :amount, :trackid ] + end + + # both URLs are IP restricted + self.test_url = 'https://apgcert.first-pay.com/AcqENGIN/SecureCapture' + self.live_url = 'https://acqengin.first-pay.com/AcqENGIN/SecureCapture' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.first-pay.com' + + # The name of the gateway + self.display_name = 'First Pay' + + # all transactions are in cents + self.money_format = :cents + + ACTIONS = { + 'sale' => 1, + 'credit' => 2, + 'void' => 3 + } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, creditcard, options = {}) + post = FirstPayPostData.new + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + + commit('sale', money, post) + end + + def refund(money, reference, options = {}) + requires!(options, :credit_card) + + post = FirstPayPostData.new + add_invoice(post, options) + add_creditcard(post, options[:credit_card]) + add_address(post, options) + add_customer_data(post, options) + add_credit_data(post, reference) + + commit('credit', money, post) + end + + def credit(money, reference, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, reference, options) + end + + def void(money, creditcard, options = {}) + post = FirstPayPostData.new + add_creditcard(post, creditcard) + add_void_data(post, options) + add_invoice(post, options) + add_customer_data(post, options) + + commit('void', money, post) + end + + + private + + def add_customer_data(post, options) + post[:cardip] = options[:ip] + post[:email] = options[:email] + end + + def add_address(post, options) + if billing_address = options[:billing_address] || options[:address] + post[:addr] = billing_address[:address1].to_s + ' ' + billing_address[:address2].to_s + post[:city] = billing_address[:city] + post[:state] = billing_address[:state] + post[:zip] = billing_address[:zip] + post[:country] = billing_address[:country] + end + end + + def add_invoice(post, options) + post[:trackid] = rand(Time.now.to_i) + end + + def add_creditcard(post, creditcard) + post[:member] = creditcard.first_name.to_s + " " + creditcard.last_name.to_s + post[:card] = creditcard.number + post[:exp] = expdate(creditcard) + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def add_credit_data(post, transaction_id) + post[:transid] = transaction_id + end + + def add_void_data(post, options) + post[:transid] = options[:transactionid] + end + + def commit(action, money, post) + response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(action, post, money)) ) + + Response.new(response[:response] == 'CAPTURED', response[:message], response, + :test => test?, + :authorization => response[:authorization], + :avs_result => { :code => response[:avsresponse] }, + :cvv_result => response[:cvvresponse]) + end + + def parse(body) + response = {} + + # check for an error first + if body.include?('!ERROR!') + response[:response] = 'ERROR' + response[:message] = error_message_from(body) + else + # a capture / not captured response will be : delimited + split = body.split(':') + response[:response] = split[0] + + # FirstPay docs are worthless. turns out the transactionid is required for credits + # so we need to store that in authorization, not the actual auth. + if response[:response] == 'CAPTURED' + response[:message] = 'CAPTURED' + response[:authorization] = split[9] # actually the transactionid + response[:auth] = split[1] + response[:avsresponse] = split[3] + response[:cvvresponse] = split[17] + else + # NOT CAPTURED response + response[:message] = split[1] + response[:transactionid] = split[9] + end + end + + return response + end + + def error_message_from(response) + # error messages use this format - '!ERROR! 704-MISSING BASIC DATA TYPE:card, exp, zip, addr, member, amount\n' + response.split("! ")[1].chomp + end + + def post_data(action, post, money) + post[:vid] = @options[:login] + post[:password] = @options[:password] + post[:action] = ACTIONS[action] + post[:amount] = amount(money) + + return post.to_post_data + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/firstdata_e4.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/firstdata_e4.rb new file mode 100644 index 000000000..ee9f6b4b4 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/firstdata_e4.rb @@ -0,0 +1,314 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class FirstdataE4Gateway < Gateway + # TransArmor support requires v11 or lower + self.test_url = "https://api.demo.globalgatewaye4.firstdata.com/transaction/v11" + self.live_url = "https://api.globalgatewaye4.firstdata.com/transaction/v11" + + TRANSACTIONS = { + :sale => "00", + :authorization => "01", + :capture => "32", + :void => "33", + :credit => "34", + :store => "05" + } + + POST_HEADERS = { + "Accepts" => "application/xml", + "Content-Type" => "application/xml" + } + + SUCCESS = "true" + + SENSITIVE_FIELDS = [:verification_str2, :expiry_date, :card_number] + + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover] + self.supported_countries = ["CA", "US"] + self.default_currency = "USD" + self.homepage_url = "http://www.firstdata.com" + self.display_name = "FirstData Global Gateway e4" + + # Create a new FirstdataE4Gateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- The EXACT ID. Also known as the Gateway ID. + # (Found in your administration terminal settings) + # * :password -- The terminal password (not your account password) + def initialize(options = {}) + requires!(options, :login, :password) + @options = options + + super + end + + def authorize(money, credit_card_or_store_authorization, options = {}) + commit(:authorization, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)) + end + + def purchase(money, credit_card_or_store_authorization, options = {}) + commit(:sale, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)) + end + + def capture(money, authorization, options = {}) + commit(:capture, build_capture_or_credit_request(money, authorization, options)) + end + + def void(authorization, options = {}) + commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options)) + end + + def refund(money, authorization, options = {}) + commit(:credit, build_capture_or_credit_request(money, authorization, options)) + end + + # Tokenize a credit card with TransArmor + # + # The TransArmor token and other card data necessary for subsequent + # transactions is stored in the response's +authorization+ attribute. + # The authorization string may be passed to +authorize+ and +purchase+ + # instead of a +ActiveMerchant::Billing::CreditCard+ instance. + # + # TransArmor support must be explicitly activated on your gateway + # account by FirstData. If your authorization string is empty, contact + # FirstData support for account setup assistance. + # + # === Example + # + # # Generate token + # result = gateway.store(credit_card) + # if result.success? + # my_record.update_attributes(:authorization => result.authorization) + # end + # + # # Use token + # result = gateway.purchase(1000, my_record.authorization) + # + # https://firstdata.zendesk.com/entries/21303361-transarmor-tokenization + def store(credit_card, options = {}) + commit(:store, build_store_request(credit_card, options), credit_card) + end + + private + + def build_request(action, body) + xml = Builder::XmlMarkup.new + + xml.instruct! + xml.tag! "Transaction" do + add_credentials(xml) + add_transaction_type(xml, action) + xml << body + end + + xml.target! + end + + def build_sale_or_authorization_request(money, credit_card_or_store_authorization, options) + xml = Builder::XmlMarkup.new + + add_amount(xml, money) + + if credit_card_or_store_authorization.is_a? String + add_credit_card_token(xml, credit_card_or_store_authorization) + else + add_credit_card(xml, credit_card_or_store_authorization) + end + + add_customer_data(xml, options) + add_invoice(xml, options) + + xml.target! + end + + def build_capture_or_credit_request(money, identification, options) + xml = Builder::XmlMarkup.new + + add_identification(xml, identification) + add_amount(xml, money) + add_customer_data(xml, options) + + xml.target! + end + + def build_store_request(credit_card, options) + xml = Builder::XmlMarkup.new + + add_credit_card(xml, credit_card) + add_customer_data(xml, options) + + xml.target! + end + + def add_credentials(xml) + xml.tag! "ExactID", @options[:login] + xml.tag! "Password", @options[:password] + end + + def add_transaction_type(xml, action) + xml.tag! "Transaction_Type", TRANSACTIONS[action] + end + + def add_identification(xml, identification) + authorization_num, transaction_tag, _ = identification.split(";") + + xml.tag! "Authorization_Num", authorization_num + xml.tag! "Transaction_Tag", transaction_tag + end + + def add_amount(xml, money) + xml.tag! "DollarAmount", amount(money) + end + + def add_credit_card(xml, credit_card) + xml.tag! "Card_Number", credit_card.number + xml.tag! "Expiry_Date", expdate(credit_card) + xml.tag! "CardHoldersName", credit_card.name + xml.tag! "CardType", credit_card.brand + + if credit_card.verification_value? + xml.tag! "CVD_Presence_Ind", "1" + xml.tag! "VerificationStr2", credit_card.verification_value + end + end + + def add_credit_card_token(xml, store_authorization) + params = store_authorization.split(";") + credit_card = CreditCard.new( + :brand => params[1], + :first_name => params[2], + :last_name => params[3], + :month => params[4], + :year => params[5]) + + xml.tag! "TransarmorToken", params[0] + xml.tag! "Expiry_Date", expdate(credit_card) + xml.tag! "CardHoldersName", credit_card.name + xml.tag! "CardType", credit_card.brand + end + + def add_customer_data(xml, options) + xml.tag! "Customer_Ref", options[:customer] if options[:customer] + xml.tag! "Client_IP", options[:ip] if options[:ip] + xml.tag! "Client_Email", options[:email] if options[:email] + end + + def add_address(xml, options) + if address = (options[:billing_address] || options[:address]) + xml.tag! "ZipCode", address[:zip] + end + end + + def add_invoice(xml, options) + xml.tag! "Reference_No", options[:order_id] + xml.tag! "Reference_3", options[:description] if options[:description] + end + + def expdate(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" + end + + def commit(action, request, credit_card = nil) + url = (test? ? self.test_url : self.live_url) + begin + response = parse(ssl_post(url, build_request(action, request), POST_HEADERS)) + rescue ResponseError => e + response = parse_error(e.response) + end + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => response_authorization(action, response, credit_card), + :avs_result => {:code => response[:avs]}, + :cvv_result => response[:cvv2] + ) + end + + def successful?(response) + response[:transaction_approved] == SUCCESS + end + + def response_authorization(action, response, credit_card) + if action == :store + store_authorization_from(response, credit_card) + else + authorization_from(response) + end + end + + def authorization_from(response) + if response[:authorization_num] && response[:transaction_tag] + [ + response[:authorization_num], + response[:transaction_tag], + (response[:dollar_amount].to_f * 100).to_i + ].join(";") + else + "" + end + end + + def store_authorization_from(response, credit_card) + if response[:transarmor_token].present? + [ + response[:transarmor_token], + credit_card.brand, + credit_card.first_name, + credit_card.last_name, + credit_card.month, + credit_card.year + ].map { |value| value.to_s.gsub(/;/, "") }.join(";") + else + raise StandardError, "TransArmor support is not enabled on your #{display_name} account" + end + end + + def money_from_authorization(auth) + _, _, amount = auth.split(/;/, 3) + amount.to_i # return the # of cents, no need to divide + end + + def message_from(response) + if(response[:faultcode] && response[:faultstring]) + response[:faultstring] + elsif(response[:error_number] && response[:error_number] != "0") + response[:error_description] + else + result = (response[:exact_message] || "") + result << " - #{response[:bank_message]}" if response[:bank_message].present? + result + end + end + + def parse_error(error) + { + :transaction_approved => "false", + :error_number => error.code, + :error_description => error.body + } + end + + def parse(xml) + response = {} + xml = REXML::Document.new(xml) + + if root = REXML::XPath.first(xml, "//TransactionResult") + parse_elements(response, root) + end + + response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) } + end + + def parse_elements(response, root) + root.elements.to_a.each do |node| + response[node.name.gsub(/EXact/, "Exact").underscore.to_sym] = (node.text || "").strip + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/garanti.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/garanti.rb new file mode 100644 index 000000000..c6de668a1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/garanti.rb @@ -0,0 +1,257 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class GarantiGateway < Gateway + self.live_url = self.test_url = 'https://sanalposprov.garanti.com.tr/VPServlet' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US','TR'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + # The homepage URL of the gateway + self.homepage_url = 'https://sanalposweb.garanti.com.tr' + + # The name of the gateway + self.display_name = 'Garanti Sanal POS' + + self.default_currency = 'TRL' + + self.money_format = :cents + + CURRENCY_CODES = { + 'YTL' => 949, + 'TRL' => 949, + 'TL' => 949, + 'USD' => 840, + 'EUR' => 978, + 'GBP' => 826, + 'JPY' => 392 + } + + + def initialize(options = {}) + requires!(options, :login, :password, :terminal_id, :merchant_id) + super + end + + def purchase(money, credit_card, options = {}) + options = options.merge(:gvp_order_type => "sales") + commit(money, build_sale_request(money, credit_card, options)) + end + + def authorize(money, credit_card, options = {}) + options = options.merge(:gvp_order_type => "preauth") + commit(money, build_authorize_request(money, credit_card, options)) + end + + def capture(money, ref_id, options = {}) + options = options.merge(:gvp_order_type => "postauth") + commit(money, build_capture_request(money, ref_id, options)) + end + + private + + def security_data + rjusted_terminal_id = @options[:terminal_id].to_s.rjust(9, "0") + Digest::SHA1.hexdigest(@options[:password].to_s + rjusted_terminal_id).upcase + end + + def generate_hash_data(order_id, terminal_id, credit_card_number, amount, security_data) + data = [order_id, terminal_id, credit_card_number, amount, security_data].join + Digest::SHA1.hexdigest(data).upcase + end + + def build_xml_request(money, credit_card, options, &block) + card_number = credit_card.respond_to?(:number) ? credit_card.number : '' + hash_data = generate_hash_data(format_order_id(options[:order_id]), @options[:terminal_id], card_number, amount(money), security_data) + + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8" + + xml.tag! 'GVPSRequest' do + xml.tag! 'Mode', test? ? 'TEST' : 'PROD' + xml.tag! 'Version', 'V0.01' + xml.tag! 'Terminal' do + xml.tag! 'ProvUserID', 'PROVAUT' + xml.tag! 'HashData', hash_data + xml.tag! 'UserID', @options[:login] + xml.tag! 'ID', @options[:terminal_id] + xml.tag! 'MerchantID', @options[:merchant_id] + end + + if block_given? + yield xml + else + xml.target! + end + end + end + + def build_sale_request(money, credit_card, options) + build_xml_request(money, credit_card, options) do |xml| + add_customer_data(xml, options) + add_order_data(xml, options) do |xml| + add_addresses(xml, options) + end + add_credit_card(xml, credit_card) + add_transaction_data(xml, money, options) + + xml.target! + end + end + + def build_authorize_request(money, credit_card, options) + build_xml_request(money, credit_card, options) do |xml| + add_customer_data(xml, options) + add_order_data(xml, options) do |xml| + add_addresses(xml, options) + end + add_credit_card(xml, credit_card) + add_transaction_data(xml, money, options) + + xml.target! + end + end + + def build_capture_request(money, ref_id, options) + options = options.merge(:order_id => ref_id) + build_xml_request(money, ref_id, options) do |xml| + add_customer_data(xml, options) + add_order_data(xml, options) + add_transaction_data(xml, money, options) + + xml.target! + end + end + + def add_customer_data(xml, options) + xml.tag! 'Customer' do + xml.tag! 'IPAddress', options[:ip] || '1.1.1.1' + xml.tag! 'EmailAddress', options[:email] + end + end + + def add_order_data(xml, options, &block) + xml.tag! 'Order' do + xml.tag! 'OrderID', format_order_id(options[:order_id]) + xml.tag! 'GroupID' + + if block_given? + yield xml + end + end + end + + def add_credit_card(xml, credit_card) + xml.tag! 'Card' do + xml.tag! 'Number', credit_card.number + xml.tag! 'ExpireDate', [format_exp(credit_card.month), format_exp(credit_card.year)].join + xml.tag! 'CVV2', credit_card.verification_value + end + end + + def format_exp(value) + format(value, :two_digits) + end + + # OrderId field must be A-Za-z0-9_ format and max 36 char + def format_order_id(order_id) + order_id.to_s.gsub(/[^A-Za-z0-9_]/, '')[0...36] + end + + def add_addresses(xml, options) + xml.tag! 'AddressList' do + if billing_address = options[:billing_address] || options[:address] + xml.tag! 'Address' do + xml.tag! 'Type', 'B' + add_address(xml, billing_address) + end + end + + if options[:shipping_address] + xml.tag! 'Address' do + xml.tag! 'Type', 'S' + add_address(xml, options[:shipping_address]) + end + end + end + end + + def add_address(xml, address) + xml.tag! 'Name', normalize(address[:name]) + address_text = address[:address1] + address_text << " #{ address[:address2]}" if address[:address2] + xml.tag! 'Text', normalize(address_text) + xml.tag! 'City', normalize(address[:city]) + xml.tag! 'District', normalize(address[:state]) + xml.tag! 'PostalCode', address[:zip] + xml.tag! 'Country', normalize(address[:country]) + xml.tag! 'Company', normalize(address[:company]) + xml.tag! 'PhoneNumber', address[:phone].to_s.gsub(/[^0-9]/, '') if address[:phone] + end + + def normalize(text) + return unless text + + if ActiveSupport::Inflector.method(:transliterate).arity == -2 + ActiveSupport::Inflector.transliterate(text,'') + elsif RUBY_VERSION >= '1.9' + text.gsub(/[^\x00-\x7F]+/, '') + else + ActiveSupport::Inflector.transliterate(text).to_s + end + end + + def add_transaction_data(xml, money, options) + xml.tag! 'Transaction' do + xml.tag! 'Type', options[:gvp_order_type] + xml.tag! 'Amount', amount(money) + xml.tag! 'CurrencyCode', currency_code(options[:currency] || currency(money)) + xml.tag! 'CardholderPresentCode', 0 + end + end + + def currency_code(currency) + CURRENCY_CODES[currency] || CURRENCY_CODES[default_currency] + end + + def commit(money,request) + raw_response = ssl_post(self.live_url, "data=" + request) + response = parse(raw_response) + + success = success?(response) + + Response.new(success, + success ? 'Approved' : "Declined (Reason: #{response[:reason_code]} - #{response[:error_msg]} - #{response[:sys_err_msg]})", + response, + :test => test?, + :authorization => response[:order_id]) + end + + def parse(body) + xml = REXML::Document.new(body) + + response = {} + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each{|element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def success?(response) + response[:message] == "Approved" + end + + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/hdfc.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/hdfc.rb new file mode 100644 index 000000000..be9738577 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/hdfc.rb @@ -0,0 +1,207 @@ +require "nokogiri" + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class HdfcGateway < Gateway + self.display_name = "HDFC" + self.homepage_url = "http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i" + + self.test_url = "https://securepgtest.fssnet.co.in/pgway/servlet/" + self.live_url = "https://securepg.fssnet.co.in/pgway/servlet/" + + self.supported_countries = ["IN"] + self.default_currency = "INR" + self.money_format = :dollars + self.supported_cardtypes = [:visa, :master, :discover, :diners_club] + + def initialize(options={}) + requires!(options, :login, :password) + super + end + + def purchase(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit("purchase", post) + end + + def authorize(amount, payment_method, options={}) + post = {} + add_invoice(post, amount, options) + add_payment_method(post, payment_method) + add_customer_data(post, options) + + commit("authorize", post) + end + + def capture(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit("capture", post) + end + + def refund(amount, authorization, options={}) + post = {} + add_invoice(post, amount, options) + add_reference(post, authorization) + add_customer_data(post, options) + + commit("refund", post) + end + + private + + CURRENCY_CODES = Hash.new{|h,k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}")} + CURRENCY_CODES["AED"] = "784" + CURRENCY_CODES["AUD"] = "036" + CURRENCY_CODES["CAD"] = "124" + CURRENCY_CODES["EUR"] = "978" + CURRENCY_CODES["GBP"] = "826" + CURRENCY_CODES["INR"] = "356" + CURRENCY_CODES["OMR"] = "512" + CURRENCY_CODES["QAR"] = "634" + CURRENCY_CODES["SGD"] = "702" + CURRENCY_CODES["USD"] = "840" + + def add_invoice(post, amount, options) + post[:amt] = amount(amount) + post[:currencycode] = CURRENCY_CODES[options[:currency] || currency(amount)] + post[:trackid] = escape(options[:order_id], 40) if options[:order_id] + post[:udf1] = escape(options[:description]) if options[:description] + post[:eci] = options[:eci] if options[:eci] + end + + def add_customer_data(post, options) + post[:udf2] = escape(options[:email]) if options[:email] + if address = (options[:billing_address] || options[:address]) + post[:udf3] = escape(address[:phone]) if address[:phone] + post[:udf4] = escape(< "1", + "refund" => "2", + "authorize" => "4", + "capture" => "5", + } + + def commit(action, post) + post[:id] = @options[:login] + post[:password] = @options[:password] + post[:action] = ACTIONS[action] if ACTIONS[action] + + raw = parse(ssl_post(url(action), build_request(post))) + + succeeded = success_from(raw[:result]) + Response.new( + succeeded, + message_from(succeeded, raw), + raw, + :authorization => authorization_from(post, raw), + :test => test? + ) + end + + def build_request(post) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + post.each do |field, value| + xml.tag!(field, value) + end + xml.target! + end + + def url(action) + endpoint = "TranPortalXMLServlet" + (test? ? test_url : live_url) + endpoint + end + + def success_from(result) + case result + when "CAPTURED", "APPROVED", "NOT ENROLLED", "ENROLLED" + true + else + false + end + end + + def message_from(succeeded, response) + if succeeded + "Succeeded" + else + (response[:error_text] || response[:result] || "Unable to read error message").split("-").last + end + end + + def authorization_from(request, response) + [response[:tranid], request[:member]].join("|") + end + + def split_authorization(authorization) + tranid, member = authorization.split("|") + [tranid, member] + end + + def escape(string, max_length=250) + return "" unless string + if max_length + string = string[0...max_length] + end + string.gsub(/[^A-Za-z0-9 \-_@\.\n]/, '') + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_base.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_base.rb new file mode 100644 index 000000000..360f0ad5e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_base.rb @@ -0,0 +1,249 @@ +require File.dirname(__FILE__) + '/ideal_response' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # Implementation contains some simplifications + # - does not support multiple subID per merchant + # - language is fixed to 'nl' + class IdealBaseGateway < Gateway + class_attribute :server_pem, :pem_password, :default_expiration_period + self.default_expiration_period = 'PT10M' + self.default_currency = 'EUR' + self.pem_password = true + + self.abstract_class = true + + # These constants will never change for most users + AUTHENTICATION_TYPE = 'SHA1_RSA' + LANGUAGE = 'nl' + SUB_ID = '0' + API_VERSION = '1.1.0' + + def initialize(options = {}) + requires!(options, :login, :password, :pem) + + options[:pem_password] = options[:password] + super + end + + # Setup transaction. Get redirect_url from response.service_url + def setup_purchase(money, options = {}) + requires!(options, :issuer_id, :return_url, :order_id, :currency, :description, :entrance_code) + + commit(build_transaction_request(money, options)) + end + + # Check status of transaction and confirm payment + # transaction_id must be a valid transaction_id from a prior setup. + def capture(transaction, options = {}) + options[:transaction_id] = transaction + commit(build_status_request(options)) + end + + # Get list of issuers from response.issuer_list + def issuers + commit(build_directory_request) + end + + private + + def url + (test? ? test_url : live_url) + end + + def token + if @token.nil? + @token = create_fingerprint(@options[:pem]) + end + @token + end + + # + # + # 2001-12-17T09:30:47.0Z + # + # 1003 + # + # + # 000123456 + # 0 + # passkey + # 1 + # 3823ad872eff23 + # https://www.mijnwinkel.nl/betaalafhandeling + # + # + # + # iDEAL-aankoop 21 + # 5999 + # EUR + # PT3M30S + # nl + # Documentensuite + # D67tyx6rw9IhY71 + # + # + def build_transaction_request(money, options) + date_time_stamp = create_time_stamp + message = date_time_stamp + + options[:issuer_id] + + @options[:login] + + SUB_ID + + options[:return_url] + + options[:order_id] + + money.to_s + + (options[:currency] || currency(money)) + + LANGUAGE + + options[:description] + + options[:entrance_code] + token_code = sign_message(@options[:pem], @options[:password], message) + + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct! + xml.tag! 'AcquirerTrxReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do + xml.tag! 'createDateTimeStamp', date_time_stamp + xml.tag! 'Issuer' do + xml.tag! 'issuerID', options[:issuer_id] + end + xml.tag! 'Merchant' do + xml.tag! 'merchantID', @options[:login] + xml.tag! 'subID', SUB_ID + xml.tag! 'authentication', AUTHENTICATION_TYPE + xml.tag! 'token', token + xml.tag! 'tokenCode', token_code + xml.tag! 'merchantReturnURL', options[:return_url] + end + xml.tag! 'Transaction' do + xml.tag! 'purchaseID', options[:order_id] + xml.tag! 'amount', money + xml.tag! 'currency', options[:currency] + xml.tag! 'expirationPeriod', options[:expiration_period] || default_expiration_period + xml.tag! 'language', LANGUAGE + xml.tag! 'description', options[:description] + xml.tag! 'entranceCode', options[:entrance_code] + end + xml.target! + end + end + + # + # + # 2001-12-17T09:30:47.0Z + # + # 000123456 + # 0 + # keyed hash + # 1 + # 3823ad872eff23 + # + # + # 0001023456789112 + # + # + def build_status_request(options) + datetimestamp = create_time_stamp + message = datetimestamp + @options[:login] + SUB_ID + options[:transaction_id] + tokenCode = sign_message(@options[:pem], @options[:password], message) + + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct! + xml.tag! 'AcquirerStatusReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do + xml.tag! 'createDateTimeStamp', datetimestamp + xml.tag! 'Merchant' do + xml.tag! 'merchantID', @options[:login] + xml.tag! 'subID', SUB_ID + xml.tag! 'authentication' , AUTHENTICATION_TYPE + xml.tag! 'token', token + xml.tag! 'tokenCode', tokenCode + end + xml.tag! 'Transaction' do + xml.tag! 'transactionID', options[:transaction_id] + end + end + xml.target! + end + + # + # + # 2001-12-17T09:30:47.0Z + # + # 000000001 + # 0 + # 1 + # hashkey + # WajqV1a3nDen0be2r196g9FGFF= + # + # + def build_directory_request + datetimestamp = create_time_stamp + message = datetimestamp + @options[:login] + SUB_ID + tokenCode = sign_message(@options[:pem], @options[:password], message) + + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct! + xml.tag! 'DirectoryReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do + xml.tag! 'createDateTimeStamp', datetimestamp + xml.tag! 'Merchant' do + xml.tag! 'merchantID', @options[:login] + xml.tag! 'subID', SUB_ID + xml.tag! 'authentication', AUTHENTICATION_TYPE + xml.tag! 'token', token + xml.tag! 'tokenCode', tokenCode + end + end + xml.target! + end + + def commit(request) + raw_response = ssl_post(url, request) + response = Hash.from_xml(raw_response.to_s) + response_type = response.keys[0] + + case response_type + when 'AcquirerTrxRes', 'DirectoryRes' + success = true + when 'ErrorRes' + success = false + when 'AcquirerStatusRes' + raise SecurityError, "Message verification failed.", caller unless status_response_verified?(response) + success = (response['AcquirerStatusRes']['Transaction']['status'] == 'Success') + else + raise ArgumentError, "Unknown response type.", caller + end + + return IdealResponse.new(success, response.keys[0], response, :test => test?) + end + + def create_fingerprint(cert_file) + cert_data = OpenSSL::X509::Certificate.new(cert_file).to_s + cert_data = cert_data.sub(/-----BEGIN CERTIFICATE-----/, '') + cert_data = cert_data.sub(/-----END CERTIFICATE-----/, '') + fingerprint = Base64.decode64(cert_data) + fingerprint = Digest::SHA1.hexdigest(fingerprint) + return fingerprint.upcase + end + + def sign_message(private_key_data, password, data) + private_key = OpenSSL::PKey::RSA.new(private_key_data, password) + signature = private_key.sign(OpenSSL::Digest::SHA1.new, data.gsub('\s', '')) + return Base64.encode64(signature).gsub(/\n/, '') + end + + def verify_message(cert_file, data, signature) + public_key = OpenSSL::X509::Certificate.new(cert_file).public_key + return public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), data) + end + + def status_response_verified?(response) + transaction = response['AcquirerStatusRes']['Transaction'] + message = response['AcquirerStatusRes']['createDateTimeStamp'] + transaction['transactionID' ] + transaction['status'] + message << transaction['consumerAccountNumber'].to_s + verify_message(server_pem, message, response['AcquirerStatusRes']['Signature']['signatureValue']) + end + + def create_time_stamp + Time.now.gmtime.strftime('%Y-%m-%dT%H:%M:%S.000Z') + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem new file mode 100755 index 000000000..3259cfa33 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICQDCCAakCBELvbPYwDQYJKoZIhvcNAQEEBQAwZzELMAkGA1UEBhMCREUxDzANBgNVBAgTBkhl +c3NlbjESMBAGA1UEBxMJRnJhbmtmdXJ0MQ4wDAYDVQQKEwVpREVBTDEOMAwGA1UECxMFaURFQUwx +EzARBgNVBAMTCmlERUFMIFJhYm8wHhcNMDUwODAyMTI1NDE0WhcNMTUwNzMxMTI1NDE0WjBnMQsw +CQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMRIwEAYDVQQHEwlGcmFua2Z1cnQxDjAMBgNVBAoT +BWlERUFMMQ4wDAYDVQQLEwVpREVBTDETMBEGA1UEAxMKaURFQUwgUmFibzCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEA486iIKVhr8RNjxH+PZ3yTWx/8k2fqDFm8XU8I1Z5esRmPFnXmlgA8cG7 +e9AaBPaLoP7Dc8dRQoUO66KMakzGI/WAVdHIJiiKrz8xOcioIgrzPSqec7aqse3J28UraEHkGESJ +7dAW7Pw/shrmpmkzKsunLt6AkXss4W3JUndZUN0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCGy/FK +Lotp2ZOTtuLMgvDy74eicq/Znv4bLfpglzAPHycRHcHsXuer/lNHyvpKf2gdYe+IFalUW3OJZWIM +jpm4UniJ16RPdgwWVRJEdPr/P7JXMIqD2IEOgujuuTQ7x0VgCf9XrsPsP9ZR5DIPcDDhbrpSE0yF +Do77nwG61xMaGA== +-----END CERTIFICATE----- diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_response.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_response.rb new file mode 100644 index 000000000..e050964ae --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal/ideal_response.rb @@ -0,0 +1,29 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class IdealResponse < Response + + def issuer_list + list = @params.values[0]['Directory']['Issuer'] + case list + when Hash + return [list] + when Array + return list + end + end + + def service_url + @params.values[0]['Issuer']['issuerAuthenticationURL'] + end + + def transaction + @params.values[0]['Transaction'] + end + + def error + @params.values[0]['Error'] + end + + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal_rabobank.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal_rabobank.rb new file mode 100644 index 000000000..1edf7bfbc --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ideal_rabobank.rb @@ -0,0 +1,66 @@ +require File.dirname(__FILE__) + '/ideal/ideal_base' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # First, make sure you have everything setup correctly and all of your dependencies in place with: + # + # require 'rubygems' + # require 'active_merchant' + # + # ActiveMerchant expects the amounts to be given as an Integer in cents. In this case, 10 EUR becomes 1000. + # + # Create certificates for authentication: + # + # The PEM file expected should contain both the certificate and the generated PEM file. + # Some sample shell commands to generate the certificates: + # + # openssl genrsa -aes128 -out priv.pem -passout pass:[YOUR PASSWORD] 1024 + # openssl req -x509 -new -key priv.pem -passin pass:[YOUR PASSWORD] -days 3000 -out cert.cer + # cat cert.cer priv.pem > ideal.pem + # + # Following the steps above, upload cert.cer to the ideal web interface and pass the path of ideal.pem to the :pem option. + # + # Configure the gateway using your iDEAL bank account info and security settings: + # + # Create gateway: + # gateway = ActiveMerchant::Billing::IdealRabobankGateway.new( + # :login => '123456789', # 9 digit merchant number + # :pem => File.read(Rails.root + 'config/ideal.pem'), + # :password => 'password' # password for the PEM key + # ) + # + # Get list of issuers to fill selection list on your payment form: + # response = gateway.issuers + # list = response.issuer_list + # + # Request transaction: + # + # options = { + # :issuer_id => '0001', + # :expiration_period => 'PT10M', + # :return_url => 'http://www.return.url', + # :order_id => '1234567890123456', + # :currency => 'EUR', + # :description => 'Een omschrijving', + # :entrance_code => '1234' + # } + # + # response = gateway.setup_purchase(amount, options) + # transaction_id = response.transaction['transactionID'] + # redirect_url = response.service_url + # + # Mandatory status request will confirm transaction: + # response = gateway.capture(transaction_id) + # + # Implementation contains some simplifications + # - does not support multiple subID per merchant + # - language is fixed to 'nl' + class IdealRabobankGateway < IdealBaseGateway + class_attribute :test_url, :live_url + + self.test_url = 'https://idealtest.rabobank.nl/ideal/iDeal' + self.live_url = 'https://ideal.rabobank.nl/ideal/iDeal' + self.server_pem = File.read(File.dirname(__FILE__) + '/ideal/ideal_rabobank.pem') + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/inspire.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/inspire.rb new file mode 100644 index 000000000..a3646b7ff --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/inspire.rb @@ -0,0 +1,221 @@ +require File.join(File.dirname(__FILE__), '..', 'check.rb') +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class InspireGateway < Gateway + self.live_url = self.test_url = 'https://secure.inspiregateway.net/api/transact.php' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express] + self.homepage_url = 'http://www.inspiregateway.com' + self.display_name = 'Inspire Commerce' + + # Creates a new InspireGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- The Inspire Username. + # * :password -- The Inspire Passowrd. + # See the Inspire Integration Guide for details. (default: +false+) + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Pass :store => true in the options to store the + # payment info at Inspire Gateway and get a generated + # customer_vault_id in the response. + # Pass :store => some_number_or_string to specify the + # customer_vault_id InspireGateway should use (make sure it's + # unique). + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_payment_source(post, creditcard,options) + add_address(post, creditcard, options) + add_customer_data(post, options) + + commit('auth', money, post) + end + + def purchase(money, payment_source, options = {}) + post = {} + add_invoice(post, options) + add_payment_source(post, payment_source, options) + add_address(post, payment_source, options) + add_customer_data(post, options) + + commit('sale', money, post) + end + + def capture(money, authorization, options = {}) + post ={} + post[:transactionid] = authorization + commit('capture', money, post) + end + + def void(authorization, options = {}) + post ={} + post[:transactionid] = authorization + commit('void', nil, post) + end + + # Update the values (such as CC expiration) stored at + # InspireGateway. The CC number must be supplied in the + # CreditCard object. + def update(vault_id, creditcard, options = {}) + post = {} + post[:customer_vault] = "update_customer" + add_customer_vault_id(post, vault_id) + add_creditcard(post, creditcard, options) + add_address(post, creditcard, options) + add_customer_data(post, options) + + commit(nil, nil, post) + end + + def delete(vault_id) + post = {} + post[:customer_vault] = "delete_customer" + add_customer_vault_id(post, vault_id) + commit(nil, nil, post) + end + + # To match the other stored-value gateways, like TrustCommerce, + # store and unstore need to be defined + def store(creditcard, options = {}) + billing_id = options.delete(:billing_id).to_s || true + authorize(100, creditcard, options.merge(:store => billing_id)) + end + + alias_method :unstore, :delete + + private + def add_customer_data(post, options) + if options.has_key? :email + post[:email] = options[:email] + end + + if options.has_key? :ip + post[:ipaddress] = options[:ip] + end + end + + def add_address(post, creditcard, options) + if address = options[:billing_address] || options[:address] + post[:address1] = address[:address1].to_s + post[:address2] = address[:address2].to_s unless address[:address2].blank? + 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 + end + + def add_invoice(post, options) + post[:orderid] = options[:order_id].to_s.gsub(/[^\w.]/, '') + post[:orderdescription] = options[:description] + end + + def add_payment_source(params, source, options={}) + case determine_funding_source(source) + when :vault then add_customer_vault_id(params, source) + when :credit_card then add_creditcard(params, source, options) + when :check then add_check(params, source) + end + end + + def add_customer_vault_id(params,vault_id) + params[:customer_vault_id] = vault_id + end + + def add_creditcard(post, creditcard,options) + if options[:store] + post[:customer_vault] = "add_customer" + post[:customer_vault_id] = options[:store] unless options[:store] == true + end + post[:ccnumber] = creditcard.number + post[:cvv] = creditcard.verification_value if creditcard.verification_value? + post[:ccexp] = expdate(creditcard) + post[:firstname] = creditcard.first_name + post[:lastname] = creditcard.last_name + end + + def add_check(post, check) + post[:payment] = 'check' # Set transaction to ACH + post[:checkname] = check.name # The name on the customer's Checking Account + post[:checkaba] = check.routing_number # The customer's bank routing number + post[:checkaccount] = check.account_number # The customer's account number + post[:account_holder_type] = check.account_holder_type # The customer's type of ACH account + post[:account_type] = check.account_type # The customer's type of ACH account + end + + def parse(body) + results = {} + body.split(/&/).each do |pair| + key,val = pair.split(/=/) + results[key] = val + end + + results + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) if money + + response = parse( ssl_post(self.live_url, post_data(action,parameters)) ) + + Response.new(response["response"] == "1", message_from(response), response, + :authorization => response["transactionid"], + :test => test?, + :cvv_result => response["cvvresponse"], + :avs_result => { :code => response["avsresponse"] } + ) + + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}#{year[-2..-1]}" + end + + + def message_from(response) + case response["responsetext"] + when "SUCCESS","Approved" + "This transaction has been approved" + when "DECLINE" + "This transaction has been declined" + else + response["responsetext"] + end + end + + def post_data(action, parameters = {}) + post = {} + post[:username] = @options[:login] + post[:password] = @options[:password] + post[:type] = action if action + + request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&") + request + end + + def determine_funding_source(source) + case + when source.is_a?(String) then :vault + when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card + when card_brand(source) == 'check' then :check + else raise ArgumentError, "Unsupported funding source provided" + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/instapay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/instapay.rb new file mode 100755 index 000000000..ee4ddc550 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/instapay.rb @@ -0,0 +1,163 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class InstapayGateway < Gateway + self.live_url = 'https://trans.instapaygateway.com/cgi-bin/process.cgi' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + self.money_format = :dollars + self.default_currency = 'USD' + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.instapayllc.com' + + # The name of the gateway + self.display_name = 'InstaPay' + + SUCCESS = "Accepted" + SUCCESS_MESSAGE = "The transaction has been approved" + + def initialize(options = {}) + requires!(options, :login) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + post[:authonly] = 1 + add_amount(post, money) + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + + commit('ns_quicksale_cc', post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + + commit('ns_quicksale_cc', post) + end + + def capture(money, authorization, options = {}) + post = {} + add_amount(post, money) + add_reference(post, authorization) + commit('ns_quicksale_cc', post) + end + + private + + def add_amount(post, money) + post[:amount] = amount(money) + end + + def add_reference(post, reference) + post[:postonly] = reference + end + + def add_customer_data(post, options) + post[:ci_email] = options[:email] + post["ci_IP Address"] = options[:ip] + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:ci_billaddr1] = address[:address1] + post[:ci_billaddr2] = address[:address2] + post[:ci_billcity] = address[:city] + post[:ci_billstate] = address[:state] + post[:ci_billzip] = address[:zip] + post[:ci_billcountry] = address[:country] + post[:ci_phone] = address[:phone] + end + + if address = options[:shipping_address] + post[:ci_shipaddr1] = address[:address1] + post[:ci_shipaddr2] = address[:address2] + post[:ci_shipcity] = address[:city] + post[:ci_shipstate] = address[:state] + post[:ci_shipzip] = address[:zip] + post[:ci_shipcountry] = address[:country] + end + end + + def add_invoice(post, options) + post[:merchantordernumber] = options[:order_id] + post[:ci_memo] = options[:description] + post[:pocustomerrefid] = options[:invoice] + end + + def add_creditcard(post, creditcard) + post[:ccnum] = creditcard.number + post[:expmon] = format(creditcard.month, :two_digits) + post[:cvv2] = creditcard.verification_value if creditcard.verification_value? + post[:expyear] = creditcard.year + post[:ccname] = creditcard.name + end + + def parse(body) + results = {} + fields = body.split("\r\n") + + response = fields[1].split('=') + response_data = response[1].split(':') + + if response[0] == SUCCESS + results[:success] = true + results[:message] = SUCCESS_MESSAGE + results[:transaction_type] = response_data[0] + results[:authorization_code] = response_data[1] + results[:reference_number] = response_data[2] + results[:batch_number] = response_data[3] + results[:transaction_id] = response_data[4] + results[:avs_result] = response_data[5] + results[:authorize_net] = response_data[6] + results[:cvv_result] = response_data[7] + else + results[:success] = false + results[:result] = response_data[0] + results[:response_code] = response_data[1] + results[:message] = response_data[2] + end + + fields[1..-1].each do |pair| + key, value = pair.split('=') + results[key] = value + end + results + end + + def commit(action, parameters) + data = ssl_post self.live_url, post_data(action, parameters) + response = parse(data) + + Response.new(response[:success] , response[:message], response, + :authorization => response[:transaction_id], + :avs_result => { :code => response[:avs_result] }, + :cvv_result => response[:cvv_result] + ) + end + + def post_data(action, parameters = {}) + post = {} + post[:acctid] = @options[:login] + if(@options[:password]) + post[:merchantpin] = @options[:password] + end + post[:action] = action + request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + request + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/iridium.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/iridium.rb new file mode 100644 index 000000000..f0156b3d5 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/iridium.rb @@ -0,0 +1,262 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # For more information on the Iridium Gateway please download the + # documentation from their Merchant Management System. + # + # The login and password are not the username and password you use to + # login to the Iridium Merchant Management System. Instead, you will + # use the API username and password you were issued separately. + class IridiumGateway < Gateway + self.live_url = self.test_url = 'https://gw1.iridiumcorp.net/' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['GB', 'ES'] + self.default_currency = 'EUR' + self.money_format = :cents + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :solo, :diners_club] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.iridiumcorp.co.uk/' + + # The name of the gateway + self.display_name = 'Iridium' + + CURRENCY_CODES = { + "AUD" => '036', + "CAD" => '124', + "EUR" => '978', + "GBP" => '826', + "MXN" => '484', + "NZD" => '554', + "USD" => '840', + } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, payment_source, options = {}) + setup_address_hash(options) + + if payment_source.respond_to?(:number) + commit(build_purchase_request('PREAUTH', money, payment_source, options), options) + else + commit(build_reference_request('PREAUTH', money, payment_source, options), options) + end + end + + def purchase(money, payment_source, options = {}) + setup_address_hash(options) + + if payment_source.respond_to?(:number) + commit(build_purchase_request('SALE', money, payment_source, options), options) + else + commit(build_reference_request('SALE', money, payment_source, options), options) + end + end + + def capture(money, authorization, options = {}) + commit(build_reference_request('COLLECTION', money, authorization, options), options) + end + + def credit(money, authorization, options={}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def refund(money, authorization, options={}) + commit(build_reference_request('REFUND', money, authorization, options), options) + end + + def void(authorization, options={}) + commit(build_reference_request('VOID', nil, authorization, options), options) + end + + private + + def build_purchase_request(type, money, creditcard, options) + options.merge!(:action => 'CardDetailsTransaction') + build_request(options) do |xml| + add_purchase_data(xml, type, money, options) + add_creditcard(xml, creditcard) + add_customerdetails(xml, creditcard, options[:billing_address], options) + end + end + + def build_reference_request(type, money, authorization, options) + options.merge!(:action => 'CrossReferenceTransaction') + order_id, cross_reference, auth_id = authorization.split(";") + build_request(options) do |xml| + if money + details = {'CurrencyCode' => currency_code(options[:currency] || default_currency), 'Amount' => amount(money)} + else + details = {'CurrencyCode' => currency_code(default_currency), 'Amount' => '0'} + end + xml.tag! 'TransactionDetails', details do + xml.tag! 'MessageDetails', {'TransactionType' => type, 'CrossReference' => cross_reference} + xml.tag! 'OrderID', (options[:order_id] || order_id) + end + end + end + + def build_request(options) + requires!(options, :action) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.tag! 'soap:Envelope', { 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do + xml.tag! 'soap:Body' do + xml.tag! options[:action], {'xmlns' => "https://www.thepaymentgateway.net/"} do + xml.tag! 'PaymentMessage' do + add_merchant_data(xml, options) + yield(xml) + end + end + end + end + xml.target! + end + + def setup_address_hash(options) + options[:billing_address] = options[:billing_address] || options[:address] || {} + options[:shipping_address] = options[:shipping_address] || {} + end + + def add_purchase_data(xml, type, money, options) + requires!(options, :order_id) + xml.tag! 'TransactionDetails', {'Amount' => amount(money), 'CurrencyCode' => currency_code(options[:currency] || currency(money))} do + xml.tag! 'MessageDetails', {'TransactionType' => type} + xml.tag! 'OrderID', options[:order_id] + xml.tag! 'TransactionControl' do + xml.tag! 'ThreeDSecureOverridePolicy', 'FALSE' + xml.tag! 'EchoAVSCheckResult', 'TRUE' + xml.tag! 'EchoCV2CheckResult', 'TRUE' + end + end + end + + def add_customerdetails(xml, creditcard, address, options, shipTo = false) + xml.tag! 'CustomerDetails' do + if address + unless address[:country].blank? + country_code = Country.find(address[:country]).code(:numeric) + end + xml.tag! 'BillingAddress' do + xml.tag! 'Address1', address[:address1] + xml.tag! 'Address2', address[:address2] + xml.tag! 'City', address[:city] + xml.tag! 'State', address[:state] + xml.tag! 'PostCode', address[:zip] + xml.tag! 'CountryCode', country_code if country_code + end + xml.tag! 'PhoneNumber', address[:phone] + end + + xml.tag! 'EmailAddress', options[:email] + xml.tag! 'CustomerIPAddress', options[:ip] || "127.0.0.1" + end + end + + def add_creditcard(xml, creditcard) + xml.tag! 'CardDetails' do + xml.tag! 'CardName', creditcard.name + xml.tag! 'CV2', creditcard.verification_value if creditcard.verification_value + xml.tag! 'CardNumber', creditcard.number + xml.tag! 'ExpiryDate', { 'Month' => creditcard.month.to_s.rjust(2, "0"), 'Year' => creditcard.year.to_s[/\d\d$/] } + end + end + + def add_merchant_data(xml, options) + xml.tag! 'MerchantAuthentication', {"MerchantID" => @options[:login], "Password" => @options[:password]} + end + + def commit(request, options) + requires!(options, :action) + response = parse(ssl_post(test? ? self.test_url : self.live_url, request, + {"SOAPAction" => "https://www.thepaymentgateway.net/#{options[:action]}", + "Content-Type" => "text/xml; charset=utf-8" })) + + success = response[:transaction_result][:status_code] == "0" + message = response[:transaction_result][:message] + authorization = success ? [ options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code] ].compact.join(";") : nil + + Response.new(success, message, response, + :test => test?, + :authorization => authorization) + end + + def parse(xml) + reply = {} + xml = REXML::Document.new(xml) + if (root = REXML::XPath.first(xml, "//CardDetailsTransactionResponse")) or + (root = REXML::XPath.first(xml, "//CrossReferenceTransactionResponse")) + root.elements.to_a.each do |node| + case node.name + when 'Message' + reply[:message] = reply(node.text) + else + parse_element(reply, node) + end + end + elsif root = REXML::XPath.first(xml, "//soap:Fault") + parse_element(reply, root) + reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}" + end + reply + end + + def parse_element(reply, node) + case node.name + when "CrossReferenceTransactionResult" + reply[:transaction_result] = {} + node.attributes.each do |a,b| + reply[:transaction_result][a.underscore.to_sym] = b + end + node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements? + + when "CardDetailsTransactionResult" + reply[:transaction_result] = {} + node.attributes.each do |a,b| + reply[:transaction_result][a.underscore.to_sym] = b + end + node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements? + + when "TransactionOutputData" + reply[:transaction_output_data] = {} + node.attributes.each{|a,b| reply[:transaction_output_data][a.underscore.to_sym] = b } + node.elements.each{|e| parse_element(reply[:transaction_output_data], e) } if node.has_elements? + when "CustomVariables" + reply[:custom_variables] = {} + node.attributes.each{|a,b| reply[:custom_variables][a.underscore.to_sym] = b } + node.elements.each{|e| parse_element(reply[:custom_variables], e) } if node.has_elements? + when "GatewayEntryPoints" + reply[:gateway_entry_points] = {} + node.attributes.each{|a,b| reply[:gateway_entry_points][a.underscore.to_sym] = b } + node.elements.each{|e| parse_element(reply[:gateway_entry_points], e) } if node.has_elements? + else + k = node.name.underscore.to_sym + if node.has_elements? + reply[k] = {} + node.elements.each{|e| parse_element(reply[k], e) } + else + if node.has_attributes? + reply[k] = {} + node.attributes.each{|a,b| reply[k][a.underscore.to_sym] = b } + else + reply[k] = node.text + end + end + end + reply + end + + def currency_code(currency) + CURRENCY_CODES[currency] + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/itransact.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/itransact.rb new file mode 100644 index 000000000..0d96f7556 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/itransact.rb @@ -0,0 +1,448 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # iTransact, Inc. is an authorized reseller of the PaymentClearing gateway. If your merchant service provider uses PaymentClearing.com to process payments, you can use this module. + # + # + # Please note, the username and API Access Key are not what you use to log into the Merchant Control Panel. + # + # ==== How to get your GatewayID and API Access Key + # + # 1. If you don't already have a Gateway Account, go to http://www.itransact.com/merchant/test.html to sign up. + # 2. Go to http://support.paymentclearing.com and login or register, if necessary. + # 3. Click on "Submit a Ticket." + # 4. Select "Merchant Support" as the department and click "Next" + # 5. Enter *both* your company name and GatewayID. Put "API Access Key" in the subject. In the body, you can request a username, but it may already be in use. + # + # ==== Initialization + # + # Once you have the username, API Access Key, and your GatewayId, you're ready + # to begin. You initialize the Gateway like so: + # + # gateway = ActiveMerchant::Billing::ItransactGateway.new( + # :login => "#{THE_USERNAME}", + # :password => "#{THE_API_ACCESS_KEY}", + # :gateway_id => "#{THE_GATEWAY_ID}" + # ) + # + # ==== Important Notes + # 1. Recurring is not implemented + # 1. CreditTransactions are not implemented (these are credits not related to a previously run transaction). + # 1. TransactionStatus is not implemented + # + class ItransactGateway < Gateway + self.live_url = self.test_url = 'https://secure.paymentclearing.com/cgi-bin/rc/xmltrans2.cgi' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.itransact.com/' + + # The name of the gateway + self.display_name = 'iTransact' + + # + # Creates a new instance of the iTransact Gateway. + # + # ==== Parameters + # * options - A Hash of options + # + # ==== Options Hash + # * :login - A String containing your PaymentClearing assigned API Access Username + # * :password - A String containing your PaymentClearing assigned API Access Key + # * :gateway_id - A String containing your PaymentClearing assigned GatewayID + # * :test_mode - true or false. Run *all* transactions with the 'TestMode' element set to 'TRUE'. + # + def initialize(options = {}) + requires!(options, :login, :password, :gateway_id) + super + end + + # Performs an authorize transaction. In PaymentClearing's documentation + # this is known as a "PreAuth" transaction. + # + # ==== Parameters + # * money - The amount to be captured. Should be an Integer amount in cents. + # * creditcard - The CreditCard details for the transaction + # * options - A Hash of options + # + # ==== Options Hash + # The standard options apply here (:order_id, :ip, :customer, :invoice, :merchant, :description, :email, :currency, :address, :billing_address, :shipping_address), as well as: + # * :order_items - An Array of Hash objects with the keys :description, :cost (in cents!), and :quantity. If this is provided, :description and money will be ignored. + # * :vendor_data - An Array of Hash objects with the keys being the name of the VendorData element and value being the value. + # * :send_customer_email - true or false. Runs the transaction with the 'SendCustomerEmail' element set to 'TRUE' or 'FALSE'. + # * :send_merchant_email - true or false. Runs the transaction with the 'SendMerchantEmail' element set to 'TRUE' or 'FALSE'. + # * :email_text - An Array of (up to ten (10)) String objects to be included in emails + # * :test_mode - true or false. Runs the transaction with the 'TestMode' element set to 'TRUE' or 'FALSE'. + # + # ==== Examples + # response = gateway.authorize(1000, creditcard, + # :order_id => '1212', :address => {...}, :email => 'test@test.com', + # :order_items => [ + # {:description => 'Line Item 1', :cost => '8.98', :quantity => '6'}, + # {:description => 'Line Item 2', :cost => '6.99', :quantity => '4'} + # ], + # :vendor_data => [{'repId' => '1234567'}, {'customerId' => '9886'}], + # :send_customer_email => true, + # :send_merchant_email => true, + # :email_text => ['line1', 'line2', 'line3'], + # :test_mode => true + # ) + # + def authorize(money, payment_source, options = {}) + payload = Nokogiri::XML::Builder.new do |xml| + xml.AuthTransaction { + xml.Preauth + add_customer_data(xml, payment_source, options) + add_invoice(xml, money, options) + add_payment_source(xml, payment_source) + add_transaction_control(xml, options) + add_vendor_data(xml, options) + } + end.doc + + commit(payload) + end + + # Performs an authorize and capture in single transaction. In PaymentClearing's + # documentation this is known as an "Auth" or a "Sale" transaction + # + # ==== Parameters + # * money - The amount to be captured. Should be nil or an Integer amount in cents. + # * creditcard - The CreditCard details for the transaction + # * options - A Hash of options + # + # ==== Options Hash + # The standard options apply here (:order_id, :ip, :customer, :invoice, :merchant, :description, :email, :currency, :address, :billing_address, :shipping_address), as well as: + # * :order_items - An Array of Hash objects with the keys :description, :cost (in cents!), and :quantity. If this is provided, :description and money will be ignored. + # * :vendor_data - An Array of Hash objects with the keys being the name of the VendorData element and value being the value. + # * :send_customer_email - true or false. Runs the transaction with the 'SendCustomerEmail' element set to 'TRUE' or 'FALSE'. + # * :send_merchant_email - true or false. Runs the transaction with the 'SendMerchantEmail' element set to 'TRUE' or 'FALSE'. + # * :email_text - An Array of (up to ten (10)) String objects to be included in emails + # * :test_mode - true or false. Runs the transaction with the 'TestMode' element set to 'TRUE' or 'FALSE'. + # + # ==== Examples + # response = gateway.purchase(1000, creditcard, + # :order_id => '1212', :address => {...}, :email => 'test@test.com', + # :order_items => [ + # {:description => 'Line Item 1', :cost => '8.98', :quantity => '6'}, + # {:description => 'Line Item 2', :cost => '6.99', :quantity => '4'} + # ], + # :vendor_data => [{'repId' => '1234567'}, {'customerId' => '9886'}], + # :send_customer_email => true, + # :send_merchant_email => true, + # :email_text => ['line1', 'line2', 'line3'], + # :test_mode => true + # ) + # + def purchase(money, payment_source, options = {}) + payload = Nokogiri::XML::Builder.new do |xml| + xml.AuthTransaction { + add_customer_data(xml, payment_source, options) + add_invoice(xml, money, options) + add_payment_source(xml, payment_source) + add_transaction_control(xml, options) + add_vendor_data(xml, options) + } + end.doc + + commit(payload) + end + + # Captures the funds from an authorize transaction. In PaymentClearing's + # documentation this is known as a "PostAuth" transaction. + # + # ==== Parameters + # * money - The amount to be captured. Should be an Integer amount in cents + # * authorization - The authorization returned from the previous capture or purchase request + # * options - A Hash of options, all are optional. + # + # ==== Options Hash + # The standard options apply here (:order_id, :ip, :customer, :invoice, :merchant, :description, :email, :currency, :address, :billing_address, :shipping_address), as well as: + # * :vendor_data - An Array of Hash objects with the keys being the name of the VendorData element and value being the value. + # * :send_customer_email - true or false. Runs the transaction with the 'SendCustomerEmail' element set to 'TRUE' or 'FALSE'. + # * :send_merchant_email - true or false. Runs the transaction with the 'SendMerchantEmail' element set to 'TRUE' or 'FALSE'. + # * :email_text - An Array of (up to ten (10)) String objects to be included in emails + # * :test_mode - true or false. Runs the transaction with the 'TestMode' element set to 'TRUE' or 'FALSE'. + # + # ==== Examples + # response = gateway.capture(1000, creditcard, + # :vendor_data => [{'repId' => '1234567'}, {'customerId' => '9886'}], + # :send_customer_email => true, + # :send_merchant_email => true, + # :email_text => ['line1', 'line2', 'line3'], + # :test_mode => true + # ) + # + def capture(money, authorization, options = {}) + payload = Nokogiri::XML::Builder.new do |xml| + xml.PostAuthTransaction { + xml.OperationXID(authorization) + add_invoice(xml, money, options) + add_transaction_control(xml, options) + add_vendor_data(xml, options) + } + end.doc + + commit(payload) + end + + # This will reverse a previously run transaction which *has* *not* settled. + # + # ==== Parameters + # * authorization - The authorization returned from the previous capture or purchase request + # * options - A Hash of options, all are optional + # + # ==== Options Hash + # The standard options (:order_id, :ip, :customer, :invoice, :merchant, :description, :email, :currency, :address, :billing_address, :shipping_address) are ignored. + # * :vendor_data - An Array of Hash objects with the keys being the name of the VendorData element and value being the value. + # * :send_customer_email - true or false. Runs the transaction with the 'SendCustomerEmail' element set to 'TRUE' or 'FALSE'. + # * :send_merchant_email - true or false. Runs the transaction with the 'SendMerchantEmail' element set to 'TRUE' or 'FALSE'. + # * :email_text - An Array of (up to ten (10)) String objects to be included in emails + # * :test_mode - true or false. Runs the transaction with the 'TestMode' element set to 'TRUE' or 'FALSE'. + # + # ==== Examples + # response = gateway.void('9999999999', + # :vendor_data => [{'repId' => '1234567'}, {'customerId' => '9886'}], + # :send_customer_email => true, + # :send_merchant_email => true, + # :email_text => ['line1', 'line2', 'line3'], + # :test_mode => true + # ) + # + def void(authorization, options = {}) + payload = Nokogiri::XML::Builder.new do |xml| + xml.VoidTransaction { + xml.OperationXID(authorization) + add_transaction_control(xml, options) + add_vendor_data(xml, options) + } + end.doc + + commit(payload) + end + + # This will reverse a previously run transaction which *has* settled. + # + # ==== Parameters + # * money - The amount to be credited. Should be an Integer amount in cents + # * authorization - The authorization returned from the previous capture or purchase request + # * options - A Hash of options, all are optional + # + # ==== Options Hash + # The standard options (:order_id, :ip, :customer, :invoice, :merchant, :description, :email, :currency, :address, :billing_address, :shipping_address) are ignored. + # * :vendor_data - An Array of Hash objects with the keys being the name of the VendorData element and value being the value. + # * :send_customer_email - true or false. Runs the transaction with the 'SendCustomerEmail' element set to 'TRUE' or 'FALSE'. + # * :send_merchant_email - true or false. Runs the transaction with the 'SendMerchantEmail' element set to 'TRUE' or 'FALSE'. + # * :email_text - An Array of (up to ten (10)) String objects to be included in emails + # * :test_mode - true or false. Runs the transaction with the 'TestMode' element set to 'TRUE' or 'FALSE'. + # + # ==== Examples + # response = gateway.refund(555, '9999999999', + # :vendor_data => [{'repId' => '1234567'}, {'customerId' => '9886'}], + # :send_customer_email => true, + # :send_merchant_email => true, + # :email_text => ['line1', 'line2', 'line3'], + # :test_mode => true + # ) + # + def refund(money, authorization, options = {}) + payload = Nokogiri::XML::Builder.new do |xml| + xml.TranCredTransaction { + xml.OperationXID(authorization) + add_invoice(xml, money, options) + add_transaction_control(xml, options) + add_vendor_data(xml, options) + } + end.doc + + commit(payload) + end + + private + + def add_customer_data(xml, payment_source, options) + billing_address = options[:billing_address] || options[:address] + shipping_address = options[:shipping_address] || options[:address] + + xml.CustomerData { + xml.Email(options[:email]) unless options[:email].blank? + xml.CustId(options[:order_id]) unless options[:order_id].blank? + xml.BillingAddress { + xml.FirstName(payment_source.first_name || parse_first_name(billing_address[:name])) + xml.LastName(payment_source.last_name || parse_last_name(billing_address[:name])) + xml.Address1(billing_address[:address1]) + xml.Address2(billing_address[:address2]) unless billing_address[:address2].blank? + xml.City(billing_address[:city]) + xml.State(billing_address[:state]) + xml.Zip(billing_address[:zip]) + xml.Country(billing_address[:country]) + xml.Phone(billing_address[:phone]) + } + xml.ShippingAddress { + xml.FirstName(payment_source.first_name || parse_first_name(shipping_address[:name])) + xml.LastName(payment_source.last_name || parse_last_name(shipping_address[:name])) + xml.Address1(shipping_address[:address1]) + xml.Address2(shipping_address[:address2]) unless shipping_address[:address2].blank? + xml.City(shipping_address[:city]) + xml.State(shipping_address[:state]) + xml.Zip(shipping_address[:zip]) + xml.Country(shipping_address[:country]) + xml.Phone(shipping_address[:phone]) + } unless shipping_address.blank? + } + end + + def add_invoice(xml, money, options) + xml.AuthCode options[:force] if options[:force] + if options[:order_items].blank? + xml.Total(amount(money)) unless(money.nil? || money < 0.01) + xml.Description(options[:description]) unless( options[:description].blank?) + else + xml.OrderItems { + options[:order_items].each do |item| + xml.Item { + xml.Description(item[:description]) + xml.Cost(amount(item[:cost])) + xml.Qty(item[:quantity].to_s) + } + end + } + end + end + + def add_payment_source(xml, source) + case determine_funding_source(source) + when :credit_card then add_creditcard(xml, source) + when :check then add_check(xml, source) + end + end + + def determine_funding_source(payment_source) + case payment_source + when ActiveMerchant::Billing::CreditCard + :credit_card + when ActiveMerchant::Billing::Check + :check + end + end + + def add_creditcard(xml, creditcard) + xml.AccountInfo { + xml.CardAccount { + xml.AccountNumber(creditcard.number.to_s) + xml.ExpirationMonth(creditcard.month.to_s.rjust(2,'0')) + xml.ExpirationYear(creditcard.year.to_s) + xml.CVVNumber(creditcard.verification_value.to_s) unless creditcard.verification_value.blank? + } + } + end + + def add_check(xml, check) + xml.AccountInfo { + xml.ABA(check.routing_number.to_s) + xml.AccountNumber(check.account_number.to_s) + xml.AccountSource(check.account_type.to_s) + xml.AccountType(check.account_holder_type.to_s) + xml.CheckNumber(check.number.to_s) + } + end + + def add_transaction_control(xml, options) + xml.TransactionControl { + # if there was a 'global' option set... + xml.TestMode(@options[:test_mode].upcase) if !@options[:test_mode].blank? + # allow the global option to be overridden... + xml.TestMode(options[:test_mode].upcase) if !options[:test_mode].blank? + xml.SendCustomerEmail(options[:send_customer_email].upcase) unless options[:send_customer_email].blank? + xml.SendMerchantEmail(options[:send_merchant_email].upcase) unless options[:send_merchant_email].blank? + xml.EmailText { + options[:email_text].each do |item| + xml.EmailTextItem(item) + end + } if options[:email_text] + } + end + + def add_vendor_data(xml, options) + return if options[:vendor_data].blank? + xml.VendorData { + options[:vendor_data].each do |k,v| + xml.Element { + xml.Name(k) + xml.Key(v) + } + end + } + end + + def commit(payload) + # Set the Content-Type header -- otherwise the URL decoding messes up + # the Base64 encoded payload signature! + response = parse(ssl_post(self.live_url, post_data(payload), 'Content-Type' => 'text/xml')) + + Response.new(successful?(response), response[:error_message], response, + :test => test?, + :authorization => response[:xid], + :avs_result => { :code => response[:avs_response] }, + :cvv_result => response[:cvv_response]) + end + + def post_data(payload) + payload_xml = payload.root.to_xml(:indent => 0) + + payload_signature = sign_payload(payload_xml) + + request = Nokogiri::XML::Builder.new do |xml| + xml.GatewayInterface { + xml.APICredentials { + xml.Username(@options[:login]) + xml.PayloadSignature(payload_signature) + xml.TargetGateway(@options[:gateway_id]) + } + } + end.doc + + request.root.children.first.after payload.root + request.to_xml(:indent => 0) + end + + def parse(raw_xml) + doc = REXML::Document.new(raw_xml) + response = Hash.new + transaction_result = doc.root.get_elements('TransactionResponse/TransactionResult/*') + transaction_result.each do |e| + response[e.name.to_s.underscore.to_sym] = e.text unless e.text.blank? + end + response + end + + def successful?(response) + # Turns out the PaymentClearing gateway is not consistent... + response[:status].downcase =='ok' + end + + def test_mode?(response) + # The '1' is a legacy thing; most of the time it should be 'TRUE'... + response[:test_mode] == 'TRUE' || response[:test_mode] == '1' + end + + def message_from(response) + response[:error_message] + end + + def sign_payload(payload) + key = @options[:password].to_s + digest=OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new(key), key, payload) + signature = Base64.encode64(digest) + signature.chomp! + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/jetpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/jetpay.rb new file mode 100644 index 000000000..7a7002d0b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/jetpay.rb @@ -0,0 +1,275 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class JetpayGateway < Gateway + self.test_url = 'https://test1.jetpay.com/jetpay' + self.live_url = 'https://gateway17.jetpay.com/jetpay' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.jetpay.com/' + + # The name of the gateway + self.display_name = 'JetPay' + + # all transactions are in cents + self.money_format = :cents + + ACTION_CODE_MESSAGES = { + "001" => "Refer to card issuer.", + "002" => "Refer to card issuer, special condition.", + "003" => "Pick up card.", + "200" => "Deny - Pick up card.", + "005" => "Do not honor.", + "100" => "Deny.", + "006" => "Error.", + "181" => "Format error.", + "007" => "Pickup card, special condition.", + "104" => "Deny - New card issued.", + "110" => "Invalid amount.", + "014" => "Invalid account number (no such number).", + "111" => "Invalid account.", + "015" => "No such issuer.", + "103" => "Deny - Invalid manual Entry 4DBC.", + "182" => "Please wait.", + "109" => "Invalid merchant.", + "041" => "Pick up card (lost card).", + "043" => "Pick up card (stolen card).", + "051" => "Insufficient funds.", + "052" => "No checking account.", + "105" => "Deny - Account Cancelled.", + "054" => "Expired Card.", + "101" => "Expired Card.", + "183" => "Invalid currency code.", + "057" => "Transaction not permitted to cardholder.", + "115" => "Service not permitted.", + "062" => "Restricted card.", + "189" => "Deny - Cancelled or Closed Merchant/SE.", + "188" => "Deny - Expiration date required.", + "125" => "Invalid effective date.", + "122" => "Invalid card (CID) security code.", + "400" => "Reversal accepted.", + "992" => "DECLINE/TIMEOUT.", + "107" => "Please Call Issuer.", + "025" => "Transaction Not Found.", + "981" => "AVS Error.", + "913" => "Invalid Card Type.", + "996" => "Terminal ID Not Found.", + nil => "No response returned (missing credentials?)." + } + + def initialize(options = {}) + requires!(options, :login) + super + end + + def purchase(money, credit_card, options = {}) + commit(money, build_sale_request(money, credit_card, options)) + end + + def authorize(money, credit_card, options = {}) + commit(money, build_authonly_request(money, credit_card, options)) + end + + def capture(money, reference, options = {}) + commit(money, build_capture_request('CAPT', reference.split(";").first)) + end + + def void(reference, options = {}) + transaction_id, approval, amount = reference.split(";") + commit(amount.to_i, build_void_request(amount.to_i, transaction_id, approval)) + end + + def credit(money, transaction_id_or_card, options = {}) + if transaction_id_or_card.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, transaction_id_or_card, options) + else + commit(money, build_credit_request('CREDIT', money, nil, transaction_id_or_card)) + end + end + + def refund(money, reference, options = {}) + transaction_id = reference.split(";").first + credit_card = options[:credit_card] + commit(money, build_credit_request('CREDIT', money, transaction_id, credit_card)) + end + + + private + + def build_xml_request(transaction_type, transaction_id = nil, &block) + xml = Builder::XmlMarkup.new + xml.tag! 'JetPay' do + # The basic values needed for any request + xml.tag! 'TerminalID', @options[:login] + xml.tag! 'TransactionType', transaction_type + xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id + + if block_given? + yield xml + else + xml.target! + end + end + end + + def build_sale_request(money, credit_card, options) + build_xml_request('SALE') do |xml| + add_credit_card(xml, credit_card) + add_addresses(xml, options) + add_customer_data(xml, options) + add_invoice_data(xml, options) + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + def build_authonly_request(money, credit_card, options) + build_xml_request('AUTHONLY') do |xml| + add_credit_card(xml, credit_card) + add_addresses(xml, options) + add_customer_data(xml, options) + add_invoice_data(xml, options) + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + def build_capture_request(transaction_type, transaction_id) + build_xml_request(transaction_type, transaction_id) + end + + def build_void_request(money, transaction_id, approval) + build_xml_request('VOID', transaction_id) do |xml| + xml.tag! 'Approval', approval + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + # `transaction_id` may be nil for unlinked credit transactions. + def build_credit_request(transaction_type, money, transaction_id, card) + build_xml_request(transaction_type, transaction_id) do |xml| + add_credit_card(xml, card) if card + xml.tag! 'TotalAmount', amount(money) + + xml.target! + end + end + + def commit(money, request) + response = parse(ssl_post(test? ? self.test_url : self.live_url, request)) + + success = success?(response) + Response.new(success, + success ? 'APPROVED' : message_from(response), + response, + :test => test?, + :authorization => authorization_from(response, money), + :avs_result => { :code => response[:avs] }, + :cvv_result => response[:cvv2] + ) + end + + def parse(body) + return {} if body.blank? + + xml = REXML::Document.new(body) + + response = {} + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each{|element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + def format_exp(value) + format(value, :two_digits) + end + + def success?(response) + response[:action_code] == "000" + end + + def message_from(response) + ACTION_CODE_MESSAGES[response[:action_code]] + end + + def authorization_from(response, money) + original_amount = amount(money) if money + [ response[:transaction_id], response[:approval], original_amount ].join(";") + end + + def add_credit_card(xml, credit_card) + xml.tag! 'CardNum', credit_card.number + xml.tag! 'CardExpMonth', format_exp(credit_card.month) + xml.tag! 'CardExpYear', format_exp(credit_card.year) + + if credit_card.first_name || credit_card.last_name + xml.tag! 'CardName', [credit_card.first_name,credit_card.last_name].compact.join(' ') + end + + unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0) + xml.tag! 'CVV2', credit_card.verification_value + end + end + + def add_addresses(xml, options) + if billing_address = options[:billing_address] || options[:address] + xml.tag! 'BillingAddress', [billing_address[:address1], billing_address[:address2]].compact.join(" ") + xml.tag! 'BillingCity', billing_address[:city] + xml.tag! 'BillingStateProv', billing_address[:state] + xml.tag! 'BillingPostalCode', billing_address[:zip] + xml.tag! 'BillingCountry', lookup_country_code(billing_address[:country]) + xml.tag! 'BillingPhone', billing_address[:phone] + end + + if shipping_address = options[:shipping_address] + xml.tag! 'ShippingInfo' do + xml.tag! 'ShippingName', shipping_address[:name] + + xml.tag! 'ShippingAddr' do + xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(" ") + xml.tag! 'City', shipping_address[:city] + xml.tag! 'StateProv', shipping_address[:state] + xml.tag! 'PostalCode', shipping_address[:zip] + xml.tag! 'Country', lookup_country_code(shipping_address[:country]) + end + end + end + end + + def add_customer_data(xml, options) + xml.tag! 'Email', options[:email] if options[:email] + xml.tag! 'UserIPAddress', options[:ip] if options[:ip] + end + + def add_invoice_data(xml, options) + xml.tag! 'OrderNumber', options[:order_id] if options[:order_id] + xml.tag! 'TaxAmount', amount(options[:tax]) if options[:tax] + end + + def lookup_country_code(code) + country = Country.find(code) rescue nil + country && country.code(:alpha3) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/linkpoint.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/linkpoint.rb new file mode 100644 index 000000000..10917c875 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/linkpoint.rb @@ -0,0 +1,447 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + + # Initialization Options + # :login Your store number + # :pem The text of your linkpoint PEM file. Note + # this is not the path to file, but its + # contents. If you are only using one PEM + # file on your site you can declare it + # globally and then you won't need to + # include this option + # + # + # A valid store number is required. Unfortunately, with LinkPoint + # YOU CAN'T JUST USE ANY OLD STORE NUMBER. Also, you can't just + # generate your own PEM file. You'll need to use a special PEM file + # provided by LinkPoint. + # + # Go to http://www.linkpoint.com/support/sup_teststore.asp to set up + # a test account and obtain your PEM file. + # + # Declaring PEM file Globally + # ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) + # + # + # Valid Order Options + # :result => + # LIVE Production mode + # GOOD Approved response in test mode + # DECLINE Declined response in test mode + # DUPLICATE Duplicate response in test mode + # + # :ponumber Order number + # + # :transactionorigin => Source of the transaction + # ECI Email or Internet + # MAIL Mail order + # MOTO Mail order/Telephone + # TELEPHONE Telephone + # RETAIL Face-to-face + # + # :ordertype => + # SALE Real live sale + # PREAUTH Authorize only + # POSTAUTH Forced Ticket or Ticket Only transaction + # VOID + # CREDIT + # CALCSHIPPING For shipping charges calculations + # CALCTAX For sales tax calculations + # + # Recurring Options + # :action => + # SUBMIT + # MODIFY + # CANCEL + # + # :installments Identifies how many recurring payments to charge the customer + # :startdate Date to begin charging the recurring payments. Format: YYYYMMDD or "immediate" + # :periodicity => + # MONTHLY + # BIMONTHLY + # WEEKLY + # BIWEEKLY + # YEARLY + # DAILY + # :threshold Tells how many times to retry the transaction (if it fails) before contacting the merchant. + # :comments Uh... comments + # + # + # For reference: + # + # https://www.linkpointcentral.com/lpc/docs/Help/APIHelp/lpintguide.htm + # + # Entities = { + # :payment => [:subtotal, :tax, :vattax, :shipping, :chargetotal], + # :billing => [:name, :address1, :address2, :city, :state, :zip, :country, :email, :phone, :fax, :addrnum], + # :shipping => [:name, :address1, :address2, :city, :state, :zip, :country, :weight, :items, :carrier, :total], + # :creditcard => [:cardnumber, :cardexpmonth, :cardexpyear, :cvmvalue, :track], + # :telecheck => [:routing, :account, :checknumber, :bankname, :bankstate, :dl, :dlstate, :void, :accounttype, :ssn], + # :transactiondetails => [:transactionorigin, :oid, :ponumber, :taxexempt, :terminaltype, :ip, :reference_number, :recurring, :tdate], + # :periodic => [:action, :installments, :threshold, :startdate, :periodicity, :comments], + # :notes => [:comments, :referred] + # :items => [:item => [:price, :quantity, :description, :id, :options => [:option => [:name, :value]]]] + # } + # + # + # LinkPoint's Items entity is an optional entity that can be attached to orders. + # It is entered as :line_items to be consistent with the CyberSource implementation + # + # The line_item hash goes in the options hash and should look like + # + # :line_items => [ + # { + # :id => '123456', + # :description => 'Logo T-Shirt', + # :price => '12.00', + # :quantity => '1', + # :options => [ + # { + # :name => 'Color', + # :value => 'Red' + # }, + # { + # :name => 'Size', + # :value => 'XL' + # } + # ] + # }, + # { + # :id => '111', + # :description => 'keychain', + # :price => '3.00', + # :quantity => '1' + # } + # ] + # This functionality is only supported by this particular gateway may + # be changed at any time + # + class LinkpointGateway < Gateway + # Your global PEM file. This will be assigned to you by linkpoint + # + # Example: + # + # ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) + # + cattr_accessor :pem_file + + self.test_url = 'https://staging.linkpt.net:1129/' + self.live_url = 'https://secure.linkpt.net:1129/' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.homepage_url = 'http://www.linkpoint.com/' + self.display_name = 'LinkPoint' + + def initialize(options = {}) + requires!(options, :login) + + @options = { + :result => 'LIVE', + :pem => LinkpointGateway.pem_file + }.update(options) + + raise ArgumentError, "You need to pass in your pem file using the :pem parameter or set it globally using ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) or similar" if @options[:pem].blank? + end + + # Send a purchase request with periodic options + # Recurring Options + # :action => + # SUBMIT + # MODIFY + # CANCEL + # + # :installments Identifies how many recurring payments to charge the customer + # :startdate Date to begin charging the recurring payments. Format: YYYYMMDD or "immediate" + # :periodicity => + # :monthly + # :bimonthly + # :weekly + # :biweekly + # :yearly + # :daily + # :threshold Tells how many times to retry the transaction (if it fails) before contacting the merchant. + # :comments Uh... comments + # + def recurring(money, creditcard, options={}) + requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily], :installments, :order_id ) + + options.update( + :ordertype => "SALE", + :action => options[:action] || "SUBMIT", + :installments => options[:installments] || 12, + :startdate => options[:startdate] || "immediate", + :periodicity => options[:periodicity].to_s || "monthly", + :comments => options[:comments] || nil, + :threshold => options[:threshold] || 3 + ) + commit(money, creditcard, options) + end + + # Buy the thing + def purchase(money, creditcard, options={}) + requires!(options, :order_id) + options.update( + :ordertype => "SALE" + ) + commit(money, creditcard, options) + end + + # + # Authorize the transaction + # + # Reserves the funds on the customer's credit card, but does not charge the card. + # + def authorize(money, creditcard, options = {}) + requires!(options, :order_id) + options.update( + :ordertype => "PREAUTH" + ) + commit(money, creditcard, options) + end + + # + # Post an authorization. + # + # Captures the funds from an authorized transaction. + # Order_id must be a valid order id from a prior authorized transaction. + # + def capture(money, authorization, options = {}) + options.update( + :order_id => authorization, + :ordertype => "POSTAUTH" + ) + commit(money, nil, options) + end + + # Void a previous transaction + def void(identification, options = {}) + options.update( + :order_id => identification, + :ordertype => "VOID" + ) + commit(nil, nil, options) + end + + # + # Refund an order + # + # identification must be a valid order id previously submitted by SALE + # + def refund(money, identification, options = {}) + options.update( + :ordertype => "CREDIT", + :order_id => identification + ) + commit(money, nil, options) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + private + # Commit the transaction by posting the XML file to the LinkPoint server + def commit(money, creditcard, options = {}) + response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(money, creditcard, options))) + + Response.new(successful?(response), response[:message], response, + :test => test?, + :authorization => response[:ordernum], + :avs_result => { :code => response[:avs].to_s[2,1] }, + :cvv_result => response[:avs].to_s[3,1] + ) + end + + def successful?(response) + response[:approved] == "APPROVED" + end + + # Build the XML file + def post_data(money, creditcard, options) + params = parameters(money, creditcard, options) + + xml = REXML::Document.new + order = xml.add_element("order") + + # Merchant Info + merchantinfo = order.add_element("merchantinfo") + merchantinfo.add_element("configfile").text = @options[:login] + + # Loop over the params hash to construct the XML string + for key, value in params + elem = order.add_element(key.to_s) + if key == :items + build_items(elem, value) + else + for k, v in params[key] + elem.add_element(k.to_s).text = params[key][k].to_s if params[key][k] + end + end + # Linkpoint doesn't understand empty elements: + order.delete(elem) if elem.size == 0 + end + return xml.to_s + end + + # adds LinkPoint's Items entity to the XML. Called from post_data + def build_items(element, items) + for item in items + item_element = element.add_element("item") + for key, value in item + if key == :options + options_element = item_element.add_element("options") + for option in value + opt_element = options_element.add_element("option") + opt_element.add_element("name").text = option[:name] unless option[:name].blank? + opt_element.add_element("value").text = option[:value] unless option[:value].blank? + end + else + item_element.add_element(key.to_s).text = item[key].to_s unless item[key].blank? + end + end + end + end + + # Set up the parameters hash just once so we don't have to do it + # for every action. + def parameters(money, creditcard, options = {}) + + params = { + :payment => { + :subtotal => amount(options[:subtotal]), + :tax => amount(options[:tax]), + :vattax => amount(options[:vattax]), + :shipping => amount(options[:shipping]), + :chargetotal => amount(money) + }, + :transactiondetails => { + :transactionorigin => options[:transactionorigin] || "ECI", + :oid => options[:order_id], + :ponumber => options[:ponumber], + :taxexempt => options[:taxexempt], + :terminaltype => options[:terminaltype], + :ip => options[:ip], + :reference_number => options[:reference_number], + :recurring => options[:recurring] || "NO", #DO NOT USE if you are using the periodic billing option. + :tdate => options[:tdate] + }, + :orderoptions => { + :ordertype => options[:ordertype], + :result => @options[:result] + }, + :periodic => { + :action => options[:action], + :installments => options[:installments], + :threshold => options[:threshold], + :startdate => options[:startdate], + :periodicity => options[:periodicity], + :comments => options[:comments] + }, + :telecheck => { + :routing => options[:telecheck_routing], + :account => options[:telecheck_account], + :checknumber => options[:telecheck_checknumber], + :bankname => options[:telecheck_bankname], + :dl => options[:telecheck_dl], + :dlstate => options[:telecheck_dlstate], + :void => options[:telecheck_void], + :accounttype => options[:telecheck_accounttype], + :ssn => options[:telecheck_ssn], + } + } + + if creditcard + params[:creditcard] = { + :cardnumber => creditcard.number, + :cardexpmonth => creditcard.month, + :cardexpyear => format_creditcard_expiry_year(creditcard.year), + :track => nil + } + + if creditcard.verification_value? + params[:creditcard][:cvmvalue] = creditcard.verification_value + params[:creditcard][:cvmindicator] = 'provided' + else + params[:creditcard][:cvmindicator] = 'not_provided' + end + end + + if billing_address = options[:billing_address] || options[:address] + + params[:billing] = {} + params[:billing][:name] = billing_address[:name] || (creditcard ? creditcard.name : nil) + params[:billing][:address1] = billing_address[:address1] unless billing_address[:address1].blank? + params[:billing][:address2] = billing_address[:address2] unless billing_address[:address2].blank? + params[:billing][:city] = billing_address[:city] unless billing_address[:city].blank? + params[:billing][:state] = billing_address[:state] unless billing_address[:state].blank? + params[:billing][:zip] = billing_address[:zip] unless billing_address[:zip].blank? + params[:billing][:country] = billing_address[:country] unless billing_address[:country].blank? + params[:billing][:company] = billing_address[:company] unless billing_address[:company].blank? + params[:billing][:phone] = billing_address[:phone] unless billing_address[:phone].blank? + params[:billing][:email] = options[:email] unless options[:email].blank? + end + + if shipping_address = options[:shipping_address] + + params[:shipping] = {} + params[:shipping][:name] = shipping_address[:name] || (creditcard ? creditcard.name : nil) + params[:shipping][:address1] = shipping_address[:address1] unless shipping_address[:address1].blank? + params[:shipping][:address2] = shipping_address[:address2] unless shipping_address[:address2].blank? + params[:shipping][:city] = shipping_address[:city] unless shipping_address[:city].blank? + params[:shipping][:state] = shipping_address[:state] unless shipping_address[:state].blank? + params[:shipping][:zip] = shipping_address[:zip] unless shipping_address[:zip].blank? + params[:shipping][:country] = shipping_address[:country] unless shipping_address[:country].blank? + end + + params[:items] = options[:line_items] if options[:line_items] + + return params + end + + def parse(xml) + + # For reference, a typical response... + # + # + # + # + # + # This is a test transaction and will not show up in the Reports + # + # Thu Feb 2 15:40:21 2006 + # + # + # APPROVED + # + + response = {:message => "Global Error Receipt", :complete => false} + + xml = REXML::Document.new("#{xml}") + xml.root.elements.each do |node| + response[node.name.downcase.sub(/^r_/, '').to_sym] = normalize(node.text) + end unless xml.root.nil? + + response + 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 format_creditcard_expiry_year(year) + sprintf("%.4i", year)[-2..-1] + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/litle.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/litle.rb new file mode 100755 index 000000000..fcc8b740f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/litle.rb @@ -0,0 +1,540 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class LitleGateway < Gateway + # Specific to Litle options: + # * :merchant_id - Merchant Id assigned by Litle + # * :user - Username assigned by Litle + # * :password - Password assigned by Litle + # * :version - The version of the api you are using (eg, '8.10') + # * :proxy_addr - Proxy address - nil if not needed + # * :proxy_port - Proxy port - nil if not needed + # * :url - URL assigned by Litle (for testing, use the sandbox) + # + # Standard Active Merchant options + # * :order_id - The order number + # * :ip - The IP address of the customer making the purchase + # * :customer - The name, customer number, or other information that identifies the customer + # * :invoice - The invoice number + # * :merchant - The name or description of the merchant offering the product + # * :description - A description of the transaction + # * :email - The email address of the customer + # * :currency - 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. + # * :billing_address - A hash containing the billing address of the customer. + # * :shipping_address - A hash containing the shipping address of the customer. + # + # The :billing_address, and :shipping_address hashes can have the following keys: + # + # * :name - The full name of the customer. + # * :company - The company name of the customer. + # * :address1 - The primary street address of the customer. + # * :address2 - Additional line of address information. + # * :city - The city of the customer. + # * :state - 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. + # * :country - 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. + # * :zip - The zip or postal code of the customer. + # * :phone - The phone number of the customer. + + self.test_url = 'https://www.testlitle.com/sandbox/communicator/online' + self.live_url = 'https://payments.litle.com/vap/communicator/online' + + LITLE_SCHEMA_VERSION = '8.13' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.litle.com/' + + # The name of the gateway + self.display_name = 'Litle & Co.' + + self.default_currency = 'USD' + + def initialize(options = {}) + begin + require 'LitleOnline' + rescue LoadError + raise "Could not load the LitleOnline gem (>= 08.13.2). Use `gem install LitleOnline` to install it." + end + + @litle = LitleOnline::LitleOnlineRequest.new + + options[:version] ||= LITLE_SCHEMA_VERSION + options[:merchant] ||= 'Default Report Group' + options[:user] ||= options[:login] + + requires!(options, :merchant_id, :user, :password, :merchant, :version) + + super + end + + def authorize(money, creditcard_or_token, options = {}) + to_pass = build_authorize_request(money, creditcard_or_token, options) + build_response(:authorization, @litle.authorization(to_pass)) + end + + def purchase(money, creditcard_or_token, options = {}) + to_pass = build_purchase_request(money, creditcard_or_token, options) + build_response(:sale, @litle.sale(to_pass)) + end + + def capture(money, authorization, options = {}) + transaction_id, kind = split_authorization(authorization) + to_pass = create_capture_hash(money, transaction_id, options) + build_response(:capture, @litle.capture(to_pass)) + end + + # Note: Litle requires that authorization requests be voided via auth_reversal + # and other requests via void. To maintain the same interface as the other + # gateways the transaction_id and the kind of transaction are concatenated + # together with a ; separator (e.g. 1234;authorization) + # + # A partial auth_reversal can be accomplished by passing :amount as an option + def void(identification, options = {}) + transaction_id, kind = split_authorization(identification) + if(kind == 'authorization') + to_pass = create_auth_reversal_hash(transaction_id, options[:amount], options) + build_response(:authReversal, @litle.auth_reversal(to_pass)) + else + to_pass = create_void_hash(transaction_id, options) + build_response(:void, @litle.void(to_pass)) + end + end + + def credit(money, identification_or_token, options = {}) + to_pass = build_credit_request(money, identification_or_token, options) + build_response(:credit, @litle.credit(to_pass)) + end + + def store(creditcard, options = {}) + to_pass = create_token_hash(creditcard, options) + build_response(:registerToken, @litle.register_token_request(to_pass), %w(000 801 802)) + end + + private + + CARD_TYPE = { + 'visa' => 'VI', + 'master' => 'MC', + 'american_express' => 'AX', + 'discover' => 'DI', + 'jcb' => 'DI', + 'diners_club' => 'DI' + } + + AVS_RESPONSE_CODE = { + '00' => 'Y', + '01' => 'X', + '02' => 'D', + '10' => 'Z', + '11' => 'W', + '12' => 'A', + '13' => 'A', + '14' => 'P', + '20' => 'N', + '30' => 'S', + '31' => 'R', + '32' => 'U', + '33' => 'R', + '34' => 'I', + '40' => 'E' + } + + def url + return @options[:url] if @options[:url].present? + + test? ? self.test_url : self.live_url + end + + def build_response(kind, litle_response, valid_responses=%w(000)) + response = Hash.from_xml(litle_response.raw_xml.to_s)['litleOnlineResponse'] + + if response['response'] == "0" + detail = response["#{kind}Response"] + fraud = fraud_result(detail) + Response.new( + valid_responses.include?(detail['response']), + detail['message'], + { :litleOnlineResponse => response }, + :authorization => authorization_from(detail, kind), + :avs_result => { :code => fraud['avs'] }, + :cvv_result => fraud['cvv'], + :test => test? + ) + else + Response.new(false, response['message'], :litleOnlineResponse => response, :test => test?) + end + end + + # Generates an authorization string of the appropriate id and the kind of transaction + # See #void for how the kind is used + def authorization_from(litle_response, kind) + case kind + when :registerToken + authorization = litle_response['litleToken'] + else + authorization = [litle_response['litleTxnId'], kind.to_s].join(";") + end + end + + def split_authorization(authorization) + transaction_id, kind = authorization.to_s.split(';') + [transaction_id, kind] + end + + def build_authorize_request(money, creditcard_or_token, options) + payment_method = build_payment_method(creditcard_or_token, options) + + hash = create_hash(money, options) + + add_creditcard_or_cardtoken_hash(hash, payment_method) + + hash + end + + def build_purchase_request(money, creditcard_or_token, options) + payment_method = build_payment_method(creditcard_or_token, options) + + hash = create_hash(money, options) + + add_creditcard_or_cardtoken_hash(hash, payment_method) + + hash + end + + def build_credit_request(money, identification_or_token, options) + payment_method = build_payment_method(identification_or_token, options) + + hash = create_hash(money, options) + + add_identification_or_cardtoken_hash(hash, payment_method) + + unless payment_method.is_a?(LitleCardToken) + hash['orderSource'] = nil + hash['orderId'] = nil + end + + hash + end + + def build_payment_method(payment_method, options) + result = payment_method + + # Build instance of the LitleCardToken class for internal use if this is a token request. + if payment_method.is_a?(String) && options.has_key?(:token) + result = LitleCardToken.new(:token => payment_method) + result.month = options[:token][:month] + result.year = options[:token][:year] + result.verification_value = options[:token][:verification_value] + result.brand = options[:token][:brand] + end + + result + end + + def add_creditcard_or_cardtoken_hash(hash, creditcard_or_cardtoken) + if creditcard_or_cardtoken.is_a?(LitleCardToken) + add_cardtoken_hash(hash, creditcard_or_cardtoken) + else + add_creditcard_hash(hash, creditcard_or_cardtoken) + end + end + + def add_identification_or_cardtoken_hash(hash, identification_or_cardtoken) + if identification_or_cardtoken.is_a?(LitleCardToken) + add_cardtoken_hash(hash, identification_or_cardtoken) + else + transaction_id, kind = split_authorization(identification_or_cardtoken) + hash['litleTxnId'] = transaction_id + end + end + + def add_cardtoken_hash(hash, cardtoken) + token_info = {} + token_info['litleToken'] = cardtoken.token + token_info['expDate'] = cardtoken.exp_date if cardtoken.exp_date? + token_info['cardValidationNum'] = cardtoken.verification_value unless cardtoken.verification_value.blank? + token_info['type'] = cardtoken.type unless cardtoken.type.blank? + + hash['token'] = token_info + hash + end + + def add_creditcard_hash(hash, creditcard) + cc_type = CARD_TYPE[creditcard.brand] + exp_date_yr = creditcard.year.to_s[2..3] + exp_date_mo = '%02d' % creditcard.month.to_i + exp_date = exp_date_mo + exp_date_yr + + card_info = { + 'type' => cc_type, + 'number' => creditcard.number, + 'expDate' => exp_date, + 'cardValidationNum' => creditcard.verification_value + } + + hash['card'] = card_info + hash + end + + def create_capture_hash(money, authorization, options) + hash = create_hash(money, options) + hash['litleTxnId'] = authorization + hash + end + + def create_token_hash(creditcard, options) + hash = create_hash(0, options) + hash['accountNumber'] = creditcard.number + hash + end + + def create_void_hash(identification, options) + hash = create_hash(nil, options) + hash['litleTxnId'] = identification + hash + end + + def create_auth_reversal_hash(identification, money, options) + hash = create_hash(money, options) + hash['litleTxnId'] = identification + hash + end + + def create_hash(money, options) + fraud_check_type = {} + if options[:ip] + fraud_check_type['customerIpAddress'] = options[:ip] + end + + enhanced_data = {} + if options[:invoice] + enhanced_data['invoiceReferenceNumber'] = options[:invoice] + end + + if options[:description] + enhanced_data['customerReference'] = options[:description] + end + + if options[:billing_address] + bill_to_address = { + 'name' => options[:billing_address][:name], + 'companyName' => options[:billing_address][:company], + 'addressLine1' => options[:billing_address][:address1], + 'addressLine2' => options[:billing_address][:address2], + 'city' => options[:billing_address][:city], + 'state' => options[:billing_address][:state], + 'zip' => options[:billing_address][:zip], + 'country' => options[:billing_address][:country], + 'email' => options[:email], + 'phone' => options[:billing_address][:phone] + } + end + if options[:shipping_address] + ship_to_address = { + 'name' => options[:shipping_address][:name], + 'companyName' => options[:shipping_address][:company], + 'addressLine1' => options[:shipping_address][:address1], + 'addressLine2' => options[:shipping_address][:address2], + 'city' => options[:shipping_address][:city], + 'state' => options[:shipping_address][:state], + 'zip' => options[:shipping_address][:zip], + 'country' => options[:shipping_address][:country], + 'email' => options[:email], + 'phone' => options[:shipping_address][:phone] + } + end + + hash = { + 'billToAddress' => bill_to_address, + 'shipToAddress' => ship_to_address, + 'orderId' => (options[:order_id] || @options[:order_id]), + 'customerId' => options[:customer], + 'reportGroup' => (options[:merchant] || @options[:merchant]), + 'merchantId' => (options[:merchant_id] || @options[:merchant_id]), + 'orderSource' => (options[:order_source] || 'ecommerce'), + 'enhancedData' => enhanced_data, + 'fraudCheckType' => fraud_check_type, + 'user' => (options[:user] || @options[:user]), + 'password' => (options[:password] || @options[:password]), + 'version' => (options[:version] || @options[:version]), + 'url' => (options[:url] || url), + 'proxy_addr' => (options[:proxy_addr] || @options[:proxy_addr]), + 'proxy_port' => (options[:proxy_port] || @options[:proxy_port]), + 'id' => (options[:id] || options[:order_id] || @options[:order_id]) + } + + if (!money.nil? && money.to_s.length > 0) + hash.merge!({ 'amount' => money }) + end + hash + end + + def fraud_result(authorization_response) + if result = authorization_response['fraudResult'] + if result.key?('cardValidationResult') + cvv_to_pass = result['cardValidationResult'].blank? ? "P" : result['cardValidationResult'] + end + + avs_to_pass = AVS_RESPONSE_CODE[result['avsResult']] unless result['avsResult'].blank? + end + { 'cvv' => cvv_to_pass, 'avs' => avs_to_pass } + end + + # A +LitleCardToken+ object represents a tokenized credit card, and is capable of validating the various + # data associated with these. + # + # == Example Usage + # token = LitleCardToken.new( + # :token => '1234567890123456', + # :month => '9', + # :year => '2010', + # :brand => 'visa', + # :verification_value => '123' + # ) + # + # token.valid? # => true + # cc.exp_date # => 0910 + # + class LitleCardToken + include Validateable + + # Returns or sets the token. (required) + # + # @return [String] + attr_accessor :token + + # Returns or sets the expiry month for the card associated with token. (optional) + # + # @return [Integer] + attr_accessor :month + + # Returns or sets the expiry year for the card associated with token. (optional) + # + # @return [Integer] + attr_accessor :year + + # Returns or sets the card verification value. (optional) + # + # @return [String] the verification value + attr_accessor :verification_value + + # Returns or sets the credit card brand. (optional) + # + # Valid card types are + # + # * +'visa'+ + # * +'master'+ + # * +'discover'+ + # * +'american_express'+ + # * +'diners_club'+ + # * +'jcb'+ + # * +'switch'+ + # * +'solo'+ + # * +'dankort'+ + # * +'maestro'+ + # * +'forbrugsforeningen'+ + # * +'laser'+ + # + # @return (String) the credit card brand + attr_accessor :brand + + # Returns the Litle credit card type identifier. + # + # @return (String) the credit card type identifier + def type + CARD_TYPE[brand] unless brand.blank? + end + + # Returns true if the expiration date is set. + # + # @return (Boolean) + def exp_date? + !month.to_i.zero? && !year.to_i.zero? + end + + # Returns the card token expiration date in MMYY format. + # + # @return (String) the expiration date in MMYY format + def exp_date + result = '' + if exp_date? + exp_date_yr = year.to_s[2..3] + exp_date_mo = '%02d' % month.to_i + + result = exp_date_mo + exp_date_yr + end + result + end + + # Validates the card token details. + # + # Any validation errors are added to the {#errors} attribute. + def validate + validate_card_token + validate_expiration_date + validate_card_brand + end + + def check? + false + end + + private + + CARD_TYPE = { + 'visa' => 'VI', + 'master' => 'MC', + 'american_express' => 'AX', + 'discover' => 'DI', + 'jcb' => 'DI', + 'diners_club' => 'DI' + } + + def before_validate #:nodoc: + self.month = month.to_i + self.year = year.to_i + end + + # Litle XML Reference Guide 1.8.2 + # + # The length of the original card number is reflected in the token, so a + # submitted 16-digit number results in a 16-digit token. Also, all tokens + # use only numeric characters, so you do not have to change your + # systems to accept alpha-numeric characters. + # + # The credit card token numbers themselves have two parts. + # The last four digits match the last four digits of the card number. + # The remaining digits (length can vary based upon original card number + # length) are a randomly generated. + def validate_card_token #:nodoc: + if token.to_s.length < 12 || token.to_s.match(/\A\d+\Z/).nil? + errors.add :token, "is not a valid card token" + end + end + + def validate_expiration_date #:nodoc: + if !month.to_i.zero? || !year.to_i.zero? + errors.add :month, "is not a valid month" unless valid_month?(month) + errors.add :year, "is not a valid year" unless valid_expiry_year?(year) + end + end + + def validate_card_brand #:nodoc: + errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand) + end + + def valid_month?(month) + (1..12).include?(month.to_i) + end + + def valid_expiry_year?(year) + year.to_s =~ /\A\d{4}\Z/ && year.to_i > 1987 + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_e_solutions.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_e_solutions.rb new file mode 100644 index 000000000..c1ae20084 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_e_solutions.rb @@ -0,0 +1,176 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MerchantESolutionsGateway < Gateway + self.test_url = 'https://cert.merchante-solutions.com/mes-api/tridentApi' + self.live_url = 'https://api.merchante-solutions.com/mes-api/tridentApi' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.merchante-solutions.com/' + + # The name of the gateway + self.display_name = 'Merchant e-Solutions' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard_or_card_id, options = {}) + post = {} + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + post[:moto_ecommerce_ind] = options[:moto_ecommerce_ind] if options.has_key?(:moto_ecommerce_ind) + add_invoice(post, options) + add_payment_source(post, creditcard_or_card_id, options) + add_address(post, options) + commit('P', money, post) + end + + def purchase(money, creditcard_or_card_id, options = {}) + post = {} + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + post[:moto_ecommerce_ind] = options[:moto_ecommerce_ind] if options.has_key?(:moto_ecommerce_ind) + add_invoice(post, options) + add_payment_source(post, creditcard_or_card_id, options) + add_address(post, options) + commit('D', money, post) + end + + def capture(money, transaction_id, options = {}) + post ={} + post[:transaction_id] = transaction_id + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + commit('S', money, post) + end + + def store(creditcard, options = {}) + post = {} + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + add_creditcard(post, creditcard, options) + commit('T', nil, post) + end + + def unstore(card_id) + post = {} + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + post[:card_id] = card_id + commit('X', nil, post) + end + + def refund(money, identification, options = {}) + post = {} + post[:transaction_id] = identification + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + options.delete(:customer) + options.delete(:billing_address) + commit('U', money, options.merge(post)) + end + + def credit(money, creditcard_or_card_id, options = {}) + post = {} + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + add_invoice(post, options) + add_payment_source(post, creditcard_or_card_id, options) + commit('C', money, post) + end + + def void(transaction_id, options = {}) + post = {} + post[:transaction_id] = transaction_id + post[:client_reference_number] = options[:customer] if options.has_key?(:customer) + options.delete(:customer) + options.delete(:billing_address) + commit('V', nil, options.merge(post)) + end + + private + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:cardholder_street_address] = address[:address1].to_s.gsub(/[^\w.]/, '+') + post[:cardholder_zip] = address[:zip].to_s + end + end + + def add_invoice(post, options) + if options.has_key? :order_id + post[:invoice_number] = options[:order_id].to_s.gsub(/[^\w.]/, '') + end + end + + def add_payment_source(post, creditcard_or_card_id, options) + if creditcard_or_card_id.is_a?(String) + # using stored card + post[:card_id] = creditcard_or_card_id + post[:card_exp_date] = options[:expiration_date] if options[:expiration_date] + else + # card info is provided + add_creditcard(post, creditcard_or_card_id, options) + end + end + + def add_creditcard(post, creditcard, options) + post[:card_number] = creditcard.number + post[:cvv2] = creditcard.verification_value if creditcard.verification_value? + post[:card_exp_date] = expdate(creditcard) + end + + def parse(body) + results = {} + body.split(/&/).each do |pair| + key,val = pair.split(/=/) + results[key] = val + end + results + end + + def commit(action, money, parameters) + url = test? ? self.test_url : self.live_url + parameters[:transaction_amount] = amount(money) if money unless action == 'V' + + + response = begin + parse( ssl_post(url, post_data(action,parameters)) ) + rescue ActiveMerchant::ResponseError => e + { "error_code" => "404", "auth_response_text" => e.to_s } + end + + Response.new(response["error_code"] == "000", message_from(response), response, + :authorization => response["transaction_id"], + :test => test?, + :cvv_result => response["cvv2_result"], + :avs_result => { :code => response["avs_result"] } + ) + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + "#{month}#{year[-2..-1]}" + end + + def message_from(response) + if response["error_code"] == "000" + "This transaction has been approved" + else + response["auth_response_text"] + end + end + + def post_data(action, parameters = {}) + post = {} + post[:profile_id] = @options[:login] + post[:profile_key] = @options[:password] + post[:transaction_type] = action if action + + request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&") + request + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_ware.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_ware.rb new file mode 100644 index 000000000..940432918 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_ware.rb @@ -0,0 +1,323 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MerchantWareGateway < Gateway + class_attribute :v4_live_url + + self.live_url = self.test_url = 'https://ps1.merchantware.net/MerchantWARE/ws/RetailTransaction/TXRetail.asmx' + self.v4_live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://merchantwarehouse.com/merchantware' + self.display_name = 'MerchantWARE' + + ENV_NAMESPACES = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", + "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema", + "xmlns:env" => "http://schemas.xmlsoap.org/soap/envelope/" + } + ENV_NAMESPACES_V4 = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", + "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema", + "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/" + } + + TX_NAMESPACE = "http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail" + TX_NAMESPACE_V4 = "http://schemas.merchantwarehouse.com/merchantware/40/Credit/" + + ACTIONS = { + :purchase => "IssueKeyedSale", + :authorize => "IssueKeyedPreAuth", + :capture => "IssuePostAuth", + :void => "VoidPreAuthorization", + :credit => "IssueKeyedRefund", + :reference_credit => "IssueRefundByReference" + } + + # Creates a new MerchantWareGateway + # + # The gateway requires that a valid login, password, and name be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login - The MerchantWARE SiteID. + # * :password - The MerchantWARE Key. + # * :name - The MerchantWARE Name. + def initialize(options = {}) + requires!(options, :login, :password, :name) + super + end + + # Authorize a credit card for a given amount. + # + # ==== Parameters + # * money - The amount to be authorized as an Integer value in cents. + # * credit_card - The CreditCard details for the transaction. + # * options + # * :order_id - A unique reference for this order (required). + # * :billing_address - The billing address for the cardholder. + def authorize(money, credit_card, options = {}) + request = build_purchase_request(:authorize, money, credit_card, options) + commit(:authorize, request) + end + + # Authorize and immediately capture funds from a credit card. + # + # ==== Parameters + # * money - The amount to be authorized as anInteger value in cents. + # * credit_card - The CreditCard details for the transaction. + # * options + # * :order_id - A unique reference for this order (required). + # * :billing_address - The billing address for the cardholder. + def purchase(money, credit_card, options = {}) + request = build_purchase_request(:purchase, money, credit_card, options) + commit(:purchase, request) + end + + # Capture authorized funds from a credit card. + # + # ==== Parameters + # * money - The amount to be captured as anInteger value in cents. + # * authorization - The authorization string returned from the initial authorization. + def capture(money, authorization, options = {}) + request = build_capture_request(:capture, money, authorization, options) + commit(:capture, request) + end + + # Void a transaction. + # + # ==== Parameters + # * authorization - The authorization string returned from the initial authorization or purchase. + def void(authorization, options = {}) + reference, options[:order_id] = split_reference(authorization) + request = v4_soap_request(:void) do |xml| + add_reference_token(xml, reference) + end + commit(:void, request, true) + end + + # Refund an amount back a cardholder + # + # ==== Parameters + # + # * money - The amount to be refunded as an Integer value in cents. + # * identification - The credit card you want to refund or the authorization for the existing transaction you are refunding. + # * options + # * :order_id - A unique reference for this order (required when performing a non-referenced credit) + def credit(money, identification, options = {}) + if identification.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + else + perform_credit(money, identification, options) + end + end + + def refund(money, reference, options = {}) + perform_reference_credit(money, reference, options) + end + + private + + def soap_request(action) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! "env:Envelope", ENV_NAMESPACES do + xml.tag! "env:Body" do + xml.tag! ACTIONS[action], "xmlns" => TX_NAMESPACE do + add_credentials(xml) + yield xml + end + end + end + xml.target! + end + + def v4_soap_request(action) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! "soap:Envelope", ENV_NAMESPACES_V4 do + xml.tag! "soap:Body" do + xml.tag! ACTIONS[:void], "xmlns" => TX_NAMESPACE_V4 do + xml.tag! "merchantName", @options[:name] + xml.tag! "merchantSiteId", @options[:login] + xml.tag! "merchantKey", @options[:password] + yield xml + end + end + end + xml.target! + end + + def build_purchase_request(action, money, credit_card, options) + requires!(options, :order_id) + + request = soap_request(action) do |xml| + add_invoice(xml, options) + add_amount(xml, money) + add_credit_card(xml, credit_card) + add_address(xml, options) + end + end + + def build_capture_request(action, money, identification, options) + reference, options[:order_id] = split_reference(identification) + + request = soap_request(action) do |xml| + add_reference(xml, reference) + add_invoice(xml, options) + add_amount(xml, money) + end + end + + def perform_reference_credit(money, identification, options) + reference, options[:order_id] = split_reference(identification) + + request = soap_request(:reference_credit) do |xml| + add_reference(xml, reference) + add_invoice(xml, options) + add_amount(xml, money, "strOverrideAmount") + end + + commit(:reference_credit, request) + end + + def perform_credit(money, credit_card, options) + requires!(options, :order_id) + + request = soap_request(:credit) do |xml| + add_invoice(xml, options) + add_amount(xml, money) + add_credit_card(xml, credit_card) + end + + commit(:credit, request) + end + + def add_credentials(xml) + xml.tag! "strSiteId", @options[:login] + xml.tag! "strKey", @options[:password] + xml.tag! "strName", @options[:name] + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def add_invoice(xml, options) + xml.tag! "strOrderNumber", options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25) + end + + def add_amount(xml, money, tag = "strAmount") + xml.tag! tag, amount(money) + end + + def add_reference(xml, reference) + xml.tag! "strReferenceCode", reference + end + + def add_reference_token(xml, reference) + xml.tag! "token", reference + end + + def add_address(xml, options) + if address = options[:billing_address] || options[:address] + xml.tag! "strAVSStreetAddress", address[:address1] + xml.tag! "strAVSZipCode", address[:zip] + end + end + + def add_credit_card(xml, credit_card) + xml.tag! "strPAN", credit_card.number + xml.tag! "strExpDate", expdate(credit_card) + xml.tag! "strCardHolder", credit_card.name + xml.tag! "strCVCode", credit_card.verification_value if credit_card.verification_value? + end + + def split_reference(reference) + reference.to_s.split(";") + end + + def parse(action, data) + response = {} + xml = REXML::Document.new(data) + + root = REXML::XPath.first(xml, "//#{ACTIONS[action]}Response/#{ACTIONS[action]}Result") + + root.elements.each do |element| + response[element.name] = element.text + end + + status, code, message = response["ApprovalStatus"].split(";") + response[:status] = status + + if response[:success] = status == "APPROVED" + response[:message] = status + else + response[:message] = message + response[:failure_code] = code + end + + response + end + + def parse_error(http_response) + response = {} + response[:http_code] = http_response.code + response[:http_message] = http_response.message + response[:success] = false + + document = REXML::Document.new(http_response.body) + + node = REXML::XPath.first(document, "//soap:Fault") + + node.elements.each do |element| + response[element.name] = element.text + end + + response[:message] = response["faultstring"].to_s.gsub("\n", " ") + response + rescue REXML::ParseException => e + response[:http_body] = http_response.body + response[:message] = "Failed to parse the failed response" + response + end + + def soap_action(action, v4 = false) + v4 ? "#{TX_NAMESPACE_V4}#{ACTIONS[action]}" : "#{TX_NAMESPACE}/#{ACTIONS[action]}" + end + + def url(v4 = false) + v4 ? v4_live_url : live_url + end + + def commit(action, request, v4 = false) + begin + data = ssl_post(url(v4), request, + "Content-Type" => 'text/xml; charset=utf-8', + "SOAPAction" => soap_action(action, v4) + ) + response = parse(action, data) + rescue ActiveMerchant::ResponseError => e + response = parse_error(e.response) + end + + Response.new(response[:success], response[:message], response, + :test => test?, + :authorization => authorization_from(response), + :avs_result => { :code => response["AVSResponse"] }, + :cvv_result => response["CVResponse"] + ) + end + + def authorization_from(response) + if response[:success] + [ response["ReferenceID"], response["OrderNumber"] ].join(";") + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_warrior.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_warrior.rb new file mode 100644 index 000000000..be58bd0bf --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/merchant_warrior.rb @@ -0,0 +1,190 @@ +require 'digest/md5' +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MerchantWarriorGateway < Gateway + TOKEN_TEST_URL = 'https://base.merchantwarrior.com/token/' + TOKEN_LIVE_URL = 'https://api.merchantwarrior.com/token/' + + POST_TEST_URL = 'https://base.merchantwarrior.com/post/' + POST_LIVE_URL = 'https://api.merchantwarrior.com/post/' + + self.supported_countries = ['AU'] + self.supported_cardtypes = [:visa, :master, :american_express, + :diners_club, :discover] + self.homepage_url = 'http://www.merchantwarrior.com/' + self.display_name = 'MerchantWarrior' + + self.money_format = :dollars + self.default_currency = 'AUD' + + def initialize(options = {}) + requires!(options, :merchant_uuid, :api_key, :api_passphrase) + super + end + + def authorize(money, payment_method, options = {}) + post = {} + add_amount(post, money, options) + add_product(post, options) + add_address(post, options) + add_payment_method(post, payment_method) + commit('processAuth', post) + end + + def purchase(money, payment_method, options = {}) + post = {} + add_amount(post, money, options) + add_product(post, options) + add_address(post, options) + add_payment_method(post, payment_method) + commit('processCard', post) + end + + def capture(money, identification) + post = {} + add_amount(post, money, options) + add_transaction(post, identification) + post.merge!('captureAmount' => money.to_s) + commit('processCapture', post) + end + + def refund(money, identification) + post = {} + add_amount(post, money, options) + add_transaction(post, identification) + post['refundAmount'] = money + commit('refundCard', post) + end + + def store(creditcard, options = {}) + post = { + 'cardName' => creditcard.name, + 'cardNumber' => creditcard.number, + 'cardExpiryMonth' => format(creditcard.month, :two_digits), + 'cardExpiryYear' => format(creditcard.year, :two_digits) + } + commit('addCard', post) + end + + private + + def add_transaction(post, identification) + post['transactionID'] = identification + end + + def add_address(post, options) + return unless(address = options[:address]) + + post['customerName'] = address[:name] + post['customerCountry'] = address[:country] + post['customerState'] = address[:state] + post['customerCity'] = address[:city] + post['customerAddress'] = address[:address1] + post['customerPostCode'] = address[:zip] + end + + def add_product(post, options) + post['transactionProduct'] = options[:transaction_product] + end + + def add_payment_method(post, payment_method) + if payment_method.respond_to?(:number) + add_creditcard(post, payment_method) + else + add_token(post, payment_method) + end + end + + def add_token(post, token) + post['cardID'] = token + end + + def add_creditcard(post, creditcard) + post['paymentCardNumber'] = creditcard.number + post['paymentCardName'] = creditcard.name + post['paymentCardExpiry'] = creditcard.expiry_date.expiration.strftime("%m%y") + end + + def add_amount(post, money, options) + currency = (options[:currency] || currency(money)) + + post['transactionAmount'] = money.to_s + post['transactionCurrency'] = currency + post['hash'] = verification_hash(money, currency) + end + + def verification_hash(money, currency) + Digest::MD5.hexdigest( + ( + @options[:api_passphrase].to_s + + @options[:merchant_uuid].to_s + + money.to_s + + currency + ).downcase + ) + end + + def parse(body) + xml = REXML::Document.new(body) + + response = {} + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each{|element| parse_element(response, element)} + else + response[node.name.underscore.to_sym] = node.text + end + end + + def commit(action, post) + add_auth(action, post) + + response = parse(ssl_post(url_for(action, post), post_data(post))) + + Response.new( + success?(response), + response[:response_message], + response, + :test => test?, + :authorization => (response[:card_id] || response[:transaction_id]) + ) + end + + def add_auth(action, post) + post['merchantUUID'] = @options[:merchant_uuid] + post['apiKey'] = @options[:api_key] + unless token?(post) + post['method'] = action + end + end + + def url_for(action, post) + if token?(post) + [(test? ? TOKEN_TEST_URL : TOKEN_LIVE_URL), action].join("/") + else + (test? ? POST_TEST_URL : POST_LIVE_URL) + end + end + + def token?(post) + (post["cardID"] || post["cardName"]) + end + + def success?(response) + (response[:response_code] == '0') + end + + def post_data(post) + post.collect{|k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&") + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/mercury.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/mercury.rb new file mode 100644 index 000000000..2819cc488 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/mercury.rb @@ -0,0 +1,272 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MercuryGateway < Gateway + URLS = { + :test => 'https://w1.mercurydev.net/ws/ws.asmx', + :live => 'https://w1.mercurypay.com/ws/ws.asmx' + } + + self.homepage_url = 'http://www.mercurypay.com' + self.display_name = 'Mercury' + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.default_currency = 'USD' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card, options = {}) + requires!(options, :order_id) + + request = build_non_authorized_request('Sale', money, credit_card, options) + commit('Sale', request) + end + + def credit(money, credit_card, options = {}) + requires!(options, :order_id) + + request = build_non_authorized_request('Return', money, credit_card, options) + commit('Return', request) + end + + def authorize(money, credit_card, options = {}) + requires!(options, :order_id) + + options[:authorized] ||= money + request = build_non_authorized_request('PreAuth', money, credit_card, options) + commit('PreAuth', request) + end + + def capture(money, authorization, options = {}) + requires!(options, :credit_card) + options[:authorized] ||= money + request = build_authorized_request('PreAuthCapture', money, authorization, options[:credit_card], options) + commit('PreAuthCapture', request) + end + + def refund(money, authorization, options = {}) + requires!(options, :credit_card) + + request = build_authorized_request('VoidSale', money, authorization, options[:credit_card], options) + commit(options[:void], request) + end + + private + + def build_non_authorized_request(action, money, credit_card, options) + xml = Builder::XmlMarkup.new + + xml.tag! "TStream" do + xml.tag! "Transaction" do + xml.tag! 'TranType', 'Credit' + xml.tag! 'TranCode', action + if action == 'PreAuth' || action == 'Sale' + xml.tag! "PartialAuth", "Allow" + end + add_invoice(xml, options[:order_id], nil, options) + add_customer_data(xml, options) + add_amount(xml, money, options) + add_credit_card(xml, credit_card, action) + add_address(xml, options) + end + end + xml = xml.target! + end + + def build_authorized_request(action, money, authorization, credit_card, options) + xml = Builder::XmlMarkup.new + + invoice_no, ref_no, auth_code, acq_ref_data, process_data = split_authorization(authorization) + + xml.tag! "TStream" do + xml.tag! "Transaction" do + xml.tag! 'TranType', 'Credit' + xml.tag! 'TranCode', action + if action == 'PreAuthCapture' + xml.tag! "PartialAuth", "Allow" + end + add_invoice(xml, invoice_no, ref_no, options) + add_customer_data(xml, options) + add_amount(xml, money, options) + add_credit_card(xml, credit_card, action) + add_address(xml, options) + xml.tag! 'TranInfo' do + xml.tag! "AuthCode", auth_code + xml.tag! "AcqRefData", acq_ref_data + xml.tag! "ProcessData", process_data + end + end + end + xml = xml.target! + end + + def add_invoice(xml, invoice_no, ref_no, options) + if /^\d+$/ !~ invoice_no.to_s + raise ArgumentError.new("#{invoice_no} is not numeric as required by Mercury") + end + + xml.tag! 'InvoiceNo', invoice_no + xml.tag! 'RefNo', ref_no || invoice_no + xml.tag! 'OperatorID', options[:merchant] if options[:merchant] + xml.tag! 'Memo', options[:description] if options[:description] + end + + def add_customer_data(xml, options) + xml.tag! 'IpAddress', options[:ip] if options[:ip] + if options[:customer] + xml.tag! "TranInfo" do + xml.tag! 'CustomerCode', options[:customer] + end + end + xml.tag! 'MerchantID', @options[:login] + end + + def add_amount(xml, money, options = {}) + xml.tag! 'Amount' do + xml.tag! 'Purchase', amount(money) + xml.tag! 'Tax', options[:tax] if options[:tax] + xml.tag! 'Authorize', amount(options[:authorized]) if options[:authorized] + xml.tag! 'Gratuity', amount(options[:tip]) if options[:tip] + end + end + + CARD_CODES = { + 'visa' => 'VISA', + 'master' => 'M/C', + 'american_express' => 'AMEX', + 'discover' => 'DCVR', + 'diners_club' => 'DCLB', + 'jcb' => 'JCB' + } + + def add_credit_card(xml, credit_card, action) + xml.tag! 'Account' do + xml.tag! 'AcctNo', credit_card.number + xml.tag! 'ExpDate', expdate(credit_card) + end + xml.tag! 'CardType', CARD_CODES[credit_card.brand] if credit_card.brand + + include_cvv = !%w(Return PreAuthCapture).include?(action) + xml.tag! 'CVVData', credit_card.verification_value if(include_cvv && credit_card.verification_value) + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def add_address(xml, options) + if billing_address = options[:billing_address] || options[:address] + xml.tag! 'AVS' do + xml.tag! 'Address', billing_address[:address1] + xml.tag! 'Zip', billing_address[:zip] + end + end + end + + def parse(action, body) + response = {} + hashify_xml!(unescape_xml(body), response) + response + end + + def hashify_xml!(xml, response) + xml = REXML::Document.new(xml) + + xml.elements.each("//CmdResponse/*") do |node| + response[node.name.underscore.to_sym] = node.text + end + + xml.elements.each("//TranResponse/*") do |node| + if node.name.to_s == "Amount" + node.elements.each do |amt| + response[amt.name.underscore.to_sym] = amt.text + end + else + response[node.name.underscore.to_sym] = node.text + end + end + end + + def endpoint_url + URLS[test? ? :test : :live] + end + + def build_soap_request(body) + xml = Builder::XmlMarkup.new + + xml.instruct! + xml.tag! 'soap:Envelope', ENVELOPE_NAMESPACES do + xml.tag! 'soap:Body' do + xml.tag! 'CreditTransaction', 'xmlns' => homepage_url do + xml.tag! 'tran' do + xml << escape_xml(body) + end + xml.tag! 'pw', @options[:password] + end + end + end + xml.target! + end + + def build_header + { + "SOAPAction" => "http://www.mercurypay.com/CreditTransaction", + "Content-Type" => "text/xml; charset=utf-8" + } + end + + SUCCESS_CODES = [ 'Approved', 'Success' ] + + def commit(action, request) + response = parse(action, ssl_post(endpoint_url, build_soap_request(request), build_header)) + + success = SUCCESS_CODES.include?(response[:cmd_status]) + message = success ? 'Success' : message_from(response) + + Response.new(success, message, response, + :test => test?, + :authorization => authorization_from(response), + :avs_result => { :code => response[:avs_result] }, + :cvv_result => response[:cvv_result]) + end + + def message_from(response) + response[:text_response] + end + + def authorization_from(response) + [ + response[:invoice_no], + response[:ref_no], + response[:auth_code], + response[:acq_ref_data], + response[:process_data] + ].join(";") + end + + def split_authorization(authorization) + invoice_no, ref_no, auth_code, acq_ref_data, process_data = authorization.split(";") + [invoice_no, ref_no, auth_code, acq_ref_data, process_data] + end + + ENVELOPE_NAMESPACES = { + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap' => "http://schemas.xmlsoap.org/soap/envelope/", + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' + } + + def escape_xml(xml) + "\n\n" + end + + def unescape_xml(escaped_xml) + escaped_xml.gsub(/\>/,'>').gsub(/\</,'<') + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/metrics_global.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/metrics_global.rb new file mode 100644 index 000000000..fd7bf7643 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/metrics_global.rb @@ -0,0 +1,322 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # For more information on the Metrics Global Payment Gateway, visit the {Metrics Global website}[www.metricsglobal.com]. + # Further documentation on AVS and CVV response codes are available under the support section of the Metrics Global + # control panel. + # + # === Metrics Global Payment Gateway Authentication + # + # The login and password for the gateway are the same as the username and password used to log in to the Metrics Global + # control panel. Contact Metrics Global support to receive credentials for the control panel. + # + # === Demo Account + # + # There is a public demo account available with the following credentials: + # + # Login: demo + # Password: password + class MetricsGlobalGateway < Gateway + API_VERSION = '3.1' + + class_attribute :test_url, :live_url + + self.test_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll?testing=true" + self.live_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll" + + 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.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.homepage_url = 'http://www.metricsglobal.com' + self.display_name = 'Metrics Global' + + CARD_CODE_ERRORS = %w( N S ) + AVS_ERRORS = %w( A E N R W Z ) + AVS_REASON_CODES = %w(27 45) + + # Creates a new MetricsGlobalGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- The username required to access the Metrics Global control panel. (REQUIRED) + # * :password -- The password required to access the Metrics Global control panel. (REQUIRED) + # * :test -- +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 + # + # * money -- The amount to be authorized as an Integer value in cents. + # * creditcard -- The CreditCard details for the transaction. + # * options -- A hash of optional parameters. + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + 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 + # + # * money -- The amount to be purchased as an Integer value in cents. + # * creditcard -- The CreditCard details for the transaction. + # * options -- A hash of optional parameters. + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + 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 + # + # * money -- The amount to be captured as an Integer value in cents. + # * authorization -- The authorization returned from the previous authorize request. + def capture(money, authorization, options = {}) + post = {:trans_id => authorization} + add_customer_data(post, options) + commit('PRIOR_AUTH_CAPTURE', money, post) + end + + # Void a previous transaction + # + # ==== Parameters + # + # * authorization - 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 + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * identification -- The ID of the original transaction against which the refund is being issued. + # * options -- A hash of parameters. + # + # ==== Options + # + # * :card_number -- The credit card number the refund is being issued to. (REQUIRED) + # * :first_name -- The first name of the account being refunded. + # * :last_name -- The last name of the account being refunded. + # * :zip -- 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 + + 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) + + 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_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_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] + 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." + 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 + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs.rb new file mode 100644 index 000000000..7eabdfdfa --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs.rb @@ -0,0 +1,265 @@ +require File.dirname(__FILE__) + '/migs/migs_codes' + +require 'digest/md5' # Used in add_secure_hash + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class MigsGateway < Gateway + include MigsCodes + + API_VERSION = 1 + + class_attribute :server_hosted_url, :merchant_hosted_url + + self.server_hosted_url = 'https://migs.mastercard.com.au/vpcpay' + self.merchant_hosted_url = 'https://migs.mastercard.com.au/vpcdps' + + self.live_url = self.server_hosted_url + + # MiGS is supported throughout Asia Pacific, Middle East and Africa + # MiGS is used in Australia (AU) by ANZ (eGate), CBA (CommWeb) and more + # Source of Country List: http://www.scribd.com/doc/17811923 + self.supported_countries = %w(AU AE BD BN EG HK ID IN JO KW LB LK MU MV MY NZ OM PH QA SA SG TT VN) + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + + self.money_format = :cents + + # The homepage URL of the gateway + self.homepage_url = 'http://mastercard.com/mastercardsps' + + # The name of the gateway + self.display_name = 'MasterCard Internet Gateway Service (MiGS)' + + # Creates a new MigsGateway + # The advanced_login/advanced_password fields are needed for + # advanced methods such as the capture, refund and status methods + # + # ==== Options + # + # * :login -- The MiGS Merchant ID (REQUIRED) + # * :password -- The MiGS Access Code (REQUIRED) + # * :secure_hash -- The MiGS Secure Hash + # (Required for Server Hosted payments) + # * :advanced_login -- The MiGS AMA User + # * :advanced_password -- The MiGS AMA User's password + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # ==== Options + # + # * :order_id -- A reference for tracking the order (REQUIRED) + # * :unique_id -- A unique id for this request (Max 40 chars). + # If not supplied one will be generated. + def purchase(money, creditcard, options = {}) + requires!(options, :order_id) + + post = {} + post[:Amount] = amount(money) + add_invoice(post, options) + add_creditcard(post, creditcard) + add_standard_parameters('pay', post, options[:unique_id]) + + commit(post) + end + + # MiGS works by merchants being either purchase only or authorize/capture + # So authorize is the same as purchase when in authorize mode + alias_method :authorize, :purchase + + # ==== Options + # + # * :unique_id -- A unique id for this request (Max 40 chars). + # If not supplied one will be generated. + def capture(money, authorization, options = {}) + requires!(@options, :advanced_login, :advanced_password) + + post = options.merge(:TransNo => authorization) + post[:Amount] = amount(money) + add_advanced_user(post) + add_standard_parameters('capture', post, options[:unique_id]) + + commit(post) + end + + # ==== Options + # + # * :unique_id -- A unique id for this request (Max 40 chars). + # If not supplied one will be generated. + def refund(money, authorization, options = {}) + requires!(@options, :advanced_login, :advanced_password) + + post = options.merge(:TransNo => authorization) + post[:Amount] = amount(money) + add_advanced_user(post) + add_standard_parameters('refund', post, options[:unique_id]) + + commit(post) + end + + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + # Checks the status of a previous transaction + # This can be useful when a response is not received due to network issues + # + # ==== Parameters + # + # * unique_id -- Unique id of transaction to find. + # This is the value of the option supplied in other methods or + # if not supplied is returned with key :MerchTxnRef + def status(unique_id) + requires!(@options, :advanced_login, :advanced_password) + + post = {} + add_advanced_user(post) + add_standard_parameters('queryDR', post, unique_id) + + commit(post) + end + + # Generates a URL to redirect user to MiGS to process payment + # Once user is finished MiGS will redirect back to specified URL + # With a response hash which can be turned into a Response object + # with purchase_offsite_response + # + # ==== Options + # + # * :order_id -- A reference for tracking the order (REQUIRED) + # * :locale -- Change the language of the redirected page + # Values are 2 digit locale, e.g. en, es + # * :return_url -- the URL to return to once the payment is complete + # * :card_type -- Providing this skips the card type step. + # Values are ActiveMerchant formats: e.g. master, visa, american_express, diners_club + # * :unique_id -- Unique id of transaction to find. + # If not supplied one will be generated. + def purchase_offsite_url(money, options = {}) + requires!(options, :order_id, :return_url) + requires!(@options, :secure_hash) + + post = {} + post[:Amount] = amount(money) + add_invoice(post, options) + add_creditcard_type(post, options[:card_type]) if options[:card_type] + + post.merge!( + :Locale => options[:locale] || 'en', + :ReturnURL => options[:return_url] + ) + + add_standard_parameters('pay', post, options[:unique_id]) + + add_secure_hash(post) + + self.server_hosted_url + '?' + post_data(post) + end + + # Parses a response from purchase_offsite_url once user is redirected back + # + # ==== Parameters + # + # * data -- All params when offsite payment returns + # e.g. returns to http://company.com/return?a=1&b=2, then input "a=1&b=2" + def purchase_offsite_response(data) + requires!(@options, :secure_hash) + + response_hash = parse(data) + + expected_secure_hash = calculate_secure_hash(response_hash.reject{|k, v| k == :SecureHash}, @options[:secure_hash]) + unless response_hash[:SecureHash] == expected_secure_hash + raise SecurityError, "Secure Hash mismatch, response may be tampered with" + end + + response_object(response_hash) + end + + def test? + @options[:login].start_with?('TEST') + end + + private + + def add_advanced_user(post) + post[:User] = @options[:advanced_login] + post[:Password] = @options[:advanced_password] + end + + def add_invoice(post, options) + post[:OrderInfo] = options[:order_id] + end + + def add_creditcard(post, creditcard) + post[:CardNum] = creditcard.number + post[:CardSecurityCode] = creditcard.verification_value if creditcard.verification_value? + post[:CardExp] = format(creditcard.year, :two_digits) + format(creditcard.month, :two_digits) + end + + def add_creditcard_type(post, card_type) + post[:Gateway] = 'ssl' + post[:card] = CARD_TYPES.detect{|ct| ct.am_code == card_type}.migs_long_code + end + + def parse(body) + params = CGI::parse(body) + hash = {} + params.each do |key, value| + hash[key.gsub('vpc_', '').to_sym] = value[0] + end + hash + end + + def commit(post) + data = ssl_post self.merchant_hosted_url, post_data(post) + response_hash = parse(data) + response_object(response_hash) + end + + def response_object(response) + Response.new(success?(response), response[:Message], response, + :test => test?, + :authorization => response[:TransactionNo], + :fraud_review => fraud_review?(response), + :avs_result => { :code => response[:AVSResultCode] }, + :cvv_result => response[:CSCResultCode] + ) + end + + def success?(response) + response[:TxnResponseCode] == '0' + end + + def fraud_review?(response) + ISSUER_RESPONSE_CODES[response[:AcqResponseCode]] == 'Suspected Fraud' + end + + def add_standard_parameters(action, post, unique_id = nil) + post.merge!( + :Version => API_VERSION, + :Merchant => @options[:login], + :AccessCode => @options[:password], + :Command => action, + :MerchTxnRef => unique_id || generate_unique_id.slice(0, 40) + ) + end + + def post_data(post) + post.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def add_secure_hash(post) + post[:SecureHash] = calculate_secure_hash(post, @options[:secure_hash]) + end + + def calculate_secure_hash(post, secure_hash) + sorted_values = post.sort_by(&:to_s).map(&:last) + input = secure_hash + sorted_values.join + Digest::MD5.hexdigest(input).upcase + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs/migs_codes.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs/migs_codes.rb new file mode 100644 index 000000000..9eae5f4bd --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/migs/migs_codes.rb @@ -0,0 +1,100 @@ +module ActiveMerchant + module Billing + module MigsCodes + TXN_RESPONSE_CODES = { + '?' => 'Response Unknown', + '0' => 'Transaction Successful', + '1' => 'Transaction Declined - Bank Error', + '2' => 'Bank Declined Transaction', + '3' => 'Transaction Declined - No Reply from Bank', + '4' => 'Transaction Declined - Expired Card', + '5' => 'Transaction Declined - Insufficient funds', + '6' => 'Transaction Declined - Error Communicating with Bank', + '7' => 'Payment Server Processing Error - Typically caused by invalid input data such as an invalid credit card number. Processing errors can also occur', + '8' => 'Transaction Declined - Transaction Type Not Supported', + '9' => 'Bank Declined Transaction (Do not contact Bank)', + 'A' => 'Transaction Aborted', + 'C' => 'Transaction Cancelled', + 'D' => 'Deferred Transaction', + 'E' => 'Issuer Returned a Referral Response', + 'F' => '3D Secure Authentication Failed', + 'I' => 'Card Security Code Failed', + 'L' => 'Shopping Transaction Locked (This indicates that there is another transaction taking place using the same shopping transaction number)', + 'N' => 'Cardholder is not enrolled in 3D Secure (Authentication Only)', + 'P' => 'Transaction is Pending', + 'R' => 'Retry Limits Exceeded, Transaction Not Processed', + 'S' => 'Duplicate OrderInfo used. (This is only relevant for Payment Servers that enforce the uniqueness of this field)', + 'U' => 'Card Security Code Failed' + } + + ISSUER_RESPONSE_CODES = { + '00' => 'Approved', + '01' => 'Refer to Card Issuer', + '02' => 'Refer to Card Issuer', + '03' => 'Invalid Merchant', + '04' => 'Pick Up Card', + '05' => 'Do Not Honor', + '07' => 'Pick Up Card', + '12' => 'Invalid Transaction', + '14' => 'Invalid Card Number (No such Number)', + '15' => 'No Such Issuer', + '33' => 'Expired Card', + '34' => 'Suspected Fraud', + '36' => 'Restricted Card', + '39' => 'No Credit Account', + '41' => 'Card Reported Lost', + '43' => 'Stolen Card', + '51' => 'Insufficient Funds', + '54' => 'Expired Card', + '57' => 'Transaction Not Permitted', + '59' => 'Suspected Fraud', + '62' => 'Restricted Card', + '65' => 'Exceeds withdrawal frequency limit', + '91' => 'Cannot Contact Issuer' + } + + VERIFIED_3D_CODES = { + 'Y' => 'The cardholder was successfully authenticated.', + 'E' => 'The cardholder is not enrolled.', + 'N' => 'The cardholder was not verified.', + 'U' => 'The cardholder\'s Issuer was unable to authenticate due to a system error at the Issuer.', + 'F' => 'An error exists in the format of the request from the merchant. For example, the request did not contain all required fields, or the format of some fields was invalid.', + 'A' => 'Authentication of your Merchant ID and Password to the Directory Server Failed (see "What does a Payment Authentication Status of "A" mean?" on page 85).', + 'D' => 'Error communicating with the Directory Server, for example, the Payment Server could not connect to the directory server or there was a versioning mismatch.', + 'C' => 'The card type is not supported for authentication.', + 'M' => 'This indicates that attempts processing was used. Verification is marked with status M - ACS attempts processing used. Payment is performed with authentication. Attempts is when a cardholder has successfully passed the directory server but decides not to continue with the authentication process and cancels.', + 'S' => 'The signature on the response received from the Issuer could not be validated. This should be considered a failure.', + 'T' => 'ACS timed out. The Issuer\'s ACS did not respond to the Authentication request within the time out period.', + 'P' => 'Error parsing input from Issuer.', + 'I' => 'Internal Payment Server system error. This could be caused by a temporary DB failure or an error in the security module or by some error in an internal system.' + } + + class CreditCardType + attr_accessor :am_code, :migs_code, :migs_long_code, :name + def initialize(am_code, migs_code, migs_long_code, name) + @am_code = am_code + @migs_code = migs_code + @migs_long_code = migs_long_code + @name = name + end + end + + CARD_TYPES = [ + # The following are 4 different representations of credit card types + # am_code: The active merchant code + # migs_code: Used in response for purchase/authorize/status + # migs_long_code: Used to pre-select card for server_purchase_url + # name: The nice display name + %w(american_express AE Amex American\ Express), + %w(diners_club DC Dinersclub Diners\ Club), + %w(jcb JC JCB JCB\ Card), + %w(maestro MS Maestro Maestro\ Card), + %w(master MC Mastercard MasterCard), + %w(na PL PrivateLabelCard Private\ Label\ Card), + %w(visa VC Visa Visa\ Card') + ].map do |am_code, migs_code, migs_long_code, name| + CreditCardType.new(am_code, migs_code, migs_long_code, name) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments.rb new file mode 100644 index 000000000..91f059017 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments.rb @@ -0,0 +1,37 @@ +require File.dirname(__FILE__) + '/modern_payments_cim' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ModernPaymentsGateway < Gateway + self.supported_countries = ModernPaymentsCimGateway.supported_countries + self.supported_cardtypes = ModernPaymentsCimGateway.supported_cardtypes + self.homepage_url = ModernPaymentsCimGateway.homepage_url + self.display_name = ModernPaymentsCimGateway.display_name + + self.abstract_class = true + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card, options = {}) + customer_response = cim.create_customer(options) + return customer_response unless customer_response.success? + + customer_id = customer_response.params["create_customer_result"] + + card_response = cim.modify_customer_credit_card(customer_id, credit_card) + return card_response unless card_response.success? + + cim.authorize_credit_card_payment(customer_id, money) + end + + private + def cim + @cim ||= ModernPaymentsCimGateway.new(@options) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments_cim.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments_cim.rb new file mode 100644 index 000000000..756055ad7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/modern_payments_cim.rb @@ -0,0 +1,219 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ModernPaymentsCimGateway < Gateway #:nodoc: + self.test_url = "https://secure.modpay.com/netservices/test/ModpayTest.asmx" + self.live_url = 'https://secure.modpay.com/ws/modpay.asmx' + + LIVE_XMLNS = "https://secure.modpay.com/ws/" + TEST_XMLNS = "https://secure.modpay.com/netservices/test/" + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.modpay.com' + self.display_name = 'Modern Payments' + + SUCCESS_MESSAGE = "Transaction accepted" + FAILURE_MESSAGE = "Transaction failed" + ERROR_MESSAGE = "Transaction error" + + PAYMENT_METHOD = { + :check => 1, + :credit_card => 2 + } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def create_customer(options = {}) + post = {} + add_customer_data(post, options) + add_address(post, options) + + commit('CreateCustomer', post) + end + + def modify_customer_credit_card(customer_id, credit_card) + raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank? + + post = {} + add_customer_id(post, customer_id) + add_credit_card(post, credit_card) + + commit('ModifyCustomerCreditCard', post) + end + + def authorize_credit_card_payment(customer_id, amount) + raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank? + + post = {} + add_customer_id(post, customer_id) + add_amount(post, amount) + + commit('AuthorizeCreditCardPayment', post) + end + + def create_payment(customer_id, amount, options = {}) + raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank? + + post = {} + add_customer_id(post, customer_id) + add_amount(post, amount) + add_payment_details(post, options) + + commit('CreatePayment', post) + end + + private + def add_payment_details(post, options) + post[:pmtDate] = (options[:payment_date] || Time.now.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + post[:pmtType] = PAYMENT_METHOD[options[:payment_method] || :credit_card] + end + + def add_amount(post, money) + post[:pmtAmount] = amount(money) + end + + def add_customer_id(post, customer_id) + post[:custId] = customer_id + end + + def add_customer_data(post, options) + post[:acctNum] = options[:customer] + end + + def add_address(post, options) + address = options[:billing_address] || options[:address] || {} + + if name = address[:name] + segments = name.split(' ') + post[:lastName] = segments.pop + post[:firstName] = segments.join(' ') + else + post[:firstName] = address[:first_name] + post[:lastName] = address[:last_name] + end + + post[:address] = address[:address1] + post[:city] = address[:city] + post[:state] = address[:state] + post[:zip] = address[:zip] + post[:phone] = address[:phone] + post[:fax] = address[:fax] + post[:email] = options[:email] + end + + def add_credit_card(post, credit_card) + post[:ccName] = credit_card.name + post[:ccNum] = credit_card.number + post[:expMonth] = credit_card.month + post[:expYear] = credit_card.year + end + + def build_request(action, params) + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! 'env:Envelope', + { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } do + + xml.tag! 'env:Body' do + xml.tag! action, { "xmlns" => xmlns(action) } do + xml.tag! "clientId", @options[:login] + xml.tag! "clientCode", @options[:password] + params.each {|key, value| xml.tag! key, value } + end + end + end + xml.target! + end + + def xmlns(action) + if test? && action == 'AuthorizeCreditCardPayment' + TEST_XMLNS + else + LIVE_XMLNS + end + end + + def url(action) + if test? && action == 'AuthorizeCreditCardPayment' + self.test_url + else + self.live_url + end + end + + def commit(action, params) + data = ssl_post(url(action), build_request(action, params), + { 'Content-Type' =>'text/xml; charset=utf-8', + 'SOAPAction' => "#{xmlns(action)}#{action}" } + ) + + response = parse(action, data) + Response.new(successful?(action, response), message_from(action, response), response, + :test => test?, + :authorization => authorization_from(action, response), + :avs_result => { :code => response[:avs_code] } + ) + end + + def authorization_from(action, response) + response[authorization_key(action)] + end + + def authorization_key(action) + action == "AuthorizeCreditCardPayment" ? :trans_id : "#{action.underscore}_result".to_sym + end + + def successful?(action, response) + key = authorization_key(action) + + if key == :trans_id + response[:approved] == "true" + else + response[key].to_i > 0 + end + end + + def message_from(action, response) + if response[:faultcode] + ERROR_MESSAGE + elsif successful?(action, response) + SUCCESS_MESSAGE + else + FAILURE_MESSAGE + end + end + + def parse(action, xml) + response = {} + response[:action] = action + + xml = REXML::Document.new(xml) + if root = REXML::XPath.first(xml, "//#{action}Response") + root.elements.to_a.each do |node| + parse_element(response, node) + end + elsif root = REXML::XPath.first(xml, "//soap:Fault") + root.elements.to_a.each do |node| + response[node.name.underscore.to_sym] = node.text + end + end + + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each{|e| parse_element(response, e) } + else + response[node.name.underscore.to_sym] = node.text.to_s.strip + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris.rb new file mode 100644 index 000000000..e254b9489 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris.rb @@ -0,0 +1,244 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + + # To learn more about the Moneris gateway, please contact + # eselectplus@moneris.com for a copy of their integration guide. For + # information on remote testing, please see "Test Environment Penny Value + # Response Table", and "Test Environment eFraud (AVS and CVD) Penny + # Response Values", available at Moneris' {eSelect Plus Documentation + # Centre}[https://www3.moneris.com/connect/en/documents/index.html]. + class MonerisGateway < Gateway + self.test_url = 'https://esqa.moneris.com/gateway2/servlet/MpgRequest' + self.live_url = 'https://www3.moneris.com/gateway2/servlet/MpgRequest' + + self.supported_countries = ['CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover] + self.homepage_url = 'http://www.moneris.com/' + self.display_name = 'Moneris' + + # login is your Store ID + # password is your API Token + def initialize(options = {}) + requires!(options, :login, :password) + options = { :crypt_type => 7 }.merge(options) + super + end + + # Referred to as "PreAuth" in the Moneris integration guide, this action + # verifies and locks funds on a customer's card, which then must be + # captured at a later date. + # + # Pass in +order_id+ and optionally a +customer+ parameter. + def authorize(money, creditcard_or_datakey, options = {}) + requires!(options, :order_id) + post = {} + add_payment_source(post, creditcard_or_datakey) + post[:amount] = amount(money) + post[:order_id] = options[:order_id] + post[:cust_id] = options[:customer] + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + action = (post[:data_key].blank?) ? 'preauth' : 'res_preauth_cc' + commit(action, post) + end + + # This action verifies funding on a customer's card and readies them for + # deposit in a merchant's account. + # + # Pass in order_id and optionally a customer parameter + def purchase(money, creditcard_or_datakey, options = {}) + requires!(options, :order_id) + post = {} + add_payment_source(post, creditcard_or_datakey) + post[:amount] = amount(money) + post[:order_id] = options[:order_id] + post[:cust_id] = options[:customer] + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + action = (post[:data_key].blank?) ? 'purchase' : 'res_purchase_cc' + commit(action, post) + end + + # This method retrieves locked funds from a customer's account (from a + # PreAuth) and prepares them for deposit in a merchant's account. + # + # Note: Moneris requires both the order_id and the transaction number of + # the original authorization. To maintain the same interface as the other + # gateways the two numbers are concatenated together with a ; separator as + # the authorization number returned by authorization + def capture(money, authorization, options = {}) + commit 'completion', crediting_params(authorization, :comp_amount => amount(money)) + end + + # Voiding cancels an open authorization. + # + # Concatenate your transaction number and order_id by using a semicolon + # (';'). This is to keep the Moneris interface consistent with other + # gateways. (See +capture+ for details.) + def void(authorization, options = {}) + capture(0, authorization, options) + end + + # Performs a refund. This method requires that the original transaction + # number and order number be included. Concatenate your transaction + # number and order_id by using a semicolon (';'). This is to keep the + # Moneris interface consistent with other gateways. (See +capture+ for + # details.) + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def refund(money, authorization, options = {}) + commit 'refund', crediting_params(authorization, :amount => amount(money)) + end + + def store(credit_card, options = {}) + post = {} + post[:pan] = credit_card.number + post[:expdate] = expdate(credit_card) + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + commit('res_add_cc', post) + end + + def unstore(data_key) + post = {} + post[:data_key] = data_key + commit('res_delete', post) + end + + def update(data_key, credit_card, options = {}) + post = {} + post[:pan] = credit_card.number + post[:expdate] = expdate(credit_card) + post[:data_key] = data_key + post[:crypt_type] = options[:crypt_type] || @options[:crypt_type] + commit('res_update_cc', post) + end + + private # :nodoc: all + + def expdate(creditcard) + sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month) + end + + def add_payment_source(post, source) + if source.is_a?(String) + post[:data_key] = source + else + post[:pan] = source.number + post[:expdate] = expdate(source) + end + end + + # Common params used amongst the +credit+, +void+ and +capture+ methods + def crediting_params(authorization, options = {}) + { + :txn_number => split_authorization(authorization).first, + :order_id => split_authorization(authorization).last, + :crypt_type => options[:crypt_type] || @options[:crypt_type] + }.merge(options) + end + + # Splits an +authorization+ param and retrives the order id and + # transaction number in that order. + def split_authorization(authorization) + if authorization.nil? || authorization.empty? || authorization !~ /;/ + raise ArgumentError, 'You must include a valid authorization code (e.g. "1234;567")' + else + authorization.split(';') + end + end + + def commit(action, parameters = {}) + response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters))) + + Response.new(successful?(response), message_from(response[:message]), response, + :test => test?, + :authorization => authorization_from(response) + ) + end + + # Generates a Moneris authorization string of the form 'trans_id;receipt_id'. + def authorization_from(response = {}) + if response[:trans_id] && response[:receipt_id] + "#{response[:trans_id]};#{response[:receipt_id]}" + end + end + + # Tests for a successful response from Moneris' servers + def successful?(response) + response[:response_code] && + response[:complete] && + (0..49).include?(response[:response_code].to_i) + end + + def parse(xml) + response = { :message => "Global Error Receipt", :complete => false } + hashify_xml!(xml, response) + response + end + + def hashify_xml!(xml, response) + xml = REXML::Document.new(xml) + return if xml.root.nil? + xml.elements.each('//receipt/*') do |node| + response[node.name.underscore.to_sym] = normalize(node.text) + end + end + + def post_data(action, parameters = {}) + xml = REXML::Document.new + root = xml.add_element("request") + root.add_element("store_id").text = options[:login] + root.add_element("api_token").text = options[:password] + transaction = root.add_element(action) + + # Must add the elements in the correct order + actions[action].each do |key| + transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank? + end + + xml.to_s + end + + def message_from(message) + return 'Unspecified error' if message.blank? + message.gsub(/[^\w]/, ' ').split.join(" ").capitalize + end + + # Make a Ruby type out of the response string + def normalize(field) + case field + when "true" then true + when "false" then false + when '', "null" then nil + else field + end + end + + def actions + { + "purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + "preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + "command" => [:order_id], + "refund" => [:order_id, :amount, :txn_number, :crypt_type], + "indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + "completion" => [:order_id, :comp_amount, :txn_number, :crypt_type], + "purchasecorrection" => [:order_id, :txn_number, :crypt_type], + "cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav], + "cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], + "transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + "Batchcloseall" => [], + "opentotals" => [:ecr_number], + "batchclose" => [:ecr_number], + "res_add_cc" => [:pan, :expdate, :crypt_type], + "res_delete" => [:data_key], + "res_update_cc" => [:data_key, :pan, :expdate, :crypt_type], + "res_purchase_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type], + "res_preauth_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type] + } + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris_us.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris_us.rb new file mode 100644 index 000000000..92feec273 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/moneris_us.rb @@ -0,0 +1,208 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + + # To learn more about the Moneris (US) gateway, please contact + # ussales@moneris.com for a copy of their integration guide. For + # information on remote testing, please see "Test Environment Penny Value + # Response Table", and "Test Environment eFraud (AVS and CVD) Penny + # Response Values", available at Moneris' {eSelect Plus Documentation + # Centre}[https://www3.moneris.com/connect/en/documents/index.html]. + class MonerisUsGateway < Gateway + self.test_url = 'https://esplusqa.moneris.com/gateway_us/servlet/MpgRequest' + self.live_url = 'https://esplus.moneris.com/gateway_us/servlet/MpgRequest' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover] + self.homepage_url = 'http://www.monerisusa.com/' + self.display_name = 'Moneris (US)' + + # login is your Store ID + # password is your API Token + def initialize(options = {}) + requires!(options, :login, :password) + options = { :crypt_type => 7 }.merge(options) + super + end + + # Referred to as "PreAuth" in the Moneris integration guide, this action + # verifies and locks funds on a customer's card, which then must be + # captured at a later date. + # + # Pass in +order_id+ and optionally a +customer+ parameter. + def authorize(money, creditcard, options = {}) + debit_commit 'us_preauth', money, creditcard, options + end + + # This action verifies funding on a customer's card, and readies them for + # deposit in a merchant's account. + # + # Pass in order_id and optionally a customer parameter + def purchase(money, creditcard, options = {}) + debit_commit 'us_purchase', money, creditcard, options + end + + # This method retrieves locked funds from a customer's account (from a + # PreAuth) and prepares them for deposit in a merchant's account. + # + # Note: Moneris requires both the order_id and the transaction number of + # the original authorization. To maintain the same interface as the other + # gateways the two numbers are concatenated together with a ; separator as + # the authorization number returned by authorization + def capture(money, authorization, options = {}) + commit 'us_completion', crediting_params(authorization, :comp_amount => amount(money)) + end + + # Voiding requires the original transaction ID and order ID of some open + # transaction. Closed transactions must be refunded. Note that the only + # methods which may be voided are +capture+ and +purchase+. + # + # Concatenate your transaction number and order_id by using a semicolon + # (';'). This is to keep the Moneris interface consistent with other + # gateways. (See +capture+ for details.) + def void(authorization, options = {}) + commit 'us_purchasecorrection', crediting_params(authorization) + end + + # Performs a refund. This method requires that the original transaction + # number and order number be included. Concatenate your transaction + # number and order_id by using a semicolon (';'). This is to keep the + # Moneris interface consistent with other gateways. (See +capture+ for + # details.) + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def refund(money, authorization, options = {}) + commit 'us_refund', crediting_params(authorization, :amount => amount(money)) + end + + private # :nodoc: all + + def expdate(creditcard) + sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month) + end + + def debit_commit(commit_type, money, creditcard, options) + requires!(options, :order_id) + commit(commit_type, debit_params(money, creditcard, options)) + end + + # Common params used amongst the +purchase+ and +authorization+ methods + def debit_params(money, creditcard, options = {}) + { + :order_id => options[:order_id], + :cust_id => options[:customer], + :amount => amount(money), + :pan => creditcard.number, + :expdate => expdate(creditcard), + :crypt_type => options[:crypt_type] || @options[:crypt_type] + } + end + + # Common params used amongst the +credit+, +void+ and +capture+ methods + def crediting_params(authorization, options = {}) + { + :txn_number => split_authorization(authorization).first, + :order_id => split_authorization(authorization).last, + :crypt_type => options[:crypt_type] || @options[:crypt_type] + }.merge(options) + end + + # Splits an +authorization+ param and retrives the order id and + # transaction number in that order. + def split_authorization(authorization) + if authorization.nil? || authorization.empty? || authorization !~ /;/ + raise ArgumentError, 'You must include a valid authorization code (e.g. "1234;567")' + else + authorization.split(';') + end + end + + def commit(action, parameters = {}) + response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters))) + + Response.new(successful?(response), message_from(response[:message]), response, + :test => test?, + :authorization => authorization_from(response) + ) + end + + # Generates a Moneris authorization string of the form 'trans_id;receipt_id'. + def authorization_from(response = {}) + if response[:trans_id] && response[:receipt_id] + "#{response[:trans_id]};#{response[:receipt_id]}" + end + end + + # Tests for a successful response from Moneris' servers + def successful?(response) + response[:response_code] && + response[:complete] && + (0..49).include?(response[:response_code].to_i) + end + + def parse(xml) + response = { :message => "Global Error Receipt", :complete => false } + hashify_xml!(xml, response) + response + end + + def hashify_xml!(xml, response) + xml = REXML::Document.new(xml) + return if xml.root.nil? + xml.elements.each('//receipt/*') do |node| + response[node.name.underscore.to_sym] = normalize(node.text) + end + end + + def post_data(action, parameters = {}) + xml = REXML::Document.new + root = xml.add_element("request") + root.add_element("store_id").text = options[:login] + root.add_element("api_token").text = options[:password] + transaction = root.add_element(action) + + # Must add the elements in the correct order + actions[action].each do |key| + transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank? + end + + xml.to_s + end + + def message_from(message) + return 'Unspecified error' if message.blank? + message.gsub(/[^\w]/, ' ').split.join(" ").capitalize + end + + # Make a Ruby type out of the response string + def normalize(field) + case field + when "true" then true + when "false" then false + when '', "null" then nil + else field + end + end + + def actions + { + "us_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + "us_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + "us_refund" => [:order_id, :amount, :txn_number, :crypt_type], + "us_ind_refund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type], + "us_completion" => [:order_id, :comp_amount, :txn_number, :crypt_type], + "us_purchasecorrection" => [:order_id, :txn_number, :crypt_type], + "us_cavv_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], + "us_cavv_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv], + "us_batchcloseall" => [], + "us_opentotals" => [:ecr_number], + "us_batchclose" => [:ecr_number] + } + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nab_transact.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nab_transact.rb new file mode 100644 index 000000000..39e324f40 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nab_transact.rb @@ -0,0 +1,269 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # The National Australia Bank provide a payment gateway that seems to + # be a rebadged Securepay Australia service, though some differences exist. + class NabTransactGateway < Gateway + API_VERSION = 'xml-4.2' + PERIODIC_API_VERSION = "spxml-4.2" + + class_attribute :test_periodic_url, :live_periodic_url + + self.test_url = 'https://transact.nab.com.au/test/xmlapi/payment' + self.live_url = 'https://transact.nab.com.au/live/xmlapi/payment' + self.test_periodic_url = 'https://transact.nab.com.au/xmlapidemo/periodic' + self.live_periodic_url = 'https://transact.nab.com.au/xmlapi/periodic' + + self.supported_countries = ['AU'] + + # The card types supported by the payment gateway + # Note that support for Diners, Amex, and JCB require extra + # steps in setting up your account, as detailed in the NAB Transact API + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + + self.homepage_url = 'http://transact.nab.com.au' + self.display_name = 'NAB Transact' + + cattr_accessor :request_timeout + self.request_timeout = 60 + + self.money_format = :cents + self.default_currency = 'AUD' + + #Transactions currently accepted by NAB Transact XML API + TRANSACTIONS = { + :purchase => 0, #Standard Payment + :refund => 4, #Refund + :void => 6, #Client Reversal (Void) + :authorization => 10, #Preauthorise + :capture => 11 #Preauthorise Complete (Advice) + } + + PERIODIC_TYPES = { + :addcrn => 5, + :editcrn => 5, + :deletecrn => 5, + :trigger => 8 + } + + SUCCESS_CODES = [ '00', '08', '11', '16', '77' ] + + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card_or_stored_id, options = {}) + if credit_card_or_stored_id.respond_to?(:number) + #Credit card for instant payment + commit :purchase, build_purchase_request(money, credit_card_or_stored_id, options) + else + #Triggered payment for an existing stored credit card + options[:billing_id] = credit_card_or_stored_id.to_s + commit_periodic build_periodic_item(:trigger, money, nil, options) + end + end + + def refund(money, authorization, options = {}) + commit :refund, build_reference_request(money, authorization) + end + + def store(creditcard, options = {}) + requires!(options, :billing_id, :amount) + commit_periodic(build_periodic_item(:addcrn, options[:amount], creditcard, options)) + end + + def unstore(identification, options = {}) + options[:billing_id] = identification + commit_periodic(build_periodic_item(:deletecrn, options[:amount], nil, options)) + end + + private + + def add_metadata(xml, options) + if options[:merchant_name] || options[:merchant_location] + xml.tag! 'metadata' do + xml.tag! 'meta', :name => 'ca_name', :value => options[:merchant_name] if options[:merchant_name] + xml.tag! 'meta', :name => 'ca_location', :value => options[:merchant_location] if options[:merchant_location] + end + end + end + + def build_purchase_request(money, credit_card, options) + xml = Builder::XmlMarkup.new + xml.tag! 'amount', amount(money) + xml.tag! 'currency', options[:currency] || currency(money) + xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '') + + xml.tag! 'CreditCardInfo' do + xml.tag! 'cardNumber', credit_card.number + xml.tag! 'expiryDate', expdate(credit_card) + xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? + end + + add_metadata(xml, options) + + xml.target! + end + + def build_reference_request(money, reference) + xml = Builder::XmlMarkup.new + + transaction_id, order_id, preauth_id, original_amount = reference.split('*') + + xml.tag! 'amount', (money ? amount(money) : original_amount) + xml.tag! 'currency', options[:currency] || currency(money) + xml.tag! 'txnID', transaction_id + xml.tag! 'purchaseOrderNo', order_id + xml.tag! 'preauthID', preauth_id + + xml.target! + end + + #Generate payment request XML + # - API is set to allow multiple Txn's but currentlu only allows one + # - txnSource = 23 - (XML) + def build_request(action, body) + xml = Builder::XmlMarkup.new + xml.instruct! + xml.tag! 'NABTransactMessage' do + xml.tag! 'MessageInfo' do + xml.tag! 'messageID', Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageTimestamp', generate_timestamp + xml.tag! 'timeoutValue', request_timeout + xml.tag! 'apiVersion', API_VERSION + end + + xml.tag! 'MerchantInfo' do + xml.tag! 'merchantID', @options[:login] + xml.tag! 'password', @options[:password] + end + + xml.tag! 'RequestType', 'Payment' + xml.tag! 'Payment' do + xml.tag! 'TxnList', "count" => 1 do + xml.tag! 'Txn', "ID" => 1 do + xml.tag! 'txnType', TRANSACTIONS[action] + xml.tag! 'txnSource', 23 + xml << body + end + end + end + end + + xml.target! + end + + def build_periodic_item(action, money, credit_card, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'actionType', action.to_s + xml.tag! 'periodicType', PERIODIC_TYPES[action] if PERIODIC_TYPES[action] + xml.tag! 'currency', options[:currency] || currency(money) + xml.tag! 'crn', options[:billing_id] + + if credit_card + xml.tag! 'CreditCardInfo' do + xml.tag! 'cardNumber', credit_card.number + xml.tag! 'expiryDate', expdate(credit_card) + xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? + end + end + xml.tag! 'amount', amount(money) + + xml.target! + end + + def build_periodic_request(body) + xml = Builder::XmlMarkup.new + xml.instruct! + xml.tag! 'NABTransactMessage' do + xml.tag! 'MessageInfo' do + xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageTimestamp', generate_timestamp + xml.tag! 'timeoutValue', request_timeout + xml.tag! 'apiVersion', PERIODIC_API_VERSION + end + + xml.tag! 'MerchantInfo' do + xml.tag! 'merchantID', @options[:login] + xml.tag! 'password', @options[:password] + end + + xml.tag! 'RequestType', 'Periodic' + xml.tag! 'Periodic' do + xml.tag! 'PeriodicList', "count" => 1 do + xml.tag! 'PeriodicItem', "ID" => 1 do + xml << body + end + end + end + end + + xml.target! + end + + def commit(action, request) + response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(action, request))) + + Response.new(success?(response), message_from(response), response, + :test => test?, + :authorization => authorization_from(response) + ) + end + + def commit_periodic(request) + response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, build_periodic_request(request))) + Response.new(success?(response), message_from(response), response, + :test => test?, + :authorization => authorization_from(response) + ) + end + + def success?(response) + SUCCESS_CODES.include?(response[:response_code]) + end + + def authorization_from(response) + [response[:txn_id], response[:purchase_order_no], response[:preauth_id], response[:amount]].join('*') + end + + def message_from(response) + response[:response_text] || response[:status_description] + end + + def expdate(credit_card) + "#{format(credit_card.month, :two_digits)}/#{format(credit_card.year, :two_digits)}" + end + + def parse(body) + xml = REXML::Document.new(body) + + response = {} + + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each{|element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + # YYYYDDMMHHNNSSKKK000sOOO + def generate_timestamp + time = Time.now.utc + time.strftime("%Y%d%m%H%M%S#{time.usec}+000") + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/net_registry.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/net_registry.rb new file mode 100644 index 000000000..fddad0d16 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/net_registry.rb @@ -0,0 +1,193 @@ +module ActiveMerchant + module Billing + # Gateway for netregistry.com.au. + # + # Note that NetRegistry itself uses gateway service providers. At the + # time of this writing, there are at least two (Quest and Ingenico). + # This module has only been tested with Quest. + # + # Also note that NetRegistry does not offer a test mode, nor does it + # have support for the authorize/capture/void functionality by default + # (you may arrange for this as described in "Programming for + # NetRegistry's E-commerce Gateway." [http://rubyurl.com/hNG]), and no + # #void functionality is documented. As a result, the #authorize and + # #capture have not yet been tested through a live gateway, and #void + # will raise an error. + # + # If you have this functionality enabled, please consider contributing + # to ActiveMerchant by writing tests/code for these methods, and + # submitting a patch. + # + # In addition to the standard ActiveMerchant functionality, the + # response will contain a 'receipt' parameter + # (response.params['receipt']) if a receipt was issued by the gateway. + class NetRegistryGateway < Gateway + self.live_url = self.test_url = 'https://4tknox.au.com/cgi-bin/themerchant.au.com/ecom/external2.pl' + + FILTERED_PARAMS = [ 'card_no', 'card_expiry', 'receipt_array' ] + + self.supported_countries = ['AU'] + + # Note that support for Diners, Amex, and JCB require extra + # steps in setting up your account, as detailed in + # "Programming for NetRegistry's E-commerce Gateway." + # [http://rubyurl.com/hNG] + self.supported_cardtypes = [:visa, :master, :diners_club, :american_express, :jcb] + self.display_name = 'NetRegistry' + self.homepage_url = 'http://www.netregistry.com.au' + + TRANSACTIONS = { + :authorization => 'preauth', + :purchase => 'purchase', + :capture => 'completion', + :status => 'status', + :refund => 'refund' + } + + # Create a new NetRegistry gateway. + # + # Options :login and :password must be given. + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Note that #authorize and #capture only work if your account + # vendor is St George, and if your account has been setup as + # described in "Programming for NetRegistry's E-commerce + # Gateway." [http://rubyurl.com/hNG] + def authorize(money, credit_card, options = {}) + params = { + 'AMOUNT' => amount(money), + 'CCNUM' => credit_card.number, + 'CCEXP' => expiry(credit_card) + } + add_request_details(params, options) + commit(:authorization, params) + end + + # Note that #authorize and #capture only work if your account + # vendor is St George, and if your account has been setup as + # described in "Programming for NetRegistry's E-commerce + # Gateway." [http://rubyurl.com/hNG] + def capture(money, authorization, options = {}) + requires!(options, :credit_card) + credit_card = options[:credit_card] + + params = { + 'PREAUTHNUM' => authorization, + 'AMOUNT' => amount(money), + 'CCNUM' => credit_card.number, + 'CCEXP' => expiry(credit_card) + } + add_request_details(params, options) + commit(:capture, params) + end + + def purchase(money, credit_card, options = {}) + params = { + 'AMOUNT' => amount(money), + 'CCNUM' => credit_card.number, + 'CCEXP' => expiry(credit_card) + } + add_request_details(params, options) + commit(:purchase, params) + end + + def refund(money, identification, options = {}) + params = { + 'AMOUNT' => amount(money), + 'TXNREF' => identification + } + add_request_details(params, options) + commit(:refund, params) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + # Specific to NetRegistry. + # + # Run a 'status' command. This lets you view the status of a + # completed transaction. + # + def status(identification) + params = { + 'TXNREF' => identification + } + + commit(:status, params) + end + + private + def add_request_details(params, options) + params['COMMENT'] = options[:description] unless options[:description].blank? + end + + # Return the expiry for the given creditcard in the required + # format for a command. + def expiry(credit_card) + month = format(credit_card.month, :two_digits) + year = format(credit_card.year , :two_digits) + "#{month}/#{year}" + end + + # Post the a request with the given parameters and return the + # response object. + # + # Login and password are added automatically, and the comment is + # omitted if nil. + def commit(action, params) + # get gateway response + response = parse( ssl_post(self.live_url, post_data(action, params)) ) + + Response.new(response['status'] == 'approved', message_from(response), response, + :authorization => authorization_from(response, action) + ) + end + + def post_data(action, params) + params['COMMAND'] = TRANSACTIONS[action] + params['LOGIN'] = "#{@options[:login]}/#{@options[:password]}" + URI.encode(params.map{|k,v| "#{k}=#{v}"}.join('&')) + end + + def parse(response) + params = {} + + lines = response.split("\n") + + # Just incase there is no real response returned + params['status'] = lines[0] + params['response_text'] = lines[1] + + started = false + lines.each do |line| + if started + key, val = line.chomp.split(/=/, 2) + params[key] = val unless FILTERED_PARAMS.include?(key) + end + + started = line.chomp =~ /^\.$/ unless started + end + + params + end + + def message_from(response) + response['response_text'] + end + + def authorization_from(response, command) + case command + when :purchase + response['txn_ref'] + when :authorization + response['transaction_no'] + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netaxept.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netaxept.rb new file mode 100644 index 000000000..3ee6443f5 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netaxept.rb @@ -0,0 +1,181 @@ +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class NetaxeptGateway < Gateway + self.test_url = 'https://epayment-test.bbs.no/' + self.live_url = 'https://epayment.bbs.no/' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['NO', 'DK', 'SE', 'FI'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.betalingsterminal.no/Netthandel-forside/' + + # The name of the gateway + self.display_name = 'BBS Netaxept' + + self.money_format = :cents + + self.default_currency = 'NOK' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, creditcard, options = {}) + requires!(options, :order_id) + + MultiResponse.run do |r| + r.process{authorize(money, creditcard, options)} + r.process{capture(money, r.authorization, options)} + end + end + + def authorize(money, creditcard, options = {}) + requires!(options, :order_id) + + MultiResponse.run do |r| + r.process{setup_transaction(money, options)} + r.process{add_and_auth_credit_card(r.authorization, creditcard, options)} + r.process{query_transaction(r.authorization, options)} + end + end + + def capture(money, authorization, options = {}) + post = {} + add_credentials(post, options) + add_authorization(post, authorization, money) + post[:operation] = "Capture" + commit("Netaxept/process.aspx", post) + end + + def refund(money, authorization, options = {}) + post = {} + add_credentials(post, options) + add_authorization(post, authorization, money) + post[:operation] = "Credit" + commit("Netaxept/process.aspx", post) + end + + def void(authorization, options = {}) + post = {} + add_credentials(post, options) + add_authorization(post, authorization) + post[:operation] = "Annul" + commit("Netaxept/process.aspx", post) + end + + private + + def setup_transaction(money, options) + post = {} + add_credentials(post, options) + add_order(post, money, options) + commit("Netaxept/Register.aspx", post) + end + + def add_and_auth_credit_card(authorization, creditcard, options) + post = {} + add_credentials(post, options, false) + add_authorization(post, authorization) + add_creditcard(post, creditcard) + commit("terminal/default.aspx", post, false) + end + + def query_transaction(authorization, options) + post = {} + add_credentials(post, options) + add_authorization(post, authorization) + commit("Netaxept/query.aspx", post) + end + + def add_credentials(post, options, secure=true) + post[:merchantId] = @options[:login] + post[:token] = @options[:password] if secure + end + + def add_authorization(post, authorization, money=nil) + post[:transactionId] = authorization + post[:transactionAmount] = amount(money) if money + end + + def add_order(post, money, options) + post[:serviceType] = 'M' + post[:orderNumber] = options[:order_id] + post[:amount] = amount(money) + post[:currencyCode] = (options[:currency] || currency(money)) + post[:autoAuth] = "true" + end + + def add_creditcard(post, options) + post[:pan] = options.number + post[:expiryDate] = format(options.month, :two_digits) + format(options.year, :two_digits) + post[:securityCode] = options.verification_value + end + + def commit(path, parameters, xml=true) + raw = parse(ssl_get(build_url(path, parameters)), xml) + + success = false + authorization = (raw["TransactionId"] || parameters[:transactionId]) + if raw[:container] =~ /Exception|Error/ + message = (raw["Message"] || raw["Error"]["Message"]) + elsif raw["Error"] && !raw["Error"].empty? + message = (raw["Error"]["ResponseText"] || raw["Error"]["ResponseCode"]) + else + message = (raw["ResponseText"] || raw["ResponseCode"] || "OK") + success = true + end + + Response.new( + success, + message, + raw, + :test => test?, + :authorization => authorization + ) + end + + def parse(result, expects_xml=true) + if expects_xml + doc = REXML::Document.new(result) + extract_xml(doc.root).merge(:container => doc.root.name) + else + {:result => result} + end + end + + def extract_xml(element) + if element.has_elements? + hash = {} + element.elements.each do |e| + hash[e.name] = extract_xml(e) + end + hash + else + element.text + end + end + + def build_url(base, parameters=nil) + url = (test? ? self.test_url : self.live_url).dup + url << base + if parameters + url << '?' + url << encode(parameters) + end + url + end + + def encode(hash) + hash.collect{|(k,v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join('&') + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netbilling.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netbilling.rb new file mode 100644 index 000000000..6488d54d1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netbilling.rb @@ -0,0 +1,197 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class NetbillingGateway < Gateway + self.live_url = self.test_url = 'https://secure.netbilling.com:1402/gw/sas/direct3.1' + + TRANSACTIONS = { + :authorization => 'A', + :purchase => 'S', + :refund => 'R', + :credit => 'C', + :capture => 'D', + :void => 'U' + } + + SUCCESS_CODES = [ '1', 'T' ] + SUCCESS_MESSAGE = 'The transaction was approved' + FAILURE_MESSAGE = 'The transaction failed' + TEST_LOGIN = '104901072025' + + self.display_name = 'NETbilling' + self.homepage_url = 'http://www.netbilling.com' + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + def initialize(options = {}) + requires!(options, :login) + super + end + + def authorize(money, credit_card, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, credit_card, options) + add_customer_data(post, options) + + commit(:authorization, post) + end + + def purchase(money, credit_card, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, credit_card, options) + add_customer_data(post, options) + + commit(:purchase, post) + end + + def capture(money, authorization, options = {}) + post = {} + add_reference(post, authorization) + commit(:capture, post) + end + + def refund(money, source, options = {}) + post = {} + add_amount(post, money) + add_reference(post, source) + commit(:refund, post) + end + + def credit(money, credit_card, options = {}) + post = {} + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, credit_card, options) + add_customer_data(post, options) + + commit(:credit, post) + end + + def void(source, options = {}) + post = {} + add_reference(post, source) + commit(:void, post) + end + + def test? + (@options[:login] == TEST_LOGIN || super) + end + + private + + def add_amount(post, money) + post[:amount] = amount(money) + end + + def add_reference(post, reference) + post[:orig_id] = reference + end + + def add_customer_data(post, options) + post[:cust_email] = options[:email] + post[:cust_ip] = options[:ip] + end + + def add_address(post, credit_card, options) + if billing_address = options[:billing_address] || options[:address] + post[:bill_street] = billing_address[:address1] + post[:cust_phone] = billing_address[:phone] + post[:bill_zip] = billing_address[:zip] + post[:bill_city] = billing_address[:city] + post[:bill_country] = billing_address[:country] + post[:bill_state] = billing_address[:state] + end + + if shipping_address = options[:shipping_address] + first_name, last_name = parse_first_and_last_name(shipping_address[:name]) + + post[:ship_name1] = first_name + post[:ship_name2] = last_name + post[:ship_street] = shipping_address[:address1] + post[:ship_zip] = shipping_address[:zip] + post[:ship_city] = shipping_address[:city] + post[:ship_country] = shipping_address[:country] + post[:ship_state] = shipping_address[:state] + end + end + + def add_invoice(post, options) + post[:description] = options[:description] + end + + def add_credit_card(post, credit_card) + post[:bill_name1] = credit_card.first_name + post[:bill_name2] = credit_card.last_name + post[:card_number] = credit_card.number + post[:card_expire] = expdate(credit_card) + post[:card_cvv2] = credit_card.verification_value + end + + def parse(body) + results = {} + body.split(/&/).each do |pair| + key,val = pair.split(/\=/) + results[key.to_sym] = CGI.unescape(val) + end + results + end + + def commit(action, parameters) + response = parse(ssl_post(self.live_url, post_data(action, parameters))) + + Response.new(success?(response), message_from(response), response, + :test => test_response?(response), + :authorization => response[:trans_id], + :avs_result => { :code => response[:avs_code]}, + :cvv_result => response[:cvv2_code] + ) + rescue ActiveMerchant::ResponseError => e + raise unless(e.response.code =~ /^[67]\d\d$/) + return Response.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) + end + + def test_response?(response) + !!(test? || response[:auth_msg] =~ /TEST/) + end + + def success?(response) + SUCCESS_CODES.include?(response[:status_code]) + end + + def message_from(response) + success?(response) ? SUCCESS_MESSAGE : (response[:auth_msg] || FAILURE_MESSAGE) + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def post_data(action, parameters = {}) + parameters[:account_id] = @options[:login] + parameters[:site_tag] = @options[:site_tag] if @options[:site_tag].present? + parameters[:pay_type] = 'C' + parameters[:tran_type] = TRANSACTIONS[action] + + parameters.reject{|k,v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def parse_first_and_last_name(value) + name = value.to_s.split(' ') + + last_name = name.pop || '' + first_name = name.join(' ') + [ first_name, last_name ] + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netpay.rb new file mode 100644 index 000000000..babf95425 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/netpay.rb @@ -0,0 +1,223 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # + # NETPAY Gateway + # + # Support for NETPAY's HTTP Connector payment gateway in Mexico. + # + # The gateway sends requests as HTTP POST and receives the response details + # in the HTTP header, making the process really rather simple. + # + # Support for calls to the authorize and capture methods have been included + # as per the Netpay manuals, however, your millage may vary with these + # methods. At the time of writing (January 2013) they were not fully + # supported by the production or test gateways. This situation is + # expected to change within a few weeks/months. + # + # Purchases can be cancelled (`#void`) only within 24 hours of the + # transaction. After this, a refund should be performed instead. + # + # In addition to the regular ActiveMerchant transaction options, NETPAY + # also supports a `:mode` parameter. This allows testing to be peformed + # in production and force specific results. + # + # * 'P' - Production + # * 'A' - Approved + # * 'D' - Declined + # * 'R' - Random (Approved or Declined) + # * 'T' - Test + # + # For example: + # + # response = @gateway.purchase(1000, card, :mode => 'D') + # response.success # false + # + class NetpayGateway < Gateway + self.test_url = 'http://200.57.87.243:8855' + self.live_url = 'https://suite.netpay.com.mx/acquirerprd' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['MX'] + + self.default_currency = 'MXN' + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.netpay.com.mx' + + # The name of the gateway + self.display_name = 'NETPAY Gateway' + + CURRENCY_CODES = { + "MXN" => '484' + } + + # The header keys that we will provide in the response params hash + RESPONSE_KEYS = ['ResponseMsg', 'ResponseText', 'ResponseCode', 'TimeIn', 'TimeOut', 'AuthCode', 'OrderId', 'CardTypeName', 'MerchantId', 'IssuerAuthDate'] + + def initialize(options = {}) + requires!(options, :store_id, :login, :password) + super + end + + # Send an authorization request + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_customer_data(post, options) + add_amount(post, money, options) + + commit('PreAuth', post, options) + end + + # Capture an authorization + def capture(money, authorization, options = {}) + post = {} + add_order_id(post, order_id_from(authorization)) + add_amount(post, money, options) + + commit('PostAuth', post, options) + end + + # Cancel an auth/purchase within first 24 hours + def void(authorization, options = {}) + post = {} + order_id, amount, currency = split_authorization(authorization) + add_order_id(post, order_id) + post['Total'] = (options[:amount] || amount) + post['CurrencyCode'] = currency + + commit('Refund', post, options) + end + + # Make a purchase. + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_customer_data(post, options) + add_amount(post, money, options) + + commit('Auth', post, options) + end + + # Perform a Credit transaction. + def refund(money, authorization, options = {}) + post = {} + add_order_id(post, order_id_from(authorization)) + add_amount(post, money, options) + + #commit('Refund', post, options) + commit('Credit', post, options) + end + + private + + def add_login_data(post) + post['StoreId'] = @options[:store_id] + post['UserName'] = @options[:login] + post['Password'] = @options[:password] + end + + def add_action(post, action, options) + post['ResourceName'] = action + post['ContentType'] = 'Transaction' + post['Mode'] = options[:mode] || 'P' + end + + def add_order_id(post, order_id) + post['OrderId'] = order_id + end + + def add_amount(post, money, options) + post['Total'] = amount(money) + post['CurrencyCode'] = currency_code(options[:currency] || currency(money)) + end + + def add_customer_data(post, options) + post['IPAddress'] = options[:ip] unless options[:ip].blank? + end + + def add_invoice(post, options) + post['Comments'] = options[:description] if options[:description] + end + + def add_creditcard(post, creditcard) + post['CardNumber'] = creditcard.number + post['ExpDate'] = expdate(creditcard) + post['CustomerName'] = creditcard.name + post['CVV2'] = creditcard.verification_value unless creditcard.verification_value.nil? + end + + def build_authorization(request_params, response_params) + [response_params['OrderId'], request_params['Total'], request_params['CurrencyCode']].join('|') + end + + def split_authorization(authorization) + order_id, amount, currency = authorization.split("|") + [order_id, amount, currency] + end + + def order_id_from(authorization) + split_authorization(authorization).first + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}/#{year[-2..-1]}" + end + + def url + test? ? test_url : live_url + end + + def parse(response, request_params) + response_params = params_from_response(response) + + success = (response_params['ResponseCode'] == '00') + message = response_params['ResponseText'] || response_params['ResponseMsg'] + options = @options.merge(:test => test?, + :authorization => build_authorization(request_params, response_params)) + + Response.new(success, message, response_params, options) + end + + def commit(action, parameters, options) + add_login_data(parameters) + add_action(parameters, action, options) + + post = parameters.collect{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + parse(ssl_post(url, post), parameters) + end + + # Override the regular handle response so we can access the headers + def handle_response(response) + case response.code.to_i + when 200...300 + response + else + raise ResponseError.new(response) + end + end + + # Return a hash containing all the useful, or informative values from netpay + def params_from_response(response) + params = {} + RESPONSE_KEYS.each do |k| + params[k] = response[k] unless response[k].to_s.empty? + end + params + end + + def currency_code(currency) + return currency if currency =~ /^\d+$/ + CURRENCY_CODES[currency] + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nmi.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nmi.rb new file mode 100644 index 000000000..12c62afdb --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/nmi.rb @@ -0,0 +1,13 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class NmiGateway < AuthorizeNetGateway + self.test_url = 'https://secure.networkmerchants.com/gateway/transact.dll' + self.live_url = 'https://secure.networkmerchants.com/gateway/transact.dll' + self.homepage_url = 'http://nmi.com/' + self.display_name = 'NMI' + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ogone.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ogone.rb new file mode 100644 index 000000000..92a38d1ca --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/ogone.rb @@ -0,0 +1,424 @@ +# coding: utf-8 +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # = Ogone DirectLink Gateway + # + # DirectLink is the API version of the Ogone Payment Platform. It allows server to server + # communication between Ogone systems and your e-commerce website. + # + # This implementation follows the specification provided in the DirectLink integration + # guide version 4.3.0 (25 April 2012), available here: + # https://secure.ogone.com/ncol/Ogone_DirectLink_EN.pdf + # + # It also features aliases, which allow to store/unstore credit cards, as specified in + # the Alias Manager Option guide version 3.2.1 (25 April 2012) available here: + # https://secure.ogone.com/ncol/Ogone_Alias_EN.pdf + # + # It also implements the 3-D Secure feature, as specified in the DirectLink with + # 3-D Secure guide version 3.0 (25 April 2012) available here: + # https://secure.ogone.com/ncol/Ogone_DirectLink-3-D_EN.pdf + # + # It was last tested on Release 4.92 of Ogone DirectLink + AliasManager + Direct Link 3D + # (25 April 2012). + # + # For any questions or comments, please contact one of the following: + # - Joel Cogen (joel.cogen@belighted.com) + # - Nicolas Jacobeus (nicolas.jacobeus@belighted.com), + # - Sébastien Grosjean (public@zencocoon.com), + # - Rémy Coutable (remy@jilion.com). + # + # == Usage + # + # gateway = ActiveMerchant::Billing::OgoneGateway.new( + # :login => "my_ogone_psp_id", + # :user => "my_ogone_user_id", + # :password => "my_ogone_pswd", + # :signature => "my_ogone_sha_signature", # Only if you configured your Ogone environment so. + # :signature_encryptor => "sha512", # Can be "none" (default), "sha1", "sha256" or "sha512". + # # Must be the same as the one configured in your Ogone account. + # ) + # + # # set up credit card object as in main ActiveMerchant example + # creditcard = ActiveMerchant::Billing::CreditCard.new( + # :type => 'visa', + # :number => '4242424242424242', + # :month => 8, + # :year => 2009, + # :first_name => 'Bob', + # :last_name => 'Bobsen' + # ) + # + # # run request + # response = gateway.purchase(1000, creditcard, :order_id => "1") # charge 10 EUR + # + # If you don't provide an :order_id, the gateway will generate a random one for you. + # + # puts response.success? # Check whether the transaction was successful + # puts response.message # Retrieve the message returned by Ogone + # puts response.authorization # Retrieve the unique transaction ID returned by Ogone + # puts response.order_id # Retrieve the order ID + # + # == Alias feature + # + # To use the alias feature, simply add :billing_id in the options hash: + # + # # Associate the alias to that credit card + # gateway.purchase(1000, creditcard, :order_id => "1", :billing_id => "myawesomecustomer") + # + # # You can use the alias instead of the credit card for subsequent orders + # gateway.purchase(2000, "myawesomecustomer", :order_id => "2") + # + # # You can also create an alias without making a purchase using store + # gateway.store(creditcard, :billing_id => "myawesomecustomer") + # + # # When using store, you can also let Ogone generate the alias for you + # response = gateway.store(creditcard) + # puts response.billing_id # Retrieve the generated alias + # + # == 3-D Secure feature + # + # To use the 3-D Secure feature, simply add :d3d => true in the options hash: + # gateway.purchase(2000, "myawesomecustomer", :order_id => "2", :d3d => true) + # + # Specific 3-D Secure request options are (please refer to the documentation for more infos about these options): + # :win_3ds => :main_window (default), :pop_up or :pop_ix. + # :http_accept => "*/*" (default), or any other HTTP_ACCEPT header value. + # :http_user_agent => The cardholder's User-Agent string + # :accept_url => URL of the web page to show the customer when the payment is authorized. + # (or waiting to be authorized). + # :decline_url => URL of the web page to show the customer when the acquirer rejects the authorization + # more than the maximum permitted number of authorization attempts (10 by default, but can + # be changed in the "Global transaction parameters" tab, "Payment retry" section of the + # Technical Information page). + # :exception_url => URL of the web page to show the customer when the payment result is uncertain. + # :paramplus => Field to submit the miscellaneous parameters and their values that you wish to be + # returned in the post sale request or final redirection. + # :complus => Field to submit a value you wish to be returned in the post sale request or output. + # :language => Customer's language, for example: "en_EN" + # + class OgoneGateway < Gateway + + URLS = { + :order => 'https://secure.ogone.com/ncol/%s/orderdirect.asp', + :maintenance => 'https://secure.ogone.com/ncol/%s/maintenancedirect.asp' + } + + CVV_MAPPING = { 'OK' => 'M', + 'KO' => 'N', + 'NO' => 'P' } + + AVS_MAPPING = { 'OK' => 'M', + 'KO' => 'N', + 'NO' => 'R' } + + SUCCESS_MESSAGE = "The transaction was successful" + + THREE_D_SECURE_DISPLAY_WAYS = { :main_window => 'MAINW', # display the identification page in the main window + # (default value). + :pop_up => 'POPUP', # display the identification page in a pop-up window + # and return to the main window at the end. + :pop_ix => 'POPIX' } # display the identification page in a pop-up window + # and remain in the pop-up window. + + OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE = "Signature usage will be the default for a future release of ActiveMerchant. You should either begin using it, or update your configuration to explicitly disable it (signature_encryptor: none)" + OGONE_STORE_OPTION_DEPRECATION_MESSAGE = "The 'store' option has been renamed to 'billing_id', and its usage is deprecated." + + self.test_url = URLS[:order] % "test" + self.live_url = URLS[:order] % "prod" + + self.supported_countries = ['BE', 'DE', 'FR', 'NL', 'AT', 'CH'] + # also supports Airplus and UATP + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro] + self.homepage_url = 'http://www.ogone.com/' + self.display_name = 'Ogone' + self.default_currency = 'EUR' + self.money_format = :cents + + def initialize(options = {}) + requires!(options, :login, :user, :password) + super + end + + # Verify and reserve the specified amount on the account, without actually doing the transaction. + def authorize(money, payment_source, options = {}) + post = {} + add_invoice(post, options) + add_payment_source(post, payment_source, options) + add_address(post, payment_source, options) + add_customer_data(post, options) + add_money(post, money, options) + commit('RES', post) + end + + # Verify and transfer the specified amount. + def purchase(money, payment_source, options = {}) + post = {} + action = options[:action] || 'SAL' + add_invoice(post, options) + add_payment_source(post, payment_source, options) + add_address(post, payment_source, options) + add_customer_data(post, options) + add_money(post, money, options) + commit(action, post) + end + + # Complete a previously authorized transaction. + def capture(money, authorization, options = {}) + post = {} + action = options[:action] || 'SAL' + add_authorization(post, reference_from(authorization)) + add_invoice(post, options) + add_customer_data(post, options) + add_money(post, money, options) + commit(action, post) + end + + # Cancels a previously authorized transaction. + def void(identification, options = {}) + post = {} + add_authorization(post, reference_from(identification)) + commit('DES', post) + end + + # Credit the specified account by a specific amount. + def credit(money, identification_or_credit_card, options = {}) + if reference_transaction?(identification_or_credit_card) + deprecated CREDIT_DEPRECATION_MESSAGE + # Referenced credit: refund of a settled transaction + refund(money, identification_or_credit_card, options) + else # must be a credit card or card reference + perform_non_referenced_credit(money, identification_or_credit_card, options) + end + end + + # Refund of a settled transaction + def refund(money, reference, options = {}) + perform_reference_credit(money, reference, options) + end + + # Store a credit card by creating an Ogone Alias + def store(payment_source, options = {}) + options.merge!(:alias_operation => 'BYPSP') unless(options.has_key?(:billing_id) || options.has_key?(:store)) + response = authorize(1, payment_source, options) + void(response.authorization) if response.success? + response + end + + private + + def reference_from(authorization) + authorization.split(";").first + end + + def reference_transaction?(identifier) + return false unless identifier.is_a?(String) + _, action = identifier.split(";") + !action.nil? + end + + def perform_reference_credit(money, payment_target, options = {}) + post = {} + add_authorization(post, reference_from(payment_target)) + add_money(post, money, options) + commit('RFD', post) + end + + def perform_non_referenced_credit(money, payment_target, options = {}) + # Non-referenced credit: acts like a reverse purchase + post = {} + add_invoice(post, options) + add_payment_source(post, payment_target, options) + add_address(post, payment_target, options) + add_customer_data(post, options) + add_money(post, money, options) + commit('RFD', post) + end + + def add_payment_source(post, payment_source, options) + if payment_source.is_a?(String) + add_alias(post, payment_source, options[:alias_operation]) + add_eci(post, options[:eci] || '9') + else + if options.has_key?(:store) + deprecated OGONE_STORE_OPTION_DEPRECATION_MESSAGE + options[:billing_id] ||= options[:store] + end + add_alias(post, options[:billing_id], options[:alias_operation]) + add_eci(post, options[:eci] || '7') + add_d3d(post, options) if options[:d3d] + add_creditcard(post, payment_source) + end + end + + def add_d3d(post, options) + add_pair post, 'FLAG3D', 'Y' + win_3ds = THREE_D_SECURE_DISPLAY_WAYS.key?(options[:win_3ds]) ? + THREE_D_SECURE_DISPLAY_WAYS[options[:win_3ds]] : + THREE_D_SECURE_DISPLAY_WAYS[:main_window] + add_pair post, 'WIN3DS', win_3ds + + add_pair post, 'HTTP_ACCEPT', options[:http_accept] || "*/*" + add_pair post, 'HTTP_USER_AGENT', options[:http_user_agent] if options[:http_user_agent] + add_pair post, 'ACCEPTURL', options[:accept_url] if options[:accept_url] + add_pair post, 'DECLINEURL', options[:decline_url] if options[:decline_url] + add_pair post, 'EXCEPTIONURL', options[:exception_url] if options[:exception_url] + add_pair post, 'PARAMPLUS', options[:paramplus] if options[:paramplus] + add_pair post, 'COMPLUS', options[:complus] if options[:complus] + add_pair post, 'LANGUAGE', options[:language] if options[:language] + end + + def add_eci(post, eci) + add_pair post, 'ECI', eci.to_s + end + + def add_alias(post, _alias, alias_operation = nil) + add_pair post, 'ALIAS', _alias + add_pair post, 'ALIASOPERATION', alias_operation unless alias_operation.nil? + end + + def add_authorization(post, authorization) + add_pair post, 'PAYID', authorization + end + + def add_money(post, money, options) + add_pair post, 'currency', options[:currency] || @options[:currency] || currency(money) + add_pair post, 'amount', amount(money) + end + + def add_customer_data(post, options) + add_pair post, 'EMAIL', options[:email] + add_pair post, 'REMOTE_ADDR', options[:ip] + end + + def add_address(post, creditcard, options) + return unless options[:billing_address] + add_pair post, 'Owneraddress', options[:billing_address][:address1] + add_pair post, 'OwnerZip', options[:billing_address][:zip] + add_pair post, 'ownertown', options[:billing_address][:city] + add_pair post, 'ownercty', options[:billing_address][:country] + add_pair post, 'ownertelno', options[:billing_address][:phone] + end + + def add_invoice(post, options) + add_pair post, 'orderID', options[:order_id] || generate_unique_id[0...30] + add_pair post, 'COM', options[:description] + end + + def add_creditcard(post, creditcard) + add_pair post, 'CN', creditcard.name + add_pair post, 'CARDNO', creditcard.number + add_pair post, 'ED', "%02d%02s" % [creditcard.month, creditcard.year.to_s[-2..-1]] + add_pair post, 'CVC', creditcard.verification_value + end + + def parse(body) + xml_root = REXML::Document.new(body).root + response = convert_attributes_to_hash(xml_root.attributes) + + # Add HTML_ANSWER element (3-D Secure specific to the response's params) + # Note: HTML_ANSWER is not an attribute so we add it "by hand" to the response + if html_answer = REXML::XPath.first(xml_root, "//HTML_ANSWER") + response["HTML_ANSWER"] = html_answer.text + end + + response + end + + def commit(action, parameters) + add_pair parameters, 'PSPID', @options[:login] + add_pair parameters, 'USERID', @options[:user] + add_pair parameters, 'PSWD', @options[:password] + + url = URLS[parameters['PAYID'] ? :maintenance : :order] % [test? ? "test" : "prod"] + response = parse(ssl_post(url, post_data(action, parameters))) + + options = { + :authorization => [response["PAYID"], action].join(";"), + :test => test?, + :avs_result => { :code => AVS_MAPPING[response["AAVCheck"]] }, + :cvv_result => CVV_MAPPING[response["CVCCheck"]] + } + OgoneResponse.new(successful?(response), message_from(response), response, options) + end + + def successful?(response) + response["NCERROR"] == "0" + end + + def message_from(response) + if successful?(response) + SUCCESS_MESSAGE + else + format_error_message(response["NCERRORPLUS"]) + end + end + + def format_error_message(message) + raw_message = message.to_s.strip + case raw_message + when /\|/ + raw_message.split("|").join(", ").capitalize + when /\// + raw_message.split("/").first.to_s.capitalize + else + raw_message.to_s.capitalize + end + end + + def post_data(action, parameters = {}) + add_pair parameters, 'Operation', action + add_signature(parameters) + parameters.to_query + end + + def add_signature(parameters) + if @options[:signature].blank? + deprecated(OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) unless(@options[:signature_encryptor] == "none") + return + end + + sha_encryptor = case @options[:signature_encryptor] + when 'sha256' + Digest::SHA256 + when 'sha512' + Digest::SHA512 + else + Digest::SHA1 + end + + string_to_digest = if @options[:signature_encryptor] + parameters.sort { |a, b| a[0].upcase <=> b[0].upcase }.map { |k, v| "#{k.upcase}=#{v}" }.join(@options[:signature]) + else + %w[orderID amount currency CARDNO PSPID Operation ALIAS].map { |key| parameters[key] }.join + end + string_to_digest << @options[:signature] + + add_pair parameters, 'SHASign', sha_encryptor.hexdigest(string_to_digest).upcase + end + + def add_pair(post, key, value) + post[key] = value if !value.blank? + end + + def convert_attributes_to_hash(rexml_attributes) + response_hash = {} + rexml_attributes.each do |key, value| + response_hash[key] = value + end + response_hash + end + end + + class OgoneResponse < Response + def order_id + @params['orderID'] + end + + def billing_id + @params['ALIAS'] + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/optimal_payment.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/optimal_payment.rb new file mode 100644 index 000000000..604eefa18 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/optimal_payment.rb @@ -0,0 +1,297 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class OptimalPaymentGateway < Gateway + self.test_url = 'https://webservices.test.optimalpayments.com/creditcardWS/CreditCardServlet/v1' + self.live_url = 'https://webservices.optimalpayments.com/creditcardWS/CreditCardServlet/v1' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['CA', 'US', 'GB'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :solo] # :switch? + + # The homepage URL of the gateway + self.homepage_url = 'http://www.optimalpayments.com/' + + # The name of the gateway + self.display_name = 'Optimal Payments' + + def initialize(options = {}) + #requires!(options, :login, :password) + super + end + + def authorize(money, card_or_auth, options = {}) + parse_card_or_auth(card_or_auth, options) + commit("cc#{@stored_data}Authorize", money, options) + end + alias stored_authorize authorize # back-compat + + def purchase(money, card_or_auth, options = {}) + parse_card_or_auth(card_or_auth, options) + commit("cc#{@stored_data}Purchase", money, options) + end + alias stored_purchase purchase # back-compat + + def refund(money, authorization, options = {}) + options[:confirmationNumber] = authorization + commit('ccCredit', money, options) + end + + def void(authorization, options = {}) + options[:confirmationNumber] = authorization + commit('ccAuthorizeReversal', nil, options) + end + + def capture(money, authorization, options = {}) + options[:confirmationNumber] = authorization + commit('ccSettlement', money, options) + end + + private + + def parse_card_or_auth(card_or_auth, options) + if card_or_auth.respond_to?(:number) + @credit_card = card_or_auth + @stored_data = "" + else + options[:confirmationNumber] = card_or_auth + @stored_data = "StoredData" + end + end + + def parse(body) + REXML::Document.new(body || '') + end + + def commit(action, money, post) + post[:order_id] ||= 'order_id' + + xml = case action + when 'ccAuthorize', 'ccPurchase', 'ccVerification' + cc_auth_request(money, post) + when 'ccCredit', 'ccSettlement' + cc_post_auth_request(money, post) + when 'ccStoredDataAuthorize', 'ccStoredDataPurchase' + cc_stored_data_request(money, post) + when 'ccAuthorizeReversal' + cc_auth_reversal_request(post) + #when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment' + # cc_cancel_request(money, post) + #when 'ccPayment' + # cc_payment_request(money, post) + #when 'ccAuthenticate' + # cc_authenticate_request(money, post) + else + raise 'Unknown Action' + end + txnRequest = URI.encode(xml) + response = parse(ssl_post(test? ? self.test_url : self.live_url, "txnMode=#{action}&txnRequest=#{txnRequest}")) + + Response.new(successful?(response), message_from(response), hash_from_xml(response), + :test => test?, + :authorization => authorization_from(response), + :avs_result => { :code => avs_result_from(response) }, + :cvv_result => cvv_result_from(response) + ) + end + + def successful?(response) + REXML::XPath.first(response, '//decision').text == 'ACCEPTED' rescue false + end + + def message_from(response) + REXML::XPath.each(response, '//detail') do |detail| + if detail.is_a?(REXML::Element) && detail.elements['tag'].text == 'InternalResponseDescription' + return detail.elements['value'].text + end + end + nil + end + + def authorization_from(response) + get_text_from_document(response, '//confirmationNumber') + end + + def avs_result_from(response) + get_text_from_document(response, '//avsResponse') + end + + def cvv_result_from(response) + get_text_from_document(response, '//cvdResponse') + end + + def hash_from_xml(response) + hsh = {} + %w(confirmationNumber authCode + decision code description + actionCode avsResponse cvdResponse + txnTime duplicateFound + ).each do |tag| + node = REXML::XPath.first(response, "//#{tag}") + hsh[tag] = node.text if node + end + REXML::XPath.each(response, '//detail') do |detail| + next unless detail.is_a?(REXML::Element) + tag = detail.elements['tag'].text + value = detail.elements['value'].text + hsh[tag] = value + end + hsh + end + + def xml_document(root_tag) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag!(root_tag, schema) do + yield xml + end + xml.target! + end + + def get_text_from_document(document, node) + node = REXML::XPath.first(document, node) + node && node.text + end + + def cc_auth_request(money, opts) + xml_document('ccAuthRequestV1') do |xml| + build_merchant_account(xml, @options) + xml.merchantRefNum opts[:order_id] + xml.amount(money/100.0) + build_card(xml, opts) + build_billing_details(xml, opts) + build_shipping_details(xml, opts) + end + end + + def cc_auth_reversal_request(opts) + xml_document('ccAuthReversalRequestV1') do |xml| + build_merchant_account(xml, @options) + xml.confirmationNumber opts[:confirmationNumber] + xml.merchantRefNum opts[:order_id] + end + end + + def cc_post_auth_request(money, opts) + xml_document('ccPostAuthRequestV1') do |xml| + build_merchant_account(xml, @options) + xml.confirmationNumber opts[:confirmationNumber] + xml.merchantRefNum opts[:order_id] + xml.amount(money/100.0) + end + end + + def cc_stored_data_request(money, opts) + xml_document('ccStoredDataRequestV1') do |xml| + build_merchant_account(xml, @options) + xml.merchantRefNum opts[:order_id] + xml.confirmationNumber opts[:confirmationNumber] + xml.amount(money/100.0) + end + end + + # untested + # + # def cc_cancel_request(opts) + # xml_document('ccCancelRequestV1') do |xml| + # build_merchant_account(xml, @options) + # xml.confirmationNumber opts[:confirmationNumber] + # end + # end + # + # def cc_payment_request(money, opts) + # xml_document('ccPaymentRequestV1') do |xml| + # build_merchant_account(xml, @options) + # xml.merchantRefNum opts[:order_id] + # xml.amount(money/100.0) + # build_card(xml, opts) + # build_billing_details(xml, opts) + # end + # end + # + # def cc_authenticate_request(opts) + # xml_document('ccAuthenticateRequestV1') do |xml| + # build_merchant_account(xml, @options) + # xml.confirmationNumber opts[:confirmationNumber] + # xml.paymentResponse 'myPaymentResponse' + # end + # end + + def schema + { 'xmlns' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xsi:schemaLocation' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1' + } + end + + def build_merchant_account(xml, opts) + xml.tag! 'merchantAccount' do + xml.tag! 'accountNum' , opts[:account] + xml.tag! 'storeID' , opts[:login] + xml.tag! 'storePwd' , opts[:password] + end + end + + def build_card(xml, opts) + xml.tag! 'card' do + xml.tag! 'cardNum' , @credit_card.number + xml.tag! 'cardExpiry' do + xml.tag! 'month' , @credit_card.month + xml.tag! 'year' , @credit_card.year + end + if brand = card_type(@credit_card.brand) + xml.tag! 'cardType' , brand + end + if @credit_card.verification_value + xml.tag! 'cvdIndicator' , '1' # Value Provided + xml.tag! 'cvd' , @credit_card.verification_value + end + end + end + + def build_billing_details(xml, opts) + xml.tag! 'billingDetails' do + xml.tag! 'cardPayMethod', 'WEB' + build_address(xml, opts[:billing_address], opts[:email]) + end + end + + def build_shipping_details(xml, opts) + xml.tag! 'shippingDetails' do + build_address(xml, opts[:shipping_address], opts[:email]) + end if opts[:shipping_address].present? + end + + def build_address(xml, addr, email=nil) + if addr[:name] + xml.tag! 'firstName', CGI.escape(addr[:name].split(' ').first) # TODO: parse properly + xml.tag! 'lastName' , CGI.escape(addr[:name].split(' ').last ) + end + xml.tag! 'street' , CGI.escape(addr[:address1]) if addr[:address1].present? + xml.tag! 'street2', CGI.escape(addr[:address2]) if addr[:address2].present? + xml.tag! 'city' , CGI.escape(addr[:city] ) if addr[:city].present? + if addr[:state].present? + state_tag = %w(US CA).include?(addr[:country]) ? 'state' : 'region' + xml.tag! state_tag, CGI.escape(addr[:state]) + end + xml.tag! 'country', CGI.escape(addr[:country] ) if addr[:country].present? + xml.tag! 'zip' , CGI.escape(addr[:zip] ) if addr[:zip].present? + xml.tag! 'phone' , CGI.escape(addr[:phone] ) if addr[:phone].present? + xml.tag! 'email', CGI.escape(email) if email + end + + def card_type(key) + { 'visa' => 'VI', + 'master' => 'MC', + 'american_express'=> 'AM', + 'discover' => 'DI', + 'diners_club' => 'DC', + #'switch' => '', + 'solo' => 'SO' + }[key] + end + + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital.rb new file mode 100644 index 000000000..c26ebbac4 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital.rb @@ -0,0 +1,633 @@ +require File.dirname(__FILE__) + '/orbital/orbital_soft_descriptors' +require File.dirname(__FILE__) + '/orbital/avs_result' +require "rexml/document" + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # For more information on Orbital, visit the {integration center}[http://download.chasepaymentech.com] + # + # ==== Authentication Options + # + # The Orbital Gateway supports two methods of authenticating incoming requests: + # Source IP authentication and Connection Username/Password authentication + # + # In addition, these IP addresses/Connection Usernames must be affiliated with the Merchant IDs + # for which the client should be submitting transactions. + # + # This does allow Third Party Hosting service organizations presenting on behalf of other + # merchants to submit transactions. However, each time a new customer is added, the + # merchant or Third-Party hosting organization needs to ensure that the new Merchant IDs + # or Chain IDs are affiliated with the hosting companies IPs or Connection Usernames. + # + # If the merchant expects to have more than one merchant account with the Orbital + # Gateway, it should have its IP addresses/Connection Usernames affiliated at the Chain + # level hierarchy within the Orbital Gateway. Each time a new merchant ID is added, as + # long as it is placed within the same Chain, it will simply work. Otherwise, the additional + # MIDs will need to be affiliated with the merchant IPs or Connection Usernames respectively. + # For example, we generally affiliate all Salem accounts [BIN 000001] with + # their Company Number [formerly called MA #] number so all MIDs or Divisions under that + # Company will automatically be affiliated. + + class OrbitalGateway < Gateway + API_VERSION = "5.6" + + POST_HEADERS = { + "MIME-Version" => "1.1", + "Content-Type" => "application/PTI56", + "Content-transfer-encoding" => "text", + "Request-number" => '1', + "Document-type" => "Request", + "Interface-Version" => "Ruby|ActiveMerchant|Proprietary Gateway" + } + + SUCCESS, APPROVED = '0', '00' + + class_attribute :secondary_test_url, :secondary_live_url + + self.test_url = "https://orbitalvar1.paymentech.net/authorize" + self.secondary_test_url = "https://orbitalvar2.paymentech.net/authorize" + + self.live_url = "https://orbital1.paymentech.net/authorize" + self.secondary_live_url = "https://orbital2.paymentech.net/authorize" + + self.supported_countries = ["US", "CA"] + self.default_currency = "CAD" + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + + self.display_name = 'Orbital Paymentech' + self.homepage_url = 'http://chasepaymentech.com/' + + self.money_format = :cents + + AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB'] + + CURRENCY_CODES = { + "AUD" => '036', + "CAD" => '124', + "CZK" => '203', + "DKK" => '208', + "HKD" => '344', + "ICK" => '352', + "JPY" => '392', + "MXN" => '484', + "NZD" => '554', + "NOK" => '578', + "SGD" => '702', + "SEK" => '752', + "CHF" => '756', + "GBP" => '826', + "USD" => '840', + "EUR" => '978' + } + + # INDUSTRY TYPES + ECOMMERCE_TRANSACTION = 'EC' + RECURRING_PAYMENT_TRANSACTION = 'RC' + MAIL_ORDER_TELEPHONE_ORDER_TRANSACTION = 'MO' + INTERACTIVE_VOICE_RESPONSE = 'IV' + # INTERACTIVE_VOICE_RESPONSE = 'IN' + + # Auth Only No Capture + AUTH_ONLY = 'A' + # AC - Auth and Capture = 'AC' + AUTH_AND_CAPTURE = 'AC' + # F - Force Auth No Capture and no online authorization = 'F' + FORCE_AUTH_ONLY = 'F' + # FR - Force Auth No Capture and no online authorization = 'FR' + # FC - Force Auth and Capture no online authorization = 'FC' + FORCE_AUTH_AND_CAPTURE = 'FC' + # Refund and Capture no online authorization + REFUND = 'R' + + # Tax Inds + TAX_NOT_PROVIDED = 0 + TAX_INCLUDED = 1 + NON_TAXABLE_TRANSACTION = 2 + + # Customer Profile Actions + CREATE = 'C' + RETRIEVE = 'R' + UPDATE = 'U' + DELETE = 'D' + + RECURRING = 'R' + DEFERRED = 'D' + + # Status + # Profile Status Flag + # This field is used to set the status of a Customer Profile. + ACTIVE = 'A' + INACTIVE = 'I' + MANUAL_SUSPEND = 'MS' + + # CustomerProfileOrderOverrideInd + # Defines if any Order Data can be pre-populated from + # the Customer Reference Number (CustomerRefNum) + NO_MAPPING_TO_ORDER_DATA = 'NO' + USE_CRN_FOR_ORDER_ID = 'OI' + USE_CRN_FOR_COMMENTS = 'OD' + USE_CRN_FOR_ORDER_ID_AND_COMMENTS = 'OA' + + # CustomerProfileFromOrderInd + # Method to use to Generate the Customer Profile Number + # When Customer Profile Action Type = Create, defines + # what the Customer Profile Number will be: + AUTO_GENERATE = 'A' # Auto-Generate the CustomerRefNum + USE_CUSTOMER_REF_NUM = 'S' # Use CustomerRefNum field + USE_ORDER_ID = 'O' # Use OrderID field + USE_COMMENTS = 'D' # Use Comments field + + def initialize(options = {}) + requires!(options, :merchant_id) + requires!(options, :login, :password) unless options[:ip_authentication] + super + end + + # A – Authorization request + def authorize(money, creditcard, options = {}) + order = build_new_order_xml(AUTH_ONLY, money, options) do |xml| + add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn] + add_address(xml, creditcard, options) + if @options[:customer_profiles] + add_customer_data(xml, options) + add_managed_billing(xml, options) + end + end + commit(order, :authorize, options[:trace_number]) + end + + # AC – Authorization and Capture + def purchase(money, creditcard, options = {}) + order = build_new_order_xml(AUTH_AND_CAPTURE, money, options) do |xml| + add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn] + add_address(xml, creditcard, options) + if @options[:customer_profiles] + add_customer_data(xml, options) + add_managed_billing(xml, options) + end + end + commit(order, :purchase, options[:trace_number]) + end + + # MFC - Mark For Capture + def capture(money, authorization, options = {}) + commit(build_mark_for_capture_xml(money, authorization, options), :capture) + end + + # R – Refund request + def refund(money, authorization, options = {}) + order = build_new_order_xml(REFUND, money, options.merge(:authorization => authorization)) do |xml| + add_refund(xml, options[:currency]) + xml.tag! :CustomerRefNum, options[:customer_ref_num] if @options[:customer_profiles] && options[:profile_txn] + end + commit(order, :refund, options[:trace_number]) + end + + def credit(money, authorization, options= {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def void(authorization, options = {}, deprecated = {}) + if(!options.kind_of?(Hash)) + deprecated("Calling the void method with an amount parameter is deprecated and will be removed in a future version.") + return void(options, deprecated.merge(:amount => authorization)) + end + + order = build_void_request_xml(authorization, options) + commit(order, :void, options[:trace_number]) + end + + + # ==== Customer Profiles + # :customer_ref_num should be set unless your happy with Orbital providing one + # + # :customer_profile_order_override_ind can be set to map + # the CustomerRefNum to OrderID or Comments. Defaults to 'NO' - no mapping + # + # 'NO' - No mapping to order data + # 'OI' - Use for + # 'OD' - Use for + # 'OA' - Use for and + # + # :order_default_description can be set optionally. 64 char max. + # + # :order_default_amount can be set optionally. integer as cents. + # + # :status defaults to Active + # + # 'A' - Active + # 'I' - Inactive + # 'MS' - Manual Suspend + + def add_customer_profile(creditcard, options = {}) + options.merge!(:customer_profile_action => CREATE) + order = build_customer_request_xml(creditcard, options) + commit(order, :add_customer_profile) + end + + def update_customer_profile(creditcard, options = {}) + options.merge!(:customer_profile_action => UPDATE) + order = build_customer_request_xml(creditcard, options) + commit(order, :update_customer_profile) + end + + def retrieve_customer_profile(customer_ref_num) + options = {:customer_profile_action => RETRIEVE, :customer_ref_num => customer_ref_num} + order = build_customer_request_xml(nil, options) + commit(order, :retrieve_customer_profile) + end + + def delete_customer_profile(customer_ref_num) + options = {:customer_profile_action => DELETE, :customer_ref_num => customer_ref_num} + order = build_customer_request_xml(nil, options) + commit(order, :delete_customer_profile) + end + + private + + def authorization_string(*args) + args.compact.join(";") + end + + def split_authorization(authorization) + authorization.split(';') + end + + def add_customer_data(xml, options) + if options[:profile_txn] + xml.tag! :CustomerRefNum, options[:customer_ref_num] + else + if options[:customer_ref_num] + xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM + xml.tag! :CustomerRefNum, options[:customer_ref_num] + else + xml.tag! :CustomerProfileFromOrderInd, AUTO_GENERATE + end + xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || NO_MAPPING_TO_ORDER_DATA + end + end + + def add_soft_descriptors(xml, soft_desc) + xml.tag! :SDMerchantName, soft_desc.merchant_name if soft_desc.merchant_name + xml.tag! :SDProductDescription, soft_desc.product_description if soft_desc.product_description + xml.tag! :SDMerchantCity, soft_desc.merchant_city if soft_desc.merchant_city + xml.tag! :SDMerchantPhone, soft_desc.merchant_phone if soft_desc.merchant_phone + xml.tag! :SDMerchantURL, soft_desc.merchant_url if soft_desc.merchant_url + xml.tag! :SDMerchantEmail, soft_desc.merchant_email if soft_desc.merchant_email + end + + def add_address(xml, creditcard, options) + if(address = (options[:billing_address] || options[:address])) + avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s) + + if avs_supported + xml.tag! :AVSzip, (address[:zip] ? address[:zip].to_s[0..9] : nil) + xml.tag! :AVSaddress1, (address[:address1] ? address[:address1][0..29] : nil) + xml.tag! :AVSaddress2, (address[:address2] ? address[:address2][0..29] : nil) + xml.tag! :AVScity, (address[:city] ? address[:city][0..19] : nil) + xml.tag! :AVSstate, address[:state] + xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil) + end + # can't look in billing address? + xml.tag! :AVSname, ((creditcard && creditcard.name) ? creditcard.name[0..29] : nil) + xml.tag! :AVScountryCode, (avs_supported ? address[:country] : '') + + # Needs to come after AVScountryCode + add_destination_address(xml, address) if avs_supported + end + end + + def add_destination_address(xml, address) + if address[:dest_zip] + xml.tag! :AVSDestzip, (address[:dest_zip] ? address[:dest_zip].to_s[0..9] : nil) + xml.tag! :AVSDestaddress1, (address[:dest_address1] ? address[:dest_address1][0..29] : nil) + xml.tag! :AVSDestaddress2, (address[:dest_address2] ? address[:dest_address2][0..29] : nil) + xml.tag! :AVSDestcity, (address[:dest_city] ? address[:dest_city][0..19] : nil) + xml.tag! :AVSDeststate, address[:dest_state] + xml.tag! :AVSDestphoneNum, (address[:dest_phone] ? address[:dest_phone].scan(/\d/).join.to_s[0..13] : nil) + + xml.tag! :AVSDestname, (address[:dest_name] ? address[:dest_name][0..29] : nil) + xml.tag! :AVSDestcountryCode, address[:dest_country] + end + end + + # For Profile requests + def add_customer_address(xml, options) + if(address = (options[:billing_address] || options[:address])) + xml.tag! :CustomerAddress1, (address[:address1] ? address[:address1][0..29] : nil) + xml.tag! :CustomerAddress2, (address[:address2] ? address[:address2][0..29] : nil) + xml.tag! :CustomerCity, (address[:city] ? address[:city][0..19] : nil) + xml.tag! :CustomerState, address[:state] + xml.tag! :CustomerZIP, (address[:zip] ? address[:zip].to_s[0..9] : nil) + xml.tag! :CustomerEmail, address[:email].to_s[0..49] if address[:email] + xml.tag! :CustomerPhone, (address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil) + xml.tag! :CustomerCountryCode, (address[:country] ? address[:country][0..1] : nil) + end + end + + def add_creditcard(xml, creditcard, currency=nil) + xml.tag! :AccountNum, creditcard.number + xml.tag! :Exp, expiry_date(creditcard) + + xml.tag! :CurrencyCode, currency_code(currency) + xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen. + + # If you are trying to collect a Card Verification Number + # (CardSecVal) for a Visa or Discover transaction, pass one of these values: + # 1 Value is Present + # 2 Value on card but illegible + # 9 Cardholder states data not available + # If the transaction is not a Visa or Discover transaction: + # Null-fill this attribute OR + # Do not submit the attribute at all. + # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf + if %w( visa discover ).include?(creditcard.brand) + xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9') + end + xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value? + end + + def add_refund(xml, currency=nil) + xml.tag! :AccountNum, nil + + xml.tag! :CurrencyCode, currency_code(currency) + xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen. + end + + def add_managed_billing(xml, options) + if mb = options[:managed_billing] + # default to recurring (R). Other option is deferred (D). + xml.tag! :MBType, mb[:type] || RECURRING + # default to Customer Reference Number + xml.tag! :MBOrderIdGenerationMethod, mb[:order_id_generation_method] || 'IO' + # By default use MBRecurringEndDate, set to N. + # MMDDYYYY + xml.tag! :MBRecurringStartDate, mb[:start_date].scan(/\d/).join.to_s if mb[:start_date] + # MMDDYYYY + xml.tag! :MBRecurringEndDate, mb[:end_date].scan(/\d/).join.to_s if mb[:end_date] + # By default listen to any value set in MBRecurringEndDate. + xml.tag! :MBRecurringNoEndDateFlag, mb[:no_end_date_flag] || 'N' # 'Y' || 'N' (Yes or No). + xml.tag! :MBRecurringMaxBillings, mb[:max_billings] if mb[:max_billings] + xml.tag! :MBRecurringFrequency, mb[:frequency] if mb[:frequency] + xml.tag! :MBDeferredBillDate, mb[:deferred_bill_date] if mb[:deferred_bill_date] + xml.tag! :MBMicroPaymentMaxDollarValue, mb[:max_dollar_value] if mb[:max_dollar_value] + xml.tag! :MBMicroPaymentMaxBillingDays, mb[:max_billing_days] if mb[:max_billing_days] + xml.tag! :MBMicroPaymentMaxTransactions, mb[:max_transactions] if mb[:max_transactions] + end + end + + def parse(body) + response = {} + xml = REXML::Document.new(body) + root = REXML::XPath.first(xml, "//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 + + def commit(order, message_type, trace_number=nil) + headers = POST_HEADERS.merge("Content-length" => order.size.to_s) + headers.merge!( "Trace-number" => trace_number.to_s, + "Merchant-Id" => @options[:merchant_id] ) if @options[:retry_logic] && trace_number + request = lambda{|url| parse(ssl_post(url, order, headers))} + + # Failover URL will be attempted in the event of a connection error + response = begin + request.call(remote_url) + rescue ConnectionError + request.call(remote_url(:secondary)) + end + + Response.new(success?(response, message_type), message_from(response), response, + { + :authorization => authorization_string(response[:tx_ref_num], response[:order_id]), + :test => self.test?, + :avs_result => OrbitalGateway::AVSResult.new(response[:avs_resp_code]), + :cvv_result => response[:cvv2_resp_code] + } + ) + end + + def remote_url(url=:primary) + if url == :primary + (self.test? ? self.test_url : self.live_url) + else + (self.test? ? self.secondary_test_url : self.secondary_live_url) + end + end + + def success?(response, message_type) + if [:refund, :void].include?(message_type) + response[:proc_status] == SUCCESS + elsif response[:customer_profile_action] + response[:profile_proc_status] == SUCCESS + else + response[:proc_status] == SUCCESS && + response[:resp_code] == APPROVED + end + end + + def message_from(response) + response[:resp_msg] || response[:status_msg] || response[:customer_profile_message] + end + + def ip_authentication? + @options[:ip_authentication] == true + end + + def build_new_order_xml(action, money, parameters = {}) + requires!(parameters, :order_id) + xml = xml_envelope + xml.tag! :Request do + xml.tag! :NewOrder do + add_xml_credentials(xml) + # EC - Ecommerce transaction + # RC - Recurring Payment transaction + # MO - Mail Order Telephone Order transaction + # IV - Interactive Voice Response + # IN - Interactive Voice Response + xml.tag! :IndustryType, parameters[:industry_type] || ECOMMERCE_TRANSACTION + # A - Auth Only No Capture + # AC - Auth and Capture + # F - Force Auth No Capture and no online authorization + # FR - Force Auth No Capture and no online authorization + # FC - Force Auth and Capture no online authorization + # R - Refund and Capture no online authorization + xml.tag! :MessageType, action + add_bin_merchant_and_terminal(xml, parameters) + + yield xml if block_given? + + xml.tag! :OrderID, format_order_id(parameters[:order_id]) + xml.tag! :Amount, amount(money) + xml.tag! :Comments, parameters[:comments] if parameters[:comments] + + # CustomerAni, AVSPhoneType and AVSDestPhoneType could be added here. + + if parameters[:soft_descriptors].is_a?(OrbitalSoftDescriptors) + add_soft_descriptors(xml, parameters[:soft_descriptors]) + end + + set_recurring_ind(xml, parameters) + + # Append Transaction Reference Number at the end for Refund transactions + if action == REFUND + tx_ref_num, _ = split_authorization(parameters[:authorization]) + xml.tag! :TxRefNum, tx_ref_num + end + end + end + xml.target! + end + + # For Canadian transactions on PNS Tampa on New Order + # RF - First Recurring Transaction + # RS - Subsequent Recurring Transactions + def set_recurring_ind(xml, parameters) + if parameters[:recurring_ind] + raise "RecurringInd must be set to either \"RF\" or \"RS\"" unless %w(RF RS).include?(parameters[:recurring_ind]) + xml.tag! :RecurringInd, parameters[:recurring_ind] + end + end + + def build_mark_for_capture_xml(money, authorization, parameters = {}) + tx_ref_num, order_id = split_authorization(authorization) + xml = xml_envelope + xml.tag! :Request do + xml.tag! :MarkForCapture do + add_xml_credentials(xml) + xml.tag! :OrderID, format_order_id(order_id) + xml.tag! :Amount, amount(money) + add_bin_merchant_and_terminal(xml, parameters) + xml.tag! :TxRefNum, tx_ref_num + end + end + xml.target! + end + + def build_void_request_xml(authorization, parameters = {}) + tx_ref_num, order_id = split_authorization(authorization) + xml = xml_envelope + xml.tag! :Request do + xml.tag! :Reversal do + add_xml_credentials(xml) + xml.tag! :TxRefNum, tx_ref_num + xml.tag! :TxRefIdx, parameters[:transaction_index] + xml.tag! :AdjustedAmt, parameters[:amount] # setting adjusted amount to nil will void entire amount + xml.tag! :OrderID, format_order_id(order_id || parameters[:order_id]) + add_bin_merchant_and_terminal(xml, parameters) + xml.tag! :ReversalRetryNumber, parameters[:reversal_retry_number] if parameters[:reversal_retry_number] + xml.tag! :OnlineReversalInd, parameters[:online_reversal_ind] if parameters[:online_reversal_ind] + end + end + xml.target! + end + + def currency_code(currency) + CURRENCY_CODES[(currency || self.default_currency)].to_s + end + + def expiry_date(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" + end + + def bin + @options[:bin] || (salem_mid? ? '000001' : '000002') + end + + def xml_envelope + xml = Builder::XmlMarkup.new(:indent => 2) + xml.instruct!(:xml, :version => '1.0', :encoding => 'UTF-8') + xml + end + + def add_xml_credentials(xml) + unless ip_authentication? + xml.tag! :OrbitalConnectionUsername, @options[:login] + xml.tag! :OrbitalConnectionPassword, @options[:password] + end + end + + def add_bin_merchant_and_terminal(xml, parameters) + xml.tag! :BIN, bin + xml.tag! :MerchantID, @options[:merchant_id] + xml.tag! :TerminalID, parameters[:terminal_id] || '001' + end + + def salem_mid? + @options[:merchant_id].length == 6 + end + + # The valid characters include: + # + # 1. all letters and digits + # 2. - , $ @ & and a space character, though the space character cannot be the leading character + # 3. PINless Debit transactions can only use uppercase and lowercase alpha (A-Z, a-z) and numeric (0-9) + def format_order_id(order_id) + illegal_characters = /[^,$@\- \w]/ + order_id = order_id.to_s.gsub(/\./, '-') + order_id.gsub!(illegal_characters, '') + order_id[0...22] + end + + def build_customer_request_xml(creditcard, options = {}) + xml = xml_envelope + xml.tag! :Request do + xml.tag! :Profile do + xml.tag! :OrbitalConnectionUsername, @options[:login] unless ip_authentication? + xml.tag! :OrbitalConnectionPassword, @options[:password] unless ip_authentication? + xml.tag! :CustomerBin, bin + xml.tag! :CustomerMerchantID, @options[:merchant_id] + xml.tag! :CustomerName, creditcard.name if creditcard + xml.tag! :CustomerRefNum, options[:customer_ref_num] if options[:customer_ref_num] + + add_customer_address(xml, options) + + xml.tag! :CustomerProfileAction, options[:customer_profile_action] # C, R, U, D + # NO No mapping to order data + # OI Use for + # OD Use for + # OA Use for and + xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || NO_MAPPING_TO_ORDER_DATA + + if options[:customer_profile_action] == CREATE + # A Auto-Generate the CustomerRefNum + # S Use CustomerRefNum field + # O Use OrderID field + # D Use Comments field + xml.tag! :CustomerProfileFromOrderInd, (options[:customer_ref_num] ? USE_CUSTOMER_REF_NUM : AUTO_GENERATE) + end + + xml.tag! :OrderDefaultDescription, options[:order_default_description][0..63] if options[:order_default_description] + xml.tag! :OrderDefaultAmount, options[:order_default_amount] if options[:order_default_amount] + + if [CREATE, UPDATE].include? options[:customer_profile_action] + xml.tag! :CustomerAccountType, 'CC' # Only credit card supported + xml.tag! :Status, options[:status] || ACTIVE # Active + end + + xml.tag! :CCAccountNum, creditcard.number if creditcard + xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime("%m%y") if creditcard + + # This has to come after CCExpireDate. + add_managed_billing(xml, options) + end + end + xml.target! + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/avs_result.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/avs_result.rb new file mode 100644 index 000000000..264030a23 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/avs_result.rb @@ -0,0 +1,93 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class OrbitalGateway < Gateway + # Unfortunately, Orbital uses their own special codes for AVS responses + # that are different than the standard codes defined in + # ActiveMerchant::Billing::AVSResult. + # + # This class encapsulates the response codes shown on page 240 of their spec: + # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf + # + class AVSResult < ActiveMerchant::Billing::AVSResult + CODES = { + '1' => 'No address supplied', + '2' => 'Bill-to address did not pass Auth Host edit checks', + '3' => 'AVS not performed', + '4' => 'Issuer does not participate in AVS', + '5' => 'Edit-error - AVS data is invalid', + '6' => 'System unavailable or time-out', + '7' => 'Address information unavailable', + '8' => 'Transaction Ineligible for AVS', + '9' => 'Zip Match/Zip 4 Match/Locale match', + 'A' => 'Zip Match/Zip 4 Match/Locale no match', + 'B' => 'Zip Match/Zip 4 no Match/Locale match', + 'C' => 'Zip Match/Zip 4 no Match/Locale no match', + 'D' => 'Zip No Match/Zip 4 Match/Locale match', + 'E' => 'Zip No Match/Zip 4 Match/Locale no match', + 'F' => 'Zip No Match/Zip 4 No Match/Locale match', + 'G' => 'No match at all', + 'H' => 'Zip Match/Locale match', + 'J' => 'Issuer does not participate in Global AVS', + 'JA' => 'International street address and postal match', + 'JB' => 'International street address match. Postal code not verified', + 'JC' => 'International street address and postal code not verified', + 'JD' => 'International postal code match. Street address not verified', + 'M1' => 'Merchant Override Decline', + 'M2' => 'Cardholder name, billing address, and postal code matches', + 'M3' => 'Cardholder name and billing code matches', + 'M4' => 'Cardholder name and billing address matches', + 'M5' => 'Cardholder name incorrect, billing address and postal code match', + 'M6' => 'Cardholder name incorrect, billing address matches', + 'M7' => 'Cardholder name incorrect, billing address matches', + 'M8' => 'Cardholder name, billing address and postal code are all incorrect', + 'N3' => 'Address matches, ZIP not verified', + 'N4' => 'Address and ZIP code not verified due to incompatible formats', + 'N5' => 'Address and ZIP code match (International only)', + 'N6' => 'Address not verified (International only)', + 'N7' => 'ZIP matches, address not verified', + 'N8' => 'Address and ZIP code match (International only)', + 'N9' => 'Address and ZIP code match (UK only)', + 'R' => 'Issuer does not participate in AVS', + 'UX' => 'Unknown', + 'X' => 'Zip Match/Zip 4 Match/Address Match', + 'Z' => 'Zip Match/Locale no match', + } + + # Map vendor's AVS result code to a postal match code + ORBITAL_POSTAL_MATCH_CODE = { + 'Y' => %w( 9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z ), + 'N' => %w( D E F G M8 ), + 'X' => %w( 4 J R ), + nil => %w( 1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UX ) + }.inject({}) do |map, (type, codes)| + codes.each { |code| map[code] = type } + map + end + + # Map vendor's AVS result code to a street match code + ORBITAL_STREET_MATCH_CODE = { + 'Y' => %w( 9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X ), + 'N' => %w( A C E G M8 Z ), + 'X' => %w( 4 J R ), + nil => %w( 1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UX ) + }.inject({}) do |map, (type, codes)| + codes.each { |code| map[code] = type } + map + end + + def self.messages + CODES + end + + def initialize(code) + @code = code.to_s.strip.upcase unless code.blank? + if @code + @message = CODES[@code] + @postal_match = ORBITAL_POSTAL_MATCH_CODE[@code] + @street_match = ORBITAL_STREET_MATCH_CODE[@code] + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb new file mode 100644 index 000000000..010af3911 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb @@ -0,0 +1,46 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class OrbitalSoftDescriptors + include Validateable + + PHONE_FORMAT_1 = /\A\d{3}-\d{3}-\d{4}\z/ + PHONE_FORMAT_2 = /\A\d{3}-\w{7}\z/ + + # ==== Tampa PNS Soft Descriptors + # The support for Soft Descriptors via the PNS Host is only for customers processing through Chase + # Paymentech Canada. + + # Unlike Salem, the only value that gets passed on the cardholder statement is the Merchant Name field. + # And for these customers, it is a maximum of 25 bytes of data. + # + # All other Soft Descriptor fields can optionally be sent, but will not be submitted to the settlement host + # and will not display on the cardholder statement. + + attr_accessor :merchant_name, :product_description, :merchant_city, :merchant_phone, :merchant_url, :merchant_email + + def initialize(options = {}) + self.merchant_name = options[:merchant_name] + self.merchant_city = options[:merchant_city] + self.merchant_phone = options[:merchant_phone] + self.merchant_url = options[:merchant_url] + self.merchant_email = options[:merchant_email] + end + + def validate + errors.add(:merchant_name, "is required") if self.merchant_name.blank? + errors.add(:merchant_name, "is required to be 25 bytes or less") if self.merchant_name.bytesize > 25 + + unless self.merchant_phone.blank? || self.merchant_phone.match(PHONE_FORMAT_1) || self.merchant_phone.match(PHONE_FORMAT_2) + errors.add(:merchant_phone, "is required to follow \"NNN-NNN-NNNN\" or \"NNN-AAAAAAA\" format") + end + + [:merchant_email, :merchant_url].each do |attr| + unless self.send(attr).blank? + errors.add(attr, "is required to be 13 bytes or less") if self.send(attr).bytesize > 13 + end + end + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_gate_xml.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_gate_xml.rb new file mode 100644 index 000000000..5f84e9f6d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_gate_xml.rb @@ -0,0 +1,261 @@ +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This gateway accepts the following arguments: + # :login => your PayJunction username + # :password => your PayJunction pass + # Example use: + # + # gateway = ActiveMerchant::Billing::Base.gateway(:pay_gate_xml).new( + # :login => "my_account", + # :password => "my_pass" + # ) + # + # # set up credit card obj as in main ActiveMerchant example + # creditcard = ActiveMerchant::Billing::CreditCard.new( + # :type => 'visa', + # :number => '4242424242424242', + # :month => 8, + # :year => 2009, + # :first_name => 'Bob', + # :last_name => 'Bobsen' + # ) + # + # # run request + # response = gateway.purchase(1000, creditcard) # charge 10 dollars + # + # 1) Check whether the transaction was successful + # + # response.success? + # + # 2) Retrieve the message returned by PayJunction + # + # response.message + # + # 3) Retrieve the unique transaction ID returned by PayGateXML + # + # response.authorization + # + # This gateway has many other features which are not implemented here yet + # The basic setup here only supports auth/capture transactions. + # + # Test Transactions + # + # PayGateXML has a global test user/pass, but you can also sign up for your own. + # The class and the test come equipped with the global test creds + # + # Usage Details + # + # Below is a map of only SOME of the values accepted by PayGateXML and how you should submit + # each to ActiveMerchant + # + # PayGateXML Field ActiveMerchant Use + # + # pgid use :login value to gateway instantation + # pwd use :password value to gateway instantiation + # + # cname credit_card.name + # cc credit_card.number + # exp credit_card values formatted to YYYYMMDD + # budp South Africa only - set to 0 if purchase is not on budget + # amt include as argument to method for your transaction type + # ver do nothing, always set to current API version + # + # cref provide as :invoice in options, varchar(80) + # cur 3 char field, currently only ZAR + # cvv credit_card.verification + # bno batch processing number, i.e. you supply this + # + # others -- not used in this implementation + # nurl, rurl - must remain blank or absent or they will use an alternative authentication mechanism + # email, ip - must remain blank or absent or they will use a PayGate extra service call PayProtector + # threed - must remain blank unless you are using your own 3D Secure server + # + class PayGateXmlGateway < Gateway + self.live_url = 'https://www.paygate.co.za/payxml/process.trans' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US', 'ZA'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + + # The homepage URL of the gateway + self.homepage_url = 'http://paygate.co.za/' + + # The name of the gateway + self.display_name = 'PayGate PayXML' + + # PayGate only supports Rands + self.default_currency = 'ZAR' + + # PayGate accepts only lowest denomination + self.money_format = :cents + + # PayGateXML public test account - you can get a private one too + TEST_ID_3DSECURE = '10011013800' + TEST_ID = '10011021600' + TEST_PWD = 'test' + + API_VERSION = '4.0' + + DECLINE_CODES = { + # Credit Card Errors - These RESULT_CODEs are returned if the transaction cannot be authorized due to a problem with the card. The TRANSACTION_STATUS will be 2 + 900001 => "Call for Approval", + 900002 => "Card Expired", + 900003 => "Insufficient Funds", + 900004 => "Invalid Card Number", + 900005 => "Bank Interface Timeout", # indicates a communications failure between the banks systems + 900006 => "Invalid Card", + 900007 => "Declined", + 900009 => "Lost Card", + 900010 => "Invalid Card Length", + 900011 => "Suspected Fraud", + 900012 => "Card Reported As Stolen", + 900013 => "Restricted Card", + 900014 => "Excessive Card Usage", + 900015 => "Card Blacklisted", + + 900207 => "Declined; authentication failed", # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly + + 990020 => "Auth Declined", + + 991001 => "Invalid expiry date", + 991002 => "Invalid amount", + + # Communication Errors - These RESULT_CODEs are returned if the transaction cannot be completed due to an unexpected error. TRANSACTION_STATUS will be 0. + 900205 => "Unexpected authentication result (phase 1)", + 900206 => "Unexpected authentication result (phase 1)", + + 990001 => "Could not insert into Database", + + 990022 => "Bank not available", + + 990053 => "Error processing transaction", + + # Miscellaneous - Unless otherwise noted, the TRANSACTION_STATUS will be 0. + 900209 => "Transaction verification failed (phase 2)", # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered + 900210 => "Authentication complete; transaction must be restarted", # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed. Most likely caused by the customer clicking the refresh button + + 990024 => "Duplicate Transaction Detected. Please check before submitting", + + 990028 => "Transaction cancelled" # Customer clicks the 'Cancel' button on the payment page + } + + SUCCESS_CODES = %w( 990004 990005 990017 990012 990018 990031 ) + + TRANSACTION_CODES = { + 0 => 'Not Done', + 1 => 'Approved', + 2 => 'Declined', + 3 => 'Paid', + 4 => 'Refunded', + 5 => 'Received by PayGate', + 6 => 'Replied to Client' + } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, creditcard, options = {}) + MultiResponse.run do |r| + r.process{authorize(money, creditcard, options)} + r.process{capture(money, r.authorization, options)} + end + end + + def authorize(money, creditcard, options = {}) + action = 'authtx' + + options.merge!(:money => money, :creditcard => creditcard) + commit(action, build_request(action, options)) + end + + def capture(money, authorization, options = {}) + action = 'settletx' + + options.merge!(:money => money, :authorization => authorization) + commit(action, build_request(action, options)) + end + + private + + def successful?(response) + SUCCESS_CODES.include?(response[:res]) + end + + def build_request(action, options={}) + xml = Builder::XmlMarkup.new + xml.instruct! + + xml.tag! 'protocol', :ver => API_VERSION, :pgid => (test? ? TEST_ID : @options[:login]), :pwd => @options[:password] do |protocol| + case action + when 'authtx' + money = options.delete(:money) + creditcard = options.delete(:creditcard) + build_authorization(protocol, money, creditcard, options) + when 'settletx' + money = options.delete(:money) + authorization = options.delete(:authorization) + build_capture(protocol, money, authorization, options) + else + raise "no action specified for build_request" + end + end + + xml.target! + end + + def build_authorization(xml, money, creditcard, options={}) + xml.tag! 'authtx', { + :cref => options[:order_id], + :cname => creditcard.name, + :cc => creditcard.number, + :exp => "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :four_digits)}", + :budp => 0, + :amt => amount(money), + :cur => (options[:currency] || currency(money)), + :cvv => creditcard.verification_value + } + end + + def build_capture(xml, money, authorization, options={}) + xml.tag! 'settletx', { + :tid => authorization + } + end + + def parse(action, body) + hash = {} + xml = REXML::Document.new(body) + + response_action = action.gsub(/tx/, 'rx') + root = REXML::XPath.first(xml.root, response_action) + # we might have gotten an error + if root.nil? + root = REXML::XPath.first(xml.root, 'errorrx') + end + root.attributes.each do |name, value| + hash[name.to_sym] = value + end + hash + end + + def commit(action, request) + response = parse(action, ssl_post(self.live_url, request)) + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => response[:tid] + ) + end + + def message_from(response) + (response[:rdesc] || response[:edesc]) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_junction.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_junction.rb new file mode 100644 index 000000000..ceb2e184d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_junction.rb @@ -0,0 +1,396 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # PayJunction Gateway + # + # This gateway accepts the following arguments: + # :login => your PayJunction username + # :password => your PayJunction pass + # + # Example use: + # + # gateway = ActiveMerchant::Billing::Base.gateway(:pay_junction).new( + # :login => "my_account", + # :password => "my_pass" + # ) + # + # # set up credit card obj as in main ActiveMerchant example + # creditcard = ActiveMerchant::Billing::CreditCard.new( + # :type => 'visa', + # :number => '4242424242424242', + # :month => 8, + # :year => 2009, + # :first_name => 'Bob', + # :last_name => 'Bobsen' + # ) + # + # # optionally specify address if using AVS + # address = { :address1 => '101 Test Ave', :city => 'Test', :state => 'TS', + # :zip => '10101', :country => 'US' } + # + # # run request + # response = gateway.purchase(1000, creditcard, :address => address) # charge 10 dollars + # + # 1) Check whether the transaction was successful + # + # response.success? + # + # 2) Retrieve the message returned by PayJunction + # + # response.message + # + # 3) Retrieve the unique transaction ID returned by PayJunction + # + # response.authorization + # + # This gateway supports "instant" transactions. These transactions allow you + # to execute an operation on a previously run card without card information + # provided you have the transaction id from a previous transaction with the + # same card. All functions that take a credit card object for this gateway + # can take a transaction id string instead. + # + # Test Transactions + # + # See the source for initialize() for test account information. Note that + # PayJunction does not allow test transactions on your account, so if the + # gateway is running in :test mode your transaction will be run against + # PayJunction's global test account and will not show up in your account. + # + # Transactions ran on this account go through a test processor, so there is no + # need to void or otherwise cancel transactions. However, for further safety, + # please use the special card numbers 4433221111223344 or 4444333322221111 and + # keep transaction amounts below $4.00 when testing. + # + # Also note, transactions ran for an amount between $0.00 and $1.99 will likely + # result in denial. To demonstrate approvals, use amounts between $2.00 and $4.00. + # + # Test transactions can be checked by logging into + # PayJunction Web Login with username 'pj-cm-01' and password 'pj-cm-01p' + # + # Usage Details + # + # Below is a map of values accepted by PayJunction and how you should submit + # each to ActiveMerchant + # + # PayJunction Field ActiveMerchant Use + # + # dc_logon provide as :login value to gateway instantation + # dc_password provide as :password value to gateway instantiation + # + # dc_name will be retrieved from credit_card.name + # dc_first_name :first_name on CreditCard object instantation + # dc_last_name :last_name on CreditCard object instantation + # dc_number :number on CreditCard object instantation + # dc_expiration_month :month on CreditCard object instantation + # dc_expiration_year :year on CreditCard object instantation + # dc_verification_number :verification_value on CC object instantation + # + # dc_transaction_amount include as argument to method for your transaction type + # dc_transaction_type do nothing, set by your transaction type + # dc_version do nothing, always "1.2" + # + # dc_transaction_id submit as a string in place of CreditCard obj for + # "instant" transactions. + # + # dc_invoice :order_id in options for transaction method + # dc_notes :description in options for transaction method + # + # See example use above for address AVS fields + # See #recurring for periodic transaction fields + class PayJunctionGateway < Gateway + API_VERSION = '1.2' + + class_attribute :test_url, :live_url + + self.test_url = "https://www.payjunctionlabs.com/quick_link" + self.live_url = "https://payjunction.com/quick_link" + + TEST_LOGIN = 'pj-ql-01' + TEST_PASSWORD = 'pj-ql-01p' + + SUCCESS_CODES = ["00", "85"] + SUCCESS_MESSAGE = 'The transaction was approved.' + + FAILURE_MESSAGE = 'The transaction was declined.' + + DECLINE_CODES = { + "AE" => 'Address verification failed because address did not match.', + 'ZE' => 'Address verification failed because zip did not match.', + 'XE' => 'Address verification failed because zip and address did not match.', + 'YE' => 'Address verification failed because zip and address did not match.', + 'OE' => 'Address verification failed because address or zip did not match.', + 'UE' => 'Address verification failed because cardholder address unavailable.', + 'RE' => 'Address verification failed because address verification system is not working.', + 'SE' => 'Address verification failed because address verification system is unavailable.', + 'EE' => 'Address verification failed because transaction is not a mail or phone order.', + 'GE' => 'Address verification failed because international support is unavailable.', + 'CE' => 'Declined because CVV2/CVC2 code did not match.', + '04' => 'Declined. Pick up card.', + '07' => 'Declined. Pick up card (Special Condition).', + '41' => 'Declined. Pick up card (Lost).', + '43' => 'Declined. Pick up card (Stolen).', + '13' => 'Declined because of the amount is invalid.', + '14' => 'Declined because the card number is invalid.', + '80' => 'Declined because of an invalid date.', + '05' => 'Declined. Do not honor.', + '51' => 'Declined because of insufficient funds.', + 'N4' => 'Declined because the amount exceeds issuer withdrawal limit.', + '61' => 'Declined because the amount exceeds withdrawal limit.', + '62' => 'Declined because of an invalid service code (restricted).', + '65' => 'Declined because the card activity limit exceeded.', + '93' => 'Declined because there a violation (the transaction could not be completed).', + '06' => 'Declined because address verification failed.', + '54' => 'Declined because the card has expired.', + '15' => 'Declined because there is no such issuer.', + '96' => 'Declined because of a system error.', + 'N7' => 'Declined because of a CVV2/CVC2 mismatch.', + 'M4' => 'Declined.', + "FE" => "There was a format error with your Trinity Gateway Service (API) request.", + "LE" => "Could not log you in (problem with dc_logon and/or dc_password).", + 'NL' => 'Aborted because of a system error, please try again later. ', + 'AB' => 'Aborted because of an upstream system error, please try again later.' + } + + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = ['US'] + self.homepage_url = 'http://www.payjunction.com/' + self.display_name = 'PayJunction' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # The first half of the preauth(authorize)/postauth(capture) model. + # Checks to make sure funds are available for a transaction, and returns a + # transaction_id that can be used later to postauthorize (capture) the funds. + def authorize(money, payment_source, options = {}) + parameters = { + :transaction_amount => amount(money), + } + + add_payment_source(parameters, payment_source) + add_address(parameters, options) + add_optional_fields(parameters, options) + commit('AUTHORIZATION', parameters) + end + + # A simple sale, capturing funds immediately. + # Execute authorization and capture in a single step. + def purchase(money, payment_source, options = {}) + parameters = { + :transaction_amount => amount(money), + } + + add_payment_source(parameters, payment_source) + add_address(parameters, options) + add_optional_fields(parameters, options) + commit('AUTHORIZATION_CAPTURE', parameters) + end + + # The second half of the preauth(authorize)/postauth(capture) model. + # Retrieve funds that have been previously authorized with _authorization_ + def capture(money, authorization, options = {}) + parameters = { + :transaction_id => authorization, + :posture => 'capture' + } + + add_optional_fields(parameters, options) + commit('update', parameters) + end + + # Return money to a card that was previously billed. + # _authorization_ should be the transaction id of the transaction we are returning. + def refund(money, authorization, options = {}) + parameters = { + :transaction_amount => amount(money), + :transaction_id => authorization + } + + commit('CREDIT', parameters) + end + + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + # Cancel a transaction that has been charged but has not yet made it + # through the batch process. + def void(authorization, options = {}) + parameters = { + :transaction_id => authorization, + :posture => 'void' + } + + add_optional_fields(parameters, options) + commit('update', parameters) + end + + # Set up a sale that will be made on a regular basis for the same amount + # (ex. $20 a month for 12 months) + # + # The parameter :periodicity should be specified as either :monthly, :weekly, or :daily + # The parameter :payments should be the number of payments to be made + # + # gateway.recurring('2000', creditcard, :periodicity => :monthly, :payments => 12) + # + # The optional parameter :starting_at takes a date or time argument or a string in + # YYYYMMDD format and can be used to specify when the first charge will be made. + # If omitted the first charge will be immediate. + def recurring(money, payment_source, options = {}) + requires!(options, [:periodicity, :monthly, :weekly, :daily], :payments) + + periodic_type = case options[:periodicity] + when :monthly + 'month' + when :weekly + 'week' + when :daily + 'day' + end + + if options[:starting_at].nil? + start_date = Time.now.strftime('%Y-%m-%d') + elsif options[:starting_at].is_a?(String) + sa = options[:starting_at] + start_date = "#{sa[0..3]}-#{sa[4..5]}-#{sa[6..7]}" + else + start_date = options[:starting_at].strftime('%Y-%m-%d') + end + + parameters = { + :transaction_amount => amount(money), + :schedule_periodic_type => periodic_type, + :schedule_create => 'true', + :schedule_limit => options[:payments].to_i > 1 ? options[:payments] : 1, + :schedule_periodic_number => 1, + :schedule_start => start_date + } + + add_payment_source(parameters, payment_source) + add_optional_fields(parameters, options) + add_address(parameters, options) + commit('AUTHORIZATION_CAPTURE', parameters) + end + + def test? + (test_login? || super) + end + + private + + def test_login? + @options[:login] == TEST_LOGIN && @options[:password] == TEST_PASSWORD + end + + # add fields depending on payment source selected (cc or transaction id) + def add_payment_source(params, source) + if source.is_a?(String) + add_billing_id(params, source) + else + add_creditcard(params, source) + end + end + + # add fields for credit card + def add_creditcard(params, creditcard) + params[:name] = creditcard.name + params[:number] = creditcard.number + params[:expiration_month] = creditcard.month + params[:expiration_year] = creditcard.year + params[:verification_number] = creditcard.verification_value if creditcard.verification_value? + end + + # add field for "instant" transaction, using previous transaction id + def add_billing_id(params, billingid) + params[:transaction_id] = billingid + end + + # add address fields if present + def add_address(params, options) + address = options[:billing_address] || options[:address] + + if address + params[:address] = address[:address1] unless address[:address1].blank? + params[:city] = address[:city] unless address[:city].blank? + params[:state] = address[:state] unless address[:state].blank? + params[:zipcode] = address[:zip] unless address[:zip].blank? + params[:country] = address[:country] unless address[:country].blank? + end + end + + def add_optional_fields(params, options) + params[:notes] = options[:description] unless options[:description].blank? + params[:invoice] = options[:order_id].to_s.gsub(/[^-\/\w.,']/, '') unless options[:order_id].blank? + end + + def commit(action, parameters) + url = test? ? self.test_url : self.live_url + + response = parse( ssl_post(url, post_data(action, parameters)) ) + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => response[:transaction_id] || parameters[:transaction_id] + ) + end + + def successful?(response) + SUCCESS_CODES.include?(response[:response_code]) || response[:query_status] == true + end + + def message_from(response) + if successful?(response) + SUCCESS_MESSAGE + else + DECLINE_CODES[response[:response_code]] || FAILURE_MESSAGE + end + end + + def post_data(action, params) + if test? + # test requests must use global test account + params[:logon] = TEST_LOGIN + params[:password] = TEST_PASSWORD + else + params[:logon] = @options[:login] + params[:password] = @options[:password] + end + params[:version] = API_VERSION + params[:transaction_type] = action + + params.reject{|k,v| v.blank?}.collect{ |k, v| "dc_#{k.to_s}=#{CGI.escape(v.to_s)}" }.join("&") + end + + def parse(body) + # PayJunction uses the Field Separator ASCII character to separate key/val + # pairs in the response. The character's octal value is 034. + # + # Sample response: + # + # transaction_id=44752response_code=M4response_message=Declined (INV TEST CARD). + + pairs = body.chomp.split("\034") + response = {} + pairs.each do |pair| + key, val = pair.split('=') + response[key[3..-1].to_sym] = val ? normalize(val) : nil + end + response + 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 + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_secure.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_secure.rb new file mode 100644 index 000000000..16df1f9fa --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pay_secure.rb @@ -0,0 +1,119 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaySecureGateway < Gateway + self.live_url = self.test_url = 'https://clearance.commsecure.com.au/cgi-bin/PSDirect' + + self.money_format = :cents + + # Currently Authorization and Capture is not implemented because + # capturing requires the original credit card information + TRANSACTIONS = { + :purchase => 'PURCHASE', + :authorization => 'AUTHORISE', + :capture => 'ADVICE', + :credit => 'REFUND' + } + + SUCCESS = 'Accepted' + SUCCESS_MESSAGE = 'The transaction was approved' + + self.supported_countries = ['AU'] + self.homepage_url = 'http://www.commsecure.com.au/paysecure.shtml' + self.display_name = 'PaySecure' + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card, options = {}) + requires!(options, :order_id) + + post = {} + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, credit_card) + + commit(:purchase, money, post) + end + + private + # Used for capturing, which is currently not supported. + def add_reference(post, identification) + auth, trans_id = identification.split(";") + post[:authnum] = auth + post[:transid] = trans_id + end + + def add_amount(post, money) + post[:amount] = amount(money) + end + + def add_invoice(post, options) + post[:merchant_transid] = options[:order_id].to_s.slice(0,21) + post[:memnum] = options[:invoice] + post[:custnum] = options[:customer] + post[:clientdata] = options[:description] + end + + def add_credit_card(post, credit_card) + post[:cardnum] = credit_card.number + post[:cardname] = credit_card.name + post[:expiry] = expdate(credit_card) + post[:cvv2] = credit_card.verification_value + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def commit(action, money, parameters) + response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) + + Response.new(successful?(response), message_from(response), response, + :test => test_response?(response), + :authorization => authorization_from(response) + ) + + end + + def successful?(response) + response[:status] == SUCCESS + end + + def authorization_from(response) + [ response[:authnum], response[:transid] ].compact.join(";") + end + + def test_response?(response) + !!(response[:transid] =~ /SimProxy/) + end + + def message_from(response) + successful?(response) ? SUCCESS_MESSAGE : response[:errorstring] + end + + def parse(body) + response = {} + body.to_s.each_line do |l| + key, value = l.split(":", 2) + response[key.to_s.downcase.to_sym] = value.strip + end + response + end + + def post_data(action, parameters = {}) + parameters[:request_type] = TRANSACTIONS[action] + parameters[:merchant_id] = @options[:login] + parameters[:password] = @options[:password] + + parameters.reject{|k,v| v.blank?}.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join("&") + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paybox_direct.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paybox_direct.rb new file mode 100644 index 000000000..a3e3d70ae --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paybox_direct.rb @@ -0,0 +1,196 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayboxDirectGateway < Gateway + class_attribute :live_url_backup + + self.test_url = 'https://preprod-ppps.paybox.com/PPPS.php' + self.live_url = 'https://ppps.paybox.com/PPPS.php' + self.live_url_backup = 'https://ppps1.paybox.com/PPPS.php' + + # Payment API Version + API_VERSION = '00103' + + # Transactions hash + TRANSACTIONS = { + :authorization => '00001', + :capture => '00002', + :purchase => '00003', + :unreferenced_credit => '00004', + :void => '00005', + :refund => '00014' + } + + CURRENCY_CODES = { + "AUD"=> '036', + "CAD"=> '124', + "CZK"=> '203', + "DKK"=> '208', + "HKD"=> '344', + "ICK"=> '352', + "JPY"=> '392', + "NOK"=> '578', + "SGD"=> '702', + "SEK"=> '752', + "CHF"=> '756', + "GBP"=> '826', + "USD"=> '840', + "EUR"=> '978' + } + + SUCCESS_CODES = ['00000'] + UNAVAILABILITY_CODES = ['00001', '00097', '00098'] + SUCCESS_MESSAGE = 'The transaction was approved' + FAILURE_MESSAGE = 'The transaction failed' + + # Money is referenced in cents + self.money_format = :cents + self.default_currency = 'EUR' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['FR'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.paybox.com/' + + # The name of the gateway + self.display_name = 'Paybox Direct' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + commit('authorization', money, post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + commit('purchase', money, post) + end + + def capture(money, authorization, options = {}) + requires!(options, :order_id) + post = {} + add_invoice(post, options) + post[:numappel] = authorization[0,10] + post[:numtrans] = authorization[10,10] + commit('capture', money, post) + end + + def void(identification, options = {}) + requires!(options, :order_id, :amount) + post ={} + add_invoice(post, options) + add_reference(post, identification) + post[:porteur] = '000000000000000' + post[:dateval] = '0000' + commit('void', options[:amount], post) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + def refund(money, identification, options = {}) + post = {} + add_invoice(post, options) + add_reference(post, identification) + commit('refund', money, post) + end + + private + + def add_invoice(post, options) + post[:reference] = options[:order_id] + end + + def add_creditcard(post, creditcard) + post[:porteur] = creditcard.number + post[:dateval] = expdate(creditcard) + post[:cvv] = creditcard.verification_value if creditcard.verification_value? + end + + def add_reference(post, identification) + post[:numappel] = identification[0,10] + post[:numtrans] = identification[10,10] + end + + def parse(body) + results = {} + body.split(/&/).each do |pair| + key,val = pair.split(/\=/) + results[key.downcase.to_sym] = CGI.unescape(val) if val + end + results + end + + def commit(action, money = nil, parameters = nil) + parameters[:montant] = ('0000000000' + (money ? amount(money) : ''))[-10..-1] + parameters[:devise] = CURRENCY_CODES[options[:currency] || currency(money)] + request_data = post_data(action,parameters) + response = parse(ssl_post(test? ? self.test_url : self.live_url, request_data)) + response = parse(ssl_post(self.live_url_backup, request_data)) if service_unavailable?(response) && !test? + Response.new(success?(response), message_from(response), response.merge( + :timestamp => parameters[:dateq]), + :test => test?, + :authorization => response[:numappel].to_s + response[:numtrans].to_s, + :fraud_review => false, + :sent_params => parameters.delete_if{|key,value| ['porteur','dateval','cvv'].include?(key.to_s)} + ) + end + + def success?(response) + SUCCESS_CODES.include?(response[:codereponse]) + end + + def service_unavailable?(response) + UNAVAILABILITY_CODES.include?(response[:codereponse]) + end + + def message_from(response) + success?(response) ? SUCCESS_MESSAGE : (response[:commentaire] || FAILURE_MESSAGE) + end + + def post_data(action, parameters = {}) + + parameters.update( + :version => API_VERSION, + :type => TRANSACTIONS[action.to_sym], + :dateq => Time.now.strftime('%d%m%Y%H%M%S'), + :numquestion => unique_id(parameters[:order_id]), + :site => @options[:login].to_s[0,7], + :rang => @options[:login].to_s[7..-1], + :cle => @options[:password], + :pays => '', + :archivage => parameters[:order_id] + ) + + parameters.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def unique_id(seed = 0) + randkey = "#{seed}#{Time.now.usec}".to_i % 2147483647 # Max paybox value for the question number + + "0000000000#{randkey}"[-10..-1] + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow.rb new file mode 100644 index 000000000..770c426d8 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow.rb @@ -0,0 +1,268 @@ +require File.dirname(__FILE__) + '/payflow/payflow_common_api' +require File.dirname(__FILE__) + '/payflow/payflow_response' +require File.dirname(__FILE__) + '/payflow_express' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayflowGateway < Gateway + include PayflowCommonAPI + + RECURRING_ACTIONS = Set.new([:add, :modify, :cancel, :inquiry, :reactivate, :payment]) + + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover, :diners_club] + self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_payflow-pro-overview-outside' + self.display_name = 'PayPal Payflow Pro' + + def authorize(money, credit_card_or_reference, options = {}) + request = build_sale_or_authorization_request(:authorization, money, credit_card_or_reference, options) + + commit(request, options) + end + + def purchase(money, credit_card_or_reference, options = {}) + request = build_sale_or_authorization_request(:purchase, money, credit_card_or_reference, options) + + commit(request, options) + end + + def credit(money, identification_or_credit_card, options = {}) + if identification_or_credit_card.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + # Perform referenced credit + refund(money, identification_or_credit_card, options) + else + # Perform non-referenced credit + request = build_credit_card_request(:credit, money, identification_or_credit_card, options) + commit(request, options) + end + end + + def refund(money, reference, options = {}) + commit(build_reference_request(:credit, money, reference, options), options) + end + + # Adds or modifies a recurring Payflow profile. See the Payflow Pro Recurring Billing Guide for more details: + # https://www.paypal.com/en_US/pdf/PayflowPro_RecurringBilling_Guide.pdf + # + # Several options are available to customize the recurring profile: + # + # * profile_id - is only required for editing a recurring profile + # * starting_at - takes a Date, Time, or string in mmddyyyy format. The date must be in the future. + # * name - The name of the customer to be billed. If not specified, the name from the credit card is used. + # * periodicity - The frequency that the recurring payments will occur at. Can be one of + # :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly + # * payments - The term, or number of payments that will be made + # * comment - A comment associated with the profile + def recurring(money, credit_card, options = {}) + options[:name] = credit_card.name if options[:name].blank? && credit_card + request = build_recurring_request(options[:profile_id] ? :modify : :add, money, options) do |xml| + add_credit_card(xml, credit_card) if credit_card + end + commit(request, options.merge(:request_type => :recurring)) + end + + def cancel_recurring(profile_id) + request = build_recurring_request(:cancel, 0, :profile_id => profile_id) + commit(request, options.merge(:request_type => :recurring)) + end + + def recurring_inquiry(profile_id, options = {}) + request = build_recurring_request(:inquiry, nil, options.update( :profile_id => profile_id )) + commit(request, options.merge(:request_type => :recurring)) + end + + def express + @express ||= PayflowExpressGateway.new(@options) + end + + private + def build_sale_or_authorization_request(action, money, credit_card_or_reference, options) + if credit_card_or_reference.is_a?(String) + build_reference_sale_or_authorization_request(action, money, credit_card_or_reference, options) + else + build_credit_card_request(action, money, credit_card_or_reference, options) + end + end + + def build_reference_sale_or_authorization_request(action, money, reference, options) + xml = Builder::XmlMarkup.new + xml.tag! TRANSACTIONS[action] do + xml.tag! 'PayData' do + xml.tag! 'Invoice' do + # Fields accepted by PayFlow and recommended to be provided even for Reference Transaction, per Payflow docs. + xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? + xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank? + xml.tag! 'Description', options[:description] unless options[:description].blank? + xml.tag! 'Comment', options[:comment] unless options[:comment].blank? + xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? + xml.tag! 'TaxAmt', options[:taxamt] unless options[:taxamt].blank? + xml.tag! 'FreightAmt', options[:freightamt] unless options[:freightamt].blank? + xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank? + xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank? + + billing_address = options[:billing_address] || options[:address] + add_address(xml, 'BillTo', billing_address, options) if billing_address + add_address(xml, 'ShipTo', options[:shipping_address],options) if options[:shipping_address] + + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + end + xml.tag! 'Tender' do + xml.tag! 'Card' do + xml.tag! 'ExtData', 'Name' => 'ORIGID', 'Value' => reference + end + end + end + end + xml.target! + end + + def build_credit_card_request(action, money, credit_card, options) + xml = Builder::XmlMarkup.new + xml.tag! TRANSACTIONS[action] do + xml.tag! 'PayData' do + xml.tag! 'Invoice' do + xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? + xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank? + xml.tag! 'Description', options[:description] unless options[:description].blank? + # Comment and Comment2 will show up in manager.paypal.com as Comment1 and Comment2 + xml.tag! 'Comment', options[:comment] unless options[:comment].blank? + xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? + xml.tag! 'TaxAmt', options[:taxamt] unless options[:taxamt].blank? + xml.tag! 'FreightAmt', options[:freightamt] unless options[:freightamt].blank? + xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank? + xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank? + + billing_address = options[:billing_address] || options[:address] + add_address(xml, 'BillTo', billing_address, options) if billing_address + add_address(xml, 'ShipTo', options[:shipping_address], options) if options[:shipping_address] + + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + end + + xml.tag! 'Tender' do + add_credit_card(xml, credit_card) + end + end + end + xml.target! + end + + def add_credit_card(xml, credit_card) + xml.tag! 'Card' do + xml.tag! 'CardType', credit_card_type(credit_card) + xml.tag! 'CardNum', credit_card.number + xml.tag! 'ExpDate', expdate(credit_card) + xml.tag! 'NameOnCard', credit_card.first_name + xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value? + + if requires_start_date_or_issue_number?(credit_card) + xml.tag!('ExtData', 'Name' => 'CardStart', 'Value' => startdate(credit_card)) unless credit_card.start_month.blank? || credit_card.start_year.blank? + xml.tag!('ExtData', 'Name' => 'CardIssue', 'Value' => format(credit_card.issue_number, :two_digits)) unless credit_card.issue_number.blank? + end + xml.tag! 'ExtData', 'Name' => 'LASTNAME', 'Value' => credit_card.last_name + end + end + + def credit_card_type(credit_card) + return '' if card_brand(credit_card).blank? + + CARD_MAPPING[card_brand(credit_card).to_sym] + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year.to_s.sub(/^0+/, '')) + month = sprintf("%.2i", creditcard.month.to_s.sub(/^0+/, '')) + + "#{year}#{month}" + end + + def startdate(creditcard) + year = format(creditcard.start_year, :two_digits) + month = format(creditcard.start_month, :two_digits) + + "#{month}#{year}" + end + + def build_recurring_request(action, money, options) + unless RECURRING_ACTIONS.include?(action) + raise StandardError, "Invalid Recurring Profile Action: #{action}" + end + + xml = Builder::XmlMarkup.new + xml.tag! 'RecurringProfiles' do + xml.tag! 'RecurringProfile' do + xml.tag! action.to_s.capitalize do + unless [:cancel, :inquiry].include?(action) + xml.tag! 'RPData' do + xml.tag! 'Name', options[:name] unless options[:name].nil? + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + xml.tag! 'PayPeriod', get_pay_period(options) + xml.tag! 'Term', options[:payments] unless options[:payments].nil? + xml.tag! 'Comment', options[:comment] unless options[:comment].nil? + xml.tag! 'RetryNumDays', options[:retry_num_days] unless options[:retry_num_days].nil? + xml.tag! 'MaxFailPayments', options[:max_fail_payments] unless options[:max_fail_payments].nil? + + if initial_tx = options[:initial_transaction] + requires!(initial_tx, [:type, :authorization, :purchase]) + requires!(initial_tx, :amount) if initial_tx[:type] == :purchase + + xml.tag! 'OptionalTrans', TRANSACTIONS[initial_tx[:type]] + xml.tag! 'OptionalTransAmt', amount(initial_tx[:amount]) unless initial_tx[:amount].blank? + end + + if action == :add + xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1 ) + else + xml.tag! 'Start', format_rp_date(options[:starting_at]) unless options[:starting_at].nil? + end + + xml.tag! 'EMail', options[:email] unless options[:email].nil? + + billing_address = options[:billing_address] || options[:address] + add_address(xml, 'BillTo', billing_address, options) if billing_address + add_address(xml, 'ShipTo', options[:shipping_address], options) if options[:shipping_address] + end + xml.tag! 'Tender' do + yield xml + end + end + if action != :add + xml.tag! "ProfileID", options[:profile_id] + end + if action == :inquiry + xml.tag! "PaymentHistory", ( options[:history] ? 'Y' : 'N' ) + end + end + end + end + end + + def get_pay_period(options) + requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly]) + case options[:periodicity] + when :weekly then 'Weekly' + when :biweekly then 'Bi-weekly' + when :semimonthly then 'Semi-monthly' + when :quadweekly then 'Every four weeks' + when :monthly then 'Monthly' + when :quarterly then 'Quarterly' + when :semiyearly then 'Semi-yearly' + when :yearly then 'Yearly' + end + end + + def format_rp_date(time) + case time + when Time, Date then time.strftime("%m%d%Y") + else + time.to_s + end + end + + def build_response(success, message, response, options = {}) + PayflowResponse.new(success, message, response, options) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb new file mode 100644 index 000000000..a27684e65 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb @@ -0,0 +1,210 @@ +require 'nokogiri' +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module PayflowCommonAPI + def self.included(base) + base.default_currency = 'USD' + + base.class_attribute :partner + + # Set the default partner to PayPal + base.partner = 'PayPal' + + base.supported_countries = ['US', 'CA', 'SG', 'AU'] + + base.class_attribute :timeout + base.timeout = 60 + + base.test_url = 'https://pilot-payflowpro.paypal.com' + base.live_url = 'https://payflowpro.paypal.com' + + # Enable safe retry of failed connections + # Payflow is safe to retry because retried transactions use the same + # X-VPS-Request-ID header. If a transaction is detected as a duplicate + # only the original transaction data will be used by Payflow, and the + # subsequent Responses will have a :duplicate parameter set in the params + # hash. + base.retry_safe = true + end + + XMLNS = 'http://www.paypal.com/XMLPay' + + CARD_MAPPING = { + :visa => 'Visa', + :master => 'MasterCard', + :discover => 'Discover', + :american_express => 'Amex', + :jcb => 'JCB', + :diners_club => 'DinersClub', + :switch => 'Switch', + :solo => 'Solo' + } + + TRANSACTIONS = { + :purchase => "Sale", + :authorization => "Authorization", + :capture => "Capture", + :void => "Void", + :credit => "Credit" + } + + CVV_CODE = { + 'Match' => 'M', + 'No Match' => 'N', + 'Service Not Available' => 'U', + 'Service not Requested' => 'P' + } + + def initialize(options = {}) + requires!(options, :login, :password) + + options[:partner] = partner if options[:partner].blank? + super + end + + def capture(money, authorization, options = {}) + request = build_reference_request(:capture, money, authorization, options) + commit(request, options) + end + + def void(authorization, options = {}) + request = build_reference_request(:void, nil, authorization, options) + commit(request, options) + end + + private + def build_request(body, options = {}) + xml = Builder::XmlMarkup.new + xml.instruct! + xml.tag! 'XMLPayRequest', 'Timeout' => timeout.to_s, 'version' => "2.1", "xmlns" => XMLNS do + xml.tag! 'RequestData' do + xml.tag! 'Vendor', @options[:login] + xml.tag! 'Partner', @options[:partner] + if options[:request_type] == :recurring + xml << body + else + xml.tag! 'Transactions' do + xml.tag! 'Transaction', 'CustRef' => options[:customer] do + xml.tag! 'Verbosity', 'MEDIUM' + xml << body + end + end + end + end + xml.tag! 'RequestAuth' do + xml.tag! 'UserPass' do + xml.tag! 'User', !@options[:user].blank? ? @options[:user] : @options[:login] + xml.tag! 'Password', @options[:password] + end + end + end + xml.target! + end + + def build_reference_request(action, money, authorization, options) + xml = Builder::XmlMarkup.new + xml.tag! TRANSACTIONS[action] do + xml.tag! 'PNRef', authorization + + unless money.nil? + xml.tag! 'Invoice' do + xml.tag!('TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)) + xml.tag!('Description', options[:description]) unless options[:description].blank? + xml.tag!('Comment', options[:comment]) unless options[:comment].blank? + xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank? + end + end + end + + xml.target! + end + + def add_address(xml, tag, address, options) + return if address.nil? + xml.tag! tag do + xml.tag! 'FirstName', address[:first_name] unless address[:first_name].blank? + xml.tag! 'LastName', address[:last_name] unless address[:last_name].blank? + xml.tag! 'EMail', options[:email] unless options[:email].blank? + xml.tag! 'Phone', address[:phone] unless address[:phone].blank? + xml.tag! 'CustCode', options[:customer] if !options[:customer].blank? && tag == 'BillTo' + xml.tag! 'PONum', options[:po_number] if !options[:po_number].blank? && tag == 'BillTo' + + xml.tag! 'Address' do + xml.tag! 'Street', address[:address1] unless address[:address1].blank? + xml.tag! 'City', address[:city] unless address[:city].blank? + xml.tag! 'State', address[:state].blank? ? "N/A" : address[:state] + xml.tag! 'Country', address[:country] unless address[:country].blank? + xml.tag! 'Zip', address[:zip] unless address[:zip].blank? + end + end + end + + def parse(data) + response = {} + xml = Nokogiri::XML(data) + xml.remove_namespaces! + root = xml.xpath("//ResponseData") + + # REXML::XPath in Ruby 1.8.6 is now unable to match nodes based on their attributes + tx_result = root.xpath(".//TransactionResult").first + + if tx_result && tx_result.attributes['Duplicate'].to_s == "true" + response[:duplicate] = true + end + + root.xpath(".//*").each do |node| + parse_element(response, node) + end + + response + end + + def parse_element(response, node) + node_name = node.name.underscore.to_sym + case + when node_name == :rp_payment_result + # Since we'll have multiple history items, we can't just flatten everything + # down as we do everywhere else. RPPaymentResult elements are not contained + # in an RPPaymentResults element so we'll come here multiple times + response[node_name] ||= [] + response[node_name] << ( payment_result_response = {} ) + node.xpath(".//*").each{ |e| parse_element(payment_result_response, e) } + when node.xpath(".//*").to_a.any? + node.xpath(".//*").each{|e| parse_element(response, e) } + when node_name.to_s =~ /amt$/ + # *Amt elements don't put the value in the #text - instead they use a Currency attribute + response[node_name] = node.attributes['Currency'].to_s + when node_name == :ext_data + response[node.attributes['Name'].to_s.underscore.to_sym] = node.attributes['Value'].to_s + else + response[node_name] = node.text + end + end + + def build_headers(content_length) + { + "Content-Type" => "text/xml", + "Content-Length" => content_length.to_s, + "X-VPS-Client-Timeout" => timeout.to_s, + "X-VPS-VIT-Integration-Product" => "ActiveMerchant", + "X-VPS-VIT-Runtime-Version" => RUBY_VERSION, + "X-VPS-Request-ID" => Utils.generate_unique_id + } + end + + def commit(request_body, options = {}) + request = build_request(request_body, options) + headers = build_headers(request.size) + + response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers)) + + build_response(response[:result] == "0", response[:message], response, + :test => test?, + :authorization => response[:pn_ref] || response[:rp_ref], + :cvv_result => CVV_CODE[response[:cv_result]], + :avs_result => { :code => response[:avs_result] } + ) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb new file mode 100644 index 000000000..7b4068dea --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb @@ -0,0 +1,39 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayflowExpressResponse < Response + def email + @params['e_mail'] + end + + def full_name + "#{@params['name']} #{@params['lastname']}" + end + + def token + @params['token'] + end + + def payer_id + @params['payer_id'] + end + + # Really the shipping country, but it is all the information provided + def payer_country + address['country'] + end + + def address + { 'name' => full_name, + 'company' => nil, + 'address1' => @params['street'], + 'address2' => @params['shiptostreet2'] || @params['street2'], + 'city' => @params['city'], + 'state' => @params['state'], + 'country' => @params['country'], + 'zip' => @params['zip'], + 'phone' => nil + } + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_response.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_response.rb new file mode 100644 index 000000000..d2b6e6700 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow/payflow_response.rb @@ -0,0 +1,13 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayflowResponse < Response + def profile_id + @params['profile_id'] + end + + def payment_history + @payment_history ||= @params['rp_payment_result'].collect{ |result| result.stringify_keys } rescue [] + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express.rb new file mode 100644 index 000000000..f6183218d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express.rb @@ -0,0 +1,224 @@ +require File.dirname(__FILE__) + '/payflow/payflow_common_api' +require File.dirname(__FILE__) + '/payflow/payflow_express_response' +require File.dirname(__FILE__) + '/paypal_express_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # ==General Parameters + # The following parameters are supported for #setup_authorization, #setup_purchase, #authorize and #purchase transactions. I've read + # in the docs that they recommend you pass the exact same parameters to both setup and authorize/purchase. + # + # This information was gleaned from a mix of: + # * PayFlow documentation + # * for key value pairs: {Express Checkout for Payflow Pro (PDF)}[https://cms.paypal.com/cms_content/US/en_US/files/developer/PFP_ExpressCheckout_PP.pdf] + # * XMLPay: {Payflow Pro XMLPay Developer's Guide (PDF)}[https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_PayflowPro_XMLPay_Guide.pdf] + # * previous ActiveMerchant code + # * trial & error + # + # The following parameters are currently supported. + # [:ip] (opt) Customer IP Address + # [:order_id] (opt) An order or invoice number. This will be passed through to the Payflow backend at manager.paypal.com, and show up as "Supplier Reference #" + # [:description] (opt) Order description, shown to buyer (after redirected to PayPal). If Order Line Items are used (see below), then the description is suppressed. This will not be passed through to the Payflow backend. + # [:billing_address] (opt) See ActiveMerchant::Billing::Gateway for details + # [:shipping_address] (opt) See ActiveMerchant::Billing::Gateway for details + # [:currency] (req) Currency of transaction, will be set to USD by default for PayFlow Express if not specified + # [:email] (opt) Email of buyer; used to pre-fill PayPal login screen + # [:payer_id] (opt) Unique PayPal buyer account identification number, as returned by details_for request + # [:token] (req for #authorize & #purchase) Token returned by setup transaction + # [:no_shipping] (opt) Boolean for whether or not to display shipping address to buyer + # [:address_override] (opt) Boolean. If true, display shipping address passed by parameters, rather than shipping address on file with PayPal + # [:allow_note] (opt) Boolean for permitting buyer to add note during checkout. Note contents can be retrieved with details_for transaction + # [:return_url] (req) URL to which the buyer’s browser is returned after choosing to pay. + # [:cancel_return_url] (req) URL to which the buyer is returned if the buyer cancels the order. + # [:notify_url] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:comment] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment1 + # [:comment2] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment2 + # [:discount] (opt) Total discounts in cents + # + # ==Line Items + # Support for order line items is available, but has to be enabled on the PayFlow backend. This is what I was told by Todd Sieber at Technical Support: + # + # You will need to call Payflow Support at 1-888-883-9770, choose option #2. Request that they update your account in "Pandora" under Product Settings >> PayPal Mark and update the Features Bitmap to 1111111111111112. This is 15 ones and a two. + # + # See here[https://www.x.com/message/206214#206214] for the forum discussion (requires login to {x.com}[https://x.com] + # + # [:items] (opt) Array of Order Line Items hashes. These are shown to the buyer after redirect to PayPal. + # + # + # + # The following keys are supported for line items: + # [:name] Name of line item + # [:description] Description of line item + # [:amount] Line Item Amount in Cents (as Integer) + # [:quantity] Line Item Quantity (default to 1 if left blank) + # + # ====Customization of Payment Page + # [:page_style] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:header_image] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # ====Additional options for old Checkout Experience, being phased out in 2010 and 2011 + # [:header_background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + # [:header_border_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction. + + + class PayflowExpressGateway < Gateway + include PayflowCommonAPI + include PaypalExpressCommon + + self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr' + self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside' + self.display_name = 'PayPal Express Checkout' + + def authorize(money, options = {}) + requires!(options, :token, :payer_id) + request = build_sale_or_authorization_request('Authorization', money, options) + commit(request, options) + end + + def purchase(money, options = {}) + requires!(options, :token, :payer_id) + request = build_sale_or_authorization_request('Sale', money, options) + commit(request, options) + end + + def refund(money, identification, options = {}) + request = build_reference_request(:credit, money, identification, options) + commit(request, options) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + def setup_authorization(money, options = {}) + requires!(options, :return_url, :cancel_return_url) + + request = build_setup_express_sale_or_authorization_request('Authorization', money, options) + commit(request, options) + end + + def setup_purchase(money, options = {}) + requires!(options, :return_url, :cancel_return_url) + + request = build_setup_express_sale_or_authorization_request('Sale', money, options) + commit(request, options) + end + + def details_for(token) + request = build_get_express_details_request(token) + commit(request, options) + end + + private + def build_get_express_details_request(token) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'GetExpressCheckout' do + xml.tag! 'Authorization' do + xml.tag! 'PayData' do + xml.tag! 'Tender' do + xml.tag! 'PayPal' do + xml.tag! 'Token', token + end + end + end + end + end + xml.target! + end + + def build_setup_express_sale_or_authorization_request(action, money, options = {}) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'SetExpressCheckout' do + xml.tag! action do + add_pay_data xml, money, options + end + end + xml.target! + end + + def build_sale_or_authorization_request(action, money, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'DoExpressCheckout' do + xml.tag! action do + add_pay_data xml, money, options + end + end + xml.target! + end + + def add_pay_data(xml, money, options) + xml.tag! 'PayData' do + xml.tag! 'Invoice' do + xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? + xml.tag! 'InvNum', options[:order_id] unless options[:order_id].blank? + # Description field will be shown to buyer, unless line items are also being supplied (then only line items are shown). + xml.tag! 'Description', options[:description] unless options[:description].blank? + # Comment, Comment2 should make it to the backend at manager.paypal.com, as with Payflow credit card transactions + # but that doesn't seem to work (yet?). See: https://www.x.com/thread/51908?tstart=0 + xml.tag! 'Comment', options[:comment] unless options[:comment].nil? + xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].nil? + + billing_address = options[:billing_address] || options[:address] + add_address(xml, 'BillTo', billing_address, options) if billing_address + add_address(xml, 'ShipTo', options[:shipping_address], options) if options[:shipping_address] + + # Note: To get order line-items to show up with Payflow Express, this feature has to be enabled on the backend. + # Call Support at 888 883 9770, press 2. Then request that they update your account in "Pandora" under Product Settings >> PayPal + # Mark and update the Features Bitmap to 1111111111111112. This is 15 ones and a two. + # See here for the forum discussion: https://www.x.com/message/206214#206214 + items = options[:items] || [] + items.each_with_index do |item, index| + xml.tag! 'ExtData', 'Name' => "L_DESC#{index}", 'Value' => item[:description] + xml.tag! 'ExtData', 'Name' => "L_COST#{index}", 'Value' => amount(item[:amount]) + xml.tag! 'ExtData', 'Name' => "L_QTY#{index}", 'Value' => item[:quantity] || '1' + xml.tag! 'ExtData', 'Name' => "L_NAME#{index}", 'Value' => item[:name] + # Note: An ItemURL is supported in Paypal Express (different API), but not PayFlow Express, as far as I can tell. + # L_URLn nor L_ITEMURLn seem to work + end + if items.any? + xml.tag! 'ExtData', 'Name' => 'CURRENCY', 'Value' => options[:currency] || currency(money) + xml.tag! 'ExtData', 'Name' => "ITEMAMT", 'Value' => amount(options[:subtotal] || money) + end + xml.tag! 'DiscountAmt', amount(options[:discount]) if options[:discount] + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + + end + + xml.tag! 'Tender' do + add_paypal_details(xml, options) + end + end + end + + def add_paypal_details(xml, options) + xml.tag! 'PayPal' do + xml.tag! 'EMail', options[:email] unless options[:email].blank? + xml.tag! 'ReturnURL', options[:return_url] unless options[:return_url].blank? + xml.tag! 'CancelURL', options[:cancel_return_url] unless options[:cancel_return_url].blank? + xml.tag! 'NotifyURL', options[:notify_url] unless options[:notify_url].blank? + xml.tag! 'PayerID', options[:payer_id] unless options[:payer_id].blank? + xml.tag! 'Token', options[:token] unless options[:token].blank? + xml.tag! 'NoShipping', options[:no_shipping] ? '1' : '0' + xml.tag! 'AddressOverride', options[:address_override] ? '1' : '0' + xml.tag! 'ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank? + + # Customization of the payment page + xml.tag! 'PageStyle', options[:page_style] unless options[:page_style].blank? + xml.tag! 'HeaderImage', options[:header_image] unless options[:header_image].blank? + xml.tag! 'PayflowColor', options[:background_color] unless options[:background_color].blank? + # Note: HeaderImage and PayflowColor apply to both the new (as of 2010) and the old checkout experience + # HeaderBackColor and HeaderBorderColor apply only to the old checkout experience which is being phased out. + xml.tag! 'HeaderBackColor', options[:header_background_color] unless options[:header_background_color].blank? + xml.tag! 'HeaderBorderColor', options[:header_border_color] unless options[:header_border_color].blank? + xml.tag! 'ExtData', 'Name' => 'ALLOWNOTE', 'Value' => options[:allow_note] + end + end + + def build_response(success, message, response, options = {}) + PayflowExpressResponse.new(success, message, response, options) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express_uk.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express_uk.rb new file mode 100644 index 000000000..0469e65e6 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_express_uk.rb @@ -0,0 +1,15 @@ +require File.dirname(__FILE__) + '/payflow_express' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayflowExpressUkGateway < PayflowExpressGateway + self.default_currency = 'GBP' + self.partner = 'PayPalUk' + + self.supported_countries = ['GB'] + self.homepage_url = 'https://www.paypal.com/uk/cgi-bin/webscr?cmd=_additional-payment-overview-outside' + self.display_name = 'PayPal Express Checkout (UK)' + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_uk.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_uk.rb new file mode 100644 index 000000000..12250c1d5 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payflow_uk.rb @@ -0,0 +1,21 @@ +require File.dirname(__FILE__) + '/payflow' +require File.dirname(__FILE__) + '/payflow_express_uk' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayflowUkGateway < PayflowGateway + self.default_currency = 'GBP' + self.partner = 'PayPalUk' + + def express + @express ||= PayflowExpressUkGateway.new(@options) + end + + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :solo, :switch] + self.supported_countries = ['GB'] + self.homepage_url = 'https://www.paypal.com/uk/cgi-bin/webscr?cmd=_wp-pro-overview-outside' + self.display_name = 'PayPal Website Payments Pro (UK)' + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payment_express.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payment_express.rb new file mode 100644 index 000000000..7128cd191 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payment_express.rb @@ -0,0 +1,340 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + + # In NZ DPS supports ANZ, Westpac, National Bank, ASB and BNZ. + # In Australia DPS supports ANZ, NAB, Westpac, CBA, St George and Bank of South Australia. + # The Maybank in Malaysia is supported and the Citibank for Singapore. + class PaymentExpressGateway < Gateway + self.default_currency = 'NZD' + # PS supports all major credit cards; Visa, Mastercard, Amex, Diners, BankCard & JCB. + # Various white label cards can be accepted as well; Farmers, AirNZCard and Elders etc. + # Please note that not all acquirers and Eftpos networks can support some of these card types. + # VISA, Mastercard, Diners Club and Farmers cards are supported + # + # However, regular accounts with DPS only support VISA and Mastercard + self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ] + + self.supported_countries = %w[ AU MY NZ SG ZA GB US ] + + self.homepage_url = 'http://www.paymentexpress.com/' + self.display_name = 'PaymentExpress' + + self.live_url = self.test_url = 'https://sec.paymentexpress.com/pxpost.aspx' + + APPROVED = '1' + + TRANSACTIONS = { + :purchase => 'Purchase', + :credit => 'Refund', + :authorization => 'Auth', + :capture => 'Complete', + :validate => 'Validate' + } + + # We require the DPS gateway username and password when the object is created. + # + # The PaymentExpress gateway also supports a :use_custom_payment_token boolean option. + # If set to true the gateway will use BillingId for the Token type. If set to false, + # then the token will be sent as the DPS specified "DpsBillingId". This is per the documentation at + # http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#Tokenbilling + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Funds are transferred immediately. + # + # `payment_source` can be a usual ActiveMerchant credit_card object, or can also + # be a string of the `DpsBillingId` or `BillingId` which can be gotten through the + # store method. If you are using a `BillingId` instead of `DpsBillingId` you must + # also set the instance method `#use_billing_id_for_token` to true, see the `#store` + # method for an example of how to do this. + def purchase(money, payment_source, options = {}) + request = build_purchase_or_authorization_request(money, payment_source, options) + commit(:purchase, request) + end + + # NOTE: Perhaps in options we allow a transaction note to be inserted + # Verifies that funds are available for the requested card and amount and reserves the specified amount. + # See: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#Authcomplete + # + # `payment_source` can be a usual ActiveMerchant credit_card object or a token, see #purchased method + def authorize(money, payment_source, options = {}) + request = build_purchase_or_authorization_request(money, payment_source, options) + commit(:authorization, request) + end + + # Transfer pre-authorized funds immediately + # See: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#Authcomplete + def capture(money, identification, options = {}) + request = build_capture_or_credit_request(money, identification, options) + commit(:capture, request) + end + + # Refund funds to the card holder + def refund(money, identification, options = {}) + requires!(options, :description) + + request = build_capture_or_credit_request(money, identification, options) + commit(:credit, request) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + # Token Based Billing + # + # Instead of storing the credit card details locally, you can store them inside the + # Payment Express system and instead bill future transactions against a token. + # + # This token can either be specified by your code or autogenerated by the PaymentExpress + # system. The default is to let PaymentExpress generate the token for you and so use + # the `DpsBillingId`. If you do not pass in any option of the `billing_id`, then the store + # method will ask PaymentExpress to create a token for you. Additionally, if you are + # using the default `DpsBillingId`, you do not have to do anything extra in the + # initialization of your gateway object. + # + # To specify and use your own token, you need to do two things. + # + # Firstly, pass in a `:billing_id` as an option in the hash of this store method. No + # validation is done on this BillingId by PaymentExpress so you must ensure that it is unique. + # + # gateway.store(credit_card, {:billing_id => 'YourUniqueBillingId'}) + # + # Secondly, you will need to pass in the option `{:use_custom_payment_token => true}` when + # initializing your gateway instance, like so: + # + # gateway = ActiveMerchant::Billing::PaymentExpressGateway.new( + # :login => 'USERNAME', + # :password => 'PASSWORD', + # :use_custom_payment_token => true + # ) + # + # see: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#Tokenbilling + # + # Note, once stored, PaymentExpress does not support unstoring a stored card. + def store(credit_card, options = {}) + request = build_token_request(credit_card, options) + commit(:validate, request) + end + + private + + def use_custom_payment_token? + @options[:use_custom_payment_token] + end + + def build_purchase_or_authorization_request(money, payment_source, options) + result = new_transaction + + if payment_source.is_a?(String) + add_billing_token(result, payment_source) + else + add_credit_card(result, payment_source) + end + + add_amount(result, money, options) + add_invoice(result, options) + add_address_verification_data(result, options) + add_optional_elements(result, options) + result + end + + def build_capture_or_credit_request(money, identification, options) + result = new_transaction + + add_amount(result, money, options) + add_invoice(result, options) + add_reference(result, identification) + add_optional_elements(result, options) + result + end + + def build_token_request(credit_card, options) + result = new_transaction + add_credit_card(result, credit_card) + add_amount(result, 100, options) #need to make an auth request for $1 + add_token_request(result, options) + add_optional_elements(result, options) + result + end + + def add_credentials(xml) + xml.add_element("PostUsername").text = @options[:login] + xml.add_element("PostPassword").text = @options[:password] + end + + def add_reference(xml, identification) + xml.add_element("DpsTxnRef").text = identification + end + + def add_credit_card(xml, credit_card) + xml.add_element("CardHolderName").text = credit_card.name + xml.add_element("CardNumber").text = credit_card.number + xml.add_element("DateExpiry").text = format_date(credit_card.month, credit_card.year) + + if credit_card.verification_value? + xml.add_element("Cvc2").text = credit_card.verification_value + xml.add_element("Cvc2Presence").text = "1" + end + + if requires_start_date_or_issue_number?(credit_card) + xml.add_element("DateStart").text = format_date(credit_card.start_month, credit_card.start_year) unless credit_card.start_month.blank? || credit_card.start_year.blank? + xml.add_element("IssueNumber").text = credit_card.issue_number unless credit_card.issue_number.blank? + end + end + + def add_billing_token(xml, token) + if use_custom_payment_token? + xml.add_element("BillingId").text = token + else + xml.add_element("DpsBillingId").text = token + end + end + + def add_token_request(xml, options) + xml.add_element("BillingId").text = options[:billing_id] if options[:billing_id] + xml.add_element("EnableAddBillCard").text = 1 + end + + def add_amount(xml, money, options) + xml.add_element("Amount").text = amount(money) + xml.add_element("InputCurrency").text = options[:currency] || currency(money) + end + + def add_transaction_type(xml, action) + xml.add_element("TxnType").text = TRANSACTIONS[action] + end + + def add_invoice(xml, options) + xml.add_element("TxnId").text = options[:order_id].to_s.slice(0, 16) unless options[:order_id].blank? + xml.add_element("MerchantReference").text = options[:description].to_s.slice(0, 50) unless options[:description].blank? + end + + def add_address_verification_data(xml, options) + address = options[:billing_address] || options[:address] + return if address.nil? + + xml.add_element("EnableAvsData").text = 1 + xml.add_element("AvsAction").text = 1 + + xml.add_element("AvsStreetAddress").text = address[:address1] + xml.add_element("AvsPostCode").text = address[:zip] + end + + # The options hash may contain optional data which will be passed + # through the the specialized optional fields at PaymentExpress + # as follows: + # + # { + # :client_type => :web, # Possible values are: :web, :ivr, :moto, :unattended, :internet, or :recurring + # :txn_data1 => "String up to 255 characters", + # :txn_data2 => "String up to 255 characters", + # :txn_data3 => "String up to 255 characters" + # } + # + # +:client_type+, while not documented for PxPost, will be sent as + # the +ClientType+ XML element as described in the documentation for + # the PaymentExpress WebService: http://www.paymentexpress.com/Technical_Resources/Ecommerce_NonHosted/WebService#clientType + # (PaymentExpress have confirmed that this value works the same in PxPost). + # The value sent for +:client_type+ will be normalized and sent + # as one of the explicit values allowed by PxPost: + # + # :web => "Web" + # :ivr => "IVR" + # :moto => "MOTO" + # :unattended => "Unattended" + # :internet => "Internet" + # :recurring => "Recurring" + # + # If you set the +:client_type+ to any value not listed above, + # the ClientType element WILL NOT BE INCLUDED at all in the + # POST data. + # + # +:txn_data1+, +:txn_data2+, and +:txn_data3+ will be sent as + # +TxnData1+, +TxnData2+, and +TxnData3+, respectively, and are + # free form fields of the merchant's choosing, as documented here: + # http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#txndata + # + # These optional elements are added to all transaction types: + # +purchase+, +authorize+, +capture+, +refund+, +store+ + def add_optional_elements(xml, options) + if client_type = normalized_client_type(options[:client_type]) + xml.add_element("ClientType").text = client_type + end + + xml.add_element("TxnData1").text = options[:txn_data1].to_s.slice(0,255) unless options[:txn_data1].blank? + xml.add_element("TxnData2").text = options[:txn_data2].to_s.slice(0,255) unless options[:txn_data2].blank? + xml.add_element("TxnData3").text = options[:txn_data3].to_s.slice(0,255) unless options[:txn_data3].blank? + end + + def new_transaction + REXML::Document.new.add_element("Txn") + end + + # Take in the request and post it to DPS + def commit(action, request) + add_credentials(request) + add_transaction_type(request, action) + + # Parse the XML response + response = parse( ssl_post(self.live_url, request.to_s) ) + + # Return a response + PaymentExpressResponse.new(response[:success] == APPROVED, response[:card_holder_help_text], response, + :test => response[:test_mode] == '1', + :authorization => response[:dps_txn_ref] + ) + end + + # Response XML documentation: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#XMLTxnOutput + def parse(xml_string) + response = {} + + xml = REXML::Document.new(xml_string) + + # Gather all root elements such as HelpText + xml.elements.each('Txn/*') do |element| + response[element.name.underscore.to_sym] = element.text unless element.name == 'Transaction' + end + + # Gather all transaction elements and prefix with "account_" + # So we could access the MerchantResponseText by going + # response[account_merchant_response_text] + xml.elements.each('Txn/Transaction/*') do |element| + response[element.name.underscore.to_sym] = element.text + end + + response + end + + def format_date(month, year) + "#{format(month, :two_digits)}#{format(year, :two_digits)}" + end + + def normalized_client_type(client_type_from_options) + case client_type_from_options.to_s.downcase + when 'web' then "Web" + when 'ivr' then "IVR" + when 'moto' then "MOTO" + when 'unattended' then "Unattended" + when 'internet' then "Internet" + when 'recurring' then "Recurring" + else nil + end + end + end + + class PaymentExpressResponse < Response + # add a method to response so we can easily get the token + # for Validate transactions + def token + @params["billing_id"] || @params["dps_billing_id"] + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paymill.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paymill.rb new file mode 100644 index 000000000..05b14bbc9 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paymill.rb @@ -0,0 +1,179 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaymillGateway < Gateway + self.supported_countries = %w(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) + + self.supported_cardtypes = [:visa, :master] + self.homepage_url = 'https://paymill.com' + self.display_name = 'PAYMILL' + self.money_format = :cents + self.default_currency = 'EUR' + + def initialize(options = {}) + requires!(options, :public_key, :private_key) + super + end + + def purchase(money, payment_method, options = {}) + case payment_method + when String + purchase_with_token(money, payment_method, options) + else + MultiResponse.run do |r| + r.process { save_card(payment_method) } + r.process { purchase_with_token(money, r.authorization, options) } + end + end + end + + def authorize(money, payment_method, options = {}) + case payment_method + when String + authorize_with_token(money, payment_method, options) + else + MultiResponse.run do |r| + r.process { save_card(payment_method) } + r.process { authorize_with_token(money, r.authorization, options) } + end + end + end + + def capture(money, authorization, options = {}) + post = {} + + add_amount(post, money, options) + post[:preauthorization] = preauth(authorization) + post[:description] = options[:description] + commit(:post, 'transactions', post) + end + + def refund(money, authorization, options={}) + post = {} + + post[:amount] = amount(money) + post[:description] = options[:description] + commit(:post, "refunds/#{transaction_id(authorization)}", post) + end + + def store(credit_card, options={}) + save_card(credit_card) + end + + private + + def add_credit_card(post, credit_card) + post['account.number'] = credit_card.number + post['account.expiry.month'] = sprintf("%.2i", credit_card.month) + post['account.expiry.year'] = sprintf("%.4i", credit_card.year) + post['account.verification'] = credit_card.verification_value + end + + def headers + { 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:private_key]}:X").chomp) } + end + + def commit(method, url, parameters=nil) + begin + raw_response = ssl_request(method, "https://api.paymill.com/v2/#{url}", post_data(parameters), headers) + rescue ResponseError => e + parsed = JSON.parse(e.response.body) + return Response.new(false, parsed['error'], parsed, {}) + end + + response_from(raw_response) + end + + def response_from(raw_response) + parsed = JSON.parse(raw_response) + + options = { + :authorization => authorization_from(parsed), + :test => (parsed['mode'] == 'test'), + } + + Response.new(true, 'Transaction approved', parsed, options) + end + + def authorization_from(parsed_response) + [ + parsed_response['data']['id'], + parsed_response['data']['preauthorization'].try(:[], 'id') + ].join(";") + end + + def purchase_with_token(money, card_token, options) + post = {} + + add_amount(post, money, options) + post[:token] = card_token + post[:description] = options[:description] + commit(:post, 'transactions', post) + end + + def authorize_with_token(money, card_token, options) + post = {} + + add_amount(post, money, options) + post[:token] = card_token + commit(:post, 'preauthorizations', post) + end + + def save_card(credit_card) + post = {} + + add_credit_card(post, credit_card) + post['channel.id'] = @options[:public_key] + post['jsonPFunction'] = 'jsonPFunction' + post['transaction.mode'] = (test? ? 'CONNECTOR_TEST' : 'LIVE') + + begin + raw_response = ssl_request(:get, "#{save_card_url}?#{post_data(post)}", nil, {}) + rescue ResponseError => e + return Response.new(false, e.response.body, e.response.body, {}) + end + + response_for_save_from(raw_response) + end + + def response_for_save_from(raw_response) + options = { :test => test? } + + parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, '')) + if parsed['error'] + succeeded = false + message = parsed['error']['message'] + else + succeeded = parsed['transaction']['processing']['result'] == 'ACK' + message = parsed['transaction']['processing']['return']['message'] + options[:authorization] = parsed['transaction']['identification']['uniqueId'] if succeeded + end + + Response.new(succeeded, message, parsed, options) + end + + def save_card_url + (test? ? 'https://test-token.paymill.com' : 'https://token-v2.paymill.de') + end + + def post_data(params) + no_blanks = params.reject { |key, value| value.blank? } + no_blanks.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def add_amount(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + end + + def preauth(authorization) + authorization.split(";").last + end + + def transaction_id(authorization) + authorization.split(';').first + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal.rb new file mode 100644 index 000000000..49235b852 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal.rb @@ -0,0 +1,106 @@ +require File.dirname(__FILE__) + '/paypal/paypal_common_api' +require File.dirname(__FILE__) + '/paypal/paypal_recurring_api' +require File.dirname(__FILE__) + '/paypal_express' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaypalGateway < Gateway + include PaypalCommonAPI + include PaypalRecurringApi + + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = ['US'] + self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside' + self.display_name = 'PayPal Website Payments Pro (US)' + + def authorize(money, credit_card_or_referenced_id, options = {}) + requires!(options, :ip) + commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Authorization', money, credit_card_or_referenced_id, options) + end + + def purchase(money, credit_card_or_referenced_id, options = {}) + requires!(options, :ip) + commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Sale', money, credit_card_or_referenced_id, options) + end + + def express + @express ||= PaypalExpressGateway.new(@options) + end + + private + + def define_transaction_type(transaction_arg) + if transaction_arg.is_a?(String) + return 'DoReferenceTransaction' + else + return 'DoDirectPayment' + end + end + + def build_sale_or_authorization_request(action, money, credit_card_or_referenced_id, options) + transaction_type = define_transaction_type(credit_card_or_referenced_id) + reference_id = credit_card_or_referenced_id if transaction_type == "DoReferenceTransaction" + + billing_address = options[:billing_address] || options[:address] + currency_code = options[:currency] || currency(money) + + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! transaction_type + 'Req', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! transaction_type + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:' + transaction_type + 'RequestDetails' do + xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction' + xml.tag! 'n2:PaymentAction', action + add_payment_details(xml, money, currency_code, options) + add_credit_card(xml, credit_card_or_referenced_id, billing_address, options) unless transaction_type == 'DoReferenceTransaction' + xml.tag! 'n2:IPAddress', options[:ip] + end + end + end + + xml.target! + end + + def add_credit_card(xml, credit_card, address, options) + xml.tag! 'n2:CreditCard' do + xml.tag! 'n2:CreditCardType', credit_card_type(card_brand(credit_card)) + xml.tag! 'n2:CreditCardNumber', credit_card.number + xml.tag! 'n2:ExpMonth', format(credit_card.month, :two_digits) + xml.tag! 'n2:ExpYear', format(credit_card.year, :four_digits) + xml.tag! 'n2:CVV2', credit_card.verification_value + + if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s) + xml.tag! 'n2:StartMonth', format(credit_card.start_month, :two_digits) unless credit_card.start_month.blank? + xml.tag! 'n2:StartYear', format(credit_card.start_year, :four_digits) unless credit_card.start_year.blank? + xml.tag! 'n2:IssueNumber', format(credit_card.issue_number, :two_digits) unless credit_card.issue_number.blank? + end + + xml.tag! 'n2:CardOwner' do + xml.tag! 'n2:PayerName' do + xml.tag! 'n2:FirstName', credit_card.first_name + xml.tag! 'n2:LastName', credit_card.last_name + end + + xml.tag! 'n2:Payer', options[:email] + add_address(xml, 'n2:Address', address) + end + end + end + + def credit_card_type(type) + case type + when 'visa' then 'Visa' + when 'master' then 'MasterCard' + when 'discover' then 'Discover' + when 'american_express' then 'Amex' + when 'switch' then 'Switch' + when 'solo' then 'Solo' + end + end + + def build_response(success, message, response, options = {}) + Response.new(success, message, response, options) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb new file mode 100644 index 000000000..4e8697b0d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb @@ -0,0 +1,654 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This module is included in both PaypalGateway and PaypalExpressGateway + module PaypalCommonAPI + API_VERSION = '72' + + URLS = { + :test => { :certificate => 'https://api.sandbox.paypal.com/2.0/', + :signature => 'https://api-3t.sandbox.paypal.com/2.0/' }, + :live => { :certificate => 'https://api-aa.paypal.com/2.0/', + :signature => 'https://api-3t.paypal.com/2.0/' } + } + + PAYPAL_NAMESPACE = 'urn:ebay:api:PayPalAPI' + EBAY_NAMESPACE = 'urn:ebay:apis:eBLBaseComponents' + + ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' + } + CREDENTIALS_NAMESPACES = { 'xmlns' => PAYPAL_NAMESPACE, + 'xmlns:n1' => EBAY_NAMESPACE, + 'env:mustUnderstand' => '0' + } + + AUSTRALIAN_STATES = { + 'ACT' => 'Australian Capital Territory', + 'NSW' => 'New South Wales', + 'NT' => 'Northern Territory', + 'QLD' => 'Queensland', + 'SA' => 'South Australia', + 'TAS' => 'Tasmania', + 'VIC' => 'Victoria', + 'WA' => 'Western Australia' + } + + SUCCESS_CODES = [ 'Success', 'SuccessWithWarning' ] + + FRAUD_REVIEW_CODE = "11610" + + def self.included(base) + base.default_currency = 'USD' + base.cattr_accessor :pem_file + base.cattr_accessor :signature + base.live_url = URLS[:live][:signature] + base.test_url = URLS[:test][:signature] + end + + # The gateway must be configured with either your PayPal PEM file + # or your PayPal API Signature. Only one is required. + # + # :pem The text of your PayPal PEM file. Note + # this is not the path to file, but its + # contents. If you are only using one PEM + # file on your site you can declare it + # globally and then you won't need to + # include this option + # + # :signature The text of your PayPal signature. + # If you are only using one API Signature + # on your site you can declare it + # globally and then you won't need to + # include this option + def initialize(options = {}) + requires!(options, :login, :password) + + headers = {'X-PP-AUTHORIZATION' => options.delete(:auth_signature), 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} if options[:auth_signature] + options = { + :pem => pem_file, + :signature => signature, + :headers => headers || {} + }.update(options) + + + if options[:pem].blank? && options[:signature].blank? + raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal" + end + + super(options) + end + + def reauthorize(money, authorization, options = {}) + commit 'DoReauthorization', build_reauthorize_request(money, authorization, options) + end + + def capture(money, authorization, options = {}) + commit 'DoCapture', build_capture_request(money, authorization, options) + end + + # Transfer money to one or more recipients. + # + # gateway.transfer 1000, 'bob@example.com', + # :subject => "The money I owe you", :note => "Sorry it's so late" + # + # gateway.transfer [1000, 'fred@example.com'], + # [2450, 'wilma@example.com', :note => 'You will receive another payment on 3/24'], + # [2000, 'barney@example.com'], + # :subject => "Your Earnings", :note => "Thanks for your business." + # + def transfer(*args) + commit 'MassPay', build_mass_pay_request(*args) + end + + def void(authorization, options = {}) + commit 'DoVoid', build_void_request(authorization, options) + end + + # Refunds a transaction. + # + # For a full refund pass nil for the amount: + # + # gateway.refund nil, 'G39883289DH238' + # + # This will automatically make the :refund_type be "Full". + # + # For a partial refund just pass the amount as usual: + # + # gateway.refund 100, 'UBU83983N920' + # + def refund(money, identification, options = {}) + commit 'RefundTransaction', build_refund_request(money, identification, options) + end + + def credit(money, identification, options = {}) + deprecated Gateway::CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + # ==== For full documentation see {Paypal API Reference:}[https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_soap_r_DoReferenceTransaction] + # ==== Parameter: + # * :money -- (Required) The amount of this new transaction, + # required fo the payment details portion of this request + # + # ==== Options: + # * :reference_id -- (Required) A transaction ID from a previous purchase, such as a credit card charge using the DoDirectPayment API, or a billing agreement ID. + # * :payment_action -- (Optional) How you want to obtain payment. It is one of the following values: + # + # Authorization – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture. + # Sale – This is a final sale for which you are requesting payment. + # + # * :ip_address -- (Optional) IP address of the buyer’s browser. + # Note: PayPal records this IP addresses as a means to detect possible fraud. + # * :req_confirm_shipping -- Whether you require that the buyer’s shipping address on file with PayPal be a confirmed address. You must have permission from PayPal to not require a confirmed address. It is one of the following values: + # + # 0 – You do not require that the buyer’s shipping address be a confirmed address. + # 1 – You require that the buyer’s shipping address be a confirmed address. + # + # * :merchant_session_id -- (Optional) Your buyer session identification token. + # * :return_fmf_details -- (Optional) Flag to indicate whether you want the results returned by Fraud Management Filters. By default, you do not receive this information. It is one of the following values: + # + # 0 – Do not receive FMF details (default) + # 1 – Receive FMF details + # + # * :soft_descriptor -- (Optional) Per transaction description of the payment that is passed to the consumer’s credit card statement. If the API request provides a value for the soft descriptor field, the full descriptor displayed on the buyer’s statement has the following format: + # + # <1 space> + # The soft descriptor can contain only the following characters: + # + # Alphanumeric characters + # - (dash) + # * (asterisk) + # . (period) + # {space} + # + def reference_transaction(money, options = {}) + requires!(options, :reference_id) + commit 'DoReferenceTransaction', build_reference_transaction_request(money, options) + end + + def transaction_details(transaction_id) + commit 'GetTransactionDetails', build_get_transaction_details(transaction_id) + end + + # ==== For full documentation see {PayPal API Reference}[https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_soap_r_TransactionSearch] + # ==== Options: + # * :payer -- (Optional) Search by the buyer’s email address. + # * :receipt_id -- (Optional) Search by the PayPal Account Optional receipt ID. + # * :receiver -- (Optional) Search by the receiver’s email address. If the merchant account has only one email address, this is the primary email. It can also be a non-primary email address. + # * :transaction_id -- (Optional) Search by the transaction ID. The returned results are from the merchant’s transaction records. + # * :invoice_id -- (Optional) Search by invoice identification key, as set by you for the original transaction. This field searches the records for items the merchant sells, not the items purchased. + # * :card_number -- (Optional) Search by credit card number, as set by you for the original transaction. This field searches the records for items the merchant sells, not the items purchased. + # * :auction_item_number -- (Optional) Search by auction item number of the purchased goods. + # * :transaction_class -- (Optional) Search by classification of transaction. Some kinds of possible classes of transactions are not searchable with this field. You cannot search for bank transfer withdrawals, for example. It is one of the following values: + # All – All transaction classifications + # Sent – Only payments sent + # Received – Only payments received + # MassPay – Only mass payments + # MoneyRequest – Only money requests + # FundsAdded – Only funds added to balance + # FundsWithdrawn – Only funds withdrawn from balance + # Referral – Only transactions involving referrals + # Fee – Only transactions involving fees + # Subscription – Only transactions involving subscriptions + # Dividend – Only transactions involving dividends + # Billpay – Only transactions involving BillPay Transactions + # Refund – Only transactions involving funds + # CurrencyConversions – Only transactions involving currency conversions + # BalanceTransfer – Only transactions involving balance transfers + # Reversal – Only transactions involving BillPay reversals + # Shipping – Only transactions involving UPS shipping fees + # BalanceAffecting – Only transactions that affect the account balance + # ECheck – Only transactions involving eCheck + # + # * :currency_code -- (Optional) Search by currency code. + # * :status -- (Optional) Search by transaction status. It is one of the following values: + # One of: + # Pending – The payment is pending. The specific reason the payment is pending is returned by the GetTransactionDetails API PendingReason field. + # Processing – The payment is being processed. + # Success – The payment has been completed and the funds have been added successfully to your account balance. + # Denied – You denied the payment. This happens only if the payment was previously pending. + # Reversed – A payment was reversed due to a chargeback or other type of reversal. The funds have been removed from your account balance and returned to the buyer. + # + def transaction_search(options) + requires!(options, :start_date) + commit 'TransactionSearch', build_transaction_search(options) + end + + # ==== Parameters: + # * :return_all_currencies -- Either '1' or '0' + # 0 – Return only the balance for the primary currency holding. + # 1 – Return the balance for each currency holding. + # + def balance(return_all_currencies = false) + clean_currency_argument = case return_all_currencies + when 1, '1' , true; '1' + else + '0' + end + commit 'GetBalance', build_get_balance(clean_currency_argument) + end + + # DoAuthorization takes the transaction_id returned when you call + # DoExpressCheckoutPayment with a PaymentAction of 'Order'. + # When you did that, you created an order authorization subject to settlement + # with PayPal DoAuthorization and DoCapture + # + # ==== Parameters: + # * :transaction_id -- The ID returned by DoExpressCheckoutPayment with a PaymentAction of 'Order'. + # * :money -- The amount of money to be authorized for this purchase. + # + def authorize_transaction(transaction_id, money, options = {}) + commit 'DoAuthorization', build_do_authorize(transaction_id, money, options) + end + + # The ManagePendingTransactionStatus API operation accepts or denys a + # pending transaction held by Fraud Management Filters. + # + # ==== Parameters: + # * :transaction_id -- The ID of the transaction held by Fraud Management Filters. + # * :action -- Either 'Accept' or 'Deny' + # + def manage_pending_transaction(transaction_id, action) + commit 'ManagePendingTransactionStatus', build_manage_pending_transaction_status(transaction_id, action) + end + + private + def build_request_wrapper(action, options = {}) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! action + 'Req', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! action + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + if options[:request_details] + xml.tag! 'n2:' + action + 'RequestDetails' do + yield(xml) + end + else + yield(xml) + end + end + end + xml.target! + end + + def build_do_authorize(transaction_id, money, options = {}) + build_request_wrapper('DoAuthorization') do |xml| + xml.tag! 'TransactionID', transaction_id + xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money) + end + end + + def build_reauthorize_request(money, authorization, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'DoReauthorizationReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'DoReauthorizationRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'AuthorizationID', authorization + xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money) + end + end + + xml.target! + end + + def build_capture_request(money, authorization, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'DoCaptureReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'DoCaptureRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'AuthorizationID', authorization + xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money) + xml.tag! 'CompleteType', options[:complete_type] || 'Complete' + xml.tag! 'InvoiceID', options[:order_id] unless options[:order_id].blank? + xml.tag! 'Note', options[:description] + end + end + + xml.target! + end + + def build_refund_request(money, identification, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'RefundTransactionReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'RefundTransactionRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'TransactionID', identification + xml.tag! 'Amount', amount(money), 'currencyID' => (options[:currency] || currency(money)) if money.present? + xml.tag! 'RefundType', (options[:refund_type] || (money.present? ? 'Partial' : 'Full')) + xml.tag! 'Memo', options[:note] unless options[:note].blank? + end + end + + xml.target! + end + + def build_void_request(authorization, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'DoVoidReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'DoVoidRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'AuthorizationID', authorization + xml.tag! 'Note', options[:description] + end + end + + xml.target! + end + + def build_mass_pay_request(*args) + default_options = args.last.is_a?(Hash) ? args.pop : {} + recipients = args.first.is_a?(Array) ? args : [args] + + xml = Builder::XmlMarkup.new + + xml.tag! 'MassPayReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'MassPayRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'EmailSubject', default_options[:subject] if default_options[:subject] + recipients.each do |money, recipient, options| + options ||= default_options + xml.tag! 'MassPayItem' do + xml.tag! 'ReceiverEmail', recipient + xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money) + xml.tag! 'Note', options[:note] if options[:note] + xml.tag! 'UniqueId', options[:unique_id] if options[:unique_id] + end + end + end + end + + xml.target! + end + + def build_manage_pending_transaction_status(transaction_id, action) + build_request_wrapper('ManagePendingTransactionStatus') do |xml| + xml.tag! 'TransactionID', transaction_id + xml.tag! 'Action', action + end + end + + def build_reference_transaction_request(money, options) + opts = options.dup + opts[:ip_address] ||= opts[:ip] + currency_code = opts[:currency] || currency(money) + reference_transaction_optional_fields = %w{ n2:ReferenceID n2:PaymentAction + n2:PaymentType n2:IPAddress + n2:ReqConfirmShipping n2:MerchantSessionId + n2:ReturnFMFDetails n2:SoftDescriptor } + build_request_wrapper('DoReferenceTransaction', :request_details => true) do |xml| + add_optional_fields(xml, reference_transaction_optional_fields, opts) + add_payment_details(xml, money, currency_code, opts) + end + end + + def build_get_transaction_details(transaction_id) + build_request_wrapper('GetTransactionDetails') do |xml| + xml.tag! 'TransactionID', transaction_id + end + end + + def build_transaction_search(options) + currency_code = options[:currency_code] + currency_code ||= currency(options[:amount]) if options[:amount] + transaction_search_optional_fields = %w{ Payer ReceiptID Receiver + TransactionID InvoiceID CardNumber + AuctionItemNumber TransactionClass + CurrencyCode Status ProfileID } + build_request_wrapper('TransactionSearch') do |xml| + xml.tag! 'StartDate', date_to_iso(options[:start_date]) + xml.tag! 'EndDate', date_to_iso(options[:end_date]) unless options[:end_date].blank? + add_optional_fields(xml, transaction_search_optional_fields, options) + xml.tag! 'Amount', localized_amount(options[:amount], currency_code), 'currencyID' => currency_code unless options[:amount].blank? + end + end + + + def build_get_balance(return_all_currencies) + build_request_wrapper('GetBalance') do |xml| + xml.tag! 'ReturnAllCurrencies', return_all_currencies unless return_all_currencies.nil? + end + end + + def parse(action, xml) + legacy_hash = legacy_parse(action, xml) + xml = strip_attributes(xml) + hash = Hash.from_xml(xml) + hash = hash.fetch('Envelope').fetch('Body').fetch("#{action}Response") + hash = hash["#{action}ResponseDetails"] if hash["#{action}ResponseDetails"] + + legacy_hash.merge(hash) + rescue IndexError + legacy_hash.merge(hash['Envelope']['Body']) + end + + def strip_attributes(xml) + xml = REXML::Document.new(xml) + REXML::XPath.each(xml, '//SOAP-ENV:Envelope//*[@*]') do |el| + el.attributes.each_attribute { |a| a.remove } + end + xml.to_s + end + + def legacy_parse(action, xml) + response = {} + + error_messages = [] + error_codes = [] + + xml = REXML::Document.new(xml) + if root = REXML::XPath.first(xml, "//#{action}Response") + root.elements.each do |node| + case node.name + when 'Errors' + short_message = nil + long_message = nil + + node.elements.each do |child| + case child.name + when "LongMessage" + long_message = child.text unless child.text.blank? + when "ShortMessage" + short_message = child.text unless child.text.blank? + when "ErrorCode" + error_codes << child.text unless child.text.blank? + end + end + + if message = long_message || short_message + error_messages << message + end + else + legacy_parse_element(response, node) + end + end + response[:message] = error_messages.uniq.join(". ") unless error_messages.empty? + response[:error_codes] = error_codes.uniq.join(",") unless error_codes.empty? + elsif root = REXML::XPath.first(xml, "//SOAP-ENV:Fault") + legacy_parse_element(response, root) + response[:message] = "#{response[:faultcode]}: #{response[:faultstring]} - #{response[:detail]}" + end + + response + end + + def legacy_parse_element(response, node) + if node.has_elements? + node.elements.each{|e| legacy_parse_element(response, e) } + else + response[node.name.underscore.to_sym] = node.text + node.attributes.each do |k, v| + response["#{node.name.underscore}_#{k.underscore}".to_sym] = v if k == 'currencyID' + end + end + end + + def build_request(body) + xml = Builder::XmlMarkup.new + + xml.instruct! + xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do + xml.tag! 'env:Header' do + add_credentials(xml) unless @options[:headers] && @options[:headers]['X-PP-AUTHORIZATION'] + end + + xml.tag! 'env:Body' do + xml << body + end + end + xml.target! + end + + def add_credentials(xml) + xml.tag! 'RequesterCredentials', CREDENTIALS_NAMESPACES do + xml.tag! 'n1:Credentials' do + xml.tag! 'n1:Username', @options[:login] + xml.tag! 'n1:Password', @options[:password] + xml.tag! 'n1:Subject', @options[:subject] + xml.tag! 'n1:Signature', @options[:signature] unless @options[:signature].blank? + end + end + end + + def add_address(xml, element, address) + return if address.nil? + xml.tag! element do + xml.tag! 'n2:Name', address[:name] + xml.tag! 'n2:Street1', address[:address1] + xml.tag! 'n2:Street2', address[:address2] + xml.tag! 'n2:CityName', address[:city] + xml.tag! 'n2:StateOrProvince', address[:state].blank? ? 'N/A' : address[:state] + xml.tag! 'n2:Country', address[:country] + xml.tag! 'n2:Phone', address[:phone] unless address[:phone].blank? + xml.tag! 'n2:PostalCode', address[:zip] + end + end + + def add_payment_details_items_xml(xml, options, currency_code) + options[:items].each do |item| + xml.tag! 'n2:PaymentDetailsItem' do + xml.tag! 'n2:Name', item[:name] + xml.tag! 'n2:Number', item[:number] + xml.tag! 'n2:Quantity', item[:quantity] + if item[:amount] + xml.tag! 'n2:Amount', localized_amount(item[:amount], currency_code), 'currencyID' => currency_code + end + xml.tag! 'n2:Description', item[:description] + xml.tag! 'n2:ItemURL', item[:url] + xml.tag! 'n2:ItemCategory', item[:category] if item[:category] + end + end + end + + def add_payment_details(xml, money, currency_code, options = {}) + xml.tag! 'n2:PaymentDetails' do + xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code + + # All of the values must be included together and add up to the order total + if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) } + xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code + xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code),'currencyID' => currency_code + xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code),'currencyID' => currency_code + xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code + end + + xml.tag! 'n2:InsuranceTotal', localized_amount(options[:insurance_total], currency_code),'currencyID' => currency_code unless options[:insurance_total].blank? + xml.tag! 'n2:ShippingDiscount', localized_amount(options[:shipping_discount], currency_code),'currencyID' => currency_code unless options[:shipping_discount].blank? + xml.tag! 'n2:InsuranceOptionOffered', options[:insurance_option_offered] if options.has_key?(:insurance_option_offered) + + xml.tag! 'n2:OrderDescription', options[:description] unless options[:description].blank? + + # Custom field Character length and limitations: 256 single-byte alphanumeric characters + xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank? + + xml.tag! 'n2:InvoiceID', (options[:order_id] || options[:invoice_id]) unless (options[:order_id] || options[:invoice_id]).blank? + xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank? + + # The notify URL applies only to DoExpressCheckoutPayment. + # This value is ignored when set in SetExpressCheckout or GetExpressCheckoutDetails + xml.tag! 'n2:NotifyURL', options[:notify_url] unless options[:notify_url].blank? + + add_address(xml, 'n2:ShipToAddress', options[:shipping_address]) unless options[:shipping_address].blank? + + add_payment_details_items_xml(xml, options, currency_code) unless options[:items].blank? + + add_express_only_payment_details(xml, options) if options[:express_request] + + # Any value other than Y – This is not a recurring transaction + # To pass Y in this field, you must have established a billing agreement with + # the buyer specifying the amount, frequency, and duration of the recurring payment. + # requires version 80.0 of the API + xml.tag! 'n2:Recurring', options[:recurring] unless options[:recurring].blank? + end + end + + def add_express_only_payment_details(xml, options = {}) + add_optional_fields(xml, + %w{n2:NoteText n2:SoftDescriptor + n2:TransactionId n2:AllowedPaymentMethodType + n2:PaymentRequestID n2:PaymentAction}, + options) + end + + def add_optional_fields(xml, optional_fields, options = {}) + optional_fields.each do |optional_text_field| + if optional_text_field =~ /(\w+:)(\w+)/ + ns = $1 + field = $2 + field_as_symbol = field.underscore.to_sym + else + ns = '' + field = optional_text_field + field_as_symbol = optional_text_field.underscore.to_sym + end + xml.tag! ns + field, options[field_as_symbol] unless options[field_as_symbol].blank? + end + xml + end + + def endpoint_url + URLS[test? ? :test : :live][@options[:signature].blank? ? :certificate : :signature] + end + + def commit(action, request) + response = parse(action, ssl_post(endpoint_url, build_request(request), @options[:headers])) + + build_response(successful?(response), message_from(response), response, + :test => test?, + :authorization => authorization_from(response), + :fraud_review => fraud_review?(response), + :avs_result => { :code => response[:avs_code] }, + :cvv_result => response[:cvv2_code] + ) + end + + def fraud_review?(response) + response[:error_codes] == FRAUD_REVIEW_CODE + end + + def authorization_from(response) + ( + response[:transaction_id] || + response[:authorization_id] || + response[:refund_transaction_id] || + response[:billing_agreement_id] + ) + end + + def successful?(response) + SUCCESS_CODES.include?(response[:ack]) + end + + def message_from(response) + response[:message] || response[:ack] + end + + def date_to_iso(date) + (date.is_a?(Date) ? date.to_time : date).utc.iso8601 + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb new file mode 100644 index 000000000..7cd7aadbb --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb @@ -0,0 +1,61 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaypalExpressResponse < Response + def email + info['Payer'] + end + + def info + (@params['PayerInfo']||{}) + end + + def details + (@params['PaymentDetails']||{}) + end + + def name + payer = (info['PayerName']||{}) + [payer['FirstName'], payer['MiddleName'], payer['LastName']].compact.join(' ') + end + + def token + @params['Token'] + end + + def payer_id + info['PayerID'] + end + + def payer_country + info['PayerCountry'] + end + + # PayPal returns a contact telephone number only if your Merchant account + # profile settings require that the buyer enter one. + def contact_phone + @params['ContactPhone'] + end + + def address + address = (details['ShipToAddress']||{}) + { 'name' => address['Name'], + 'company' => info['PayerBusiness'], + 'address1' => address['Street1'], + 'address2' => address['Street2'], + 'city' => address['CityName'], + 'state' => address['StateOrProvince'], + 'country' => address['Country'], + 'zip' => address['PostalCode'], + 'phone' => (contact_phone || address['Phone']) + } + end + + def shipping + shipping = (@params['UserSelectedOptions']||{}) + { 'amount' => shipping['ShippingOptionAmount'], + 'name' => shipping['ShippingOptionName'] + } + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb new file mode 100644 index 000000000..157037cb1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb @@ -0,0 +1,248 @@ +require File.dirname(__FILE__) + '/paypal_common_api' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This module is included in both PaypalGateway and PaypalExpressGateway + module PaypalRecurringApi + PAYPAL_NAMESPACE = ActiveMerchant::Billing::PaypalCommonAPI::PAYPAL_NAMESPACE + API_VERSION = ActiveMerchant::Billing::PaypalCommonAPI::API_VERSION + EBAY_NAMESPACE = ActiveMerchant::Billing::PaypalCommonAPI::EBAY_NAMESPACE + # Create a recurring payment. + # + # This transaction creates a recurring payment profile + # ==== Parameters + # + # * money -- The amount to be charged to the customer at each interval as an Integer value in cents. + # * credit_card -- The CreditCard details for the transaction. + # * options -- A hash of parameters. + # + # ==== Options + # + # * :period -- [Day, Week, SemiMonth, Month, Year] default: Month + # * :frequency -- a number + # * :cycles -- Limit to certain # of cycles (OPTIONAL) + # * :start_date -- When does the charging starts (REQUIRED) + # * :description -- The description to appear in the profile (REQUIRED) + + def recurring(amount, credit_card, options = {}) + options[:credit_card] = credit_card + options[:amount] = amount + requires!(options, :description, :start_date, :period, :frequency, :amount) + commit 'CreateRecurringPaymentsProfile', build_create_profile_request(options) + end + + # Update a recurring payment's details. + # + # This transaction updates an existing Recurring Billing Profile + # 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 the type of plan you're subscribed with paypal. Web Payment Pro + # seems to have the ability to update the most field. + # + # ==== Parameters + # + # * options -- A hash of parameters. + # + # ==== Options + # + # * :profile_id -- A string containing the :profile_id + # of the recurring payment already in place for a given credit card. (REQUIRED) + def update_recurring(options={}) + requires!(options, :profile_id) + opts = options.dup + commit 'UpdateRecurringPaymentsProfile', build_change_profile_request(opts.delete(:profile_id), opts) + end + + # Cancel a recurring payment. + # + # This transaction cancels an existing recurring billing profile. Your account must have recurring billing enabled + # and the subscription must have already been created previously by calling recurring() + # + # ==== Parameters + # + # * profile_id -- A string containing the +profile_id+ of the + # recurring payment already in place for a given credit card. (REQUIRED) + # * options -- A hash with extra info ('note' for ex.) + def cancel_recurring(profile_id, options = {}) + raise_error_if_blank('profile_id', profile_id) + commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Cancel', options) + end + + # Get Subscription Status of a recurring payment profile. + # + # ==== Parameters + # + # * profile_id -- A string containing the +profile_id+ of the + # recurring payment already in place for a given credit card. (REQUIRED) + def status_recurring(profile_id) + raise_error_if_blank('profile_id', profile_id) + commit 'GetRecurringPaymentsProfileDetails', build_get_profile_details_request(profile_id) + end + + # Suspends a recurring payment profile. + # + # ==== Parameters + # + # * profile_id -- A string containing the +profile_id+ of the + # recurring payment already in place for a given credit card. (REQUIRED) + def suspend_recurring(profile_id, options = {}) + raise_error_if_blank('profile_id', profile_id) + commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Suspend', options) + end + + # Reactivates a suspended recurring payment profile. + # + # ==== Parameters + # + # * profile_id -- A string containing the +profile_id+ of the + # recurring payment already in place for a given credit card. (REQUIRED) + def reactivate_recurring(profile_id, options = {}) + raise_error_if_blank('profile_id', profile_id) +commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Reactivate', options) + end + + # Bills outstanding amount to a recurring payment profile. + # + # ==== Parameters + # + # * profile_id -- A string containing the +profile_id+ of the + # recurring payment already in place for a given credit card. (REQUIRED) + def bill_outstanding_amount(profile_id, options = {}) + raise_error_if_blank('profile_id', profile_id) + commit 'BillOutstandingAmount', build_bill_outstanding_amount(profile_id, options) + end + + private + def raise_error_if_blank(field_name, field) + raise ArgumentError.new("Missing required parameter: #{field_name}") if field.blank? + end + def build_create_profile_request(options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'CreateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'CreateRecurringPaymentsProfileRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:CreateRecurringPaymentsProfileRequestDetails' do + xml.tag! 'Token', options[:token] unless options[:token].blank? + if options[:credit_card] + add_credit_card(xml, options[:credit_card], (options[:billing_address] || options[:address]), options) + end + xml.tag! 'n2:RecurringPaymentsProfileDetails' do + xml.tag! 'n2:BillingStartDate', (options[:start_date].is_a?(Date) ? options[:start_date].to_time : options[:start_date]).utc.iso8601 + xml.tag! 'n2:ProfileReference', options[:profile_reference] unless options[:profile_reference].blank? + end + xml.tag! 'n2:ScheduleDetails' do + xml.tag! 'n2:Description', options[:description] + xml.tag! 'n2:PaymentPeriod' do + xml.tag! 'n2:BillingPeriod', options[:period] || 'Month' + xml.tag! 'n2:BillingFrequency', options[:frequency] + xml.tag! 'n2:TotalBillingCycles', options[:total_billing_cycles] unless options[:total_billing_cycles].blank? + xml.tag! 'n2:Amount', amount(options[:amount]), 'currencyID' => options[:currency] || 'USD' + xml.tag! 'n2:TaxAmount', amount(options[:tax_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:tax_amount].blank? + xml.tag! 'n2:ShippingAmount', amount(options[:shipping_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:shipping_amount].blank? + end + if !options[:trial_amount].blank? + xml.tag! 'n2:TrialPeriod' do + xml.tag! 'n2:BillingPeriod', options[:trial_period] || 'Month' + xml.tag! 'n2:BillingFrequency', options[:trial_frequency] + xml.tag! 'n2:TotalBillingCycles', options[:trial_cycles] || 1 + xml.tag! 'n2:Amount', amount(options[:trial_amount]), 'currencyID' => options[:currency] || 'USD' + xml.tag! 'n2:TaxAmount', amount(options[:trial_tax_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:trial_tax_amount].blank? + xml.tag! 'n2:ShippingAmount', amount(options[:trial_shipping_amount] || 0), 'currencyID' => options[:currency] || 'USD' unless options[:trial_shipping_amount].blank? + end + end + if !options[:initial_amount].blank? + xml.tag! 'n2:ActivationDetails' do + xml.tag! 'n2:InitialAmount', amount(options[:initial_amount]), 'currencyID' => options[:currency] || 'USD' + xml.tag! 'n2:FailedInitialAmountAction', options[:continue_on_failure] ? 'ContinueOnFailure' : 'CancelOnFailure' + end + end + xml.tag! 'n2:MaxFailedPayments', options[:max_failed_payments] unless options[:max_failed_payments].blank? + xml.tag! 'n2:AutoBillOutstandingAmount', options[:auto_bill_outstanding] ? 'AddToNextBilling' : 'NoAutoBill' + end + end + end + end + xml.target! + end + + def build_get_profile_details_request(profile_id) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'GetRecurringPaymentsProfileDetailsReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'GetRecurringPaymentsProfileDetailsRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'ProfileID', profile_id + end + end + xml.target! + end + + def build_change_profile_request(profile_id, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'UpdateRecurringPaymentsProfileReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'UpdateRecurringPaymentsProfileRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:UpdateRecurringPaymentsProfileRequestDetails' do + xml.tag! 'ProfileID', profile_id + if options[:credit_card] + add_credit_card(xml, options[:credit_card], options[:address], options) + end + xml.tag! 'n2:Note', options[:note] unless options[:note].blank? + xml.tag! 'n2:Description', options[:description] unless options[:description].blank? + xml.tag! 'n2:ProfileReference', options[:reference] unless options[:reference].blank? + xml.tag! 'n2:AdditionalBillingCycles', options[:additional_billing_cycles] unless options[:additional_billing_cycles].blank? + xml.tag! 'n2:MaxFailedPayments', options[:max_failed_payments] unless options[:max_failed_payments].blank? + xml.tag! 'n2:AutoBillOutstandingAmount', options[:auto_bill_outstanding] ? 'AddToNextBilling' : 'NoAutoBill' + if options.has_key?(:amount) + xml.tag! 'n2:Amount', amount(options[:amount]), 'currencyID' => options[:currency] || 'USD' + end + if options.has_key?(:tax_amount) + xml.tag! 'n2:TaxAmount', amount(options[:tax_amount] || 0), 'currencyID' => options[:currency] || 'USD' + end + if options.has_key?(:start_date) + xml.tag! 'n2:BillingStartDate', (options[:start_date].is_a?(Date) ? options[:start_date].to_time : options[:start_date]).utc.iso8601 + end + if options.has_key?(:outstanding_balance) + xml.tag! 'n2:OutstandingBalance', amount(options[:outstanding_balance]), 'currencyID' => options[:currency] || 'USD' + end + end + end + end + xml.target! + end + + def build_manage_profile_request(profile_id, action, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'ManageRecurringPaymentsProfileStatusReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'ManageRecurringPaymentsProfileStatusRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:ManageRecurringPaymentsProfileStatusRequestDetails' do + xml.tag! 'ProfileID', profile_id + xml.tag! 'n2:Action', action + xml.tag! 'n2:Note', options[:note] unless options[:note].blank? + end + end + end + xml.target! + end + + def build_bill_outstanding_amount(profile_id, options) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'BillOutstandingAmountReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'BillOutstandingAmountRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:BillOutstandingAmountRequestDetails' do + xml.tag! 'ProfileID', profile_id + if options.has_key?(:amount) + xml.tag! 'n2:Amount', amount(options[:amount]), 'currencyID' => options[:currency] || 'USD' + end + xml.tag! 'n2:Note', options[:note] unless options[:note].blank? + end + end + end + xml.target! + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_bogus.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_bogus.rb new file mode 100644 index 000000000..79f7dcefa --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_bogus.rb @@ -0,0 +1,53 @@ +require File.dirname(__FILE__) + '/paypal/paypal_common_api' +require File.dirname(__FILE__) + '/paypal/paypal_express_response' +require File.dirname(__FILE__) + '/paypal_express_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaypalBogusGateway < BogusGateway + + REDIRECT_URL = "https://bogus.paypal.com" + + def setup_authorization money, options = {} + requires!(options, :return_url, :cancel_return_url) + + PaypalExpressResponse.new true, SUCCESS_MESSAGE, { Token: AUTHORIZATION }, test: true + end + + def setup_purchase money, options = {} + requires!(options, :return_url, :cancel_return_url) + + PaypalExpressResponse.new true, SUCCESS_MESSAGE, { Token: AUTHORIZATION }, test: true + end + + def authorize money, options = {} + requires!(options, :token, :payer_id) + + case normalize(options[:token]) + when '1' + PaypalExpressResponse.new false, FAILURE_MESSAGE, {:authorized_amount => money}, :test => true + else + PaypalExpressResponse.new true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION + end + end + + def purchase money, options = {} + requires!(options, :token, :payer_id) + + case normalize(options[:token]) + when '1' + PaypalExpressResponse.new false, FAILURE_MESSAGE, {:amount => money}, :test => true + else + PaypalExpressResponse.new true, SUCCESS_MESSAGE, {:amount => money}, :test => true, :authorization => AUTHORIZATION + end + end + + def redirect_url_for token + REDIRECT_URL + end + + end + end +end + + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_ca.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_ca.rb new file mode 100644 index 000000000..69c94ec3c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_ca.rb @@ -0,0 +1,13 @@ +require File.dirname(__FILE__) + '/paypal' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # The PayPal gateway for PayPal Website Payments Pro Canada only supports Visa and MasterCard + class PaypalCaGateway < PaypalGateway + self.supported_cardtypes = [:visa, :master] + self.supported_countries = ['CA'] + self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside' + self.display_name = 'PayPal Website Payments Pro (CA)' + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_digital_goods.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_digital_goods.rb new file mode 100644 index 000000000..ce5824cea --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_digital_goods.rb @@ -0,0 +1,43 @@ +require File.dirname(__FILE__) + '/paypal/paypal_common_api' +require File.dirname(__FILE__) + '/paypal/paypal_express_response' +require File.dirname(__FILE__) + '/paypal_express_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaypalDigitalGoodsGateway < PaypalExpressGateway + self.test_redirect_url = 'https://www.sandbox.paypal.com/incontext' + self.live_redirect_url = 'https://www.paypal.com/incontext' + + self.supported_countries = %w(AU CA CN FI GB ID IN IT MY NO NZ PH PL SE SG TH VN) + self.homepage_url = 'https://www.x.com/community/ppx/xspaces/digital_goods' + self.display_name = 'PayPal Express Checkout for Digital Goods' + + def redirect_url_for(token, options = {}) + "#{redirect_url}?token=#{token}&useraction=commit" + end + + # GATEWAY.setup_purchase(100, + # :ip => "127.0.0.1", + # :description => "Test Title", + # :return_url => "http://return.url", + # :cancel_return_url => "http://cancel.url", + # :items => [ { :name => "Charge", + # :number => "1", + # :quantity => "1", + # :amount => 100, + # :description => "Description", + # :category => "Digital" } ] ) + def build_setup_request(action, money, options) + requires!(options, :items) + raise ArgumentError, "Must include at least 1 Item" unless options[:items].length > 0 + options[:items].each do |item| + requires!(item, :name, :number, :quantity, :amount, :description, :category) + raise ArgumentError, "Each of the items must have the category 'Digital'" unless item[:category] == 'Digital' + end + + super + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express.rb new file mode 100644 index 000000000..4657a6f8b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express.rb @@ -0,0 +1,222 @@ +require File.dirname(__FILE__) + '/paypal/paypal_common_api' +require File.dirname(__FILE__) + '/paypal/paypal_express_response' +require File.dirname(__FILE__) + '/paypal/paypal_recurring_api' +require File.dirname(__FILE__) + '/paypal_express_common' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaypalExpressGateway < Gateway + include PaypalCommonAPI + include PaypalExpressCommon + include PaypalRecurringApi + + NON_STANDARD_LOCALE_CODES = { + 'DK' => 'da_DK', + 'IL' => 'he_IL', + 'ID' => 'id_ID', + 'JP' => 'jp_JP', + 'NO' => 'no_NO', + 'BR' => 'pt_BR', + 'RU' => 'ru_RU', + 'SE' => 'sv_SE', + 'TH' => 'th_TH', + 'TR' => 'tr_TR', + 'CN' => 'zh_CN', + 'HK' => 'zh_HK', + 'TW' => 'zh_TW' + } + + self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr' + self.supported_countries = ['US'] + self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside' + self.display_name = 'PayPal Express Checkout' + + def setup_authorization(money, options = {}) + requires!(options, :return_url, :cancel_return_url) + + commit 'SetExpressCheckout', build_setup_request('Authorization', money, options) + end + + def setup_purchase(money, options = {}) + requires!(options, :return_url, :cancel_return_url) + + commit 'SetExpressCheckout', build_setup_request('Sale', money, options) + end + + def details_for(token) + commit 'GetExpressCheckoutDetails', build_get_details_request(token) + end + + def authorize(money, options = {}) + requires!(options, :token, :payer_id) + + commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Authorization', money, options) + end + + def purchase(money, options = {}) + requires!(options, :token, :payer_id) + + commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Sale', money, options) + end + + def store(token, options = {}) + commit 'CreateBillingAgreement', build_create_billing_agreement_request(token, options) + end + + def authorize_reference_transaction(money, options = {}) + requires!(options, :reference_id, :payment_type, :invoice_id, :description, :ip) + + commit 'DoReferenceTransaction', build_reference_transaction_request('Authorization', money, options) + end + + def reference_transaction(money, options = {}) + requires!(options, :reference_id) + + commit 'DoReferenceTransaction', build_reference_transaction_request('Sale', money, options) + end + + private + def build_get_details_request(token) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'GetExpressCheckoutDetailsReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'GetExpressCheckoutDetailsRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'Token', token + end + end + + xml.target! + end + + def build_sale_or_authorization_request(action, money, options) + currency_code = options[:currency] || currency(money) + + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'DoExpressCheckoutPaymentReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'DoExpressCheckoutPaymentRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:DoExpressCheckoutPaymentRequestDetails' do + xml.tag! 'n2:PaymentAction', action + xml.tag! 'n2:Token', options[:token] + xml.tag! 'n2:PayerID', options[:payer_id] + add_payment_details(xml, money, currency_code, options) + end + end + end + + xml.target! + end + + def build_setup_request(action, money, options) + currency_code = options[:currency] || currency(money) + options[:payment_action] = action + options[:express_request] = true + options[:shipping_address] ||= options[:address] + + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'SetExpressCheckoutReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'SetExpressCheckoutRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:SetExpressCheckoutRequestDetails' do + xml.tag! 'n2:ReturnURL', options[:return_url] + xml.tag! 'n2:CancelURL', options[:cancel_return_url] + if options[:max_amount] + xml.tag! 'n2:MaxAmount', localized_amount(options[:max_amount], currency_code), 'currencyID' => currency_code + end + xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0' + xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0' + xml.tag! 'n2:LocaleCode', locale_code(options[:locale]) unless options[:locale].blank? + xml.tag! 'n2:BrandName', options[:brand_name] unless options[:brand_name].blank? + # Customization of the payment page + xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank? + xml.tag! 'n2:cpp-header-image', options[:header_image] unless options[:header_image].blank? + xml.tag! 'n2:cpp-header-border-color', options[:header_border_color] unless options[:header_border_color].blank? + xml.tag! 'n2:cpp-header-back-color', options[:header_background_color] unless options[:header_background_color].blank? + xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank? + if options[:allow_guest_checkout] + xml.tag! 'n2:SolutionType', 'Sole' + xml.tag! 'n2:LandingPage', options[:landing_page] || 'Billing' + end + xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank? + + if options[:billing_agreement] + xml.tag! 'n2:BillingAgreementDetails' do + xml.tag! 'n2:BillingType', options[:billing_agreement][:type] + xml.tag! 'n2:BillingAgreementDescription', options[:billing_agreement][:description] + xml.tag! 'n2:PaymentType', options[:billing_agreement][:payment_type] || 'InstantOnly' + end + end + + if !options[:allow_note].nil? + xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0' + end + xml.tag! 'n2:CallbackURL', options[:callback_url] unless options[:callback_url].blank? + + add_payment_details(xml, money, currency_code, options) + if options[:shipping_options] + options[:shipping_options].each do |shipping_option| + xml.tag! 'n2:FlatRateShippingOptions' do + xml.tag! 'n2:ShippingOptionIsDefault', shipping_option[:default] + xml.tag! 'n2:ShippingOptionAmount', localized_amount(shipping_option[:amount], currency_code), 'currencyID' => currency_code + xml.tag! 'n2:ShippingOptionName', shipping_option[:name] + end + end + end + + xml.tag! 'n2:CallbackTimeout', options[:callback_timeout] unless options[:callback_timeout].blank? + xml.tag! 'n2:CallbackVersion', options[:callback_version] unless options[:callback_version].blank? + + if options.has_key?(:allow_buyer_optin) + xml.tag! 'n2:BuyerEmailOptInEnable', (options[:allow_buyer_optin] ? '1' : '0') + end + end + end + end + + xml.target! + end + + def build_create_billing_agreement_request(token, options = {}) + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'CreateBillingAgreementReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'CreateBillingAgreementRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'Token', token + end + end + + xml.target! + end + + def build_reference_transaction_request(action, money, options) + currency_code = options[:currency] || currency(money) + + # I am not sure why it's set like this for express gateway + # but I don't want to break the existing behavior + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'DoReferenceTransactionReq', 'xmlns' => PAYPAL_NAMESPACE do + xml.tag! 'DoReferenceTransactionRequest', 'xmlns:n2' => EBAY_NAMESPACE do + xml.tag! 'n2:Version', API_VERSION + xml.tag! 'n2:DoReferenceTransactionRequestDetails' do + xml.tag! 'n2:ReferenceID', options[:reference_id] + xml.tag! 'n2:PaymentAction', action + xml.tag! 'n2:PaymentType', options[:payment_type] || 'Any' + add_payment_details(xml, money, currency_code, options) + xml.tag! 'n2:IPAddress', options[:ip] + end + end + end + + xml.target! + end + + def build_response(success, message, response, options = {}) + PaypalExpressResponse.new(success, message, response, options) + end + + def locale_code(country_code) + NON_STANDARD_LOCALE_CODES[country_code] || country_code + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express_common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express_common.rb new file mode 100644 index 000000000..47cf830d3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paypal_express_common.rb @@ -0,0 +1,30 @@ +module ActiveMerchant + module Billing + module PaypalExpressCommon + def self.included(base) + if base.respond_to?(:class_attribute) + base.class_attribute :test_redirect_url + base.class_attribute :live_redirect_url + else + base.class_inheritable_accessor :test_redirect_url + base.class_inheritable_accessor :live_redirect_url + end + base.live_redirect_url = 'https://www.paypal.com/cgibin/webscr' + end + + def redirect_url + test? ? test_redirect_url : live_redirect_url + end + + def redirect_url_for(token, options = {}) + options = {:review => true, :mobile => false}.update(options) + + cmd = options[:mobile] ? '_express-checkout-mobile' : '_express-checkout' + url = "#{redirect_url}?cmd=#{cmd}&token=#{token}" + url += '&useraction=commit' unless options[:review] + + url + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paystation.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paystation.rb new file mode 100644 index 000000000..af8518101 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/paystation.rb @@ -0,0 +1,199 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PaystationGateway < Gateway + + self.live_url = self.test_url = "https://www.paystation.co.nz/direct/paystation.dll" + + # an "error code" of "0" means "No error - transaction successful" + SUCCESSFUL_RESPONSE_CODE = '0' + + # an "error code" of "34" means "Future Payment Stored OK" + SUCCESSFUL_FUTURE_PAYMENT = '34' + + # TODO: check this with paystation + self.supported_countries = ['NZ'] + + # TODO: check this with paystation (amex and diners need to be enabled) + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club ] + + self.homepage_url = 'http://paystation.co.nz' + self.display_name = 'Paystation' + + self.default_currency = 'NZD' + self.money_format = :cents + + def initialize(options = {}) + requires!(options, :paystation_id, :gateway_id) + super + end + + def authorize(money, credit_card, options = {}) + post = new_request + + add_invoice(post, options) + add_amount(post, money, options) + + add_credit_card(post, credit_card) + + add_authorize_flag(post, options) + + commit(post) + end + + def capture(money, authorization_token, options = {}) + post = new_request + + add_invoice(post, options) + add_amount(post, money, options) + + add_authorization_token(post, authorization_token, options[:credit_card_verification]) + + commit(post) + end + + def purchase(money, payment_source, options = {}) + post = new_request + + add_invoice(post, options) + add_amount(post, money, options) + + if payment_source.is_a?(String) + add_token(post, payment_source) + else + add_credit_card(post, payment_source) + end + + add_customer_data(post, options) if options.has_key?(:customer) + + commit(post) + end + + def store(credit_card, options = {}) + post = new_request + + add_invoice(post, options) + add_credit_card(post, credit_card) + store_credit_card(post, options) + + commit(post) + end + + private + + def new_request + { + :pi => @options[:paystation_id], # paystation account id + :gi => @options[:gateway_id], # paystation gateway id + "2p" => "t", # two-party transaction type + :nr => "t", # -- redirect?? + :df => "yymm" # date format: optional sometimes, required others + } + end + + def add_customer_data(post, options) + post[:mc] = options[:customer] + end + + def add_invoice(post, options) + requires!(options, :order_id) + + post[:ms] = options[:order_id] # "Merchant Session", must be unique per request + post[:mo] = options[:invoice] # "Order Details", displayed in Paystation Admin + post[:mr] = options[:description] # "Merchant Reference Code", seen from Paystation Admin + end + + def add_credit_card(post, credit_card) + + post[:cn] = credit_card.number + post[:ct] = credit_card.brand + post[:ex] = format_date(credit_card.month, credit_card.year) + post[:cc] = credit_card.verification_value if credit_card.verification_value? + + end + + # bill a token (stored via "store") rather than a Credit Card + def add_token(post, token) + post[:fp] = "t" # turn on "future payments" - what paystation calls Token Billing + post[:ft] = token + end + + def store_credit_card(post, options) + + post[:fp] = "t" # turn on "future payments" - what paystation calls Token Billing + post[:fs] = "t" # tells paystation to store right now, not bill + post[:ft] = options[:token] if options[:token] # specify a token to use that, or let Paystation generate one + + end + + def add_authorize_flag(post, options) + post[:pa] = "t" # tells Paystation that this is a pre-auth authorisation payment (account must be in pre-auth mode) + end + + def add_authorization_token(post, auth_token, verification_value = nil) + post[:cp] = "t" # Capture Payment flag – tells Paystation this transaction should be treated as a capture payment + post[:cx] = auth_token + post[:cc] = verification_value + end + + def add_amount(post, money, options) + + post[:am] = amount(money) + post[:cu] = options[:currency] || currency(money) + + end + + def parse(xml_response) + response = {} + + xml = REXML::Document.new(xml_response) + + # for normal payments, the root node is + # for "future payments", it's + xml.elements.each("#{xml.root.name}/*") do |element| + response[element.name.underscore.to_sym] = element.text + end + + response + end + + def commit(post) + + post[:tm] = "T" if test? # test mode + + pstn_prefix_params = post.collect { |key, value| "pstn_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + + # need include paystation param as "initiator flag for payment engine" + data = ssl_post(self.live_url, "#{pstn_prefix_params}&paystation=_empty") + response = parse(data) + message = message_from(response) + + PaystationResponse.new(success?(response), message, response, + :test => (response[:tm] && response[:tm].downcase == "t"), + :authorization => response[:paystation_transaction_id] + ) + end + + def success?(response) + (response[:ec] == SUCCESSFUL_RESPONSE_CODE) || (response[:ec] == SUCCESSFUL_FUTURE_PAYMENT) + end + + def message_from(response) + response[:em] + end + + def format_date(month, year) + "#{format(year, :two_digits)}#{format(month, :two_digits)}" + end + + end + + class PaystationResponse < Response + # add a method to response so we can easily get the token + # for Validate transactions + def token + @params["future_payment_token"] + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payway.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payway.rb new file mode 100644 index 000000000..10777f7b0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/payway.rb @@ -0,0 +1,207 @@ +module ActiveMerchant + module Billing + class PaywayGateway < Gateway + self.live_url = self.test_url = 'https://ccapi.client.qvalent.com/payway/ccapi' + + self.supported_countries = [ 'AU' ] + self.supported_cardtypes = [ :visa, :master, :diners_club, :american_express, :bankcard ] + self.display_name = 'Pay Way' + self.homepage_url = 'http://www.payway.com.au' + self.default_currency = 'AUD' + self.money_format = :cents + + SUMMARY_CODES = { + '0' => 'Approved', + '1' => 'Declined', + '2' => 'Erred', + '3' => 'Rejected' + } + + RESPONSE_CODES= { + '00' => 'Completed Successfully', + '01' => 'Refer to card issuer', + '03' => 'Invalid merchant', + '04' => 'Pick-up card', + '05' => 'Do not honour', + '08' => 'Honour only with identification', + '12' => 'Invalid transaction', + '13' => 'Invalid amount', + '14' => 'Invalid card number (no such number)', + '30' => 'Format error', + '36' => 'Restricted card', + '41' => 'Lost card', + '42' => 'No universal card', + '43' => 'Stolen card', + '51' => 'Not sufficient funds', + '54' => 'Expired card', + '61' => 'Exceeds withdrawal amount limits', + '62' => 'Restricted card', + '65' => 'Exceeds withdrawal frequency limit', + '91' => 'Issuer or switch is inoperative', + '92' => 'Financial institution or intermediate network facility cannot be found for routing', + '94' => 'Duplicate transmission', + 'Q1' => 'Unknown Buyer', + 'Q2' => 'Transaction Pending', + 'Q3' => 'Payment Gateway Connection Error', + 'Q4' => 'Payment Gateway Unavailable', + 'Q5' => 'Invalid Transaction', + 'Q6' => 'Duplicate Transaction - requery to determine status', + 'QA' => 'Invalid parameters or Initialisation failed', + 'QB' => 'Order type not currently supported', + 'QC' => 'Invalid Order Type', + 'QD' => 'Invalid Payment Amount - Payment amount less than minimum/exceeds maximum allowed limit', + 'QE' => 'Internal Error', + 'QF' => 'Transaction Failed', + 'QG' => 'Unknown Customer Order Number', + 'QH' => 'Unknown Customer Username or Password', + 'QI' => 'Transaction incomplete - contact Westpac to confirm reconciliation', + 'QJ' => 'Invalid Client Certificate', + 'QK' => 'Unknown Customer Merchant', + 'QL' => 'Business Group not configured for customer', + 'QM' => 'Payment Instrument not configured for customer', + 'QN' => 'Configuration Error', + 'QO' => 'Missing Payment Instrument', + 'QP' => 'Missing Supplier Account', + 'QQ' => 'Invalid Credit Card Verification Number', + 'QR' => 'Transaction Retry', + 'QS' => 'Transaction Successful', + 'QT' => 'Invalid currency', + 'QU' => 'Unknown Customer IP Address', + 'QV' => 'Invalid Original Order Number specified for Refund, Refund amount exceeds capture amount, or Previous capture was not approved', + 'QW' => 'Invalid Reference Number', + 'QX' => 'Network Error has occurred', + 'QY' => 'Card Type Not Accepted', + 'QZ' => 'Zero value transaction' + } + + TRANSACTIONS = { + :authorize => 'preauth', + :purchase => 'capture', + :capture => 'captureWithoutAuth', + :status => 'query', + :refund => 'refund', + :store => 'registerAccount' + } + + def initialize(options={}) + @options = options + + @options[:merchant] ||= 'TEST' if test? + requires!(options, :username, :password, :merchant, :pem) + + @options[:eci] ||= 'SSL' + end + + def authorize(amount, payment_method, options={}) + requires!(options, :order_id) + + post = {} + add_payment_method(post, payment_method) + add_order(post, amount, options) + commit(:authorize, post) + end + + def capture(amount, authorization, options={}) + requires!(options, :order_id) + + post = {} + add_reference(post, authorization) + add_order(post, amount, options) + commit(:capture, post) + end + + def purchase(amount, payment_method, options={}) + requires!(options, :order_id) + + post = {} + add_payment_method(post, payment_method) + add_order(post, amount, options) + commit(:purchase, post) + end + + def refund(amount, authorization, options={}) + requires!(options, :order_id) + + post = {} + add_reference(post, authorization) + add_order(post, amount, options) + commit(:refund, post) + end + + def store(credit_card, options={}) + requires!(options, :billing_id) + + post = {} + add_payment_method(post, credit_card) + add_payment_method(post, options[:billing_id]) + commit(:store, post) + end + + def status(options={}) + requires!(options, :order_id) + + commit(:status, 'customer.orderNumber' => options[:order_id]) + end + + private + + def add_payment_method(post, payment_method) + if payment_method.respond_to?(:number) + post['card.cardHolderName'] = "#{payment_method.first_name} #{payment_method.last_name}" + post['card.PAN'] = payment_method.number + post['card.CVN'] = payment_method.verification_value + post['card.expiryYear'] = payment_method.year.to_s[-2,2] + post['card.expiryMonth'] = sprintf('%02d', payment_method.month) + else + post['customer.customerReferenceNumber'] = payment_method + end + end + + def add_reference(post, reference) + post['customer.originalOrderNumber'] = reference + end + + def add_order(post, amount, options) + post['order.ECI'] = @options[:eci] + post['order.amount'] = amount + post['card.currency'] = (options[:currency] || currency(amount)) + post['customer.orderNumber'] = options[:order_id][0...20] + end + + def add_auth(post) + post['customer.username'] = @options[:username] + post['customer.password'] = @options[:password] + post['customer.merchant'] = @options[:merchant] + end + + # Creates the request and returns the summarized result + def commit(action, post) + add_auth(post) + post.merge!('order.type' => TRANSACTIONS[action]) + + request = post.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&") + response = ssl_post(self.live_url, request) + + params = {} + CGI.parse(response).each_pair do |key, value| + actual_key = key.split(".").last + params[actual_key.underscore.to_sym] = value[0] + end + + message = "#{SUMMARY_CODES[params[:summary_code]]} - #{RESPONSE_CODES[params[:response_code]]}" + + success = (params[:summary_code] ? (params[:summary_code] == "0") : (params[:response_code] == "00")) + + Response.new(success, message, params, + :test => (@options[:merchant].to_s == "TEST"), + :authorization => post[:order_number] + ) + rescue ActiveMerchant::ResponseError => e + raise unless e.response.code == '403' + return Response.new(false, "Invalid credentials", {}, :test => test?) + rescue ActiveMerchant::ClientCertificateError + return Response.new(false, "Invalid certificate", {}, :test => test?) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pin.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pin.rb new file mode 100644 index 000000000..8e1c2ffd0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/pin.rb @@ -0,0 +1,165 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PinGateway < Gateway + self.test_url = 'https://test-api.pin.net.au/1' + self.live_url = 'https://api.pin.net.au/1' + + self.default_currency = 'AUD' + self.money_format = :cents + self.supported_countries = ['AU'] + self.supported_cardtypes = [:visa, :master] + self.homepage_url = 'http://www.pin.net.au/' + self.display_name = 'Pin' + + def initialize(options = {}) + requires!(options, :api_key) + super + end + + # Create a charge using a credit card, card token or customer token + # + # To charge a credit card: purchase([money], [creditcard hash], ...) + # To charge a customer: purchase([money], [token], ...) + def purchase(money, creditcard, options = {}) + post = {} + + add_amount(post, money, options) + add_customer_data(post, options) + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, creditcard, options) + + commit('charges', post, options) + end + + # Create a customer and associated credit card. The token that is returned + # can be used instead of a credit card parameter in the purchase method + def store(creditcard, options = {}) + post = {} + + add_creditcard(post, creditcard) + add_customer_data(post, options) + add_address(post, creditcard, options) + commit('customers', post, options) + end + + # Refund a transaction, note that the money attribute is ignored at the + # moment as the API does not support partial refunds. The parameter is + # kept for compatibility reasons + def refund(money, token, options = {}) + commit("charges/#{CGI.escape(token)}/refunds", { :amount => amount(money) }, options) + end + + private + def add_amount(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)) + post[:currency] = post[:currency].upcase if post[:currency] + end + + def add_customer_data(post, options) + post[:email] = options[:email] + post[:ip_address] = options[:ip] + end + + def add_address(post, creditcard, options) + return if creditcard.kind_of?(String) + address = (options[:billing_address] || options[:address]) + return unless address + + post[:card] ||= {} + post[:card].merge!( + :address_line1 => address[:address1], + :address_city => address[:city], + :address_postcode => address[:zip], + :address_state => address[:state], + :address_country => address[:country] + ) + end + + def add_invoice(post, options) + post[:description] = options[:description] || "Active Merchant Purchase" + end + + def add_creditcard(post, creditcard) + if creditcard.respond_to?(:number) + post[:card] ||= {} + + post[:card].merge!( + :number => creditcard.number, + :expiry_month => creditcard.month, + :expiry_year => creditcard.year, + :cvc => creditcard.verification_value, + :name => "#{creditcard.first_name} #{creditcard.last_name}" + ) + elsif creditcard.kind_of?(String) + if creditcard =~ /^card_/ + post[:card_token] = creditcard + else + post[:customer_token] = creditcard + end + end + end + + def headers(params = {}) + result = { + "Content-Type" => "application/json", + "Authorization" => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}" + } + + result['X-Partner-Key'] = params[:partner_key] if params[:partner_key] + result['X-Safe-Card'] = params[:safe_card] if params[:safe_card] + result + end + + def commit(action, params, options) + url = "#{test? ? test_url : live_url}/#{action}" + + begin + body = parse(ssl_post(url, post_data(params), headers(options))) + rescue ResponseError => e + body = parse(e.response.body) + end + + if body["response"] + success_response(body) + elsif body["error"] + error_response(body) + end + end + + def success_response(body) + response = body["response"] + Response.new( + true, + response['status_message'], + body, + :authorization => token(response), + :test => test? + ) + end + + def error_response(body) + Response.new( + false, + body['error_description'], + body, + :authorization => nil, + :test => test? + ) + end + + def token(response) + response['token'] + end + + def parse(body) + JSON.parse(body) + end + + def post_data(parameters = {}) + parameters.to_json + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/plugnpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/plugnpay.rb new file mode 100644 index 000000000..8f743cebd --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/plugnpay.rb @@ -0,0 +1,294 @@ +module ActiveMerchant + module Billing + class PlugnpayGateway < Gateway + class PlugnpayPostData < PostData + # Fields that will be sent even if they are blank + self.required_fields = [ :publisher_name, :publisher_password, + :card_amount, :card_name, :card_number, :card_exp, :orderID ] + end + self.live_url = self.test_url = 'https://pay1.plugnpay.com/payment/pnpremote.cgi' + + CARD_CODE_MESSAGES = { + "M" => "Card verification number matched", + "N" => "Card verification number didn't match", + "P" => "Card verification number was not processed", + "S" => "Card verification number should be on card but was not indicated", + "U" => "Issuer was not certified for card verification" + } + + CARD_CODE_ERRORS = %w( N S ) + + AVS_MESSAGES = { + "A" => "Street address matches billing information, zip/postal code does not", + "B" => "Address information not provided for address verification check", + "E" => "Address verification service error", + "G" => "Non-U.S. card-issuing bank", + "N" => "Neither street address nor zip/postal match billing information", + "P" => "Address verification not applicable for this transaction", + "R" => "Payment gateway was unavailable or timed out", + "S" => "Address verification service not supported by issuer", + "U" => "Address information is unavailable", + "W" => "9-digit zip/postal code matches billing information, street address does not", + "X" => "Street address and 9-digit zip/postal code matches billing information", + "Y" => "Street address and 5-digit zip/postal code matches billing information", + "Z" => "5-digit zip/postal code matches billing information, street address does not", + } + + AVS_ERRORS = %w( A E N R W Z ) + + PAYMENT_GATEWAY_RESPONSES = { + "P01" => "AVS Mismatch Failure", + "P02" => "CVV2 Mismatch Failure", + "P21" => "Transaction may not be marked", + "P30" => "Test Tran. Bad Card", + "P35" => "Test Tran. Problem", + "P40" => "Username already exists", + "P41" => "Username is blank", + "P50" => "Fraud Screen Failure", + "P51" => "Missing PIN Code", + "P52" => "Invalid Bank Acct. No.", + "P53" => "Invalid Bank Routing No.", + "P54" => "Invalid/Missing Check No.", + "P55" => "Invalid Credit Card No.", + "P56" => "Invalid CVV2/CVC2 No.", + "P57" => "Expired. CC Exp. Date", + "P58" => "Missing Data", + "P59" => "Missing Email Address", + "P60" => "Zip Code does not match Billing State.", + "P61" => "Invalid Billing Zip Code", + "P62" => "Zip Code does not match Shipping State.", + "P63" => "Invalid Shipping Zip Code", + "P64" => "Invalid Credit Card CVV2/CVC2 Format.", + "P65" => "Maximum number of attempts has been exceeded.", + "P66" => "Credit Card number has been flagged and can not be used to access this service.", + "P67" => "IP Address is on Blocked List.", + "P68" => "Billing country does not match ipaddress country.", + "P69" => "US based ipaddresses are currently blocked.", + "P70" => "Credit Cards issued from this bank are currently not being accepted.", + "P71" => "Credit Cards issued from this bank are currently not being accepted.", + "P72" => "Daily volume exceeded.", + "P73" => "Too many transactions within allotted time.", + "P91" => "Missing/incorrect password", + "P92" => "Account not configured for mobil administration", + "P93" => "IP Not registered to username.", + "P94" => "Mode not permitted for this account.", + "P95" => "Currently Blank", + "P96" => "Currently Blank", + "P97" => "Processor not responding", + "P98" => "Missing merchant/publisher name", + "P99" => "Currently Blank" + } + + TRANSACTIONS = { + :authorization => 'auth', + :purchase => 'auth', + :capture => 'mark', + :void => 'void', + :refund => 'return', + :credit => 'newreturn' + } + + SUCCESS_CODES = [ 'pending', 'success' ] + FAILURE_CODES = [ 'badcard', 'fraud' ] + + self.default_currency = 'USD' + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.plugnpay.com/' + self.display_name = "Plug'n Pay" + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, creditcard, options = {}) + post = PlugnpayPostData.new + + add_amount(post, money, options) + add_creditcard(post, creditcard) + add_addresses(post, options) + add_invoice_data(post, options) + add_customer_data(post, options) + + post[:authtype] = 'authpostauth' + commit(:authorization, post) + end + + def authorize(money, creditcard, options = {}) + post = PlugnpayPostData.new + + add_amount(post, money, options) + add_creditcard(post, creditcard) + add_addresses(post, options) + add_invoice_data(post, options) + add_customer_data(post, options) + + post[:authtype] = 'authonly' + commit(:authorization, post) + end + + def capture(money, authorization, options = {}) + post = PlugnpayPostData.new + + post[:orderID] = authorization + + add_amount(post, money, options) + add_customer_data(post, options) + + commit(:capture, post) + end + + def void(authorization, options = {}) + post = PlugnpayPostData.new + + post[:orderID] = authorization + post[:txn_type] = 'auth' + + commit(:void, post) + end + + def credit(money, identification_or_creditcard, options = {}) + post = PlugnpayPostData.new + add_amount(post, money, options) + + if identification_or_creditcard.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification_or_creditcard, options) + else + add_creditcard(post, identification_or_creditcard) + add_addresses(post, options) + add_customer_data(post, options) + + commit(:credit, post) + end + end + + def refund(money, reference, options = {}) + post = PlugnpayPostData.new + add_amount(post, money, options) + post[:orderID] = reference + commit(:refund, post) + end + + private + def commit(action, post) + response = parse( ssl_post(self.live_url, post_data(action, post)) ) + success = SUCCESS_CODES.include?(response[:finalstatus]) + message = success ? 'Success' : message_from(response) + + Response.new(success, message, response, + :test => test?, + :authorization => response[:orderid], + :avs_result => { :code => response[:avs_code] }, + :cvv_result => response[:cvvresp] + ) + end + + def parse(body) + body = CGI.unescape(body) + results = {} + body.split('&').collect { |e| e.split('=') }.each do |key,value| + results[key.downcase.to_sym] = normalize(value.to_s.strip) + end + + results.delete(:publisher_password) + results[:avs_message] = AVS_MESSAGES[results[:avs_code]] if results[:avs_code] + results[:card_code_message] = CARD_CODE_MESSAGES[results[:cvvresp]] if results[:cvvresp] + + results + end + + def post_data(action, post) + post[:mode] = TRANSACTIONS[action] + post[:convert] = 'underscores' + post[:app_level] = 0 + post[:publisher_name] = @options[:login] + post[:publisher_password] = @options[:password] + + post.to_s + end + + def add_creditcard(post, creditcard) + post[:card_number] = creditcard.number + post[:card_cvv] = creditcard.verification_value + post[:card_exp] = expdate(creditcard) + post[:card_name] = creditcard.name.slice(0..38) + end + + def add_customer_data(post, options) + post[:email] = options[:email] + post[:dontsndmail] = 'yes' unless options[:send_email_confirmation] + post[:ipaddress] = options[:ip] + end + + def add_invoice_data(post, options) + post[:shipping] = amount(options[:shipping]) unless options[:shipping].blank? + post[:tax] = amount(options[:tax]) unless options[:tax].blank? + end + + def add_addresses(post, options) + if address = options[:billing_address] || options[:address] + post[:card_address1] = address[:address1] + post[:card_zip] = address[:zip] + post[:card_city] = address[:city] + post[:card_country] = address[:country] + post[:phone] = address[:phone] + + case address[:country] + when 'US', 'CA' + post[:card_state] = address[:state] + else + post[:card_state] = 'ZZ' + post[:card_prov] = address[:state] + end + end + + if shipping_address = options[:shipping_address] || address + post[:shipname] = shipping_address[:name] + post[:address1] = shipping_address[:address1] + post[:address2] = shipping_address[:address2] + post[:city] = shipping_address[:city] + + case shipping_address[:country] + when 'US', 'CA' + post[:state] = shipping_address[:state] + else + post[:state] = 'ZZ' + post[:province] = shipping_address[:state] + end + + post[:country] = shipping_address[:country] + post[:zip] = shipping_address[:zip] + end + end + + def add_amount(post, money, options) + post[:card_amount] = amount(money) + post[:currency] = options[:currency] || currency(money) + 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) + PAYMENT_GATEWAY_RESPONSES[results[:resp_code]] + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}/#{year[-2..-1]}" + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psigate.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psigate.rb new file mode 100644 index 000000000..c30a1f20f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psigate.rb @@ -0,0 +1,227 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # This class implements the Psigate gateway for the ActiveMerchant module. + # + # Modifications by Sean O'Hara ( sohara at sohara dot com ) + # + # Usage for a PreAuth (authorize) is as follows: + # + # gateway = PsigateGateway.new( + # :login => 'teststore', + # :password => 'psigate1234' + # ) + # + # creditcard = CreditCard.new( + # :number => '4242424242424242', + # :month => 8, + # :year => 2006, + # :first_name => 'Longbob', + # :last_name => 'Longsen' + # ) + # + # twenty = 2000 + # response = @gateway.authorize(twenty, creditcard, + # :order_id => 1234, + # :billing_address => { + # :address1 => '123 fairweather Lane', + # :address2 => 'Apt B', + # :city => 'New York', + # :state => 'NY', + # :country => 'U.S.A.', + # :zip => '10010' + # }, + # :email => 'jack@yahoo.com' + # ) + class PsigateGateway < Gateway + self.test_url = 'https://dev.psigate.com:7989/Messenger/XMLMessenger' + self.live_url = 'https://secure.psigate.com:7934/Messenger/XMLMessenger' + + self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_countries = ['CA'] + self.homepage_url = 'http://www.psigate.com/' + self.display_name = 'Psigate' + + SUCCESS_MESSAGE = 'Success' + FAILURE_MESSAGE = 'The transaction was declined' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard, options = {}) + requires!(options, :order_id) + options[:CardAction] = "1" + commit(money, creditcard, options) + end + + def purchase(money, creditcard, options = {}) + requires!(options, :order_id) + options[:CardAction] = "0" + commit(money, creditcard, options) + end + + def capture(money, authorization, options = {}) + options[:CardAction] = "2" + options[:order_id], options[:trans_ref_number] = split_authorization(authorization) + commit(money, nil, options) + end + + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def refund(money, authorization, options = {}) + options[:CardAction] = "3" + options[:order_id], options[:trans_ref_number] = split_authorization(authorization) + commit(money, nil, options) + end + + def void(authorization, options = {}) + options[:CardAction] = "9" + options[:order_id], options[:trans_ref_number] = split_authorization(authorization) + commit(nil, nil, options) + end + + private + + def commit(money, creditcard, options = {}) + response = parse(ssl_post(url, post_data(money, creditcard, options))) + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => build_authorization(response) , + :avs_result => { :code => response[:avsresult] }, + :cvv_result => response[:cardidresult] + ) + end + + def url + (test? ? self.test_url : self.live_url) + end + + def successful?(response) + response[:approved] == "APPROVED" + end + + def parse(xml) + response = {:message => "Global Error Receipt", :complete => false} + + xml = REXML::Document.new(xml) + xml.elements.each('//Result/*') do |node| + response[node.name.downcase.to_sym] = normalize(node.text) + end unless xml.root.nil? + + response + end + + def post_data(money, creditcard, options) + xml = REXML::Document.new + xml << REXML::XMLDecl.new + root = xml.add_element("Order") + + parameters(money, creditcard, options).each do |key, value| + root.add_element(key.to_s).text = value if value + end + + xml.to_s + end + + def parameters(money, creditcard, options = {}) + params = { + # General order parameters + :StoreID => @options[:login], + :Passphrase => @options[:password], + :TestResult => options[:test_result], + :OrderID => options[:order_id], + :UserID => options[:user_id], + :Phone => options[:phone], + :Fax => options[:fax], + :Email => options[:email], + :TransRefNumber => options[:trans_ref_number], + + # Credit Card parameters + :PaymentType => "CC", + :CardAction => options[:CardAction], + + # Financial parameters + :CustomerIP => options[:ip], + :SubTotal => amount(money), + :Tax1 => options[:tax1], + :Tax2 => options[:tax2], + :ShippingTotal => options[:shipping_total], + } + + if creditcard + exp_month = sprintf("%.2i", creditcard.month) unless creditcard.month.blank? + exp_year = creditcard.year.to_s[2,2] unless creditcard.year.blank? + card_id_code = (creditcard.verification_value.blank? ? nil : "1") + + params.update( + :CardNumber => creditcard.number, + :CardExpMonth => exp_month, + :CardExpYear => exp_year, + :CardIDCode => card_id_code, + :CardIDNumber => creditcard.verification_value + ) + end + + if(address = (options[:billing_address] || options[:address])) + params[:Bname] = address[:name] || creditcard.name + params[:Baddress1] = address[:address1] unless address[:address1].blank? + params[:Baddress2] = address[:address2] unless address[:address2].blank? + params[:Bcity] = address[:city] unless address[:city].blank? + params[:Bprovince] = address[:state] unless address[:state].blank? + params[:Bpostalcode] = address[:zip] unless address[:zip].blank? + params[:Bcountry] = address[:country] unless address[:country].blank? + params[:Bcompany] = address[:company] unless address[:company].blank? + end + + if address = options[:shipping_address] + params[:Sname] = address[:name] || creditcard.name + params[:Saddress1] = address[:address1] unless address[:address1].blank? + params[:Saddress2] = address[:address2] unless address[:address2].blank? + params[:Scity] = address[:city] unless address[:city].blank? + params[:Sprovince] = address[:state] unless address[:state].blank? + params[:Spostalcode] = address[:zip] unless address[:zip].blank? + params[:Scountry] = address[:country] unless address[:country].blank? + params[:Scompany] = address[:company] unless address[:company].blank? + end + + params + end + + def message_from(response) + if response[:approved] == "APPROVED" + return SUCCESS_MESSAGE + else + return FAILURE_MESSAGE if response[:errmsg].blank? + return response[:errmsg].gsub(/[^\w]/, ' ').split.join(" ").capitalize + 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 split_authorization(authorization) + order_id, trans_ref_number = authorization.split(';') + [order_id, trans_ref_number] + end + + def build_authorization(response) + [response[:orderid], response[:transrefnumber]].join(";") + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psl_card.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psl_card.rb new file mode 100644 index 000000000..758aac6fe --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/psl_card.rb @@ -0,0 +1,303 @@ +module ActiveMerchant + module Billing + # + # ActiveMerchant PSL Card Gateway + # + # Notes: + # -To be able to use the capture function, the IP address of the machine must be + # registered with PSL + # -ESALE_KEYED should only be used in situations where the cardholder perceives the + # transaction to be Internet-based, such as purchasing from a web site/on-line store. + # If the Internet is used purely for the transport of information from the merchant + # directly to the gateway then the appropriate cardholder present or not present message + # type should be used rather than the ‘E’ equivalent. + # -The CV2 / AVS policies are set up with the account settings when signing up for an account + class PslCardGateway < Gateway + self.money_format = :cents + self.default_currency = 'GBP' + + self.supported_countries = ['GB'] + # Visa Credit, Visa Debit, Mastercard, Maestro, Solo, Electron, + # American Express, Diners Club, JCB, International Maestro, + # Style, Clydesdale Financial Services, Other + + self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :switch, :solo, :maestro ] + self.homepage_url = 'http://www.paymentsolutionsltd.com/' + self.display_name = 'PSL Payment Solutions' + + # Default ISO 3166 country code (GB) + cattr_accessor :location + self.location = 826 + + # PslCard server self.live_url - The url is the same whether testing or live - use + # the test account when testing... + self.live_url = self.test_url = 'https://pslcard3.paymentsolutionsltd.com/secure/transact.asp?' + + # eCommerce sale transaction, details keyed by merchant or cardholder + MESSAGE_TYPE = 'ESALE_KEYED' + + # The type of response that we want to get from PSL, options are HTML, XML or REDIRECT + RESPONSE_ACTION = 'HTML' + + # Currency Codes + CURRENCY_CODES = { + 'AUD' => 036, + 'GBP' => 826, + 'USD' => 840 + } + + #The terminal used - only for swipe transactions, so hard coded to 32 for online + EMV_TERMINAL_TYPE = 32 + + #Different Dispatch types + DISPATCH_LATER = 'LATER' + DISPATCH_NOW = 'NOW' + + # Return codes + APPROVED = '00' + + #Nominal amount to authorize for a 'dispatch later' type + #The nominal amount is held straight away, when the goods are ready + #to be dispatched, PSL is informed and the full amount is the + #taken. + NOMINAL_AMOUNT = 101 + + AVS_CODE = { + "ALL MATCH" => 'Y', + "SECURITY CODE MATCH ONLY" => 'N', + "ADDRESS MATCH ONLY" => 'Y', + "NO DATA MATCHES" => 'N', + "DATA NOT CHECKED" => 'R', + "SECURITY CHECKS NOT SUPPORTED" => 'X' + } + + CVV_CODE = { + "ALL MATCH" => 'M', + "SECURITY CODE MATCH ONLY" => 'M', + "ADDRESS MATCH ONLY" => 'N', + "NO DATA MATCHES" => 'N', + "DATA NOT CHECKED" => 'P', + "SECURITY CHECKS NOT SUPPORTED" => 'X' + } + + # Create a new PslCardGateway + # + # The gateway requires that a valid :login be passed in the options hash + # + # Paramaters: + # -options: + # :login - the PslCard account login (required) + def initialize(options = {}) + requires!(options, :login) + + super + end + + # Purchase the item straight away + # + # Parameters: + # -money: Amount to be charged as an Integer value in cents + # -authorization: the PSL cross reference from the previous authorization + # -options: + # + # Returns: + # -ActiveRecord::Billing::Response object + # + def purchase(money, credit_card, options = {}) + post = {} + + add_amount(post, money, DISPATCH_NOW, options) + add_credit_card(post, credit_card) + add_address(post, options) + add_invoice(post, options) + add_purchase_details(post) + + commit(post) + end + + # Authorize the transaction + # + # Reserves the funds on the customer's credit card, but does not + # charge the card. + # + # This implementation does not authorize the full amount, rather it checks that the full amount + # is available and only 'reserves' the nominal amount (currently a pound and a penny) + # + # Parameters: + # -money: Amount to be charged as an Integer value in cents + # -authorization: the PSL cross reference from the previous authorization + # -options: + # + # Returns: + # -ActiveRecord::Billing::Response object + # + def authorize(money, credit_card, options = {}) + post = {} + + add_amount(post, money, DISPATCH_LATER, options) + add_credit_card(post, credit_card) + add_address(post, options) + add_invoice(post, options) + add_purchase_details(post) + + commit(post) + end + + # Post an authorization. + # + # Captures the funds from an authorized transaction. + # + # Parameters: + # -money: Amount to be charged as an Integer value in cents + # -authorization: The PSL Cross Reference + # -options: + # + # Returns: + # -ActiveRecord::Billing::Response object + # + def capture(money, authorization, options = {}) + post = {} + + add_amount(post, money, DISPATCH_NOW, options) + add_reference(post, authorization) + add_purchase_details(post) + + commit(post) + end + + private + + def add_credit_card(post, credit_card) + post[:QAName] = credit_card.name + post[:CardNumber] = credit_card.number + post[:EMVTerminalType] = EMV_TERMINAL_TYPE + post[:ExpMonth] = credit_card.month + post[:ExpYear] = credit_card.year + + if requires_start_date_or_issue_number?(credit_card) + post[:IssueNumber] = credit_card.issue_number unless credit_card.issue_number.blank? + post[:StartMonth] = credit_card.start_month unless credit_card.start_month.blank? + post[:StartYear] = credit_card.start_year unless credit_card.start_year.blank? + end + + # CV2 check + post[:AVSCV2Check] = credit_card.verification_value? ? 'YES' : 'NO' + post[:CV2] = credit_card.verification_value if credit_card.verification_value? + end + + def add_address(post, options) + address = options[:billing_address] || options[:address] + return if address.nil? + + post[:QAAddress] = [:address1, :address2, :city, :state].collect{|a| address[a]}.reject{|a| a.blank?}.join(' ') + post[:QAPostcode] = address[:zip] + end + + def add_invoice(post, options) + post[:MerchantName] = options[:merchant] || 'Merchant Name' # May use this as the order_id field + post[:OrderID] = options[:order_id] unless options[:order_id].blank? + end + + def add_reference(post, authorization) + post[:CrossReference] = authorization + end + + def add_amount(post, money, dispatch_type, options) + post[:CurrencyCode] = currency_code(options[:currency] || currency(money)) + + if dispatch_type == DISPATCH_LATER + post[:amount] = amount(NOMINAL_AMOUNT) + post[:DispatchLaterAmount] = amount(money) + else + post[:amount] = amount(money) + end + + post[:Dispatch] = dispatch_type + end + + def add_purchase_details(post) + post[:EchoAmount] = 'YES' + post[:SCBI] = 'YES' # Return information about the transaction + post[:MessageType] = MESSAGE_TYPE + end + + # Get the currency code for the passed money object + # + # The money class stores the currency as an ISO 4217:2001 Alphanumeric, + # however PSL requires the ISO 4217:2001 Numeric code. + # + # Parameters: + # -money: Integer value in cents + # + # Returns: + # -the ISO 4217:2001 Numberic currency code + # + def currency_code(currency) + CURRENCY_CODES[currency] + end + + # Parse the PSL response and create a Response object + # + # Parameters: + # -body: The response string returned from PSL, Formatted: + # Key=value&key=value... + # + # Returns: + # -a hash with all of the values returned in the PSL response + # + def parse(body) + + fields = {} + for line in body.split('&') + key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + fields[key] = CGI.unescape(value) + end + fields.symbolize_keys + end + + # Send the passed data to PSL for processing + # + # Parameters: + # -request: The data that is to be sent to PSL + # + # Returns: + # - ActiveMerchant::Billing::Response object + # + def commit(request) + response = parse( ssl_post(self.live_url, post_data(request)) ) + + Response.new(response[:ResponseCode] == APPROVED, response[:Message], response, + :test => test?, + :authorization => response[:CrossReference], + :cvv_result => CVV_CODE[response[:AVSCV2Check]], + :avs_result => { :code => AVS_CODE[response[:AVSCV2Check]] } + ) + end + + # Put the passed data into a format that can be submitted to PSL + # Key=Value&Key=Value... + # + # Any ampersands and equal signs are removed from the data being posted + # as PSL puts them back into the response string which then cannot be parsed. + # This is after escaping before sending the request to PSL - this is a work + # around for the time being + # + # Parameters: + # -post: Hash of all the data to be sent + # + # Returns: + # -String: the data to be sent + # + def post_data(post) + post[:CountryCode] = self.location + post[:MerchantID] = @options[:login] + post[:ValidityID] = @options[:password] + post[:ResponseAction] = RESPONSE_ACTION + + post.collect { |key, value| + "#{key}=#{CGI.escape(value.to_s.tr('&=', ' '))}" + }.join("&") + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/qbms.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/qbms.rb new file mode 100644 index 000000000..a1ca4bccc --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/qbms.rb @@ -0,0 +1,292 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class QbmsGateway < Gateway + API_VERSION = '4.0' + + class_attribute :test_url, :live_url + + self.test_url = "https://webmerchantaccount.ptc.quickbooks.com/j/AppGateway" + self.live_url = "https://webmerchantaccount.quickbooks.com/j/AppGateway" + + self.homepage_url = 'http://payments.intuit.com/' + self.display_name = 'QuickBooks Merchant Services' + self.default_currency = 'USD' + self.supported_cardtypes = [ :visa, :master, :discover, :american_express, :diners_club, :jcb ] + self.supported_countries = [ 'US' ] + + TYPES = { + :authorize => 'CustomerCreditCardAuth', + :capture => 'CustomerCreditCardCapture', + :purchase => 'CustomerCreditCardCharge', + :refund => 'CustomerCreditCardTxnVoidOrRefund', + :void => 'CustomerCreditCardTxnVoid', + :query => 'MerchantAccountQuery', + } + + # Creates a new QbmsGateway + # + # The gateway requires that a valid app id, app login, and ticket be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login -- The App Login (REQUIRED) + # * :ticket -- The Connection Ticket. (REQUIRED) + # * :pem -- The PEM-encoded SSL client key and certificate. (REQUIRED) + # * :test -- +true+ or +false+. If true, perform transactions against the test server. + # Otherwise, perform transactions against the production server. + # + def initialize(options = {}) + requires!(options, :login, :ticket) + super + end + + # Performs an authorization, which reserves the funds on the customer's credit card, but does not + # charge the card. + # + # ==== Parameters + # + # * money -- The amount to be authorized as an Integer value in cents. + # * creditcard -- The CreditCard details for the transaction. + # * options -- A hash of optional parameters. + # + def authorize(money, creditcard, options = {}) + commit(:authorize, money, options.merge(:credit_card => creditcard)) + end + + # Perform a purchase, which is essentially an authorization and capture in a single operation. + # + # ==== Parameters + # + # * money -- The amount to be purchased as an Integer value in cents. + # * creditcard -- The CreditCard details for the transaction. + # * options -- A hash of optional parameters. + # + def purchase(money, creditcard, options = {}) + commit(:purchase, money, options.merge(:credit_card => creditcard)) + end + + # Captures the funds from an authorized transaction. + # + # ==== Parameters + # + # * money -- The amount to be captured as an Integer value in cents. + # * authorization -- The authorization returned from the previous authorize request. + # + def capture(money, authorization, options = {}) + commit(:capture, money, options.merge(:transaction_id => authorization)) + end + + # Void a previous transaction + # + # ==== Parameters + # + # * authorization - The authorization returned from the previous authorize request. + # + def void(authorization, options = {}) + commit(:void, nil, options.merge(:transaction_id => authorization)) + end + + # Credit an account. + # + # This transaction is also referred to as a Refund and indicates to the gateway that + # money should flow from the merchant to the customer. + # + # ==== Parameters + # + # * money -- The amount to be credited to the customer as an Integer value in cents. + # * identification -- The ID of the original transaction against which the credit is being issued. + # * options -- A hash of parameters. + # + # + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options = {}) + end + + def refund(money, identification, options = {}) + commit(:refund, money, options.merge(:transaction_id => identification)) + end + + # Query the merchant account status + def query + commit(:query, nil, {}) + end + + private + + def hosted? + @options[:pem] + end + + def commit(action, money, parameters) + url = test? ? self.test_url : self.live_url + + type = TYPES[action] + parameters[:trans_request_id] ||= SecureRandom.hex(10) + + req = build_request(type, money, parameters) + + data = ssl_post(url, req, "Content-Type" => "application/x-qbmsxml") + response = parse(type, data) + message = (response[:status_message] || '').strip + + Response.new(success?(response), message, response, + :test => test?, + :authorization => response[:credit_card_trans_id], + :fraud_review => fraud_review?(response), + :avs_result => { :code => avs_result(response) }, + :cvv_result => cvv_result(response) + ) + end + + def success?(response) + response[:status_code] == 0 + end + + def fraud_review?(response) + [10100, 10101].member? response[:status_code] + end + + def parse(type, body) + xml = REXML::Document.new(body) + + signon = REXML::XPath.first(xml, "//SignonMsgsRs/#{hosted? ? 'SignonAppCertRs' : 'SignonDesktopRs'}") + status_code = signon.attributes["statusCode"].to_i + + if status_code != 0 + return { + :status_code => status_code, + :status_message => signon.attributes["statusMessage"], + } + end + + response = REXML::XPath.first(xml, "//QBMSXMLMsgsRs/#{type}Rs") + + results = { + :status_code => response.attributes["statusCode"].to_i, + :status_message => response.attributes["statusMessage"], + } + + response.elements.each do |e| + name = e.name.underscore.to_sym + value = e.text() + + if old_value = results[name] + results[name] = [old_value] if !old_value.kind_of?(Array) + results[name] << value + else + results[name] = value + end + end + + results + end + + def build_request(type, money, parameters = {}) + xml = Builder::XmlMarkup.new(:indent => 0) + + xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + xml.instruct!(:qbmsxml, :version => API_VERSION) + + xml.tag!("QBMSXML") do + xml.tag!("SignonMsgsRq") do + xml.tag!(hosted? ? "SignonAppCertRq" : "SignonDesktopRq") do + xml.tag!("ClientDateTime", Time.now.xmlschema) + xml.tag!("ApplicationLogin", @options[:login]) + xml.tag!("ConnectionTicket", @options[:ticket]) + end + end + + xml.tag!("QBMSXMLMsgsRq") do + xml.tag!("#{type}Rq") do + method("build_#{type}").call(xml, money, parameters) + end + end + end + + xml.target! + end + + def build_CustomerCreditCardAuth(xml, money, parameters) + cc = parameters[:credit_card] + name = "#{cc.first_name} #{cc.last_name}"[0...30] + + xml.tag!("TransRequestID", parameters[:trans_request_id]) + xml.tag!("CreditCardNumber", cc.number) + xml.tag!("ExpirationMonth", cc.month) + xml.tag!("ExpirationYear", cc.year) + xml.tag!("IsECommerce", "true") + xml.tag!("Amount", amount(money)) + xml.tag!("NameOnCard", name) + add_address(xml, parameters) + xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value? + end + + def build_CustomerCreditCardCapture(xml, money, parameters) + xml.tag!("TransRequestID", parameters[:trans_request_id]) + xml.tag!("CreditCardTransID", parameters[:transaction_id]) + xml.tag!("Amount", amount(money)) + end + + def build_CustomerCreditCardCharge(xml, money, parameters) + cc = parameters[:credit_card] + name = "#{cc.first_name} #{cc.last_name}"[0...30] + + xml.tag!("TransRequestID", parameters[:trans_request_id]) + xml.tag!("CreditCardNumber", cc.number) + xml.tag!("ExpirationMonth", cc.month) + xml.tag!("ExpirationYear", cc.year) + xml.tag!("IsECommerce", "true") + xml.tag!("Amount", amount(money)) + xml.tag!("NameOnCard", name) + add_address(xml, parameters) + xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value? + end + + def build_CustomerCreditCardTxnVoidOrRefund(xml, money, parameters) + xml.tag!("TransRequestID", parameters[:trans_request_id]) + xml.tag!("CreditCardTransID", parameters[:transaction_id]) + xml.tag!("Amount", amount(money)) + end + + def build_CustomerCreditCardTxnVoid(xml, money, parameters) + xml.tag!("TransRequestID", parameters[:trans_request_id]) + xml.tag!("CreditCardTransID", parameters[:transaction_id]) + end + + # Called reflectively by build_request + def build_MerchantAccountQuery(xml, money, parameters) + end + + def add_address(xml, parameters) + if address = parameters[:billing_address] || parameters[:address] + xml.tag!("CreditCardAddress", (address[:address1] || "")[0...30]) + xml.tag!("CreditCardPostalCode", (address[:zip] || "")[0...9]) + end + end + + def cvv_result(response) + case response[:card_security_code_match] + when "Pass" then 'M' + when "Fail" then 'N' + when "NotAvailable" then 'P' + end + end + + def avs_result(response) + case "#{response[:avs_street]}|#{response[:avs_zip]}" + when "Pass|Pass" then "D" + when "Pass|Fail" then "A" + when "Pass|NotAvailable" then "B" + when "Fail|Pass" then "Z" + when "Fail|Fail" then "C" + when "Fail|NotAvailable" then "N" + when "NotAvailable|Pass" then "P" + when "NotAvailable|Fail" then "N" + when "NotAvailable|NotAvailable" then "U" + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quantum.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quantum.rb new file mode 100644 index 000000000..7d8b51be1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quantum.rb @@ -0,0 +1,276 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # ActiveMerchant Implementation for Quantum Gateway XML Requester Service + # Based on API Doc from 8/6/2009 + # + # Important Notes + # * Support is included for a customer id via the :customer option, invoice number via :invoice option, invoice description via :merchant option and memo via :description option + # * You can force email of receipt with :email_receipt => true + # * You can force email of merchant receipt with :merchant_receipt => true + # * You can exclude CVV with :ignore_cvv => true + # * All transactions use dollar values. + class QuantumGateway < Gateway + self.live_url = self.test_url = 'https://secure.quantumgateway.com/cgi/xml_requester.php' + + # visa, master, american_express, discover + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = ['US'] + self.default_currency = 'USD' + self.money_format = :dollars + self.homepage_url = 'http://www.quantumgateway.com' + self.display_name = 'Quantum Gateway' + + # These are the options that can be used when creating a new Quantum Gateway object. + # + # :login => Your Quantum Gateway Gateway ID + # + # :password => Your Quantum Gateway Vault Key or Restrict Key + # + # NOTE: For testing supply your test GatewayLogin and GatewayKey + # + # :email_receipt => true if you want a receipt sent to the customer (false be default) + # + # :merchant_receipt => true if you want to override receiving the merchant receipt + # + # :ignore_avs => true ignore both AVS and CVV verification + # :ignore_cvv => true don't want to use CVV so continue processing even if CVV would have failed + # + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Request an authorization for an amount from CyberSource + # + def authorize(money, creditcard, options = {}) + setup_address_hash(options) + commit(build_auth_request(money, creditcard, options), options ) + end + + # Capture an authorization that has previously been requested + def capture(money, authorization, options = {}) + setup_address_hash(options) + commit(build_capture_request(money, authorization, options), options) + end + + # Purchase is an auth followed by a capture + # You must supply an order_id in the options hash + def purchase(money, creditcard, options = {}) + setup_address_hash(options) + commit(build_purchase_request(money, creditcard, options), options) + end + + def void(identification, options = {}) + commit(build_void_request(identification, options), options) + end + + def refund(money, identification, options = {}) + commit(build_credit_request(money, identification, options), options) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + private + + def setup_address_hash(options) + options[:billing_address] = options[:billing_address] || options[:address] || {} + end + + def build_auth_request(money, creditcard, options) + xml = Builder::XmlMarkup.new + add_common_credit_card_info(xml,'AUTH_ONLY') + add_purchase_data(xml, money) + add_creditcard(xml, creditcard) + add_address(xml, creditcard, options[:billing_address], options) + add_invoice_details(xml, options) + add_customer_details(xml, options) + add_memo(xml, options) + add_business_rules_data(xml) + xml.target! + end + + def build_capture_request(money, authorization, options) + xml = Builder::XmlMarkup.new + add_common_credit_card_info(xml,'PREVIOUS_SALE') + transaction_id, _ = authorization_parts_from(authorization) + add_transaction_id(xml, transaction_id) + xml.target! + end + + def build_purchase_request(money, creditcard, options) + xml = Builder::XmlMarkup.new + add_common_credit_card_info(xml, @options[:ignore_avs] || @options[:ignore_cvv] ? 'SALES' : 'AUTH_CAPTURE') + add_address(xml, creditcard, options[:billing_address], options) + add_purchase_data(xml, money) + add_creditcard(xml, creditcard) + add_invoice_details(xml, options) + add_customer_details(xml, options) + add_memo(xml, options) + add_business_rules_data(xml) + xml.target! + end + + def build_void_request(authorization, options) + xml = Builder::XmlMarkup.new + add_common_credit_card_info(xml,'VOID') + transaction_id, _ = authorization_parts_from(authorization) + add_transaction_id(xml, transaction_id) + xml.target! + end + + def build_credit_request(money, authorization, options) + xml = Builder::XmlMarkup.new + add_common_credit_card_info(xml,'RETURN') + add_purchase_data(xml, money) + transaction_id, cc = authorization_parts_from(authorization) + add_transaction_id(xml, transaction_id) + xml.tag! 'CreditCardNumber', cc + xml.target! + end + + def add_common_credit_card_info(xml, process_type) + xml.tag! 'RequestType', 'ProcessSingleTransaction' + xml.tag! 'TransactionType', 'CREDIT' + xml.tag! 'PaymentType', 'CC' + xml.tag! 'ProcessType', process_type + end + + def add_business_rules_data(xml) + xml.tag!('CustomerEmail', @options[:email_receipt] ? 'Y' : 'N') + xml.tag!('MerchantEmail', @options[:merchant_receipt] ? 'Y' : 'N') + end + + def add_invoice_details(xml, options) + xml.tag! 'InvoiceNumber', options[:invoice] + xml.tag! 'InvoiceDescription', options[:merchant] + end + + def add_customer_details(xml, options) + xml.tag! 'CustomerID', options[:customer] + end + + def add_transaction_id(xml, transaction_id) + xml.tag! 'TransactionID', transaction_id + end + + def add_memo(xml, options) + xml.tag! 'Memo', options[:description] + end + + def add_purchase_data(xml, money = 0) + xml.tag! 'Amount', amount(money) + xml.tag! 'TransactionDate', Time.now + end + + def add_address(xml, creditcard, address, options, shipTo = false) + xml.tag! 'FirstName', creditcard.first_name + xml.tag! 'LastName', creditcard.last_name + xml.tag! 'Address', address[:address1] # => there is no support for address2 in quantum + xml.tag! 'City', address[:city] + xml.tag! 'State', address[:state] + xml.tag! 'ZipCode', address[:zip] + xml.tag! 'Country', address[:country] + xml.tag! 'EmailAddress', options[:email] + xml.tag! 'IPAddress', options[:ip] + end + + def add_creditcard(xml, creditcard) + xml.tag! 'PaymentType', 'CC' + xml.tag! 'CreditCardNumber', creditcard.number + xml.tag! 'ExpireMonth', format(creditcard.month, :two_digits) + xml.tag! 'ExpireYear', format(creditcard.year, :four_digits) + xml.tag!('CVV2', creditcard.verification_value) unless (@options[:ignore_cvv] || creditcard.verification_value.blank? ) + end + + # Where we actually build the full SOAP request using builder + def build_request(body, options) + xml = Builder::XmlMarkup.new + xml.instruct! + xml.tag! 'QGWRequest' do + xml.tag! 'Authentication' do + xml.tag! 'GatewayLogin', @options[:login] + xml.tag! 'GatewayKey', @options[:password] + end + xml.tag! 'Request' do + xml << body + end + end + xml.target! + end + + # Contact CyberSource, make the SOAP request, and parse the reply into a Response object + def commit(request, options) + headers = { 'Content-Type' => 'text/xml' } + response = parse(ssl_post(self.live_url, build_request(request, options), headers)) + + success = response[:request_status] == "Success" + message = response[:request_message] + + if success # => checking for connectivity success first + success = %w(APPROVED FORCED VOIDED).include?(response[:Status]) + message = response[:StatusDescription] + authorization = success ? authorization_for(response) : nil + end + + Response.new(success, message, response, + :test => test?, + :authorization => authorization, + :avs_result => { :code => response[:AVSResponseCode] }, + :cvv_result => response[:CVV2ResponseCode] + ) + end + + # Parse the SOAP response + # Technique inspired by the Paypal Gateway + def parse(xml) + reply = {} + + begin + xml = REXML::Document.new(xml) + + root = REXML::XPath.first(xml, "//QGWRequest/ResponseSummary") + parse_element(reply, root) + reply[:request_status] = reply[:Status] + reply[:request_message] = "#{reply[:Status]}: #{reply[:StatusDescription]}" + + if root = REXML::XPath.first(xml, "//QGWRequest/Result") + root.elements.to_a.each do |node| + parse_element(reply, node) + end + end + rescue Exception => e + reply[:request_status] = 'Failure' + reply[:request_message] = "Failure: There was a problem parsing the response XML" + end + + return reply + end + + def parse_element(reply, node) + if node.has_elements? + node.elements.each{|e| parse_element(reply, e) } + else + if node.parent.name =~ /item/ + parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '') + reply[(parent + '_' + node.name).to_sym] = node.text + else + reply[node.name.to_sym] = node.text + end + end + return reply + end + + def authorization_for(reply) + "#{reply[:TransactionID]};#{reply[:CreditCardNumber]}" + end + + def authorization_parts_from(authorization) + authorization.split(/;/) + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quickpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quickpay.rb new file mode 100644 index 000000000..141d1ff03 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/quickpay.rb @@ -0,0 +1,335 @@ +require 'rexml/document' +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class QuickpayGateway < Gateway + self.live_url = self.test_url = 'https://secure.quickpay.dk/api' + + self.default_currency = 'DKK' + self.money_format = :cents + self.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro] + self.supported_countries = ['DK', 'SE'] + self.homepage_url = 'http://quickpay.dk/' + self.display_name = 'Quickpay' + + MD5_CHECK_FIELDS = { + 3 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate + cvd cardtypelock testmode), + + :capture => %w(protocol msgtype merchant amount transaction), + + :cancel => %w(protocol msgtype merchant transaction), + + :refund => %w(protocol msgtype merchant amount transaction), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode), + + :recurring => %w(protocol msgtype merchant ordernumber amount + currency autocapture transaction), + + :status => %w(protocol msgtype merchant transaction), + + :chstatus => %w(protocol msgtype merchant) + }, + + 4 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :capture => %w(protocol msgtype merchant amount transaction apikey), + + :cancel => %w(protocol msgtype merchant transaction apikey), + + :refund => %w(protocol msgtype merchant amount transaction apikey), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :recurring => %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), + + :status => %w(protocol msgtype merchant transaction apikey), + + :chstatus => %w(protocol msgtype merchant apikey) + }, + + 5 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :capture => %w(protocol msgtype merchant amount transaction apikey), + + :cancel => %w(protocol msgtype merchant transaction apikey), + + :refund => %w(protocol msgtype merchant amount transaction apikey), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :recurring => %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), + + :status => %w(protocol msgtype merchant transaction apikey), + + :chstatus => %w(protocol msgtype merchant apikey) + }, + + 6 => { + :authorize => %w(protocol msgtype merchant ordernumber amount + currency autocapture cardnumber expirationdate cvd + cardtypelock testmode fraud_remote_addr + fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :capture => %w(protocol msgtype merchant amount transaction + apikey), + + :cancel => %w(protocol msgtype merchant transaction apikey), + + :refund => %w(protocol msgtype merchant amount transaction apikey), + + :subscribe => %w(protocol msgtype merchant ordernumber cardnumber + expirationdate cvd cardtypelock description testmode + fraud_remote_addr fraud_http_accept fraud_http_accept_language + fraud_http_accept_encoding fraud_http_accept_charset + fraud_http_referer fraud_http_user_agent apikey), + + :recurring => %w(protocol msgtype merchant ordernumber amount currency + autocapture transaction apikey), + + :status => %w(protocol msgtype merchant transaction apikey), + + :chstatus => %w(protocol msgtype merchant apikey) + } + } + + APPROVED = '000' + + # The login is the QuickpayId + # The password is the md5checkword from the Quickpay manager + # To use the API-key from the Quickpay manager, specify :api-key + # Using the API-key, requires that you use version 4+. Specify :version => 4/5/6 in options. + def initialize(options = {}) + requires!(options, :login, :password) + @protocol = options.delete(:version) || 3 # default to protocol version 3 + super + end + + def authorize(money, credit_card_or_reference, options = {}) + post = {} + + action = recurring_or_authorize(credit_card_or_reference) + + add_amount(post, money, options) + add_invoice(post, options) + add_creditcard_or_reference(post, credit_card_or_reference, options) + add_autocapture(post, false) + add_fraud_parameters(post, options) if action.eql?(:authorize) + add_testmode(post) + + commit(action, post) + end + + def purchase(money, credit_card_or_reference, options = {}) + post = {} + + action = recurring_or_authorize(credit_card_or_reference) + + add_amount(post, money, options) + add_creditcard_or_reference(post, credit_card_or_reference, options) + add_invoice(post, options) + add_fraud_parameters(post, options) if action.eql?(:authorize) + add_autocapture(post, true) + + commit(action, post) + end + + def capture(money, authorization, options = {}) + post = {} + + add_reference(post, authorization) + add_amount_without_currency(post, money) + commit(:capture, post) + end + + def void(identification, options = {}) + post = {} + + add_reference(post, identification) + + commit(:cancel, post) + end + + def refund(money, identification, options = {}) + post = {} + + add_amount_without_currency(post, money) + add_reference(post, identification) + + commit(:refund, post) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + def store(creditcard, options = {}) + post = {} + + add_creditcard(post, creditcard, options) + add_invoice(post, options) + add_description(post, options) + add_fraud_parameters(post, options) + add_testmode(post) + + commit(:subscribe, post) + end + + private + + def add_amount(post, money, options = {}) + post[:amount] = amount(money) + post[:currency] = options[:currency] || currency(money) + end + + def add_amount_without_currency(post, money, options = {}) + post[:amount] = amount(money) + end + + def add_invoice(post, options) + post[:ordernumber] = format_order_number(options[:order_id]) + end + + def add_creditcard(post, credit_card, options) + post[:cardnumber] = credit_card.number + post[:cvd] = credit_card.verification_value + post[:expirationdate] = expdate(credit_card) + post[:cardtypelock] = options[:cardtypelock] unless options[:cardtypelock].blank? + end + + def add_reference(post, identification) + post[:transaction] = identification + end + + def add_creditcard_or_reference(post, credit_card_or_reference, options) + if credit_card_or_reference.is_a?(String) + add_reference(post, credit_card_or_reference) + else + add_creditcard(post, credit_card_or_reference, options) + end + end + + def add_autocapture(post, autocapture) + post[:autocapture] = autocapture ? 1 : 0 + end + + def recurring_or_authorize(credit_card_or_reference) + credit_card_or_reference.is_a?(String) ? :recurring : :authorize + end + + def add_description(post, options) + post[:description] = options[:description] + end + + def add_testmode(post) + return if post[:transaction].present? + post[:testmode] = test? ? '1' : '0' + end + + def add_fraud_parameters(post, options) + if @protocol >= 4 + post[:fraud_remote_addr] = options[:fraud_remote_addr] if options[:fraud_remote_addr] + post[:fraud_http_accept] = options[:fraud_http_accept] if options[:fraud_http_accept] + post[:fraud_http_accept_language] = options[:fraud_http_accept_language] if options[:fraud_http_accept_language] + post[:fraud_http_accept_encoding] = options[:fraud_http_accept_encoding] if options[:fraud_http_accept_encoding] + post[:fraud_http_accept_charset] = options[:fraud_http_accept_charset] if options[:fraud_http_accept_charset] + post[:fraud_http_referer] = options[:fraud_http_referer] if options[:fraud_http_referer] + post[:fraud_http_user_agent] = options[:fraud_http_user_agent] if options[:fraud_http_user_agent] + end + end + + def commit(action, params) + response = parse(ssl_post(self.live_url, post_data(action, params))) + + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => response[:transaction] + ) + end + + def successful?(response) + response[:qpstat] == APPROVED + end + + def parse(data) + response = {} + + doc = REXML::Document.new(data) + + doc.root.elements.each do |element| + response[element.name.to_sym] = element.text + end + + response + end + + def message_from(response) + response[:qpstatmsg].to_s + end + + def post_data(action, params = {}) + params[:protocol] = @protocol + params[:msgtype] = action.to_s + params[:merchant] = @options[:login] + params[:apikey] = @options[:apikey] if @options[:apikey] + params[:md5check] = generate_check_hash(action, params) + + params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def generate_check_hash(action, params) + string = MD5_CHECK_FIELDS[@protocol][action].collect do |key| + params[key.to_sym] + end.join('') + + # Add the md5checkword + string << @options[:password].to_s + + Digest::MD5.hexdigest(string) + end + + def expdate(credit_card) + year = format(credit_card.year, :two_digits) + month = format(credit_card.month, :two_digits) + + "#{year}#{month}" + end + + # Limited to 20 digits max + def format_order_number(number) + number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20] + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/realex.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/realex.rb new file mode 100644 index 000000000..e16a3165d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/realex.rb @@ -0,0 +1,303 @@ +require 'nokogiri' +require 'digest/sha1' + +module ActiveMerchant + module Billing + # Realex is the leading CC gateway in Ireland + # see http://www.realexpayments.com + # Contributed by John Ward (john@ward.name) + # see http://thinedgeofthewedge.blogspot.com + # + # Realex works using the following + # login - The unique id of the merchant + # password - The secret is used to digitally sign the request + # account - This is an optional third part of the authentication process + # and is used if the merchant wishes do distuinguish cc traffic from the different sources + # by using a different account. This must be created in advance + # + # the Realex team decided to make the orderid unique per request, + # so if validation fails you can not correct and resend using the + # same order id + class RealexGateway < Gateway + self.live_url = self.test_url = 'https://epage.payandshop.com/epage-remote.cgi' + + CARD_MAPPING = { + 'master' => 'MC', + 'visa' => 'VISA', + 'american_express' => 'AMEX', + 'diners_club' => 'DINERS', + 'switch' => 'SWITCH', + 'solo' => 'SWITCH', + 'laser' => 'LASER' + } + + self.money_format = :cents + self.default_currency = 'EUR' + self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :switch, :solo, :laser ] + self.supported_countries = [ 'IE', 'GB' ] + self.homepage_url = 'http://www.realexpayments.com/' + self.display_name = 'Realex' + + SUCCESS, DECLINED = "Successful", "Declined" + BANK_ERROR = REALEX_ERROR = "Gateway is in maintenance. Please try again later." + ERROR = CLIENT_DEACTIVATED = "Gateway Error" + + def initialize(options = {}) + requires!(options, :login, :password) + options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options.has_key?(:rebate_secret) + super + end + + def purchase(money, credit_card, options = {}) + requires!(options, :order_id) + + request = build_purchase_or_authorization_request(:purchase, money, credit_card, options) + commit(request) + end + + def authorize(money, creditcard, options = {}) + requires!(options, :order_id) + + request = build_purchase_or_authorization_request(:authorization, money, creditcard, options) + commit(request) + end + + def capture(money, authorization, options = {}) + request = build_capture_request(authorization, options) + commit(request) + end + + def refund(money, authorization, options = {}) + request = build_refund_request(money, authorization, options) + commit(request) + end + + def credit(money, authorization, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, authorization, options) + end + + def void(authorization, options = {}) + request = build_void_request(authorization, options) + commit(request) + end + + private + def commit(request) + response = parse(ssl_post(self.live_url, request)) + + Response.new(response[:result] == "00", message_from(response), response, + :test => response[:message] =~ /\[ test system \]/, + :authorization => authorization_from(response), + :cvv_result => response[:cvnresult], + :avs_result => { + :street_match => response[:avspostcoderesponse], + :postal_match => response[:avspostcoderesponse] + } + ) + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.xpath('//response/*').each do |node| + if (node.elements.size == 0) + response[node.name.downcase.to_sym] = normalize(node.text) + else + node.elements.each do |childnode| + name = "#{node.name.downcase}_#{childnode.name.downcase}" + response[name.to_sym] = normalize(childnode.text) + end + end + end unless doc.root.nil? + + response + end + + def authorization_from(parsed) + [parsed[:orderid], parsed[:pasref], parsed[:authcode]].join(';') + end + + def build_purchase_or_authorization_request(action, money, credit_card, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'auth' do + add_merchant_details(xml, options) + xml.tag! 'orderid', sanitize_order_id(options[:order_id]) + add_amount(xml, money, options) + add_card(xml, credit_card) + xml.tag! 'autosettle', 'flag' => auto_settle_flag(action) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number) + add_comments(xml, options) + add_address_and_customer_info(xml, options) + end + xml.target! + end + + def build_capture_request(authorization, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'settle' do + add_merchant_details(xml, options) + add_transaction_identifiers(xml, authorization, options) + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), nil, nil, nil) + end + xml.target! + end + + def build_refund_request(money, authorization, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'rebate' do + add_merchant_details(xml, options) + add_transaction_identifiers(xml, authorization, options) + xml.tag! 'amount', amount(money), 'currency' => options[:currency] || currency(money) + xml.tag! 'refundhash', @options[:refund_hash] if @options[:refund_hash] + xml.tag! 'autosettle', 'flag' => 1 + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), nil) + end + xml.target! + end + + def build_void_request(authorization, options) + timestamp = new_timestamp + xml = Builder::XmlMarkup.new :indent => 2 + xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'void' do + add_merchant_details(xml, options) + add_transaction_identifiers(xml, authorization, options) + add_comments(xml, options) + add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), nil, nil, nil) + end + xml.target! + end + + def add_address_and_customer_info(xml, options) + billing_address = options[:billing_address] || options[:address] + shipping_address = options[:shipping_address] + + return unless billing_address || shipping_address || options[:customer] || options[:invoice] || options[:ip] + + xml.tag! 'tssinfo' do + xml.tag! 'custnum', options[:customer] if options[:customer] + xml.tag! 'prodid', options[:invoice] if options[:invoice] + xml.tag! 'custipaddress', options[:ip] if options[:ip] + + if billing_address + xml.tag! 'address', 'type' => 'billing' do + xml.tag! 'code', format_shipping_zip_code(billing_address[:zip]) + xml.tag! 'country', billing_address[:country] + end + end + + if shipping_address + xml.tag! 'address', 'type' => 'shipping' do + xml.tag! 'code', format_shipping_zip_code(shipping_address[:zip]) + xml.tag! 'country', shipping_address[:country] + end + end + end + end + + def add_merchant_details(xml, options) + xml.tag! 'merchantid', @options[:login] + if options[:account] || @options[:account] + xml.tag! 'account', (options[:account] || @options[:account]) + end + end + + def add_transaction_identifiers(xml, authorization, options) + options[:order_id], pasref, authcode = authorization.split(';') + xml.tag! 'orderid', sanitize_order_id(options[:order_id]) + xml.tag! 'pasref', pasref + xml.tag! 'authcode', authcode + end + + def add_comments(xml, options) + return unless options[:description] + xml.tag! 'comments' do + xml.tag! 'comment', options[:description], 'id' => 1 + end + end + + def add_amount(xml, money, options) + xml.tag! 'amount', amount(money), 'currency' => options[:currency] || currency(money) + end + + def add_card(xml, credit_card) + xml.tag! 'card' do + xml.tag! 'number', credit_card.number + xml.tag! 'expdate', expiry_date(credit_card) + xml.tag! 'chname', credit_card.name + xml.tag! 'type', CARD_MAPPING[card_brand(credit_card).to_s] + xml.tag! 'issueno', credit_card.issue_number + xml.tag! 'cvn' do + xml.tag! 'number', credit_card.verification_value + xml.tag! 'presind', (options['presind'] || (credit_card.verification_value? ? 1 : nil)) + end + end + end + + def format_shipping_zip_code(zip) + zip.to_s.gsub(/\W/, '') + end + + def new_timestamp + Time.now.strftime('%Y%m%d%H%M%S') + end + + def add_signed_digest(xml, *values) + string = Digest::SHA1.hexdigest(values.join(".")) + xml.tag! 'sha1hash', Digest::SHA1.hexdigest([string, @options[:password]].join(".")) + end + + def auto_settle_flag(action) + action == :authorization ? '0' : '1' + end + + def expiry_date(credit_card) + "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" + end + + 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(response) + message = nil + case response[:result] + when "00" + message = SUCCESS + when "101" + message = response[:message] + when "102", "103" + message = DECLINED + when /^2[0-9][0-9]/ + message = BANK_ERROR + when /^3[0-9][0-9]/ + message = REALEX_ERROR + when /^5[0-9][0-9]/ + message = response[:message] + when "600", "601", "603" + message = ERROR + when "666" + message = CLIENT_DEACTIVATED + else + message = DECLINED + end + end + + def sanitize_order_id(order_id) + order_id.to_s.gsub(/[^a-zA-Z0-9\-_]/, '') + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/redsys.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/redsys.rb new file mode 100644 index 000000000..583e02539 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/redsys.rb @@ -0,0 +1,394 @@ +# coding: utf-8 +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # = Redsys Merchant Gateway + # + # Gateway support for the Spanish "Redsys" payment gateway system. This is + # used by many banks in Spain and is particularly well supported by + # Catalunya Caixa's ecommerce department. + # + # Standard ActiveMerchant methods are supported, with one notable exception: + # :order_id must be provided and must conform to a very specific format. + # + # == Example use: + # + # gateway = ActiveMerchant::Billing::RedsysGateway.new( + # :login => "091358382", + # :secret_key => "qwertyasdf0123456789" + # ) + # + # # Run a purchase for 10 euros + # response = gateway.purchase(1000, creditcard, :order_id => "123456") + # puts reponse.success? # => true + # + # # Partially refund the purchase + # response = gateway.refund(500, response.authorization) + # + # Redsys requires an order_id be provided with each transaction of a + # specific format. The rules are as follows: + # + # * Minimum length: 4 + # * Maximum length: 12 + # * First 4 digits must be numerical + # * Remaining 8 digits may be alphanumeric + # + # Much of the code for this library is based on the active_merchant_sermepa + # integration gateway which uses essentially the same API but with the + # banks own payment screen. + # + # Written by Samuel Lown for Cabify. For implementation questions, or + # test access details please get in touch: sam@cabify.com. + class RedsysGateway < Gateway + self.live_url = "https://sis.sermepa.es/sis/operaciones" + self.test_url = "https://sis-t.sermepa.es:25443/sis/operaciones" + + # Sensible region specific defaults. + self.supported_countries = ['ES'] + self.default_currency = 'EUR' + self.money_format = :cents + + # Not all card types may be actived by the bank! + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club] + + # Homepage URL of the gateway for reference + self.homepage_url = "http://www.redsys.es/" + + # What to call this gateway + self.display_name = "Redsys" + + CURRENCY_CODES = { + "ARS" => '032', + "AUD" => '036', + "BRL" => '986', + "BOB" => '068', + "CAD" => '124', + "CHF" => '756', + "CLP" => '152', + "COP" => '170', + "EUR" => '978', + "GBP" => '826', + "GTQ" => '320', + "JPY" => '392', + "MXN" => '484', + "NZD" => '554', + "PEN" => '604', + "RUB" => '643', + "USD" => '840', + "UYU" => '858' + } + + # The set of supported transactions for this gateway. + # More operations are supported by the gateway itself, but + # are not supported in this library. + SUPPORTED_TRANSACTIONS = { + :purchase => 'A', + :authorize => '1', + :capture => '2', + :refund => '3', + :cancel => '9' + } + + # These are the text meanings sent back by the acquirer when + # a card has been rejected. Syntax or general request errors + # are not covered here. + RESPONSE_TEXTS = { + # Accepted Codes + 0 => "Transaction Approved", + 400 => "Cancellation Accepted", + 481 => "Cancellation Accepted", + 500 => "Reconciliation Accepted", + 900 => "Refund / Confirmation approved", + + # Declined error codes + 101 => "Card expired", + 102 => "Card blocked temporarily or under susciption of fraud", + 104 => "Transaction not permitted", + 107 => "Contact the card issuer", + 109 => "Invalid identification by merchant or POS terminal", + 110 => "Invalid amount", + 114 => "Card cannot be used to the requested transaction", + 116 => "Insufficient credit", + 118 => "Non-registered card", + 125 => "Card not effective", + 129 => "CVV2/CVC2 Error", + 167 => "Contact the card issuer: suspected fraud", + 180 => "Card out of service", + 181 => "Card with credit or debit restrictions", + 182 => "Card with credit or debit restrictions", + 184 => "Authentication error", + 190 => "Refusal with no specific reason", + 191 => "Expiry date incorrect", + + # Declined, and suspected of fraud + 201 => "Card expired", + 202 => "Card blocked temporarily or under suscipition of fraud", + 204 => "Transaction not permitted", + 207 => "Contact the card issuer", + 208 => "Lost or stolen card", + 209 => "Lost or stolen card", + 280 => "CVV2/CVC2 Error", + 290 => "Declined with no specific reason", + + # More general codes for specific types of transaction + 480 => "Original transaction not located, or time-out exceeded", + 501 => "Original transaction not located, or time-out exceeded", + 502 => "Original transaction not located, or time-out exceeded", + 503 => "Original transaction not located, or time-out exceeded", + + # Declined transactions by the bank + 904 => "Merchant not registered at FUC", + 909 => "System error", + 912 => "Issuer not available", + 913 => "Duplicate transmission", + 916 => "Amount too low", + 928 => "Time-out exceeded", + 940 => "Transaction cancelled previously", + 941 => "Authorization operation already cancelled", + 942 => "Original authorization declined", + 943 => "Different details from origin transaction", + 944 => "Session error", + 945 => "Duplicate transmission", + 946 => "Cancellation of transaction while in progress", + 947 => "Duplicate tranmission while in progress", + 949 => "POS Inoperative", + 950 => "Refund not possible", + 9064 => "Card number incorrect", + 9078 => "No payment method available", + 9093 => "Non-existent card", + 9218 => "Recursive transaction in bad gateway", + 9253 => "Check-digit incorrect", + 9256 => "Preauth not allowed for merchant", + 9257 => "Preauth not allowed for card", + 9261 => "Operating limit exceeded", + 9912 => "Issuer not available", + 9913 => "Confirmation error", + 9914 => "KO Confirmation" + } + + # Creates a new instance + # + # Redsys requires a login and secret_key, and optionally also accepts a + # non-default terminal. + # + # ==== Options + # + # * :login -- The Redsys Merchant ID (REQUIRED) + # * :secret_key -- The Redsys Secret Key. (REQUIRED) + # * :terminal -- The Redsys Terminal. Defaults to 1. (OPTIONAL) + # * :test -- +true+ or +false+. Defaults to +false+. (OPTIONAL) + def initialize(options = {}) + requires!(options, :login, :secret_key) + options[:terminal] ||= 1 + super + end + + def purchase(money, creditcard, options = {}) + requires!(options, :order_id) + + data = {} + add_action(data, :purchase) + add_amount(data, money, options) + add_order(data, options[:order_id]) + add_creditcard(data, creditcard) + + commit data + end + + def authorize(money, creditcard, options = {}) + requires!(options, :order_id) + + data = {} + add_action(data, :authorize) + add_amount(data, money, options) + add_order(data, options[:order_id]) + add_creditcard(data, creditcard) + + commit data + end + + def capture(money, authorization, options = {}) + data = {} + add_action(data, :capture) + add_amount(data, money, options) + order_id, _, _ = split_authorization(authorization) + add_order(data, order_id) + + commit data + end + + def void(authorization, options = {}) + data = {} + add_action(data, :cancel) + order_id, amount, currency = split_authorization(authorization) + add_amount(data, amount, :currency => currency) + add_order(data, order_id) + + commit data + end + + def refund(money, authorization, options = {}) + data = {} + add_action(data, :refund) + add_amount(data, money, options) + order_id, _, _ = split_authorization(authorization) + add_order(data, order_id) + + commit data + end + + private + + def add_action(data, action) + data[:action] = transaction_code(action) + end + + def add_amount(data, money, options) + data[:amount] = amount(money).to_s + data[:currency] = currency_code(options[:currency] || currency(money)) + end + + def add_order(data, order_id) + raise ArgumentError.new("Invalid order_id format") unless(/^\d{4}[\da-zA-Z]{0,8}$/ =~ order_id) + data[:order_id] = order_id + end + + def url + test? ? test_url : live_url + end + + def add_creditcard(data, card) + name = [card.first_name, card.last_name].join(' ').slice(0, 60) + year = sprintf("%.4i", card.year) + month = sprintf("%.2i", card.month) + data[:card] = { + :name => name, + :pan => card.number, + :date => "#{year[2..3]}#{month}", + :cvv => card.verification_value + } + end + + def commit(data) + headers = { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + xml = build_xml_request(data) + parse(ssl_post(url, "entrada=#{CGI.escape(xml)}", headers)) + end + + def build_signature(data) + str = data[:amount] + + data[:order_id].to_s + + @options[:login].to_s + + data[:currency] + + if card = data[:card] + str << card[:pan] + str << card[:cvv] if card[:cvv] + end + + str << data[:action] + str << @options[:secret_key] + + Digest::SHA1.hexdigest(str) + end + + def build_xml_request(data) + xml = Builder::XmlMarkup.new :indent => 2 + xml.DATOSENTRADA do + # Basic elements + xml.DS_Version 0.1 + xml.DS_MERCHANT_CURRENCY data[:currency] + xml.DS_MERCHANT_AMOUNT data[:amount] + xml.DS_MERCHANT_ORDER data[:order_id] + xml.DS_MERCHANT_TRANSACTIONTYPE data[:action] + xml.DS_MERCHANT_TERMINAL @options[:terminal] + xml.DS_MERCHANT_MERCHANTCODE @options[:login] + xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) + + # Only when card is present + if data[:card] + xml.DS_MERCHANT_TITULAR data[:card][:name] + xml.DS_MERCHANT_PAN data[:card][:pan] + xml.DS_MERCHANT_EXPIRYDATE data[:card][:date] + xml.DS_MERCHANT_CVV2 data[:card][:cvv] + end + end + xml.target! + end + + def parse(data) + params = {} + success = false + message = "" + options = @options.merge(:test => test?) + xml = Nokogiri::XML(data) + code = xml.xpath("//RETORNOXML/CODIGO").text + if code == "0" + op = xml.xpath("//RETORNOXML/OPERACION") + op.children.each do |element| + params[element.name.downcase.to_sym] = element.text + end + + if validate_signature(params) + message = response_text(params[:ds_response]) + options[:authorization] = build_authorization(params) + success = is_success_response?(params[:ds_response]) + else + message = "Response failed validation check" + end + else + # Some kind of programmer error with the request! + message = "#{code} ERROR" + end + + Response.new(success, message, params, options) + end + + def validate_signature(data) + str = data[:ds_amount] + + data[:ds_order].to_s + + data[:ds_merchantcode] + + data[:ds_currency] + + data[:ds_response] + + data[:ds_cardnumber].to_s + + data[:ds_transactiontype].to_s + + data[:ds_securepayment].to_s + + @options[:secret_key] + + sig = Digest::SHA1.hexdigest(str) + data[:ds_signature].to_s.downcase == sig + end + + def build_authorization(params) + [params[:ds_order], params[:ds_amount], params[:ds_currency]].join("|") + end + + def split_authorization(authorization) + order_id, amount, currency = authorization.split("|") + [order_id, amount.to_i, currency] + end + + def currency_code(currency) + return currency if currency =~ /^\d+$/ + CURRENCY_CODES[currency] + end + + def transaction_code(type) + SUPPORTED_TRANSACTIONS[type] + end + + def response_text(code) + code = code.to_i + code = 0 if code < 100 + RESPONSE_TEXTS[code] || "Unkown code, please check in manual" + end + + def is_success_response?(code) + (code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage.rb new file mode 100644 index 000000000..5fbae0df9 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage.rb @@ -0,0 +1,152 @@ +require File.dirname(__FILE__) + '/sage/sage_bankcard' +require File.dirname(__FILE__) + '/sage/sage_virtual_check' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SageGateway < Gateway + self.supported_countries = SageBankcardGateway.supported_countries + self.supported_cardtypes = SageBankcardGateway.supported_cardtypes + + self.abstract_class = true + + # Creates a new SageGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * :login - The Sage Payment Solutions Merchant ID Number. + # * :password - The Sage Payment Solutions Merchant Key Number. + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Performs an authorization transaction + # + # ==== Parameters + # * money - The amount to be authorized as an integer value in cents. + # * credit_card - The CreditCard object to be used as the funding source for the transaction. + # * options - A hash of optional parameters. + # * :order_id - A unique reference for this order. (maximum of 20 characters). + # * :email - The customer's email address + # * :customer - The Customer Number for Purchase Card Level II Transactions + # * :billing_address - The customer's billing address as a hash of address information. + # * :address1 - The billing address street + # * :city - The billing address city + # * :state - The billing address state + # * :country - The 2 digit ISO billing address country code + # * :zip - The billing address zip code + # * :phone - The billing address phone number + # * :fax - The billing address fax number + # * :shipping_address - The customer's shipping address as a hash of address information. + # * :name - The name at the shipping address + # * :address1 - The shipping address street + # * :city - The shipping address city + # * :state - The shipping address state code + # * :country - The 2 digit ISO shipping address country code + # * :zip - The shipping address zip code + # * :tax - The tax amount for the transaction as an Integer value in cents. Maps to Sage T_tax. + # * :shipping - The shipping amount for the transaction as an Integer value in cents. Maps to Sage T_shipping. + def authorize(money, credit_card, options = {}) + bankcard.authorize(money, credit_card, options) + end + + # Performs a purchase, which is essentially an authorization and capture in a single operation. + # + # ==== Parameters + # + # * money - The amount to be authorized as an integer value in cents. + # * source - The CreditCard or Check object to be used as the funding source for the transaction. + # * options - A hash of optional parameters. + # * :order_id - A unique reference for this order. (maximum of 20 characters). + # * :email - The customer's email address + # * :customer - The Customer Number for Purchase Card Level II Transactions + # * :billing_address - The customer's billing address as a hash of address information. + # * :address1 - The billing address street + # * :city - The billing address city + # * :state - The billing address state + # * :country - The 2 digit ISO billing address country code + # * :zip - The billing address zip code + # * :phone - The billing address phone number + # * :fax - The billing address fax number + # * :shipping_address - The customer's shipping address as a hash of address information. + # * :name - The name at the shipping address + # * :address1 - The shipping address street + # * :city - The shipping address city + # * :state - The shipping address state code + # * :country - The 2 digit ISO shipping address country code + # * :zip - The shipping address zip code + # * :tax - The tax amount for the transaction as an integer value in cents. Maps to Sage T_tax. + # * :shipping - The shipping amount for the transaction as an integer value in cents. Maps to Sage T_shipping. + # + # ==== Additional options in the +options+ hash for when using a Check as the funding source + # * :originator_id - 10 digit originator. If not provided, Sage will use the default Originator ID for the specific customer type. + # * :addenda - Transaction addenda. + # * :ssn - The customer's Social Security Number. + # * :drivers_license_state - The customer's drivers license state code. + # * :drivers_license_number - The customer's drivers license number. + # * :date_of_birth - The customer's date of birth as a Time or Date object or a string in the format mm/dd/yyyy. + def purchase(money, source, options = {}) + if card_brand(source) == "check" + virtual_check.purchase(money, source, options) + else + bankcard.purchase(money, source, options) + end + end + + # Captures authorized funds. + # + # ==== Parameters + # + # * money - The amount to be authorized as an integer value in cents. Sage doesn't support changing the capture amount, so the full amount of the initial transaction will be captured. + # * reference - The authorization reference string returned by the original transaction's Response#authorization. + def capture(money, reference, options = {}) + bankcard.capture(money, reference, options) + end + + # Voids a prior transaction. Works for both CreditCard and Check transactions. + # + # ==== Parameters + # + # * reference - The authorization reference string returned by the original transaction's Response#authorization. + def void(reference, options = {}) + if reference.split(";").last == "virtual_check" + virtual_check.void(reference, options) + else + bankcard.void(reference, options) + end + end + + def credit(money, source, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, source, options) + end + + # Performs a refund transaction. + # + # ==== Parameters + # + # * money - The amount to be authorized as an integer value in cents. + # * source - The CreditCard or Check object to be used as the target for the refund. + def refund(money, source, options = {}) + if card_brand(source) == "check" + virtual_check.refund(money, source, options) + else + bankcard.refund(money, source, options) + end + end + + private + def bankcard + @bankcard ||= SageBankcardGateway.new(@options) + end + + def virtual_check + @virtual_check ||= SageVirtualCheckGateway.new(@options) + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb new file mode 100644 index 000000000..893db3ee4 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb @@ -0,0 +1,93 @@ +require File.dirname(__FILE__) + '/sage_core' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SageBankcardGateway < Gateway #:nodoc: + include SageCore + self.live_url = 'https://www.sagepayments.net/cgi-bin/eftBankcard.dll?transaction' + self.source = 'bankcard' + + # Credit cards supported by Sage + # * VISA + # * MasterCard + # * AMEX + # * Diners + # * Carte Blanche + # * Discover + # * JCB + # * Sears + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + def authorize(money, credit_card, options = {}) + post = {} + add_credit_card(post, credit_card) + add_transaction_data(post, money, options) + commit(:authorization, post) + end + + def purchase(money, credit_card, options = {}) + post = {} + add_credit_card(post, credit_card) + add_transaction_data(post, money, options) + commit(:purchase, post) + end + + # The +money+ amount is not used. The entire amount of the + # initial authorization will be captured. + def capture(money, reference, options = {}) + post = {} + add_reference(post, reference) + commit(:capture, post) + end + + def void(reference, options = {}) + post = {} + add_reference(post, reference) + commit(:void, post) + end + + def credit(money, credit_card, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, credit_card, options) + end + + def refund(money, credit_card, options = {}) + post = {} + add_credit_card(post, credit_card) + add_transaction_data(post, money, options) + commit(:credit, post) + end + + private + def exp_date(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def add_credit_card(post, credit_card) + post[:C_name] = credit_card.name + post[:C_cardnumber] = credit_card.number + post[:C_exp] = exp_date(credit_card) + post[:C_cvv] = credit_card.verification_value if credit_card.verification_value? + end + + def parse(data) + response = {} + response[:success] = data[1,1] + response[:code] = data[2,6] + response[:message] = data[8,32].strip + response[:front_end] = data[40, 2] + response[:cvv_result] = data[42, 1] + response[:avs_result] = data[43, 1].strip + response[:risk] = data[44, 2] + response[:reference] = data[46, 10] + + response[:order_number], response[:recurring] = data[57...-1].split("\034") + response + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_core.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_core.rb new file mode 100644 index 000000000..41580d8cc --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_core.rb @@ -0,0 +1,114 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module SageCore #:nodoc: + def self.included(base) + base.cattr_accessor :source + base.supported_countries = ['US', 'CA'] + base.homepage_url = 'http://www.sagepayments.com' + base.display_name = 'Sage Payment Solutions' + end + + # Transactions types: + # 01 - Sale + # 02 - AuthOnly + # 03 - Force/PriorAuthSale + # 04 - Void + # 06 - Credit + # 11 - PriorAuthSale by Reference* + TRANSACTIONS = { + :purchase => '01', + :authorization => '02', + :capture => '11', + :void => '04', + :credit => '06' + } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + private + def add_invoice(post, options) + post[:T_ordernum] = options[:order_id].slice(0, 20) + post[:T_tax] = amount(options[:tax]) unless options[:tax].blank? + post[:T_shipping] = amount(options[:shipping]) unless options[:shipping].blank? + end + + def add_reference(post, reference) + ref, source = reference.to_s.split(";") + post[:T_reference] = ref + end + + def add_amount(post, money) + post[:T_amt] = amount(money) + end + + def add_customer_data(post, options) + post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil + end + + def add_addresses(post, options) + billing_address = options[:billing_address] || options[:address] || {} + + post[:C_address] = billing_address[:address1] + post[:C_city] = billing_address[:city] + + if ['US', 'CA'].include?(billing_address[:country]) + post[:C_state] = billing_address[:state] + else + post[:C_state] = "Outside of United States" + end + + post[:C_zip] = billing_address[:zip] + post[:C_country] = billing_address[:country] + post[:C_telephone] = billing_address[:phone] + post[:C_fax] = billing_address[:fax] + post[:C_email] = options[:email] + + if shipping_address = options[:shipping_address] + post[:C_ship_name] = shipping_address[:name] + post[:C_ship_address] = shipping_address[:address1] + post[:C_ship_city] = shipping_address[:city] + post[:C_ship_state] = shipping_address[:state] + post[:C_ship_zip] = shipping_address[:zip] + post[:C_ship_country] = shipping_address[:country] + end + end + + def add_transaction_data(post, money, options) + add_amount(post, money) + add_invoice(post, options) + add_addresses(post, options) + add_customer_data(post, options) + end + + def commit(action, params) + response = parse(ssl_post(self.live_url, post_data(action, params))) + + Response.new(success?(response), response[:message], response, + :test => test?, + :authorization => authorization_from(response), + :avs_result => { :code => response[:avs_result] }, + :cvv_result => response[:cvv_result] + ) + end + + def authorization_from(response) + "#{response[:reference]};#{source}" + end + + def success?(response) + response[:success] == 'A' + end + + def post_data(action, params = {}) + params[:M_id] = @options[:login] + params[:M_key] = @options[:password] + params[:T_code] = TRANSACTIONS[action] + + params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb new file mode 100644 index 000000000..e197fe5d1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb @@ -0,0 +1,102 @@ +require File.dirname(__FILE__) + '/sage_core' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SageVirtualCheckGateway < Gateway #:nodoc: + include SageCore + self.live_url = 'https://www.sagepayments.net/cgi-bin/eftVirtualCheck.dll?transaction' + self.source = 'virtual_check' + + def purchase(money, credit_card, options = {}) + post = {} + add_check(post, credit_card) + add_check_customer_data(post, options) + add_transaction_data(post, money, options) + commit(:purchase, post) + end + + def void(reference, options = {}) + post = {} + add_reference(post, reference) + commit(:void, post) + end + + def credit(money, credit_card, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, source, options) + end + + def refund(money, credit_card, options = {}) + post = {} + add_check(post, credit_card) + add_check_customer_data(post, options) + add_transaction_data(post, money, options) + commit(:credit, post) + end + + private + def add_check(post, check) + post[:C_first_name] = check.first_name + post[:C_last_name] = check.last_name + post[:C_rte] = check.routing_number + post[:C_acct] = check.account_number + post[:C_check_number] = check.number + post[:C_acct_type] = account_type(check) + end + + def add_check_customer_data(post, options) + # Required  Customer Type – (NACHA Transaction Class) + # CCD for Commercial, Merchant Initiated + # PPD for Personal, Merchant Initiated + # WEB for Internet, Consumer Initiated + # RCK for Returned Checks + # ARC for Account Receivable Entry + # TEL for TelephoneInitiated + post[:C_customer_type] = "WEB" + + # Optional  10  Digit Originator  ID – Assigned  By for  each transaction  class  or  business  purpose. If  not provided, the default Originator ID for the specific  Customer Type will be applied.  + post[:C_originator_id] = options[:originator_id] + + # Optional  Transaction Addenda + post[:T_addenda] = options[:addenda] + + # Required  Check  Writer  Social  Security  Number  (  Numbers Only, No Dashes )  + post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '') + + post[:C_dl_state_code] = options[:drivers_license_state] + post[:C_dl_number] = options[:drivers_license_number] + post[:C_dob] = format_birth_date(options[:date_of_birth]) + end + + def format_birth_date(date) + date.respond_to?(:strftime) ? date.strftime("%m/%d/%Y") : date + end + + # DDA for Checking + # SAV for Savings  + def account_type(check) + case check.account_type + when 'checking' then 'DDA' + when 'savings' then 'SAV' + else raise ArgumentError, "Unknown account type #{check.account_type}" + end + end + + def parse(data) + response = {} + response[:success] = data[1,1] + response[:code] = data[2,6].strip + response[:message] = data[8,32].strip + response[:risk] = data[40, 2] + response[:reference] = data[42, 10] + + extra_data = data[53...-1].split("\034") + response[:order_number] = extra_data[0] + response[:authentication_indicator] = extra_data[1] + response[:authentication_disclosure] = extra_data[2] + response + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage_pay.rb new file mode 100644 index 000000000..41a649a6c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sage_pay.rb @@ -0,0 +1,324 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SagePayGateway < Gateway + cattr_accessor :simulate + self.simulate = false + + class_attribute :simulator_url + + self.test_url = 'https://test.sagepay.com/gateway/service' + self.live_url = 'https://live.sagepay.com/gateway/service' + self.simulator_url = 'https://test.sagepay.com/Simulator' + + APPROVED = 'OK' + + TRANSACTIONS = { + :purchase => 'PAYMENT', + :credit => 'REFUND', + :authorization => 'DEFERRED', + :capture => 'RELEASE', + :void => 'VOID', + :abort => 'ABORT' + } + + CREDIT_CARDS = { + :visa => "VISA", + :master => "MC", + :delta => "DELTA", + :solo => "SOLO", + :switch => "MAESTRO", + :maestro => "MAESTRO", + :american_express => "AMEX", + :electron => "UKE", + :diners_club => "DC", + :jcb => "JCB" + } + + ELECTRON = /^(424519|42496[23]|450875|48440[6-8]|4844[1-5][1-5]|4917[3-5][0-9]|491880)\d{10}(\d{3})?$/ + + AVS_CVV_CODE = { + "NOTPROVIDED" => nil, + "NOTCHECKED" => 'X', + "MATCHED" => 'Y', + "NOTMATCHED" => 'N' + } + + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :switch, :solo, :maestro, :diners_club] + self.supported_countries = ['GB'] + self.default_currency = 'GBP' + + self.homepage_url = 'http://www.sagepay.com' + self.display_name = 'SagePay' + + def initialize(options = {}) + requires!(options, :login) + super + end + + def purchase(money, credit_card, options = {}) + requires!(options, :order_id) + + post = {} + + add_amount(post, money, options) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, options) + add_customer_data(post, options) + add_optional_data(post, options) + + commit(:purchase, post) + end + + def authorize(money, credit_card, options = {}) + requires!(options, :order_id) + + post = {} + + add_amount(post, money, options) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, options) + add_customer_data(post, options) + add_optional_data(post, options) + + commit(:authorization, post) + end + + # You can only capture a transaction once, even if you didn't capture the full amount the first time. + def capture(money, identification, options = {}) + post = {} + + add_reference(post, identification) + add_release_amount(post, money, options) + + commit(:capture, post) + end + + def void(identification, options = {}) + post = {} + + add_reference(post, identification) + action = abort_or_void_from(identification) + + commit(action, post) + end + + # Refunding requires a new order_id to passed in, as well as a description + def refund(money, identification, options = {}) + requires!(options, :order_id, :description) + + post = {} + + add_credit_reference(post, identification) + add_amount(post, money, options) + add_invoice(post, options) + + commit(:credit, post) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + private + def add_reference(post, identification) + order_id, transaction_id, authorization, security_key = identification.split(';') + + add_pair(post, :VendorTxCode, order_id) + add_pair(post, :VPSTxId, transaction_id) + add_pair(post, :TxAuthNo, authorization) + add_pair(post, :SecurityKey, security_key) + end + + def add_credit_reference(post, identification) + order_id, transaction_id, authorization, security_key = identification.split(';') + + add_pair(post, :RelatedVendorTxCode, order_id) + add_pair(post, :RelatedVPSTxId, transaction_id) + add_pair(post, :RelatedTxAuthNo, authorization) + add_pair(post, :RelatedSecurityKey, security_key) + end + + def add_amount(post, money, options) + currency = options[:currency] || currency(money) + add_pair(post, :Amount, localized_amount(money, currency), :required => true) + add_pair(post, :Currency, currency, :required => true) + end + + # doesn't actually use the currency -- dodgy! + def add_release_amount(post, money, options) + add_pair(post, :ReleaseAmount, amount(money), :required => true) + end + + def add_customer_data(post, options) + add_pair(post, :CustomerEMail, options[:email][0,255]) unless options[:email].blank? + add_pair(post, :BillingPhone, options[:phone].gsub(/[^0-9+]/, '')[0,20]) unless options[:phone].blank? + add_pair(post, :ClientIPAddress, options[:ip]) + end + + def add_optional_data(post, options) + add_pair(post, :GiftAidPayment, options[:gift_aid_payment]) unless options[:gift_aid_payment].blank? + add_pair(post, :Apply3DSecure, options[:apply_3d_secure]) unless options[:apply_3d_secure].blank? + end + + def add_address(post, options) + if billing_address = options[:billing_address] || options[:address] + first_name, last_name = parse_first_and_last_name(billing_address[:name]) + add_pair(post, :BillingSurname, last_name) + add_pair(post, :BillingFirstnames, first_name) + add_pair(post, :BillingAddress1, billing_address[:address1]) + add_pair(post, :BillingAddress2, billing_address[:address2]) + add_pair(post, :BillingCity, billing_address[:city]) + add_pair(post, :BillingState, billing_address[:state]) if billing_address[:country] == 'US' + add_pair(post, :BillingCountry, billing_address[:country]) + add_pair(post, :BillingPostCode, billing_address[:zip]) + end + + if shipping_address = options[:shipping_address] || billing_address + first_name, last_name = parse_first_and_last_name(shipping_address[:name]) + add_pair(post, :DeliverySurname, last_name) + add_pair(post, :DeliveryFirstnames, first_name) + add_pair(post, :DeliveryAddress1, shipping_address[:address1]) + add_pair(post, :DeliveryAddress2, shipping_address[:address2]) + add_pair(post, :DeliveryCity, shipping_address[:city]) + add_pair(post, :DeliveryState, shipping_address[:state]) if shipping_address[:country] == 'US' + add_pair(post, :DeliveryCountry, shipping_address[:country]) + add_pair(post, :DeliveryPostCode, shipping_address[:zip]) + end + end + + def add_invoice(post, options) + add_pair(post, :VendorTxCode, sanitize_order_id(options[:order_id]), :required => true) + add_pair(post, :Description, options[:description] || options[:order_id]) + end + + def add_credit_card(post, credit_card) + add_pair(post, :CardHolder, credit_card.name, :required => true) + add_pair(post, :CardNumber, credit_card.number, :required => true) + + add_pair(post, :ExpiryDate, format_date(credit_card.month, credit_card.year), :required => true) + + if requires_start_date_or_issue_number?(credit_card) + add_pair(post, :StartDate, format_date(credit_card.start_month, credit_card.start_year)) + add_pair(post, :IssueNumber, credit_card.issue_number) + end + add_pair(post, :CardType, map_card_type(credit_card)) + + add_pair(post, :CV2, credit_card.verification_value) + end + + def sanitize_order_id(order_id) + order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '') + end + + def map_card_type(credit_card) + raise ArgumentError, "The credit card type must be provided" if card_brand(credit_card).blank? + + card_type = card_brand(credit_card).to_sym + + # Check if it is an electron card + if card_type == :visa && credit_card.number =~ ELECTRON + CREDIT_CARDS[:electron] + else + CREDIT_CARDS[card_type] + end + end + + # MMYY format + def format_date(month, year) + return nil if year.blank? || month.blank? + + year = sprintf("%.4i", year) + month = sprintf("%.2i", month) + + "#{month}#{year[-2..-1]}" + end + + def commit(action, parameters) + response = parse( ssl_post(url_for(action), post_data(action, parameters)) ) + + Response.new(response["Status"] == APPROVED, message_from(response), response, + :test => test?, + :authorization => authorization_from(response, parameters, action), + :avs_result => { + :street_match => AVS_CVV_CODE[ response["AddressResult"] ], + :postal_match => AVS_CVV_CODE[ response["PostCodeResult"] ], + }, + :cvv_result => AVS_CVV_CODE[ response["CV2Result"] ] + ) + end + + def authorization_from(response, params, action) + [ params[:VendorTxCode], + response["VPSTxId"], + response["TxAuthNo"], + response["SecurityKey"], + action ].join(";") + end + + def abort_or_void_from(identification) + original_transaction = identification.split(';').last + original_transaction == 'authorization' ? :abort : :void + end + + def url_for(action) + simulate ? build_simulator_url(action) : build_url(action) + end + + def build_url(action) + endpoint = [ :purchase, :authorization ].include?(action) ? "vspdirect-register" : TRANSACTIONS[action].downcase + "#{test? ? self.test_url : self.live_url}/#{endpoint}.vsp" + end + + def build_simulator_url(action) + endpoint = [ :purchase, :authorization ].include?(action) ? "VSPDirectGateway.asp" : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx" + "#{self.simulator_url}/#{endpoint}" + end + + def message_from(response) + response['Status'] == APPROVED ? 'Success' : (response['StatusDetail'] || 'Unspecified error') # simonr 20080207 can't actually get non-nil blanks, so this is shorter + end + + def post_data(action, parameters = {}) + parameters.update( + :Vendor => @options[:login], + :TxType => TRANSACTIONS[action], + :VPSProtocol => "2.23" + ) + + parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + # SagePay returns data in the following format + # Key1=value1 + # Key2=value2 + def parse(body) + result = {} + body.to_s.each_line do |pair| + result[$1] = $2 if pair.strip =~ /\A([^=]+)=(.+)\Z/im + end + result + end + + def add_pair(post, key, value, options = {}) + post[key] = value if !value.blank? || options[:required] + end + + def parse_first_and_last_name(value) + name = value.to_s.split(' ') + + last_name = name.pop || '' + first_name = name.join(' ') + [ first_name[0,20], last_name[0,20] ] + end + + def localized_amount(money, currency) + amount = amount(money) + CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) ? amount.split('.').first : amount + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sallie_mae.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sallie_mae.rb new file mode 100644 index 000000000..1098790d2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/sallie_mae.rb @@ -0,0 +1,143 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SallieMaeGateway < Gateway + self.live_url = self.test_url = 'https://trans.salliemae.com/cgi-bin/process.cgi' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.salliemae.com/' + + # The name of the gateway + self.display_name = 'Sallie Mae' + + def initialize(options = {}) + requires!(options, :login) + super + end + + def test? + @options[:login] == "TEST0" + end + + def authorize(money, creditcard, options = {}) + post = PostData.new + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, creditcard, options) + add_customer_data(post, options) + + commit(:authonly, money, post) + end + + def purchase(money, creditcard, options = {}) + post = PostData.new + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, creditcard, options) + add_customer_data(post, options) + + commit(:sale, money, post) + end + + def capture(money, authorization, options = {}) + post = PostData.new + post[:postonly] = authorization + commit(:capture, money, post) + end + + private + + def add_customer_data(post, options) + if address = options[:billing_address] || options[:shipping_address] || options[:address] + post[:ci_phone] = address[:phone].to_s + end + + post[:ci_email] = options[:email].to_s unless options[:email].blank? + post[:ci_IP] = options[:ip].to_s unless options[:ip].blank? + end + + def add_address(post, creditcard, options) + if address = options[:billing_address] || options[:address] + post[:ci_billaddr1] = address[:address1].to_s + post[:ci_billaddr2] = address[:address2].to_s unless address[:address2].blank? + post[:ci_billcity] = address[:city].to_s + post[:ci_billstate] = address[:state].to_s + post[:ci_billzip] = address[:zip].to_s + end + + if shipping_address = options[:shipping_address] || options[:address] + post[:ci_shipaddr1] = shipping_address[:address1].to_s + post[:ci_shipaddr2] = shipping_address[:address2].to_s unless shipping_address[:address2].blank? + post[:ci_shipcity] = shipping_address[:city].to_s + post[:ci_shipstate] = shipping_address[:state].to_s + post[:ci_shipzip] = shipping_address[:zip].to_s + end + end + + def add_invoice(post, options) + memo = "OrderID: #{options[:order_id]}\nDescription: #{options[:description]}" + post[:ci_memo] = memo + end + + def add_creditcard(post, creditcard) + post[:ccnum] = creditcard.number.to_s + post[:ccname] = creditcard.name.to_s + post[:cvv2] = creditcard.verification_value.to_s if creditcard.verification_value? + post[:expmon] = creditcard.month.to_s + post[:expyear] = creditcard.year.to_s + end + + def parse(body) + h = {} + body.gsub!("", "") + body. + split("\r\n"). + map do |i| + a = i.split("=") + h[a.first] = a.last unless a.first.nil? + end + h + end + + def commit(action, money, parameters) + parameters[:acctid] = @options[:login].to_s + parameters[:subid] = @options[:sub_id].to_s unless @options[:sub_id].blank? + parameters[:amount] = amount(money) + + case action + when :sale + parameters[:action] = "ns_quicksale_cc" + when :authonly + parameters[:action] = "ns_quicksale_cc" + parameters[:authonly] = 1 + when :capture + parameters[:action] = "ns_quicksale_cc" + end + + response = parse(ssl_post(self.live_url, parameters.to_post_data) || "") + Response.new(successful?(response), message_from(response), response, + :test => test?, + :authorization => response["refcode"] + ) + end + + def successful?(response) + response["Status"] == "Accepted" + end + + def message_from(response) + if successful?(response) + "Accepted" + else + response["Reason"].split(":")[2].capitalize unless response["Reason"].nil? + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/samurai.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/samurai.rb new file mode 100644 index 000000000..c263c79f0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/samurai.rb @@ -0,0 +1,118 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SamuraiGateway < Gateway + + self.homepage_url = 'https://samurai.feefighters.com' + self.display_name = 'Samurai' + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + self.default_currency = 'USD' + self.money_format = :dollars + + def initialize(options = {}) + begin + require 'samurai' + rescue LoadError + raise "Could not load the samurai gem (>= 0.2.25). Use `gem install samurai` to install it." + end + + requires!(options, :login, :password, :processor_token) + Samurai.options = { + :merchant_key => options[:login], + :merchant_password => options[:password], + :processor_token => options[:processor_token] + } + + super + end + + def authorize(money, credit_card_or_vault_id, options = {}) + token = payment_method_token(credit_card_or_vault_id, options) + return token if token.is_a?(Response) + + authorize = Samurai::Processor.authorize(token, amount(money), processor_options(options)) + handle_result(authorize) + end + + def purchase(money, credit_card_or_vault_id, options = {}) + token = payment_method_token(credit_card_or_vault_id, options) + return token if token.is_a?(Response) + + purchase = Samurai::Processor.purchase(token, amount(money), processor_options(options)) + handle_result(purchase) + end + + def capture(money, authorization_id, options = {}) + transaction = Samurai::Transaction.find(authorization_id) + handle_result(transaction.capture(amount(money))) + end + + def refund(money, transaction_id, options = {}) + transaction = Samurai::Transaction.find(transaction_id) + handle_result(transaction.credit(amount(money))) + end + + def void(transaction_id, options = {}) + transaction = Samurai::Transaction.find(transaction_id) + handle_result(transaction.void) + end + + def store(creditcard, options = {}) + address = options[:billing_address] || options[:address] || {} + + result = Samurai::PaymentMethod.create({ + :card_number => creditcard.number, + :expiry_month => creditcard.month.to_s.rjust(2, "0"), + :expiry_year => creditcard.year.to_s, + :cvv => creditcard.verification_value, + :first_name => creditcard.first_name, + :last_name => creditcard.last_name, + :address_1 => address[:address1], + :address_2 => address[:address2], + :city => address[:city], + :zip => address[:zip], + :sandbox => test? + }) + result.retain if options[:retain] && result.is_sensitive_data_valid && result.payment_method_token + + Response.new(result.is_sensitive_data_valid, + message_from_result(result), + { :payment_method_token => result.is_sensitive_data_valid && result.payment_method_token }) + end + + private + + def payment_method_token(credit_card_or_vault_id, options) + return credit_card_or_vault_id if credit_card_or_vault_id.is_a?(String) + store_result = store(credit_card_or_vault_id, options) + store_result.success? ? store_result.params["payment_method_token"] : store_result + end + + def handle_result(result) + response_params, response_options = {}, {} + if result.success? + response_options[:test] = test? + response_options[:authorization] = result.reference_id + response_params[:reference_id] = result.reference_id + response_params[:transaction_token] = result.transaction_token + response_params[:payment_method_token] = result.payment_method.payment_method_token + end + + response_options[:avs_result] = { :code => result.processor_response && result.processor_response.avs_result_code } + response_options[:cvv_result] = result.processor_response && result.processor_response.cvv_result_code + + message = message_from_result(result) + Response.new(result.success?, message, response_params, response_options) + end + + def message_from_result(result) + return "OK" if result.success? + result.errors.map {|_, messages| messages }.join(" ") + end + + def processor_options(options) + options.slice(:billing_reference, :customer_reference, :custom, :descriptor) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_net.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_net.rb new file mode 100644 index 000000000..52973b89d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_net.rb @@ -0,0 +1,329 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SecureNetGateway < Gateway + + API_VERSION = "4.0" + + TRANSACTIONS = { + :auth_only => "0000", # + :partial_auth_only => "0001", + :auth_capture => "0100", # + :partial_auth_capture => "0101", + :prior_auth_capture => "0200", + :capture_only => "0300", # + :void => "0400", # + :partial_void => "0401", + :credit => "0500", # + :credit_authonly => "0501", + :credit_priorauthcapture => "0502", + :force_credit => "0600", + :force_credit_authonly => "0601", + :force_credit_priorauthcapture => "0602", + :verification => "0700", + :auth_increment => "0800", + :issue => "0900", + :activate => "0901", + :redeem => "0902", + :redeem_partial => "0903", + :deactivate => "0904", + :reactivate => "0905", + :inquiry_balance => "0906" + } + + XML_ATTRIBUTES = { 'xmlns' => "http://gateway.securenet.com/API/Contracts", + 'xmlns:i' => "http://www.w3.org/2001/XMLSchema-instance" + } + NIL_ATTRIBUTE = { 'i:nil' => "true" } + +# SUCCESS = "true" +# SENSITIVE_FIELDS = [ :verification_str2, :expiry_date, :card_number ] + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.securenet.com/' + self.display_name = 'SecureNet' +# self.wiredump_device = STDOUT + +# self.test_url = 'https://certify.securenet.com/api/Gateway.svc' + self.test_url = 'https://certify.securenet.com/API/gateway.svc/webHttp/ProcessTransaction' + self.live_url = 'https://gateway.securenet.com/api/Gateway.svc' + + APPROVED, DECLINED, ERROR = 1, 2, 3 + + RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT = 0, 2, 3 + AVS_RESULT_CODE, CARD_CODE_RESPONSE_CODE, TRANSACTION_ID = 5, 6, 8 + + CARD_CODE_ERRORS = %w( N S ) + AVS_ERRORS = %w( A E N R W Z ) + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard, options = {}) + commit(build_sale_or_authorization_request(creditcard, options, :auth_only), money) + end + + def purchase(money, creditcard, options = {}) + commit(build_sale_or_authorization_request(creditcard, options, :auth_capture), money) + end + + def capture(money, creditcard, authorization, options = {}) + commit(build_capture_request(authorization, creditcard, options, :prior_auth_capture), money) + end + + def void(money, creditcard, authorization, options = {}) + commit(build_void_request(authorization, creditcard, options, :void), money) + end + + def credit(money, creditcard, authorization, options = {}) + commit(build_credit_request(authorization, creditcard, options, :credit), money) + end + + private + def commit(request, money) + xml = build_request(request, money) + data = ssl_post(self.test_url, xml, "Content-Type" => "text/xml") + response = parse(data) + + test_mode = test? + Response.new(success?(response), message_from(response), response, + :test => test_mode, + :authorization => response[:transactionid], + :avs_result => { :code => response[:avs_result_code] }, + :cvv_result => response[:card_code_response_code] + ) + end + + def build_request(request, money) + xml = Builder::XmlMarkup.new + + xml.instruct! + xml.tag!("TRANSACTION", XML_ATTRIBUTES) do + xml.tag! 'AMOUNT', amount(money) + xml << request + end + + xml.target! + end + + def build_sale_or_authorization_request(creditcard, options, action) + xml = Builder::XmlMarkup.new + + add_credit_card(xml, creditcard) + xml.tag! 'CODE', TRANSACTIONS[action] + add_customer_data(xml, options) + add_address(xml, creditcard, options) + xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID + xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 + add_invoice(xml, options) + add_merchant_key(xml, options) + xml.tag! 'METHOD', 'CC' + xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] + xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it + xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? + xml.tag! 'TEST', 'TRUE' + xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 + xml.tag! 'TRANSACTION_SERVICE', 0 + + xml.target! + end + + def build_capture_or_credit_request(identification, options) + xml = Builder::XmlMarkup.new + + add_identification(xml, identification) + add_customer_data(xml, options) + + xml.target! + end + + def build_capture_request(authorization, creditcard, options, action) + xml = Builder::XmlMarkup.new + + add_credit_card(xml, creditcard) + xml.tag! 'CODE', TRANSACTIONS[action] + add_customer_data(xml, options) + xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID + xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 + add_merchant_key(xml, options) + xml.tag! 'METHOD', 'CC' + xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] + xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it + xml.tag! 'REF_TRANSID', authorization + xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? + xml.tag! 'TEST', 'TRUE' + xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 + xml.tag! 'TRANSACTION_SERVICE', 0 + + xml.target! + end + + def build_credit_request(authorization, creditcard, options, action) +# requires!(options, :card_number) + xml = Builder::XmlMarkup.new + + add_credit_card(xml, creditcard) + xml.tag! 'CODE', TRANSACTIONS[action] + add_customer_data(xml, options) + xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID + xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 + add_merchant_key(xml, options) + xml.tag! 'METHOD', 'CC' + xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] + xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it + xml.tag! 'REF_TRANSID', authorization + xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? + xml.tag! 'TEST', 'TRUE' + xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 + xml.tag! 'TRANSACTION_SERVICE', 0 + + xml.target! + end + + def build_void_request(authorization, creditcard, options, action) + xml = Builder::XmlMarkup.new + + add_credit_card(xml, creditcard) + xml.tag! 'CODE', TRANSACTIONS[action] + add_customer_data(xml, options) + xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID + xml.tag! 'INSTALLMENT_SEQUENCENUM', 1 + add_merchant_key(xml, options) + xml.tag! 'METHOD', 'CC' + xml.tag! 'ORDERID', options[:order_id]#'30'.to_i.to_s#'22'# @options[:order_id] + xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it + xml.tag! 'REF_TRANSID', authorization + xml.tag! 'RETAIL_LANENUM', '0' # Docs say string, but it's an integer!? + xml.tag! 'TEST', 'TRUE' + xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0 + xml.tag! 'TRANSACTION_SERVICE', 0 + + xml.target! + end + + ######################################################################### + # FUNCTIONS RELATED TO BUILDING THE XML + ######################################################################### + def add_credit_card(xml, creditcard) + xml.tag!("CARD") do + xml.tag! 'CARDCODE', creditcard.verification_value if creditcard.verification_value? + xml.tag! 'CARDNUMBER', creditcard.number + xml.tag! 'EXPDATE', expdate(creditcard) + end + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}#{year[-2..-1]}" + end + + def add_customer_data(xml, options) + if options.has_key? :customer + xml.tag! 'CUSTOMERID', options[:customer] + end + + if options.has_key? :ip + xml.tag! 'CUSTOMERIP', options[:ip] + end + end + + def add_address(xml, creditcard, options) + + if address = options[:billing_address] || options[:address] + xml.tag!("CUSTOMER_BILL") do + xml.tag! 'ADDRESS', address[:address1].to_s + xml.tag! 'CITY', address[:city].to_s + xml.tag! 'COMPANY', address[:company].to_s + xml.tag! 'COUNTRY', address[:country].to_s + if options.has_key? :email + xml.tag! 'EMAIL', options[:email] +# xml.tag! 'EMAIL', 'myemail@yahoo.com' + xml.tag! 'EMAILRECEIPT', 'FALSE' + end + xml.tag! 'FIRSTNAME', creditcard.first_name + xml.tag! 'LASTNAME', creditcard.last_name + xml.tag! 'PHONE', address[:phone].to_s + xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] + xml.tag! 'ZIP', address[:zip].to_s + end + end + + if address = options[:shipping_address] + xml.tag!("CUSTOMER_SHIP") do + xml.tag! 'ADDRESS', address[:address1].to_s + xml.tag! 'CITY', address[:city].to_s + xml.tag! 'COMPANY', address[:company].to_s + xml.tag! 'COUNTRY', address[:country].to_s + xml.tag! 'FIRSTNAME', address[:first_name].to_s + xml.tag! 'LASTNAME', address[:last_name].to_s + xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state] + xml.tag! 'ZIP', address[:zip].to_s + end + else + xml.tag!('CUSTOMER_SHIP', NIL_ATTRIBUTE) do + end + end + + end + + def add_invoice(xml, options) + xml.tag! 'INVOICEDESC', options[:description] + xml.tag! 'INVOICENUM', 'inv-8' + end + + def add_merchant_key(xml, options) + xml.tag!("MERCHANT_KEY") do + xml.tag! 'GROUPID', 0 + xml.tag! 'SECUREKEY', @options[:password] + xml.tag! 'SECURENETID', @options[:login] + end + end + + ######################################################################### + # FUNCTIONS RELATED TO THE RESPONSE + ######################################################################### + def success?(response) + response[:response_code].to_i == APPROVED + end + + def message_from(response) + if response[:response_code].to_i == DECLINED + return CVVResult.messages[ response[:card_code_response_code] ] if CARD_CODE_ERRORS.include?(response[:card_code_response_code]) + return AVSResult.messages[ response[:avs_result_code] ] if AVS_ERRORS.include?(response[:avs_result_code]) + end + + return response[:response_reason_text].nil? ? '' : response[:response_reason_text][0..-1] + end + + def parse(xml) + response = {} + xml = REXML::Document.new(xml) + root = REXML::XPath.first(xml, "//GATEWAYRESPONSE")# || +# root = REXML::XPath.first(xml, "//ProcessTransactionResponse")# || +# 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 + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay.rb new file mode 100644 index 000000000..96aca9e82 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay.rb @@ -0,0 +1,28 @@ +require File.dirname(__FILE__) + '/authorize_net' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SecurePayGateway < AuthorizeNetGateway + self.live_url = self.test_url = 'https://www.securepay.com/AuthSpayAdapter/process.aspx' + + self.homepage_url = 'http://www.securepay.com/' + self.display_name = 'SecurePay' + + # Limit support to purchase() for the time being + # JRuby chokes here + # undef_method :authorize, :capture, :void, :credit + + undef_method :authorize + undef_method :capture + undef_method :void + undef_method :credit + + private + + def split(response) + response.split(',') + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_au.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_au.rb new file mode 100644 index 000000000..dd552114e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_au.rb @@ -0,0 +1,279 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SecurePayAuGateway < Gateway + API_VERSION = 'xml-4.2' + PERIODIC_API_VERSION = 'spxml-3.0' + + class_attribute :test_periodic_url, :live_periodic_url + + self.test_url = 'https://www.securepay.com.au/test/payment' + self.live_url = 'https://www.securepay.com.au/xmlapi/payment' + + self.test_periodic_url = 'https://test.securepay.com.au/xmlapi/periodic' + self.live_periodic_url = 'https://api.securepay.com.au/xmlapi/periodic' + + self.supported_countries = ['AU'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] + + # The homepage URL of the gateway + self.homepage_url = 'http://securepay.com.au' + + # The name of the gateway + self.display_name = 'SecurePay' + + class_attribute :request_timeout + self.request_timeout = 60 + + self.money_format = :cents + self.default_currency = 'AUD' + + # 0 Standard Payment + # 4 Refund + # 6 Client Reversal (Void) + # 10 Preauthorise + # 11 Preauth Complete (Advice) + TRANSACTIONS = { + :purchase => 0, + :authorization => 10, + :capture => 11, + :void => 6, + :refund => 4 + } + + PERIODIC_ACTIONS = { + :add_triggered => "add", + :remove_triggered => "delete", + :trigger => "trigger" + } + + PERIODIC_TYPES = { + :add_triggered => 4, + :remove_triggered => nil, + :trigger => nil + } + + SUCCESS_CODES = [ '00', '08', '11', '16', '77' ] + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card_or_stored_id, options = {}) + if credit_card_or_stored_id.respond_to?(:number) + requires!(options, :order_id) + commit :purchase, build_purchase_request(money, credit_card_or_stored_id, options) + else + options[:billing_id] = credit_card_or_stored_id.to_s + commit_periodic(build_periodic_item(:trigger, money, nil, options)) + end + end + + def authorize(money, credit_card, options = {}) + requires!(options, :order_id) + commit :authorization, build_purchase_request(money, credit_card, options) + end + + def capture(money, reference, options = {}) + commit :capture, build_reference_request(money, reference) + end + + def refund(money, reference, options = {}) + commit :refund, build_reference_request(money, reference) + end + + def credit(money, reference, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, reference) + end + + def void(reference, options = {}) + commit :void, build_reference_request(nil, reference) + end + + def store(creditcard, options = {}) + requires!(options, :billing_id, :amount) + commit_periodic(build_periodic_item(:add_triggered, options[:amount], creditcard, options)) + end + + def unstore(identification, options = {}) + options[:billing_id] = identification + commit_periodic(build_periodic_item(:remove_triggered, options[:amount], nil, options)) + end + + private + + def build_purchase_request(money, credit_card, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'amount', amount(money) + xml.tag! 'currency', options[:currency] || currency(money) + xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '') + + xml.tag! 'CreditCardInfo' do + xml.tag! 'cardNumber', credit_card.number + xml.tag! 'expiryDate', expdate(credit_card) + xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? + end + + xml.target! + end + + def build_reference_request(money, reference) + xml = Builder::XmlMarkup.new + + transaction_id, order_id, preauth_id, original_amount = reference.split('*') + + xml.tag! 'amount', (money ? amount(money) : original_amount) + xml.tag! 'currency', options[:currency] || currency(money) + xml.tag! 'txnID', transaction_id + xml.tag! 'purchaseOrderNo', order_id + xml.tag! 'preauthID', preauth_id + + xml.target! + end + + def build_request(action, body) + xml = Builder::XmlMarkup.new + xml.instruct! + xml.tag! 'SecurePayMessage' do + xml.tag! 'MessageInfo' do + xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageTimestamp', generate_timestamp + xml.tag! 'timeoutValue', request_timeout + xml.tag! 'apiVersion', API_VERSION + end + + xml.tag! 'MerchantInfo' do + xml.tag! 'merchantID', @options[:login] + xml.tag! 'password', @options[:password] + end + + xml.tag! 'RequestType', 'Payment' + xml.tag! 'Payment' do + xml.tag! 'TxnList', "count" => 1 do + xml.tag! 'Txn', "ID" => 1 do + xml.tag! 'txnType', TRANSACTIONS[action] + xml.tag! 'txnSource', 23 + xml << body + end + end + end + end + + xml.target! + end + + def commit(action, request) + response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(action, request))) + + Response.new(success?(response), message_from(response), response, + :test => test?, + :authorization => authorization_from(response) + ) + end + + def build_periodic_item(action, money, credit_card, options) + xml = Builder::XmlMarkup.new + + xml.tag! 'actionType', PERIODIC_ACTIONS[action] + xml.tag! 'clientID', options[:billing_id].to_s + + if credit_card + xml.tag! 'CreditCardInfo' do + xml.tag! 'cardNumber', credit_card.number + xml.tag! 'expiryDate', expdate(credit_card) + xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value? + end + end + xml.tag! 'amount', amount(money) + xml.tag! 'periodicType', PERIODIC_TYPES[action] if PERIODIC_TYPES[action] + + xml.target! + end + + def build_periodic_request(body) + xml = Builder::XmlMarkup.new + xml.instruct! + xml.tag! 'SecurePayMessage' do + xml.tag! 'MessageInfo' do + xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30) + xml.tag! 'messageTimestamp', generate_timestamp + xml.tag! 'timeoutValue', request_timeout + xml.tag! 'apiVersion', PERIODIC_API_VERSION + end + + xml.tag! 'MerchantInfo' do + xml.tag! 'merchantID', @options[:login] + xml.tag! 'password', @options[:password] + end + + xml.tag! 'RequestType', 'Periodic' + xml.tag! 'Periodic' do + xml.tag! 'PeriodicList', "count" => 1 do + xml.tag! 'PeriodicItem', "ID" => 1 do + xml << body + end + end + end + end + xml.target! + end + + def commit_periodic(request) + my_request = build_periodic_request(request) + #puts my_request + response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, my_request)) + + Response.new(success?(response), message_from(response), response, + :test => test?, + :authorization => authorization_from(response) + ) + end + + def success?(response) + SUCCESS_CODES.include?(response[:response_code]) + end + + def authorization_from(response) + [response[:txn_id], response[:purchase_order_no], response[:preauth_id], response[:amount]].join('*') + end + + def message_from(response) + response[:response_text] || response[:status_description] + end + + def expdate(credit_card) + "#{format(credit_card.month, :two_digits)}/#{format(credit_card.year, :two_digits)}" + end + + def parse(body) + xml = REXML::Document.new(body) + + response = {} + + xml.root.elements.to_a.each do |node| + parse_element(response, node) + end + + response + end + + def parse_element(response, node) + if node.has_elements? + node.elements.each{|element| parse_element(response, element) } + else + response[node.name.underscore.to_sym] = node.text + end + end + + # YYYYDDMMHHNNSSKKK000sOOO + def generate_timestamp + time = Time.now.utc + time.strftime("%Y%d%m%H%M%S#{time.usec}+000") + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_tech.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_tech.rb new file mode 100644 index 000000000..7ab6f62e1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/secure_pay_tech.rb @@ -0,0 +1,112 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SecurePayTechGateway < Gateway + class SecurePayTechPostData < PostData + self.required_fields = [ :OrderReference, :CardNumber, :CardExpiry, :CardHolderName, :CardType, :MerchantID, :MerchantKey, :Amount, :Currency ] + end + + self.live_url = self.test_url = 'https://tx.securepaytech.com/web/HttpPostPurchase' + + PAYMENT_GATEWAY_RESPONSES = { + 1 => "Transaction OK", + 2 => "Insufficient funds", + 3 => "Card expired", + 4 => "Card declined", + 5 => "Server error", + 6 => "Communications error", + 7 => "Unsupported transaction type", + 8 => "Bad or malformed request", + 9 => "Invalid card number" + } + + self.default_currency = 'NZD' + self.supported_countries = ['NZ'] + self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] + self.homepage_url = 'http://www.securepaytech.com/' + self.display_name = 'SecurePayTech' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, creditcard, options = {}) + post = SecurePayTechPostData.new + + add_invoice(post, money, options) + add_creditcard(post, creditcard) + + commit(:purchase, post) + end + + private + + def add_invoice(post, money, options) + post[:Amount] = amount(money) + post[:Currency] = options[:currency] || currency(money) + + post[:OrderReference] = options[:order_id] + end + + def add_creditcard(post, creditcard) + post[:CardNumber] = creditcard.number + post[:CardExpiry] = expdate(creditcard) + post[:CardHolderName] = creditcard.name + + if creditcard.verification_value? + post[:EnableCSC] = true + post[:CSC] = creditcard.verification_value + end + + # SPT will autodetect this + post[:CardType] = 0 + end + + def parse(body) + response = CGI.unescape(body).split(',') + + result = {} + result[:result_code] = response[0].to_i + + if response.length == 2 + result[:fail_reason] = response[1] + else + result[:merchant_transaction_reference] = response[1] + result[:receipt_number] = response[2] + result[:transaction_number] = response[3] + result[:authorisation_id] = response[4] + result[:batch_number] = response[5] + end + + result + end + + def commit(action, post) + response = parse( ssl_post(self.live_url, post_data(action, post) ) ) + + Response.new(response[:result_code] == 1, message_from(response), response, + :test => test?, + :authorization => response[:merchant_transaction_reference] + ) + end + + def message_from(result) + PAYMENT_GATEWAY_RESPONSES[result[:result_code]] + end + + def post_data(action, post) + post[:MerchantID] = @options[:login] + post[:MerchantKey] = @options[:password] + post.to_s + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}#{year[-2..-1]}" + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/skip_jack.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/skip_jack.rb new file mode 100644 index 000000000..f694bc81c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/skip_jack.rb @@ -0,0 +1,453 @@ +#!ruby19 +# encoding: utf-8 + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SkipJackGateway < Gateway + API_VERSION = '?.?' + + self.live_url = "https://www.skipjackic.com" + self.test_url = "https://developer.skipjackic.com" + + BASIC_PATH = "/scripts/evolvcc.dll" + ADVANCED_PATH = "/evolvcc/evolvcc.aspx" + + ACTIONS = { + :authorization => 'AuthorizeAPI', + :change_status => 'SJAPI_TransactionChangeStatusRequest', + :get_status => 'SJAPI_TransactionStatusRequest' + } + + SUCCESS_MESSAGE = 'The transaction was successful.' + + MONETARY_CHANGE_STATUSES = ['SETTLE', 'AUTHORIZE', 'AUTHORIZE ADDITIONAL', 'CREDIT', 'SPLITSETTLE'] + + CARD_CODE_ERRORS = %w( N S "" ) + + CARD_CODE_MESSAGES = { + "M" => "Card verification number matched", + "N" => "Card verification number didn't match", + "P" => "Card verification number was not processed", + "S" => "Card verification number should be on card but was not indicated", + "U" => "Issuer was not certified for card verification", + "" => "Transaction failed because incorrect card verification number was entered or no number was entered" + } + + AVS_ERRORS = %w( A B C E I N O P R W Z ) + + AVS_MESSAGES = { + "A" => "Street address matches billing information, zip/postal code does not", + "B" => "Street address match for international transaction. Postal code not verified due to incompatible formats", + "C" => "Street address and postal code not verified for internation transaction due to incompatible formats", + "D" => "Street address and postal code match for international transaction", + "E" => "Address verification service error", + "I" => "Address information not verified by international issuer", + "M" => "Street address and postal code match for international transaction", + "N" => "Neither street address nor zip/postal match billing information", + "O" => "Non-US issuer does not participate", + "P" => "Postal codes match for international transaction but street address not verified due to incompatible formats", + "P" => "Address verification not applicable for this transaction", + "R" => "Payment gateway was unavailable or timed out", + "S" => "Address verification service not supported by issuer", + "U" => "Address information is unavailable", + "W" => "9-digit zip/postal code matches billing information, street address does not", + "X" => "Street address and 9-digit zip/postal code matches billing information", + "Y" => "Street address and 5-digit zip/postal code matches billing information", + "Z" => "5-digit zip/postal code matches billing information, street address does not", + } + + CHANGE_STATUS_ERROR_MESSAGES = { + '0' => 'Success', + '-1' => 'Invalid Command', + '-2' => 'Parameter Missing', + '-3' => 'Failed retrieving response', + '-4' => 'Invalid Status', + '-5' => 'Failed reading security flags', + '-6' => 'Developer serial number not found', + '-7' => 'Invalid Serial Number' + } + + TRANSACTION_CURRENT_STATUS = { + '0' => 'Idle', + '1' => 'Authorized', + '2' => 'Denied', + '3' => 'Settled', + '4' => 'Credited', + '5' => 'Deleted', + '6' => 'Archived', + '7' => 'Pre-Authorized', + '8' => 'Split Settled' + } + + TRANSACTION_PENDING_STATUS = { + '0' => 'Idle', + '1' => 'Pending Credit', + '2' => 'Pending Settlement', + '3' => 'Pending Authorization', + '4' => 'Pending Manual Settlement', + '5' => 'Pending Recurring' + } + + RETURN_CODE_MESSAGES = { + '-1' => 'Data was not by received intact by Skipjack Transaction Network.', + '0' => 'Communication Failure. Error in Request and Response at IP level.', + '1' => 'Valid Data. Authorization request was valid.', + '-35' => 'Invalid credit card number. Retry with correct credit card number.', + '-37' => 'Merchant Processor Unavailable. Skipjack is unable to communicate with payment Processor. Retry', + '-39' => 'Length or value of HTML Serial. Number Invalid serial number. Check HTML Serial Number length and that it is a correct/valid number. Confirm you are sending to the correct environment (Development or Production)', + '-51' => 'The value or length for billing zip code is incorrect.', + '-52' => 'The value or length for shipping zip code is incorrect.', + '-53' => 'The value or length for credit card expiration month is incorrect.', + '-54' => 'The value or length of the month or year of the credit card account number was incorrect.', + '-55' => 'The value or length or billing street address is incorrect.', + '-56' => 'The value or length of the shipping address is incorrect.', + '-57' => 'The length of the transaction amount must be at least 3 digits long (excluding the decimal place).', + '-58' => 'Length or value in Merchant Name Merchant Name associated with Skipjack account is misconfigured or invalid', + '-59' => 'Length or value in Merchant Address Merchant Address associated with Skipjack account is misconfigured or invalid', + '-60' => 'Length or value in Merchant State Merchant State associated with Skipjack account is misconfigured or invalid', + '-61' => 'The value or length for shipping state/province is empty.', + '-62' => 'The value for length orderstring is empty.', + '-64' => 'The value for the phone number is incorrect.', + '-65' => 'The value or length for billing name is empty.', + '-66' => 'The value or length for billing e-mail is empty.', + '-67' => 'The value or length for billing street address is empty.', + '-68' => 'The value or length for billing city is empty.', + '-69' => 'The value or length for billing state is empty.', + '-70' => 'Empty zipcode Zip Code field is empty.', + '-71' => 'Empty ordernumber Ordernumber field is empty.', + '-72' => 'Empty accountnumber Accountnumber field is empty', + '-73' => 'Empty month Month field is empty.', + '-74' => 'Empty year Year field is empty.', + '-75' => 'Empty serialnumber Serialnumber field is empty.', + '-76' => 'Empty transactionamount Transaction amount field is empty.', + '-77' => 'Empty orderstring Orderstring field is empty.', + '-78' => 'Empty shiptophone Shiptophone field is empty.', + '-79' => 'The value or length for billing name is empty.', + '-80' => 'Length shipto name Error in the length or value of shiptophone.', + '-81' => 'Length or value of Customer location', + '-82' => 'The value or length for billing state is empty.', + '-83' => 'The value or length for shipping phone is empty.', + '-84' => 'There is already an existing pending transaction in the register sharing the posted Order Number.', + '-85' => 'Airline leg info invalid Airline leg field value is invalid or empty.', + '-86' => 'Airline ticket info invalid Airline ticket info field is invalid or empty', + '-87' => 'Point of Sale check routing number must be 9 numeric digits Point of Sale check routing number is invalid or empty.', + '-88' => 'Point of Sale check account number missing or invalid Point of Sale check account number is invalid or empty.', + '-89' => 'Point of Sale check MICR missing or invalid Point of Sale check MICR invalid or empty.', + '-90' => 'Point of Sale check number missing or invalid Point of Sale check number invalid or empty.', + '-91' => 'CVV2 Invalid or empty "Make CVV a required field feature" enabled (New feature 01 April 2006) in the Merchant Account Setup interface but no CVV code was sent in the transaction data.', + '-92' => 'Approval Code Invalid Approval Code Invalid. Approval Code is a 6 digit code.', + '-93' => 'Blind Credits Request Refused "Allow Blind Credits" option must be enabled on the Skipjack Merchant Account.', + '-94' => 'Blind Credits Failed', + '-95' => 'Voice Authorization Request Refused Voice Authorization option must be enabled on the Skipjack Merchant Account.', + '-96' => 'Voice Authorizations Failed', + '-97' => 'Fraud Rejection Violates Velocity Settling.', + '-98' => 'Invalid Discount Amount', + '-99' => 'POS PIN Debit Pin Block Debit-specific', + '-100' => 'POS PIN Debit Invalid Key Serial Number Debit-specific', + '-101' => 'Invalid Authentication Data Data for Verified by Visa/MC Secure Code is invalid.', + '-102' => 'Authentication Data Not Allowed', + '-103' => 'POS Check Invalid Birth Date POS check dateofbirth variable contains a birth date in an incorrect format. Use MM/DD/YYYY format for this variable.', + '-104' => 'POS Check Invalid Identification Type POS check identificationtype variable contains a identification type value which is invalid. Use the single digit value where Social Security Number=1, Drivers License=2 for this variable.', + '-105' => 'Invalid trackdata Track Data is in invalid format.', + '-106' => 'POS Check Invalid Account Type', + '-107' => 'POS PIN Debit Invalid Sequence Number', + '-108' => 'Invalid Transaction ID', + '-109' => 'Invalid From Account Type', + '-110' => 'Pos Error Invalid To Account Type', + '-112' => 'Pos Error Invalid Auth Option', + '-113' => 'Pos Error Transaction Failed', + '-114' => 'Pos Error Invalid Incoming Eci', + '-115' => 'POS Check Invalid Check Type', + '-116' => 'POS Check Invalid Lane Number POS Check lane or cash register number is invalid. Use a valid lane or cash register number that has been configured in the Skipjack Merchant Account.', + '-117' => 'POS Check Invalid Cashier Number' + } + + self.supported_countries = ['US', 'CA'] + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover, :diners_club] + self.homepage_url = 'http://www.skipjack.com/' + self.display_name = 'SkipJack' + + # Creates a new SkipJackGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * <tt>:login</tt> -- The SkipJack Merchant Serial Number. + # * <tt>:password</tt> -- The SkipJack Developer Serial Number. + # * <tt>:test => +true+ or +false+</tt> -- Use the test or live SkipJack url. + # * <tt>:advanced => +true+ or +false+</tt> -- Set to true if you're using an advanced processor + # See the SkipJack Integration Guide for details. (default: +false+) + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard, options = {}) + requires!(options, :order_id, :email) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_address(post, options) + add_customer_data(post, options) + commit(:authorization, money, post) + end + + def purchase(money, creditcard, options = {}) + post = {} + authorization = authorize(money, creditcard, options) + if authorization.success? + capture(money, authorization.authorization) + else + authorization + end + end + + # Captures the funds from an authorized transaction. + # + # ==== Parameters + # + # * <tt>money</tt> -- The amount to be capture as an Integer in cents. + # * <tt>authorization</tt> -- The authorization returned from the previous authorize request. + # * <tt>options</tt> -- A hash of optional parameters. + # + # ==== Options + # + # * <tt>:force_settlement</tt> -- Force the settlement to occur as soon as possible. This option is not supported by other gateways. See the SkipJack API reference for more details + def capture(money, authorization, options = {}) + post = { } + add_status_action(post, 'SETTLE') + add_forced_settlement(post, options) + add_transaction_id(post, authorization) + commit(:change_status, money, post) + end + + def void(authorization, options = {}) + post = {} + add_status_action(post, 'DELETE') + add_forced_settlement(post, options) + add_transaction_id(post, authorization) + commit(:change_status, nil, post) + end + + def refund(money, identification, options = {}) + post = {} + add_status_action(post, 'CREDIT') + add_forced_settlement(post, options) + add_transaction_id(post, identification) + commit(:change_status, money, post) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + def status(order_id) + commit(:get_status, nil, :szOrderNumber => order_id) + end + + private + + def advanced? + @options[:advanced] + end + + def add_forced_settlement(post, options) + post[:szForceSettlement] = options[:force_settlment] ? 1 : 0 + end + + def add_status_action(post, action) + post[:szDesiredStatus] = action + end + + def commit(action, money, parameters) + response = parse( ssl_post( url_for(action), post_data(action, money, parameters) ), action ) + + # Pass along the original transaction id in the case an update transaction + Response.new(response[:success], message_from(response, action), response, + :test => test?, + :authorization => response[:szTransactionFileName] || parameters[:szTransactionId], + :avs_result => { :code => response[:szAVSResponseCode] }, + :cvv_result => response[:szCVV2ResponseCode] + ) + end + + def url_for(action) + result = test? ? self.test_url : self.live_url + result += advanced? && action == :authorization ? ADVANCED_PATH : BASIC_PATH + result += "?#{ACTIONS[action]}" + end + + def add_credentials(params, action) + if action == :authorization + params[:SerialNumber] = @options[:login] + params[:DeveloperSerialNumber] = @options[:password] + else + params[:szSerialNumber] = @options[:login] + params[:szDeveloperSerialNumber] = @options[:password] + end + end + + def add_amount(params, action, money) + if action == :authorization + params[:TransactionAmount] = amount(money) + else + params[:szAmount] = amount(money) if MONETARY_CHANGE_STATUSES.include?(params[:szDesiredStatus]) + end + end + + def parse(body, action) + case action + when :authorization + parse_authorization_response(body) + when :get_status + parse_status_response(body, [ :SerialNumber, :TransactionAmount, :TransactionStatusCode, :TransactionStatusMessage, :OrderNumber, :TransactionDateTime, :TransactionID, :ApprovalCode, :BatchNumber ]) + else + parse_status_response(body, [ :SerialNumber, :TransactionAmount, :DesiredStatus, :StatusResponse, :StatusResponseMessage, :OrderNumber, :AuditID ]) + end + end + + def split_lines(body) + body.split(/[\r\n]+/) + end + + def split_line(line) + line.split(/","/).collect { |key| key.sub(/"*([^"]*)"*/, '\1').strip; } + end + + def authorize_response_map(body) + lines = split_lines(body) + keys, values = split_line(lines[0]), split_line(lines[1]) + Hash[*(keys.zip(values).flatten)].symbolize_keys + end + + def parse_authorization_response(body) + result = authorize_response_map(body) + result[:success] = (result[:szIsApproved] == '1') + result + end + + def parse_status_response(body, response_keys) + lines = split_lines(body) + + keys = [ :szSerialNumber, :szErrorCode, :szNumberRecords] + values = split_line(lines[0])[0..2] + + result = Hash[*(keys.zip(values).flatten)] + + result[:szErrorMessage] = '' + result[:success] = (result[:szErrorCode] == '0') + + if result[:success] + lines[1..-1].each do |line| + values = split_line(line) + response_keys.each_with_index do |key, index| + result[key] = values[index] + end + end + else + result[:szErrorMessage] = lines[1] + end + result + end + + def post_data(action, money, params = {}) + add_credentials(params, action) + add_amount(params, action, money) + sorted_params = params.to_a.sort{|a,b| a.to_s <=> b.to_s}.reverse + sorted_params.collect { |key, value| "#{key.to_s}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def add_transaction_id(post, transaction_id) + post[:szTransactionId] = transaction_id + end + + def add_invoice(post, options) + post[:OrderNumber] = sanitize_order_id(options[:order_id]) + post[:CustomerCode] = options[:customer].to_s.slice(0, 17) + post[:InvoiceNumber] = options[:invoice] + post[:OrderDescription] = options[:description] + + if order_items = options[:items] + post[:OrderString] = order_items.collect { |item| "#{item[:sku]}~#{item[:description].tr('~','-')}~#{item[:declared_value]}~#{item[:quantity]}~#{item[:taxable]}~~~~~~~~#{item[:tax_rate]}~||"}.join + else + post[:OrderString] = '1~None~0.00~0~N~||' + end + end + + def add_creditcard(post, creditcard) + post[:AccountNumber] = creditcard.number + post[:Month] = creditcard.month + post[:Year] = creditcard.year + post[:CVV2] = creditcard.verification_value if creditcard.verification_value? + post[:SJName] = creditcard.name + end + + def add_customer_data(post, options) + post[:Email] = options[:email] + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:StreetAddress] = address[:address1] + post[:StreetAddress2] = address[:address2] + post[:City] = address[:city] + post[:State] = address[:state] + post[:ZipCode] = address[:zip] + post[:Country] = address[:country] + post[:Phone] = address[:phone] + post[:Fax] = address[:fax] + end + + if address = options[:shipping_address] + post[:ShipToName] = address[:name] + post[:ShipToStreetAddress] = address[:address1] + post[:ShipToStreetAddress2] = address[:address2] + post[:ShipToCity] = address[:city] + post[:ShipToState] = address[:state] + post[:ShipToZipCode] = address[:zip] + post[:ShipToCountry] = address[:country] + post[:ShipToPhone] = address[:phone] + post[:ShipToFax] = address[:fax] + end + + # The phone number for the shipping address is required + # Use the billing address phone number if a shipping address + # phone number wasn't provided + post[:ShipToPhone] = post[:Phone] if post[:ShipToPhone].blank? + end + + def message_from(response, action) + case action + when :authorization + message_from_authorization(response) + when :get_status + message_from_status(response) + else + message_from_status(response) + end + end + + def message_from_authorization(response) + if response[:success] + return SUCCESS_MESSAGE + else + return CARD_CODE_MESSAGES[response[:szCVV2ResponseCode]] if CARD_CODE_ERRORS.include?(response[:szCVV2ResponseCode]) + return AVS_MESSAGES[response[:szAVSResponseMessage]] if AVS_ERRORS.include?(response[:szAVSResponseCode]) + return RETURN_CODE_MESSAGES[response[:szReturnCode]] if response[:szReturnCode] != '1' + return response[:szAuthorizationDeclinedMessage] + end + end + + def message_from_status(response) + response[:success] ? SUCCESS_MESSAGE : response[:szErrorMessage] + end + + def sanitize_order_id(value) + value.to_s.gsub(/[^\w.]/, '') + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/smart_ps.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/smart_ps.rb new file mode 100644 index 000000000..bc1690e9e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/smart_ps.rb @@ -0,0 +1,272 @@ +require File.join(File.dirname(__FILE__), '..', 'check.rb') + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SmartPs < Gateway #:nodoc: + + ## + # This is the base gateway for processors who use the smartPS processing system + + self.abstract_class = true + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Pass :store => true in the options to store the + # payment info at the gateway and get a generated + # customer_vault_id in the response. + # Pass :store => some_number_or_string to specify the + # customer_vault_id the gateway should use (make sure it's + # unique). + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_payment_source(post, creditcard,options) + add_address(post, options[:billing_address] || options[:address]) + add_address(post, options[:shipping_address], "shipping") + add_customer_data(post, options) + add_currency(post, money, options) + add_taxes(post, options) + add_processor(post, options) + commit('auth', money, post) + end + + def purchase(money, payment_source, options = {}) + post = {} + add_invoice(post, options) + add_payment_source(post, payment_source, options) + add_address(post, options[:billing_address] || options[:address]) + add_address(post, options[:shipping_address], "shipping") + add_customer_data(post, options) + add_currency(post, money, options) + add_taxes(post, options) + add_processor(post, options) + commit('sale', money, post) + end + + def capture(money, authorization, options = {}) + post ={} + post[:transactionid] = authorization + commit('capture', money, post) + end + + def void(authorization, options = {}) + post ={} + post[:transactionid] = authorization + commit('void', nil, post) + end + + def credit(money, payment_source, options = {}) + post = {} + add_invoice(post, options) + add_payment_source(post, payment_source, options) + add_address(post, options[:billing_address] || options[:address]) + add_customer_data(post, options) + add_sku(post,options) + add_currency(post, money, options) + add_processor(post, options) + commit('credit', money, post) + end + + def refund(money, auth, options = {}) + post = {} + add_transaction(post, auth) + commit('refund', money, post) + end + + + # Update the values (such as CC expiration) stored at + # the gateway. The CC number must be supplied in the + # CreditCard object. + def update(vault_id, creditcard, options = {}) + post = {} + post[:customer_vault] = "update_customer" + add_customer_vault_id(post, vault_id) + add_creditcard(post, creditcard, options) + add_address(post, options[:billing_address] || options[:address]) + add_customer_data(post, options) + + commit(nil, nil, post) + end + + # Amend an existing transaction + def amend(auth, options = {}) + post = {} + add_invoice(post, options) + add_transaction(post, auth) + commit('update', nil, post) + end + + + def delete(vault_id) + post = {} + post[:customer_vault] = "delete_customer" + add_customer_vault_id(post, vault_id) + commit(nil, nil, post) + end + + # To match the other stored-value gateways, like TrustCommerce, + # store and unstore need to be defined + def store(payment_source, options = {}) + post = {} + billing_id = options.delete(:billing_id).to_s || true + add_payment_source(post, payment_source, :store => billing_id) + add_address(post, options[:billing_address] || options[:address]) + add_customer_data(post, options) + commit(nil, nil, post) + end + + alias_method :unstore, :delete + + private + def add_customer_data(post, options) + if options.has_key? :email + post[:email] = options[:email] + end + + if options.has_key? :ip + post[:ipaddress] = options[:ip] + end + end + + def add_address(post, address,prefix="") + prefix +="_" unless prefix.blank? + unless address.blank? or address.values.blank? + post[prefix+"address1"] = address[:address1].to_s + post[prefix+"address2"] = address[:address2].to_s unless address[:address2].blank? + post[prefix+"company"] = address[:company].to_s + post[prefix+"phone"] = address[:phone].to_s + post[prefix+"zip"] = address[:zip].to_s + post[prefix+"city"] = address[:city].to_s + post[prefix+"country"] = address[:country].to_s + post[prefix+"state"] = address[:state].blank? ? 'n/a' : address[:state] + end + end + + def add_currency(post, money, options) + post[:currency] = options[:currency] || currency(money) + end + + def add_taxes(post, options) + post[:tax] = amount(options[:tax]) + end + + def add_processor(post, options) + post[:processor] = options[:processor] unless options[:processor].nil? + end + + def add_invoice(post, options) + post[:orderid] = options[:order_id].to_s.gsub(/[^\w.]/, '') + end + + def add_payment_source(params, source, options={}) + case determine_funding_source(source) + when :vault then add_customer_vault_id(params, source) + when :credit_card then add_creditcard(params, source, options) + when :check then add_check(params, source, options) + end + end + + def add_customer_vault_id(params, vault_id) + params[:customer_vault_id] = vault_id + end + + def add_creditcard(post, creditcard, options) + if options[:store] + post[:customer_vault] = "add_customer" + post[:customer_vault_id] = options[:store] unless options[:store] == true + end + post[:ccnumber] = creditcard.number + post[:cvv] = creditcard.verification_value if creditcard.verification_value? + post[:ccexp] = expdate(creditcard) + post[:firstname] = creditcard.first_name + post[:lastname] = creditcard.last_name + end + + def add_check(post, check, options) + if options[:store] + post[:customer_vault] = "add_customer" + post[:customer_vault_id] = options[:store] unless options[:store] == true + end + + post[:payment] = 'check' # Set transaction to ACH + post[:checkname] = check.name # The name on the customer's Checking Account + post[:checkaba] = check.routing_number # The customer's bank routing number + post[:checkaccount] = check.account_number # The customer's account number + post[:account_holder_type] = check.account_holder_type # The customer's type of ACH account + post[:account_type] = check.account_type # The customer's type of ACH account + end + + def add_sku(post,options) + post["product_sku_#"] = options[:sku] || options["product_sku_#"] + end + + def add_transaction(post, auth) + post[:transactionid] = auth + end + + def parse(body) + results = {} + body.split(/&/).each do |pair| + key,val = pair.split(/=/) + results[key] = val + end + + results + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) if money + response = parse( ssl_post(self.live_url, post_data(action,parameters)) ) + Response.new(response["response"] == "1", message_from(response), response, + :authorization => (response["transactionid"] || response["customer_vault_id"]), + :test => test?, + :cvv_result => response["cvvresponse"], + :avs_result => { :code => response["avsresponse"] } + ) + + end + + def expdate(creditcard) + year = sprintf("%.04i", creditcard.year.to_i) + month = sprintf("%.02i", creditcard.month.to_i) + + "#{month}#{year[-2..-1]}" + end + + + def message_from(response) + case response["responsetext"] + when "SUCCESS", "Approved", nil # This is dubious, but responses from UPDATE are nil. + "This transaction has been approved" + when "DECLINE" + "This transaction has been declined" + else + response["responsetext"] + end + end + + def post_data(action, parameters = {}) + post = {} + post[:username] = @options[:login] + post[:password] = @options[:password] + post[:type] = action if action + + request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&") + request + end + + def determine_funding_source(source) + case + when source.is_a?(String) then :vault + when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card + when card_brand(source) == 'check' then :check + else raise ArgumentError, "Unsupported funding source provided" + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/spreedly_core.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/spreedly_core.rb new file mode 100644 index 000000000..1a07ab062 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/spreedly_core.rb @@ -0,0 +1,233 @@ +require 'nokogiri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # Public: This gateway allows you to interact with any gateway you've + # created in Spreedly (https://spreedly.com). It's an adapter which can be + # particularly useful if you already have code interacting with + # ActiveMerchant and want to easily take advantage of Spreedly's vault. + class SpreedlyCoreGateway < Gateway + self.live_url = 'https://core.spreedly.com/v1' + + self.supported_countries = %w(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) + + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'https://spreedly.com' + self.display_name = 'Spreedly' + self.money_format = :cents + self.default_currency = 'USD' + + # Public: Create a new Spreedly gateway. + # + # options - A hash of options: + # :login - The environment key. + # :password - The access secret. + # :gateway_token - The token of the gateway you've created in + # Spreedly. + def initialize(options = {}) + requires!(options, :login, :password, :gateway_token) + super + end + + # Public: Run a purchase transaction. + # + # money - The monetary amount of the transaction in cents. + # payment_method - The CreditCard or the Spreedly payment method token. + # options - A standard ActiveMerchant options hash + def purchase(money, payment_method, options = {}) + if payment_method.is_a?(String) + purchase_with_token(money, payment_method, options) + else + MultiResponse.run do |r| + r.process { save_card(false, payment_method, options) } + r.process { purchase_with_token(money, r.authorization, options) } + end + end + end + + # Public: Run an authorize transaction. + # + # money - The monetary amount of the transaction in cents. + # payment_method - The CreditCard or the Spreedly payment method token. + # options - A standard ActiveMerchant options hash + def authorize(money, payment_method, options = {}) + if payment_method.is_a?(String) + authorize_with_token(money, payment_method, options) + else + MultiResponse.run do |r| + r.process { save_card(false, payment_method, options) } + r.process { authorize_with_token(money, r.authorization, options) } + end + end + end + + def capture(money, authorization, options={}) + request = build_xml_request('transaction') do |doc| + add_invoice(doc, money, options) + end + + commit("transactions/#{authorization}/capture.xml", request) + end + + def refund(money, authorization, options={}) + request = build_xml_request('transaction') do |doc| + add_invoice(doc, money, options) + end + + commit("transactions/#{authorization}/credit.xml", request) + end + + def void(authorization, options={}) + commit("transactions/#{authorization}/void.xml", '') + end + + # Public: Store a credit card in the Spreedly vault and retain it. + # + # credit_card - The CreditCard to store + # options - A standard ActiveMerchant options hash + def store(credit_card, options={}) + save_card(true, credit_card, options) + end + + # Public: Redact the CreditCard in Spreedly. This wipes the sensitive + # payment information from the card. + # + # credit_card - The CreditCard to store + # options - A standard ActiveMerchant options hash + def unstore(authorization, options={}) + commit("payment_methods/#{authorization}/redact.xml", '', :put) + end + + private + def save_card(retain, credit_card, options) + request = build_xml_request('payment_method') do |doc| + add_credit_card(doc, credit_card, options) + add_data(doc, options) + doc.retained(true) if retain + end + + commit("payment_methods.xml", request, :post, :payment_method_token) + end + + def purchase_with_token(money, payment_method_token, options) + request = auth_purchase_request(money, payment_method_token, options) + commit("gateways/#{@options[:gateway_token]}/purchase.xml", request) + end + + def authorize_with_token(money, payment_method_token, options) + request = auth_purchase_request(money, payment_method_token, options) + commit("gateways/#{@options[:gateway_token]}/authorize.xml", request) + end + + def auth_purchase_request(money, payment_method_token, options) + build_xml_request('transaction') do |doc| + add_invoice(doc, money, options) + doc.payment_method_token(payment_method_token) + end + end + + def add_invoice(doc, money, options) + doc.amount amount(money) + doc.currency_code(options[:currency] || currency(money) || default_currency) + end + + def add_credit_card(doc, credit_card, options) + doc.credit_card do + doc.number(credit_card.number) + doc.first_name(credit_card.first_name) + doc.last_name(credit_card.last_name) + doc.month(credit_card.month) + doc.year(credit_card.year) + doc.email(options[:email]) + doc.address1(options[:billing_address].try(:[], :address1)) + doc.address2(options[:billing_address].try(:[], :address2)) + doc.city(options[:billing_address].try(:[], :city)) + doc.state(options[:billing_address].try(:[], :state)) + doc.zip(options[:billing_address].try(:[], :zip)) + end + end + + def add_data(doc, options) + doc.data do + data_to_doc(doc, options[:data]) + end + end + + def data_to_doc(doc, value) + return doc.text value unless value.kind_of? Hash + value.each do |k, v| + doc.send(k) do + data_to_doc(doc, v) + end + end + end + + def parse(xml) + response = {} + + doc = Nokogiri::XML(xml) + doc.root.xpath('*').each do |node| + if (node.elements.empty?) + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + childnode_to_response(response, node, childnode) + end + end + end + + response + end + + def childnode_to_response(response, node, childnode) + name = "#{node.name.downcase}_#{childnode.name.downcase}" + if name == 'payment_method_data' && !childnode.elements.empty? + response[name.to_sym] = Hash.from_xml(childnode.to_s).values.first + else + response[name.to_sym] = childnode.text + end + end + + def build_xml_request(root) + builder = Nokogiri::XML::Builder.new + builder.__send__(root) do |doc| + yield(doc) + end + builder.to_xml + end + + def commit(relative_url, request, method = :post, authorization_field = :token) + begin + raw_response = ssl_request(method, "#{live_url}/#{relative_url}", request, headers) + rescue ResponseError => e + raw_response = e.response.body + end + + response_from(raw_response, authorization_field) + end + + def response_from(raw_response, authorization_field) + parsed = parse(raw_response) + options = { + :authorization => parsed[authorization_field], + :test => (parsed[:on_test_gateway] == 'true'), + :avs_result => { :code => parsed[:response_avs_code] }, + :cvv_result => parsed[:response_cvv_code] + } + + Response.new(parsed[:succeeded] == 'true', parsed[:message] || parsed[:error], parsed, options) + end + + def headers + { + 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:login]}:#{@options[:password]}").chomp), + 'Content-Type' => 'text/xml' + } + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/stripe.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/stripe.rb new file mode 100644 index 000000000..4c1f7e42d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/stripe.rb @@ -0,0 +1,255 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class StripeGateway < Gateway + self.live_url = 'https://api.stripe.com/v1/' + + AVS_CODE_TRANSLATOR = { + 'line1: pass, zip: pass' => 'Y', + 'line1: pass, zip: fail' => 'A', + 'line1: pass, zip: unchecked' => 'B', + 'line1: fail, zip: pass' => 'Z', + 'line1: fail, zip: fail' => 'N', + 'line1: unchecked, zip: pass' => 'P', + 'line1: unchecked, zip: unchecked' => 'I' + } + + CVC_CODE_TRANSLATOR = { + 'pass' => 'M', + 'fail' => 'N', + 'unchecked' => 'P' + } + + self.supported_countries = ['US', 'CA'] + self.default_currency = 'USD' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club] + + self.homepage_url = 'https://stripe.com/' + self.display_name = 'Stripe' + + def initialize(options = {}) + requires!(options, :login) + @api_key = options[:login] + super + end + + def authorize(money, creditcard, options = {}) + post = create_post_for_auth_or_purchase(money, creditcard, options) + post[:capture] = "false" + meta = generate_meta(options) + + commit(:post, 'charges', post, meta) + end + + # To create a charge on a card or a token, call + # + # purchase(money, card_hash_or_token, { ... }) + # + # To create a charge on a customer, call + # + # purchase(money, nil, { :customer => id, ... }) + def purchase(money, creditcard, options = {}) + post = create_post_for_auth_or_purchase(money, creditcard, options) + meta = generate_meta(options) + + commit(:post, 'charges', post, meta) + end + + def capture(money, authorization, options = {}) + commit(:post, "charges/#{CGI.escape(authorization)}/capture", {:amount => amount(money)}) + end + + def void(identification, options = {}) + commit(:post, "charges/#{CGI.escape(identification)}/refund", {}) + end + + def refund(money, identification, options = {}) + meta = generate_meta(options) + post = {} + + post[:amount] = amount(money) if money + + commit(:post, "charges/#{CGI.escape(identification)}/refund", post, meta) + end + + def store(creditcard, options = {}) + post = {} + add_creditcard(post, creditcard, options) + post[:description] = options[:description] + post[:email] = options[:email] + + meta = generate_meta(options) + path = if options[:customer] + "customers/#{CGI.escape(options[:customer])}" + else + 'customers' + end + + commit(:post, path, post, meta) + end + + def update(customer_id, creditcard, options = {}) + options = options.merge(:customer => customer_id) + store(creditcard, options) + end + + def unstore(customer_id, options = {}) + meta = generate_meta(options) + commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, meta) + end + + private + + def create_post_for_auth_or_purchase(money, creditcard, options) + post = {} + add_amount(post, money, options) + add_creditcard(post, creditcard, options) + add_customer(post, options) + add_customer_data(post,options) + post[:description] = options[:description] || options[:email] + post[:application_fee] = options[:application_fee] if options[:application_fee] + add_flags(post, options) + post + end + + def add_amount(post, money, options) + post[:amount] = amount(money) + post[:currency] = (options[:currency] || currency(money)).downcase + end + + def add_customer_data(post, options) + metadata_options = [:description,:browser_ip,:user_agent,:referrer] + post.update(options.slice(*metadata_options)) + + post[:external_id] = options[:order_id] + post[:payment_user_agent] = "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}" + end + + def add_address(post, options) + return unless post[:card] && post[:card].kind_of?(Hash) + if address = options[:billing_address] || options[:address] + post[:card][:address_line1] = address[:address1] if address[:address1] + post[:card][:address_line2] = address[:address2] if address[:address2] + post[:card][:address_country] = address[:country] if address[:country] + post[:card][:address_zip] = address[:zip] if address[:zip] + post[:card][:address_state] = address[:state] if address[:state] + post[:card][:address_city] = address[:city] if address[:city] + end + end + + def add_creditcard(post, creditcard, options) + if creditcard.respond_to?(:number) + card = {} + card[:number] = creditcard.number + card[:exp_month] = creditcard.month + card[:exp_year] = creditcard.year + card[:cvc] = creditcard.verification_value if creditcard.verification_value? + card[:name] = creditcard.name if creditcard.name + post[:card] = card + + add_address(post, options) + elsif creditcard.kind_of?(String) + post[:card] = creditcard + end + end + + def add_customer(post, options) + post[:customer] = options[:customer] if options[:customer] && !post[:card] + end + + def add_flags(post, options) + post[:uncaptured] = true if options[:uncaptured] + end + + def parse(body) + JSON.parse(body) + 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 generate_meta(options) + {:ip => options[:ip]} + end + + def headers(meta={}) + @@ua ||= JSON.dump({ + :bindings_version => ActiveMerchant::VERSION, + :lang => 'ruby', + :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", + :platform => RUBY_PLATFORM, + :publisher => 'active_merchant', + :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil) + }) + + { + "Authorization" => "Basic " + Base64.encode64(@api_key.to_s + ":").strip, + "User-Agent" => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + "X-Stripe-Client-User-Agent" => @@ua, + "X-Stripe-Client-User-Metadata" => meta.to_json + } + end + + def commit(method, url, parameters=nil, meta={}) + raw_response = response = nil + success = false + begin + raw_response = ssl_request(method, self.live_url + url, post_data(parameters), headers(meta)) + response = parse(raw_response) + success = !response.key?("error") + rescue ResponseError => e + raw_response = e.response.body + response = response_error(raw_response) + rescue JSON::ParserError + response = json_error(raw_response) + end + + card = response["card"] || response["active_card"] || {} + avs_code = AVS_CODE_TRANSLATOR["line1: #{card["address_line1_check"]}, zip: #{card["address_zip_check"]}"] + cvc_code = CVC_CODE_TRANSLATOR[card["cvc_check"]] + Response.new(success, + success ? "Transaction approved" : response["error"]["message"], + response, + :test => response.has_key?("livemode") ? !response["livemode"] : false, + :authorization => response["id"], + :avs_result => { :code => avs_code }, + :cvv_result => cvc_code + ) + 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 Stripe API. Please contact support@stripe.com if you continue to receive this message.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { + "error" => { + "message" => msg + } + } + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trans_first.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trans_first.rb new file mode 100644 index 000000000..013fdf581 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trans_first.rb @@ -0,0 +1,126 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TransFirstGateway < Gateway + self.live_url = self.test_url = 'https://webservices.primerchants.com/creditcard.asmx/CCSale' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.transfirst.com/' + self.display_name = 'TransFirst' + + UNUSED_FIELDS = %w(ECIValue UserId CAVVData TrackData POSInd EComInd MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSEntryMode POSConditionCode AuthCharInd CardCertData) + + DECLINED = 'The transaction was declined' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card, options = {}) + post = {} + + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, options) + + commit(post) + end + + private + def add_amount(post, money) + add_pair(post, :Amount, amount(money), :required => true) + end + + def add_address(post, options) + address = options[:billing_address] || options[:address] + + if address + add_pair(post, :Address, address[:address1]) + add_pair(post, :ZipCode, address[:zip]) + end + end + + def add_invoice(post, options) + add_pair(post, :RefID, options[:order_id], :required => true) + add_pair(post, :PONumber, options[:invoice], :required => true) + add_pair(post, :SaleTaxAmount, amount(options[:tax] || 0)) + add_pair(post, :PaymentDesc, options[:description], :required => true) + add_pair(post, :TaxIndicator, 0) + end + + def add_credit_card(post, credit_card) + add_pair(post, :CardHolderName, credit_card.name, :required => true) + add_pair(post, :CardNumber, credit_card.number, :required => true) + + add_pair(post, :Expiration, expdate(credit_card), :required => true) + add_pair(post, :CVV2, credit_card.verification_value) + end + + def add_unused_fields(post) + UNUSED_FIELDS.each do |f| + post[f] = "" + end + end + + def expdate(credit_card) + year = format(credit_card.year, :two_digits) + month = format(credit_card.month, :two_digits) + + "#{month}#{year}" + end + + def parse(data) + response = {} + + xml = REXML::Document.new(data) + root = REXML::XPath.first(xml, "//CCSaleDebitResponse") + + if root.nil? + response[:message] = data.to_s.strip + else + root.elements.to_a.each do |node| + response[node.name.underscore.to_sym] = node.text + end + end + + response + end + + def commit(params) + response = parse( ssl_post(self.live_url, post_data(params)) ) + + Response.new(response[:status] == "Authorized", message_from(response), response, + :test => test?, + :authorization => response[:trans_id], + :avs_result => { :code => response[:avs_code] }, + :cvv_result => response[:cvv2_code] + ) + end + + def message_from(response) + case response[:message] + when 'Call Voice Center' + DECLINED + else + response[:message] + end + end + + def post_data(params = {}) + add_unused_fields(params) + params[:MerchantID] = @options[:login] + params[:RegKey] = @options[:password] + + request = params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + request + end + + def add_pair(post, key, value, options = {}) + post[key] = value if !value.blank? || options[:required] + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transax.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transax.rb new file mode 100644 index 000000000..c11c830ba --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transax.rb @@ -0,0 +1,23 @@ +require File.join(File.dirname(__FILE__),'smart_ps.rb') + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TransaxGateway < SmartPs + self.live_url = self.test_url = 'https://secure.nelixtransax.net/api/transact.php' + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['US'] + + # The card types supported by the payment gateway + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + # The homepage URL of the gateway + self.homepage_url = 'https://www.nelixtransax.com/' + + # The name of the gateway + self.display_name = 'NELiX TransaX' + + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transnational.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transnational.rb new file mode 100644 index 000000000..5c49489ba --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/transnational.rb @@ -0,0 +1,239 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class TransnationalGateway < Gateway + self.live_url = self.test_url = 'https://secure.networkmerchants.com/api/transact.php' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + + self.homepage_url = 'http://www.tnbci.com/' + self.display_name = 'Transnational' + + self.money_format = :dollars + self.default_currency = 'USD' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, creditcard_or_vault_id, options = {}) + post = build_auth_post(money, creditcard_or_vault_id, options) + commit('auth', post) + end + + def purchase(money, creditcard_or_vault_id, options = {}) + post = build_purchase_post(money, creditcard_or_vault_id, options) + commit('sale', post) + end + + def capture(money, authorization, options = {}) + post = build_capture_post(money, authorization, options) + commit('capture', post) + end + + def void(authorization, options = {}) + post = build_void_post(authorization, options) + commit('void', post) + end + + def refund(money, authorization, options = {}) + post = build_refund_post(money, authorization, options) + commit('refund', post) + end + + def store(creditcard, options = {}) + post = build_store_post(creditcard, options) + commit_vault('add_customer', post) + end + + def unstore(customer_vault_id, options = {}) + post = build_unstore_post(customer_vault_id, options) + commit_vault('delete_customer', post) + end + + private + + def build_auth_post(money, creditcard_or_vault_id, options) + post = {} + add_order(post, options) + add_address(post, options) + add_shipping_address(post, options) + add_payment_method(post, creditcard_or_vault_id, options) + add_amount(post, money) + post + end + + def build_purchase_post(money, creditcard, options) + build_auth_post(money, creditcard, options) + end + + def build_capture_post(money, authorization, option) + post = {} + post[:transactionid] = authorization + add_amount(post, money) + post + end + + def build_void_post(authorization, options) + post = {} + post[:transactionid] = authorization + post + end + + def build_refund_post(money, authorization, options) + post = {} + post[:transactionid] = authorization + add_amount(post, money) + post + end + + def build_store_post(creditcard_or_check, options) + post = {} + add_address(post, options) + add_shipping_address(post, options) + add_payment_method(post, creditcard_or_check, options) + post + end + + def build_unstore_post(customer_vault_id, options) + post = {} + post['customer_vault_id'] = customer_vault_id + post + end + + def add_order(post, options) + post[:orderid] = options[:order_id] + post[:orderdescription] = options[:description] + end + + def add_address(post, options) + post[:email] = options[:email] + post[:ipaddress] = options[:ip] + + address = options[:billing_address] || options[:address] || {} + post[:address1] = address[:address1] + post[:address2] = address[:address2] + post[:city] = address[:city] + post[:state] = address[:state] + post[:zip] = address[:zip] + post[:country] = address[:country] + post[:phone] = address[:phone] + end + + def add_shipping_address(post, options) + shipping_address = options[:shipping_address] || {} + post[:shipping_address1] = shipping_address[:address1] + post[:shipping_address2] = shipping_address[:address2] + post[:shipping_city] = shipping_address[:city] + post[:shipping_state] = shipping_address[:state] + post[:shipping_zip] = shipping_address[:zip] + post[:shipping_country] = shipping_address[:country] + end + + def add_swipe_data(post, options) + # unencrypted tracks + post[:track_1] = options[:track_1] + post[:track_2] = options[:track_2] + post[:track_3] = options[:track_3] + + # encrypted tracks + post[:magnesafe_track_1] = options[:magnesafe_track_1] + post[:magnesafe_track_2] = options[:magnesafe_track_2] + post[:magnesafe_track_3] = options[:magnesafe_track_3] + post[:magnesafe_magneprint] = options[:magnesafe_magneprint] + post[:magnesafe_ksn] = options[:magnesafe_ksn] + post[:magnesafe_magneprint_status] = options[:magnesafe_magneprint_status] + end + + def add_payment_method(post, creditcard_or_check_or_vault_id, options) + post[:processor_id] = options[:processor_id] + post[:customer_vault] = 'add_customer' if options[:store] + + add_swipe_data(post, options) + + # creditcard_or_check can be blank if using swipe data + if creditcard_or_check_or_vault_id.is_a?(CreditCard) # creditcard or check + creditcard = creditcard_or_check_or_vault_id + post[:firstname] = creditcard.first_name + post[:lastname] = creditcard.last_name + post[:ccnumber] = creditcard.number + post[:ccexp] = format(creditcard.month, :two_digits) + format(creditcard.year, :two_digits) + post[:cvv] = creditcard.verification_value + post[:payment] = 'creditcard' + elsif creditcard_or_check_or_vault_id.is_a?(Check) + check = creditcard_or_check_or_vault_id + post[:firstname] = check.first_name + post[:lastname] = check.last_name + post[:checkname] = check.name + post[:checkaba] = check.routing_number + post[:checkaccount] = check.account_number + post[:account_type] = check.account_type + post[:account_holder_type] = check.account_holder_type + post[:payment] = 'check' + else + post[:customer_vault_id] = creditcard_or_check_or_vault_id + end + end + + def add_login(post) + post[:username] = @options[:login] + post[:password] = @options[:password] + end + + def add_amount(post, money) + post[:currency] = options[:currency] || currency(money) + post[:amount] = amount(money) + end + + def commit_vault(action, parameters) + commit(nil, parameters.merge(:customer_vault => action)) + end + + def commit(action, parameters) + raw = parse(ssl_post(self.live_url, build_request(action, parameters))) + + success = (raw['response'] == ResponseCodes::APPROVED) + + authorization = authorization_from(success, parameters, raw) + + Response.new(success, raw['responsetext'], raw, + :test => test?, + :authorization => authorization, + :avs_result => { :code => raw['avsresponse']}, + :cvv_result => raw['cvvresponse'] + ) + end + + def build_request(action, parameters) + parameters[:type] = action if action + add_login(parameters) + parameters.to_query + end + + def authorization_from(success, parameters, response) + return nil unless success + + authorization = response['transactionid'] + if(parameters[:customer_vault] && (authorization.nil? || authorization.empty?)) + authorization = response['customer_vault_id'] + end + + authorization + end + + class ResponseCodes + APPROVED = '1' + DENIED = '2' + ERROR = '3' + end + + def parse(raw_response) + rsp = CGI.parse(raw_response) + rsp.keys.each { |k| rsp[k] = rsp[k].first } # flatten out the values + rsp + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trust_commerce.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trust_commerce.rb new file mode 100644 index 000000000..e0a5b9c27 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/trust_commerce.rb @@ -0,0 +1,421 @@ +begin + require 'tclink' +rescue LoadError + # Falls back to an SSL post to TrustCommerce +end + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # TO USE: + # First, make sure you have everything setup correctly and all of your dependencies in place with: + # + # require 'rubygems' + # require 'active_merchant' + # + # ActiveMerchant expects amounts to be Integer values in cents + # + # tendollar = 1000 + # + # Next, create a credit card object using a TC approved test card. + # + # creditcard = ActiveMerchant::Billing::CreditCard.new( + # :number => '4111111111111111', + # :month => 8, + # :year => 2006, + # :first_name => 'Longbob', + # :last_name => 'Longsen' + # ) + # + # To finish setting up, create the active_merchant object you will be using, with the TrustCommerce gateway. If you have a + # functional TrustCommerce account, replace login and password with your account info. Otherwise the defaults will work for + # testing. + # + # gateway = ActiveMerchant::Billing::Base.gateway(:trust_commerce).new(:login => "TestMerchant", :password => "password") + # + # Now we are ready to process our transaction + # + # response = gateway.purchase(tendollar, creditcard) + # + # Sending a transaction to TrustCommerce with active_merchant returns a Response object, which consistently allows you to: + # + # 1) Check whether the transaction was successful + # + # response.success? + # + # 2) Retrieve any message returned by TrustCommerce, either a "transaction was successful" note or an explanation of why the + # transaction was rejected. + # + # response.message + # + # 3) Retrieve and store the unique transaction ID returned by Trust Commerece, for use in referencing the transaction in the future. + # + # response.params["transid"] + # + # For higher performance and failover with the TrustCommerceGateway you can install the TCLink library from http://www.trustcommerce.com/tclink.html. + # Follow the instructions available there to get it working on your system. ActiveMerchant will automatically use tclink if available. + # + # The TCLink library has the following added benefits: + # * Good transaction times. Transaction duration under 1.2 seconds are common. + # * Fail-over to geographically distributed servers for extreme reliability + # + # Once it is installed, you should be able to make sure + # that it is visible to your ruby install by opening irb and typing "require 'tclink'", which should return "true". + # + # This should be enough to get you started with Trust Commerce and active_merchant. For further information, review the methods + # below and the rest of active_merchant's documentation, as well as Trust Commerce's user and developer documentation. + + class TrustCommerceGateway < Gateway + self.live_url = self.test_url = 'https://vault.trustcommerce.com/trans/' + + SUCCESS_TYPES = ["approved", "accepted"] + + DECLINE_CODES = { + "decline" => "The credit card was declined", + "avs" => "AVS failed; the address entered does not match the billing address on file at the bank", + "cvv" => "CVV failed; the number provided is not the correct verification number for the card", + "call" => "The card must be authorized manually over the phone", + "expiredcard" => "Issuer was not certified for card verification", + "carderror" => "Card number is invalid", + "authexpired" => "Attempt to postauth an expired (more than 14 days old) preauth", + "fraud" => "CrediGuard fraud score was below requested threshold", + "blacklist" => "CrediGuard blacklist value was triggered", + "velocity" => "CrediGuard velocity control value was triggered", + "dailylimit" => "Daily limit in transaction count or amount as been reached", + "weeklylimit" => "Weekly limit in transaction count or amount as been reached", + "monthlylimit" => "Monthly limit in transaction count or amount as been reached" + } + + BADDATA_CODES = { + "missingfields" => "One or more parameters required for this transaction type were not sent", + "extrafields" => "Parameters not allowed for this transaction type were sent", + "badformat" => "A field was improperly formatted, such as non-digit characters in a number field", + "badlength" => "A field was longer or shorter than the server allows", + "merchantcantaccept" => "The merchant can't accept data passed in this field", + "mismatch" => "Data in one of the offending fields did not cross-check with the other offending field" + } + + ERROR_CODES = { + "cantconnect" => "Couldn't connect to the TrustCommerce gateway", + "dnsfailure" => "The TCLink software was unable to resolve DNS hostnames", + "linkfailure" => "The connection was established, but was severed before the transaction could complete", + "failtoprocess" => "The bank servers are offline and unable to authorize transactions" + } + + TEST_LOGIN = 'TestMerchant' + TEST_PASSWORD = 'password' + + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club, :jcb] + self.supported_countries = ['US'] + self.homepage_url = 'http://www.trustcommerce.com/' + self.display_name = 'TrustCommerce' + + def self.tclink? + defined?(TCLink) + end + + # Creates a new TrustCommerceGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * <tt>:login</tt> -- The TrustCommerce account login. + # * <tt>:password</tt> -- The TrustCommerce account password. + # * <tt>:test => +true+ or +false+</tt> -- Perform test transactions + # + # ==== Test Account Credentials + # * <tt>:login</tt> -- TestMerchant + # * <tt>:password</tt> -- password + def initialize(options = {}) + requires!(options, :login, :password) + + super + end + + def tclink? + self.class.tclink? + end + + def test? + ((@options[:login] == TEST_LOGIN && @options[:password] == TEST_PASSWORD) || super) + end + + # authorize() is the first half of the preauth(authorize)/postauth(capture) model. The TC API docs call this + # preauth, we preserve active_merchant's nomenclature of authorize() for consistency with the rest of the library. This + # method simply checks to make sure funds are available for a transaction, and returns a transid that can be used later to + # postauthorize (capture) the funds. + + def authorize(money, creditcard_or_billing_id, options = {}) + parameters = { + :amount => amount(money), + } + + add_order_id(parameters, options) + add_customer_data(parameters, options) + add_payment_source(parameters, creditcard_or_billing_id) + add_addresses(parameters, options) + commit('preauth', parameters) + end + + # purchase() is a simple sale. This is one of the most common types of transactions, and is extremely simple. All that you need + # to process a purchase are an amount in cents or a money object and a creditcard object or billingid string. + def purchase(money, creditcard_or_billing_id, options = {}) + parameters = { + :amount => amount(money), + } + + add_order_id(parameters, options) + add_customer_data(parameters, options) + add_payment_source(parameters, creditcard_or_billing_id) + add_addresses(parameters, options) + commit('sale', parameters) + end + + # capture() is the second half of the preauth(authorize)/postauth(capture) model. The TC API docs call this + # postauth, we preserve active_merchant's nomenclature of capture() for consistency with the rest of the library. To process + # a postauthorization with TC, you need an amount in cents or a money object, and a TC transid. + def capture(money, authorization, options = {}) + parameters = { + :amount => amount(money), + :transid => authorization, + } + + commit('postauth', parameters) + end + + # refund() allows you to return money to a card that was previously billed. You need to supply the amount, in cents or a money object, + # that you want to refund, and a TC transid for the transaction that you are refunding. + def refund(money, identification, options = {}) + parameters = { + :amount => amount(money), + :transid => identification + } + + commit('credit', parameters) + end + + def credit(money, identification, options = {}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + # void() clears an existing authorization and releases the reserved fund + # s back to the cardholder. The TC API refers to this transaction as a + # reversal. After voiding, you will no longer be able to capture funds + # from this authorization. TrustCommerce seems to always return a status + # of "accepted" even if the transid you are trying to deauthorize has + # already been captured. Note: Your account needs to be configured by + # TrustCommerce to allow for reversal transactions before you can use this + # method. + # + # NOTE: AMEX preauth's cannot be reversed. If you want to clear it more + # quickly than the automatic expiration (7-10 days), you will have to + # capture it and then immediately issue a credit for the same amount + # which should clear the customers credit card with 48 hours according to + # TC. + def void(authorization, options = {}) + parameters = { + :transid => authorization, + } + + commit('reversal', parameters) + end + + # recurring() a TrustCommerce account that is activated for Citatdel, TrustCommerce's + # hosted customer billing info database. + # + # Recurring billing uses the same TC action as a plain-vanilla 'store', but we have a separate method for clarity. It can be called + # like store, with the addition of a required 'periodicity' parameter: + # + # The parameter :periodicity should be specified as either :bimonthly, :monthly, :biweekly, :weekly, :yearly or :daily + # + # gateway.recurring(tendollar, creditcard, :periodicity => :weekly) + # + # You can optionally specify how long you want payments to continue using 'payments' + def recurring(money, creditcard, options = {}) + requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily] ) + + cycle = case options[:periodicity] + when :monthly + '1m' + when :bimonthly + '2m' + when :weekly + '1w' + when :biweekly + '2w' + when :yearly + '1y' + when :daily + '1d' + end + + parameters = { + :amount => amount(money), + :cycle => cycle, + :verify => options[:verify] || 'y', + :billingid => options[:billingid] || nil, + :payments => options[:payments] || nil, + } + + add_creditcard(parameters, creditcard) + + commit('store', parameters) + end + + # store() requires a TrustCommerce account that is activated for Citatdel. You can call it with a credit card and a billing ID + # you would like to use to reference the stored credit card info for future captures. Use 'verify' to specify whether you want + # to simply store the card in the DB, or you want TC to verify the data first. + + def store(creditcard, options = {}) + parameters = { + :verify => options[:verify] || 'y', + :billingid => options[:billingid] || options[:billing_id] || nil, + } + + add_creditcard(parameters, creditcard) + add_addresses(parameters, options) + commit('store', parameters) + end + + # To unstore a creditcard stored in Citadel using store() or recurring(), all that is required is the billing id. When you run + # unstore() the information will be removed and a Response object will be returned indicating the success of the action. + def unstore(identification, options = {}) + parameters = { + :billingid => identification, + } + + commit('unstore', parameters) + end + + private + def add_payment_source(params, source) + if source.is_a?(String) + add_billing_id(params, source) + else + add_creditcard(params, source) + end + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}#{year[-2..-1]}" + end + + def add_creditcard(params, creditcard) + params[:media] = "cc" + params[:name] = creditcard.name + params[:cc] = creditcard.number + params[:exp] = expdate(creditcard) + params[:cvv] = creditcard.verification_value if creditcard.verification_value? + end + + def add_order_id(params, options) + params[:ticket] = options[:order_id] unless options[:order_id].blank? + end + + def add_billing_id(params, billingid) + params[:billingid] = billingid + end + + def add_customer_data(params, options) + params[:email] = options[:email] unless options[:email].blank? + params[:ip] = options[:ip] unless options[:ip].blank? + end + + def add_addresses(params, options) + address = options[:billing_address] || options[:address] + + if address + params[:address1] = address[:address1] unless address[:address1].blank? + params[:address2] = address[:address2] unless address[:address2].blank? + params[:city] = address[:city] unless address[:city].blank? + params[:state] = address[:state] unless address[:state].blank? + params[:zip] = address[:zip] unless address[:zip].blank? + params[:country] = address[:country] unless address[:country].blank? + params[:avs] = 'n' + end + + if shipping_address = options[:shipping_address] + params[:shipto_name] = shipping_address[:name] unless shipping_address[:name].blank? + params[:shipto_address1] = shipping_address[:address1] unless shipping_address[:address1].blank? + params[:shipto_address2] = shipping_address[:address2] unless shipping_address[:address2].blank? + params[:shipto_city] = shipping_address[:city] unless shipping_address[:city].blank? + params[:shipto_state] = shipping_address[:state] unless shipping_address[:state].blank? + params[:shipto_zip] = shipping_address[:zip] unless shipping_address[:zip].blank? + params[:shipto_country] = shipping_address[:country] unless shipping_address[:country].blank? + end + end + + def clean_and_stringify_params(parameters) + # TCLink wants us to send a hash with string keys, and activemerchant pushes everything around with + # symbol keys. Before sending our input to TCLink, we convert all our keys to strings and dump the symbol keys. + # We also remove any pairs with nil values, as these confuse TCLink. + parameters.keys.reverse.each do |key| + if parameters[key] + parameters[key.to_s] = parameters[key] + end + parameters.delete(key) + end + end + + def post_data(parameters) + parameters.collect { |key, value| "#{key}=#{ CGI.escape(value.to_s)}" }.join("&") + end + + def commit(action, parameters) + parameters[:custid] = @options[:login] + parameters[:password] = @options[:password] + parameters[:demo] = test? ? 'y' : 'n' + parameters[:action] = action + + clean_and_stringify_params(parameters) + + data = if tclink? + TCLink.send(parameters) + else + parse( ssl_post(self.live_url, post_data(parameters)) ) + end + + # to be considered successful, transaction status must be either "approved" or "accepted" + success = SUCCESS_TYPES.include?(data["status"]) + message = message_from(data) + Response.new(success, message, data, + :test => test?, + :authorization => data["transid"], + :cvv_result => data["cvv"], + :avs_result => { :code => data["avs"] } + ) + end + + def parse(body) + results = {} + + body.split(/\n/).each do |pair| + key,val = pair.split(/=/) + results[key] = val + end + + results + end + + def message_from(data) + status = case data["status"] + when "decline" + return DECLINE_CODES[data["declinetype"]] + when "baddata" + return BADDATA_CODES[data["error"]] + when "error" + return ERROR_CODES[data["errortype"]] + else + return "The transaction was successful" + end + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay.rb new file mode 100644 index 000000000..2ca57cdd0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay.rb @@ -0,0 +1,25 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + ## + # Delegates to the appropriate gateway, either the Transaction or Advanced + # depending on options passed to new. + # + class UsaEpayGateway < Gateway + + self.abstract_class = true + + ## + # Creates an instance of UsaEpayTransactionGateway by default, but if + # :software id or :live_url are passed in the options hash it will + # create an instance of UsaEpayAdvancedGateway. + # + def self.new(options={}) + unless options.has_key?(:software_id) || options.has_key?(:live_url) + UsaEpayTransactionGateway.new(options) + else + UsaEpayAdvancedGateway.new(options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_advanced.rb new file mode 100644 index 000000000..6b3faeb31 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -0,0 +1,1501 @@ +require 'securerandom' +require 'digest' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + # ==== USA ePay Advanced SOAP Interface + # + # This class encapuslates USA ePay's Advanced SOAP Interface. The Advanced Soap Interface allows + # standard transactions, storing customer information, and recurring billing. Storing sensitive + # information on USA ePay's servers can help with PCI DSS compliance, since customer and card data + # do not need to be stored locally. + # + # Make sure you have enabled this functionality for your account with USA ePay. + # + # Information about the Advanced SOAP interface is available on the {USA ePay wiki}[http://wiki.usaepay.com/developer/soap]. + # + # ==== Login, Password, and Software ID + # + # Please follow all of USA ePay's directions for acquiring all accounts and settings. + # + # The value used for <tt>:login</tt> is the Key value found in the Merchant Console under Settings > Source + # Key. You will have to add this key in the USA ePay Merchant Console. + # + # The value used for <tt>:password</tt> is the pin value also found and assigned in the Merchant Console under + # Settings > Source Key. The pin is required to use all but basic transactions in the SOAP interface. + # You will have to add the pin to your source key, as it defaults to none. + # + # The value used for the <tt>:software_id</tt> is found in the Developer's Login under the Developers Center + # in your WSDL. It is the 8 character value in <soap:address> tag. A masked example: + # <soap:address location="https://www.usaepay.com/soap/gate/XXXXXXXX"/> + # It is also found in the link to your WSDL. This is required as every account has a different path + # SOAP requests are submitted to. Optionally, you can provide the entire urls via <tt>:live_url</tt> and <tt>:test_url</tt>, if your prefer. + # + # ==== Responses + # * <tt>#success?</tt> -- +true+ if transmitted and returned correctly + # * <tt>#message</tt> -- response or fault message + # * <tt>#authorization</tt> -- reference_number or nil + # * <tt>#params</tt> -- hash of entire soap response contents + # + # ==== Address Options + # * <tt>:billing_address/:shipping_address</tt> -- contains some extra options + # * <tt>:name</tt> -- virtual attribute; will split to first and last name + # * <tt>:first_name</tt> + # * <tt>:last_name</tt> + # * <tt>:address1 </tt> + # * <tt>:address2 </tt> + # * <tt>:city </tt> + # * <tt>:state </tt> + # * <tt>:zip </tt> + # * <tt>:country </tt> + # * <tt>:phone</tt> + # * <tt>:email</tt> + # * <tt>:fax</tt> + # * <tt>:company</tt> + # + # ==== Support: + # * Questions: post to {active_merchant google group}[http://groups.google.com/group/activemerchant] + # * Feedback/fixes: matt (at) nearapogee (dot) com + # + # ==== Links: + # * {USA ePay Merchant Console}[https://sandbox.usaepay.com/login] + # * {USA ePay Developer Login}[https://www.usaepay.com/developer/login] + # + class UsaEpayAdvancedGateway < Gateway + API_VERSION = "1.4" + + TEST_URL_BASE = 'https://sandbox.usaepay.com/soap/gate/' #:nodoc: + LIVE_URL_BASE = 'https://www.usaepay.com/soap/gate/' #:nodoc: + + self.test_url = TEST_URL_BASE + self.live_url = LIVE_URL_BASE + + FAILURE_MESSAGE = "Default Failure" #:nodoc: + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb] + self.homepage_url = 'http://www.usaepay.com/' + self.display_name = 'USA ePay Advanced SOAP Interface' + + CUSTOMER_OPTIONS = { + :id => [:string, 'CustomerID'], # merchant assigned number + :notes => [:string, 'Notes'], + :data => [:string, 'CustomData'], + :url => [:string, 'URL'], + # Recurring Billing + :enabled => [:boolean, 'Enabled'], + :schedule => [:string, 'Schedule'], + :number_left => [:integer, 'NumLeft'], + :currency => [:string, 'Currency'], + :description => [:string, 'Description'], + :order_id => [:string, 'OrderID'], + :user => [:string, 'User'], + :source => [:string, 'Source'], + :send_receipt => [:boolean, 'SendReceipt'], + :receipt_note => [:string, 'ReceiptNote'], + # Point of Sale + :price_tier => [:string, 'PriceTier'], + :tax_class => [:string, 'TaxClass'], + :lookup_code => [:string, 'LookupCode'] + } #:nodoc: + + ADDRESS_OPTIONS = { + :first_name => [:string, 'FirstName'], + :last_name => [:string, 'LastName'], + :address1 => [:string, 'Street'], + :address2 => [:string, 'Street2'], + :city => [:string, 'City'], + :state => [:string, 'State'], + :zip => [:string, 'Zip'], + :country => [:string, 'Country'], + :phone => [:string, 'Phone'], + :email => [:string, 'Email'], + :fax => [:string, 'Fax'], + :company => [:string, 'Company'] + } #:nodoc: + + CUSTOMER_TRANSACTION_REQUEST_OPTIONS = { + :command => [:string, 'Command'], + :ignore_duplicate => [:boolean, 'IgnoreDuplicate'], + :client_ip => [:string, 'ClientIP'], + :customer_receipt => [:boolean, 'CustReceipt'], + :customer_email => [:boolean, 'CustReceiptEmail'], + :customer_template => [:boolean, 'CustReceiptName'], + :merchant_receipt => [:boolean, 'MerchReceipt'], + :merchant_email => [:boolean, 'MerchReceiptEmail'], + :merchant_template => [:boolean, 'MerchReceiptName'], + :verification_value => [:boolean, 'isRecurring'], + :software => [:string, 'Software'] + } #:nodoc: + + TRANSACTION_REQUEST_OBJECT_OPTIONS = { + :command => [:string, 'Command'], + :ignore_duplicate => [:boolean, 'IgnoreDuplicate'], + :authorization_code => [:string, 'AuthCode'], + :reference_number => [:string, 'RefNum'], + :account_holder => [:string, 'AccountHolder'], + :client_ip => [:string, 'ClientIP'], + :customer_id => [:string, 'CustomerID'], + :customer_receipt => [:boolean, 'CustReceipt'], + :customer_template => [:boolean, 'CustReceiptName'], + :software => [:string, 'Software'] + } #:nodoc: + + TRANSACTION_DETAIL_OPTIONS = { + :invoice => [:string, 'Invoice'], + :po_number => [:string, 'PONum'], + :order_id => [:string, 'OrderID'], + :clerk => [:string, 'Clerk'], + :terminal => [:string, 'Terminal'], + :table => [:string, 'Table'], + :description => [:string, 'Description'], + :comments => [:string, 'Comments'], + :allow_partial_auth => [:boolean, 'AllowPartialAuth'], + :currency => [:string, 'Currency'], + :non_tax => [:boolean, 'NonTax'], + } #:nodoc: + + TRANSACTION_DETAIL_MONEY_OPTIONS = { + :amount => [:double, 'Amount'], + :tax => [:double, 'Tax'], + :tip => [:double, 'Tip'], + :non_tax => [:boolean, 'NonTax'], + :shipping => [:double, 'Shipping'], + :discount => [:double, 'Discount'], + :subtotal => [:double, 'Subtotal'] + } #:nodoc: + + CREDIT_CARD_DATA_OPTIONS = { + :magnetic_stripe => [:string, 'MagStripe'], + :dukpt => [:string, 'DUKPT'], + :signature => [:string, 'Signature'], + :terminal_type => [:string, 'TermType'], + :magnetic_support => [:string, 'MagSupport'], + :xid => [:string, 'XID'], + :cavv => [:string, 'CAVV'], + :eci => [:integer, 'ECI'], + :internal_card_authorization => [:boolean, 'InternalCardAuth'], + :pares => [:string, 'Pares'] + } #:nodoc: + + CHECK_DATA_OPTIONS = { + :check_number => [:integer, 'CheckNumber'], + :drivers_license => [:string, 'DriversLicense'], + :drivers_license_state => [:string, 'DriversLicenseState'], + :record_type => [:string, 'RecordType'], + :aux_on_us => [:string, 'AuxOnUS'], + :epc_code => [:string, 'EpcCode'], + :front_image => [:string, 'FrontImage'], + :back_image => [:string, 'BackImage'] + } #:nodoc: + + RECURRING_BILLING_OPTIONS = { + :schedule => [:string, 'Schedule'], + :number_left => [:integer, 'NumLeft'], + :enabled => [:boolean, 'Enabled'] + } #:nodoc: + + AVS_RESULTS = { + 'Y' => %w( YYY Y YYA YYD ), + 'Z' => %w( NYZ Z ), + 'A' => %w( YNA A YNY ), + 'N' => %w( NNN N NN ), + 'X' => %w( YYX X ), + 'W' => %w( NYW W ), + 'XXW' => %w( XXW ), + 'XXU' => %w( XXU ), + 'R' => %w( XXR R U E ), + 'S' => %w( XXS S ), + 'XXE' => %w( XXE ), + 'G' => %w( XXG G C I ), + 'B' => %w( YYG B M ), + 'D' => %w( GGG D ), + 'P' => %w( YGG P ) + }.inject({}) do |map, (type, codes)| + codes.each { |code| map[code] = type } + map + end #:nodoc: + + AVS_CUSTOM_MESSAGES = { + 'XXW' => 'Card number not on file.', + 'XXU' => 'Address information not verified for domestic transaction.', + 'XXE' => 'Address verification not allowed for card type.' + } #:nodoc: + + # Create a new gateway. + # + # ==== Required + # * At least the live_url OR the software_id must be present. + # * <tt>:software_id</tt> -- 8 character software id + # OR + # * <tt>:test_url</tt> -- full url for testing + # * <tt>:live_url</tt> -- full url for live/production + # + # ==== Optional + # * <tt>:soap_response</tt> -- set to +true+ to add :soap_response to the params hash containing the entire soap xml message + # + def initialize(options = {}) + requires!(options, :login, :password) + + if options[:software_id] + self.live_url = "#{LIVE_URL_BASE}#{options[:software_id].to_s}" + self.test_url = "#{TEST_URL_BASE}#{options[:software_id].to_s}" + else + self.live_url = options[:live_url].to_s + self.test_url = options[:test_url].to_s if options[:test_url] + end + + super + end + + # Standard Gateway Methods ====================================== + + # Make a purchase with a credit card. (Authorize and + # capture for settlement.) + # + # Note: See run_transaction for additional options. + # + def purchase(money, creditcard, options={}) + run_sale(options.merge!(:amount => money, :payment_method => creditcard)) + end + + # Authorize an amount on a credit card or account. + # + # Note: See run_transaction for additional options. + # + def authorize(money, creditcard, options={}) + run_auth_only(options.merge!(:amount => money, :payment_method => creditcard)) + end + + # Capture an authorized transaction. + # + # Note: See run_transaction for additional options. + # + def capture(money, identification, options={}) + capture_transaction(options.merge!(:amount => money, :reference_number => identification)) + end + + # Void a previous transaction that has not been settled. + # + # Note: See run_transaction for additional options. + # + def void(identification, options={}) + void_transaction(options.merge!(:reference_number => identification)) + end + + # Refund a previous transaction. + # + # Note: See run_transaction for additional options. + # + def refund(money, identification, options={}) + refund_transaction(options.merge!(:amount => money, :reference_number => identification)) + end + + def credit(money, identification, options={}) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, identification, options) + end + + # Customer ====================================================== + + # Add a customer. + # + # ==== Options + # * <tt>:id</tt> -- merchant assigned id + # * <tt>:notes</tt> -- notes about customer + # * <tt>:data</tt> -- base64 data about customer + # * <tt>:url</tt> -- customer website + # * <tt>:billing_address</tt> -- usual options + # * <tt>:payment_methods</tt> -- array of payment method hashes. + # * <tt>:method</tt> -- credit_card or check + # * <tt>:name</tt> -- optional name/label for the method + # * <tt>:sort</tt> -- optional integer value specifying the backup sort order, 0 is default + # + # ==== Recurring Options + # * <tt>:enabled</tt> -- +true+ enables recurring + # * <tt>:schedule</tt> -- daily, weekly, bi-weekly (every two weeks), monthly, bi-monthly (every two months), quarterly, bi-annually (every six months), annually, first of month, last day of month + # * <tt>:number_left</tt> -- number of payments left; -1 for unlimited + # * <tt>:next</tt> -- date of next payment (Date/Time) + # * <tt>:amount</tt> -- amount of recurring payment + # * <tt>:tax</tt> -- tax portion of amount + # * <tt>:currency</tt> -- numeric currency code + # * <tt>:description</tt> -- description of transaction + # * <tt>:order_id</tt> -- transaction order id + # * <tt>:user</tt> -- merchant username assigned to transaction + # * <tt>:source</tt> -- name of source key assigned to billing + # * <tt>:send_receipt</tt> -- +true+ to send client a receipt + # * <tt>:receipt_note</tt> -- leave a note on the receipt + # + # ==== Point of Sale Options + # * <tt>:price_tier</tt> -- name of customer price tier + # * <tt>:tax_class</tt> -- tax class + # * <tt>:lookup_code</tt> -- lookup code from customer/member id card; barcode or magnetic stripe; can be assigned by merchant; defaults to system assigned if blank + # + # ==== Response + # * <tt>#message</tt> -- customer number assigned by gateway + # + def add_customer(options={}) + request = build_request(__method__, options) + commit(__method__, request) + end + + # Update a customer by replacing all of the customer details.. + # + # Use quickUpdateCustomer to just update a few attributes. + # + # ==== Options + # * Same as add_customer + # + def update_customer(options={}) + requires! options, :customer_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Enable a customer for recurring billing. + # + # Note: Customer does not need to have all recurring paramerters to succeed. + # + # ==== Required + # * <tt>:customer_number</tt> + # + def enable_customer(options={}) + requires! options, :customer_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Disable a customer for recurring billing. + # + # ==== Required + # * <tt>:customer_number</tt> + # + def disable_customer(options={}) + requires! options, :customer_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Add a payment method to a customer. + # + # ==== Required + # * <tt>:customer_number</tt> -- number returned by add_customer response.message + # * <tt>:payment_method</tt> + # * <tt>:method</tt> -- credit_card or check + # * <tt>:name</tt> -- optional name/label for the method + # * <tt>:sort</tt> -- an integer value specifying the backup sort order, 0 is default + # + # ==== Optional + # * <tt>:make_default</tt> -- set +true+ to make default + # * <tt>:verify</tt> -- set +true+ to run auth_only verification; throws fault if cannot verify + # + # ==== Response + # * <tt>#message</tt> -- method_id of new customer payment method + # + def add_customer_payment_method(options={}) + requires! options, :customer_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Retrive all of the payment methods belonging to a customer + # + # ==== Required + # * <tt>:customer_number</tt> + # + # ==== Response + # * <tt>#message</tt> -- either a single hash or an array of hashes of payment methods + # + def get_customer_payment_methods(options={}) + requires! options, :customer_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Retrive one of the payment methods belonging to a customer + # + # ==== Required + # * <tt>:customer_number</tt> + # * <tt>:method_id</tt> + # + # ==== Response + # * <tt>#message</tt> -- hash of payment method + # + def get_customer_payment_method(options={}) + requires! options, :customer_number, :method_id + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Update a customer payment method. + # + # ==== Required + # * <tt>:method_id</tt> -- method_id to update + # + # ==== Options + # * <tt>:method</tt> -- credit_card or check + # * <tt>:name</tt> -- optional name/label for the method + # * <tt>:sort</tt> -- an integer value specifying the backup sort order, 0 is default + # * <tt>:verify</tt> -- set +true+ to run auth_only verification; throws fault if cannot verify + # + # ==== Response + # * <tt>#message</tt> -- hash of payment method + # + def update_customer_payment_method(options={}) + requires! options, :method_id + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Delete one the payment methods beloning to a customer + # + # ==== Required + # * <tt>:customer_number</tt> + # * <tt>:method_id</tt> + # + def delete_customer_payment_method(options={}) + requires! options, :customer_number, :method_id + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Delete a customer. + # + # ==== Required + # * <tt>:customer_number</tt> + # + def delete_customer(options={}) + requires! options, :customer_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Run a transaction for an existing customer in the database. + # + # ==== Required Options + # * <tt>:customer_number</tt> -- gateway assigned identifier + # * <tt>:command</tt> -- Sale, AuthOnly, Credit, Check, CheckCredit + # * <tt>:amount</tt> -- total amount + # + # ==== Options + # * <tt>:method_id</tt> -- which payment method to use, 0/nil/omitted for default method + # * <tt>:ignore_duplicate</tt> -- +true+ overrides duplicate transaction + # * <tt>:client_ip</tt> -- client ip address + # * <tt>:customer_receipt</tt> -- +true+, sends receipt to customer. active_merchant defaults to +false+ + # * <tt>:customer_email</tt> -- specify if different than customer record + # * <tt>:customer_template</tt> -- name of template + # * <tt>:merchant_receipt</tt> -- +true+, sends receipt to merchant. active_merchant defaults to +false+ + # * <tt>:merchant_email</tt> -- required if :merchant_receipt set to +true+ + # * <tt>:merchant_template</tt> -- name of template + # * <tt>:recurring</tt> -- defaults to +false+ *see documentation* + # * <tt>:verification_value</tt> -- pci forbids storage of this value, only required for CVV2 validation + # * <tt>:software</tt> -- active_merchant sets to required gateway option value + # * <tt>:line_items</tt> -- XXX not implemented yet + # * <tt>:custom_fields</tt> -- XXX not implemented yet + # + # ==== Transaction Options + # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number + # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters + # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters + # * <tt>:clerk</tt> -- sales clerk + # * <tt>:terminal</tt> -- terminal name + # * <tt>:table</tt> -- table name/number + # * <tt>:description</tt> -- description + # * <tt>:comments</tt> -- comments + # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+ + # * <tt>:currency</tt> -- numeric currency code + # * <tt>:tax</tt> -- tax portion of amount + # * <tt>:tip</tt> -- tip portion of amount + # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable + # * <tt>:shipping</tt> -- shipping portion of amount + # * <tt>:discount</tt> -- amount of discount + # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def run_customer_transaction(options={}) + requires! options, :customer_number, :command, :amount + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Transactions ================================================== + + # Run a transaction. + # + # Note: run_sale, run_auth_only, run_credit, run_check_sale, run_check_credit + # methods are also available. Each takes the same options as + # run_transaction, but the :command option is not required. + # + # Recurring Note: If recurring options are included USA ePay will create a + # new customer record with the supplied information. The customer number + # will be returned in the response. + # + # ==== Options + # * <tt>:method</tt> -- credit_card or check + # * <tt>:command</tt> -- sale, credit, void, creditvoid, authonly, capture, postauth, check, checkcredit; defaults to sale; only required for run_transaction when other than sale + # * <tt>:reference_number</tt> -- for the original transaction; obtained by sale or authonly + # * <tt>:authorization_code</tt> -- required for postauth; obtained offline + # * <tt>:ignore_duplicate</tt> -- set +true+ if you want to override the duplicate tranaction handling + # * <tt>:account_holder</tt> -- name of account holder + # * <tt>:customer_id</tt> -- merchant assigned id + # * <tt>:customer_receipt</tt> -- set +true+ to email receipt to billing email address + # * <tt>:customer_template</tt> -- name of template + # * <tt>:software</tt> -- stamp merchant software version for tracking + # * <tt>:billing_address</tt> -- see UsaEpayCimGateway documentation for all address fields + # * <tt>:shipping_address</tt> -- see UsaEpayCimGateway documentation for all address fields + # * <tt>:recurring</tt> -- used for recurring billing transactions + # * <tt>:schedule</tt> -- disabled, daily, weekly, bi-weekly (every two weeks), monthly, bi-monthly (every two months), quarterly, bi-annually (every six months), annually + # * <tt>:next</tt> -- date customer billed next (Date/Time) + # * <tt>:expire</tt> -- date the recurring transactions end (Date/Time) + # * <tt>:number_left</tt> -- transactions remaining in billing cycle + # * <tt>:amount</tt> -- amount to be billed each recurring transaction + # * <tt>:enabled</tt> -- states if currently active + # * <tt>:line_items</tt> -- XXX not implemented yet + # * <tt>:custom_fields</tt> -- XXX not implemented yet + # + # ==== Transaction Options + # * <tt>:amount</tt> -- total amount + # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number + # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters + # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters + # * <tt>:clerk</tt> -- sales clerk + # * <tt>:terminal</tt> -- terminal name + # * <tt>:table</tt> -- table name/number + # * <tt>:description</tt> -- description + # * <tt>:comments</tt> -- comments + # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+ + # * <tt>:currency</tt> -- numeric currency code + # * <tt>:tax</tt> -- tax portion of amount + # * <tt>:tip</tt> -- tip portion of amount + # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable + # * <tt>:shipping</tt> -- shipping portion of amount + # * <tt>:discount</tt> -- amount of discount + # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def run_transaction(options={}) + request = build_request(__method__, options) + commit(__method__, request) + end + + TRANSACTION_METHODS = [ + :run_sale, :run_auth_only, :run_credit, + :run_check_sale, :run_check_credit + ] #:nodoc: + + TRANSACTION_METHODS.each do |method| + define_method method do |options| + request = build_request(method, options) + commit(method, request) + end + end + + # Post an authorization code obtained offline. + # + # ==== Required + # * <tt>:authorization_code</tt> -- obtained offline + # + # ==== Options + # * Same as run_transaction + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def post_auth(options={}) + requires! options, :authorization_code + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Capture an authorized transaction and move it into the current batch + # for settlement. + # + # Note: Check with merchant bank for details/restrictions on differing + # amounts than the original authorization. + # + # ==== Required + # * <tt>:reference_number</tt> + # + # ==== Options + # * <tt>:amount</tt> -- may be different than original amount; 0 will void authorization + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def capture_transaction(options={}) + requires! options, :reference_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Void a transaction. + # + # Note: Can only be voided before being settled. + # + # ==== Required + # * <tt>:reference_number</tt> + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def void_transaction(options={}) + requires! options, :reference_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Refund transaction. + # + # Note: Required after a transaction has been settled. Refunds + # both credit card and check transactions. + # + # ==== Required + # * <tt>:reference_number</tt> + # * <tt>:amount</tt> -- amount to refund; 0 will refund original amount + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def refund_transaction(options={}) + requires! options, :reference_number, :amount + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Override transaction flagged for mananager approval. + # + # Note: Checks only! + # + # ==== Required + # * <tt>:reference_number</tt> + # + # ==== Options + # * <tt>:reason</tt> + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def override_transaction(options={}) + requires! options, :reference_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Quick Transactions ============================================ + + # Run a sale transaction based off of a past transaction. + # + # Transfers referenced transaction's payment method to this + # transaction. As of 6/2011, USA ePay blocks credit card numbers + # at 3 years. + # + # ==== Required + # * <tt>:reference_number</tt> -- transaction to reference payment from + # * <tt>:amount</tt> -- total amount + # + # ==== Options + # * <tt>:authorize_only</tt> -- set +true+ if you just want to authorize + # + # ==== Transaction Options + # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number + # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters + # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters + # * <tt>:clerk</tt> -- sales clerk + # * <tt>:terminal</tt> -- terminal name + # * <tt>:table</tt> -- table name/number + # * <tt>:description</tt> -- description + # * <tt>:comments</tt> -- comments + # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+ + # * <tt>:currency</tt> -- numeric currency code + # * <tt>:tax</tt> -- tax portion of amount + # * <tt>:tip</tt> -- tip portion of amount + # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable + # * <tt>:shipping</tt> -- shipping portion of amount + # * <tt>:discount</tt> -- amount of discount + # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def run_quick_sale(options={}) + requires! options, :reference_number, :amount + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Run a credit based off of a past transaction. + # + # Transfers referenced transaction's payment method to this + # transaction. As of 6/2011, USA ePay blocks credit card numbers + # at 3 years. + # + # ==== Required + # * <tt>:reference_number</tt> -- transaction to reference payment from + # + # ==== Transaction Options + # * <tt>:amount</tt> -- total amount + # * <tt>:invoice</tt> -- transaction invoice number; truncated to 10 characters; defaults to reference_number + # * <tt>:po_number</tt> -- commercial purchase order number; upto 25 characters + # * <tt>:order_id</tt> -- should be used to assign a unique id; upto 64 characters + # * <tt>:clerk</tt> -- sales clerk + # * <tt>:terminal</tt> -- terminal name + # * <tt>:table</tt> -- table name/number + # * <tt>:description</tt> -- description + # * <tt>:comments</tt> -- comments + # * <tt>:allow_partial_auth</tt> -- allow partial authorization if full amount is not available; defaults +false+ + # * <tt>:currency</tt> -- numeric currency code + # * <tt>:tax</tt> -- tax portion of amount + # * <tt>:tip</tt> -- tip portion of amount + # * <tt>:non_tax</tt> -- +true+ if transaction is non-taxable + # * <tt>:shipping</tt> -- shipping portion of amount + # * <tt>:discount</tt> -- amount of discount + # * <tt>:subtotal</tt> -- amount of transaction before tax, tip, shipping, and discount are applied + # + # ==== Response + # * <tt>#message</tt> -- transaction response hash + # + def run_quick_credit(options={}) + requires! options, :reference_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Transaction Status ============================================ + + # Retrieve details of a specified transaction. + # + # ==== Required + # * <tt>:reference_number</tt> + # + # ==== Response + # * <tt>#message</tt> -- transaction hash + # + def get_transaction(options={}) + requires! options, :reference_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Check status of a transaction. + # + # ==== Required + # * <tt>:reference_number</tt> + # + # ==== Response + # * <tt>response.success</tt> -- success of the referenced transaction + # * <tt>response.message</tt> -- message of the referenced transaction + # * <tt>response.authorization</tt> -- same as :reference_number in options + # + def get_transaction_status(options={}) + requires! options, :reference_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Check status of a transaction (custom). + # + # ==== Required + # * <tt>:reference_number</tt> + # * <tt>:fields</tt> -- string array of fields to retrieve + # * <tt>Response.AuthCode</tt> + # * <tt>Response.AvsResult</tt> + # * <tt>Response.AvsResultCode</tt> + # * <tt>Response.BatchNum</tt> + # * <tt>Response.CardCodeResult</tt> + # * <tt>Response.CardCodeResultCode</tt> + # * <tt>Response.ConversionRate</tt> + # * <tt>Response.ConvertedAmount</tt> + # * <tt>Response.ConvertedAmountCurrency</tt> + # * <tt>Response.Error</tt> + # * <tt>Response.ErrorCode</tt> + # * <tt>Response.RefNum</tt> + # * <tt>Response.Result</tt> + # * <tt>Response.ResultCode</tt> + # * <tt>Response.Status</tt> + # * <tt>Response.StatusCode</tt> + # * <tt>CheckTrace.TrackingNum</tt> + # * <tt>CheckTrace.Effective</tt> + # * <tt>CheckTrace.Processed</tt> + # * <tt>CheckTrace.Settled</tt> + # * <tt>CheckTrace.Returned</tt> + # * <tt>CheckTrace.BankNote</tt> + # * <tt>DateTime</tt> + # * <tt>AccountHolder</tt> + # * <tt>Details.Invoice</tt> + # * <tt>Details.PoNum</tt> + # * <tt>Details.OrderID</tt> + # * <tt>Details.Clerk</tt> + # * <tt>Details.Terminal</tt> + # * <tt>Details.Table</tt> + # * <tt>Details.Description</tt> + # * <tt>Details.Amount</tt> + # * <tt>Details.Currency</tt> + # * <tt>Details.Tax</tt> + # * <tt>Details.Tip</tt> + # * <tt>Details.NonTax</tt> + # * <tt>Details.Shipping</tt> + # * <tt>Details.Discount</tt> + # * <tt>Details.Subtotal</tt> + # * <tt>CreditCardData.CardType</tt> + # * <tt>CreditCardData.CardNumber</tt> + # * <tt>CreditCardData.CardExpiration</tt> + # * <tt>CreditCardData.CardCode</tt> + # * <tt>CreditCardData.AvsStreet</tt> + # * <tt>CreditCardData.AvsZip</tt> + # * <tt>CreditCardData.CardPresent</tt> + # * <tt>CheckData.CheckNumber</tt> + # * <tt>CheckData.Routing</tt> + # * <tt>CheckData.Account</tt> + # * <tt>CheckData.SSN</tt> + # * <tt>CheckData.DriversLicense</tt> + # * <tt>CheckData.DriversLicenseState</tt> + # * <tt>CheckData.RecordType</tt> + # * <tt>User</tt> + # * <tt>Source</tt> + # * <tt>ServerIP</tt> + # * <tt>ClientIP</tt> + # * <tt>CustomerID</tt> + # * <tt>BillingAddress.FirstName</tt> + # * <tt>BillingAddress.LastName</tt> + # * <tt>BillingAddress.Company</tt> + # * <tt>BillingAddress.Street</tt> + # * <tt>BillingAddress.Street2</tt> + # * <tt>BillingAddress.City</tt> + # * <tt>BillingAddress.State</tt> + # * <tt>BillingAddress.Zip</tt> + # * <tt>BillingAddress.Country</tt> + # * <tt>BillingAddress.Phone</tt> + # * <tt>BillingAddress.Fax</tt> + # * <tt>BillingAddress.Email</tt> + # * <tt>ShippingAddress.FirstName</tt> + # * <tt>ShippingAddress.LastName</tt> + # * <tt>ShippingAddress.Company</tt> + # * <tt>ShippingAddress.Street</tt> + # * <tt>ShippingAddress.Street2</tt> + # * <tt>ShippingAddress.City</tt> + # * <tt>ShippingAddress.State</tt> + # * <tt>ShippingAddress.Zip</tt> + # * <tt>ShippingAddress.Country</tt> + # * <tt>ShippingAddress.Phone</tt> + # * <tt>ShippingAddress.Fax</tt> + # * <tt>ShippingAddress.Email</tt> + # + # ==== Response + # * <tt>#message</tt> -- hash; keys are the field values + # + def get_transaction_custom(options={}) + requires! options, :reference_number, :fields + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Check status of a check transaction. + # + # ==== Required + # * <tt>:reference_number</tt> + # + # ==== Response + # * <tt>#message</tt> -- check trace hash + # + def get_check_trace(options={}) + requires! options, :reference_number + + request = build_request(__method__, options) + commit(__method__, request) + end + + # Account ======================================================= + + # Retrieve merchant account details + # + # ==== Response + # * <tt>#message</tt> -- account hash + # + def get_account_details + request = build_request(__method__) + commit(__method__, request) + end + + # Builders ====================================================== + + private + + # Build soap header, etc. + def build_request(action, options = {}) + soap = Builder::XmlMarkup.new + soap.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + soap.tag! "SOAP-ENV:Envelope", + 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:ns1' => 'urn:usaepay', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' do |soap| + soap.tag! "SOAP-ENV:Body" do |soap| + send("build_#{action}", soap, options) + end + end + soap.target! + end + + # Build generic tag. + def build_tag(soap, type, tag, value) + soap.tag!(tag, value, 'xsi:type' => "xsd:#{type}") if value != nil + end + + # Build token. + def build_token(soap, options) + seed = SecureRandom.base64(32) + hash = Digest::SHA1.hexdigest("#{@options[:login]}#{seed}#{@options[:password].to_s.strip}") + soap.Token 'xsi:type' => 'ns1:ueSecurityToken' do |soap| + build_tag soap, :string, 'ClientIP', options[:client_ip] + soap.PinHash 'xsi:type' => 'ns1:ueHash' do |soap| + build_tag soap, :string, "HashValue", hash + build_tag soap, :string, "Seed", seed + build_tag soap, :string, "Type", 'sha1' + end + build_tag soap, :string, 'SourceKey', @options[:login] + end + end + + # Customer ====================================================== + + def build_add_customer(soap, options) + soap.tag! "ns1:addCustomer" do |soap| + build_token soap, options + build_customer_data soap, options + build_tag soap, :double, 'Amount', amount(options[:amount]) + build_tag soap, :double, 'Tax', amount(options[:tax]) + build_tag soap, :string, 'Next', options[:next].strftime("%Y-%m-%d") if options[:next] + end + end + + def build_customer(soap, options, type, add_customer_data=false) + soap.tag! "ns1:#{type}" do |soap| + build_token soap, options + build_tag soap, :integer, 'CustNum', options[:customer_number] + build_customer_data soap, options if add_customer_data + end + end + + def build_update_customer(soap, options) + build_customer(soap, options, 'updateCustomer', true) + end + + def build_enable_customer(soap, options) + build_customer(soap, options, 'enableCustomer') + end + + def build_disable_customer(soap, options) + build_customer(soap, options, 'disableCustomer') + end + + def build_delete_customer(soap, options) + build_customer(soap, options, 'deleteCustomer') + end + + def build_add_customer_payment_method(soap, options) + soap.tag! "ns1:addCustomerPaymentMethod" do |soap| + build_token soap, options + build_tag soap, :integer, 'CustNum', options[:customer_number] + build_customer_payment_methods soap, options + build_tag soap, :boolean, 'MakeDefault', options[:make_default] + build_tag soap, :boolean, 'Verify', options[:verify] + end + end + + def build_get_customer_payment_method(soap, options) + soap.tag! 'ns1:getCustomerPaymentMethod' do |soap| + build_token soap, options + build_tag soap, :integer, 'CustNum', options[:customer_number] + build_tag soap, :integer, 'MethodID', options[:method_id] + end + end + + def build_get_customer_payment_methods(soap, options) + build_customer(soap, options, 'getCustomerPaymentMethods') + end + + def build_update_customer_payment_method(soap, options) + soap.tag! 'ns1:updateCustomerPaymentMethod' do |soap| + build_token soap, options + build_customer_payment_methods soap, options + build_tag soap, :boolean, 'Verify', options[:verify] + end + end + + def build_delete_customer_payment_method(soap, options) + soap.tag! "ns1:deleteCustomerPaymentMethod" do |soap| + build_token soap, options + build_tag soap, :integer, 'Custnum', options[:customer_number] + build_tag soap, :integer, 'PaymentMethodID', options[:method_id] + end + end + + def build_run_customer_transaction(soap, options) + soap.tag! "ns1:runCustomerTransaction" do |soap| + build_token soap, options + build_tag soap, :integer, 'CustNum', options[:customer_number] + build_tag soap, :integer, 'PaymentMethodID', options[:method_id] || 0 + build_customer_transaction soap, options + end + end + + # Transactions ================================================== + + def build_run_transaction(soap, options) + soap.tag! 'ns1:runTransaction' do |soap| + build_token soap, options + build_transaction_request_object soap, options, 'Parameters' + end + end + + def build_run_sale(soap, options) + soap.tag! 'ns1:runSale' do |soap| + build_token soap, options + build_transaction_request_object soap, options + end + end + + def build_run_auth_only(soap, options) + soap.tag! 'ns1:runAuthOnly' do |soap| + build_token soap, options + build_transaction_request_object soap, options + end + end + + def build_run_credit(soap, options) + soap.tag! 'ns1:runCredit' do |soap| + build_token soap, options + build_transaction_request_object soap, options + end + end + + def build_run_check_sale(soap, options) + soap.tag! 'ns1:runCheckSale' do |soap| + build_token soap, options + build_transaction_request_object soap, options + end + end + + def build_run_check_credit(soap, options) + soap.tag! 'ns1:runCheckCredit' do |soap| + build_token soap, options + build_transaction_request_object soap, options + end + end + + def build_post_auth(soap, options) + soap.tag! 'ns1:postAuth' do |soap| + build_token soap, options + build_transaction_request_object soap, options + end + end + + def build_run_quick_sale(soap, options) + soap.tag! 'ns1:runQuickSale' do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + build_transaction_detail soap, options + build_tag soap, :boolean, 'AuthOnly', options[:authorize_only] || false + end + end + + def build_run_quick_credit(soap, options) + soap.tag! 'ns1:runQuickCredit' do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + build_transaction_detail soap, options + end + end + + def build_get_transaction(soap, options) + soap.tag! "ns1:getTransaction" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + end + end + + def build_get_transaction_status(soap, options) + soap.tag! "ns1:getTransactionStatus" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + end + end + + def build_get_transaction_custom(soap, options) + soap.tag! "ns1:getTransactionCustom" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + build_transaction_field_array soap, options + end + end + + def build_get_check_trace(soap, options) + soap.tag! "ns1:getCheckTrace" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + end + end + + def build_capture_transaction(soap, options) + soap.tag! "ns1:captureTransaction" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + build_tag soap, :double, 'RefNum', amount(options[:amount]) + end + end + + def build_void_transaction(soap, options) + soap.tag! "ns1:voidTransaction" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + end + end + + def build_refund_transaction(soap, options) + soap.tag! "ns1:refundTransaction" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + build_tag soap, :integer, 'Amount', amount(options[:amount]) + end + end + + def build_override_transaction(soap, options) + soap.tag! "ns1:overrideTransaction" do |soap| + build_token soap, options + build_tag soap, :integer, 'RefNum', options[:reference_number] + build_tag soap, :string, 'Reason', options[:reason] + end + end + + # Account ======================================================= + + def build_get_account_details(soap, options) + soap.tag! "ns1:getAccountDetails" do |soap| + build_token soap, options + end + end + + # Customer Helpers ============================================== + + def build_customer_data(soap, options) + soap.CustomerData 'xsi:type' => 'ns1:CustomerObject' do + CUSTOMER_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[k] + end + build_billing_address soap, options + build_customer_payments soap, options + build_custom_fields soap, options + end + end + + def build_customer_payments(soap, options) + if options[:payment_methods] + length = options[:payment_methods].length + soap.PaymentMethods 'SOAP-ENC:arrayType' => "ns1:PaymentMethod[#{length}]", + 'xsi:type' =>"ns1:PaymentMethodArray" do |soap| + build_customer_payment_methods soap, options + end + end + end + + def extract_methods_and_tag(options) + case + when options[:payment_method] && !options[:payment_methods] + payment_methods = [options[:payment_method]] + tag_name = 'PaymentMethod' + when options[:payment_methods] && !options[:payment_method] + payment_methods = options[:payment_methods] + tag_name = 'item' + else + payment_methods = [options] + tag_name = 'PaymentMethod' + end + [payment_methods, tag_name] + end + + def build_credit_card_or_check(soap, payment_method) + case + when payment_method[:method].kind_of?(ActiveMerchant::Billing::CreditCard) + build_tag soap, :string, 'CardNumber', payment_method[:method].number + build_tag soap, :string, 'CardExpiration', + "#{"%02d" % payment_method[:method].month}#{payment_method[:method].year}" + if options[:billing_address] + build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1] + build_tag soap, :string, 'AvsZip', options[:billing_address][:zip] + end + build_tag soap, :string, 'CardCode', payment_method[:method].verification_value + when payment_method[:method].kind_of?(ActiveMerchant::Billing::Check) + build_tag soap, :string, 'Account', payment_method[:method].number + build_tag soap, :string, 'Routing', payment_method[:method].routing_number + build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize + build_tag soap, :string, 'DriversLicense', options[:drivers_license] + build_tag soap, :string, 'DriversLicenseState', options[:drivers_license_state] + build_tag soap, :string, 'RecordType', options[:record_type] + end + end + + def build_customer_payment_methods(soap, options) + payment_methods, tag_name = extract_methods_and_tag(options) + payment_methods.each do |payment_method| + soap.tag! tag_name, 'xsi:type' => "ns1:PaymentMethod" do |soap| + build_tag soap, :integer, 'MethodID', payment_method[:method_id] + build_tag soap, :string, 'MethodType', payment_method[:type] + build_tag soap, :string, 'MethodName', payment_method[:name] + build_tag soap, :integer, 'SecondarySort', payment_method[:sort] + build_credit_card_or_check(soap, payment_method) + end + end + end + + def build_customer_transaction(soap, options) + soap.Parameters 'xsi:type' => "ns1:CustomerTransactionRequest" do |soap| + build_transaction_detail soap, options + CUSTOMER_TRANSACTION_REQUEST_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[k] + end + build_custom_fields soap, options + build_line_items soap, options + end + end + + # Transaction Helpers =========================================== + + def build_transaction_request_object(soap, options, name='Params') + soap.tag! name, 'xsi:type' => "ns1:TransactionRequestObject" do |soap| + TRANSACTION_REQUEST_OBJECT_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[k] + end + case + when options[:payment_method] == nil + when options[:payment_method].kind_of?(ActiveMerchant::Billing::CreditCard) + build_credit_card_data soap, options + when options[:payment_method].kind_of?(ActiveMerchant::Billing::Check) + build_check_data soap, options + else + raise ArgumentError, 'options[:payment_method] must be a CreditCard or Check' + end + build_transaction_detail soap, options + build_billing_address soap, options + build_shipping_address soap, options + build_recurring_billing soap, options + build_line_items soap, options + build_custom_fields soap, options + end + end + + def build_transaction_detail(soap, options) + soap.Details 'xsi:type' => "ns1:TransactionDetail" do |soap| + TRANSACTION_DETAIL_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[k] + end + TRANSACTION_DETAIL_MONEY_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], amount(options[k]) + end + end + end + + def build_credit_card_data(soap, options) + soap.CreditCardData 'xsi:type' => "ns1:CreditCardData" do |soap| + build_tag soap, :string, 'CardNumber', options[:payment_method].number + build_tag soap, :string, 'CardExpiration', + "#{"%02d" % options[:payment_method].month}#{options[:payment_method].year}" + if options[:billing_address] + build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1] + build_tag soap, :string, 'AvsZip', options[:billing_address][:zip] + end + build_tag soap, :string, 'CardCode', options[:payment_method].verification_value + build_tag soap, :boolean, 'CardPresent', options[:card_present] || false + CREDIT_CARD_DATA_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[k] + end + end + end + + def build_check_data(soap, options) + soap.CheckData 'xsi:type' => "ns1:CheckData" do |soap| + build_tag soap, :string, 'Account', options[:payment_method].account_number + build_tag soap, :string, 'Routing', options[:payment_method].routing_number + build_tag soap, :string, 'AccountType', options[:payment_method].account_type.capitalize + CHECK_DATA_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[k] + end + end + end + + def build_recurring_billing(soap, options) + if options[:recurring] + soap.RecurringBilling 'xsi:type' => "ns1:RecurringBilling" do |soap| + build_tag soap, :double, 'Amount', amount(options[:recurring][:amount]) + build_tag soap, :string, 'Next', options[:recurring][:next].strftime("%Y-%m-%d") if options[:recurring][:next] + build_tag soap, :string, 'Expire', options[:recurring][:expire].strftime("%Y-%m-%d") if options[:recurring][:expire] + RECURRING_BILLING_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[:recurring][k] + end + end + end + end + + def build_transaction_field_array(soap, options) + soap.Fields 'SOAP-ENC:arryType' => "xsd:string[#{options[:fields].length}]", 'xsi:type' => 'ns1:stringArray' do |soap| + options[:fields].each do |field| + build_tag soap, :string, 'item', field + end + end + end + + # General Helpers =============================================== + + def build_billing_address(soap, options) + if options[:billing_address] + if options[:billing_address][:name] + name = options[:billing_address][:name].split(nil,2) # divide name + options[:billing_address][:first_name], options[:billing_address][:last_name] = name[0], name[1] + end + soap.BillingAddress 'xsi:type' => "ns1:Address" do + ADDRESS_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[:billing_address][k] + end + end + end + end + + def build_shipping_address(soap, options) + if options[:shipping_address] + if options[:shipping_address][:name] + name = options[:shipping_address][:name].split(nil,2) # divide name + options[:shipping_address][:first_name], options[:shipping_address][:last_name] = name[0], name[1] + end + soap.ShippingAddress 'xsi:type' => "ns1:Address" do + ADDRESS_OPTIONS.each do |k,v| + build_tag soap, v[0], v[1], options[:shipping_address][k] + end + end + end + end + + def build_line_items(soap, options) # TODO + end + + def build_custom_fields(soap, options) # TODO + end + + # Request ======================================================= + + def commit(action, request) + url = test? ? test_url : live_url + + begin + soap = ssl_post(url, request, "Content-Type" => "text/xml") + rescue ActiveMerchant::ResponseError => error + soap = error.response.body + end + + response = build_response(action, soap) + end + + def build_response(action, soap) + response_params, success, message, authorization, avs, cvv = parse(action, soap) + + response_params.merge!('soap_response' => soap) if @options[:soap_response] + + response = Response.new( + success, message, response_params, + :test => test?, :authorization => authorization, + :avs_result => avs_from(avs), + :cvv_result => cvv + ) + end + + def avs_from(avs) + avs_params = { :code => avs } + avs_params.merge!(:message => AVS_CUSTOM_MESSAGES[avs]) if AVS_CUSTOM_MESSAGES.key?(avs) + avs_params + end + + def parse(action, soap) + xml = REXML::Document.new(soap) + root = REXML::XPath.first(xml, "//SOAP-ENV:Body") + response = root ? parse_element(root[0]) : { :response => soap } + + success, message, authorization, avs, cvv = false, FAILURE_MESSAGE, nil, nil, nil + + fault = (!response) || (response.length < 1) || response.has_key?('faultcode') + return [response, success, response['faultstring'], authorization, avs, cvv] if fault + + if response.respond_to?(:[]) && p = response["#{action}_return"] + if p.respond_to?(:key?) && p.key?('result_code') + success = p['result_code'] == 'A' ? true : false + authorization = p['ref_num'] + avs = AVS_RESULTS[p['avs_result_code']] + cvv = p['card_code_result_code'] + else + success = true + end + message = case action + when :get_customer_payment_methods + p['item'] + when :get_transaction_custom + p['item'].inject({}) { |map, field| map[field['field']] = field['value']; map } + else + p + end + elsif response.respond_to?(:[]) && p = response[:response] + message = p # when response is html + end + + [response, success, message, authorization, avs, cvv] + end + + def parse_element(node) + if node.has_elements? + response = {} + node.elements.each do |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 + end + else + response = node.text + end + + response + end + + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_transaction.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_transaction.rb new file mode 100644 index 000000000..8adfeb516 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/usa_epay_transaction.rb @@ -0,0 +1,201 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + + class UsaEpayTransactionGateway < Gateway + self.test_url = self.live_url = 'https://www.usaepay.com/gate.php' + + self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_countries = ['US'] + self.homepage_url = 'http://www.usaepay.com/' + self.display_name = 'USA ePay' + + TRANSACTIONS = { + :authorization => 'authonly', + :purchase => 'sale', + :capture => 'capture', + :refund => 'refund', + :void => 'void' + } + + def initialize(options = {}) + requires!(options, :login) + super + end + + def authorize(money, credit_card, options = {}) + post = {} + + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, credit_card, options) + add_customer_data(post, options) + + commit(:authorization, post) + end + + def purchase(money, credit_card, options = {}) + post = {} + + add_amount(post, money) + add_invoice(post, options) + add_credit_card(post, credit_card) + add_address(post, credit_card, options) + add_customer_data(post, options) + + commit(:purchase, post) + end + + def capture(money, authorization, options = {}) + post = { :refNum => authorization } + + add_amount(post, money) + commit(:capture, post) + end + + def refund(money, authorization, options = {}) + post = { :refNum => authorization } + + add_amount(post, money) + commit(:refund, post) + end + + def void(authorization, options = {}) + post = { :refNum => authorization } + commit(:void, post) + end + + private + + def add_amount(post, money) + post[:amount] = amount(money) + end + + def expdate(credit_card) + year = format(credit_card.year, :two_digits) + month = format(credit_card.month, :two_digits) + + "#{month}#{year}" + end + + def add_customer_data(post, options) + address = options[:billing_address] || options[:address] || {} + post[:street] = address[:address1] + post[:zip] = address[:zip] + + if options.has_key? :email + post[:custemail] = options[:email] + post[:custreceipt] = 'No' + end + + if options.has_key? :customer + post[:custid] = options[:customer] + end + + if options.has_key? :ip + post[:ip] = options[:ip] + end + end + + def add_address(post, credit_card, options) + billing_address = options[:billing_address] || options[:address] + + add_address_for_type(:billing, post, credit_card, billing_address) if billing_address + add_address_for_type(:shipping, post, credit_card, options[:shipping_address]) if options[:shipping_address] + end + + def add_address_for_type(type, post, credit_card, address) + prefix = address_key_prefix(type) + + post[address_key(prefix, 'fname')] = credit_card.first_name + post[address_key(prefix, 'lname')] = credit_card.last_name + post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank? + post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank? + post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank? + post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank? + post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank? + post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank? + post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank? + post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank? + end + + def address_key_prefix(type) + case type + when :shipping then 'ship' + when :billing then 'bill' + end + end + + def address_key(prefix, key) + "#{prefix}#{key}".to_sym + end + + def add_invoice(post, options) + post[:invoice] = options[:order_id] + post[:description] = options[:description] + end + + def add_credit_card(post, credit_card) + post[:card] = credit_card.number + post[:cvv2] = credit_card.verification_value if credit_card.verification_value? + post[:expir] = expdate(credit_card) + post[:name] = credit_card.name + end + + def parse(body) + fields = {} + for line in body.split('&') + key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + fields[key] = CGI.unescape(value.to_s) + end + + { + :status => fields['UMstatus'], + :auth_code => fields['UMauthCode'], + :ref_num => fields['UMrefNum'], + :batch => fields['UMbatch'], + :avs_result => fields['UMavsResult'], + :avs_result_code => fields['UMavsResultCode'], + :cvv2_result => fields['UMcvv2Result'], + :cvv2_result_code => fields['UMcvv2ResultCode'], + :vpas_result_code => fields['UMvpasResultCode'], + :result => fields['UMresult'], + :error => fields['UMerror'], + :error_code => fields['UMerrorcode'], + :acs_url => fields['UMacsurl'], + :payload => fields['UMpayload'] + }.delete_if{|k, v| v.nil?} + end + + def commit(action, parameters) + response = parse( ssl_post(self.live_url, post_data(action, parameters)) ) + + Response.new(response[:status] == 'Approved', message_from(response), response, + :test => test?, + :authorization => response[:ref_num], + :cvv_result => response[:cvv2_result_code], + :avs_result => { :code => response[:avs_result_code] } + ) + end + + def message_from(response) + if response[:status] == "Approved" + return 'Success' + else + return 'Unspecified error' if response[:error].blank? + return response[:error] + end + end + + def post_data(action, parameters = {}) + parameters[:command] = TRANSACTIONS[action] + parameters[:key] = @options[:login] + parameters[:software] = 'Active Merchant' + parameters[:testmode] = (@options[:test] ? 1 : 0) + + parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/verifi.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/verifi.rb new file mode 100644 index 000000000..c0ff6765c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/verifi.rb @@ -0,0 +1,232 @@ +require 'rexml/document' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class VerifiGateway < Gateway + class VerifiPostData < PostData + # Fields that will be sent even if they are blank + self.required_fields = [ :amount, :type, :ccnumber, :ccexp, :firstname, :lastname, + :company, :address1, :address2, :city, :state, :zip, :country, :phone ] + end + + self.live_url = self.test_url = 'https://secure.verifi.com/gw/api/transact.php' + + RESPONSE_CODE_MESSAGES = { + "100" => "Transaction was Approved", + "200" => "Transaction was Declined by Processor", + "201" => "Do Not Honor", + "202" => "Insufficient Funds", + "203" => "Over Limit", + "204" => "Transaction not allowed", + "220" => "Incorrect payment Data", + "221" => "No Such Card Issuer", + "222" => "No Card Number on file with Issuer", + "223" => "Expired Card", + "224" => "Invalid Expiration Date", + "225" => "Invalid Card Security Code", + "240" => "Call Issuer for Further Information", + "250" => "Pick Up Card", + "251" => "Lost Card", + "252" => "Stolen Card", + "253" => "Fraudulent Card", + "260" => "Declined With further Instructions Available (see response text)", + "261" => "Declined - Stop All Recurring Payments", + "262" => "Declined - Stop this Recurring Program", + "263" => "Declined - Update Cardholder Data Available", + "264" => "Declined - Retry in a few days", + "300" => "Transaction was Rejected by Gateway", + "400" => "Transaction Error Returned by Processor", + "410" => "Invalid Merchant Configuration", + "411" => "Merchant Account is Inactive", + "420" => "Communication Error", + "421" => "Communication Error with Issuer", + "430" => "Duplicate Transaction at Processor", + "440" => "Processor Format Error", + "441" => "Invalid Transaction Information", + "460" => "Processor Feature Not Available", + "461" => "Unsupported Card Type" + } + + SUCCESS = 1 + + TRANSACTIONS = { + :authorization => 'auth', + :purchase => 'sale', + :capture => 'capture', + :void => 'void', + :credit => 'credit', + :refund => 'refund' + } + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.verifi.com/' + self.display_name = 'Verifi' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, credit_card, options = {}) + sale_authorization_or_credit_template(:purchase, money, credit_card, options) + end + + def authorize(money, credit_card, options = {}) + sale_authorization_or_credit_template(:authorization, money, credit_card, options) + end + + def capture(money, authorization, options = {}) + capture_void_or_refund_template(:capture, money, authorization, options) + end + + def void(authorization, options = {}) + capture_void_or_refund_template(:void, 0, authorization, options) + end + + def credit(money, credit_card_or_authorization, options = {}) + if credit_card_or_authorization.is_a?(String) + deprecated CREDIT_DEPRECATION_MESSAGE + refund(money, credit_card_or_authorization, options) + else + sale_authorization_or_credit_template(:credit, money, credit_card_or_authorization, options) + end + end + + def refund(money, reference, options = {}) + capture_void_or_refund_template(:refund, money, reference, options) + end + + private + + def sale_authorization_or_credit_template(trx_type, money, credit_card, options = {}) + post = VerifiPostData.new + add_security_key_data(post, options, money) + add_credit_card(post, credit_card) + add_addresses(post, options) + add_customer_data(post, options) + add_invoice_data(post, options) + add_optional_data(post, options) + commit(trx_type, money, post) + end + + def capture_void_or_refund_template(trx_type, money, authorization, options) + post = VerifiPostData.new + post[:transactionid] = authorization + + commit(trx_type, money, post) + end + + def add_credit_card(post, credit_card) + post[:ccnumber] = credit_card.number + post[:ccexp] = expdate(credit_card) + post[:firstname] = credit_card.first_name + post[:lastname] = credit_card.last_name + post[:cvv] = credit_card.verification_value + end + + def expdate(credit_card) + year = sprintf("%.4i", credit_card.year) + month = sprintf("%.2i", credit_card.month) + + "#{month}#{year[-2..-1]}" + end + + def add_addresses(post, options) + if billing_address = options[:billing_address] || options[:address] + post[:company] = billing_address[:company] + post[:address1] = billing_address[:address1] + post[:address2] = billing_address[:address2] + post[:city] = billing_address[:city] + post[:state] = billing_address[:state] + post[:zip] = billing_address[:zip] + post[:country] = billing_address[:country] + post[:phone] = billing_address[:phone] + post[:fax] = billing_address[:fax] + end + + if shipping_address = options[:shipping_address] + post[:shipping_firstname] = shipping_address[:first_name] + post[:shipping_lastname] = shipping_address[:last_name] + post[:shipping_company] = shipping_address[:company] + post[:shipping_address1] = shipping_address[:address1] + post[:shipping_address2] = shipping_address[:address2] + post[:shipping_city] = shipping_address[:city] + post[:shipping_state] = shipping_address[:state] + post[:shipping_zip] = shipping_address[:zip] + post[:shipping_country] = shipping_address[:country] + post[:shipping_email] = shipping_address[:email] + end + end + + def add_customer_data(post, options) + post[:email] = options[:email] + post[:ipaddress] = options[:ip] + end + + def add_invoice_data(post, options) + post[:orderid] = options[:order_id] + post[:ponumber] = options[:invoice] + post[:orderdescription] = options[:description] + post[:tax] = options[:tax] + post[:shipping] = options[:shipping] + end + + def add_optional_data(post, options) + post[:billing_method] = options[:billing_method] + post[:website] = options[:website] + post[:descriptor] = options[:descriptor] + post[:descriptor_phone] = options[:descriptor_phone] + post[:cardholder_auth] = options[:cardholder_auth] + post[:cavv] = options[:cavv] + post[:xid] = options[:xid] + post[:customer_receipt] = options[:customer_receipt] + end + + def add_security_key_data(post, options, money) + # MD5(username|password|orderid|amount|time) + now = Time.now.to_i.to_s + md5 = Digest::MD5.new + md5 << @options[:login].to_s + "|" + md5 << @options[:password].to_s + "|" + md5 << options[:order_id].to_s + "|" + md5 << amount(money).to_s + "|" + md5 << now + post[:key] = md5.hexdigest + post[:time] = now + end + + def commit(trx_type, money, post) + post[:amount] = amount(money) + + response = parse( ssl_post(self.live_url, post_data(trx_type, post)) ) + + Response.new(response[:response].to_i == SUCCESS, message_from(response), response, + :test => test?, + :authorization => response[:transactionid], + :avs_result => { :code => response[:avsresponse] }, + :cvv_result => response[:cvvresponse] + ) + end + + def message_from(response) + response[:response_code_message] ? response[:response_code_message] : "" + end + + def parse(body) + results = {} + CGI.parse(body).each { |key, value| results[key.intern] = value[0] } + results[:response_code_message] = RESPONSE_CODE_MESSAGES[results[:response_code]] if results[:response_code] + results + end + + def post_data(trx_type, post) + post[:username] = @options[:login] + post[:password] = @options[:password] + post[:type] = TRANSACTIONS[trx_type] + + post.to_s + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/viaklix.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/viaklix.rb new file mode 100644 index 000000000..307933e65 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/viaklix.rb @@ -0,0 +1,189 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ViaklixGateway < Gateway + class_attribute :test_url, :live_url, :delimiter, :actions + + self.test_url = 'https://demo.viaklix.com/process.asp' + self.live_url = 'https://www.viaklix.com/process.asp' + self.delimiter = "\r\n" + + self.actions = { + :purchase => 'SALE', + :credit => 'CREDIT' + } + + APPROVED = '0' + + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.supported_countries = ['US'] + self.display_name = 'ViaKLIX' + self.homepage_url = 'http://viaklix.com' + + # Initialize the Gateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * <tt>:login</tt> -- Merchant ID + # * <tt>:password</tt> -- PIN + # * <tt>:user</tt> -- Specify a subuser of the account (optional) + # * <tt>:test => +true+ or +false+</tt> -- Force test transactions + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + # Make a purchase + def purchase(money, creditcard, options = {}) + form = {} + add_invoice(form, options) + add_creditcard(form, creditcard) + add_address(form, options) + add_customer_data(form, options) + add_test_mode(form, options) + commit(:purchase, money, form) + end + + # Make a credit to a card (Void can only be done from the virtual terminal) + # Viaklix does not support credits by reference. You must pass in the credit card + def credit(money, creditcard, options = {}) + if creditcard.is_a?(String) + raise ArgumentError, "Reference credits are not supported. Please supply the original credit card" + end + + form = {} + add_invoice(form, options) + add_creditcard(form, creditcard) + add_address(form, options) + add_customer_data(form, options) + add_test_mode(form, options) + commit(:credit, money, form) + end + + private + def add_test_mode(form, options) + form[:test_mode] = 'TRUE' if options[:test_mode] + end + + def add_customer_data(form, options) + form[:email] = options[:email].to_s.slice(0, 100) unless options[:email].blank? + form[:customer_code] = options[:customer].to_s.slice(0, 10) unless options[:customer].blank? + end + + def add_invoice(form,options) + form[:invoice_number] = (options[:order_id] || options[:invoice]).to_s.slice(0, 10) + form[:description] = options[:description].to_s.slice(0, 255) + end + + def add_address(form,options) + billing_address = options[:billing_address] || options[:address] + + if billing_address + form[:avs_address] = billing_address[:address1].to_s.slice(0, 30) + form[:address2] = billing_address[:address2].to_s.slice(0, 30) + form[:avs_zip] = billing_address[:zip].to_s.slice(0, 10) + form[:city] = billing_address[:city].to_s.slice(0, 30) + form[:state] = billing_address[:state].to_s.slice(0, 10) + form[:company] = billing_address[:company].to_s.slice(0, 50) + form[:phone] = billing_address[:phone].to_s.slice(0, 20) + form[:country] = billing_address[:country].to_s.slice(0, 50) + end + + if shipping_address = options[:shipping_address] + first_name, last_name = parse_first_and_last_name(shipping_address[:name]) + form[:ship_to_first_name] = first_name.to_s.slice(0, 20) + form[:ship_to_last_name] = last_name.to_s.slice(0, 30) + form[:ship_to_address] = shipping_address[:address1].to_s.slice(0, 30) + form[:ship_to_city] = shipping_address[:city].to_s.slice(0, 30) + form[:ship_to_state] = shipping_address[:state].to_s.slice(0, 10) + form[:ship_to_company] = shipping_address[:company].to_s.slice(0, 50) + form[:ship_to_country] = shipping_address[:country].to_s.slice(0, 50) + form[:ship_to_zip] = shipping_address[:zip].to_s.slice(0, 10) + end + end + + def parse_first_and_last_name(value) + name = value.to_s.split(' ') + + last_name = name.pop || '' + first_name = name.join(' ') + [ first_name, last_name ] + end + + def add_creditcard(form, creditcard) + form[:card_number] = creditcard.number + form[:exp_date] = expdate(creditcard) + + if creditcard.verification_value? + add_verification_value(form, creditcard) + end + + form[:first_name] = creditcard.first_name.to_s.slice(0, 20) + form[:last_name] = creditcard.last_name.to_s.slice(0, 30) + end + + def add_verification_value(form, creditcard) + form[:cvv2cvc2] = creditcard.verification_value + form[:cvv2] = 'present' + end + + def preamble + result = { + 'merchant_id' => @options[:login], + 'pin' => @options[:password], + 'show_form' => 'false', + 'result_format' => 'ASCII' + } + + result['user_id'] = @options[:user] unless @options[:user].blank? + result + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) + parameters[:transaction_type] = self.actions[action] + + response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)) ) + + Response.new(response['result'] == APPROVED, message_from(response), response, + :test => @options[:test] || test?, + :authorization => authorization_from(response), + :avs_result => { :code => response['avs_response'] }, + :cvv_result => response['cvv2_response'] + ) + end + + def authorization_from(response) + response['txn_id'] + end + + def message_from(response) + response['result_message'] + end + + def post_data(parameters) + result = preamble + result.merge!(parameters) + result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join("&") + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + "#{month}#{year[2..3]}" + end + + # Parse the response message + def parse(msg) + resp = {} + msg.split(self.delimiter).collect{|li| + key, value = li.split("=") + resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip + } + resp + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/vindicia.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/vindicia.rb new file mode 100644 index 000000000..1d091ef00 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/vindicia.rb @@ -0,0 +1,361 @@ +begin + require "vindicia-api" +rescue LoadError + raise "Could not load the vindicia-api gem. Use `gem install vindicia-api` to install it." +end + +require 'i18n/core_ext/string/interpolate' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + + # For more information on the Vindicia Gateway please visit their {website}[http://vindicia.com/] + # + # The login and password are not the username and password you use to + # login to the Vindicia Merchant Portal. + # + # ==== Recurring Billing + # + # AutoBills are an feature of Vindicia's API that allows for creating and managing subscriptions. + # + # For more information about Vindicia's API and various other services visit their {Resource Center}[http://www.vindicia.com/resources/index.html] + class VindiciaGateway < Gateway + self.supported_countries = %w{US CA GB AU MX BR DE KR CN HK} + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://www.vindicia.com/' + self.display_name = 'Vindicia' + + class_attribute :test_url, :live_url + + self.test_url = "https://soap.prodtest.sj.vindicia.com/soap.pl" + self.live_url = "http://soap.vindicia.com/soap.pl" + + # Creates a new VindiciaGateway + # + # The gateway requires that a valid login and password be passed + # in the +options+ hash. + # + # ==== Options + # + # * <tt>:login</tt> -- Vindicia SOAP login (REQUIRED) + # * <tt>:password</tt> -- Vindicia SOAP password (REQUIRED) + # * <tt>:api_version</tt> -- Vindicia API Version - defaults to 3.6 (OPTIONAL) + # * <tt>:account_id</tt> -- Account Id which all transactions will be run against. (REQUIRED) + # * <tt>:transaction_prefix</tt> -- Prefix to order id for one-time transactions - defaults to 'X' (OPTIONAL + # * <tt>:min_chargeback_probability</tt> -- Minimum score for chargebacks - defaults to 65 (OPTIONAL) + # * <tt>:cvn_success</tt> -- Array of valid CVN Check return values - defaults to [M, P] (OPTIONAL) + # * <tt>:avs_success</tt> -- Array of valid AVS Check return values - defaults to [X, Y, A, W, Z] (OPTIONAL) + def initialize(options = {}) + requires!(options, :login, :password) + super + + config = lambda do |config| + config.login = options[:login] + config.password = options[:password] + config.api_version = options[:api_version] || "3.6" + config.endpoint = test? ? self.test_url : self.live_url + config.namespace = "http://soap.vindicia.com" + end + + if Vindicia.config.is_configured? + config.call(Vindicia.config) + else + Vindicia.configure(&config) + end + + requires!(options, :account_id) + @account_id = options[:account_id] + + @transaction_prefix = options[:transaction_prefix] || "X" + + @min_chargeback_probability = options[:min_chargeback_probability] || 65 + @cvn_success = options[:cvn_success] || %w{M P} + @avs_success = options[:avs_success] || %w{X Y A W Z} + + @allowed_authorization_statuses = %w{Authorized} + 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>creditcard</tt> -- The CreditCard details for the transaction. + # * <tt>options</tt> -- A hash of optional parameters. + def purchase(money, creditcard, options = {}) + response = authorize(money, creditcard, options) + return response if !response.success? || response.fraud_review? + + capture(money, response.authorization, options) + 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>creditcard</tt> -- The CreditCard details for the transaction. + # * <tt>options</tt> -- A hash of optional parameters. + def authorize(money, creditcard, options = {}) + vindicia_transaction = authorize_transaction(money, creditcard, options) + response = check_transaction(vindicia_transaction) + + # if this response is under fraud review because of our AVS/CVV checks void the transaction + if !response.success? && response.fraud_review? && !response.authorization.blank? + void_response = void([vindicia_transaction[:transaction][:merchantTransactionId]], options) + if void_response.success? + return response + else + return void_response + end + end + + response + end + + # Captures the funds from an authorized transaction. + # + # ==== Parameters + # + # * <tt>money</tt> -- The amount to be captured as an Integer value in cents. + # * <tt>identification</tt> -- The authorization returned from the previous authorize request. + def capture(money, identification, options = {}) + response = post(Vindicia::Transaction.capture({ + :transactions => [{ :merchantTransactionId => identification }] + })) + + if response[:return][:returnCode] != '200' || response[:qtyFail].to_i > 0 + return fail(response) + end + + success(response, identification) + end + + # Void a previous transaction + # + # ==== Parameters + # + # * <tt>identification</tt> - The authorization returned from the previous authorize request. + # * <tt>options</tt> - Extra options (currently only :ip used) + def void(identification, options = {}) + response = post(Vindicia::Transaction.cancel({ + :transactions => [{ + :account => { :merchantAccountId => @account_id }, + :merchantTransactionId => identification, + :sourceIp => options[:ip] + }] + })) + + if response[:return][:returnCode] == '200' && response[:qtyFail].to_i == 0 + success(response, identification) + else + fail(response) + end + end + + # Perform a recurring billing, which is essentially a purchase and autobill setup in a single operation. + # + # ==== Parameters + # + # * <tt>money</tt> -- The amount to be purchased as an Integer value in cents. + # * <tt>creditcard</tt> -- The CreditCard details for the transaction. + # * <tt>options</tt> -- A hash of parameters. + # + # ==== Options + # + # * <tt>:product_sku</tt> -- The subscription product's sku + # * <tt>:autobill_prefix</tt> -- Prefix to order id for subscriptions - defaults to 'A' (OPTIONAL) + def recurring(money, creditcard, options={}) + options[:recurring] = true + @autobill_prefix = options[:autobill_prefix] || "A" + + response = authorize(money, creditcard, options) + return response if !response.success? || response.fraud_review? + + capture_resp = capture(money, response.authorization, options) + return capture_resp if !response.success? + + # Setting up a recurring AutoBill requires an associated product + requires!(options, :product_sku) + autobill_response = check_subscription(authorize_subscription(options.merge(:product_sku => options[:product_sku]))) + + if autobill_response.success? + autobill_response + else + # If the AutoBill fails to set-up, void the transaction and return it as the response + void_response = void(capture_resp.authorization, options) + if void_response.success? + return autobill_response + else + return void_response + end + end + end + + protected + + def post(body) + parse(ssl_post(Vindicia.config.endpoint, body, "Content-Type" => "text/xml")) + end + + def parse(response) + # Vindicia always returns in the form of request_type_response => { actual_response } + Hash.from_xml(response)["Envelope"]["Body"].values.first.with_indifferent_access + end + + def check_transaction(vindicia_transaction) + if vindicia_transaction[:return][:returnCode] == '200' + status_log = vindicia_transaction[:transaction][:statusLog].first + if status_log[:creditCardStatus] + avs = status_log[:creditCardStatus][:avsCode] + cvn = status_log[:creditCardStatus][:cvnCode] + end + + if @allowed_authorization_statuses.include?(status_log[:status]) && + check_cvn(cvn) && check_avs(avs) + + success(vindicia_transaction, + vindicia_transaction[:transaction][:merchantTransactionId], + avs, cvn) + else + # If the transaction is authorized, but it didn't pass our AVS/CVV checks send the authorization along so + # that is gets voided. Otherwise, send no authorization. + fail(vindicia_transaction, avs, cvn, false, + @allowed_authorization_statuses.include?(status_log[:status]) ? vindicia_transaction[:transaction][:merchantTransactionId] : "") + end + else + # 406 = Chargeback risk score is higher than minChargebackProbability, transaction not authorized. + fail(vindicia_transaction, nil, nil, vindicia_transaction[:return][:return_code] == '406') + end + end + + def authorize_transaction(money, creditcard, options) + parameters = { + :amount => amount(money), + :currency => options[:currency] || currency(money) + } + + add_account_data(parameters, options) + add_customer_data(parameters, options) + add_payment_source(parameters, creditcard, options) + + post(Vindicia::Transaction.auth({ + :transaction => parameters, + :minChargebackProbability => @min_chargeback_probability + })) + end + + def add_account_data(parameters, options) + parameters[:account] = { :merchantAccountId => @account_id } + parameters[:sourceIp] = options[:ip] if options[:ip] + end + + def add_customer_data(parameters, options) + parameters[:merchantTransactionId] = transaction_id(options[:order_id]) + parameters[:shippingAddress] = convert_am_address_to_vindicia(options[:shipping_address]) + + # Transaction items must be provided for tax purposes + requires!(options, :line_items) + parameters[:transactionItems] = options[:line_items] + + if options[:recurring] + parameters[:nameValues] = [{:name => 'merchantAutoBillIdentifier', :value => autobill_id(options[:order_id])}] + end + end + + def add_payment_source(parameters, creditcard, options) + parameters[:sourcePaymentMethod] = { + :type => 'CreditCard', + :creditCard => { :account => creditcard.number, :expirationDate => "%4d%02d" % [creditcard.year, creditcard.month] }, + :accountHolderName => creditcard.name, + :nameValues => [{ :name => 'CVN', :value => creditcard.verification_value }], + :billingAddress => convert_am_address_to_vindicia(options[:billing_address] || options[:address]), + :customerSpecifiedType => creditcard.brand.capitalize, + :active => !!options[:recurring] + } + end + + def authorize_subscription(options) + parameters = {} + + add_account_data(parameters, options) + add_subscription_information(parameters, options) + + post(Vindicia::AutoBill.update({ + :autobill => parameters, + :validatePaymentMethod => false, + :minChargebackProbability => 100 + })) + end + + def check_subscription(vindicia_transaction) + if vindicia_transaction[:return][:returnCode] == '200' + if vindicia_transaction[:autobill] && vindicia_transaction[:autobill][:status] == "Active" + success(vindicia_transaction, + vindicia_transaction[:autobill][:merchantAutoBillId]) + else + fail(vindicia_transaction) + end + else + fail(vindicia_transaction) + end + end + + def add_subscription_information(parameters, options) + requires!(options, :product_sku) + + if options[:shipping_address] + parameters[:account][:shipping_address] = options[:shipping_address] + end + + parameters[:merchantAutoBillId] = autobill_id(options[:order_id]) + parameters[:product] = { :merchantProductId => options[:product_sku] } + end + + def check_avs(avs) + avs.blank? || @avs_success.include?(avs) + end + + def check_cvn(cvn) + cvn.blank? || @cvn_success.include?(cvn) + end + + def success(response, authorization, avs_code = nil, cvn_code = nil) + ActiveMerchant::Billing::Response.new(true, response[:return][:returnString], response, + { :fraud_review => false, :authorization => authorization, :test => test?, + :avs_result => { :code => avs_code }, :cvv_result => cvn_code }) + end + + def fail(response, avs_code = nil, cvn_code = nil, fraud_review = false, authorization = "") + ActiveMerchant::Billing::Response.new(false, response[:return][:returnString], response, + { :fraud_review => fraud_review || !authorization.blank?, + :authorization => authorization, :test => test?, + :avs_result => { :code => avs_code }, :cvv_result => cvn_code }) + + end + + def autobill_id(order_id) + "#{@autobill_prefix}#{order_id}" + end + + def transaction_id(order_id) + "#{@transaction_prefix}#{order_id}" + end + + # Converts valid ActiveMerchant address hash to proper Vindicia format + def convert_am_address_to_vindicia(address) + return if address.nil? + + convs = { :address1 => :addr1, :address2 => :addr2, + :state => :district, :zip => :postalCode } + + vindicia_address = {} + address.each do |key, val| + vindicia_address[convs[key] || key] = val + end + vindicia_address + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/webpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/webpay.rb new file mode 100644 index 000000000..f8730fc47 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/webpay.rb @@ -0,0 +1,53 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class WebpayGateway < StripeGateway + self.live_url = 'https://api.webpay.jp/v1/' + + self.supported_countries = ['JP'] + self.default_currency = 'JPY' + self.money_format = :cents + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club] + + self.homepage_url = 'https://webpay.jp/' + self.display_name = 'WebPay' + + def authorize(money, credit_card, options = {}) + raise NotImplementedError.new + end + + def capture(money, credit_card, options = {}) + raise NotImplementedError.new + end + + def json_error(raw_response) + msg = 'Invalid response received from the WebPay API. Please contact support@webpay.jp if you continue to receive this message.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { + "error" => { + "message" => msg + } + } + end + + def headers(meta={}) + @@ua ||= JSON.dump({ + :bindings_version => ActiveMerchant::VERSION, + :lang => 'ruby', + :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", + :platform => RUBY_PLATFORM, + :publisher => 'active_merchant', + :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil) + }) + + { + "Authorization" => "Basic " + Base64.encode64(@api_key.to_s + ":").strip, + "User-Agent" => "Webpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + "X-Webpay-Client-User-Agent" => @@ua, + "X-Webpay-Client-User-Metadata" => meta.to_json + } + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/wirecard.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/wirecard.rb new file mode 100644 index 000000000..2f1885280 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/wirecard.rb @@ -0,0 +1,313 @@ +require 'base64' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class WirecardGateway < Gateway + # Test server location + self.test_url = 'https://c3-test.wirecard.com/secure/ssl-gateway' + + # Live server location + self.live_url = 'https://c3.wirecard.com/secure/ssl-gateway' + + # The Namespaces are not really needed, because it just tells the System, that there's actually no namespace used. + # It's just specified here for completeness. + ENVELOPE_NAMESPACES = { + 'xmlns:xsi' => 'http://www.w3.org/1999/XMLSchema-instance', + 'xsi:noNamespaceSchemaLocation' => 'wirecard.xsd' + } + + PERMITTED_TRANSACTIONS = %w[ PREAUTHORIZATION CAPTURE PURCHASE ] + + RETURN_CODES = %w[ ACK NOK ] + + # Wirecard only allows phone numbers with a format like this: +xxx(yyy)zzz-zzzz-ppp, where: + # xxx = Country code + # yyy = Area or city code + # zzz-zzzz = Local number + # ppp = PBX extension + # For example, a typical U.S. or Canadian number would be "+1(202)555-1234-739" indicating PBX extension 739 at phone + # number 5551234 within area code 202 (country code 1). + VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/ + + # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_countries = ['DE'] + + # Wirecard supports all major credit and debit cards: + # Visa, Mastercard, American Express, Diners Club, + # JCB, Switch, VISA Carte Bancaire, Visa Electron and UATP cards. + # They also support the latest anti-fraud systems such as Verified by Visa or Master Secure Code. + self.supported_cardtypes = [ + :visa, :master, :american_express, :diners_club, :jcb, :switch + ] + + # The homepage URL of the gateway + self.homepage_url = 'http://www.wirecard.com' + + # The name of the gateway + self.display_name = 'Wirecard' + + # The currency should normally be EUROs + self.default_currency = 'EUR' + + # 100 is 1.00 Euro + self.money_format = :cents + + def initialize(options = {}) + # verify that username and password are supplied + requires!(options, :login, :password) + # unfortunately Wirecard also requires a BusinessCaseSignature in the XML request + requires!(options, :signature) + super + end + + # Authorization + def authorize(money, creditcard, options = {}) + options[:credit_card] = creditcard + commit(:preauthorization, money, options) + end + + # Capture Authorization + def capture(money, authorization, options = {}) + options[:preauthorization] = authorization + commit(:capture, money, options) + end + + # Purchase + def purchase(money, creditcard, options = {}) + options[:credit_card] = creditcard + commit(:purchase, money, options) + end + + private + + def prepare_options_hash(options) + result = @options.merge(options) + setup_address_hash!(result) + result + end + + # Create all address hash key value pairs so that + # it still works if only provided with one or two of them + def setup_address_hash!(options) + options[:billing_address] = options[:billing_address] || options[:address] || {} + options[:shipping_address] = options[:shipping_address] || {} + # Include Email in address-hash from options-hash + options[:billing_address][:email] = options[:email] if options[:email] + end + + # Contact WireCard, make the XML request, and parse the + # reply into a Response object + def commit(action, money, options) + request = build_request(action, money, options) + + headers = { 'Content-Type' => 'text/xml', + 'Authorization' => encoded_credentials } + + response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers)) + # Pending Status also means Acknowledged (as stated in their specification) + success = response[:FunctionResult] == "ACK" || response[:FunctionResult] == "PENDING" + message = response[:Message] + authorization = response[:GuWID] + + Response.new(success, message, response, + :test => test?, + :authorization => authorization, + :avs_result => { :code => response[:avsCode] }, + :cvv_result => response[:cvCode] + ) + rescue ResponseError => e + if e.response.code == "401" + return Response.new(false, "Invalid Login") + else + raise + end + end + + # Generates the complete xml-message, that gets sent to the gateway + def build_request(action, money, options) + options = prepare_options_hash(options) + options[:action] = action + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! + xml.tag! 'WIRECARD_BXML' do + xml.tag! 'W_REQUEST' do + xml.tag! 'W_JOB' do + xml.tag! 'JobID', '' + # UserID for this transaction + xml.tag! 'BusinessCaseSignature', options[:signature] || options[:login] + # Create the whole rest of the message + add_transaction_data(xml, money, options) + end + end + end + xml.target! + end + + # Includes the whole transaction data (payment, creditcard, address) + def add_transaction_data(xml, money, options) + options[:order_id] ||= generate_unique_id + + xml.tag! "FNC_CC_#{options[:action].to_s.upcase}" do + xml.tag! 'FunctionID', options[:description].to_s.slice(0,32) + xml.tag! 'CC_TRANSACTION' do + xml.tag! 'TransactionID', options[:order_id] + case options[:action] + when :preauthorization, :purchase + add_invoice(xml, money, options) + add_creditcard(xml, options[:credit_card]) + add_address(xml, options[:billing_address]) + when :capture + xml.tag! 'GuWID', options[:preauthorization] + add_amount(xml, money) + end + end + end + end + + # Includes the payment (amount, currency, country) to the transaction-xml + def add_invoice(xml, money, options) + add_amount(xml, money) + xml.tag! 'Currency', options[:currency] || currency(money) + xml.tag! 'CountryCode', options[:billing_address][:country] + xml.tag! 'RECURRING_TRANSACTION' do + xml.tag! 'Type', options[:recurring] || 'Single' + end + end + + # Include the amount in the transaction-xml + def add_amount(xml, money) + xml.tag! 'Amount', amount(money) + end + + # Includes the credit-card data to the transaction-xml + def add_creditcard(xml, creditcard) + raise "Creditcard must be supplied!" if creditcard.nil? + xml.tag! 'CREDIT_CARD_DATA' do + xml.tag! 'CreditCardNumber', creditcard.number + xml.tag! 'CVC2', creditcard.verification_value + xml.tag! 'ExpirationYear', creditcard.year + xml.tag! 'ExpirationMonth', format(creditcard.month, :two_digits) + xml.tag! 'CardHolderName', [creditcard.first_name, creditcard.last_name].join(' ') + end + end + + # Includes the IP address of the customer to the transaction-xml + def add_customer_data(xml, options) + return unless options[:ip] + xml.tag! 'CONTACT_DATA' do + xml.tag! 'IPAddress', options[:ip] + end + end + + # Includes the address to the transaction-xml + def add_address(xml, address) + return if address.nil? + xml.tag! 'CORPTRUSTCENTER_DATA' do + xml.tag! 'ADDRESS' do + xml.tag! 'Address1', address[:address1] + xml.tag! 'Address2', address[:address2] if address[:address2] + xml.tag! 'City', address[:city] + xml.tag! 'ZipCode', address[:zip] + + if address[:state] =~ /[A-Za-z]{2}/ && address[:country] =~ /^(us|ca)$/i + xml.tag! 'State', address[:state].upcase + end + + xml.tag! 'Country', address[:country] + xml.tag! 'Phone', address[:phone] if address[:phone] =~ VALID_PHONE_FORMAT + xml.tag! 'Email', address[:email] + end + end + end + + # Read the XML message from the gateway and check if it was successful, + # and also extract required return values from the response. + def parse(xml) + basepath = '/WIRECARD_BXML/W_RESPONSE' + response = {} + + xml = REXML::Document.new(xml) + if root = REXML::XPath.first(xml, "#{basepath}/W_JOB") + parse_response(response, root) + elsif root = REXML::XPath.first(xml, "//ERROR") + parse_error(response, root) + else + response[:Message] = "No valid XML response message received. \ + Propably wrong credentials supplied with HTTP header." + end + + response + end + + # Parse the <ProcessingStatus> Element which containts all important information + def parse_response(response, root) + status = nil + # get the root element for this Transaction + root.elements.to_a.each do |node| + if node.name =~ /FNC_CC_/ + status = REXML::XPath.first(node, "CC_TRANSACTION/PROCESSING_STATUS") + end + end + message = "" + if status + if info = status.elements['Info'] + message << info.text + end + # Get basic response information + status.elements.to_a.each do |node| + response[node.name.to_sym] = (node.text || '').strip + end + end + parse_error(root, message) + response[:Message] = message + end + + # Parse a generic error response from the gateway + def parse_error(root, message = "") + # Get errors if available and append them to the message + errors = errors_to_string(root) + unless errors.strip.blank? + message << ' - ' unless message.strip.blank? + message << errors + end + message + end + + # Parses all <ERROR> elements in the response and converts the information + # to a single string + def errors_to_string(root) + # Get context error messages (can be 0..*) + errors = [] + REXML::XPath.each(root, "//ERROR") do |error_elem| + error = {} + error[:Advice] = [] + error[:Message] = error_elem.elements['Message'].text + error_elem.elements.each('Advice') do |advice| + error[:Advice] << advice.text + end + errors << error + end + # Convert all messages to a single string + string = '' + errors.each do |error| + string << error[:Message] + error[:Advice].each_with_index do |advice, index| + string << ' (' if index == 0 + string << "#{index+1}. #{advice}" + string << ' and ' if index < error[:Advice].size - 1 + string << ')' if index == error[:Advice].size - 1 + end + end + string + end + + # Encode login and password in Base64 to supply as HTTP header + # (for http basic authentication) + def encoded_credentials + credentials = [@options[:login], @options[:password]].join(':') + "Basic " << Base64.encode64(credentials).strip + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/worldpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/worldpay.rb new file mode 100644 index 000000000..930cf1c8e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/gateways/worldpay.rb @@ -0,0 +1,302 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class WorldpayGateway < Gateway + self.test_url = 'https://secure-test.worldpay.com/jsp/merchant/xml/paymentService.jsp' + self.live_url = 'https://secure.worldpay.com/jsp/merchant/xml/paymentService.jsp' + + self.default_currency = 'GBP' + self.money_format = :cents + self.supported_countries = ['HK', 'US', 'GB', 'AU'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :laser] + self.homepage_url = 'http://www.worldpay.com/' + self.display_name = 'WorldPay' + + CARD_CODES = { + 'visa' => 'VISA-SSL', + 'master' => 'ECMC-SSL', + 'discover' => 'DISCOVER-SSL', + 'american_express' => 'AMEX-SSL', + 'jcb' => 'JCB-SSL', + 'maestro' => 'MAESTRO-SSL', + 'laser' => 'LASER-SSL' + } + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def purchase(money, payment_method, options = {}) + MultiResponse.run do |r| + r.process{authorize(money, payment_method, options)} + r.process{capture(money, r.authorization, options.merge(:authorization_validated => true))} + end + end + + def authorize(money, payment_method, options = {}) + requires!(options, :order_id) + authorize_request(money, payment_method, options) + end + + def capture(money, authorization, options = {}) + MultiResponse.run do |r| + r.process{inquire_request(authorization, options, "AUTHORISED")} unless options[:authorization_validated] + if r.params + authorization_currency = r.params['amount_currency_code'] + options = options.merge(:currency => authorization_currency) if authorization_currency.present? + end + r.process{capture_request(money, authorization, options)} + end + end + + def void(authorization, options = {}) + MultiResponse.run do |r| + r.process{inquire_request(authorization, options, "AUTHORISED")} + r.process{cancel_request(authorization, options)} + end + end + + def refund(money, authorization, options = {}) + MultiResponse.run do |r| + r.process{inquire_request(authorization, options, "CAPTURED", "SETTLED")} + r.process{refund_request(money, authorization, options)} + end + end + + private + + def authorize_request(money, payment_method, options) + commit('authorize', build_authorization_request(money, payment_method, options), "AUTHORISED") + end + + def capture_request(money, authorization, options) + commit('capture', build_capture_request(money, authorization, options), :ok) + end + + def cancel_request(authorization, options) + commit('cancel', build_void_request(authorization, options), :ok) + end + + def inquire_request(authorization, options, *success_criteria) + commit('inquiry', build_order_inquiry_request(authorization, options), *success_criteria) + end + + def refund_request(money, authorization, options) + commit('refund', build_refund_request(money, authorization, options), :ok) + end + + def build_request + xml = Builder::XmlMarkup.new :indent => 2 + xml.instruct! :xml, :encoding => 'ISO-8859-1' + xml.declare! :DOCTYPE, :paymentService, :PUBLIC, "-//WorldPay//DTD WorldPay PaymentService v1//EN", "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd" + xml.tag! 'paymentService', 'version' => "1.4", 'merchantCode' => @options[:login] do + yield xml + end + xml.target! + end + + def build_order_modify_request(authorization) + build_request do |xml| + xml.tag! 'modify' do + xml.tag! 'orderModification', 'orderCode' => authorization do + yield xml + end + end + end + end + + def build_order_inquiry_request(authorization, options) + build_request do |xml| + xml.tag! 'inquiry' do + xml.tag! 'orderInquiry', 'orderCode' => authorization + end + end + end + + def build_authorization_request(money, payment_method, options) + build_request do |xml| + xml.tag! 'submit' do + xml.tag! 'order', {'orderCode' => options[:order_id], 'installationId' => @options[:inst_id]}.reject{|_,v| !v} do + xml.description(options[:description].blank? ? "Purchase" : options[:description]) + add_amount(xml, money, options) + if options[:order_content] + xml.tag! 'orderContent' do + xml.cdata! options[:order_content] + end + end + add_payment_method(xml, money, payment_method, options) + end + end + end + end + + def build_capture_request(money, authorization, options) + build_order_modify_request(authorization) do |xml| + xml.tag! 'capture' do + time = Time.now + xml.tag! 'date', 'dayOfMonth' => time.day, 'month' => time.month, 'year'=> time.year + add_amount(xml, money, options) + end + end + end + + def build_void_request(authorization, options) + build_order_modify_request(authorization) do |xml| + xml.tag! 'cancel' + end + end + + def build_refund_request(money, authorization, options) + build_order_modify_request(authorization) do |xml| + xml.tag! 'refund' do + add_amount(xml, money, options.merge(:debit_credit_indicator => "credit")) + end + end + end + + def add_amount(xml, money, options) + currency = options[:currency] || currency(money) + amount = localized_amount(money, currency) + + amount_hash = { + :value => amount, + 'currencyCode' => currency, + 'exponent' => 2 + } + + if options[:debit_credit_indicator] + amount_hash.merge!('debitCreditIndicator' => options[:debit_credit_indicator]) + end + + xml.tag! 'amount', amount_hash + end + + def add_payment_method(xml, amount, payment_method, options) + if payment_method.is_a?(String) + xml.tag! 'payAsOrder', 'orderCode' => payment_method do + add_amount(xml, amount, options) + end + else + xml.tag! 'paymentDetails' do + xml.tag! CARD_CODES[card_brand(payment_method)] do + xml.tag! 'cardNumber', payment_method.number + xml.tag! 'expiryDate' do + xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits) + end + + xml.tag! 'cardHolderName', payment_method.name + xml.tag! 'cvc', payment_method.verification_value + + add_address(xml, 'cardAddress', (options[:billing_address] || options[:address])) + end + end + end + end + + def add_address(xml, element, address) + return if address.nil? + + xml.tag! element do + xml.tag! 'address' do + if m = /^\s*([^\s]+)\s+(.+)$/.match(address[:name]) + xml.tag! 'firstName', m[1] + xml.tag! 'lastName', m[2] + end + if m = /^\s*(\d+)\s+(.+)$/.match(address[:address1]) + xml.tag! 'street', m[2] + house_number = m[1] + else + xml.tag! 'street', address[:address1] + end + xml.tag! 'houseName', address[:address2] if address[:address2] + xml.tag! 'houseNumber', house_number if house_number.present? + xml.tag! 'postalCode', (address[:zip].present? ? address[:zip] : "0000") + xml.tag! 'city', address[:city] if address[:city] + xml.tag! 'state', (address[:state].present? ? address[:state] : 'N/A') + xml.tag! 'countryCode', address[:country] + xml.tag! 'telephoneNumber', address[:phone] if address[:phone] + end + end + end + + def parse(action, xml) + parse_element({:action => action}, REXML::Document.new(xml)) + end + + def parse_element(raw, node) + node.attributes.each do |k, v| + raw["#{node.name.underscore}_#{k.underscore}".to_sym] = v + end + if node.has_elements? + raw[node.name.underscore.to_sym] = true unless node.name.blank? + node.elements.each{|e| parse_element(raw, e) } + else + raw[node.name.underscore.to_sym] = node.text unless node.text.nil? + end + raw + end + + def commit(action, request, *success_criteria) + xmr = ssl_post(url, request, 'Content-Type' => 'text/xml', 'Authorization' => encoded_credentials) + raw = parse(action, xmr) + success, message = success_and_message_from(raw, success_criteria) + + Response.new( + success, + message, + raw, + :authorization => authorization_from(raw), + :test => test?) + + rescue ActiveMerchant::ResponseError => e + if e.response.code.to_s == "401" + return Response.new(false, "Invalid credentials", {}, :test => test?) + else + raise e + end + end + + def url + test? ? self.test_url : self.live_url + end + + # success_criteria can be: + # - a string or an array of strings (if one of many responses) + # - An array of strings if one of many responses could be considered a + # success. + def success_and_message_from(raw, success_criteria) + success = (success_criteria.include?(raw[:last_event]) || raw[:ok].present?) + if success + message = "SUCCESS" + else + message = (raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria)) + end + + [ success, message ] + end + + def required_status_message(raw, success_criteria) + if(!success_criteria.include?(raw[:last_event])) + "A transaction status of #{success_criteria.collect{|c| "'#{c}'"}.join(" or ")} is required." + end + end + + def authorization_from(raw) + pair = raw.detect{|k,v| k.to_s =~ /_order_code$/} + (pair ? pair.last : nil) + end + + def encoded_credentials + credentials = "#{@options[:login]}:#{@options[:password]}" + "Basic #{[credentials].pack('m').strip}" + end + + def localized_amount(money, currency) + amount = amount(money) + return amount unless CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) + + amount.to_i / 100 * 100 + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations.rb new file mode 100644 index 000000000..27d8bd220 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations.rb @@ -0,0 +1,17 @@ +module ActiveMerchant + module Billing + module Integrations + + Dir[File.dirname(__FILE__) + '/integrations/*.rb'].each do |f| + + # Get camelized class name + filename = File.basename(f, '.rb') + # Camelize the string to get the class name + gateway_class = filename.camelize.to_sym + + # Register for autoloading + autoload gateway_class, f + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator.rb new file mode 100644 index 000000000..9ac093aed --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator.rb @@ -0,0 +1,26 @@ +require File.dirname(__FILE__) + '/a1agregator/helper.rb' +require File.dirname(__FILE__) + '/a1agregator/notification.rb' +require File.dirname(__FILE__) + '/a1agregator/status.rb' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module A1agregator + + mattr_accessor :service_url + self.service_url = 'https://partner.a1agregator.ru/a1lite/input/' + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'check' + + def self.notification(*args) + Notification.new(*args) + end + + def self.status(login, password) + Status.new(login, password) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/helper.rb new file mode 100644 index 000000000..cddf1f853 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/helper.rb @@ -0,0 +1,31 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module A1agregator + class Helper < ActiveMerchant::Billing::Integrations::Helper + + # public key + mapping :account, 'key' + + mapping :amount, 'cost' + + mapping :order, 'order_id' + + mapping :customer, :email => 'email', + :phone => 'phone_number' + + # payment description + mapping :credential2, 'name' + + mapping :credential3, 'comment' + + # on error + # 1 - raise error + # 0 - redirect + mapping :credential4, 'verbose' + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/notification.rb new file mode 100644 index 000000000..d714f3d48 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/notification.rb @@ -0,0 +1,186 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module A1agregator + class Notification < ActiveMerchant::Billing::Integrations::Notification + + self.production_ips = [ + '78.108.178.206', + '79.137.235.129', + '95.163.96.79', + '212.24.38.100' + ] + + def initialize(*args) + super + guess_notification_type + end + + # Simple notification request params: + # tid + # name + # comment + # partner_id + # service_id + # order_id + # type + # partner_income + # system_income + + def complete? + true + end + + def transaction_id + params['tid'] + end + + def title + params['name'] + end + + def comment + params['comment'] + end + + def partner_id + params['partner_id'] + end + + def service_id + params['service_id'] + end + + def item_id + params['order_id'] + end + + def type + params['type'] + end + + def partner_income + params['partner_income'] + end + + def system_income + params['system_income'] + end + + # Additional notification request params: + # tid + # name + # comment + # partner_id + # service_id + # order_id + # type + # cost + # income_total + # income + # partner_income + # system_income + # command + # phone_number + # email + # resultStr + # date_created + # version + # check + + def inclome_total + params['income_total'] + end + + def income + params['income'] + end + + def partner_income + params['partner_income'] + end + + def system_income + params['system_income'] + end + + def command + params['command'] + end + + def phone_number + params['phone_number'] + end + + def payer_email + params['email'] + end + + def result_string + params['resultStr'] + end + + def received_at + params['date_created'] + end + + def version + params['version'] + end + + def security_key + params[A1agregator.signature_parameter_name].to_s.downcase + end + + # the money amount we received in X.2 decimal. + alias_method :gross, :system_income + + def currency + 'RUB' + end + + # Was this a test transaction? + def test? + params['test'] == '1' + end + + def simple_notification? + @notification_type == :simple + end + + def additional_notification? + @notification_type == :additional + end + + def acknowledge + security_key == signature + end + + private + + def signature + data = "#{params['tid']}\ +#{params['name']}\ +#{params['comment']}\ +#{params['partner_id']}\ +#{params['service_id']}\ +#{params['order_id']}\ +#{params['type']}\ +#{params['partner_income']}\ +#{params['system_income']}\ +#{params['test']}\ +#{@options[:secret]}" + Digest::MD5.hexdigest(data) + end + + def guess_notification_type + @notification_type = params['version'] ? :additional : :simple + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/status.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/status.rb new file mode 100644 index 000000000..264db72da --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/a1agregator/status.rb @@ -0,0 +1,38 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module A1agregator + + class Status + include PostsData + + STATUS_TEST_URL = 'https://partner.a1pay.ru/a1lite/info/' + + attr_accessor :login, :password + + def initialize(login, password) + @login, @password = login, password + end + + # agregator provides two methods: + # by tid - transaction id + # by order_id & service_id + def update(options = {}) + data = PostData.new + data[:user] = @login + data[:pass] = @password + if options[:tid] + data[:tid] = options[:tid] + else + data[:ord_id] = options[:ord_id] + data[:service_id] = options[:service_id] + end + + ssl_post(STATUS_TEST_URL, data.to_post_data) + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/action_view_helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/action_view_helper.rb new file mode 100644 index 000000000..7fed361e7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/action_view_helper.rb @@ -0,0 +1,73 @@ +require 'action_pack' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module ActionViewHelper + # This helper allows the usage of different payment integrations + # through a single form helper. Payment integrations are the + # type of service where the user is redirected to the secure + # site of the service, like Paypal or Chronopay. + # + # The helper creates a scope around a payment service helper + # which provides the specific mapping for that service. + # + # <% payment_service_for 1000, 'paypalemail@mystore.com', + # :amount => 50.00, + # :currency => 'CAD', + # :service => :paypal, + # :html => { :id => 'payment-form' } do |service| %> + # + # <% service.customer :first_name => 'Cody', + # :last_name => 'Fauser', + # :phone => '(555)555-5555', + # :email => 'cody@example.com' %> + # + # <% service.billing_address :city => 'Ottawa', + # :address1 => '21 Snowy Brook Lane', + # :address2 => 'Apt. 36', + # :state => 'ON', + # :country => 'CA', + # :zip => 'K1J1E5' %> + # + # <% service.invoice '#1000' %> + # <% service.shipping '0.00' %> + # <% service.tax '0.00' %> + # + # <% service.notify_url url_for(:only_path => false, :action => 'notify') %> + # <% service.return_url url_for(:only_path => false, :action => 'done') %> + # <% service.cancel_return_url 'http://mystore.com' %> + # <% end %> + # + def payment_service_for(order, account, options = {}, &proc) + raise ArgumentError, "Missing block" unless block_given? + + integration_module = ActiveMerchant::Billing::Integrations.const_get(options.delete(:service).to_s.camelize) + service_class = integration_module.const_get('Helper') + + form_options = options.delete(:html) || {} + service = service_class.new(order, account, options) + form_options[:method] = service.form_method + result = [] + result << form_tag(integration_module.service_url, form_options) + + result << capture(service, &proc) + + service.form_fields.each do |field, value| + result << hidden_field_tag(field, value) + end + + service.raw_html_fields.each do |field, value| + result << "<input id=\"#{field}\" name=\"#{field}\" type=\"hidden\" value=\"#{value}\" />\n" + end + + result << '</form>' + result= result.join("\n") + + concat(result.respond_to?(:html_safe) ? result.html_safe : result) + nil + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim.rb new file mode 100644 index 000000000..e4a56576e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim.rb @@ -0,0 +1,38 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module AuthorizeNetSim + autoload :Helper, 'active_merchant/billing/integrations/authorize_net_sim/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/authorize_net_sim/notification.rb' + + # Overwrite this if you want to change the ANS test url + mattr_accessor :test_url + self.test_url = 'https://test.authorize.net/gateway/transact.dll' + + # Overwrite this if you want to change the ANS production url + mattr_accessor :production_url + self.production_url = 'https://secure.authorize.net/gateway/transact.dll' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification(post) + Notification.new(post) + end + + def self.return(query_string) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb new file mode 100644 index 000000000..8709b31b0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb @@ -0,0 +1,228 @@ +require 'active_support/core_ext/float/rounding.rb' # Float#round(precision) + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module AuthorizeNetSim + # An example. Note the username as a parameter and transaction key you + # will want to use later. The amount that you pass in will be *rounded*, + # so preferably pass in X.2 decimal so that no rounding occurs. It is + # rounded because if it looks like 00.000 Authorize.Net fails the + # transaction as incorrectly formatted. + # + # payment_service_for('order_id', 'authorize_net_account', :service => :authorize_net_sim, :amount => 157.0) do |service| + # + # # You must call setup_hash and invoice + # + # service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', + # :order_timestamp => 1206836763 + # service.customer_id 8 + # service.customer :first_name => 'g', + # :last_name => 'g', + # :email => 'g@g.com', + # :phone => '3' + # service.billing_address :zip => 'g', + # :country => 'United States of America', + # :address => 'g' + # + # service.ship_to_address :first_name => 'g', + # :last_name => 'g', + # :city => '', + # :address => 'g', + # :address2 => '', + # :state => address.state, + # :country => 'United States of America', + # :zip => 'g' + # + # service.invoice "516428355" # your invoice number + # # The end-user is presented with the HTML produced by the notify_url. + # service.notify_url "http://t/authorize_net_sim/payment_received_notification_sub_step" + # service.payment_header 'My store name' + # service.add_line_item :name => 'item name', :quantity => 1, :unit_price => 0 + # service.test_request 'true' # only if it's just a test + # service.shipping '25.0' + # # Tell it to display a "0" line item for shipping, with the price in + # # the name, otherwise it isn't shown at all, leaving the end user to + # # wonder why the total is different than the sum of the line items. + # service.add_shipping_as_line_item + # server.add_tax_as_line_item # same with tax + # # See the helper.rb file for various custom fields + # end + + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :order, 'x_fp_sequence' + mapping :account, 'x_login' + + mapping :customer, :first_name => 'x_first_name', + :last_name => 'x_last_name', + :email => 'x_email', + :phone => 'x_phone' + + mapping :notify_url, 'x_relay_url' + mapping :return_url, '' # unused + mapping :cancel_return_url, '' # unused + + # Custom fields for Authorize.net SIM. + # See http://www.Authorize.Net/support/SIM_guide.pdf for more descriptions. + mapping :fax, 'x_fax' + mapping :customer_id, 'x_cust_id' + mapping :description, 'x_description' + mapping :tax, 'x_tax' + mapping :shipping, 'x_freight' + + # True or false, or 0 or 1 same effect [not required to send one, + # defaults to false]. + mapping :test_request, 'x_test_request' + + # This one is necessary for the notify url to be able to parse its + # information later! They also pass back customer id, if that's + # useful. + def invoice(number) + add_field 'x_invoice_num', number + end + + # Set the billing address. Call like service.billing_address {:city => + # 'provo, :state => 'UT'}... + def billing_address(options) + for setting in [:city, :state, :zip, :country, :po_num] do + add_field 'x_' + setting.to_s, options[setting] + end + raise 'must use address1 and address2' if options[:address] + add_field 'x_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip + end + + # Adds a custom field which you submit to Authorize.Net. These fields + # are all passed back to you verbatim when it does its relay + # (callback) to you note that if you call it twice with the same name, + # this function only uses keeps the second value you called it with. + def add_custom_field(name, value) + add_field name, value + end + + # Displays tax as a line item, so they can see it. Otherwise it isn't + # displayed. + def add_tax_as_line_item + raise unless @fields['x_tax'] + add_line_item :name => 'Total Tax', :quantity => 1, :unit_price => @fields['x_tax'], :tax => 0, :line_title => 'Tax' + end + + # Displays shipping as a line item, so they can see it. Otherwise it + # isn't displayed. + def add_shipping_as_line_item(extra_options = {}) + raise 'must set shipping/freight before calling this' unless @fields['x_freight'] + add_line_item extra_options.merge({:name => 'Shipping and Handling Cost', :quantity => 1, :unit_price => @fields['x_freight'], :line_title => 'Shipping'}) + end + + # Add ship_to_address in the same format as the normal address is + # added. + def ship_to_address(options) + for setting in [:first_name, :last_name, :company, :city, :state, :zip, :country] do + if options[setting] then + add_field 'x_ship_to_' + setting.to_s, options[setting] + end + end + raise 'must use :address1 and/or :address2' if options[:address] + add_field 'x_ship_to_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip + end + + # These control the look of the SIM payment page. Note that you can + # include a CSS header in descriptors, etc. + mapping :color_link, 'x_color_link' + mapping :color_text, 'x_color_text' + mapping :logo_url, 'x_logo_url' + mapping :background_url, 'x_background_url' # background image url for the page + mapping :payment_header, 'x_header_html_payment_form' + mapping :payment_footer, 'x_footer_html_payment_form' + + # For this to work you must have also passed in an email for the + # purchaser. + def yes_email_customer_from_authorizes_side + add_field 'x_email_customer', 'TRUE' + end + + # Add a line item to Authorize.Net. + # Call line add_line_item {:name => 'orange', :unit_price => 30, :tax_value => 'Y', :quantity => 3, } + # Note you can't pass in a negative unit price, and you can add an + # optional :line_title => 'special name' if you don't want it to say + # 'Item 1' or what not, the default coded here. + # Cannot have a negative price, nor a name with "'s or $ + # You can use the :line_title for the product name and then :name for description, if desired + def add_line_item(options) + raise 'needs name' unless options[:name] + + if @line_item_count == 30 + # Add a note that we are not showing at least one -- AN doesn't + # display more than 30 or so. + description_of_last = @raw_html_fields[-1][1] + # Pull off the second to last section, which is the description. + description_of_last =~ />([^>]*)<\|>[YN]$/ + # Create a new description, which can't be too big, so truncate here. + @raw_html_fields[-1][1] = description_of_last.gsub($1, $1[0..200] + ' + more unshown items after this one.') + end + + name = options[:name] + quantity = options[:quantity] || 1 + line_title = options[:line_title] || ('Item ' + (@line_item_count + 1).to_s) # left most field + unit_price = options[:unit_price] || 0 + unit_price = unit_price.to_f.round(2) + tax_value = options[:tax_value] || 'N' + + # Sanitization, in case they include a reserved word here, following + # their guidelines; unfortunately, they require 'raw' fields here, + # not CGI escaped, using their own delimiters. + # + # Authorize.net ignores the second field (sanitized_short_name) + raise 'illegal char for line item <|>' if name.include? '<|>' + raise 'illegal char for line item "' if name.include? '"' + raise 'cannot pass in dollar sign' if unit_price.to_s.include? '$' + raise 'must have positive or 0 unit price' if unit_price.to_f < 0 + # Using CGI::escape causes the output to be formated incorrectly in + # the HTML presented to the end-user's browser (e.g., spaces turn + # into +'s). + sanitized_short_name = name[0..30] + name = name[0..255] + + add_raw_html_field "x_line_item", "#{line_title}<|>#{sanitized_short_name}<|>#{name}<|>#{quantity}<|>#{unit_price}<|>#{tax_value}" + + @line_item_count += 1 + end + + # If you call this it will e-mail to this address a copy of a receipt + # after successful, from Authorize.Net. + def email_merchant_from_authorizes_side(to_this_email) + add_field 'x_email_merchant', to_this_email + end + + # You MUST call this at some point for it to actually work. Options + # must include :transaction_key and :order_timestamp + def setup_hash(options) + raise unless options[:transaction_key] + raise unless options[:order_timestamp] + amount = @fields['x_amount'] + data = "#{@fields['x_login']}^#{@fields['x_fp_sequence']}^#{options[:order_timestamp].to_i}^#{amount}^#{@fields['x_currency_code']}" + hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('md5'), options[:transaction_key], data) + add_field 'x_fp_hash', hmac + add_field 'x_fp_timestamp', options[:order_timestamp].to_i + end + + # Note that you should call #invoice and #setup_hash as well, for the + # response_url to actually work. + def initialize(order, account, options = {}) + super + raise 'missing parameter' unless order and account and options[:amount] + raise 'error -- amount with no digits!' unless options[:amount].to_s =~ /\d/ + add_field('x_type', 'AUTH_CAPTURE') # the only one we deal with, for now. Not refunds or anything else, currently. + add_field 'x_show_form', 'PAYMENT_FORM' + add_field 'x_relay_response', 'TRUE' + add_field 'x_duplicate_window', '28800' # large default duplicate window. + add_field 'x_currency_code', currency_code + add_field 'x_version' , '3.1' # version from doc + add_field 'x_amount', options[:amount].to_f.round(2) + @line_item_count = 0 + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb new file mode 100644 index 000000000..d2e36ab5c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb @@ -0,0 +1,340 @@ +require 'net/http' +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + # # Example: + # parser = AuthorizeNetSim::Notification.new(request.raw_post) + # passed = parser.complete? + # + # order = Order.find_by_order_number(parser.invoice_num) + # + # unless order + # @message = 'Error--unable to find your transaction! Please contact us directly.' + # return render :partial => 'authorize_net_sim_payment_response' + # end + # + # if order.total != parser.gross.to_f + # logger.error "Authorize.Net sim said they paid for #{parser.gross} and it should have been #{order.total}!" + # passed = false + # end + # + # # Theoretically, Authorize.net will *never* pass us the same transaction + # # ID twice, but we can double check that... by using + # # parser.transaction_id, and checking against previous orders' transaction + # # id's (which you can save when the order is completed).... + # unless parser.acknowledge MD5_HASH_SET_IN_AUTHORIZE_NET, AUTHORIZE_LOGIN + # passed = false + # logger.error "ALERT POSSIBLE FRAUD ATTEMPT either that or you haven't setup your md5 hash setting right in #{__FILE__} + # because a transaction came back from Authorize.Net with the wrong hash value--rejecting!" + # end + # + # unless parser.cavv_matches? and parser.avs_code_matches? + # logger.error 'Warning--non matching CC!' + params.inspect + # # Could fail them here, as well (recommended)... + # end + # + # if passed + # # Set up your session, and render something that will redirect them to + # # your site, most likely. + # else + # # Render failure or redirect them to your site where you will render failure + # end + + module AuthorizeNetSim + class Notification < ActiveMerchant::Billing::Integrations::Notification + + def unescape(val) #:nodoc: + if val + CGI::unescape val + else + val + end + end + + # Passes a hash of the address the user entered in at Authorize.Net + def billing_address + all = {} + [:fax, :city, :company, :last_name, :country, :zip, :first_name, :address, :email, :state].each do |key_out| + all[key_out] = unescape params['x_' + key_out.to_s] + end + all + end + + def customer_id + unescape params['x_cust_id'] + end + + def auth_code + unescape params['x_auth_code'] + end + + def po_num + unescape params['x_po_num'] + end + + def ship_to_address + all = {} + [:city, :last_name, :first_name, :country, :zip, :address].each do |key_out| + all[key_out] = unescape params['x_ship_to_' + key_out.to_s] + end + all + end + + # Tax amount we sent them. + def tax + unescape params['x_tax'] + end + + # Transaction type (probably going to be auth_capture, since that's + # all we set it as). + def transaction_type + unescape params['x_type'] + end + + # Payment method used--almost always CC (for credit card). + def method + unescape params['x_method'] + end + + # Ff our payment method is available. Almost always "true". + def method_available + params['x_method_available'] + end + + # Invoice num we passed in as invoice_num to them. + def invoice_num + item_id + end + + # If you pass any values to authorize that aren't its expected, it + # will pass them back to you verbatim, returned by this method. + # custom values: + def all_custom_values_passed_in_and_now_passed_back_to_us + all = {} + params.each do |key, value| + if key[0..1] != 'x_' + all[key] = unescape value + end + end + all + end + + def duty + unescape params['x_duty'] + end + + # Shipping we sent them. + def freight + unescape params['x_freight'] + end + alias_method :shipping, :freight + + def description + unescape params['x_description'] + end + + # Returns the response code as a symbol. + # {'1' => :approved, '2' => :declined, '3' => :error, '4' => :held_for_review} + def response_code_as_ruby_symbol + map = {'1' => :approved, '2' => :declined, '3' => :error, '4' => :held_for_review} + map[params['x_response_code']] + end + + def response_reason_text + unescape params['x_response_reason_text'] + end + + # The response reason text's numeric id [equivalent--just a number] + def response_reason_code + unescape params['x_response_reason_code'] + end + + # 'used internally by their gateway' + def response_subcode + params['x_response_subcode'] + end + + # They pass back a tax_exempt value. + def tax_exempt + params['x_tax_exempt'] + end + + # avs [address verification] code + # A = Address (Street) + # matches, ZIP does not + # B = Address information + # not provided for AVS + # check + # E = AVS error + # G = Non-U.S. Card Issuing + # Bank + # N = No Match on Address + # (Street) or ZIP + # P = AVS not applicable for + # this transaction + # R = Retry – System + # unavailable or timed out + # S = Service not supported + # by issuer + # U = Address information is + # unavailable + # W = Nine digit ZIP + # matches, Address (Street) + # does not + # X = Address (Street) and + # nine digit ZIP match + # Y = Address (Street) and + # five digit ZIP match + # Z = Five digit ZIP matches + # Address (Street) does not + def avs_code + params['x_avs_code'] + end + + # Returns true if their address completely matched [Y or X, P from + # #avs_code, which mean 'add+zip match', 'address + 9-zip match', and + # not applicable, respectively]. + def avs_code_matches? + return ['Y', 'X', 'P'].include? params['x_avs_code'] + end + + # cvv2 response + # M = Match + # N = No Match + # P = Not Processed + # S = Should have been + # present + # U = Issuer unable to + # process request + def cvv2_resp_code + params['x_cvv2_resp_code'] + end + + # check if #cvv2_resp_code == 'm' for Match. otherwise false + def cvv2_resp_code_matches? + return ['M'].include? cvv2_resp_code + end + + # cavv_response--'cardholder authentication verification response code'--most likely not use for SIM + # Blank or not present = + # CAVV not validated + # 0 = CAVV not validated + # because erroneous data + # was submitted + # 1 = CAVV failed validation + # 2 = CAVV passed + # validation + # 3 = CAVV validation could + # not be performed; issuer + # attempt incomplete + # 4 = CAVV validation could + # not be performed; issuer + # system error + # 5 = Reserved for future + # use + # 6 = Reserved for future + # use + # 7 = CAVV attempt – failed + # validation – issuer + # available (U.S.-issued + # card/non-U.S acquirer) + # 8 = CAVV attempt – + # passed validation – issuer + # available (U.S.-issued + # card/non-U.S. acquirer) + # 9 = CAVV attempt – failed + # validation – issuer + def cavv_response + params['x_cavv_response'] + end + + # Check if #cavv_response == '', '2', '8' one of those [non failing] + # [blank means no validated, 2 is passed, 8 is passed issuer + # available] + def cavv_matches? + ['','2','8'].include? cavv_response + end + + # Payment is complete -- returns true if x_response_code == '1' + def complete? + params["x_response_code"] == '1' + end + + # Alias for invoice number--this is the only id they pass back to us + # that we passed to them, except customer id is also passed back. + def item_id + unescape params['x_invoice_num'] + end + + # They return this number to us [it's unique to Authorize.net]. + def transaction_id + params['x_trans_id'] + end + + # When was this payment was received by the client. --unimplemented -- + # always returns nil + def received_at + nil + end + + # End-user's email + def payer_email + unescape params['x_email'] + end + + # They don't pass merchant email back to us -- unimplemented -- always + # returns nil + def receiver_email + nil + end + + # md5 hash used internally + def security_key + params['x_MD5_Hash'] + end + + # The money amount we received in X.2 decimal. Returns a string + def gross + unescape params['x_amount'] + end + + # Was this a test transaction? + def test? + params['x_test_request'] == 'true' + end + + # #method_available alias + def status + complete? + end + + # Called to request back and check if it was a valid request. + # Authorize.net passes us back a hash that includes a hash of our + # 'unique' MD5 value that we set within their system. + # + # Example: + # acknowledge('my secret md5 hash that I set within Authorize.Net', 'authorize_login') + # + # Note this is somewhat unsafe unless you actually set that md5 hash + # to something (defaults to '' in their system). + def acknowledge(md5_hash_set_in_authorize_net, authorize_net_login_name) + Digest::MD5.hexdigest(md5_hash_set_in_authorize_net + authorize_net_login_name + params['x_trans_id'] + gross) == params['x_MD5_Hash'].downcase + end + + private + + # Take the posted data and move the relevant data into a hash. + def parse(post) + @raw = post + post.split('&').each do |line| + key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + params[key] = value + end + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus.rb new file mode 100644 index 000000000..6de2b0cc8 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus.rb @@ -0,0 +1,23 @@ + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Bogus + autoload :Return, 'active_merchant/billing/integrations/bogus/return.rb' + autoload :Helper, 'active_merchant/billing/integrations/bogus/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/bogus/notification.rb' + + mattr_accessor :service_url + self.service_url = 'http://www.bogus.com' + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/helper.rb new file mode 100644 index 000000000..ffb5e2142 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/helper.rb @@ -0,0 +1,17 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Bogus + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :account, 'account' + mapping :order, 'order' + mapping :amount, 'amount' + mapping :currency, 'currency' + mapping :customer, :first_name => 'first_name', + :last_name => 'last_name' + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/notification.rb new file mode 100644 index 000000000..77630ffad --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/notification.rb @@ -0,0 +1,11 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Bogus + class Notification < ActiveMerchant::Billing::Integrations::Notification + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/return.rb new file mode 100644 index 000000000..d41ef8061 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/bogus/return.rb @@ -0,0 +1,10 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Bogus + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay.rb new file mode 100644 index 000000000..6137772ac --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay.rb @@ -0,0 +1,23 @@ + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Chronopay + autoload :Return, 'active_merchant/billing/integrations/chronopay/return.rb' + autoload :Helper, 'active_merchant/billing/integrations/chronopay/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/chronopay/notification.rb' + + mattr_accessor :service_url + self.service_url = 'https://secure.chronopay.com/index_shop.cgi' + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/helper.rb new file mode 100644 index 000000000..4f62465cb --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/helper.rb @@ -0,0 +1,120 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Chronopay + class Helper < ActiveMerchant::Billing::Integrations::Helper + # All currently supported checkout languages: + # es (Spanish) + # en (English) + # de (German) + # pt (Portuguese) + # lv (Latvian) + # cn1 (Chinese Version 1) + # cn2 (Chinese version 2) + # nl (Dutch) + # ru (Russian) + COUNTRIES_FOR_LANG = { + 'ES' => %w( AR BO CL CO CR CU DO EC SV GQ GT HN MX NI PA PY PE ES UY VE), + 'DE' => %w( DE AT CH LI ), + 'PT' => %w( AO BR CV GW MZ PT ST TL), + 'RU' => %w( BY KG KZ RU ), + 'LV' => %w( LV ), + 'CN1' => %w( CN ), + 'NL' => %w( NL ) + } + + LANG_FOR_COUNTRY = COUNTRIES_FOR_LANG.inject(Hash.new("EN")) do |memo, (lang, countries)| + countries.each do |code| + memo[code] = lang + end + memo + end + + + self.country_format = :alpha3 + + def initialize(order, account, options = {}) + super + add_field('cb_type', 'p') + end + + # product_id + mapping :account, 'product_id' + # product_name + mapping :invoice, 'product_name' + # product_price + mapping :amount, 'product_price' + # product_price_currency + mapping :currency, 'product_price_currency' + + # f_name + # s_name + # email + mapping :customer, :first_name => 'f_name', + :last_name => 's_name', + :phone => 'phone', + :email => 'email' + + # city + # street + # state + # zip + # country - The country must be a 3 digit country code + # phone + + mapping :billing_address, :city => 'city', + :address1 => 'street', + :state => 'state', + :zip => 'zip', + :country => 'country' + + def billing_address(mapping = {}) + # Gets the country code in the appropriate format or returns what we were given + # The appropriate format for Chronopay is the alpha 3 country code + country_code = lookup_country_code(mapping.delete(:country)) + add_field(mappings[:billing_address][:country], country_code) + + countries_with_supported_states = ['USA', 'CAN'] + if !countries_with_supported_states.include?(country_code) + mapping.delete(:state) + add_field(mappings[:billing_address][:state], 'XX') + end + mapping.each do |k, v| + field = mappings[:billing_address][k] + add_field(field, v) unless field.nil? + end + add_field('language', checkout_language_from_country(country_code)) + end + + # card_no + # exp_month + # exp_year + mapping :credit_card, :number => 'card_no', + :expiry_month => 'exp_month', + :expiry_year => 'exp_year' + + # cb_url + mapping :notify_url, 'cb_url' + + # cs1 + mapping :order, 'cs1' + + # cs2 + # cs3 + # decline_url + + + private + + def checkout_language_from_country(country_code) + country = Country.find(country_code) + short_code = country.code(:alpha2).to_s + LANG_FOR_COUNTRY[short_code] + rescue InvalidCountryCodeError + 'EN' + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/notification.rb new file mode 100644 index 000000000..4942c50a1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/notification.rb @@ -0,0 +1,158 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Chronopay + class Notification < ActiveMerchant::Billing::Integrations::Notification + def complete? + status == 'Completed' + end + + # Status of transaction. List of possible values: + # <tt>onetime – one time payment has been made, no repayment required;</tt>:: + # <tt>initial – first payment has been made, repayment required in corresponding period;</tt>:: + # <tt>decline – charge request has been rejected;</tt>:: + # <tt>rebill – repayment has been made together with initial transaction;</ttt>:: + # <tt>cancel – repayments has been disabled;</tt>:: + # <tt>expire – customer’s access to restricted zone membership has been expired;</tt>:: + # <tt>refund – request to refund has been received;</tt>:: + # <tt>chargeback – request to chargeback has been received.</tt>:: + # + # This implementation of Chronopay does not support subscriptions. + # The status codes used are matched to the status codes that Paypal + # sends. See Paypal::Notification#status for more details + def status + case params['transaction_type'] + when 'onetime' + 'Completed' + when 'refund' + 'Refunded' + when 'chargeback' + 'Reversed' + else + 'Failed' + end + end + + # Unique ID of transaction + def transaction_id + params['transaction_id'] + end + + # Unique ID of customer + def customer_id + params['customer_id'] + end + + # Unique ID of Merchant’s web-site + def site_id + params['site_id'] + end + + # ID of a product that was purchased + def product_id + params['product_id'] + end + + # Language + def language + params['language'] + end + + def received_at + # Date should be formatted "dd-mm-yy" to be parsed by 1.8 and 1.9 the same way + formatted_date = Date.strptime(date, "%m/%d/%Y").strftime("%d-%m-%Y") + Time.parse("#{formatted_date} #{time}") unless date.blank? || time.blank? + end + + # Date of transaction in MM/DD/YYYY format + def date + params['date'] + end + + # Time of transaction in HH:MM:SS format + def time + params['time'] + end + + # The customer's full name + def name + params['name'] + end + + # The customer's email address + def email + params['email'] + end + + # The customer's street address + def street + params['street'] + end + + # The customer's country - 3 digit country code + def country + params['country'] + end + + # The customer's city + def city + params['city'] + end + + # The customer's zip + def zip + params['zip'] + end + + # The customer's state. Only useful for US Customers + def state + params['state'] + end + + # Customer’s login for restricted access zone of Merchant’s Web-site + def username + params['username'] + end + + # Customer's password for restricted access zone of Merchant’s Web-site, as chosen + def password + params['password'] + end + + # The item id passed in the first custom parameter + def item_id + params['cs1'] + end + + # Additional parameter + def custom2 + params['cs2'] + end + + # Additional parameter + def custom3 + params['cs3'] + end + + # The currency the purchase was made in + def currency + params['currency'] + end + + # the money amount we received in X.2 decimal. + def gross + params['total'] + end + + def test? + date.blank? && time.blank? && transaction_id.blank? + end + + def acknowledge + true + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/return.rb new file mode 100644 index 000000000..399b258b3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/chronopay/return.rb @@ -0,0 +1,10 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Chronopay + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay.rb new file mode 100644 index 000000000..62e3956ff --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay.rb @@ -0,0 +1,41 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module DirecPay + autoload :Helper, File.dirname(__FILE__) + '/direc_pay/helper.rb' + autoload :Return, File.dirname(__FILE__) + '/direc_pay/return.rb' + autoload :Notification, File.dirname(__FILE__) + '/direc_pay/notification.rb' + autoload :Status, File.dirname(__FILE__) + '/direc_pay/status.rb' + + mattr_accessor :production_url, :test_url + + self.production_url = "https://www.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp" + self.test_url = "https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp" + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string, options) + end + + def self.request_status_update(mid, transaction_id, notification_url) + Status.new(mid).update(transaction_id, notification_url) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/helper.rb new file mode 100644 index 000000000..dddbfd9a3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/helper.rb @@ -0,0 +1,200 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module DirecPay + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :account, 'MID' + mapping :order, 'Merchant Order No' + mapping :amount, 'Amount' + mapping :currency, 'Currency' + mapping :country, 'Country' + + mapping :billing_address, :city => 'custCity', + :address1 => 'custAddress', + :state => 'custState', + :zip => 'custPinCode', + :country => 'custCountry', + :phone => 'custMobileNo' + + mapping :shipping_address, :name => 'deliveryName', + :city => 'deliveryCity', + :address1 => 'deliveryAddress', + :state => 'deliveryState', + :zip => 'deliveryPinCode', + :country => 'deliveryCountry', + :phone => 'deliveryMobileNo' + + mapping :customer, :name => 'custName', + :email => 'custEmailId' + + mapping :description, 'otherNotes' + mapping :edit_allowed, 'editAllowed' + + mapping :return_url, 'Success URL' + mapping :failure_url, 'Failure URL' + + mapping :operating_mode, 'Operating Mode' + mapping :other_details, 'Other Details' + mapping :collaborator, 'Collaborator' + + OPERATING_MODE = 'DOM' + COUNTRY = 'IND' + CURRENCY = 'INR' + OTHER_DETAILS = 'NULL' + EDIT_ALLOWED = 'Y' + + PHONE_CODES = { + 'IN' => '91', + 'US' => '01', + 'CA' => '01' + } + + ENCODED_PARAMS = [ :account, :operating_mode, :country, :currency, :amount, :order, :other_details, :return_url, :failure_url, :collaborator ] + + + def initialize(order, account, options = {}) + super + collaborator = ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] ? 'TOML' : 'DirecPay' + add_field(mappings[:collaborator], collaborator) + add_field(mappings[:country], 'IND') + add_field(mappings[:operating_mode], OPERATING_MODE) + add_field(mappings[:other_details], OTHER_DETAILS) + add_field(mappings[:edit_allowed], EDIT_ALLOWED) + end + + + def customer(params = {}) + add_field(mappings[:customer][:name], full_name(params)) + add_field(mappings[:customer][:email], params[:email]) + end + + # Need to format the amount to have 2 decimal places + def amount=(money) + cents = money.respond_to?(:cents) ? money.cents : money + if money.is_a?(String) or cents.to_i <= 0 + raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' + end + add_field(mappings[:amount], sprintf("%.2f", cents.to_f/100)) + end + + def shipping_address(params = {}) + super(update_address(:shipping_address, params)) + end + + def billing_address(params = {}) + super(update_address(:billing_address, params)) + end + + def form_fields + add_failure_url + add_request_parameters + + unencoded_parameters + end + + + private + + def add_request_parameters + params = ENCODED_PARAMS.map{ |param| fields[mappings[param]] } + encoded = encode_value(params.join('|')) + + add_field('requestparameter', encoded) + end + + def unencoded_parameters + params = fields.dup + # remove all encoded params from exported fields + ENCODED_PARAMS.each{ |param| params.delete(mappings[param]) } + # remove all special characters from each field value + params = params.collect{|name, value| [name, remove_special_characters(value)] } + Hash[params] + end + + def add_failure_url + if fields[mappings[:failure_url]].nil? + add_field(mappings[:failure_url], fields[mappings[:return_url]]) + end + end + + def update_address(address_type, params) + params = params.dup + address = params[:address1] + address = "#{address} #{params[:address2]}" if params[:address2].present? + address = "#{params[:company]} #{address}" if params[:company].present? + params[:address1] = address + + params[:phone] = normalize_phone_number(params[:phone]) + add_land_line_phone_for(address_type, params) + + if address_type == :shipping_address + shipping_name = full_name(params) || fields[mappings[:customer][:name]] + add_field(mappings[:shipping_address][:name], shipping_name) + end + params + end + + # Split a single phone number into the country code, area code and local number as best as possible + def add_land_line_phone_for(address_type, params) + address_field = address_type == :billing_address ? 'custPhoneNo' : 'deliveryPhNo' + + if params.has_key?(:phone2) + phone = normalize_phone_number(params[:phone2]) + phone_country_code, phone_area_code, phone_number = nil + + if params[:country] == 'IN' && phone =~ /(91)? *(\d{3}) *(\d{4,})$/ + phone_country_code, phone_area_code, phone_number = $1, $2, $3 + else + numbers = phone.split(' ') + case numbers.size + when 3 + phone_country_code, phone_area_code, phone_number = numbers + when 2 + phone_area_code, phone_number = numbers + else + phone =~ /(\d{3})(\d+)$/ + phone_area_code, phone_number = $1, $2 + end + end + + add_field("#{address_field}1", phone_country_code || phone_code_for_country(params[:country]) || '91') + add_field("#{address_field}2", phone_area_code) + add_field("#{address_field}3", phone_number) + end + end + + def normalize_phone_number(phone) + phone.gsub(/[^\d ]+/, '') if phone + end + + # Special characters are NOT allowed while posting transaction parameters on DirecPay system + def remove_special_characters(string) + string.gsub(/[~"'&#%]/, '-') + end + + def encode_value(value) + encoded = Base64.strict_encode64(value) + string_to_encode = encoded[0, 1] + "T" + encoded[1, encoded.length] + Base64.strict_encode64(string_to_encode) + end + + def decode_value(value) + decoded = Base64.decode64(value) + string_to_decode = decoded[0, 1] + decoded[2, decoded.length] + Base64.decode64(string_to_decode) + end + + def phone_code_for_country(country) + PHONE_CODES[country] + end + + def full_name(params) + return if params[:name].blank? && params[:first_name].blank? && params[:last_name].blank? + + params[:name] || "#{params[:first_name]} #{params[:last_name]}" + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/notification.rb new file mode 100644 index 000000000..bf37e9ad1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/notification.rb @@ -0,0 +1,76 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module DirecPay + class Notification < ActiveMerchant::Billing::Integrations::Notification + RESPONSE_PARAMS = ['DirecPay Reference ID', 'Flag', 'Country', 'Currency', 'Other Details', 'Merchant Order No', 'Amount'] + + def acknowledge + true + end + + def complete? + status == 'Completed' || status == 'Pending' + end + + def status + case params['Flag'] + when 'SUCCESS' + 'Completed' + when 'PENDING' + 'Pending' + when 'FAIL' + 'Failed' + else + 'Error' + end + end + + def item_id + params['Merchant Order No'] + end + + def transaction_id + params['DirecPay Reference ID'] + end + + # the money amount we received in X.2 decimal + def gross + params['Amount'] + end + + def currency + params['Currency'] + end + + def country + params['Country'] + end + + def other_details + params['Other Details'] + end + + def test? + false + end + + # Take the posted data and move the relevant data into a hash + def parse(post) + super + + values = params['responseparams'].to_s.split('|') + response_params = values.size == 3 ? ['DirecPay Reference ID', 'Flag', 'Error message'] : RESPONSE_PARAMS + response_params.each_with_index do |name, index| + params[name] = values[index] + end + params + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/return.rb new file mode 100644 index 000000000..2eb2497da --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/return.rb @@ -0,0 +1,32 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + module DirecPay + class Return < ActiveMerchant::Billing::Integrations::Return + + def initialize(post_data, options = {}) + @notification = Notification.new(treat_failure_as_pending(post_data), options) + end + + def success? + notification.complete? + end + + def message + notification.status + end + + + private + + # Work around the issue that the initial return from DirecPay is always either SUCCESS or FAIL, there is no PENDING + def treat_failure_as_pending(post_data) + post_data.sub(/FAIL/, 'PENDING') + end + end + end + + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/status.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/status.rb new file mode 100644 index 000000000..5b9f85200 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/direc_pay/status.rb @@ -0,0 +1,37 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module DirecPay + + class Status + include PostsData + + STATUS_TEST_URL = 'https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp' + STATUS_LIVE_URL = 'https://www.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp' + + attr_reader :account, :options + + def initialize(account, options = {}) + @account, @options = account, options + end + + + # Use this method to manually request a status update to the provided notification_url + def update(authorization, notification_url) + url = test? ? STATUS_TEST_URL : STATUS_LIVE_URL + parameters = [ authorization, account, notification_url ] + data = PostData.new + data[:requestparams] = parameters.join('|') + + response = ssl_get("#{url}?#{data.to_post_data}") + end + + def test? + ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking.rb new file mode 100644 index 000000000..df9ad5b5d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking.rb @@ -0,0 +1,47 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Directebanking + autoload :Return, File.dirname(__FILE__) + '/directebanking/return.rb' + autoload :Helper, File.dirname(__FILE__) + '/directebanking/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/directebanking/notification.rb' + + # Supported countries: + # Germany - DE + # Austria - AT + # Belgium - BE + # Netherlands - NL + # Switzerland - CH + # Great Britain - GB + + # Overwrite this if you want to change the directebanking test url + mattr_accessor :test_url + self.test_url = 'https://www.directebanking.com/payment/start' + + # Overwrite this if you want to change the directebanking production url + mattr_accessor :production_url + self.production_url = 'https://www.directebanking.com/payment/start' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification(post, options = {}) + Notification.new(post, options) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/helper.rb new file mode 100644 index 000000000..5e33720b7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/helper.rb @@ -0,0 +1,90 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Directebanking + class Helper < ActiveMerchant::Billing::Integrations::Helper + + # All credentials are mandatory and need to be set + # + # credential1: User ID + # credential2: Project ID + # credential3: Project Password (Algorithm: SH1) + # credential4: Notification Password (Algorithm: SH1) + def initialize(order, account, options = {}) + super + add_field('user_variable_0', order) + add_field('project_id', options[:credential2]) + @project_password = options[:credential3] + end + + SIGNATURE_FIELDS = [ + :user_id, + :project_id, + :sender_holder, + :sender_account_number, + :sender_bank_code, + :sender_country_id, + :amount, + :currency_id, + :reason_1, + :reason_2, + :user_variable_0, + :user_variable_1, + :user_variable_2, + :user_variable_3, + :user_variable_4, + :user_variable_5 + ] + + SIGNATURE_IGNORE_AT_METHOD_CREATION_FIELDS = [ + :user_id, + :amount, + :project_id, + :currency_id, + :user_variable_0, + :user_variable_1, + :user_variable_2, + :user_variable_3 + ] + + SIGNATURE_FIELDS.each do |key| + if !SIGNATURE_IGNORE_AT_METHOD_CREATION_FIELDS.include?(key) + mapping "#{key}".to_sym, "#{key.to_s}" + end + end + + # Need to format the amount to have 2 decimal places + def amount=(money) + cents = money.respond_to?(:cents) ? money.cents : money + if money.is_a?(String) or cents.to_i <= 0 + raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' + end + add_field mappings[:amount], sprintf("%.2f", cents.to_f/100) + end + + def generate_signature_string + # format of signature: user_id|project_id|sender_holder|sender_account_number|sender_bank_code| sender_country_id|amount|currency_id|reason_1|reason_2|user_variable_0|user_variable_1|user_variable_2|user_variable_3|user_variable_4|user_variable_5|project_password + SIGNATURE_FIELDS.map {|key| @fields[key.to_s]} * "|" + "|#{@project_password}" + end + + def generate_signature + Digest::SHA1.hexdigest(generate_signature_string) + end + + def form_fields + @fields.merge('hash' => generate_signature) + end + + mapping :account, 'user_id' + mapping :amount, 'amount' + mapping :currency, 'currency_id' + mapping :description, 'reason_1' + + mapping :return_url, 'user_variable_1' + mapping :cancel_return_url, 'user_variable_2' + mapping :notify_url, 'user_variable_3' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/notification.rb new file mode 100644 index 000000000..7b60290d8 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/notification.rb @@ -0,0 +1,120 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Directebanking + class Notification < ActiveMerchant::Billing::Integrations::Notification + + def initialize(data, options) + if options[:credential4].nil? + raise ArgumentError, "You need to provide the notification password (SH1) as the option :credential4 to verify that the notification originated from Directebanking (Payment Networks AG)" + end + super + end + + def complete? + status == 'Completed' + end + + def item_id + params['user_variable_0'] + end + + def transaction_id + params['transaction'] + end + + # When was this payment received by the client. + def received_at + Time.parse(params['created']) if params['created'] + end + + # the money amount we received in X.2 decimal. + def gross + "%.2f" % params['amount'].to_f + end + + def status + 'Completed' + end + + def currency + params['currency_id'] + end + + def test? + params['sender_bank_name'] == 'Testbank' + end + + # for verifying the signature of the URL parameters + PAYMENT_HOOK_SIGNATURE_FIELDS = [ + :transaction, + :user_id, + :project_id, + :sender_holder, + :sender_account_number, + :sender_bank_code, + :sender_bank_name, + :sender_bank_bic, + :sender_iban, + :sender_country_id, + :recipient_holder, + :recipient_account_number, + :recipient_bank_code, + :recipient_bank_name, + :recipient_bank_bic, + :recipient_iban, + :recipient_country_id, + :international_transaction, + :amount, + :currency_id, + :reason_1, + :reason_2, + :security_criteria, + :user_variable_0, + :user_variable_1, + :user_variable_2, + :user_variable_3, + :user_variable_4, + :user_variable_5, + :created + ] + + PAYMENT_HOOK_IGNORE_AT_METHOD_CREATION_FIELDS = [ + :transaction, + :amount, + :currency_id, + :user_variable_0, + :user_variable_1, + :user_variable_2, + :user_variable_3, + :created + ] + + # Provide access to raw fields + PAYMENT_HOOK_SIGNATURE_FIELDS.each do |key| + if !PAYMENT_HOOK_IGNORE_AT_METHOD_CREATION_FIELDS.include?(key) + define_method(key.to_s) do + params[key.to_s] + end + end + end + + def generate_signature_string + #format is: transaction|user_id|project_id|sender_holder|sender_account_number|sender_bank_code|sender_bank_name|sender_bank_bic|sender_iban|sender_country_id|recipient_holder|recipient_account_number|recipient_bank_code|recipient_bank_name|recipient_bank_bic|recipient_iban|recipient_country_id|international_transaction|amount|currency_id|reason_1|reason_2|security_criteria|user_variable_0|user_variable_1|user_variable_2|user_variable_3|user_variable_4|user_variable_5|created|notification_password + PAYMENT_HOOK_SIGNATURE_FIELDS.map {|key| params[key.to_s]} * "|" + "|#{@options[:credential4]}" + end + + def generate_signature + Digest::SHA1.hexdigest(generate_signature_string) + end + + def acknowledge + # signature_is_valid? + generate_signature.to_s == params['hash'].to_s + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/return.rb new file mode 100644 index 000000000..8f8825ad2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/directebanking/return.rb @@ -0,0 +1,11 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Directebanking + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay.rb new file mode 100644 index 000000000..5a130bd7c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay.rb @@ -0,0 +1,22 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dotpay + autoload :Return, File.dirname(__FILE__) + '/dotpay/return.rb' + autoload :Helper, File.dirname(__FILE__) + '/dotpay/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/dotpay/notification.rb' + + mattr_accessor :service_url + self.service_url = 'https://ssl.dotpay.pl' + + def self.notification(post, options = {}) + Notification.new(post, options) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/helper.rb new file mode 100644 index 000000000..464106cdc --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/helper.rb @@ -0,0 +1,77 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dotpay + class Helper < ActiveMerchant::Billing::Integrations::Helper + def initialize(order, account, options = {}) + options = {:currency => 'PLN'}.merge options + + super + + add_field('channel', '0') + add_field('ch_lock', '0') + add_field('lang', 'PL') + add_field('onlinetransfer', '0') + add_field('tax', '0') + add_field('type', '2') + end + + mapping :account, 'id' + mapping :amount, 'amount' + + mapping :billing_address, :street => 'street', + :street_n1 => 'street_n1', + :street_n2 => 'street_n2', + :addr2 => 'addr2', + :addr3 => 'addr3', + :city => 'city', + :postcode => 'postcode', + :phone => 'phone', + :country => 'country' + + mapping :buttontext, 'buttontext' + mapping :channel, 'channel' + mapping :ch_lock, 'ch_lock' + mapping :code, 'code' + mapping :control, 'control' + mapping :currency, 'currency' + + mapping :customer, :firstname => 'firstname', + :lastname => 'lastname', + :email => 'email' + + mapping :description, 'description' + mapping :lang, 'lang' + mapping :onlinetransfer, 'onlinetransfer' + mapping :order, 'description' + mapping :p_email, 'p_email' + mapping :p_info, 'p_info' + mapping :tax, 'tax' + mapping :type, 'type' + mapping :url, 'url' + mapping :urlc, 'urlc' + + def billing_address(params = {}) + country = lookup_country_code(params.delete(:country) { 'POL' }, :alpha3) + add_field(mappings[:billing_address][:country], country) + + # Everything else + params.each do |k, v| + field = mappings[:billing_address][k] + add_field(field, v) unless field.nil? + end + end + + private + + def lookup_country_code(name_or_code, format = country_format) + country = Country.find(name_or_code) + country.code(format).to_s + rescue InvalidCountryCodeError + name_or_code + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/notification.rb new file mode 100644 index 000000000..a403b15c3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/notification.rb @@ -0,0 +1,86 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dotpay + class Notification < ActiveMerchant::Billing::Integrations::Notification + def complete? + status == 'OK' && %w(2 4 5).include?(t_status) + end + + def currency + orginal_amount.split(' ')[1] + end + + # the money amount we received in X.2 decimal. + def gross + params['amount'] + end + + def pin=(value) + @options[:pin] = value + end + + def status + params['status'] + end + + def test? + params['t_id'].match('.*-TST\d+') ? true : false + end + + PAYMENT_HOOK_FIELDS = [ + :id, + :control, + :t_id, + :orginal_amount, + :email, + :service, + :code, + :username, + :password, + :t_status, + :description, + :md5, + :p_info, + :p_email, + :t_date + ] + + PAYMENT_HOOK_SIGNATURE_FIELDS = [ + :id, + :control, + :t_id, + :amount, + :email, + :service, + :code, + :username, + :password, + :t_status + ] + + # Provide access to raw fields + PAYMENT_HOOK_FIELDS.each do |key| + define_method(key.to_s) do + params[key.to_s] + end + end + + def generate_signature_string + "#{@options[:pin]}:" + PAYMENT_HOOK_SIGNATURE_FIELDS.map {|key| params[key.to_s]} * ":" + end + + def generate_signature + Digest::MD5.hexdigest(generate_signature_string) + end + + def acknowledge + generate_signature.to_s == md5.to_s + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/return.rb new file mode 100644 index 000000000..236db3d9d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dotpay/return.rb @@ -0,0 +1,11 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dotpay + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla.rb new file mode 100644 index 000000000..698d0f1ee --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla.rb @@ -0,0 +1,23 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dwolla + autoload :Return, 'active_merchant/billing/integrations/dwolla/return.rb' + autoload :Helper, 'active_merchant/billing/integrations/dwolla/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/dwolla/notification.rb' + autoload :Common, 'active_merchant/billing/integrations/dwolla/common.rb' + + mattr_accessor :service_url + self.service_url = 'https://www.dwolla.com/payment/pay' + + def self.notification(post, options={}) + Notification.new(post, options) + end + + def self.return(query_string, options={}) + Return.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/common.rb new file mode 100644 index 000000000..cbf1e0082 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/common.rb @@ -0,0 +1,21 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dwolla + module Common + def verify_signature(checkoutId, amount, notification_signature, secret) + if secret.nil? + raise ArgumentError, "You need to provide the Application secret as the option :credential3 to verify that the notification originated from Dwolla" + end + + expected_signature = Digest::SHA1.hexdigest(secret + ('%s&%.2f' % [checkoutId, amount])) + + if notification_signature != expected_signature + raise StandardError, "Dwolla signature verification failed." + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/helper.rb new file mode 100644 index 000000000..ca1a4c70e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/helper.rb @@ -0,0 +1,40 @@ +require 'digest/sha1' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dwolla + class Helper < ActiveMerchant::Billing::Integrations::Helper + def initialize(order, account, options = {}) + super + add_field('name', 'Store Purchase') + + if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] + add_field('test', 'true') + end + + timestamp = Time.now.to_i.to_s + add_field('timestamp', timestamp) + add_field('allowFundingSources', 'true') + + key = options[:credential2].to_s + secret = options[:credential3].to_s + orderid = order.to_s + signature = Digest::SHA1.hexdigest(secret + "#{key}&#{timestamp}&#{orderid}") + add_field('signature', signature) + end + + mapping :account, 'destinationid' + mapping :credential2, 'key' + mapping :notify_url, 'callback' + mapping :return_url, 'redirect' + mapping :description, 'description' + mapping :amount, 'amount' + mapping :tax, 'tax' + mapping :shipping, 'shipping' + mapping :order, 'orderid' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/notification.rb new file mode 100644 index 000000000..4b53fb7ef --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/notification.rb @@ -0,0 +1,60 @@ +require 'net/http' +require 'digest/sha1' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dwolla + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def complete? + (status == "Completed") + end + + def status + params["Status"] + end + + def transaction_id + params['TransactionId'] + end + + def item_id + params['OrderId'] + end + + def currency + "USD" + end + + def gross + params['Amount'] + end + + def error + params['Error'] + end + + def test? + params['TestMode'] + end + + def acknowledge + true + end + + private + + def parse(post) + @raw = post.to_s + json_post = JSON.parse(post) + verify_signature(json_post['CheckoutId'], json_post['Amount'], json_post['Signature'], @options[:credential3]) + + params.merge!(json_post) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/return.rb new file mode 100644 index 000000000..afd6fbf9f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/dwolla/return.rb @@ -0,0 +1,46 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Dwolla + class Return < ActiveMerchant::Billing::Integrations::Return + include Common + + def initialize(data, options) + params = parse(data) + verify_signature(params['checkoutId'], params['amount'], params['signature'], options[:credential3]) + + super + end + + def success? + (self.error.nil? && self.callback_success?) + end + + def error + params['error'] + end + + def error_description + params['error_description'] + end + + def checkout_id + params['checkoutId'] + end + + def transaction + params['transaction'] + end + + def test? + params['test'] + end + + def callback_success? + (params['postback'] != "failure") + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans.rb new file mode 100644 index 000000000..5614a0bac --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans.rb @@ -0,0 +1,48 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module EPaymentPlans + autoload :Helper, File.dirname(__FILE__) + '/e_payment_plans/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/e_payment_plans/notification.rb' + + mattr_accessor :production_url + self.production_url = 'https://www.epaymentplans.com' + + mattr_accessor :test_url + self.test_url = 'https://test.epaymentplans.com' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + "#{production_url}/order/purchase" + when :test + "#{test_url}/order/purchase" + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification_confirmation_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + "#{production_url}/order/confirmation" + when :test + "#{test_url}/order/confirmation" + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification(post, options = {}) + Notification.new(post, options) + end + + def self.return(query_string, options = {}) + Return.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb new file mode 100644 index 000000000..76a561014 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb @@ -0,0 +1,34 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module EPaymentPlans + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :account, 'order[account]' + mapping :amount, 'order[amount]' + + mapping :order, 'order[num]' + + mapping :customer, :first_name => 'order[first_name]', + :last_name => 'order[last_name]', + :email => 'order[email]', + :phone => 'order[phone]' + + mapping :billing_address, :city => 'order[city]', + :address1 => 'order[address1]', + :address2 => 'order[address2]', + :company => 'order[company]', + :state => 'order[state]', + :zip => 'order[zip]', + :country => 'order[country]' + + mapping :notify_url, 'order[notify_url]' + mapping :return_url, 'order[return_url]' + mapping :cancel_return_url, 'order[cancel_return_url]' + mapping :description, 'order[description]' + mapping :tax, 'order[tax]' + mapping :shipping, 'order[shipping]' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb new file mode 100644 index 000000000..339e25ee7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb @@ -0,0 +1,84 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module EPaymentPlans + class Notification < ActiveMerchant::Billing::Integrations::Notification + include ActiveMerchant::PostsData + def complete? + status == "Completed" + end + + def transaction_id + params['transaction_id'] + end + + def item_id + params['item_id'] + end + + # When was this payment received by the client. + def received_at + Time.parse(params['received_at'].to_s).utc + end + + def gross + params['gross'] + end + + def currency + params['currency'] + end + + def security_key + params['security_key'] + end + + # Was this a test transaction? + def test? + params['test'] == 'test' + end + + def status + params['status'].capitalize + end + + # Acknowledge the transaction to EPaymentPlans. This method has to be called after a new + # apc arrives. EPaymentPlans will verify that all the information we received are correct + # and will return ok or a fail. + # + # Example: + # + # def ipn + # notify = EPaymentPlans.notification(request.raw_post) + # + # if notify.acknowledge + # ... process order ... if notify.complete? + # else + # ... log possible hacking attempt ... + # end + def acknowledge + payload = raw + + response = ssl_post(EPaymentPlans.notification_confirmation_url, payload) + + # Replace with the appropriate codes + raise StandardError.new("Faulty EPaymentPlans result: #{response}") unless ["AUTHORISED", "DECLINED"].include?(response) + response == "AUTHORISED" + end + + private + # Take the posted data and move the relevant data into a hash + def parse(post) + @raw = post + for line in post.split('&') + key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + params[key] = value + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay.rb new file mode 100644 index 000000000..701f1d19b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay.rb @@ -0,0 +1,30 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + # Documentation: https://ssl.easypay.by/light/ + module EasyPay + autoload :Helper, File.dirname(__FILE__) + '/easy_pay/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/easy_pay/notification.rb' + autoload :Common, File.dirname(__FILE__) + '/easy_pay/common.rb' + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'EP_Hash' + + mattr_accessor :notify_signature_parameter_name + self.notify_signature_parameter_name = 'notify_signature' + + mattr_accessor :service_url + self.service_url = 'https://ssl.easypay.by/weborder/' + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/common.rb new file mode 100644 index 000000000..74a8e4b22 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/common.rb @@ -0,0 +1,40 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module EasyPay + module Common + def generate_signature(type) + string = case type + when :request + request_signature_string + when :notify + notify_signature_string + end + + Digest::MD5.hexdigest(string) + end + + def request_signature_string + [ + @fields[mappings[:account]], + @secret, + @fields[mappings[:order]], + @fields[mappings[:amount]] + ].join + end + + def notify_signature_string + [ + params['order_mer_code'], + params['sum'], + params['mer_no'], + params['card'], + params['purch_date'], + secret + ].join + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/helper.rb new file mode 100644 index 000000000..5df2a4f40 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/helper.rb @@ -0,0 +1,36 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module EasyPay + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + + def initialize(order, account, options = {}) + super + @secret = options[:credential2] + end + + def form_fields + @fields.merge(ActiveMerchant::Billing::Integrations::EasyPay.signature_parameter_name => generate_signature(:request)) + end + + def params + @fields + end + + mapping :account, 'EP_MerNo' + mapping :amount, 'EP_Sum' + mapping :order, 'EP_OrderNo' + mapping :comment, 'EP_Comment' + mapping :order_info, 'EP_OrderInfo' + mapping :expires, 'EP_Expires' + mapping :success_url, 'EP_Success_URL' + mapping :cancel_url, 'EP_Cancel_URL' + mapping :debug, 'EP_Debug' + mapping :url_type, 'EP_URL_Type' + mapping :encoding, 'EP_Encoding' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/notification.rb new file mode 100644 index 000000000..90ce7e629 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/easy_pay/notification.rb @@ -0,0 +1,59 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module EasyPay + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def initialize(data, options) + if options[:credential2].nil? + raise ArgumentError, "You need to provide the md5 secret as the option :credential2 to verify that the notification originated from EasyPay" + end + + super + end + + def self.recognizes?(params) + params.has_key?('order_mer_code') && params.has_key?('sum') + end + + def complete? + true + end + + def amount + BigDecimal.new(gross) + end + + def item_id + params['order_mer_code'] + end + + def security_key + params[ActiveMerchant::Billing::Integrations::EasyPay.notify_signature_parameter_name] + end + + def gross + params['sum'] + end + + def status + 'Completed' + end + + def secret + @options[:credential2] + end + + def acknowledge + security_key == generate_signature(:notify) + end + + def success_response(*args) + { :nothing => true } + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay.rb new file mode 100644 index 000000000..24de6aae0 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay.rb @@ -0,0 +1,21 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Epay + autoload :Helper, File.dirname(__FILE__) + '/epay/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/epay/notification.rb' + + mattr_accessor :service_url + self.service_url = 'https://ssl.ditonlinebetalingssystem.dk/integration/ewindow/Default.aspx' + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/helper.rb new file mode 100644 index 000000000..650dcd661 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/helper.rb @@ -0,0 +1,55 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Epay + class Helper < ActiveMerchant::Billing::Integrations::Helper + + def initialize(order, merchantnumber, options = {}) + super + add_field('windowstate', 3) + add_field('language', '0') + add_field('orderid', format_order_number(order)) + @fields = Hash[@fields.sort] + end + + def md5secret(value) + @md5secret = value + end + + def form_fields + @fields.merge('hash' => generate_md5hash) + end + + def generate_md5string + @fields.sort.each.map { |key, value| key != 'hash' ? value.to_s : ''} * "" + @md5secret + end + + def generate_md5hash + Digest::MD5.hexdigest(generate_md5string) + end + + # Limited to 20 digits max + def format_order_number(number) + number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20] + end + + mapping :account, 'merchantnumber' + mapping :language, 'language' + mapping :amount, 'amount' + mapping :currency, 'currency' + mapping :return_url, 'accepturl' + mapping :cancel_return_url, 'cancelurl' + mapping :notify_url, 'callbackurl' + mapping :autocapture, 'instantcapture' + mapping :description, 'description' + mapping :credential3, 'md5secret' + mapping :customer, '' + mapping :billing_address, {} + mapping :tax, '' + mapping :shipping, '' + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/notification.rb new file mode 100644 index 000000000..aa868ee51 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/epay/notification.rb @@ -0,0 +1,110 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Epay + class Notification < ActiveMerchant::Billing::Integrations::Notification + + CURRENCY_CODES = { + :ADP => '020', :AED => '784', :AFA => '004', :ALL => '008', :AMD => '051', + :ANG => '532', :AOA => '973', :ARS => '032', :AUD => '036', :AWG => '533', + :AZM => '031', :BAM => '977', :BBD => '052', :BDT => '050', :BGL => '100', + :BGN => '975', :BHD => '048', :BIF => '108', :BMD => '060', :BND => '096', + :BOB => '068', :BOV => '984', :BRL => '986', :BSD => '044', :BTN => '064', + :BWP => '072', :BYR => '974', :BZD => '084', :CAD => '124', :CDF => '976', + :CHF => '756', :CLF => '990', :CLP => '152', :CNY => '156', :COP => '170', + :CRC => '188', :CUP => '192', :CVE => '132', :CYP => '196', :CZK => '203', + :DJF => '262', :DKK => '208', :DOP => '214', :DZD => '012', :ECS => '218', + :ECV => '983', :EEK => '233', :EGP => '818', :ERN => '232', :ETB => '230', + :EUR => '978', :FJD => '242', :FKP => '238', :GBP => '826', :GEL => '981', + :GHC => '288', :GIP => '292', :GMD => '270', :GNF => '324', :GTQ => '320', + :GWP => '624', :GYD => '328', :HKD => '344', :HNL => '340', :HRK => '191', + :HTG => '332', :HUF => '348', :IDR => '360', :ILS => '376', :INR => '356', + :IQD => '368', :IRR => '364', :ISK => '352', :JMD => '388', :JOD => '400', + :JPY => '392', :KES => '404', :KGS => '417', :KHR => '116', :KMF => '174', + :KPW => '408', :KRW => '410', :KWD => '414', :KYD => '136', :KZT => '398', + :LAK => '418', :LBP => '422', :LKR => '144', :LRD => '430', :LSL => '426', + :LTL => '440', :LVL => '428', :LYD => '434', :MAD => '504', :MDL => '498', + :MGF => '450', :MKD => '807', :MMK => '104', :MNT => '496', :MOP => '446', + :MRO => '478', :MTL => '470', :MUR => '480', :MVR => '462', :MWK => '454', + :MXN => '484', :MXV => '979', :MYR => '458', :MZM => '508', :NAD => '516', + :NGN => '566', :NIO => '558', :NOK => '578', :NPR => '524', :NZD => '554', + :OMR => '512', :PAB => '590', :PEN => '604', :PGK => '598', :PHP => '608', + :PKR => '586', :PLN => '985', :PYG => '600', :QAR => '634', :ROL => '642', + :RUB => '643', :RUR => '810', :RWF => '646', :SAR => '682', :SBD => '090', + :SCR => '690', :SDD => '736', :SEK => '752', :SGD => '702', :SHP => '654', + :SIT => '705', :SKK => '703', :SLL => '694', :SOS => '706', :SRG => '740', + :STD => '678', :SVC => '222', :SYP => '760', :SZL => '748', :THB => '764', + :TJS => '972', :TMM => '795', :TND => '788', :TOP => '776', :TPE => '626', + :TRL => '792', :TRY => '949', :TTD => '780', :TWD => '901', :TZS => '834', + :UAH => '980', :UGX => '800', :USD => '840', :UYU => '858', :UZS => '860', + :VEB => '862', :VND => '704', :VUV => '548', :XAF => '950', :XCD => '951', + :XOF => '952', :XPF => '953', :YER => '886', :YUM => '891', :ZAR => '710', + :ZMK => '894', :ZWD => '716' + } + + def complete? + Integer(transaction_id) > 0 + end + + def item_id + params['orderid'] + end + + def transaction_id + params['txnid'] + end + + def received_at + Time.mktime(params['date'][0..3], params['date'][4..5], params['date'][6..7], params['time'][0..1], params['time'][2..3]) + end + + def gross + "%.2f" % (gross_cents / 100.0) + end + + def gross_cents + params['amount'].to_i + end + + def test? + return false + end + + %w(txnid orderid amount currency date time hash fraud payercountry issuercountry txnfee subscriptionid paymenttype cardno).each do |attr| + define_method(attr) do + params[attr] + end + end + + def currency + CURRENCY_CODES.invert[params['currency']].to_s + end + + def amount + Money.new(params['amount'].to_i, currency) + end + + def generate_md5string + md5string = String.new + for line in @raw.split('&') + key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten + md5string += params[key] if key != 'hash' + end + return md5string + @options[:credential3] + end + + def generate_md5hash + Digest::MD5.hexdigest(generate_md5string) + end + + def acknowledge + generate_md5hash == params['hash'] + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data.rb new file mode 100644 index 000000000..65e8f8474 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data.rb @@ -0,0 +1,38 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module FirstData + autoload :Helper, 'active_merchant/billing/integrations/first_data/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/first_data/notification.rb' + + # Overwrite this if you want to change the ANS test url + mattr_accessor :test_url + self.test_url = 'https://demo.globalgatewaye4.firstdata.com/payment' + + # Overwrite this if you want to change the ANS production url + mattr_accessor :production_url + self.production_url = 'https://checkout.globalgatewaye4.firstdata.com/payment' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification(post) + Notification.new(post) + end + + def self.return(query_string) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/helper.rb new file mode 100644 index 000000000..82cd1c0f4 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/helper.rb @@ -0,0 +1,63 @@ +require 'active_support/core_ext/float/rounding.rb' # Float#round(precision) + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module FirstData + # First Data payment pages emulates the Authorize.Net SIM API. See + # ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Helper for + # more details. + # + # An example. Note the username as a parameter and transaction key you + # will want to use later. + # + # payment_service_for('order_id', 'first_data_payment_page_id', :service => :first_data, :amount => 157.0) do |service| + # + # # You must call setup_hash and invoice + # + # service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY', + # :order_timestamp => 1206836763 + # service.customer_id 8 + # service.customer :first_name => 'g', + # :last_name => 'g', + # :email => 'g@g.com', + # :phone => '3' + # service.billing_address :zip => 'g', + # :country => 'United States of America', + # :address => 'g' + # + # service.ship_to_address :first_name => 'g', + # :last_name => 'g', + # :city => '', + # :address => 'g', + # :address2 => '', + # :state => address.state, + # :country => 'United States of America', + # :zip => 'g' + # + # service.invoice "516428355" # your invoice number + # # The end-user is presented with the HTML produced by the notify_url + # # (using the First Data Receipt Link feature). + # service.return_url "http://mysite/first_data_receipt_generator_page" + # service.payment_header 'My store name' + # service.add_line_item :name => 'item name', :quantity => 1, :unit_price => 0 + # service.test_request 'true' # only if it's just a test + # service.shipping '25.0' + # # Tell it to display a "0" line item for shipping, with the price in + # # the name, otherwise it isn't shown at all, leaving the end user to + # # wonder why the total is different than the sum of the line items. + # service.add_shipping_as_line_item + # server.add_tax_as_line_item # same with tax + # # See the helper.rb file for various custom fields + # end + class Helper < ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Helper + # Configure notify_url to use the "Relay Response" feature + mapping :notify_url, 'x_relay_url' + + # Configure return_url to use the "Receipt Link" feature + mapping :return_url, 'x_receipt_link_url' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/notification.rb new file mode 100644 index 000000000..2e786ff3d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/first_data/notification.rb @@ -0,0 +1,56 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + # First Data payment pages emulates the Authorize.Net SIM API. See + # ActiveMerchant::Billing::Integrations::FirstData::Notification for + # more details. + # + # # Example: + # parser = FirstData::Notification.new(request.raw_post) + # passed = parser.complete? + # + # order = Order.find_by_order_number(parser.invoice_num) + # + # unless order + # @message = 'Error--unable to find your transaction! Please contact us directly.' + # return render :partial => 'first_data_payment_response' + # end + # + # if order.total != parser.gross.to_f + # logger.error "First Data said they paid for #{parser.gross} and it should have been #{order.total}!" + # passed = false + # end + # + # # Theoretically, First Data will *never* pass us the same transaction + # # ID twice, but we can double check that... by using + # # parser.transaction_id, and checking against previous orders' transaction + # # id's (which you can save when the order is completed).... + # unless parser.acknowledge FIRST_DATA_TRANSACTION_KEY, FIRST_DATA_RESPONSE_KEY + # passed = false + # logger.error "ALERT POSSIBLE FRAUD ATTEMPT" + # end + # + # unless parser.cavv_matches? and parser.avs_code_matches? + # logger.error 'Warning--non matching CC!' + params.inspect + # # Could fail them here, as well (recommended)... + # end + # + # if passed + # # Set up your session, and render something that will redirect them to + # # your site, most likely. + # else + # # Render failure or redirect them to your site where you will render failure + # end + + module FirstData + class Notification < ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Notification + def acknowledge(response_key, payment_page_id) + Digest::MD5.hexdigest(response_key + payment_page_id + params['x_trans_id'] + sprintf('%.2f', gross)) == params['x_MD5_Hash'].downcase + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay.rb new file mode 100644 index 000000000..fca773c78 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay.rb @@ -0,0 +1,25 @@ +# With help from Giovanni Intini and his code for RGestPay - http://medlar.it/it/progetti/rgestpay + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Gestpay + autoload :Return, File.dirname(__FILE__) + '/gestpay/return.rb' + autoload :Common, File.dirname(__FILE__) + '/gestpay/common.rb' + autoload :Helper, File.dirname(__FILE__) + '/gestpay/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/gestpay/notification.rb' + + mattr_accessor :service_url + self.service_url = 'https://ecomm.sella.it/gestpay/pagam.asp' + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/common.rb new file mode 100644 index 000000000..213e47a12 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/common.rb @@ -0,0 +1,42 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Gestpay + module Common + VERSION = "2.0" + ENCRYPTION_PATH = "/CryptHTTPS/Encrypt.asp" + DECRYPTION_PATH = "/CryptHTTPS/Decrypt.asp" + DELIMITER = '*P1*' + + CURRENCY_MAPPING = { + 'EUR' => '242', + 'ITL' => '18', + 'BRL' => '234', + 'USD' => '1', + 'JPY' => '71', + 'HKD' => '103' + } + + def parse_response(response) + case response + when /#cryptstring#(.*)#\/cryptstring#/, /#decryptstring#(.*)#\/decryptstring#/ + $1 + when /#error#(.*)#\/error#/ + raise StandardError, "An error occurred retrieving the encrypted string from GestPay: #{$1}" + else + raise StandardError, "No response was received by GestPay" + end + end + + def ssl_get(url, path) + uri = URI.parse(url) + site = Net::HTTP.new(uri.host, uri.port) + site.use_ssl = true + site.verify_mode = OpenSSL::SSL::VERIFY_NONE + site.get(path).body + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/helper.rb new file mode 100644 index 000000000..2cb4b04e9 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/helper.rb @@ -0,0 +1,70 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Gestpay + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + # Valid language codes + # Italian => 1 + # English => 2 + # Spanish => 3 + # French => 4 + # Tedesco => 5 + def initialize(order, account, options = {}) + super + add_field('PAY1_IDLANGUAGE', 2) + end + + mapping :account, 'ShopLogin' + + mapping :amount, 'PAY1_AMOUNT' + mapping :currency, 'PAY1_UICCODE' + + mapping :order, 'PAY1_SHOPTRANSACTIONID' + + # Buyer name PAY1_CHNAME + mapping :customer, :email => 'PAY1_CHEMAIL' + + mapping :credit_card, :number => 'PAY1_CARDNUMBER', + :expiry_month => 'PAY1_EXPMONTH', + :expiry_year => 'PAY1_EXPYEAR', + :verification_value => 'PAY1_CVV' + + def customer(params = {}) + add_field(mappings[:customer][:email], params[:email]) + add_field('PAY1_CHNAME', "#{params[:first_name]} #{params[:last_name]}") + end + + def currency=(currency_code) + code = CURRENCY_MAPPING[currency_code] + raise StandardError, "Invalid currency code #{currency_code} specified" if code.nil? + + add_field(mappings[:currency], code) + end + + def form_fields + @encrypted_data ||= get_encrypted_string + + { + 'a' => @fields['ShopLogin'], + 'b' => @encrypted_data + } + end + + def get_encrypted_string + response = ssl_get(Gestpay.service_url, encryption_query_string) + parse_response(response) + end + + def encryption_query_string + fields = ['PAY1_AMOUNT', 'PAY1_SHOPTRANSACTIONID', 'PAY1_UICCODE'] + + encoded_params = fields.collect{ |field| "#{field}=#{CGI.escape(@fields[field])}" }.join(DELIMITER) + + "#{ENCRYPTION_PATH}?a=" + CGI.escape(@fields['ShopLogin']) + "&b=" + encoded_params + "&c=" + CGI.escape(VERSION) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/notification.rb new file mode 100644 index 000000000..27ed57736 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/notification.rb @@ -0,0 +1,85 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Gestpay + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def complete? + status == 'Completed' + end + + # The important param + def item_id + params['PAY1_SHOPTRANSACTIONID'] + end + + def transaction_id + params['PAY1_BANKTRANSACTIONID'] + end + + # the money amount we received in X.2 decimal. + def gross + params['PAY1_AMOUNT'] + end + + def currency + # Ruby 1.9 compat + method = CURRENCY_MAPPING.respond_to?(:key) ? :key : :index + CURRENCY_MAPPING.send(method, params['PAY1_UICCODE']) + end + + def test? + false + end + + def status + case params['PAY1_TRANSACTIONRESULT'] + when 'OK' + 'Completed' + else + 'Failed' + end + end + + def acknowledge + true + end + + private + # Take the posted data and move the relevant data into a hash + def parse(query_string) + @raw = query_string + + return if query_string.blank? + encrypted_params = parse_delimited_string(query_string) + + return if encrypted_params['a'].blank? || encrypted_params['b'].blank? + @params = decrypt_data(encrypted_params['a'], encrypted_params['b']) + end + + def parse_delimited_string(string, delimiter = '&', unencode_cgi = false) + result = {} + for line in string.split(delimiter) + key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + result[key] = unencode_cgi ? CGI.unescape(value) : value + end + result + end + + def decrypt_data(shop_login, encrypted_string) + response = ssl_get(Gestpay.service_url, decryption_query_string(shop_login, encrypted_string)) + encoded_response = parse_response(response) + parse_delimited_string(encoded_response, DELIMITER, true) + end + + def decryption_query_string(shop_login, encrypted_string) + "#{DECRYPTION_PATH}?a=" + CGI.escape(shop_login) + "&b=" + encrypted_string + "&c=" + CGI.escape(VERSION) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/return.rb new file mode 100644 index 000000000..0c01f286e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/gestpay/return.rb @@ -0,0 +1,10 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Gestpay + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/helper.rb new file mode 100644 index 000000000..dc742de55 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/helper.rb @@ -0,0 +1,117 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + class Helper #:nodoc: + attr_reader :fields + class_attribute :service_url + class_attribute :mappings + class_attribute :country_format + self.country_format = :alpha2 + + # The application making the calls to the gateway + # Useful for things like the PayPal build notation (BN) id fields + class_attribute :application_id + self.application_id = 'ActiveMerchant' + + def initialize(order, account, options = {}) + options.assert_valid_keys([:amount, :currency, :test, :credential2, :credential3, :credential4, :country, :account_name, :transaction_type]) + @fields = {} + @raw_html_fields = [] + @test = options[:test] + self.order = order + self.account = account + self.amount = options[:amount] + self.currency = options[:currency] + self.credential2 = options[:credential2] + self.credential3 = options[:credential3] + self.credential4 = options[:credential4] + end + + def self.mapping(attribute, options = {}) + self.mappings ||= {} + self.mappings[attribute] = options + end + + def add_field(name, value) + return if name.blank? || value.blank? + @fields[name.to_s] = value.to_s + end + + def add_fields(subkey, params = {}) + params.each do |k, v| + field = mappings[subkey][k] + add_field(field, v) unless field.blank? + end + end + + # Add a field that has characters that CGI::escape would mangle. Allows + # for multiple fields with the same name (e.g., to support line items). + def add_raw_html_field(name, value) + return if name.blank? || value.blank? + @raw_html_fields << [name, value] + end + + def raw_html_fields + @raw_html_fields + end + + def billing_address(params = {}) + add_address(:billing_address, params) + end + + def shipping_address(params = {}) + add_address(:shipping_address, params) + end + + def form_fields + @fields + end + + def test? + @test_mode ||= ActiveMerchant::Billing::Base.integration_mode == :test || @test + end + + def form_method + "POST" + end + + private + + def add_address(key, params) + return if mappings[key].nil? + + code = lookup_country_code(params.delete(:country)) + add_field(mappings[key][:country], code) + add_fields(key, params) + end + + def lookup_country_code(name_or_code, format = country_format) + country = Country.find(name_or_code) + country.code(format).to_s + rescue InvalidCountryCodeError + name_or_code + end + + def method_missing(method_id, *args) + method_id = method_id.to_s.gsub(/=$/, '').to_sym + # Return and do nothing if the mapping was not found. This allows + # For easy substitution of the different integrations + return if mappings[method_id].nil? + + mapping = mappings[method_id] + + case mapping + when Array + mapping.each{ |field| add_field(field, args.last) } + when Hash + options = args.last.is_a?(Hash) ? args.pop : {} + + mapping.each{ |key, field| add_field(field, options[key]) } + else + add_field(mapping, args.last) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust.rb new file mode 100644 index 000000000..0f661b813 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust.rb @@ -0,0 +1,27 @@ + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module HiTrust + autoload :Helper, File.dirname(__FILE__) + '/hi_trust/helper.rb' + autoload :Return, File.dirname(__FILE__) + '/hi_trust/return.rb' + autoload :Notification, File.dirname(__FILE__) + '/hi_trust/notification.rb' + + TEST_URL = 'https://testtrustlink.hitrust.com.tw/TrustLink/TrxReq' + LIVE_URL = 'https://trustlink.hitrust.com.tw/TrustLink/TrxReq' + + def self.service_url + ActiveMerchant::Billing::Base.integration_mode == :test ? TEST_URL : LIVE_URL + end + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/helper.rb new file mode 100644 index 000000000..a28672960 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/helper.rb @@ -0,0 +1,58 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module HiTrust + class Helper < ActiveMerchant::Billing::Integrations::Helper + + # Transaction types + # * Auth + # * AuthRe + # * Capture + # * CaptureRe + # * Refund + # * RefundRe + # * Query + def initialize(order, account, options = {}) + super + # Perform an authorization by default + add_field('Type', 'Auth') + + # Capture the payment right away + add_field('depositflag', '1') + + # Disable auto query - who knows what it does? + add_field('queryflag', '1') + + add_field('orderdesc', 'Store purchase') + end + + mapping :account, 'storeid' + mapping :amount, 'amount' + + def amount=(money) + cents = money.respond_to?(:cents) ? money.cents : money + + if money.is_a?(String) or cents.to_i < 0 + raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' + end + + add_field(mappings[:amount], cents) + end + # Supported currencies include: + # * CNY:Chinese Yuan (Renminbi) + # * TWD:New Taiwan Dollar + # * HKD:Hong Kong Dollar + # * USD:US Dollar + # * AUD:Austrian Dollar + mapping :currency, 'currency' + + mapping :order, 'ordernumber' + mapping :description, 'orderdesc' + + mapping :notify_url, 'merUpdateURL' + mapping :return_url, 'returnURL' + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/notification.rb new file mode 100644 index 000000000..06a3d8f76 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/notification.rb @@ -0,0 +1,59 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module HiTrust + class Notification < ActiveMerchant::Billing::Integrations::Notification + SUCCESS = '00' + + self.production_ips = [ '203.75.242.8' ] + + def complete? + status == 'Completed' + end + + def transaction_id + params['authRRN'] + end + + def item_id + params['ordernumber'] + end + + def received_at + Time.parse(params['orderdate']) rescue nil + end + + def currency + params['currency'] + end + + def gross + sprintf("%.2f", gross_cents.to_f / 100) + end + + def gross_cents + params['approveamount'].to_i + end + + def account + params['storeid'] + end + + def status + params['retcode'] == SUCCESS ? 'Completed' : 'Failed' + end + + def test? + ActiveMerchant::Billing::Base.integration_mode == :test + end + + def acknowledge + true + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/return.rb new file mode 100644 index 000000000..ead9511d8 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/hi_trust/return.rb @@ -0,0 +1,67 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module HiTrust + class Return < ActiveMerchant::Billing::Integrations::Return + SUCCESS = "00" + CODES = { "00" => "Operation completed successfully", + "-1" => "Unable to initialize winsock dll.", + "-2" => "Can't create stream socket.", + "-3" => "No Request Message.", + "-4" => "Can't connect to server.", + "-5" => "Send socket error.", + "-6" => "Couldn't receive data.", + "-7" => "Receive Broken message.", + "-8" => "Unable to initialize Envirnment.", + "-9" => "Can't Read Server RSA File.", + "-10" => "Can't Read Client RSA File.", + "-11" => "Web Server error.", + "-12" => "Receive Message type error.", + "-13" => "No Request Message.", + "-14" => "No Response Content.", + "-18" => "Merchant Update URL not found.", + "-19" => "Server URL not find Domain or IP.", + "-20" => "Server URL only can fill http or https.", + "-21" => "Server Config File open error.", + "-22" => "Server RSA Key File open error.", + "-23" => "Server RSA Key File read error.", + "-24" => "Server Config File have some errors, Please to check it.", + "-25" => "Merchant Config File open error.", + "-26" => "Merchant RSA Key File open error.", + "-27" => "Merchant RSA Key File read error.", + "-28" => "Merchant Config File has some errors, Please to check it.", + "-29" => "Server Type is unknown.", + "-30" => "Comm Type is unknown.", + "-31" => "Input Parameter [ORDERNO] is null or empty.", + "-32" => "Input Parameter [STOREID] is null or empty.", + "-33" => "Input Parameter [ORDERDESC] is null or empty.", + "-34" => "Input Parameter [CURRENCY] is null or empty.", + "-35" => "Input Parameter [AMOUNT] is null or empty.", + "-36" => "Input Parameter [ORDERURL] is null or empty.", + "-37" => "Input Parameter [RETURNURL] is null or empty.", + "-38" => "Input Parameter [DEPOSIT] is null or empty.", + "-39" => "Input Parameter [QUERYFLAG] is null or empty.", + "-40" => "Input Parameter [UPDATEURL] is null or empty.", + "-41" => "Input Parameter [MERUPDATEURL] is null or empty.", + "-42" => "Input Parameter [KEY] is null or empty.", + "-43" => "Input Parameter [MAC] is null or empty.", + "-44" => "Input Parameter [CIPHER] is null or empty.", + "-45" => "Input Parameter [TrxType] is wrong.", + "-100" => "TrustLink Server is closed. Or Merchant Server IP is not consistent with TrustLink Server setting.", + "-101" => "TrustLink Server receives NULL.", + "-308" => "Order Number already exists.", + "positive" => "Response from Bank. Please contact with Acquirer Bank Service or HiTRUST Call Center." + } + + def success? + params['retcode'] == SUCCESS + end + + def message + CODES[ params['retcode'] ] + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay.rb new file mode 100644 index 000000000..f43e3220e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay.rb @@ -0,0 +1,30 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + # Documentation: https://www.liqpay.com/?do=pages&p=cnb10 + module Liqpay + autoload :Helper, File.dirname(__FILE__) + '/liqpay/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/liqpay/notification.rb' + autoload :Return, File.dirname(__FILE__) + '/liqpay/return.rb' + + mattr_accessor :service_url + self.service_url = 'https://liqpay.com/?do=clickNbuy' + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'signature' + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + + def self.return(query_string) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/helper.rb new file mode 100644 index 000000000..89d158c68 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/helper.rb @@ -0,0 +1,43 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Liqpay + class Helper < ActiveMerchant::Billing::Integrations::Helper + def initialize(order, account, options = {}) + @secret = options.delete(:secret) + super + + add_field 'version', '1.2' + end + + def form_fields + xml = "<request> + <version>1.2</version> + <result_url>#{@fields["result_url"]}</result_url> + <server_url>#{@fields["server_url"]}</server_url> + <merchant_id>#{@fields["merchant_id"]}</merchant_id> + <order_id>#{@fields["order_id"]}</order_id> + <amount>#{@fields["amount"]}</amount> + <currency>#{@fields["currency"]}</currency> + <description>#{@fields["description"]}</description> + <default_phone>#{@fields["default_phone"]}</default_phone> + <pay_way>card</pay_way> + </request>".strip + sign = Base64.encode64(Digest::SHA1.digest("#{@secret}#{xml}#{@secret}")).strip + {"operation_xml" => Base64.encode64(xml), "signature" => sign} + end + + mapping :account, 'merchant_id' + mapping :amount, 'amount' + mapping :currency, 'currency' + mapping :order, 'order_id' + mapping :description, 'description' + mapping :phone, 'default_phone' + + mapping :notify_url, 'server_url' + mapping :return_url, 'result_url' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/notification.rb new file mode 100644 index 000000000..2cfb7a261 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/notification.rb @@ -0,0 +1,89 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Liqpay + class Notification < ActiveMerchant::Billing::Integrations::Notification + def self.recognizes?(params) + params.has_key?('amount') && params.has_key?('order_id') + end + + def initialize(post, options = {}) + raise ArgumentError if post.blank? + super + @params.merge!(Hash.from_xml(Base64.decode64(xml))["response"]) + end + + def xml + @params["operation_xml"] + end + + def complete? + status == 'success' + end + + def account + params['merchant_id'] + end + + def amount + BigDecimal.new(gross) + end + + def item_id + params['order_id'] + end + + def transaction_id + params['transaction_id'] + end + + def action_name + params['action_name'] # either 'result_url' or 'server_url' + end + + def version + params['version'] + end + + def sender_phone + params['sender_phone'] + end + + def security_key + params[ActiveMerchant::Billing::Integrations::Liqpay.signature_parameter_name] + end + + def gross + params['amount'] + end + + def currency + params['currency'] + end + + def status + params['status'] # 'success', 'failure' or 'wait_secure' + end + + def code + params['code'] + end + + def generate_signature_string + "#{@options[:secret]}#{Base64.decode64(xml)}#{@options[:secret]}" + end + + def generate_signature + Base64.encode64(Digest::SHA1.digest(generate_signature_string)).strip + end + + def acknowledge + security_key == generate_signature + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/return.rb new file mode 100644 index 000000000..200862d25 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/liqpay/return.rb @@ -0,0 +1,83 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Liqpay + class Return < ActiveMerchant::Billing::Integrations::Return + def self.recognizes?(params) + params.has_key?('amount') && params.has_key?('order_id') + end + + def initialize(post) + super + xml = Base64.decode64(@params["operation_xml"]) + @params.merge!(Hash.from_xml(xml)["response"]) + end + + def complete? + status == 'success' + end + + def account + params['merchant_id'] + end + + def amount + BigDecimal.new(gross) + end + + def item_id + params['order_id'] + end + + def transaction_id + params['transaction_id'] + end + + def action_name + params['action_name'] # either 'result_url' or 'server_url' + end + + def version + params['version'] + end + + def sender_phone + params['sender_phone'] + end + + def security_key + params[ActiveMerchant::Billing::Integrations::Liqpay.signature_parameter_name] + end + + def gross + params['amount'] + end + + def currency + params['currency'] + end + + def status + params['status'] # 'success', 'failure' or 'wait_secure' + end + + def code + params['code'] + end + + def generate_signature_string + ['', version, @options[:secret], action_name, sender_phone, account, gross, currency, item_id, transaction_id, status, code, ''].flatten.compact.join('|') + end + + def generate_signature + Base64.encode64(Digest::SHA1.digest(generate_signature_string)).gsub(/\n/, '') + end + + def acknowledge + security_key == generate_signature + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva.rb new file mode 100644 index 000000000..1bb12bf64 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva.rb @@ -0,0 +1,86 @@ +require File.dirname(__FILE__) + '/maksuturva/helper.rb' +require File.dirname(__FILE__) + '/maksuturva/notification.rb' + +# USAGE: +# +# First define Maksuturva seller id and authcode in an initializer: +# +# MAKSUTURVA_SELLERID = "testikauppias" +# MAKSUTURVA_AUTHCODE = "11223344556677889900" +# +# Then in view do something like this (use dynamic values for your app) +# +# <% payment_service_for 2, MAKSUTURVA_SELLERID, +# :amount => "200,00", :currency => 'EUR', :credential2 => MAKSUTURVA_AUTHCODE, +# :service => :maksuturva do |service| +# service.pmt_reference = "134662" +# service.pmt_duedate = "24.06.2012" +# service.customer :phone => "0405051909", +# :email => "antti@example.com" +# service.billing_address :city => "Helsinki", +# :address1 => "Lorem street", +# :state => "-", +# :country => 'Finland', +# :zip => "00530" +# service.pmt_orderid = "2" +# service.pmt_buyername = "Antti Akonniemi" +# service.pmt_deliveryname = "Antti Akonniemi" +# service.pmt_deliveryaddress = "Köydenpunojankatu 13" +# service.pmt_deliverypostalcode = "00180" +# service.pmt_deliverycity = "Helsinki" +# service.pmt_deliverycountry = "FI" +# service.pmt_rows = 1 +# service.pmt_row_name1 = "testi" +# service.pmt_row_desc1 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +# service.pmt_row_articlenr1 = "1" +# service.pmt_row_quantity1 = "1" +# service.pmt_row_deliverydate1 = "26.6.2012" +# service.pmt_row_price_gross1 = "200,00" +# service.pmt_row_vat1= "23,00" +# service.pmt_row_discountpercentage1 = "0,00" +# service.pmt_row_type1 = "1" +# service.pmt_charset = "UTF-8" +# service.pmt_charsethttp = "UTF-8" +# +# service.return_url "http://localhost:3000/process" +# service.cancel_return_url "http://example.com" +# service.pmt_errorreturn "http://example.com" +# +# service.pmt_delayedpayreturn "http://example.com" +# service.pmt_escrow "N" +# service.pmt_escrowchangeallowed "N" +# service.pmt_sellercosts "0,00" +# service.pmt_keygeneration "001" +# %> +# +# Then in the controller handle the return with something like this +# +# def ipn +# notify = ActiveMerchant::Billing::Integrations::Maksuturva::Notification.new(params) +# +# if notify.acknowledge(MAKSUTURVA_AUTHCODE) +# # Process order +# else +# # Show error +# end +# end +# +# For full list of available parameters etc check the integration documents +# here: +# +# https://www.maksuturva.fi/services/vendor_services/integration_guidelines.html + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Maksuturva + mattr_accessor :service_url + self.service_url = 'https://www.maksuturva.fi/NewPaymentExtended.pmt' + + def self.notification(post) + Notification.new(post) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/helper.rb new file mode 100644 index 000000000..4d6128cff --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/helper.rb @@ -0,0 +1,119 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Maksuturva + class Helper < ActiveMerchant::Billing::Integrations::Helper + def initialize(order, account, options = {}) + md5secret options.delete(:credential2) + super + add_field("pmt_action", "NEW_PAYMENT_EXTENDED") + add_field("pmt_version", "0004") + add_field("pmt_sellerid", account) + add_field("pmt_hashversion", "MD5") + end + + def md5secret(value) + @md5secret = value + end + + def form_fields + @fields.merge("pmt_hash" => generate_md5string) + end + + def generate_md5string + fields = [@fields["pmt_action"], @fields["pmt_version"]] + fields += [@fields["pmt_selleriban"]] unless @fields["pmt_selleriban"].nil? + fields += [@fields["pmt_id"], @fields["pmt_orderid"], @fields["pmt_reference"], @fields["pmt_duedate"], + @fields["pmt_amount"], @fields["pmt_currency"], @fields["pmt_okreturn"], @fields["pmt_errorreturn"], @fields["pmt_cancelreturn"], + @fields["pmt_delayedpayreturn"], @fields["pmt_escrow"], @fields["pmt_escrowchangeallowed"]] + + fields += [@fields["pmt_invoicefromseller"]] unless @fields["pmt_invoicefromseller"].nil? + fields += [@fields["pmt_paymentmethod"]] unless @fields["pmt_paymentmethod"].nil? + fields += [@fields["pmt_buyeridentificationcode"]] unless @fields["pmt_buyeridentificationcode"].nil? + + + fields += [@fields["pmt_buyername"], @fields["pmt_buyeraddress"], @fields["pmt_buyerpostalcode"], @fields["pmt_buyercity"], + @fields["pmt_buyercountry"], @fields["pmt_deliveryname"], @fields["pmt_deliveryaddress"], @fields["pmt_deliverypostalcode"], @fields["pmt_deliverycity"], + @fields["pmt_deliverycountry"], @fields["pmt_sellercosts"]] + + (1..@fields["pmt_rows"].to_i).each do |i| + fields += [@fields["pmt_row_name#{i}"], @fields["pmt_row_desc#{i}"], @fields["pmt_row_quantity#{i}"]] + fields += [@fields["pmt_row_articlenr#{i}"]] unless @fields["pmt_row_articlenr#{i}"].nil? + fields += [@fields["pmt_row_unit#{i}"]] unless @fields["pmt_row_unit#{i}"].nil? + fields += [@fields["pmt_row_deliverydate#{i}"]] + fields += [@fields["pmt_row_price_gross#{i}"]] unless @fields["pmt_row_price_gross#{i}"].nil? + fields += [@fields["pmt_row_price_net#{i}"]] unless @fields["pmt_row_price_net#{i}"].nil? + fields += [@fields["pmt_row_vat#{i}"], @fields["pmt_row_discountpercentage#{i}"], @fields["pmt_row_type#{i}"]] + end + fields += [@md5secret] + fields = fields.join("&") + "&" + Digest::MD5.hexdigest(fields).upcase + end + + mapping :pmt_selleriban, "pmt_selleriban" + mapping :pmt_reference, "pmt_reference" + mapping :pmt_duedate, "pmt_duedate" + mapping :pmt_userlocale, "pmt_userlocale" + mapping :pmt_escrow, "pmt_escrow" + mapping :pmt_escrowchangeallowed, "pmt_escrowchangeallowed" + mapping :pmt_invoicefromseller, "pmt_invoicefromseller" + mapping :pmt_paymentmethod, "pmt_paymentmethod" + mapping :pmt_buyeridentificationcode, "pmt_buyeridentificationcode" + mapping :pmt_buyername, "pmt_buyername" + + mapping :account, '' + mapping :currency, 'pmt_currency' + mapping :amount, 'pmt_amount' + + mapping :order, 'pmt_id' + mapping :pmt_orderid, 'pmt_orderid' + mapping :pmt_deliveryname, "pmt_deliveryname" + mapping :pmt_deliveryaddress, "pmt_deliveryaddress" + mapping :pmt_deliverypostalcode, "pmt_deliverypostalcode" + mapping :pmt_deliverycity, "pmt_deliverycity" + mapping :pmt_deliverycountry, "pmt_deliverycountry" + mapping :pmt_sellercosts, "pmt_sellercosts" + mapping :pmt_rows, "pmt_rows" + + (1..499.to_i).each do |i| + mapping "pmt_row_name#{i}".to_sym, "pmt_row_name#{i}" + mapping "pmt_row_desc#{i}".to_sym, "pmt_row_desc#{i}" + mapping "pmt_row_quantity#{i}".to_sym, "pmt_row_quantity#{i}" + mapping "pmt_row_articlenr#{i}".to_sym, "pmt_row_articlenr#{i}" + mapping "pmt_row_unit#{i}".to_sym, "pmt_row_unit#{i}" + mapping "pmt_row_deliverydate#{i}".to_sym, "pmt_row_deliverydate#{i}" + mapping "pmt_row_price_gross#{i}".to_sym, "pmt_row_price_gross#{i}" + mapping "pmt_row_price_net#{i}".to_sym, "pmt_row_price_net#{i}" + mapping "pmt_row_vat#{i}".to_sym, "pmt_row_vat#{i}" + mapping "pmt_row_discountpercentage#{i}".to_sym, "pmt_row_discountpercentage#{i}" + mapping "pmt_row_type#{i}".to_sym, "pmt_row_type#{i}" + end + + mapping :pmt_charset, "pmt_charset" + mapping :pmt_charsethttp, "pmt_charsethttp" + mapping :pmt_hashversion, "pmt_hashversion" + mapping :pmt_keygeneration, "pmt_keygeneration" + mapping :customer, :email => 'pmt_buyeremail', + :phone => 'pmt_buyerphone' + + mapping :billing_address, :city => 'pmt_buyercity', + :address1 => "pmt_buyeraddress", + :address2 => '', + :state => '', + :zip => "pmt_buyerpostalcode", + :country => 'pmt_buyercountry' + + mapping :notify_url, '' + mapping :return_url, 'pmt_okreturn' + mapping :pmt_errorreturn, 'pmt_errorreturn' + mapping :pmt_delayedpayreturn, 'pmt_delayedpayreturn' + mapping :cancel_return_url, 'pmt_cancelreturn' + + mapping :description, '' + mapping :tax, '' + mapping :shipping, '' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/notification.rb new file mode 100644 index 000000000..ed7cdd297 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/maksuturva/notification.rb @@ -0,0 +1,48 @@ +require "net/http" + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Maksuturva + class Notification < ActiveMerchant::Billing::Integrations::Notification + def complete? + true + end + + def transaction_id + params["pmt_id"] + end + + def security_key + params["pmt_hash"] + end + + def gross + params["pmt_amount"] + end + + def currency + params["pmt_currency"] + end + + def status + "PAID" + end + + def acknowledge(authcode) + return_authcode = [params["pmt_action"], params["pmt_version"], params["pmt_id"], params["pmt_reference"], params["pmt_amount"], params["pmt_currency"], params["pmt_sellercosts"], params["pmt_paymentmethod"], params["pmt_escrow"], authcode].join("&") + (Digest::MD5.hexdigest(return_authcode + "&").upcase == params["pmt_hash"]) + end + + private + + def parse(post) + post.each do |key, value| + params[key] = value + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers.rb new file mode 100644 index 000000000..44e034e10 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers.rb @@ -0,0 +1,26 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Moneybookers + + autoload :Notification, File.dirname(__FILE__) + '/moneybookers/notification.rb' + autoload :Helper, File.dirname(__FILE__) + '/moneybookers/helper.rb' + + mattr_accessor :production_url + self.production_url = 'https://www.moneybookers.com/app/payment.pl' + + def self.service_url + self.production_url + end + + def self.notification(post, options) + Notification.new(post, options) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/helper.rb new file mode 100644 index 000000000..47ae94648 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/helper.rb @@ -0,0 +1,75 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Moneybookers + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :account, 'pay_to_email' + mapping :order, 'transaction_id' + mapping :amount, 'amount' + mapping :currency, 'currency' + + mapping :customer, + :first_name => 'firstname', + :last_name => 'lastname', + :email => 'pay_from_email', + :phone => 'phone_number' + + mapping :billing_address, + :city => 'city', + :address1 => 'address', + :address2 => 'address2', + :state => 'state', + :zip => 'postal_code', + :country => 'country' + + mapping :notify_url, 'status_url' + mapping :return_url, 'return_url' + mapping :cancel_return_url, 'cancel_url' + mapping :description, 'detail1_text' + + MAPPED_COUNTRY_CODES = { + 'SE' => 'SV', + 'DK' => 'DA' + } + + SUPPORTED_COUNTRY_CODES = [ + 'FI', 'DE', 'ES', 'FR', + 'IT','PL', 'GR', 'RO', + 'RU', 'TR', 'CN', 'CZ', 'NL' + ] + + def initialize(order, account, options = {}) + super + add_tracking_token + add_default_parameters + add_seller_details(options) + end + + private + + def add_tracking_token + return if application_id.blank? || application_id == 'ActiveMerchant' + + add_field('merchant_fields', 'platform') + add_field('platform', application_id) + end + + def add_default_parameters + add_field('hide_login', 1) + end + + def add_seller_details(options) + add_field('recipient_description', options[:account_name]) if options[:account_name] + add_field('country', lookup_country_code(options[:country], :alpha3)) if options[:country] + add_field('language', locale_code(options[:country])) if options[:country] + end + + def locale_code(country_code) + return country_code if SUPPORTED_COUNTRY_CODES.include?(country_code) + MAPPED_COUNTRY_CODES[country_code] || 'EN' + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/notification.rb new file mode 100644 index 000000000..5d25b4cba --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/moneybookers/notification.rb @@ -0,0 +1,129 @@ +require 'net/http' +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Moneybookers + class Notification < ActiveMerchant::Billing::Integrations::Notification + + def initialize(data, options) + if options[:credential2].nil? + raise ArgumentError, "You need to provide the md5 secret as the option :credential2 to verify that the notification originated from Moneybookers" + end + super + end + + def complete? + status == 'Completed' + end + + # ‘2’ Processed – This status is sent when the transaction is processed and the funds have been received on your Moneybookers account. + # ‘0’ Pending – This status is sent when the customers pays via the pending bank transfer option. Such transactions will auto-process IF the bank transfer is received by Moneybookers. We strongly recommend that you do NOT process the order/transaction in your system upon receipt of a pending status from Moneybookers. + # ‘-1’ Cancelled – Pending transactions can either be cancelled manually by the sender in their online account history or they will auto-cancel after 14 days if still pending. + # ‘-2’ Failed – This status is sent when the customer tries to pay via Credit Card or Direct Debit but our provider declines the transaction. If you do not accept Credit Card or Direct Debit payments via Moneybookers (see page 17) then you will never receive the failed status. + # ‘-3’ Chargeback – This status could be received only if your account is configured to receive chargebacks. If this is the case, whenever a chargeback is received by Moneybookers, a -3 status will be posted on the status_url for the reversed transaction. + def status + case status_code + when '2' + 'Completed' + when '0' + 'Pending' + when '-1' + 'Cancelled' + when '-2' + 'Failed' + when '-3' + 'Reversed' + else + 'Error' + end + end + + def status_code + params['status'] + end + + def item_id + params['transaction_id'] + end + + def transaction_id + params['mb_transaction_id'] + end + + # When was this payment received by the client. + def received_at + nil + end + + def payer_email + params['pay_from_email'] + end + + def receiver_email + params['pay_to_email'] + end + + def md5sig + params['md5sig'] + end + + #Unique ID from the merchant's Moneybookers.com account, needed for calculatinon of md5 sig + def merchant_id + params['merchant_id'] + end + + # currency of the payment as posted by the merchant on the entry form + def currency + params['currency'] + end + + # amount of the payment as posted by the merchant on the entry form (ex. 39.60/39.6/39) + def gross + params['amount'] + end + + # currency of mb_amount, will always be the same as the currency of the beneficiary's account at Moneybookers.com + def merchant_currency + params['mb_currency'] + end + + # total amount of the payment in Merchants currency (ex 25.46/25.4/25) + def merchant_amount + params['mb_amount'] + end + + # Was this a test transaction? + def test? + false + end + + def secret + @options[:credential2] + end + + # Acknowledge the transaction to MoneyBooker. This method has to be called after a new + # apc arrives. It will verify that all the information we received is correct and will return a + # ok or a fail. The secret (second credential) has to be provided in the parameter :credential2 + # when instantiating the Notification object. + # + # Example: + # + # def ipn + # notify = Moneybookers.notification(request.raw_post, :credential2 => 'secret') + # + # if notify.acknowledge + # ... process order ... if notify.complete? + # else + # ... log possible hacking attempt ... + # end + def acknowledge + fields = [merchant_id, item_id, Digest::MD5.hexdigest(secret).upcase, merchant_amount, merchant_currency, status_code].join + md5sig == Digest::MD5.hexdigest(fields).upcase + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex.rb new file mode 100644 index 000000000..ff3dce6d9 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex.rb @@ -0,0 +1,88 @@ + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + # To start with Nochex, follow the instructions for installing + # ActiveMerchant as a plugin, as described on + # http://www.activemerchant.org/. + # + # The plugin will automatically add the ActionView helper for + # ActiveMerchant, which will allow you to make the Nochex payments. + # The idea behind the helper is that it generates an invisible + # forwarding screen that will automatically redirect the user. + # So you would collect all the information about the order and then + # simply render the hidden form, which redirects the user to Nochex. + # + # The syntax of the helper is as follows: + # + # <% payment_service_for 'order id', 'nochex_user_id', + # :amount => 50.00, + # :service => :nochex, + # :html => { :id => 'nochex-form' } do |service| %> + # + # <% service.customer :first_name => 'Cody', + # :last_name => 'Fauser', + # :phone => '(555)555-5555', + # :email => 'cody@example.com' %> + # + # <% service.billing_address :city => 'Ottawa', + # :address1 => '21 Snowy Brook Lane', + # :address2 => 'Apt. 36', + # :state => 'ON', + # :country => 'CA', + # :zip => 'K1J1E5' %> + # + # <% service.invoice '#1000' %> + # <% service.shipping '0.00' %> + # <% service.tax '0.00' %> + # + # <% service.notify_url url_for(:action => 'notify', :only_path => false) %> + # <% service.return_url url_for(:action => 'done', :only_path => false) %> + # <% service.cancel_return_url 'http://mystore.com' %> + # <% end %> + # + # The notify_url is the URL that the Nochex IPN will be sent. You can + # handle the notification in your controller action as follows: + # + # class NotificationController < ApplicationController + # include ActiveMerchant::Billing::Integrations + # + # def notify + # notification = Nochex::Notification.new(request.raw_post) + # + # begin + # # Acknowledge notification with Nochex + # raise StandardError, 'Illegal Notification' unless notification.acknowledge + # # Process the payment + # rescue => e + # logger.warn("Illegal notification received: #{e.message}") + # ensure + # head(:ok) + # end + # end + # end + module Nochex + autoload :Return, File.dirname(__FILE__) + '/nochex/return.rb' + autoload :Helper, File.dirname(__FILE__) + '/nochex/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/nochex/notification.rb' + + + mattr_accessor :service_url + self.service_url = 'https://secure.nochex.com' + + mattr_accessor :notification_confirmation_url + self.notification_confirmation_url = 'https://www.nochex.com/nochex.dll/apc/apc' + + # Simply a convenience method that returns a new + # ActiveMerchant::Billing::Integrations::Nochex::Notification + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/helper.rb new file mode 100644 index 000000000..e61948021 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/helper.rb @@ -0,0 +1,68 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Nochex + class Helper < ActiveMerchant::Billing::Integrations::Helper + # Required Parameters + # email + # amount + mapping :account, 'email' + mapping :amount, 'amount' + + # Set the field status = test for testing with accounts: + # Account Password + # test1@nochex.com 123456 + # test2@nochex.com 123456 + # def initialize(order, account, options = {}) + # super + # add_field('status', 'test') + # end + + # Need to format the amount to have 2 decimal places + def amount=(money) + cents = money.respond_to?(:cents) ? money.cents : money + if money.is_a?(String) or cents.to_i <= 0 + raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.' + end + add_field mappings[:amount], sprintf("%.2f", cents.to_f/100) + end + + # Optional Parameters + # ordernumber + mapping :order, 'ordernumber' + + # firstname + # lastname + # email_address_sender + mapping :customer, :first_name => 'firstname', + :last_name => 'lastname', + :email => 'email_address_sender' + + # town + # firstline + # county + # postcode + mapping :billing_address, :city => 'town', + :address1 => 'firstline', + :state => 'county', + :zip => 'postcode' + + # responderurl + mapping :notify_url, 'responderurl' + + # returnurl + mapping :return_url, 'returnurl' + + # cancelurl + mapping :cancel_return_url, 'cancelurl' + + # description + mapping :description, 'description' + + # Currently unmapped + # logo + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/notification.rb new file mode 100644 index 000000000..cf9334b3d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/notification.rb @@ -0,0 +1,94 @@ +require 'net/http' +require 'date' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Nochex + # Parser and handler for incoming Automatic Payment Confirmations from Nochex. + class Notification < ActiveMerchant::Billing::Integrations::Notification + include ActiveMerchant::PostsData + + def complete? + status == 'Completed' + end + + # Id of the order we passed to Nochex + def item_id + params['order_id'] + end + + def transaction_id + params['transaction_id'] + end + + def currency + 'GBP' + end + + # When was this payment received by the client. + def received_at + # U.K. Format: 27/09/2006 22:30:54 + return if params['transaction_date'].blank? + time = params['transaction_date'].scan(/\d+/) + Time.utc(time[2], time[1], time[0], time[3], time[4], time[5]) + end + + def payer_email + params['from_email'] + end + + def receiver_email + params['to_email'] + end + + def security_key + params['security_key'] + end + + # the money amount we received in X.2 decimal. + def gross + sprintf("%.2f", params['amount'].to_f) + end + + # Was this a test transaction? + def test? + params['status'] == 'test' + end + + def status + 'Completed' + end + + # Acknowledge the transaction to Nochex. This method has to be called after a new + # apc arrives. Nochex will verify that all the information we received are correct and will return a + # ok or a fail. This is very similar to the PayPal IPN scheme. + # + # Example: + # + # def nochex_ipn + # notify = NochexNotification.new(request.raw_post) + # + # if notify.acknowledge + # ... process order ... if notify.complete? + # else + # ... log possible hacking attempt ... + # end + def acknowledge + payload = raw + + response = ssl_post(Nochex.notification_confirmation_url, payload, + 'Content-Length' => "#{payload.size}", + 'User-Agent' => "Active Merchant -- http://activemerchant.org", + 'Content-Type' => "application/x-www-form-urlencoded" + ) + + raise StandardError.new("Faulty Nochex result: #{response}") unless ["AUTHORISED", "DECLINED"].include?(response) + + response == "AUTHORISED" + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/return.rb new file mode 100644 index 000000000..c8fd59e1a --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/nochex/return.rb @@ -0,0 +1,10 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Nochex + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/notification.rb new file mode 100644 index 000000000..56df8ee8a --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/notification.rb @@ -0,0 +1,71 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + class Notification + attr_accessor :params + attr_accessor :raw + + # set this to an array in the subclass, to specify which IPs are allowed + # to send requests + class_attribute :production_ips + + # * *Args* : + # - +doc+ -> raw post string + # - +options+ -> custom options which individual implementations can + # utilize + def initialize(post, options = {}) + @options = options + empty! + parse(post) + end + + def status + raise NotImplementedError, "Must implement this method in the subclass" + end + + # the money amount we received in X.2 decimal. + def gross + raise NotImplementedError, "Must implement this method in the subclass" + end + + def gross_cents + (gross.to_f * 100.0).round + end + + # This combines the gross and currency and returns a proper Money object. + # this requires the money library located at http://dist.leetsoft.com/api/money + def amount + return Money.new(gross_cents, currency) rescue ArgumentError + return Money.new(gross_cents) # maybe you have an own money object which doesn't take a currency? + end + + # reset the notification. + def empty! + @params = Hash.new + @raw = "" + end + + # Check if the request comes from an official IP + def valid_sender?(ip) + return true if ActiveMerchant::Billing::Base.integration_mode == :test || production_ips.blank? + production_ips.include?(ip) + end + + def test? + false + end + + private + + # Take the posted data and move the relevant data into a hash + def parse(post) + @raw = post.to_s + for line in @raw.split('&') + key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten + params[key] = CGI.unescape(value) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum.rb new file mode 100644 index 000000000..78ac7483d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum.rb @@ -0,0 +1,44 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + # Documentation: + # https://www.paxum.com/payment_docs/page.php?name=apiIntroduction + module Paxum + autoload :Helper, File.dirname(__FILE__) + '/paxum/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/paxum/notification.rb' + autoload :Return, File.dirname(__FILE__) + '/paxum/return.rb' + autoload :Common, File.dirname(__FILE__) + '/paxum/common.rb' + + mattr_accessor :test_url + self.test_url = 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin' + + mattr_accessor :production_url + self.production_url = 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin' + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'key' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/common.rb new file mode 100644 index 000000000..e158142af --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/common.rb @@ -0,0 +1,24 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paxum + module Common + def generate_signature_string + @raw_post.slice!(0) if @raw_post.starts_with?("&") + @raw_post = CGI.unescape(@raw_post) + @raw_post = "&#{@raw_post}" unless @raw_post.starts_with?("&") + arr = @raw_post.split('&') + arr.delete(arr.last) + data = arr.join('&') + + (data + secret) + end + + def generate_signature + Digest::MD5.hexdigest(generate_signature_string) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/helper.rb new file mode 100644 index 000000000..f69fb96d6 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/helper.rb @@ -0,0 +1,42 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paxum + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + + def initialize(order, account, options = {}) + @paxum_options = options.dup + options.delete(:description) + options.delete(:fail_url) + options.delete(:success_url) + options.delete(:result_url) + super + add_field "button_type_id", "1" + add_field "variables", "notify_url=#{@paxum_options[:result_url]}" + @paxum_options.each do |key, value| + add_field mappings[key], value + end + end + + def form_fields + @fields + end + + def params + @fields + end + + mapping :account, 'business_email' + mapping :amount, 'amount' + mapping :currency, 'currency' + mapping :order, 'item_id' + mapping :description, 'item_name' + mapping :fail_url, 'cancel_url' + mapping :success_url, 'finish_url' + mapping :result_url, 'notify_url' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/notification.rb new file mode 100644 index 000000000..06c5c64b2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paxum/notification.rb @@ -0,0 +1,33 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paxum + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def initialize(post, options = {}) + @raw_post = post.dup + post.slice!(0) + super + end + + def self.recognizes?(params) + (params.has_key?('transaction_item_id') && params.has_key?('transaction_amount')) + end + + def security_key + params["key"] + end + + def secret + @options[:secret] + end + + def acknowledge + (security_key == generate_signature) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast.rb new file mode 100644 index 000000000..6e44ef55f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast.rb @@ -0,0 +1,70 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + # Documentation: + # https://www.payfast.co.za/s/std/integration-guide + module PayFast + autoload :Return, File.dirname(__FILE__) + '/pay_fast/return.rb' + autoload :Helper, File.dirname(__FILE__) + '/pay_fast/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/pay_fast/notification.rb' + autoload :Common, File.dirname(__FILE__) + '/pay_fast/common.rb' + + # Overwrite this if you want to change the PayFast sandbox url + mattr_accessor :process_test_url + self.process_test_url = 'https://sandbox.payfast.co.za/eng/process' + + # Overwrite this if you want to change the PayFast production url + mattr_accessor :process_production_url + self.process_production_url = 'https://www.payfast.co.za/eng/process' + + # Overwrite this if you want to change the PayFast sandbox url + mattr_accessor :validate_test_url + self.validate_test_url = 'https://sandbox.payfast.co.za/eng/query/validate' + + # Overwrite this if you want to change the PayFast production url + mattr_accessor :validate_production_url + self.validate_production_url = 'https://www.payfast.co.za/eng/query/validate' + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'signature' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.process_production_url + when :test + self.process_test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.validate_service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.validate_production_url + when :test + self.validate_test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/common.rb new file mode 100644 index 000000000..d5fb5dc5b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/common.rb @@ -0,0 +1,42 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayFast + module Common + def generate_signature(type) + string = case type + when :request + request_signature_string + when :notify + notify_signature_string + end + + Digest::MD5.hexdigest(string) + end + + def request_attributes + [:merchant_id, :merchant_key, :return_url, :cancel_url, + :notify_url, :name_first, :name_last, :email_address, + :payment_id, :amount, :item_name, :item_description, + :custom_str1, :custom_str2, :custom_str3, :custom_str4, + :custom_str5, :custom_int1, :custom_int2, :custom_int3, + :custom_int4, :custom_int5, :email_confirmation, + :confirmation_address] + end + + def request_signature_string + request_attributes.map do |attr| + "#{mappings[attr]}=#{CGI.escape(@fields[mappings[attr]])}" if @fields[mappings[attr]].present? + end.compact.join('&') + end + + def notify_signature_string + params.map do |key, value| + "#{key}=#{CGI.escape(value)}" unless key == PayFast.signature_parameter_name + end.compact.join('&') + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/helper.rb new file mode 100644 index 000000000..6d1a43c86 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/helper.rb @@ -0,0 +1,50 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayFast + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + + def initialize(order, account, options = {}) + super + add_field('merchant_id', account) + add_field('merchant_key', options.delete(:credential2)) + add_field('m_payment_id', order) + end + + def form_fields + @fields + end + + def params + @fields + end + + mapping :merchant_id, 'merchant_id' + mapping :merchant_key, 'merchant_key' + mapping :return_url, 'return_url' + mapping :cancel_return_url, 'cancel_url' + mapping :notify_url, 'notify_url' + mapping :name_first, 'name_first' + mapping :name_last, 'name_last' + mapping :email_address, 'email_address' + mapping :payment_id, 'm_payment_id' + mapping :amount, 'amount' + mapping :item_name, 'item_name' + mapping :description, 'item_name' + + mapping :customer, :first_name => 'name_first', + :last_name => 'name_last', + :email => 'email_address', + :phone => 'phone' + + 5.times { |i| mapping :"custom_str#{i}", "custom_str#{i}" } + 5.times { |i| mapping :"custom_int#{i}", "custom_int#{i}" } + + mapping :email_confirmation, 'email_confirmation' + mapping :confirmation_address, 'confirmation_address' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/notification.rb new file mode 100644 index 000000000..b0df33321 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/notification.rb @@ -0,0 +1,134 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayFast + # Parser and handler for incoming ITN from PayFast. + # The Example shows a typical handler in a rails application. + # + # Example + # + # class BackendController < ApplicationController + # include ActiveMerchant::Billing::Integrations + # + # def pay_fast_itn + # notify = PayFast::Notification.new(request.raw_post) + # + # order = Order.find(notify.item_id) + # + # if notify.acknowledge + # begin + # + # if notify.complete? and order.total == notify.amount + # order.status = 'success' + # + # shop.ship(order) + # else + # logger.error("Failed to verify Paypal's notification, please investigate") + # end + # + # rescue => e + # order.status = 'failed' + # raise + # ensure + # order.save + # end + # end + # + # render :nothing + # end + # end + class Notification < ActiveMerchant::Billing::Integrations::Notification + include PostsData + include Common + + # Was the transaction complete? + def complete? + status == "Completed" + end + + # Status of transaction. List of possible values: + # <tt>COMPLETE</tt>:: + def status + if params['payment_status'] == "COMPLETE" + "Completed" + else + "Failed" + end + end + + # Id of this transaction (uniq PayFast transaction id) + def transaction_id + params['pf_payment_id'] + end + + # Id of this transaction (uniq Shopify transaction id) + def item_id + params['m_payment_id'] + end + + # The total amount which the payer paid. + def gross + params['amount_gross'] + end + + # The total in fees which was deducated from the amount. + def fee + params['amount_fee'] + end + + # The net amount credited to the receiver's account. + def amount + params['amount_net'] + end + + # The name of the item being charged for. + def item_name + params['item_name'] + end + + # The Merchant ID as given by the PayFast system. Used to uniquely identify the receiver's account. + def merchant_id + params['merchant_id'] + end + + def currency + nil + end + # Generated hash depends on params order so use OrderedHash instead of Hash + def empty! + super + @params = ActiveSupport::OrderedHash.new + end + + # Acknowledge the transaction to PayFast. This method has to be called after a new + # ITN arrives. PayFast will verify that all the information we received are correct and will return a + # VERIFIED or INVALID status. + # + # Example: + # + # def pay_fast_itn + # notify = PayFastNotification.new(request.raw_post) + # + # if notify.acknowledge + # ... process order ... if notify.complete? + # else + # ... log possible hacking attempt ... + # end + def acknowledge + if params[PayFast.signature_parameter_name] == generate_signature(:notify) + response = ssl_post(PayFast.validate_service_url, notify_signature_string, + 'Content-Type' => "application/x-www-form-urlencoded", + 'Content-Length' => "#{notify_signature_string.size}" + ) + raise StandardError.new("Faulty PayFast result: #{response}") unless ['VALID', 'INVALID'].include?(response) + + response == "VALID" + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/return.rb new file mode 100644 index 000000000..505dc14fa --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pay_fast/return.rb @@ -0,0 +1,10 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayFast + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link.rb new file mode 100644 index 000000000..875eac688 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link.rb @@ -0,0 +1,21 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayflowLink + autoload :Helper, 'active_merchant/billing/integrations/payflow_link/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/payflow_link/notification.rb' + + mattr_accessor :service_url + self.service_url = 'https://payflowlink.paypal.com' + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + ActiveMerchant::Billing::Integrations::Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/helper.rb new file mode 100644 index 000000000..29326dcf7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/helper.rb @@ -0,0 +1,116 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayflowLink + class Helper < ActiveMerchant::Billing::Integrations::Helper + include PostsData + + def initialize(order, account, options = {}) + super + add_field('login', account) + add_field('echodata', 'True') + add_field('user2', self.test?) + add_field('invoice', order) + add_field('vendor', account) + add_field('user', options[:credential4].presence || account) + add_field('trxtype', options[:transaction_type] || 'S') + end + + mapping :account, 'login' + mapping :credential2, 'pwd' + mapping :credential3, 'partner' + mapping :order, 'user1' + + mapping :amount, 'amt' + + + mapping :billing_address, :city => 'city', + :address => 'address', + :state => 'state', + :zip => 'zip', + :country => 'country', + :phone => 'phone', + :name => 'name' + + mapping :customer, { :first_name => 'first_name', :last_name => 'last_name' } + + def description(value) + add_field('description', normalize("#{value}").delete("#")) + end + + def customer(params = {}) + add_field(mappings[:customer][:first_name], params[:first_name]) + add_field(mappings[:customer][:last_name], params[:last_name]) + end + + def billing_address(params = {}) + # Get the country code in the correct format + # Use what we were given if we can't find anything + country_code = lookup_country_code(params.delete(:country)) + add_field(mappings[:billing_address][:country], country_code) + + add_field(mappings[:billing_address][:address], [params.delete(:address1), params.delete(:address2)].compact.join(' ')) + + province_code = params.delete(:state) + add_field(mappings[:billing_address][:state], province_code.blank? ? 'N/A' : province_code.upcase) + + # Everything else + params.each do |k, v| + field = mappings[:billing_address][k] + add_field(field, v) unless field.nil? + end + end + + def form_fields + token, token_id = request_secure_token + + {"securetoken" => token, "securetokenid" => token_id, "mode" => test? ? "test" : "live"} + end + + private + + def secure_token_id + @secure_token_id ||= Utils.generate_unique_id + end + + def secure_token_url + test? ? "https://pilot-payflowpro.paypal.com" : "https://payflowpro.paypal.com" + end + + def request_secure_token + @fields["securetokenid"] = secure_token_id + @fields["createsecuretoken"] = "Y" + + fields = @fields.collect {|key, value| "#{key}[#{value.length}]=#{value}" }.join("&") + + response = ssl_post(secure_token_url, fields) + + parse_response(response) + end + + def parse_response(response) + response = response.split("&").inject({}) do |hash, param| + key, value = param.split("=") + hash[key] = value + hash + end + + [response['SECURETOKEN'], response['SECURETOKENID']] if response['RESPMSG'] && response['RESPMSG'].downcase == "approved" + end + + def normalize(text) + return unless text + + if ActiveSupport::Inflector.method(:transliterate).arity == -2 + ActiveSupport::Inflector.transliterate(text,'') + elsif RUBY_VERSION >= '1.9' + text.gsub(/[^\x00-\x7F]+/, '') + else + ActiveSupport::Inflector.transliterate(text).to_s + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/notification.rb new file mode 100644 index 000000000..f28798694 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payflow_link/notification.rb @@ -0,0 +1,78 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayflowLink + class Notification < ActiveMerchant::Billing::Integrations::Notification + + # Was the transaction complete? + def complete? + status == "Completed" + end + + # When was this payment received by the client. + # sometimes it can happen that we get the notification much later. + # One possible scenario is that our web application was down. In this case paypal tries several + # times an hour to inform us about the notification + def received_at + DateTime.parse(params['TRANSTIME']) if params['TRANSTIME'] + rescue ArgumentError + nil + end + + def status + params['RESPMSG'] + end + + # Id of this transaction (paypal number) + def transaction_id + params['PNREF'] + end + + # What type of transaction are we dealing with? + def type + params['TYPE'] + end + + # the money amount we received in X.2 decimal. + def gross + params['AMT'] + end + + # What currency have we been dealing with + def currency + nil + end + + def status + params['RESULT'] == '0' ? 'Completed' : 'Failed' + end + + # This is the item number which we submitted to paypal + def item_id + params['USER1'] + end + + # This is the invoice which you passed to paypal + def invoice + params['INVNUM'] + end + + # Was this a test transaction? + def test? + params['USER2'] == 'true' + end + + def account + params["ACCT"] + end + + def acknowledge + true + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal.rb new file mode 100644 index 000000000..13a1d277b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal.rb @@ -0,0 +1,39 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paypal + autoload :Return, 'active_merchant/billing/integrations/paypal/return.rb' + autoload :Helper, 'active_merchant/billing/integrations/paypal/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/paypal/notification.rb' + + # Overwrite this if you want to change the Paypal test url + mattr_accessor :test_url + self.test_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr' + + # Overwrite this if you want to change the Paypal production url + mattr_accessor :production_url + self.production_url = 'https://www.paypal.com/cgi-bin/webscr' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/helper.rb new file mode 100644 index 000000000..c905ae759 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/helper.rb @@ -0,0 +1,119 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paypal + class Helper < ActiveMerchant::Billing::Integrations::Helper + CANADIAN_PROVINCES = { 'AB' => 'Alberta', + 'BC' => 'British Columbia', + 'MB' => 'Manitoba', + 'NB' => 'New Brunswick', + 'NL' => 'Newfoundland', + 'NS' => 'Nova Scotia', + 'NU' => 'Nunavut', + 'NT' => 'Northwest Territories', + 'ON' => 'Ontario', + 'PE' => 'Prince Edward Island', + 'QC' => 'Quebec', + 'SK' => 'Saskatchewan', + 'YT' => 'Yukon' + } + # See https://www.paypal.com/IntegrationCenter/ic_std-variable-reference.html for details on the following options. + mapping :order, [ 'item_number', 'custom' ] + + def initialize(order, account, options = {}) + super + add_field('cmd', '_ext-enter') + add_field('redirect_cmd', '_xclick') + add_field('quantity', 1) + add_field('item_name', 'Store purchase') + add_field('no_shipping', '1') + add_field('no_note', '1') + add_field('charset', 'utf-8') + add_field('address_override', '0') + add_field('bn', application_id.to_s.slice(0,32)) unless application_id.blank? + end + + mapping :amount, 'amount' + mapping :account, 'business' + mapping :currency, 'currency_code' + mapping :notify_url, 'notify_url' + mapping :return_url, 'return' + mapping :cancel_return_url, 'cancel_return' + mapping :invoice, 'invoice' + mapping :item_name, 'item_name' + mapping :quantity, 'quantity' + mapping :no_shipping, 'no_shipping' + mapping :no_note, 'no_note' + mapping :address_override, 'address_override' + + mapping :application_id, 'bn' + + mapping :customer, :first_name => 'first_name', + :last_name => 'last_name', + :email => 'email' + + mapping :shipping_address, :city => 'city', + :address1 => 'address1', + :address2 => 'address2', + :state => 'state', + :zip => 'zip', + :country => 'country' + + def shipping_address(params = {}) + + # Get the country code in the correct format + # Use what we were given if we can't find anything + country_code = lookup_country_code(params.delete(:country)) + add_field(mappings[:shipping_address][:country], country_code) + + if params.has_key?(:phone) + phone = params.delete(:phone).to_s + + # Whipe all non digits + phone.gsub!(/\D+/, '') + + if ['US', 'CA'].include?(country_code) && phone =~ /(\d{3})(\d{3})(\d{4})$/ + add_field('night_phone_a', $1) + add_field('night_phone_b', $2) + add_field('night_phone_c', $3) + else + add_field('night_phone_b', phone) + end + end + + province_code = params.delete(:state) + + case country_code + when 'CA' + add_field(mappings[:shipping_address][:state], CANADIAN_PROVINCES[province_code.upcase]) unless province_code.nil? + when 'US' + add_field(mappings[:shipping_address][:state], province_code) + else + add_field(mappings[:shipping_address][:state], province_code.blank? ? 'N/A' : province_code) + end + + # Everything else + params.each do |k, v| + field = mappings[:shipping_address][k] + add_field(field, v) unless field.nil? + end + end + + mapping :tax, 'tax' + mapping :shipping, 'shipping' + mapping :cmd, 'cmd' + mapping :custom, 'custom' + mapping :src, 'src' + mapping :sra, 'sra' + %w(a p t).each do |l| + (1..3).each do |i| + mapping "#{l}#{i}".to_sym, "#{l}#{i}" + end + end + end + end + end + end +end + + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/notification.rb new file mode 100644 index 000000000..7fdb217da --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/notification.rb @@ -0,0 +1,227 @@ +require 'net/http' +require 'time' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paypal + # Parser and handler for incoming Instant payment notifications from paypal. + # The Example shows a typical handler in a rails application. Note that this + # is an example, please read the Paypal API documentation for all the details + # on creating a safe payment controller. + # + # Example + # + # class BackendController < ApplicationController + # include ActiveMerchant::Billing::Integrations + # + # def paypal_ipn + # notify = Paypal::Notification.new(request.raw_post) + # + # if notify.masspay? + # masspay_items = notify.items + # end + # + # order = Order.find(notify.item_id) + # + # if notify.acknowledge + # begin + # + # if notify.complete? and order.total == notify.amount + # order.status = 'success' + # + # shop.ship(order) + # else + # logger.error("Failed to verify Paypal's notification, please investigate") + # end + # + # rescue => e + # order.status = 'failed' + # raise + # ensure + # order.save + # end + # end + # + # render :nothing + # end + # end + class Notification < ActiveMerchant::Billing::Integrations::Notification + include PostsData + + def initialize(post, options = {}) + super + extend MassPayNotification if masspay? + end + + # Was the transaction complete? + def complete? + status == "Completed" + end + + # Is it a masspay notification? + def masspay? + type == "masspay" + end + + # When was this payment received by the client. + # sometimes it can happen that we get the notification much later. + # One possible scenario is that our web application was down. In this case paypal tries several + # times an hour to inform us about the notification + def received_at + parsed_time_fields = DateTime._strptime(params['payment_date'], "%H:%M:%S %b %d, %Y %Z") + Time.gm( + parsed_time_fields[:year], + parsed_time_fields[:mon], + parsed_time_fields[:mday], + parsed_time_fields[:hour], + parsed_time_fields[:min], + parsed_time_fields[:sec] + ) + Time.zone_offset(parsed_time_fields[:zone]) + end + + # Status of transaction. List of possible values: + # <tt>Canceled-Reversal</tt>:: + # <tt>Completed</tt>:: + # <tt>Denied</tt>:: + # <tt>Expired</tt>:: + # <tt>Failed</tt>:: + # <tt>In-Progress</tt>:: + # <tt>Partially-Refunded</tt>:: + # <tt>Pending</tt>:: + # <tt>Processed</tt>:: + # <tt>Refunded</tt>:: + # <tt>Reversed</tt>:: + # <tt>Voided</tt>:: + def status + params['payment_status'] + end + + # Id of this transaction (paypal number) + def transaction_id + params['txn_id'] + end + + # What type of transaction are we dealing with? + # "cart" "send_money" "web_accept" are possible here. + def type + params['txn_type'] + end + + # the money amount we received in X.2 decimal. + def gross + params['mc_gross'] + end + + # the markup paypal charges for the transaction + def fee + params['mc_fee'] + end + + # What currency have we been dealing with + def currency + params['mc_currency'] + end + + # This is the item number which we submitted to paypal + # The custom field is also mapped to item_id because PayPal + # doesn't return item_number in dispute notifications + def item_id + params['item_number'] || params['custom'] + end + + # This is the invoice which you passed to paypal + def invoice + params['invoice'] + end + + # Was this a test transaction? + def test? + params['test_ipn'] == '1' + end + + def account + params['business'] || params['receiver_email'] + end + + # Acknowledge the transaction to paypal. This method has to be called after a new + # ipn arrives. Paypal will verify that all the information we received are correct and will return a + # ok or a fail. + # + # Example: + # + # def paypal_ipn + # notify = PaypalNotification.new(request.raw_post) + # + # if notify.acknowledge + # ... process order ... if notify.complete? + # else + # ... log possible hacking attempt ... + # end + def acknowledge + payload = raw + + response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload, + 'Content-Length' => "#{payload.size}", + 'User-Agent' => "Active Merchant -- http://activemerchant.org" + ) + + raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response) + + response == "VERIFIED" + end + end + + module MassPayNotification + # Mass pay returns a collection of MassPay Items, so inspect items to get the values + def transaction_id + end + + # Mass pay returns a collection of MassPay Items, so inspect items to get the values + def gross + end + + # Mass pay returns a collection of MassPay Items, so inspect items to get the values + def fee + end + + # Mass pay returns a collection of MassPay Items, so inspect items to get the values + def currency + end + + # Mass pay returns a collection of MassPay Items, so inspect items to get the values + def item_id + end + + # Mass pay returns a collection of MassPay Items, so inspect items to get the values + def account + end + + # Collection of notification items returned for MassPay transactions + def items + @items ||= (1..number_of_mass_pay_items).map do |item_number| + MassPayItem.new( + params["masspay_txn_id_#{item_number}"], + params["mc_gross_#{item_number}"], + params["mc_fee_#{item_number}"], + params["mc_currency_#{item_number}"], + params["unique_id_#{item_number}"], + params["receiver_email_#{item_number}"], + params["status_#{item_number}"] + ) + end + end + + private + + def number_of_mass_pay_items + @number_of_mass_pay_items ||= params.keys.select { |k| k.start_with? 'masspay_txn_id' }.size + end + end + + class MassPayItem < Struct.new(:transaction_id, :gross, :fee, :currency, :item_id, :account, :status) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/return.rb new file mode 100644 index 000000000..2b22a06a6 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal/return.rb @@ -0,0 +1,10 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paypal + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb new file mode 100644 index 000000000..f1741421e --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb @@ -0,0 +1,20 @@ +module ActiveMerchant + module Billing + module Integrations + module PaypalPaymentsAdvanced + autoload :Helper, 'active_merchant/billing/integrations/paypal_payments_advanced/helper.rb' + + mattr_accessor :service_url + self.service_url = 'https://payflowlink.paypal.com' + + def self.notification(post, options = {}) + PayflowLink::Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb new file mode 100644 index 000000000..cd1889999 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb @@ -0,0 +1,15 @@ +module ActiveMerchant + module Billing + module Integrations + module PaypalPaymentsAdvanced + class Helper < PayflowLink::Helper + + def initialize(order, account, options) + super + add_field('partner', 'PayPal') + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy.rb new file mode 100644 index 000000000..d74db8c4b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy.rb @@ -0,0 +1,36 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paysbuy + autoload :Helper, File.dirname(__FILE__) + '/paysbuy/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/paysbuy/notification.rb' + + mattr_accessor :test_url + self.test_url = 'https://demo.paysbuy.com/paynow.aspx' + + mattr_accessor :production_url + self.production_url = 'https://www.paysbuy.com/paynow.aspx' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/helper.rb new file mode 100644 index 000000000..3cd3430b1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/helper.rb @@ -0,0 +1,15 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paysbuy + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :account, 'biz' + mapping :amount, 'amt' + mapping :order, 'inv' + mapping :description, 'itm' + mapping :notify_url, 'postURL' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/notification.rb new file mode 100644 index 000000000..c7121a963 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/paysbuy/notification.rb @@ -0,0 +1,28 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Paysbuy + class Notification < ActiveMerchant::Billing::Integrations::Notification + SUCCESS = '00' + FAIL = '99' + + def complete? + status == 'Completed' + end + + def item_id + params['result'][2..-1] + end + + def status + params['result'][0..1] == SUCCESS ? 'Completed' : 'Failed' + end + + def acknowledge + true + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in.rb new file mode 100755 index 000000000..74933f1c7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in.rb @@ -0,0 +1,43 @@ +require 'digest/sha2' +require 'bigdecimal' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayuIn + autoload :Return, 'active_merchant/billing/integrations/payu_in/return.rb' + autoload :Helper, 'active_merchant/billing/integrations/payu_in/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/payu_in/notification.rb' + + mattr_accessor :test_url + mattr_accessor :production_url + + self.test_url = 'https://test.payu.in/_payment.php' + self.production_url = 'https://secure.payu.in/_payment.php' + + def self.service_url + ActiveMerchant::Billing::Base.integration_mode == :production ? self.production_url : self.test_url + end + + def self.notification(post, options = {}) + Notification.new(post, options) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + + def self.checksum(merchant_id, secret_key, *payload_items ) + options = payload_items.pop if Hash === payload_items.last + options ||= {} + payload = if options[:reverse] then + payload_items.dup.push( merchant_id || "" ).unshift( secret_key || "" ).collect{ |x| x.to_s }.join("|") + else + payload_items.dup.unshift( merchant_id || "" ).push( secret_key || "" ).collect{ |x| x.to_s }.join("|") + end + Digest::SHA512.hexdigest( payload ) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/helper.rb new file mode 100755 index 000000000..32048d309 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/helper.rb @@ -0,0 +1,74 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayuIn + class Helper < ActiveMerchant::Billing::Integrations::Helper + + mapping :amount, 'amount' + mapping :account, 'key' + mapping :order, 'txnid' + mapping :credential2, 'productinfo' + + mapping :customer, :first_name => 'firstname', + :last_name => 'lastname', + :email => 'email', + :phone => 'phone' + + mapping :billing_address, :city => 'city', + :address1 => 'address1', + :address2 => 'address2', + :state => 'state', + :zip => 'zip', + :country => 'country' + + # Which tab you want to be open default on PayU + # CC (CreditCard) or NB (NetBanking) + mapping :mode, 'pg' + + mapping :notify_url, 'notify_url' + mapping :return_url, ['surl', 'furl'] + mapping :cancel_return_url, 'curl' + mapping :checksum, 'hash' + + mapping :user_defined, { :var1 => 'udf1', + :var2 => 'udf2', + :var3 => 'udf3', + :var4 => 'udf4', + :var5 => 'udf5', + :var6 => 'udf6', + :var7 => 'udf7', + :var8 => 'udf8', + :var9 => 'udf9', + :var10 => 'udf10' + } + + def initialize(order, account, options = {}) + super + self.pg = 'CC' + end + + def form_fields + @fields.merge(mappings[:checksum] => generate_checksum) + end + + def generate_checksum( options = {} ) + checksum_fields = [ :order, :amount, :credential2, { :customer => [ :first_name, :email ] }, + { :user_defined => [ :var1, :var2, :var3, :var4, :var5, :var6, :var7, :var8, :var9, :var10 ] } ] + checksum_payload_items = checksum_fields.inject( [] ) do | items, field | + if Hash === field then + key = field.keys.first + field[key].inject( items ){ |s,x| items.push( @fields[ mappings[key][x] ] ) } + else + items.push( @fields[ mappings[field] ] ) + end + end + checksum_payload_items.push( options ) + PayuIn.checksum(@fields["key"], @fields["productinfo"], *checksum_payload_items ) + end + + end + + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/notification.rb new file mode 100755 index 000000000..385017862 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/notification.rb @@ -0,0 +1,167 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayuIn + class Notification < ActiveMerchant::Billing::Integrations::Notification + + def initialize(post, options = {}) + super(post, options) + @merchant_id = options[:credential1] + @secret_key = options[:credential2] + end + + def complete? + status == "success" + end + + # Status of the transaction. List of possible values: + # <tt>invalid</tt>:: transaction id is not present + # <tt>tampered</tt>:: transaction data has been tampered + # <tt>success</tt>:: transaction successful + # <tt>pending</tt>:: transaction is pending for some approval + # <tt>failure</tt>:: transaction failure + def status + @status ||= if checksum_ok? + if transaction_id.blank? + 'invalid' + else + transaction_status.downcase + end + else + 'tampered' + end + end + + def invoice_ok?( order_id ) + order_id.to_s == invoice.to_s + end + + # Order amount should be equal to gross - discount + def amount_ok?( order_amount, order_discount = BigDecimal.new( '0.0' ) ) + BigDecimal.new( gross ) == order_amount && BigDecimal.new( discount ) == order_discount + end + + # Status of transaction return from the PayU. List of possible values: + # <tt>SUCCESS</tt>:: + # <tt>PENDING</tt>:: + # <tt>FAILURE</tt>:: + def transaction_status + params['status'] + end + + # ID of this transaction (PayU.in number) + def transaction_id + params['mihpayid'] + end + + # Mode of Payment + # + # 'CC' for credit-card + # 'NB' for net-banking + # 'CD' for cheque or DD + # 'CO' for Cash Pickup + def type + params['mode'] + end + + # What currency have we been dealing with + def currency + 'INR' + end + + def item_id + params['txnid'] + end + + # This is the invoice which you passed to PayU.in + def invoice + params['txnid'] + end + + # Merchant Id provided by the PayU.in + def account + params['key'] + end + + # original amount send by merchant + def gross + params['amount'] + end + + # This is discount given to user - based on promotion set by merchants. + def discount + params['discount'] + end + + # Description offer for what PayU given the offer to user - based on promotion set by merchants. + def offer_description + params['offer'] + end + + # Information about the product as send by merchant + def product_info + params['productinfo'] + end + + # Email of the customer + def customer_email + params['email'] + end + + # Phone of the customer + def customer_phone + params['phone'] + end + + # Firstname of the customer + def customer_first_name + params['firstname'] + end + + # Lastname of the customer + def customer_last_name + params['lastname'] + end + + # Full address of the customer + def customer_address + { :address1 => params['address1'], :address2 => params['address2'], + :city => params['city'], :state => params['state'], + :country => params['country'], :zipcode => params['zipcode'] } + end + + def user_defined + return @user_defined if @user_defined + @user_defined = [] + 10.times{ |i| @user_defined.push( params[ "udf#{i+1}" ] ) } + @user_defined + end + + def checksum + params['hash'] + end + + def message + @message || params['error'] + end + + def acknowledge + checksum_ok? + end + + def checksum_ok? + fields = user_defined.dup.push( customer_email, customer_first_name, product_info, gross, invoice, :reverse => true ) + fields.unshift( transaction_status ) + unless PayuIn.checksum(@merchant_id, @secret_key, *fields ) == checksum + @message = 'Return checksum not matching the data provided' + return false + end + true + end + + end + end + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/return.rb new file mode 100755 index 000000000..c7a6d25c5 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/payu_in/return.rb @@ -0,0 +1,53 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module PayuIn + class Return < ActiveMerchant::Billing::Integrations::Return + + def initialize(query_string, options = {}) + super + @notification = Notification.new(query_string, options) + end + + # PayU Transaction Id + # + def transaction_id + @notification.transaction_id + end + + # Returns the status of the transaction as a string + # The status can be one of the following + # + # invalid - transaction id not present + # tampered - checksum does not mismatch + # mismatch - order id mismatch + # success - transaction success + # pending - transaction pending + # failure - transaction failure + # + # payu does not put the discount field in the checksum + # it can be easily forged by the attacker without detection + # + def status( order_id, order_amount ) + if @notification.invoice_ok?( order_id ) && @notification.amount_ok?( BigDecimal.new(order_amount) ) + @notification.status + else + 'mismatch' + end + end + + # check success of the transaction + # check order_id and + def success? + status( @params['txnid'], @params['amount'] ) == 'success' + end + + def message + @notification.message + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay.rb new file mode 100644 index 000000000..4973a9c96 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay.rb @@ -0,0 +1,31 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Pxpay + autoload :Helper, 'active_merchant/billing/integrations/pxpay/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/pxpay/notification.rb' + autoload :Return, 'active_merchant/billing/integrations/pxpay/return.rb' + + TOKEN_URL = 'https://sec.paymentexpress.com/pxpay/pxaccess.aspx' + + LIVE_URL = 'https://sec.paymentexpress.com/pxpay/pxpay.aspx' + + def self.token_url + TOKEN_URL + end + + def self.service_url + LIVE_URL + end + + def self.notification(post, options={}) + Notification.new(post, options) + end + + def self.return(query_string, options={}) + Return.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/helper.rb new file mode 100644 index 000000000..62214eccd --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/helper.rb @@ -0,0 +1,111 @@ +require 'active_support/core_ext/float/rounding.rb' # Float#round(precision) + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Pxpay + # An example. Note the username as a parameter and transaction key you + # will want to use later. The amount that you pass in will be *rounded*, + # so preferably pass in X.2 decimal so that no rounding occurs. You need + # to set :credential2 to your PxPay secret key. + # + # PxPay accounts have Failproof Notification enabled by default which means + # in addition to the user being redirected to your return_url, the return_url will + # be accessed by the PxPay servers directly, immediately after transaction success. + # + # payment_service_for('order_id', 'pxpay_user_ID', :service => :pxpay, + # :amount => 157.0, :currency => 'USD', :credential2 => 'pxpay_key') do |service| + # + # service.customer :email => 'customer@email.com' + # + # service.description 'Order 123 for MyStore' + # + # # Must specify both a return_url or PxPay will show an error instead of + # # capturing credit card details. + # + # service.return_url "http://t/pxpay/payment_received_notification_sub_step" + # + # # These fields will be copied verbatim to the Notification + # service.custom1 'custom text 1' + # service.custom2 '' + # service.custom3 '' + # # See the helper.rb file for various custom fields + # end + + class Helper < ActiveMerchant::Billing::Integrations::Helper + include PostsData + mapping :account, 'PxPayUserId' + mapping :credential2, 'PxPayKey' + mapping :currency, 'CurrencyInput' + mapping :description, 'MerchantReference' + mapping :order, 'TxnId' + mapping :customer, :email => 'EmailAddress' + + mapping :custom1, 'TxnData1' + mapping :custom2, 'TxnData2' + mapping :custom3, 'TxnData3' + + def initialize(order, account, options = {}) + super + add_field 'AmountInput', "%.2f" % options[:amount].to_f.round(2) + add_field 'EnableAddBillCard', '0' + add_field 'TxnType', 'Purchase' + end + + def return_url(url) + add_field 'UrlSuccess', url + add_field 'UrlFail', url + end + + def form_fields + # if either return URLs are blank PxPay will generate a token but redirect user to error page. + raise "error - must specify return_url" if @fields['UrlSuccess'].blank? + raise "error - must specify cancel_return_url" if @fields['UrlFail'].blank? + + result = request_secure_redirect + raise "error - failed to get token - message was #{result[:redirect]}" unless result[:valid] == "1" + + url = URI.parse(result[:redirect]) + + CGI.parse(url.query) + end + + def form_method + "GET" + end + + private + def generate_request + xml = REXML::Document.new + root = xml.add_element('GenerateRequest') + + @fields.each do | k, v | + v = v.slice(0, 50) if k == "MerchantReference" + root.add_element(k).text = v + end + + xml.to_s + end + + def request_secure_redirect + request = generate_request + + response = ssl_post(Pxpay.token_url, request) + xml = REXML::Document.new(response) + root = REXML::XPath.first(xml, "//Request") + valid = root.attributes["valid"] + redirect = root.elements["URI"].text + + # example positive response: + # <Request valid="1"><URI>https://sec.paymentexpress.com/pxpay/pxpay.aspx?userid=PxpayUser&amp;request=REQUEST_TOKEN</URI></Request> + + # example negative response: + # <Request valid="0"><URI>Invalid TxnType</URI></Request> + + {:valid => valid, :redirect => redirect} + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/notification.rb new file mode 100644 index 000000000..14c075f28 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/notification.rb @@ -0,0 +1,157 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + module Pxpay + class Notification < ActiveMerchant::Billing::Integrations::Notification + include PostsData + include RequiresParameters + + def initialize(query_string, options={}) + # PxPay appends ?result=...&userid=... to whatever return_url was specified, even if that URL ended with a ?query. + # So switch the first ? if present to a & + query_string[/\?/] = '&' if query_string[/\?/] + super + + @encrypted_params = @params + @params = {} + + requires! @encrypted_params, "result" + requires! @options, :credential1, :credential2 + + decrypt_transaction_result(@encrypted_params["result"]) + end + + # was the notification a validly formed request? + def acknowledge + @valid == '1' + end + + def status + return 'Failed' unless success? + return 'Completed' if complete? + 'Error' + end + + def complete? + @params['TxnType'] == 'Purchase' && success? + end + + def cancelled? + !success? + end + + # for field definitions see + # http://www.paymentexpress.com/Technical_Resources/Ecommerce_Hosted/PxPay + + def success? + @params['Success'] == '1' + end + + def gross + @params['AmountSettlement'] + end + + def currency + @params['CurrencySettlement'] + end + + def account + @params['userid'] + end + + def item_id + @params['TxnId'] + end + + def currency_input + @params['CurrencyInput'] + end + + def auth_code + @params['AuthCode'] + end + + def card_type + @params['CardName'] + end + + def card_holder_name + @params['CardHolderName'] + end + + def card_number + @params['CardNumber'] + end + + def expiry_date + @params['DateExpiry'] + end + + def client_ip + @params['ClientInfo'] + end + + def order_id + item_id + end + + def payer_email + @params['EmailAddress'] + end + + def transaction_id + @params['DpsTxnRef'] + end + + def settlement_date + @params['DateSettlement'] + end + + # Indication of the uniqueness of a card number + def txn_mac + @params['TxnMac'] + end + + def message + @params['ResponseText'] + end + + def optional_data + [@params['TxnData1'],@fields['TxnData2'],@fields['TxnData3']] + end + + # When was this payment was received by the client. + def received_at + settlement_date + end + + # Was this a test transaction? + def test? + nil + end + + private + + def decrypt_transaction_result(encrypted_result) + request_xml = REXML::Document.new + root = request_xml.add_element('ProcessResponse') + + root.add_element('PxPayUserId').text = @options[:credential1] + root.add_element('PxPayKey').text = @options[:credential2] + root.add_element('Response').text = encrypted_result + + @raw = ssl_post(Pxpay.token_url, request_xml.to_s) + + response_xml = REXML::Document.new(@raw) + root = REXML::XPath.first(response_xml) + @valid = root.attributes["valid"] + @params = {} + root.elements.each { |e| @params[e.name] = e.text } + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/return.rb new file mode 100644 index 000000000..39a546cf3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/pxpay/return.rb @@ -0,0 +1,25 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Pxpay + class Return < ActiveMerchant::Billing::Integrations::Return + def initialize(query_string, options={}) + @notification = Notification.new(query_string, options) + end + + def success? + @notification && @notification.complete? + end + + def cancelled? + @notification && @notification.cancelled? + end + + def message + @notification.message + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay.rb new file mode 100644 index 000000000..74884f8e8 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay.rb @@ -0,0 +1,21 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Quickpay + autoload :Helper, File.dirname(__FILE__) + '/quickpay/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/quickpay/notification.rb' + + mattr_accessor :service_url + self.service_url = 'https://secure.quickpay.dk/form/' + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/helper.rb new file mode 100644 index 000000000..8e3f61eb1 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/helper.rb @@ -0,0 +1,74 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Quickpay + class Helper < ActiveMerchant::Billing::Integrations::Helper + + def initialize(order, account, options = {}) + md5secret options.delete(:credential2) + super + add_field('protocol', '6') + add_field('msgtype', 'authorize') + add_field('language', 'da') + add_field('autocapture', 0) + add_field('testmode', test? ? 1 : 0) + add_field('ordernumber', format_order_number(order)) + end + + def md5secret(value) + @md5secret = value + end + + def form_fields + @fields.merge('md5check' => generate_md5check) + end + + def generate_md5string + MD5_CHECK_FIELDS.map {|key| @fields[key.to_s]} * "" + @md5secret + end + + def generate_md5check + Digest::MD5.hexdigest(generate_md5string) + end + + # Limited to 20 digits max + def format_order_number(number) + number.to_s.gsub(/[^\w]/, '').rjust(4, "0")[0...20] + end + + MD5_CHECK_FIELDS = [ + :protocol, :msgtype, :merchant, :language, :ordernumber, + :amount, :currency, :continueurl, :cancelurl, :callbackurl, + :autocapture, :cardtypelock, :description, :ipaddress, :testmode, + :deadline, :cardhash + ] + + mapping :protocol, 'protocol' + mapping :msgtype, 'msgtype' + mapping :account, 'merchant' + mapping :language, 'language' + mapping :amount, 'amount' + mapping :currency, 'currency' + + mapping :return_url, 'continueurl' + mapping :cancel_return_url, 'cancelurl' + mapping :notify_url, 'callbackurl' + + mapping :autocapture, 'autocapture' + mapping :cardtypelock, 'cardtypelock' + + mapping :description, 'description' + mapping :ipaddress, 'ipaddress' + mapping :testmode, 'testmode' + mapping :deadline, 'deadline' + mapping :cardhash, 'cardhash' + + mapping :customer, '' + mapping :billing_address, {} + mapping :tax, '' + mapping :shipping, '' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/notification.rb new file mode 100644 index 000000000..0d5da68a9 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/quickpay/notification.rb @@ -0,0 +1,137 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Quickpay + class Notification < ActiveMerchant::Billing::Integrations::Notification + def complete? + status == '000' + end + + def item_id + params['ordernumber'] + end + + def transaction_id + params['transaction'] + end + + def received_at + time = params['time'] + # If time only contains 12 integers then it's pre v5 format + time = "20#{params['time']}" if /[0-9]{12}/.match(params['time']) + Time.parse(time) + end + + def gross + "%.2f" % (gross_cents / 100.0) + end + + def gross_cents + params['amount'].to_i + end + + def status + params['qpstat'] + end + + def currency + params['currency'] + end + + # Provide access to raw fields from quickpay + %w( + msgtype + ordernumber + state + chstat + chstatmsg + qpstat + qpstatmsg + merchant + merchantemail + cardtype + cardnumber + cardhash + cardexpire + splitpayment + fraudprobability + fraudremarks + fraudreport + fee + ).each do |attr| + define_method(attr) do + params[attr] + end + end + + MD5_CHECK_FIELDS = [ + :msgtype, + :ordernumber, + :amount, + :currency, + :time, + :state, + :qpstat, + :qpstatmsg, + :chstat, + :chstatmsg, + :merchant, + :merchantemail, + :transaction, + :cardtype, + :cardnumber, + :cardhash, + :cardexpire, + :splitpayment, + :fraudprobability, + :fraudremarks, + :fraudreport, + :fee + ] + + def generate_md5string + MD5_CHECK_FIELDS.map { |key| params[key.to_s] } * "" + @options[:credential2].to_s + end + + def generate_md5check + Digest::MD5.hexdigest(generate_md5string) + end + + # Quickpay doesn't do acknowledgements of callback notifications + # Instead it uses and MD5 hash of all parameters + def acknowledge + generate_md5check == params['md5check'] + end + + # Take the posted data and move the relevant data into a hash + def parse(post) + # 30 + 12 + #------------------------------8a827a0e6829 + #Content-Disposition: form-data; name="msgtype" + # + #subscribe + #------------------------------8a827a0e6829 + #Content-Disposition: form-data; name="ordernumber" + # + #BILP94406 + + if post =~ /-{20,40}\w{6,24}/ + @raw = post.to_s + post.split(/-{20,40}\w{6,24}[\n\r]*/m).each do |part| + part.scan(/([^\n\r]+)[\n\r]+([^\n\r]*)/m) do |header, value| + if header.match(/name=["'](.*)["']/) + params[$1] = value.strip + end + end + end + else + super + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney.rb new file mode 100644 index 000000000..ff7c73a91 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney.rb @@ -0,0 +1,17 @@ +require File.dirname(__FILE__) + '/rbkmoney/helper.rb' +require File.dirname(__FILE__) + '/rbkmoney/notification.rb' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Rbkmoney + mattr_accessor :service_url + self.service_url = 'https://rbkmoney.ru/acceptpurchase.aspx' + + def self.notification(*args) + Notification.new(*args) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/helper.rb new file mode 100644 index 000000000..4d79584b2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/helper.rb @@ -0,0 +1,23 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Rbkmoney + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :account, 'eshopId' + mapping :amount, 'recipientAmount' + + # NOTE: rbkmoney uses outdated currency code 'RUR' + mapping :currency, 'recipientCurrency' + + mapping :order, 'orderId' + + mapping :customer, :email => 'user_email' + + mapping :credential2, 'serviceName' + mapping :credential3, 'successUrl' + mapping :credential4, 'failUrl' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/notification.rb new file mode 100644 index 000000000..a4ce79d79 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/rbkmoney/notification.rb @@ -0,0 +1,91 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Rbkmoney + class Notification < ActiveMerchant::Billing::Integrations::Notification + %w( + eshopId + paymentId + orderId + eshopAccount + serviceName + recipientAmount + recipientCurrency + paymentStatus + userName + userEmail + paymentData + secretKey + hash + ).each do |param_name| + define_method(param_name.underscore){ params[param_name] } + end + + def complete? + (payment_status == '5') + end + + def test? + false + end + + def status + case payment_status + when '3' + 'pending' + when '5' + 'completed' + else 'unknown' + end + end + + def user_fields + params.inject({}) do |fields, (k,v)| + if /\AuserField_[\d+]\z/.match(k) + fields[k] = v + end + fields + end + end + + alias_method :client_id, :eshop_id + alias_method :item_id, :order_id + alias_method :transaction_id, :payment_id + alias_method :received_at, :payment_data + alias_method :payer_email, :user_email + alias_method :gross, :recipient_amount + alias_method :currency, :recipient_currency + + def acknowledge + string = [ + eshop_id, + order_id, + service_name, + eshop_account, + recipient_amount, + recipient_currency, + payment_status, + user_name, + user_email, + payment_data, + @options[:secret] + ].join '::' + + signature = case hash.to_s.length + when 32 + Digest::MD5.hexdigest(string) + when 128 + Digest::SHA512.hexdigest(string) + else + return false + end + + signature == hash + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/return.rb new file mode 100644 index 000000000..579f5d10c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/return.rb @@ -0,0 +1,42 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + class Return + attr_accessor :params + attr_reader :notification + + def initialize(query_string, options = {}) + @params = parse(query_string) + @options = options + end + + # Successful by default. Overridden in the child class + def success? + true + end + + # Not cancelled by default. Overridden in the child class. + def cancelled? + false + end + + def message + + end + + def parse(query_string) + return {} if query_string.blank? + + query_string.split('&').inject({}) do |memo, chunk| + next if chunk.empty? + key, value = chunk.split('=', 2) + next if key.empty? + value = value.nil? ? nil : CGI.unescape(value) + memo[CGI.unescape(key)] = value + memo + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa.rb new file mode 100644 index 000000000..1b27ffed2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa.rb @@ -0,0 +1,49 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + # Documentation: http://robokassa.ru/Doc/En/Interface.aspx + module Robokassa + autoload :Helper, File.dirname(__FILE__) + '/robokassa/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/robokassa/notification.rb' + autoload :Return, File.dirname(__FILE__) + '/robokassa/return.rb' + autoload :Common, File.dirname(__FILE__) + '/robokassa/common.rb' + + # Overwrite this if you want to change the Robokassa test url + mattr_accessor :test_url + self.test_url = 'http://test.robokassa.ru/Index.aspx' + + # Overwrite this if you want to change the Robokassa production url + mattr_accessor :production_url + self.production_url = 'https://merchant.roboxchange.com/Index.aspx' + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'SignatureValue' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + + def self.return(query_string) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/common.rb new file mode 100644 index 000000000..6c8a79146 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/common.rb @@ -0,0 +1,19 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Robokassa + module Common + def generate_signature_string + custom_param_keys = params.keys.select {|key| key =~ /^shp/}.sort + custom_params = custom_param_keys.map {|key| "#{key}=#{params[key]}"} + [main_params, secret, custom_params.compact].flatten.join(':') + end + + def generate_signature + Digest::MD5.hexdigest(generate_signature_string) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/helper.rb new file mode 100644 index 000000000..58102958c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/helper.rb @@ -0,0 +1,50 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Robokassa + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + + def initialize(order, account, options = {}) + @md5secret = options.delete(:secret) + super + end + + def form_fields + @fields.merge(ActiveMerchant::Billing::Integrations::Robokassa.signature_parameter_name => generate_signature) + end + + def main_params + [:account, :amount, :order].map {|key| @fields[mappings[key]]} + end + + def params + @fields + end + + def secret + @md5secret + end + + def method_missing(method_id, *args) + method_id = method_id.to_s.gsub(/=$/, '') + + # support for robokassa custom parameters + if method_id =~ /^shp/ + add_field method_id, args.last + end + + super + end + + mapping :account, 'MrchLogin' + mapping :amount, 'OutSum' + mapping :currency, 'IncCurrLabel' + mapping :order, 'InvId' + mapping :description, 'Desc' + mapping :email, 'Email' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/notification.rb new file mode 100644 index 000000000..bc4de6e7f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/notification.rb @@ -0,0 +1,55 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Robokassa + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def self.recognizes?(params) + params.has_key?('InvId') && params.has_key?('OutSum') + end + + def complete? + true + end + + def amount + BigDecimal.new(gross) + end + + def item_id + params['InvId'] + end + + def security_key + params[ActiveMerchant::Billing::Integrations::Robokassa.signature_parameter_name].to_s.downcase + end + + def gross + params['OutSum'] + end + + def status + 'success' + end + + def secret + @options[:secret] + end + + def main_params + [gross, item_id] + end + + def acknowledge + security_key == generate_signature + end + + def success_response(*args) + "OK#{item_id}" + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/return.rb new file mode 100644 index 000000000..c9321433c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/robokassa/return.rb @@ -0,0 +1,17 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Robokassa + class Return < ActiveMerchant::Billing::Integrations::Return + def item_id + @params['InvId'] + end + + def amount + @params['OutSum'] + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form.rb new file mode 100644 index 000000000..48c092da7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form.rb @@ -0,0 +1,37 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module SagePayForm + autoload :Helper, File.dirname(__FILE__) + '/sage_pay_form/helper.rb' + autoload :Return, File.dirname(__FILE__) + '/sage_pay_form/return.rb' + autoload :Notification, File.dirname(__FILE__) + '/sage_pay_form/notification.rb' + autoload :Encryption, File.dirname(__FILE__) + '/sage_pay_form/encryption.rb' + + mattr_accessor :production_url + mattr_accessor :test_url + mattr_accessor :simulate_url + self.production_url = 'https://live.sagepay.com/gateway/service/vspform-register.vsp' + self.test_url = 'https://test.sagepay.com/gateway/service/vspform-register.vsp' + self.simulate_url = 'https://test.sagepay.com/Simulator/VSPFormGateway.asp' + + def self.return(query_string, options = {}) + Return.new(query_string, options) + end + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + when :simulate + self.simulate_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb new file mode 100644 index 000000000..9c933a427 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb @@ -0,0 +1,33 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module SagePayForm + module Encryption + def sage_encrypt(plaintext, key) + Base64.strict_encode64(sage_encrypt_xor(plaintext, key)) + end + + def sage_decrypt(ciphertext, key) + sage_encrypt_xor(Base64.decode64(ciphertext), key) + end + + def sage_encrypt_salt(min, max) + length = rand(max - min + 1) + min + SecureRandom.base64(length + 4)[0, length] + end + + private + + def sage_encrypt_xor(data, key) + raise 'No key provided' if key.blank? + + key *= (data.bytesize.to_f / key.bytesize.to_f).ceil + key = key[0, data.bytesize] + + data.bytes.zip(key.bytes).map { |b1, b2| (b1 ^ b2).chr }.join + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb new file mode 100644 index 000000000..b0295dbfc --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb @@ -0,0 +1,136 @@ +require 'uri' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module SagePayForm + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Encryption + + mapping :credential2, 'EncryptKey' + + mapping :account, 'Vendor' + mapping :amount, 'Amount' + mapping :currency, 'Currency' + + mapping :order, 'VendorTxCode' + + mapping :customer, + :first_name => 'BillingFirstnames', + :last_name => 'BillingSurname', + :email => 'CustomerEMail', + :phone => 'BillingPhone', + :send_email_confirmation => 'SendEmail' + + mapping :billing_address, + :city => 'BillingCity', + :address1 => 'BillingAddress1', + :address2 => 'BillingAddress2', + :state => 'BillingState', + :zip => 'BillingPostCode', + :country => 'BillingCountry' + + mapping :shipping_address, + :city => 'DeliveryCity', + :address1 => 'DeliveryAddress1', + :address2 => 'DeliveryAddress2', + :state => 'DeliveryState', + :zip => 'DeliveryPostCode', + :country => 'DeliveryCountry' + + mapping :return_url, 'SuccessURL' + mapping :description, 'Description' + + class_attribute :referrer_id + + def shipping_address(params = {}) + @shipping_address_set = true unless params.empty? + + params.each do |k, v| + field = mappings[:shipping_address][k] + add_field(field, v) unless field.nil? + end + end + + def map_billing_address_to_shipping_address + %w(City Address1 Address2 State PostCode Country).each do |field| + fields["Delivery#{field}"] = fields["Billing#{field}"] + end + end + + def form_fields + map_billing_address_to_shipping_address unless @shipping_address_set + + fields['DeliveryFirstnames'] ||= fields['BillingFirstnames'] + fields['DeliverySurname'] ||= fields['BillingSurname'] + + fields['FailureURL'] ||= fields['SuccessURL'] + + fields['BillingPostCode'] ||= "0000" + fields['DeliveryPostCode'] ||= "0000" + + crypt_skip = ['Vendor', 'EncryptKey', 'SendEmail'] + crypt_skip << 'BillingState' unless fields['BillingCountry'] == 'US' + crypt_skip << 'DeliveryState' unless fields['DeliveryCountry'] == 'US' + crypt_skip << 'CustomerEMail' unless fields['SendEmail'] + key = fields['EncryptKey'] + @crypt ||= create_crypt_field(fields.except(*crypt_skip), key) + + result = { + 'VPSProtocol' => '2.23', + 'TxType' => 'PAYMENT', + 'Vendor' => @fields['Vendor'], + 'Crypt' => @crypt + } + result['ReferrerID'] = referrer_id if referrer_id + result + end + + private + + def create_crypt_field(fields, key) + parts = fields.map { |k, v| "#{k}=#{sanitize(k, v)}" unless v.nil? }.compact.shuffle + parts.unshift(sage_encrypt_salt(key.length, key.length * 2)) + sage_encrypt(parts.join('&'), key) + end + + def sanitize(key, value) + reject = exact = nil + + case key + when /URL$/ + # allow all + when 'VendorTxCode' + reject = /[^A-Za-z0-9{}._-]+/ + when /[Nn]ames?$/ + reject = %r{[^[:alpha:] /\\.'-]+} + when /(?:Address[12]|City)$/ + reject = %r{[^[:alnum:] +'/\\:,.\n()-]+} + when /PostCode$/ + reject = /[^A-Za-z0-9 -]+/ + when /Phone$/ + reject = /[^0-9A-Za-z+ ()-]+/ + when 'Currency' + exact = /^[A-Z]{3}$/ + when /State$/ + exact = /^[A-Z]{2}$/ + when 'Description' + value = value[0...100] + else + reject = /&+/ + end + + if exact + raise ArgumentError, "Invalid value for #{key}: #{value.inspect}" unless value =~ exact + value + elsif reject + value.gsub(reject, ' ') + else + value + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb new file mode 100644 index 000000000..f9dc672d7 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb @@ -0,0 +1,210 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module SagePayForm + class Notification < ActiveMerchant::Billing::Integrations::Notification + class CryptError < StandardError; end + + include Encryption + + def initialize(post_data, options) + super + load_crypt_params(params['crypt'], options[:credential2]) + end + + # Was the transaction complete? + def complete? + status_code == 'OK' + end + + # Was the transaction cancelled? + # Unfortunately, we can't distinguish "user abort" from "idle too long". + def cancelled? + status_code == 'ABORT' + end + + # Text version of #complete?, since we don't support Pending. + def status + complete? ? 'Completed' : 'Failed' + end + + # Status of transaction. List of possible values: + # <tt>OK</tt>:: Transaction completed successfully. + # <tt>NOTAUTHED</tt>:: Incorrect card details / insufficient funds. + # <tt>MALFORMED</tt>:: Invalid input data. + # <tt>INVALID</tt>:: Valid input data, but some fields are incorrect. + # <tt>ABORT</tt>:: User hit cancel button or went idle for 15+ minutes. + # <tt>REJECTED</tt>:: Rejected by account fraud screening rules. + # <tt>AUTHENTICATED</tt>:: Authenticated card details secured at SagePay. + # <tt>REGISTERED</tt>:: Non-authenticated card details secured at SagePay. + # <tt>ERROR</tt>:: Problem internal to SagePay. + def status_code + params['Status'] + end + + # Check this if #completed? is false. + def message + params['StatusDetail'] + end + + # Vendor-supplied code (:order mapping). + def item_id + params['VendorTxCode'] + end + + # Internal SagePay code, typically "{LONG-UUID}". + def transaction_id + params['VPSTxId'] + end + + # Authorization number (only if #completed?). + def auth_id + params['TxAuthNo'] + end + + # Total amount (no fees). + def gross + params['Amount'] + end + + # AVS and CV2 check results. Possible values: + # <tt>ALL MATCH</tt>:: + # <tt>SECURITY CODE MATCH ONLY</tt>:: + # <tt>ADDRESS MATCH ONLY</tt>:: + # <tt>NO DATA MATCHES</tt>:: + # <tt>DATA NOT CHECKED</tt>:: + def avs_cv2_result + params['AVSCV2'] + end + + # Numeric address check. Possible values: + # <tt>NOTPROVIDED</tt>:: + # <tt>NOTCHECKED</tt>:: + # <tt>MATCHED</tt>:: + # <tt>NOTMATCHED</tt>:: + def address_result + params['AddressResult'] + end + + # Post code check. Possible values: + # <tt>NOTPROVIDED</tt>:: + # <tt>NOTCHECKED</tt>:: + # <tt>MATCHED</tt>:: + # <tt>NOTMATCHED</tt>:: + def post_code_result + params['PostCodeResult'] + end + + # CV2 code check. Possible values: + # <tt>NOTPROVIDED</tt>:: + # <tt>NOTCHECKED</tt>:: + # <tt>MATCHED</tt>:: + # <tt>NOTMATCHED</tt>:: + def cv2_result + params['CV2Result'] + end + + # Was the Gift Aid box checked? + def gift_aid? + params['GiftAid'] == '1' + end + + # Result of 3D Secure checks. Possible values: + # <tt>OK</tt>:: Authenticated correctly. + # <tt>NOTCHECKED</tt>:: Authentication not performed. + # <tt>NOTAVAILABLE</tt>:: Card not auth-capable, or auth is otherwise impossible. + # <tt>NOTAUTHED</tt>:: User failed authentication. + # <tt>INCOMPLETE</tt>:: Authentication unable to complete. + # <tt>ERROR</tt>:: Unable to attempt authentication due to data / service errors. + def buyer_auth_result + params['3DSecureStatus'] + end + + # Encoded 3D Secure result code. + def buyer_auth_result_code + params['CAVV'] + end + + # Address confirmation status. PayPal only. Possible values: + # <tt>NONE</tt>:: + # <tt>CONFIRMED</tt>:: + # <tt>UNCONFIRMED</tt>:: + def address_status + params['AddressStatus'] + end + + # Payer verification. Undocumented. + def payer_verified? + params['PayerStatus'] == 'VERIFIED' + end + + # Credit card type. Possible values: + # <tt>VISA</tt>:: Visa + # <tt>MC</tt>:: MasterCard + # <tt>DELTA</tt>:: Delta + # <tt>SOLO</tt>:: Solo + # <tt>MAESTRO</tt>:: Maestro (UK and International) + # <tt>UKE</tt>:: Visa Electron + # <tt>AMEX</tt>:: American Express + # <tt>DC</tt>:: Diners Club + # <tt>JCB</tt>:: JCB + # <tt>LASER</tt>:: Laser + # <tt>PAYPAL</tt>:: PayPal + def credit_card_type + params['CardType'] + end + + # Last four digits of credit card. + def credit_card_last_4_digits + params['Last4Digits'] + end + + # Used by composition methods, but not supplied by SagePay. + def currency + nil + end + + def test? + false + end + + def acknowledge + true + end + + private + + def load_crypt_params(crypt, key) + raise MissingCryptData if crypt.blank? + raise MissingCryptKey if key.blank? + + crypt_data = sage_decrypt(crypt.gsub(' ', '+'), key) + raise InvalidCryptData unless crypt_data =~ /(^|&)Status=/ + + params.clear + parse(crypt_data) + end + + class MissingCryptKey < CryptError + def message + 'No merchant decryption key supplied' + end + end + class MissingCryptData < CryptError + def message + 'No data received from SagePay' + end + end + class InvalidCryptData < CryptError + def message + 'Invalid data received from SagePay' + end + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/return.rb new file mode 100644 index 000000000..b2a2432ea --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/sage_pay_form/return.rb @@ -0,0 +1,31 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module SagePayForm + class Return < ActiveMerchant::Billing::Integrations::Return + + def initialize(query_string, options) + begin + @notification = Notification.new(query_string, options) + rescue Notification::CryptError => e + @message = e.message + end + end + + def success? + @notification && @notification.complete? + end + + def cancelled? + @notification && @notification.cancelled? + end + + def message + @message || @notification.message + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout.rb new file mode 100644 index 000000000..e5733aad3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout.rb @@ -0,0 +1,44 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module TwoCheckout + autoload 'Helper', File.dirname(__FILE__) + '/two_checkout/helper' + autoload 'Return', File.dirname(__FILE__) + '/two_checkout/return' + autoload 'Notification', File.dirname(__FILE__) + '/two_checkout/notification' + + mattr_accessor :payment_routine + self.payment_routine = :single_page + + def self.service_url + case self.payment_routine + when :multi_page + 'https://www.2checkout.com/checkout/purchase' + when :single_page + 'https://www.2checkout.com/checkout/spurchase' + else + raise StandardError, "Integration payment routine set to an invalid value: #{self.payment_routine}" + end + end + + def self.service_url=(service_url) + # Note: do not use this method, it is here for backward compatibility + # Use the payment_routine method to change service_url + if service_url =~ /spurchase/ + self.payment_routine = :single_page + else + self.payment_routine = :multi_page + end + end + + + def self.notification(post, options = {}) + Notification.new(post) + end + + def self.return(query_string, options = {}) + Return.new(query_string) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/helper.rb new file mode 100644 index 000000000..077b9ae42 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/helper.rb @@ -0,0 +1,91 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module TwoCheckout + class Helper < ActiveMerchant::Billing::Integrations::Helper + def initialize(order, account, options = {}) + super + add_field('fixed', 'Y') + + if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] + add_field('demo', 'Y') + end + end + + # The 2checkout vendor account number + mapping :account, 'sid' + + # The total amount to be billed, in decimal form, without a currency symbol. (8 characters, decimal, 2 characters: Example: 99999999.99) + mapping :amount, 'total' + + # Pass your order id if you are using Third Part Cart Parameters. (128 characters max) + mapping :order, 'cart_order_id' + + # Pass your order id if you are using the Pass Through Products Parameters. (50 characters max) + mapping :invoice, 'merchant_order_id' + + # Left here for backward compatibility, do not use. The line_item method will add automatically. + mapping :mode, 'mode' + + mapping :customer, :email => 'email', + :phone => 'phone' + + mapping :billing_address, :city => 'city', + :address1 => 'street_address', + :address2 => 'street_address2', + :state => 'state', + :zip => 'zip', + :country => 'country' + + mapping :shipping_address, :city => 'ship_city', + :address1 => 'ship_street_address', + :state => 'ship_state', + :zip => 'ship_zip', + :country => 'ship_country' + + # Does nothing, since we've disabled the Continue Shopping button by using the fixed = Y field + mapping :return_url, 'return_url' + + # Approved URL path + mapping :notification_url, 'x_receipt_link_url' + + def customer(params = {}) + add_field(mappings[:customer][:email], params[:email]) + add_field(mappings[:customer][:phone], params[:phone]) + add_field('card_holder_name', "#{params[:first_name]} #{params[:last_name]}") + end + + # Uses Pass Through Product Parameters to pass in lineitems. + # (must mark tanigble sales as shipped to settle the transaction) + def line_item(params = {}) + add_field('mode', '2CO') + (max_existing_line_item_id = form_fields.keys.map do |key| + i = key.to_s[/^li_(\d+)_/, 1] + (i && i.to_i) + end.compact.max || 0) + + line_item_id = max_existing_line_item_id + 1 + params.each do |key, value| + add_field("li_#{line_item_id}_#{key}", value) + end + end + + # Uses Third Party Cart parameter set to pass in lineitem details. + # (sales settle automatically) + def auto_settle(params = {}) + add_field('id_type', '1') + (max_existing_line_item_id = form_fields.keys.map do |key| + i = key.to_s[/^c_prod_(\d+)/, 1] + (i && i.to_i) + end.compact.max || 0) + + line_item_id = max_existing_line_item_id + 1 + params.each do |key, value| + add_field("c_#{key}_#{line_item_id}", value) + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/notification.rb new file mode 100644 index 000000000..8ba211b34 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/notification.rb @@ -0,0 +1,139 @@ +require 'net/http' +require 'base64' +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module TwoCheckout + class Notification < ActiveMerchant::Billing::Integrations::Notification + # card_holder_name - Provides the customer’s name. + # city - Provides the customer’s city. + # country - Provides the customer’s country. + # credit_card_processed - This parameter will always be passed back as Y. + # demo - Defines if an order was live, or if the order was a demo order. If the order was a demo, the MD5 hash will fail. + # email - Provides the email address the customer provided when placing the order. + # fixed - This parameter will only be passed back if it was passed into the purchase routine. + # ip_country - Provides the customer’s IP location. + # key - An MD5 hash used to confirm the validity of a sale. + # lang - Customer language + # merchant_order_id - The order ID you had assigned to the order. + # order_number - The 2Checkout order number associated with the order. + # invoice_id - The 2Checkout invoice number. + # pay_method - Provides seller with the customer’s payment method. CC for Credit Card, PPI for PayPal. + # phone - Provides the phone number the customer provided when placing the order. + # ship_name - Provides the ship to name for the order. + # ship_street_address - Provides ship to address. + # ship_street_address2 - Provides more detailed shipping address if this information was provided by the customer. + # ship_city - Provides ship to city. + # ship_state - Provides ship to state. + # ship_zip - Ship Zip + + # Pass Through Products Only + # li_#_name - Name of the corresponding lineitem. + # li_#_quantity - Quantity of the corresponding lineitem. + # li_#_price - Price of the corresponding lineitem. + # li_#_tangible - Specifies if the corresponding li_#_type is a tangible or intangible. ‘Y’ OR ‘N’ + # li_#_product_id - ID of the corresponding lineitem. + # li_#_product_description - Description of the corresponding lineitem. + # li_#_recurrence - # WEEK | MONTH | YEAR – always singular. + # li_#_duration - Forever or # WEEK | MONTH | YEAR – always singular, defaults to Forever. + # li_#_startup_fee - Amount in account pricing currency. + # li_#_option_#_name - Name of option. 64 characters max – cannot include '<' or '>'. + # li_#_option_#_value - Name of option. 64 characters max – cannot include '<' or '>'. + # li_#_option_#_surcharge - Amount in account pricing currency. + + #Third Party Cart Only + # cart_order_id - The order ID you had assigned to the order. + + # Allow seller to define default currency (should match 2Checkout account pricing currency) + def currency + 'USD' + end + + def complete? + status == 'Completed' + end + + # Third Party Cart parameters will return 'card_order_id' + # Pass Through Product parameters will only return 'merchant_order_id' + def item_id + if (params['cart_order_id'].nil?) + params['merchant_order_id'] + else + params['cart_order_id'] + end + end + + # 2Checkout Sale ID + def transaction_id + params['order_number'] + end + + def received_at + params[''] + end + + #Customer Email + def payer_email + params['email'] + end + + def receiver_email + params[''] + end + + # The MD5 Hash + def security_key + params['key'] + end + + # The money amount we received in X.2 decimal. + def gross + params['total'] + end + + # Was this a test transaction? # Use the hash + # Please note 2Checkout forces the order number computed in the hash to '1' on demo sales. + def test? + params['demo'] == 'Y' + end + + # 2Checkout only returns 'Y' for this parameter. If the sale is not authorized, no passback occurs. + def status + case params['credit_card_processed'] + when 'Y' + 'Completed' + else + 'Failed' + end + end + + # Secret Word defined in 2Checkout account + def secret + @options[:credential2] + end + + # Checks against MD5 Hash + def acknowledge + return false if security_key.blank? + + Digest::MD5.hexdigest("#{secret}#{params['sid']}#{transaction_id}#{gross}").upcase == security_key.upcase + end + + private + + # Parses Header Redirect Query String + def parse(post) + @raw = post.to_s + for line in @raw.split('&') + key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + params[key] = CGI.unescape(value || '') + end + end + + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/return.rb new file mode 100644 index 000000000..327d1a319 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/two_checkout/return.rb @@ -0,0 +1,17 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module TwoCheckout + class Return < ActiveMerchant::Billing::Integrations::Return + def success? + params['credit_card_processed'] == 'Y' + end + + def message + + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor.rb new file mode 100644 index 000000000..20a5a8ca3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor.rb @@ -0,0 +1,33 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Valitor + autoload :Return, 'active_merchant/billing/integrations/valitor/return.rb' + autoload :Helper, 'active_merchant/billing/integrations/valitor/helper.rb' + autoload :Notification, 'active_merchant/billing/integrations/valitor/notification.rb' + + mattr_accessor :test_url + self.test_url = 'https://testvefverslun.valitor.is/1_1/' + + mattr_accessor :production_url + self.production_url = 'https://vefverslun.valitor.is/1_1/' + + def self.test? + (ActiveMerchant::Billing::Base.integration_mode == :test) + end + + def self.service_url + (test? ? test_url : production_url) + end + + def self.notification(params, options={}) + Notification.new(params, options.merge(:test => test?)) + end + + def self.return(query_string, options={}) + Return.new(query_string, options.merge(:test => test?)) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/helper.rb new file mode 100644 index 000000000..15630f659 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/helper.rb @@ -0,0 +1,86 @@ +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Valitor + class Helper < ActiveMerchant::Billing::Integrations::Helper + include RequiresParameters + + DEFAULT_SUCCESS_TEXT = "The transaction has been completed." + + def initialize(order, account, options={}) + options[:currency] ||= 'ISK' + super + add_field 'Adeinsheimild', '0' + add_field 'KaupandaUpplysingar', '0' + add_field 'SlokkvaHaus', '0' + @security_number = options[:credential2] + @amount = options[:amount] + @order = order + end + + mapping :account, 'VefverslunID' + mapping :currency, 'Gjaldmidill' + + mapping :order, 'Tilvisunarnumer' + + mapping :notify_url, 'SlodTokstAdGjaldfaeraServerSide' + mapping :return_url, 'SlodTokstAdGjaldfaera' + mapping :cancel_return_url, 'SlodNotandiHaettirVid' + + mapping :success_text, 'SlodTokstAdGjaldfaeraTexti' + + mapping :language, 'Lang' + + def authorize_only + add_field 'Adeinsheimild', '1' + end + + def collect_customer_info + add_field 'KaupandaUpplysingar', '1' + end + + def hide_header + add_field 'SlokkvaHaus', '1' + end + + def product(id, options={}) + raise ArgumentError, "Product id #{id} is not an integer between 1 and 500" unless id.to_i > 0 && id.to_i <= 500 + requires!(options, :amount, :description) + options.assert_valid_keys([:description, :quantity, :amount, :discount]) + + add_field("Vara_#{id}_Verd", format_amount(options[:amount])) + add_field("Vara_#{id}_Fjoldi", options[:quantity] || "1") + + add_field("Vara_#{id}_Lysing", options[:description]) if options[:description] + add_field("Vara_#{id}_Afslattur", options[:discount] || '0') + + @products ||= [] + @products << id.to_i + end + + def signature + raise ArgumentError, "Security number not set" unless @security_number + parts = [@security_number, @fields['Adeinsheimild']] + @products.sort.uniq.each do |id| + parts.concat(["Vara_#{id}_Fjoldi", "Vara_#{id}_Verd", "Vara_#{id}_Afslattur"].collect{|e| @fields[e]}) + end if @products + parts.concat(%w(VefverslunID Tilvisunarnumer SlodTokstAdGjaldfaera SlodTokstAdGjaldfaeraServerSide Gjaldmidill).collect{|e| @fields[e]}) + Digest::MD5.hexdigest(parts.compact.join('')) + end + + def form_fields + product(1, :amount => @amount, :description => @order) if Array(@products).empty? + @fields[mappings[:success_text]] ||= DEFAULT_SUCCESS_TEXT + @fields.merge('RafraenUndirskrift' => signature) + end + + def format_amount(amount) + amount.to_f.round + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/notification.rb new file mode 100644 index 000000000..6aa643941 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/notification.rb @@ -0,0 +1,13 @@ +require 'active_merchant/billing/integrations/valitor/response_fields' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Valitor + class Notification < ActiveMerchant::Billing::Integrations::Notification + include ResponseFields + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/response_fields.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/response_fields.rb new file mode 100644 index 000000000..9894644f5 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/response_fields.rb @@ -0,0 +1,97 @@ +require 'digest/md5' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Valitor + module ResponseFields + def success? + status == 'Completed' + end + alias :complete? :success? + + def test? + @options[:test] + end + + def item_id + params['Tilvisunarnumer'] + end + alias :order :item_id + + def transaction_id + params['VefverslunSalaID'] + end + + def currency + nil + end + + def status + "Completed" if acknowledge + end + + def received_at + Time.parse(params['Dagsetning'].to_s) + end + + def gross + "%0.2f" % params['Upphaed'].to_s + end + + def card_type + params['Kortategund'] + end + + def card_last_four + params['KortnumerSidustu'] + end + + def authorization_number + params['Heimildarnumer'] + end + + def transaction_number + params['Faerslunumer'] + end + + def customer_name + params['Nafn'] + end + + def customer_address + params['Heimilisfang'] + end + + def customer_zip + params['Postnumer'] + end + + def customer_city + params['Stadur'] + end + + def customer_country + params['Land'] + end + + def customer_email + params['Tolvupostfang'] + end + + def customer_comment + params['Athugasemdir'] + end + + def password + @options[:credential2] + end + + def acknowledge + password ? Digest::MD5.hexdigest("#{password}#{order}") == params['RafraenUndirskriftSvar'] : true + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/return.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/return.rb new file mode 100644 index 000000000..b81cdfae2 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/valitor/return.rb @@ -0,0 +1,13 @@ +require 'active_merchant/billing/integrations/valitor/response_fields' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Valitor + class Return < ActiveMerchant::Billing::Integrations::Return + include ResponseFields + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut.rb new file mode 100644 index 000000000..504148e17 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut.rb @@ -0,0 +1,20 @@ +require File.dirname(__FILE__) + '/verkkomaksut/helper.rb' +require File.dirname(__FILE__) + '/verkkomaksut/notification.rb' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + # Usage, see the blog post here: http://blog.kiskolabs.com/post/22374612968/understanding-active-merchant-integrations and E1 API documentation here: http://docs.verkkomaksut.fi/ + module Verkkomaksut + + mattr_accessor :service_url + self.service_url = 'https://payment.verkkomaksut.fi/' + + def self.notification(post) + Notification.new(post) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb new file mode 100644 index 000000000..130290730 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb @@ -0,0 +1,87 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Verkkomaksut + class Helper < ActiveMerchant::Billing::Integrations::Helper + + # Fetches the md5secret and adds MERCHANT_ID and API TYPE to the form + def initialize(order, account, options = {}) + md5secret options.delete(:credential2) + super + add_field("MERCHANT_ID", account) + add_field("TYPE", "E1") + end + + def md5secret(value) + @md5secret = value + end + + # Adds the AUTHCODE to the form + def form_fields + @fields.merge("AUTHCODE" => generate_md5string) + end + + # Calculates the AUTHCODE + def generate_md5string + fields = [@md5secret, @fields["MERCHANT_ID"], @fields["ORDER_NUMBER"], @fields["REFERENCE_NUMBER"], @fields["ORDER_DESCRIPTION"], @fields["CURRENCY"], @fields["RETURN_ADDRESS"], @fields["CANCEL_ADDRESS"], @fields["PENDING_ADDRESS"], + @fields["NOTIFY_ADDRESS"], @fields["TYPE"], @fields["CULTURE"], @fields["PRESELECTED_METHOD"], @fields["MODE"], @fields["VISIBLE_METHODS"], @fields["GROUP"], @fields["CONTACT_TELNO"], @fields["CONTACT_CELLNO"], + @fields["CONTACT_EMAIL"], @fields["CONTACT_FIRSTNAME"], @fields["CONTACT_LASTNAME"], @fields["CONTACT_COMPANY"], @fields["CONTACT_ADDR_STREET"], @fields["CONTACT_ADDR_ZIP"], @fields["CONTACT_ADDR_CITY"], @fields["CONTACT_ADDR_COUNTRY"], @fields["INCLUDE_VAT"], + @fields["ITEMS"]] + + (0..@fields["ITEMS"].to_i-1).each do |i| + fields += [@fields["ITEM_TITLE[#{i}]"], @fields["ITEM_NO[#{i}]"], @fields["ITEM_AMOUNT[#{i}]"], @fields["ITEM_PRICE[#{i}]"], @fields["ITEM_TAX[#{i}]"], @fields["ITEM_DISCOUNT[#{i}]"], @fields["ITEM_TYPE[#{i}]"]] + end + + fields = fields.join("|") + + return Digest::MD5.hexdigest(fields).upcase + end + + # Mappings + mapping :merchant_id, "MERCHANT_ID" + mapping :order, "ORDER_NUMBER" + mapping :reference_number, "REFERENCE_NUMBER" + mapping :customer, :first_name => "CONTACT_FIRSTNAME", + :last_name => "CONTACT_LASTNAME", + :email => "CONTACT_EMAIL", + :phone => "CONTACT_CELLNO", + :tellno => "CONTACT_TELLNO", + :company => "CONTACT_COMPANY" + + + mapping :billing_address, :city => "CONTACT_ADDR_CITY", + :address1 => "CONTACT_ADDR_STREET", + :address2 => "", + :state => "", + :zip => "CONTACT_ADDR_ZIP", + :country => "CONTACT_ADDR_COUNTRY" + + mapping :notify_url, "NOTIFY_ADDRESS" + mapping :currency, "CURRENCY" + mapping :return_url, "RETURN_ADDRESS" + mapping :cancel_return_url, "CANCEL_ADDRESS" + mapping :description, "ORDER_DESCRIPTION" + mapping :tax, "" + mapping :shipping, "" + mapping :include_vat, "INCLUDE_VAT" + mapping :pending_address, "PENDING_ADDRESS" + mapping :culture, "CULTURE" + mapping :items, "ITEMS" + mapping :preselected_method, "PRESELECTED_METHOD" + mapping :mode, "MODE" + mapping :visible_methods, "VISIBLE_METHODS" + mapping :group, "GROUP" + + (0..499.to_i).each do |i| + mapping "item_no_#{i}".to_sym, "ITEM_NO[#{i}]" + mapping "item_amount_#{i}".to_sym, "ITEM_AMOUNT[#{i}]" + mapping "item_price_#{i}".to_sym, "ITEM_PRICE[#{i}]" + mapping "item_tax_#{i}".to_sym, "ITEM_TAX[#{i}]" + mapping "item_discount_#{i}".to_sym, "ITEM_DISCOUNT[#{i}]" + mapping "item_type_#{i}".to_sym, "ITEM_TYPE[#{i}]" + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb new file mode 100644 index 000000000..466a28230 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb @@ -0,0 +1,59 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Verkkomaksut + class Notification < ActiveMerchant::Billing::Integrations::Notification + + # Is the payment complete or not. Verkkomaksut only has two statuses: random string or 0000000000 which means pending + def complete? + params['PAID'] != "0000000000" + end + + # Order id + def order_id + params['ORDER_NUMBER'] + end + + # Payment method used + def method + params['METHOD'] + end + + # When was this payment received by the client. + def received_at + params['TIMESTAMP'] + end + + # Security key got from Verkkomaksut + def security_key + params['RETURN_AUTHCODE'] + end + + # Another way of asking the payment status + def status + if complete? + "PAID" + else + "PENDING" + end + end + + # Acknowldges the payment. If the authcodes match, returns true. + def acknowledge(authcode) + return_authcode = [params["ORDER_NUMBER"], params["TIMESTAMP"], params["PAID"], params["METHOD"], authcode].join("|") + Digest::MD5.hexdigest(return_authcode).upcase == params["RETURN_AUTHCODE"] + end + private + + def parse(post) + post.each do |key, value| + params[key] = value + end + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay.rb new file mode 100644 index 000000000..06aca03c3 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay.rb @@ -0,0 +1,45 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + + # Documentation: You will get it after registration steps here: + # http://reg.webpay.by/registration-form.php + module WebPay + autoload :Helper, File.dirname(__FILE__) + '/web_pay/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/web_pay/notification.rb' + autoload :Common, File.dirname(__FILE__) + '/web_pay/common.rb' + + # Overwrite this if you want to change the WebPay sandbox url + mattr_accessor :test_url + self.test_url = 'https://secure.sandbox.webpay.by:8843' + + # Overwrite this if you want to change the WebPay production url + mattr_accessor :production_url + self.production_url = 'https://secure.webpay.by' + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'wsb_signature' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/common.rb new file mode 100644 index 000000000..8a8191f2b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/common.rb @@ -0,0 +1,50 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WebPay + module Common + def generate_signature(type) + string = case type + when :request + request_signature_string + when :notify + notify_signature_string + end + if type != :notify && @fields[mappings[:version]] == '2' + Digest::SHA1.hexdigest(string) + else + Digest::MD5.hexdigest(string) + end + end + + def request_signature_string + [ + @fields[mappings[:seed]], + @fields[mappings[:account]], + @fields[mappings[:order]], + @fields[mappings[:test]], + @fields[mappings[:currency]], + @fields[mappings[:amount]], + secret + ].join + end + + def notify_signature_string + [ + params['batch_timestamp'], + params['currency_id'], + params['amount'], + params['payment_method'], + params['order_id'], + params['site_order_id'], + params['transaction_id'], + params['payment_type'], + params['rrn'], + secret + ].join + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/helper.rb new file mode 100644 index 000000000..1082f4355 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/helper.rb @@ -0,0 +1,68 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WebPay + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + + def initialize(order, account, options = {}) + @md5secret = options.delete(:secret) + @line_item_count = 0 + super + end + + def form_fields + @fields.merge(ActiveMerchant::Billing::Integrations::WebPay.signature_parameter_name => generate_signature(:request)) + end + + def params + @fields + end + + def secret + @md5secret + end + + def add_line_item(options) + options.each do |key, value| + add_field("wsb_invoice_item_#{key}[#{@line_item_count}]", value) + end + + @line_item_count += 1 + end + + def calculate_total + sum = 0 + + @line_item_count.times do |i| + sum += @fields["wsb_invoice_item_quantity[#{i}]"].to_i * @fields["wsb_invoice_item_price[#{i}]"].to_i + end + + sum + @fields[mappings[:tax]].to_i + @fields[mappings[:shipping_price]].to_i - @fields[mappings[:discount_price]].to_i + end + + mapping :scart, '*scart' + mapping :account, 'wsb_storeid' + mapping :store, 'wsb_store' + mapping :order, 'wsb_order_num' + mapping :currency, 'wsb_currency_id' + mapping :version, 'wsb_version' + mapping :language, 'wsb_language_id' + mapping :seed, 'wsb_seed' + mapping :success_url, 'wsb_return_url' + mapping :cancel_url, 'wsb_cancel_return_url' + mapping :notify_url, 'wsb_notify_url' + mapping :test, 'wsb_test' + mapping :tax, 'wsb_tax' + mapping :shipping_name, 'wsb_shipping_name' + mapping :shipping_price, 'wsb_shipping_price' + mapping :discount_name, 'wsb_discount_name' + mapping :discount_price, 'wsb_discount_price' + mapping :amount, 'wsb_total' + mapping :email, 'wsb_email' + mapping :phone, 'wsb_phone' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/notification.rb new file mode 100644 index 000000000..a0230ed66 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/web_pay/notification.rb @@ -0,0 +1,51 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WebPay + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def self.recognizes?(params) + params.has_key?('site_order_id') && params.has_key?('amount') + end + + def complete? + true + end + + def amount + BigDecimal.new(gross) + end + + def item_id + params['site_order_id'] + end + + def security_key + params[ActiveMerchant::Billing::Integrations::WebPay.signature_parameter_name] + end + + def gross + params['amount'] + end + + def status + 'success' + end + + def secret + @options[:secret] + end + + def acknowledge + (security_key == generate_signature(:notify)) + end + + def success_response(*args) + {:nothing => true} + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney.rb new file mode 100644 index 000000000..3b704846d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney.rb @@ -0,0 +1,43 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + # Documentation: + # http://wiki.webmoney.ru/projects/webmoney/wiki/Web_Merchant_Interface + module Webmoney + autoload :Helper, File.dirname(__FILE__) + '/webmoney/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/webmoney/notification.rb' + autoload :Return, File.dirname(__FILE__) + '/webmoney/return.rb' + autoload :Common, File.dirname(__FILE__) + '/webmoney/common.rb' + + mattr_accessor :test_url + self.test_url = "https://merchant.webmoney.ru/lmi/payment.asp" + + mattr_accessor :production_url + self.production_url = "https://merchant.webmoney.ru/lmi/payment.asp" + + mattr_accessor :signature_parameter_name + self.signature_parameter_name = 'LMI_HASH' + + def self.service_url + mode = ActiveMerchant::Billing::Base.integration_mode + case mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.helper(order, account, options = {}) + Helper.new(order, account, options) + end + + def self.notification(query_string, options = {}) + Notification.new(query_string, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/common.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/common.rb new file mode 100644 index 000000000..e6bb96593 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/common.rb @@ -0,0 +1,17 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Webmoney + module Common + def generate_signature_string + "#{params['LMI_PAYEE_PURSE']}#{params['LMI_PAYMENT_AMOUNT']}#{params['LMI_PAYMENT_NO']}#{params['LMI_MODE']}#{params['LMI_SYS_INVS_NO']}#{params['LMI_SYS_TRANS_NO']}#{params['LMI_SYS_TRANS_DATE']}#{secret}#{params['LMI_PAYER_PURSE']}#{params['LMI_PAYER_WM']}" + end + + def generate_signature + Digest::MD5.hexdigest(generate_signature_string).upcase + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/helper.rb new file mode 100644 index 000000000..2516d3f4f --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/helper.rb @@ -0,0 +1,39 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Webmoney + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + + def initialize(order, account, options = {}) + @webmoney_options = options.dup + options.delete(:description) + options.delete(:fail_url) + options.delete(:success_url) + options.delete(:result_url) + super + @webmoney_options.each do |key, value| + add_field mappings[key], value + end + end + + def form_fields + @fields + end + + def params + @fields + end + + mapping :account, 'LMI_PAYEE_PURSE' + mapping :amount, 'LMI_PAYMENT_AMOUNT' + mapping :order, 'LMI_PAYMENT_NO' + mapping :description, 'LMI_PAYMENT_DESC' + mapping :fail_url, 'LMI_FAIL_URL' + mapping :success_url, 'LMI_SUCCESS_URL' + mapping :result_url, 'LMI_RESULT_URL' + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/notification.rb new file mode 100644 index 000000000..6fa555604 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/webmoney/notification.rb @@ -0,0 +1,43 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Webmoney + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def recognizes? + (params.has_key?('LMI_PAYMENT_NO') && params.has_key?('LMI_PAYMENT_AMOUNT')) + end + + def amount + BigDecimal.new(gross) + end + + def key_present? + params["LMI_HASH"].present? + end + + def item_id + params['LMI_PAYMENT_NO'] + end + + def gross + params['LMI_PAYMENT_AMOUNT'] + end + + def security_key + params["LMI_HASH"] + end + + def secret + @options[:secret] + end + + def acknowledge + (security_key == generate_signature) + end + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay.rb new file mode 100644 index 000000000..73190d31b --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay.rb @@ -0,0 +1,34 @@ +require File.dirname(__FILE__) + '/world_pay/helper.rb' +require File.dirname(__FILE__) + '/world_pay/notification.rb' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WorldPay + + mattr_accessor :production_url, :test_url + self.production_url = 'https://secure.worldpay.com/wcc/purchase' + self.test_url = 'https://secure-test.worldpay.com/wcc/purchase' + + def self.service_url + case ActiveMerchant::Billing::Base.integration_mode + when :production + self.production_url + when :test + self.test_url + else + raise StandardError, "Integration mode set to an invalid value: #{mode}" + end + end + + def self.notification(post, options = {}) + Notification.new(post, options) + end + + def self.return(post, options = {}) + Return.new(post, options) + end + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/helper.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/helper.rb new file mode 100644 index 000000000..cad7fca69 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/helper.rb @@ -0,0 +1,100 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WorldPay + class Helper < ActiveMerchant::Billing::Integrations::Helper + mapping :account, 'instId' + mapping :amount, 'amount' + mapping :order, 'cartId' + mapping :currency, 'currency' + + mapping :customer, :email => 'email', + :phone => 'tel' + + mapping :billing_address, :zip => 'postcode', + :country => 'country' + + mapping :description, 'desc' + mapping :notify_url, 'MC_callback' + + + # WorldPay supports two different test modes - :always_succeed and :always_fail + def initialize(order, account, options = {}) + super + + if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] + test_mode = case options[:test] + when :always_fail + 101 + when false + 0 + else + 100 + end + add_field('testMode', test_mode.to_s) + elsif ActiveMerchant::Billing::Base.integration_mode == :always_succeed + add_field('testMode', '100') + elsif ActiveMerchant::Billing::Base.integration_mode == :always_fail + add_field('testMode', '101') + end + end + + # WorldPay only supports a single address field so we + # have to concat together - lines are separated using &#10; + def billing_address(params={}) + add_field(mappings[:billing_address][:zip], params[:zip]) + add_field(mappings[:billing_address][:country], lookup_country_code(params[:country])) + + address = [params[:address1], params[:address2], params[:city], params[:state]].compact + add_field('address', address.join('&#10;')) + end + + # WorldPay only supports a single name field so we have to concat + def customer(params={}) + add_field(mappings[:customer][:email], params[:email]) + add_field(mappings[:customer][:phone], params[:phone]) + add_field('name', "#{params[:first_name]} #{params[:last_name]}") + end + + # Support for a MD5 hash of selected fields to prevent tampering + # For futher information read the tech note at the address below: + # http://support.worldpay.com/kb/integration_guides/junior/integration/help/tech_notes/sjig_tn_009.html + def encrypt(secret, fields = [:amount, :currency, :account, :order]) + signature_fields = fields.collect{ |field| mappings[field] } + add_field('signatureFields', signature_fields.join(':')) + + field_values = fields.collect{ |field| form_fields[mappings[field]] } + signature = "#{secret}:#{field_values.join(':')}" + add_field('signature', Digest::MD5.hexdigest(signature)) + end + + # Add a time window for which the payment can be completed. Read the link below for how they work + # http://support.worldpay.com/kb/integration_guides/junior/integration/help/appendicies/sjig_10100.html + def valid_from(from_time) + add_field('authValidFrom', from_time.to_i.to_s + '000') + end + + def valid_to(to_time) + add_field('authValidTo', to_time.to_i.to_s + '000') + end + + # WorldPay supports the passing of custom parameters prefixed with the following: + # C_ : These parameters can be used in the response pages hosted on WorldPay's site + # M_ : These parameters are passed through to the callback script (if enabled) + # MC_ or CM_ : These parameters are availble both in the response and callback contexts + def response_params(params={}) + params.each{|k,v| add_field("C_#{k}",v)} + end + + def callback_params(params={}) + params.each{|k,v| add_field("M_#{k}",v)} + end + + def combined_params(params={}) + params.each{|k,v| add_field("MC_#{k}",v)} + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/notification.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/notification.rb new file mode 100644 index 000000000..afd682e9d --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/integrations/world_pay/notification.rb @@ -0,0 +1,160 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WorldPay + class Notification < ActiveMerchant::Billing::Integrations::Notification + def complete? + status == 'Completed' + end + + def account + params['instId'] + end + + def item_id + params['cartId'] + end + + def transaction_id + params['transId'] + end + + # Time this payment was received by the client in UTC time. + def received_at + Time.at(params['transTime'].to_i / 1000).utc + end + + # Callback password set in the WorldPay CMS + def security_key + params['callbackPW'] + end + + # the money amount we received in X.2 decimal. + def gross + params['authAmount'] + end + + def currency + params['authCurrency'] + end + + # Was this a test transaction? + def test? + params.key?('testMode') && params['testMode'] != '0' + end + + def status + params['transStatus'] == 'Y' ? 'Completed' : 'Cancelled' + end + + def name + params['name'] + end + + def address + params['address'] + end + + def postcode + params['postcode'] + end + + def country + params['country'] + end + + def phone_number + params['tel'] + end + + def fax_number + params['fax'] + end + + def email_address + params['email'] + end + + def card_type + params['cardType'] + end + + # WorldPay extended fraud checks returned as a 4 character string + # 1st char: Credit card CVV check + # 2nd char: Postcode AVS check + # 3rd char: Address AVS check + # 4th char: Country comparison check + # Possible values are: + # :not_supported - 0 + # :not_checked - 1 + # :matched - 2 + # :not_matched - 4 + # :partial_match - 8 + def cvv_status + return avs_value_to_symbol(params['AVS'][0].chr) + end + + def postcode_status + return avs_value_to_symbol(params['AVS'][1].chr) + end + + def address_status + return avs_value_to_symbol(params['AVS'][2].chr) + end + + def country_status + return avs_value_to_symbol(params['AVS'][3].chr) + end + + def acknowledge + return true + end + + # WorldPay supports the passing of custom parameters through to the callback script + def custom_params + return @custom_params ||= read_custom_params + end + + + private + + # Take the posted data and move the relevant data into a hash + def parse(post) + @raw = post + for line in post.split('&') + key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten + params[key] = value + end + end + + # Read the custom params into a hash + def read_custom_params + custom = {} + params.each do |key, value| + if /\A(M_|MC_|CM_)/ === key + custom[key.gsub(/\A(M_|MC_|CM_)/, '').to_sym] = value + end + end + custom + end + + # Convert a AVS value to a symbol - see above for more about AVS + def avs_value_to_symbol(value) + case value.to_s + when '8' + :partial_match + when '4' + :no_match + when '2' + :matched + when '1' + :not_checked + else + :not_supported + end + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/response.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/response.rb new file mode 100644 index 000000000..c02a22e7c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/billing/response.rb @@ -0,0 +1,77 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class Error < ActiveMerchantError #:nodoc: + end + + class Response + attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result + + def success? + @success + end + + def test? + @test + end + + def fraud_review? + @fraud_review + end + + def initialize(success, message, params = {}, options = {}) + @success, @message, @params = success, message, params.stringify_keys + @test = options[:test] || false + @authorization = options[:authorization] + @fraud_review = options[:fraud_review] + + @avs_result = if options[:avs_result].kind_of?(AVSResult) + options[:avs_result].to_hash + else + AVSResult.new(options[:avs_result]).to_hash + end + + @cvv_result = if options[:cvv_result].kind_of?(CVVResult) + options[:cvv_result].to_hash + else + CVVResult.new(options[:cvv_result]).to_hash + end + end + end + + class MultiResponse < Response + def self.run(&block) + new.tap(&block) + end + + attr_reader :responses + + def initialize + @responses = [] + end + + def process + self << yield if(responses.empty? || success?) + end + + def <<(response) + if response.is_a?(MultiResponse) + response.responses.each{|r| @responses << r} + else + @responses << response + end + end + + def success? + @responses.all?{|r| r.success?} + end + + %w(params message test authorization avs_result cvv_result test? fraud_review?).each do |m| + class_eval %( + def #{m} + (@responses.empty? ? nil : @responses.last.#{m}) + end + ) + end + end + end +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/active_merchant/version.rb b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/version.rb new file mode 100644 index 000000000..09d49a725 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/active_merchant/version.rb @@ -0,0 +1,3 @@ +module ActiveMerchant + VERSION = "1.33.0" +end diff --git a/vendor/gems/activemerchant-1.33.0/lib/activemerchant.rb b/vendor/gems/activemerchant-1.33.0/lib/activemerchant.rb new file mode 100644 index 000000000..0a3f08fee --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/activemerchant.rb @@ -0,0 +1 @@ +require 'active_merchant' \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/support/gateway_support.rb b/vendor/gems/activemerchant-1.33.0/lib/support/gateway_support.rb new file mode 100644 index 000000000..7cf3b2a3c --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/support/gateway_support.rb @@ -0,0 +1,65 @@ +require 'rubygems' +require 'active_support' +require 'active_merchant' + + +class GatewaySupport #:nodoc: + ACTIONS = [:purchase, :authorize, :capture, :void, :credit, :recurring] + + include ActiveMerchant::Billing + + attr_reader :gateways + + def initialize + Dir[File.expand_path(File.dirname(__FILE__) + '/../active_merchant/billing/gateways/*.rb')].each do |f| + filename = File.basename(f, '.rb') + gateway_name = filename + '_gateway' + begin + gateway_class = ('ActiveMerchant::Billing::' + gateway_name.camelize).constantize + rescue NameError + puts "Could not load gateway " + gateway_name.camelize + " from " + f + "." + end + end + @gateways = Gateway.implementations.sort_by(&:name) + @gateways.delete(ActiveMerchant::Billing::BogusGateway) + end + + def each_gateway + @gateways.each{|g| yield g } + end + + def features + width = 15 + + print "Name".center(width + 20) + ACTIONS.each{|f| print "#{f.to_s.capitalize.center(width)}" } + puts + + each_gateway do |g| + print "#{g.display_name.ljust(width + 20)}" + ACTIONS.each do |f| + print "#{(g.instance_methods.include?(f.to_s) ? "Y" : "N").center(width)}" + end + puts + end + end + + def to_rdoc + each_gateway do |g| + puts "* {#{g.display_name}}[#{g.homepage_url}] - #{g.supported_countries.join(', ')}" + end + end + + def to_textile + each_gateway do |g| + puts %/ * "#{g.display_name}":#{g.homepage_url} [#{g.supported_countries.join(', ')}]/ + end + end + + def to_s + each_gateway do |g| + puts "#{g.display_name} - #{g.homepage_url} [#{g.supported_countries.join(', ')}]" + end + end +end + diff --git a/vendor/gems/activemerchant-1.33.0/lib/support/outbound_hosts.rb b/vendor/gems/activemerchant-1.33.0/lib/support/outbound_hosts.rb new file mode 100644 index 000000000..bac790662 --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/support/outbound_hosts.rb @@ -0,0 +1,25 @@ +require 'uri' +require 'set' + +class OutboundHosts + def self.list + uris = Set.new + + Dir['lib/**/*.rb'].each do |file| + content = File.read(file) + + content.each_line do |line| + next if line =~ /homepage_url/ + + if line =~ /("|')(https:\/\/.*)("|')/ + uri = URI.parse($2) + uris << [uri.host, uri.port] + end + end + end + + uris.each do |uri| + puts "#{uri.first} #{uri.last}" + end + end +end \ No newline at end of file diff --git a/vendor/gems/activemerchant-1.33.0/lib/support/ssl_verify.rb b/vendor/gems/activemerchant-1.33.0/lib/support/ssl_verify.rb new file mode 100644 index 000000000..1ba28878a --- /dev/null +++ b/vendor/gems/activemerchant-1.33.0/lib/support/ssl_verify.rb @@ -0,0 +1,93 @@ +require 'active_merchant' +require 'support/gateway_support' + +class SSLVerify + + def initialize + @gateways = GatewaySupport.new.gateways + end + + def test_gateways + success, failed, missing, errored, disabled = [], [], [], [], [] + + puts "Verifying #{@gateways.count} SSL certificates\n\n" + + @gateways.each do |g| + if !g.live_url + missing << g unless g.abstract_class + next + end + + if !g.ssl_strict + disabled << g + end + + uri = URI.parse(g.live_url) + result,message = ssl_verify_peer?(uri) + case result + when :success + print "." + success << g + when :fail + print "F" + failed << {:gateway => g, :message => message} + when :error + print "E" + errored << {:gateway => g, :message => message} + end + end + + puts "\n\n\nFailed Gateways:" + failed.each do |f| + puts "#{f[:gateway].name} - #{f[:message]}" + end + + puts "\n\nError Gateways:" + errored.each do |e| + puts "#{e[:gateway].name} - #{e[:message]}" + end + + if missing.size > 0 + puts "\n\nGateways missing live_url:" + missing.each do |m| + puts m.name + end + end + + if disabled.size > 0 + puts "\n\nGateways with ssl_strict=false:" + disabled.each do |d| + puts d.name + end + end + + end + + def try_host(http, path) + http.get(path) + rescue Net::HTTPBadResponse, EOFError, SocketError + http.post(path, "") + end + + def ssl_verify_peer?(uri) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.ca_file = File.dirname(__FILE__) + '/certs/cacert.pem' + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + http.open_timeout = 60 + http.read_timeout = 60 + + if uri.path.blank? + try_host(http, "/") + else + try_host(http, uri.path) + end + + return :success + rescue OpenSSL::SSL::SSLError => ex + return :fail, ex.inspect + rescue Net::HTTPBadResponse, Errno::ETIMEDOUT, EOFError, SocketError, Errno::ECONNREFUSED, Timeout::Error => ex + return :error, ex.inspect + end + +end From 57289f5d99dfd2aa7f97c7dc90da37d51433540d Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 31 May 2013 12:54:13 +1000 Subject: [PATCH 085/184] Removed docs directory. It's not doing anything useful for us, and accidentally running "rdoc" with the wrong command line args (as I just did) dumps a bunch of stuff in it, which then pollute further commits. I'm going to gitignore it, too (see next commit). --- doc/ActiveSupport.html | 158 ---- doc/ActiveSupport/TestCase.html | 165 ---- .../TestCase/ActionController.html | 158 ---- .../TestCase/ActionController/TestCase.html | 179 ---- doc/AddUsernameToUsers.html | 211 ----- doc/ApplicationController.html | 165 ---- doc/ApplicationHelper.html | 159 ---- doc/BrowsingTest.html | 215 ----- doc/DeviseCreateUsers.html | 252 ------ doc/Gemfile.html | 165 ---- doc/Growstuff.html | 159 ---- doc/Growstuff/Application.html | 165 ---- doc/HomeController.html | 210 ----- doc/HomeControllerTest.html | 165 ---- doc/HomeHelper.html | 159 ---- doc/HomeHelperTest.html | 165 ---- doc/LICENSE_txt.html | 848 ------------------ doc/Object.html | 192 ---- doc/Rakefile.html | 129 --- doc/User.html | 237 ----- doc/UserTest.html | 165 ---- doc/created.rid | 54 -- doc/images/add.png | Bin 733 -> 0 bytes doc/images/brick.png | Bin 452 -> 0 bytes doc/images/brick_link.png | Bin 764 -> 0 bytes doc/images/bug.png | Bin 774 -> 0 bytes doc/images/bullet_black.png | Bin 211 -> 0 bytes doc/images/bullet_toggle_minus.png | Bin 207 -> 0 bytes doc/images/bullet_toggle_plus.png | Bin 209 -> 0 bytes doc/images/date.png | Bin 626 -> 0 bytes doc/images/delete.png | Bin 715 -> 0 bytes doc/images/find.png | Bin 659 -> 0 bytes doc/images/loadingAnimation.gif | Bin 5886 -> 0 bytes doc/images/macFFBgHack.png | Bin 207 -> 0 bytes doc/images/package.png | Bin 853 -> 0 bytes doc/images/page_green.png | Bin 621 -> 0 bytes doc/images/page_white_text.png | Bin 342 -> 0 bytes doc/images/page_white_width.png | Bin 309 -> 0 bytes doc/images/plugin.png | Bin 591 -> 0 bytes doc/images/ruby.png | Bin 592 -> 0 bytes doc/images/tag_blue.png | Bin 1880 -> 0 bytes doc/images/tag_green.png | Bin 613 -> 0 bytes doc/images/transparent.png | Bin 97 -> 0 bytes doc/images/wrench.png | Bin 610 -> 0 bytes doc/images/wrench_orange.png | Bin 584 -> 0 bytes doc/images/zoom.png | Bin 692 -> 0 bytes doc/index.html | 118 --- doc/js/darkfish.js | 153 ---- doc/js/jquery.js | 18 - doc/js/navigation.js | 142 --- doc/js/search.js | 94 -- doc/js/search_index.js | 1 - doc/js/searcher.js | 228 ----- doc/public/robots_txt.html | 128 --- doc/rdoc.css | 543 ----------- doc/table_of_contents.html | 123 --- 56 files changed, 6023 deletions(-) delete mode 100644 doc/ActiveSupport.html delete mode 100644 doc/ActiveSupport/TestCase.html delete mode 100644 doc/ActiveSupport/TestCase/ActionController.html delete mode 100644 doc/ActiveSupport/TestCase/ActionController/TestCase.html delete mode 100644 doc/AddUsernameToUsers.html delete mode 100644 doc/ApplicationController.html delete mode 100644 doc/ApplicationHelper.html delete mode 100644 doc/BrowsingTest.html delete mode 100644 doc/DeviseCreateUsers.html delete mode 100644 doc/Gemfile.html delete mode 100644 doc/Growstuff.html delete mode 100644 doc/Growstuff/Application.html delete mode 100644 doc/HomeController.html delete mode 100644 doc/HomeControllerTest.html delete mode 100644 doc/HomeHelper.html delete mode 100644 doc/HomeHelperTest.html delete mode 100644 doc/LICENSE_txt.html delete mode 100644 doc/Object.html delete mode 100644 doc/Rakefile.html delete mode 100644 doc/User.html delete mode 100644 doc/UserTest.html delete mode 100644 doc/created.rid delete mode 100755 doc/images/add.png delete mode 100644 doc/images/brick.png delete mode 100644 doc/images/brick_link.png delete mode 100644 doc/images/bug.png delete mode 100644 doc/images/bullet_black.png delete mode 100644 doc/images/bullet_toggle_minus.png delete mode 100644 doc/images/bullet_toggle_plus.png delete mode 100644 doc/images/date.png delete mode 100755 doc/images/delete.png delete mode 100644 doc/images/find.png delete mode 100644 doc/images/loadingAnimation.gif delete mode 100644 doc/images/macFFBgHack.png delete mode 100644 doc/images/package.png delete mode 100644 doc/images/page_green.png delete mode 100644 doc/images/page_white_text.png delete mode 100644 doc/images/page_white_width.png delete mode 100644 doc/images/plugin.png delete mode 100644 doc/images/ruby.png delete mode 100755 doc/images/tag_blue.png delete mode 100644 doc/images/tag_green.png delete mode 100644 doc/images/transparent.png delete mode 100644 doc/images/wrench.png delete mode 100644 doc/images/wrench_orange.png delete mode 100644 doc/images/zoom.png delete mode 100644 doc/index.html delete mode 100644 doc/js/darkfish.js delete mode 100644 doc/js/jquery.js delete mode 100644 doc/js/navigation.js delete mode 100644 doc/js/search.js delete mode 100644 doc/js/search_index.js delete mode 100644 doc/js/searcher.js delete mode 100644 doc/public/robots_txt.html delete mode 100644 doc/rdoc.css delete mode 100644 doc/table_of_contents.html diff --git a/doc/ActiveSupport.html b/doc/ActiveSupport.html deleted file mode 100644 index 15a6b05d1..000000000 --- a/doc/ActiveSupport.html +++ /dev/null @@ -1,158 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>module ActiveSupport - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="module"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="module">module ActiveSupport</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/ActiveSupport/TestCase.html b/doc/ActiveSupport/TestCase.html deleted file mode 100644 index ddff2978a..000000000 --- a/doc/ActiveSupport/TestCase.html +++ /dev/null @@ -1,165 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class ActiveSupport::TestCase - RDoc Documentation</title> - -<link type="text/css" media="screen" href="../rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "../"; -</script> - -<script type="text/javascript" charset="utf-8" src="../js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="../index.html">Home</a> - <a href="../table_of_contents.html#classes">Classes</a> - <a href="../table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>test/test_helper.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link"><a href="../Object.html">Object</a> - -</nav> - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="../Gemfile.html">Gemfile</a> - - <li class="file"><a href="../LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="../Rakefile.html">Rakefile</a> - - <li class="file"><a href="../public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="../ActiveSupport.html">ActiveSupport</a> - - <li><a href="../ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="../ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="../ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="../Growstuff.html">Growstuff</a> - - <li><a href="../Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="../AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="../ApplicationController.html">ApplicationController</a> - - <li><a href="../ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="../BrowsingTest.html">BrowsingTest</a> - - <li><a href="../DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="../HomeController.html">HomeController</a> - - <li><a href="../HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="../HomeHelper.html">HomeHelper</a> - - <li><a href="../HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="../Object.html">Object</a> - - <li><a href="../User.html">User</a> - - <li><a href="../UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class ActiveSupport::TestCase</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/ActiveSupport/TestCase/ActionController.html b/doc/ActiveSupport/TestCase/ActionController.html deleted file mode 100644 index 56076d9e3..000000000 --- a/doc/ActiveSupport/TestCase/ActionController.html +++ /dev/null @@ -1,158 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>module ActiveSupport::TestCase::ActionController - RDoc Documentation</title> - -<link type="text/css" media="screen" href="../../rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "../../"; -</script> - -<script type="text/javascript" charset="utf-8" src="../../js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="../../js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="../../js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="../../js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="../../js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="../../js/darkfish.js"></script> - - -<body id="top" class="module"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="../../index.html">Home</a> - <a href="../../table_of_contents.html#classes">Classes</a> - <a href="../../table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="../../Gemfile.html">Gemfile</a> - - <li class="file"><a href="../../LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="../../Rakefile.html">Rakefile</a> - - <li class="file"><a href="../../public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="../../ActiveSupport.html">ActiveSupport</a> - - <li><a href="../../ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="../../ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="../../ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="../../Growstuff.html">Growstuff</a> - - <li><a href="../../Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="../../AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="../../ApplicationController.html">ApplicationController</a> - - <li><a href="../../ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="../../BrowsingTest.html">BrowsingTest</a> - - <li><a href="../../DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="../../HomeController.html">HomeController</a> - - <li><a href="../../HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="../../HomeHelper.html">HomeHelper</a> - - <li><a href="../../HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="../../Object.html">Object</a> - - <li><a href="../../User.html">User</a> - - <li><a href="../../UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="module">module ActiveSupport::TestCase::ActionController</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/ActiveSupport/TestCase/ActionController/TestCase.html b/doc/ActiveSupport/TestCase/ActionController/TestCase.html deleted file mode 100644 index 696dde8f7..000000000 --- a/doc/ActiveSupport/TestCase/ActionController/TestCase.html +++ /dev/null @@ -1,179 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class ActiveSupport::TestCase::ActionController::TestCase - RDoc Documentation</title> - -<link type="text/css" media="screen" href="../../../rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "../../../"; -</script> - -<script type="text/javascript" charset="utf-8" src="../../../js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="../../../js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="../../../js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="../../../js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="../../../js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="../../../js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="../../../index.html">Home</a> - <a href="../../../table_of_contents.html#classes">Classes</a> - <a href="../../../table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>test/test_helper.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link"><a href="../../../Object.html">Object</a> - -</nav> - - <!-- Included Modules --> -<nav id="includes-section" class="section"> - <h3 class="section-header">Included Modules</h3> - - <ul class="link-list"> - - - <li><span class="include">Devise::TestHelpers</span> - - - </ul> -</nav> - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="../../../Gemfile.html">Gemfile</a> - - <li class="file"><a href="../../../LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="../../../Rakefile.html">Rakefile</a> - - <li class="file"><a href="../../../public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="../../../ActiveSupport.html">ActiveSupport</a> - - <li><a href="../../../ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="../../../ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="../../../ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="../../../Growstuff.html">Growstuff</a> - - <li><a href="../../../Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="../../../AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="../../../ApplicationController.html">ApplicationController</a> - - <li><a href="../../../ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="../../../BrowsingTest.html">BrowsingTest</a> - - <li><a href="../../../DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="../../../HomeController.html">HomeController</a> - - <li><a href="../../../HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="../../../HomeHelper.html">HomeHelper</a> - - <li><a href="../../../HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="../../../Object.html">Object</a> - - <li><a href="../../../User.html">User</a> - - <li><a href="../../../UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class ActiveSupport::TestCase::ActionController::TestCase</h1> - - <div id="description" class="description"> - -<p>Add more helper methods to be used by all tests here…</p> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/AddUsernameToUsers.html b/doc/AddUsernameToUsers.html deleted file mode 100644 index 7ba243ffe..000000000 --- a/doc/AddUsernameToUsers.html +++ /dev/null @@ -1,211 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class AddUsernameToUsers - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>db/migrate/20120903112806_add_username_to_users.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">ActiveRecord::Migration - -</nav> - - - <!-- Method Quickref --> -<nav id="method-list-section" class="section"> - <h3 class="section-header">Methods</h3> - - <ul class="link-list"> - - <li><a href="#method-i-change">#change</a> - - </ul> -</nav> - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class AddUsernameToUsers</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - <section id="public-instance-5Buntitled-5D-method-details" class="method-section section"> - <h3 class="section-header">Public Instance Methods</h3> - - - <div id="method-i-change" class="method-detail "> - - <div class="method-heading"> - <span class="method-name">change</span><span - class="method-args">()</span> - <span class="method-click-advice">click to toggle source</span> - </div> - - - <div class="method-description"> - - - - - - <div class="method-source-code" id="change-source"> - <pre><span class="ruby-comment"># File db/migrate/20120903112806_add_username_to_users.rb, line 2</span> -<span class="ruby-keyword">def</span> <span class="ruby-identifier">change</span> - <span class="ruby-identifier">add_column</span> <span class="ruby-value">:users</span>, <span class="ruby-value">:username</span>, <span class="ruby-value">:string</span> -<span class="ruby-keyword">end</span></pre> - </div><!-- change-source --> - - </div> - - - - - </div><!-- change-method --> - - - </section><!-- public-instance-method-details --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/ApplicationController.html b/doc/ApplicationController.html deleted file mode 100644 index 82f808c2f..000000000 --- a/doc/ApplicationController.html +++ /dev/null @@ -1,165 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class ApplicationController - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>app/controllers/application_controller.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">ActionController::Base - -</nav> - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class ApplicationController</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/ApplicationHelper.html b/doc/ApplicationHelper.html deleted file mode 100644 index 472227423..000000000 --- a/doc/ApplicationHelper.html +++ /dev/null @@ -1,159 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>module ApplicationHelper - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="module"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>app/helpers/application_helper.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="module">module ApplicationHelper</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/BrowsingTest.html b/doc/BrowsingTest.html deleted file mode 100644 index 41924631c..000000000 --- a/doc/BrowsingTest.html +++ /dev/null @@ -1,215 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class BrowsingTest - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>test/performance/browsing_test.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">ActionDispatch::PerformanceTest - -</nav> - - - <!-- Method Quickref --> -<nav id="method-list-section" class="section"> - <h3 class="section-header">Methods</h3> - - <ul class="link-list"> - - <li><a href="#method-i-test_homepage">#test_homepage</a> - - </ul> -</nav> - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class BrowsingTest</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - <section id="public-instance-5Buntitled-5D-method-details" class="method-section section"> - <h3 class="section-header">Public Instance Methods</h3> - - - <div id="method-i-test_homepage" class="method-detail "> - - <div class="method-heading"> - <span class="method-name">test_homepage</span><span - class="method-args">()</span> - <span class="method-click-advice">click to toggle source</span> - </div> - - - <div class="method-description"> - - <p>Refer to the documentation for all available options self.profile_options = -{ :runs =&gt; 5, :metrics =&gt; [:wall_time, :memory]</p> - -<pre class="ruby">:<span class="ruby-identifier">output</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-string">'tmp/performance'</span>, :<span class="ruby-identifier">formats</span> =<span class="ruby-operator">&gt;</span> [:<span class="ruby-identifier">flat</span>] } -</pre> - - - - <div class="method-source-code" id="test_homepage-source"> - <pre><span class="ruby-comment"># File test/performance/browsing_test.rb, line 9</span> -<span class="ruby-keyword">def</span> <span class="ruby-identifier">test_homepage</span> - <span class="ruby-identifier">get</span> <span class="ruby-string">'/'</span> -<span class="ruby-keyword">end</span></pre> - </div><!-- test_homepage-source --> - - </div> - - - - - </div><!-- test_homepage-method --> - - - </section><!-- public-instance-method-details --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/DeviseCreateUsers.html b/doc/DeviseCreateUsers.html deleted file mode 100644 index 47769c2e0..000000000 --- a/doc/DeviseCreateUsers.html +++ /dev/null @@ -1,252 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class DeviseCreateUsers - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>db/migrate/20120903092956_devise_create_users.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">ActiveRecord::Migration - -</nav> - - - <!-- Method Quickref --> -<nav id="method-list-section" class="section"> - <h3 class="section-header">Methods</h3> - - <ul class="link-list"> - - <li><a href="#method-i-change">#change</a> - - </ul> -</nav> - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class DeviseCreateUsers</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - <section id="public-instance-5Buntitled-5D-method-details" class="method-section section"> - <h3 class="section-header">Public Instance Methods</h3> - - - <div id="method-i-change" class="method-detail "> - - <div class="method-heading"> - <span class="method-name">change</span><span - class="method-args">()</span> - <span class="method-click-advice">click to toggle source</span> - </div> - - - <div class="method-description"> - - - - - - <div class="method-source-code" id="change-source"> - <pre><span class="ruby-comment"># File db/migrate/20120903092956_devise_create_users.rb, line 2</span> -<span class="ruby-keyword">def</span> <span class="ruby-identifier">change</span> - <span class="ruby-identifier">create_table</span>(<span class="ruby-value">:users</span>) <span class="ruby-keyword">do</span> <span class="ruby-operator">|</span><span class="ruby-identifier">t</span><span class="ruby-operator">|</span> - <span class="ruby-comment">## Database authenticatable</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">string</span> <span class="ruby-value">:email</span>, <span class="ruby-value">:null</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-keyword">false</span>, <span class="ruby-value">:default</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-string">&quot;&quot;</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">string</span> <span class="ruby-value">:encrypted_password</span>, <span class="ruby-value">:null</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-keyword">false</span>, <span class="ruby-value">:default</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-string">&quot;&quot;</span> - - <span class="ruby-comment">## Recoverable</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">string</span> <span class="ruby-value">:reset_password_token</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">datetime</span> <span class="ruby-value">:reset_password_sent_at</span> - - <span class="ruby-comment">## Rememberable</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">datetime</span> <span class="ruby-value">:remember_created_at</span> - - <span class="ruby-comment">## Trackable</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">integer</span> <span class="ruby-value">:sign_in_count</span>, <span class="ruby-value">:default</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-value">0</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">datetime</span> <span class="ruby-value">:current_sign_in_at</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">datetime</span> <span class="ruby-value">:last_sign_in_at</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">string</span> <span class="ruby-value">:current_sign_in_ip</span> - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">string</span> <span class="ruby-value">:last_sign_in_ip</span> - - <span class="ruby-comment">## Confirmable</span> - <span class="ruby-comment"># t.string :confirmation_token</span> - <span class="ruby-comment"># t.datetime :confirmed_at</span> - <span class="ruby-comment"># t.datetime :confirmation_sent_at</span> - <span class="ruby-comment"># t.string :unconfirmed_email # Only if using reconfirmable</span> - - <span class="ruby-comment">## Lockable</span> - <span class="ruby-comment"># t.integer :failed_attempts, :default =&gt; 0 # Only if lock strategy is :failed_attempts</span> - <span class="ruby-comment"># t.string :unlock_token # Only if unlock strategy is :email or :both</span> - <span class="ruby-comment"># t.datetime :locked_at</span> - - <span class="ruby-comment">## Token authenticatable</span> - <span class="ruby-comment"># t.string :authentication_token</span> - - - <span class="ruby-identifier">t</span>.<span class="ruby-identifier">timestamps</span> - <span class="ruby-keyword">end</span> - - <span class="ruby-identifier">add_index</span> <span class="ruby-value">:users</span>, <span class="ruby-value">:email</span>, <span class="ruby-value">:unique</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-keyword">true</span> - <span class="ruby-identifier">add_index</span> <span class="ruby-value">:users</span>, <span class="ruby-value">:reset_password_token</span>, <span class="ruby-value">:unique</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-keyword">true</span> - <span class="ruby-comment"># add_index :users, :confirmation_token, :unique =&gt; true</span> - <span class="ruby-comment"># add_index :users, :unlock_token, :unique =&gt; true</span> - <span class="ruby-comment"># add_index :users, :authentication_token, :unique =&gt; true</span> -<span class="ruby-keyword">end</span></pre> - </div><!-- change-source --> - - </div> - - - - - </div><!-- change-method --> - - - </section><!-- public-instance-method-details --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/Gemfile.html b/doc/Gemfile.html deleted file mode 100644 index 812c7fd3f..000000000 --- a/doc/Gemfile.html +++ /dev/null @@ -1,165 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>Gemfile - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body class="file"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation" class="description"> - -<p>source ‘<a href="https://rubygems.org">rubygems.org</a>’</p> - -<p>gem ‘bundler’, ‘&gt;=1.1.5’</p> - -<p>gem ‘rails’, ‘3.2.8’</p> - -<p># Bundle edge Rails instead: # gem ‘rails’, :git =&gt; -‘git://github.com/rails/rails.git’</p> - -<p>gem ‘sqlite3’</p> - -<p># Gems used only for assets and not required # in production environments -by default. group :assets do</p> - -<pre class="ruby"><span class="ruby-identifier">gem</span> <span class="ruby-string">'sass-rails'</span>, <span class="ruby-string">'~&gt; 3.2.3'</span> -<span class="ruby-identifier">gem</span> <span class="ruby-string">'coffee-rails'</span>, <span class="ruby-string">'~&gt; 3.2.1'</span> - -<span class="ruby-comment"># See https://github.com/sstephenson/execjs#readme for more supported runtimes</span> -<span class="ruby-comment"># long term, we'll probably want node.js for performance, but this will do for now as it's easier for new people to install</span> -<span class="ruby-identifier">gem</span> <span class="ruby-string">'therubyracer'</span>, :<span class="ruby-identifier">platforms</span> =<span class="ruby-operator">&gt;</span> :<span class="ruby-identifier">ruby</span> - -<span class="ruby-identifier">gem</span> <span class="ruby-string">'uglifier'</span>, <span class="ruby-string">'&gt;= 1.0.3'</span> -</pre> - -<p>end</p> - -<p>gem ‘jquery-rails’</p> - -<p># To use ActiveModel has_secure_password # gem ‘bcrypt-ruby’, ‘~&gt; 3.0.0’</p> - -<p># To use Jbuilder templates for JSON # gem ‘jbuilder’</p> - -<p># Use unicorn as the app server # gem ‘unicorn’</p> - -<p># Deploy with Capistrano # gem ‘capistrano’</p> - -<p># To use debugger # gem ‘debugger’</p> - -<p># GROWSTUFF ADDITIONS BELOW HERE</p> - -<p># user signup/login/etc gem ‘devise’</p> - -<p># for testing gem ‘rspec-rails’ gem ‘webrat’</p> - -</div> - - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/Growstuff.html b/doc/Growstuff.html deleted file mode 100644 index 5d254f1f8..000000000 --- a/doc/Growstuff.html +++ /dev/null @@ -1,159 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>module Growstuff - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="module"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>config/application.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="module">module Growstuff</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/Growstuff/Application.html b/doc/Growstuff/Application.html deleted file mode 100644 index 253a6c5fe..000000000 --- a/doc/Growstuff/Application.html +++ /dev/null @@ -1,165 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class Growstuff::Application - RDoc Documentation</title> - -<link type="text/css" media="screen" href="../rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "../"; -</script> - -<script type="text/javascript" charset="utf-8" src="../js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="../index.html">Home</a> - <a href="../table_of_contents.html#classes">Classes</a> - <a href="../table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>config/application.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">Rails::Application - -</nav> - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="../Gemfile.html">Gemfile</a> - - <li class="file"><a href="../LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="../Rakefile.html">Rakefile</a> - - <li class="file"><a href="../public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="../ActiveSupport.html">ActiveSupport</a> - - <li><a href="../ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="../ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="../ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="../Growstuff.html">Growstuff</a> - - <li><a href="../Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="../AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="../ApplicationController.html">ApplicationController</a> - - <li><a href="../ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="../BrowsingTest.html">BrowsingTest</a> - - <li><a href="../DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="../HomeController.html">HomeController</a> - - <li><a href="../HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="../HomeHelper.html">HomeHelper</a> - - <li><a href="../HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="../Object.html">Object</a> - - <li><a href="../User.html">User</a> - - <li><a href="../UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class Growstuff::Application</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/HomeController.html b/doc/HomeController.html deleted file mode 100644 index a4ca7e224..000000000 --- a/doc/HomeController.html +++ /dev/null @@ -1,210 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class HomeController - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>app/controllers/home_controller.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link"><a href="ApplicationController.html">ApplicationController</a> - -</nav> - - - <!-- Method Quickref --> -<nav id="method-list-section" class="section"> - <h3 class="section-header">Methods</h3> - - <ul class="link-list"> - - <li><a href="#method-i-index">#index</a> - - </ul> -</nav> - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class HomeController</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - <section id="public-instance-5Buntitled-5D-method-details" class="method-section section"> - <h3 class="section-header">Public Instance Methods</h3> - - - <div id="method-i-index" class="method-detail "> - - <div class="method-heading"> - <span class="method-name">index</span><span - class="method-args">()</span> - <span class="method-click-advice">click to toggle source</span> - </div> - - - <div class="method-description"> - - - - - - <div class="method-source-code" id="index-source"> - <pre><span class="ruby-comment"># File app/controllers/home_controller.rb, line 2</span> -<span class="ruby-keyword">def</span> <span class="ruby-identifier">index</span> -<span class="ruby-keyword">end</span></pre> - </div><!-- index-source --> - - </div> - - - - - </div><!-- index-method --> - - - </section><!-- public-instance-method-details --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/HomeControllerTest.html b/doc/HomeControllerTest.html deleted file mode 100644 index c54cef24d..000000000 --- a/doc/HomeControllerTest.html +++ /dev/null @@ -1,165 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class HomeControllerTest - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>test/functional/home_controller_test.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">ActionController::TestCase - -</nav> - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class HomeControllerTest</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/HomeHelper.html b/doc/HomeHelper.html deleted file mode 100644 index 594f2e894..000000000 --- a/doc/HomeHelper.html +++ /dev/null @@ -1,159 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>module HomeHelper - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="module"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>app/helpers/home_helper.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="module">module HomeHelper</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/HomeHelperTest.html b/doc/HomeHelperTest.html deleted file mode 100644 index 9a4ea551e..000000000 --- a/doc/HomeHelperTest.html +++ /dev/null @@ -1,165 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class HomeHelperTest - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>test/unit/helpers/home_helper_test.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">ActionView::TestCase - -</nav> - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class HomeHelperTest</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/LICENSE_txt.html b/doc/LICENSE_txt.html deleted file mode 100644 index 148588a6a..000000000 --- a/doc/LICENSE_txt.html +++ /dev/null @@ -1,848 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>LICENSE - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body class="file"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation" class="description"> - -<pre> GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. &lt;http://fsf.org/&gt; -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for</pre> - -<p>software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software.</p> - -<pre>The licenses for most software and other practical works are designed</pre> - -<p>to take away your freedom to share and change the works. By contrast, our -General Public Licenses are intended to guarantee your freedom to share and -change all versions of a program–to make sure it remains free software for -all its users.</p> - -<pre>When we speak of free software, we are referring to freedom, not</pre> - -<p>price. Our General Public Licenses are designed to make sure that you have -the freedom to distribute copies of free software (and charge for them if -you wish), that you receive source code or can get it if you want it, that -you can change the software or use pieces of it in new free programs, and -that you know you can do these things.</p> - -<pre>Developers that use our General Public Licenses protect your rights</pre> - -<p>with two steps: (1) assert copyright on the software, and (2) offer you -this License which gives you legal permission to copy, distribute and/or -modify the software.</p> - -<pre>A secondary benefit of defending all users' freedom is that</pre> - -<p>improvements made in alternate versions of the program, if they receive -widespread use, become available for other developers to incorporate. Many -developers of free software are heartened and encouraged by the resulting -cooperation. However, in the case of software used on network servers, -this result may fail to come about. The GNU General Public License permits -making a modified version and letting the public access it on a server -without ever releasing its source code to the public.</p> - -<pre>The GNU Affero General Public License is designed specifically to</pre> - -<p>ensure that, in such cases, the modified source code becomes available to -the community. It requires the operator of a network server to provide the -source code of the modified version running there to the users of that -server. Therefore, public use of a modified version, on a publicly -accessible server, gives the public access to the source code of the -modified version.</p> - -<pre>An older license, called the Affero General Public License and</pre> - -<p>published by Affero, was designed to accomplish similar goals. This is a -different license, not a version of the Affero GPL, but Affero has released -a new version of the Affero GPL which permits relicensing under this -license.</p> - -<pre>The precise terms and conditions for copying, distribution and</pre> - -<p>modification follow.</p> - -<pre> TERMS AND CONDITIONS - -0. Definitions. - -&quot;This License&quot; refers to version 3 of the GNU Affero General Public License. - -&quot;Copyright&quot; also means copyright-like laws that apply to other kinds of</pre> - -<p>works, such as semiconductor masks.</p> - -<pre>&quot;The Program&quot; refers to any copyrightable work licensed under this</pre> - -<p>License. Each licensee is addressed as “you”. “Licensees” and -“recipients” may be individuals or organizations.</p> - -<pre>To &quot;modify&quot; a work means to copy from or adapt all or part of the work</pre> - -<p>in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a “modified version” of the -earlier work or a work “based on” the earlier work.</p> - -<pre>A &quot;covered work&quot; means either the unmodified Program or a work based</pre> - -<p>on the Program.</p> - -<pre>To &quot;propagate&quot; a work means to do anything with it that, without</pre> - -<p>permission, would make you directly or secondarily liable for infringement -under applicable copyright law, except executing it on a computer or -modifying a private copy. Propagation includes copying, distribution (with -or without modification), making available to the public, and in some -countries other activities as well.</p> - -<pre>To &quot;convey&quot; a work means any kind of propagation that enables other</pre> - -<p>parties to make or receive copies. Mere interaction with a user through a -computer network, with no transfer of a copy, is not conveying.</p> - -<pre>An interactive user interface displays &quot;Appropriate Legal Notices&quot;</pre> - -<p>to the extent that it includes a convenient and prominently visible feature -that (1) displays an appropriate copyright notice, and (2) tells the user -that there is no warranty for the work (except to the extent that -warranties are provided), that licensees may convey the work under this -License, and how to view a copy of this License. If the interface presents -a list of user commands or options, such as a menu, a prominent item in the -list meets this criterion.</p> - -<pre>1. Source Code. - -The &quot;source code&quot; for a work means the preferred form of the work</pre> - -<p>for making modifications to it. “<a href="Object.html">Object</a> code” -means any non-source form of a work.</p> - -<pre>A &quot;Standard Interface&quot; means an interface that either is an official</pre> - -<p>standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that is -widely used among developers working in that language.</p> - -<pre>The &quot;System Libraries&quot; of an executable work include anything, other</pre> - -<p>than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major Component, -and (b) serves only to enable use of the work with that Major Component, or -to implement a Standard Interface for which an implementation is available -to the public in source code form. A “Major Component”, in this context, -means a major essential component (kernel, window system, and so on) of the -specific operating system (if any) on which the executable work runs, or a -compiler used to produce the work, or an object code interpreter used to -run it.</p> - -<pre>The &quot;Corresponding Source&quot; for a work in object code form means all</pre> - -<p>the source code needed to generate, install, and (for an executable work) -run the object code and to modify the work, including scripts to control -those activities. However, it does not include the work’s System -Libraries, or general-purpose tools or generally available free programs -which are used unmodified in performing those activities but which are not -part of the work. For example, Corresponding Source includes interface -definition files associated with source files for the work, and the source -code for shared libraries and dynamically linked subprograms that the work -is specifically designed to require, such as by intimate data communication -or control flow between those subprograms and other parts of the work.</p> - -<pre>The Corresponding Source need not include anything that users</pre> - -<p>can regenerate automatically from other parts of the Corresponding Source.</p> - -<pre>The Corresponding Source for a work in source code form is that</pre> - -<p>same work.</p> - -<pre>2. Basic Permissions. - -All rights granted under this License are granted for the term of</pre> - -<p>copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your rights -of fair use or other equivalent, as provided by copyright law.</p> - -<pre>You may make, run and propagate covered works that you do not</pre> - -<p>convey, without conditions so long as your license otherwise remains in -force. You may convey covered works to others for the sole purpose of -having them make modifications exclusively for you, or provide you with -facilities for running those works, provided that you comply with the terms -of this License in conveying all material for which you do not control -copyright. Those thus making or running the covered works for you must do -so exclusively on your behalf, under your direction and control, on terms -that prohibit them from making any copies of your copyrighted material -outside their relationship with you.</p> - -<pre>Conveying under any other circumstances is permitted solely under</pre> - -<p>the conditions stated below. Sublicensing is not allowed; section 10 makes -it unnecessary.</p> - -<pre>3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological</pre> - -<p>measure under any applicable law fulfilling obligations under article 11 of -the WIPO copyright treaty adopted on 20 December 1996, or similar laws -prohibiting or restricting circumvention of such measures.</p> - -<pre>When you convey a covered work, you waive any legal power to forbid</pre> - -<p>circumvention of technological measures to the extent such circumvention is -effected by exercising rights under this License with respect to the -covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work’s users, -your or third parties’ legal rights to forbid circumvention of -technological measures.</p> - -<pre>4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you</pre> - -<p>receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; keep -intact all notices stating that this License and any non-permissive terms -added in accord with section 7 apply to the code; keep intact all notices -of the absence of any warranty; and give all recipients a copy of this -License along with the Program.</p> - -<pre>You may charge any price or no price for each copy that you convey,</pre> - -<p>and you may offer support or warranty protection for a fee.</p> - -<pre>5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to</pre> - -<p>produce it from the Program, in the form of source code under the terms of -section 4, provided that you also meet all of these conditions:</p> - -<pre> a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - &quot;keep intact all notices&quot;. - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - -A compilation of a covered work with other separate and independent</pre> - -<p>works, which are not by their nature extensions of the covered work, and -which are not combined with it such as to form a larger program, in or on a -volume of a storage or distribution medium, is called an “aggregate” if the -compilation and its resulting copyright are not used to limit the access or -legal rights of the compilation’s users beyond what the individual works -permit. Inclusion of a covered work in an aggregate does not cause this -License to apply to the other parts of the aggregate.</p> - -<pre>6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms</pre> - -<p>of sections 4 and 5, provided that you also convey the machine-readable -Corresponding Source under the terms of this License, in one of these ways:</p> - -<pre class="ruby"> <span class="ruby-identifier">a</span>) <span class="ruby-constant">Convey</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-keyword">in</span>, <span class="ruby-keyword">or</span> <span class="ruby-identifier">embodied</span> <span class="ruby-keyword">in</span>, <span class="ruby-identifier">a</span> <span class="ruby-identifier">physical</span> <span class="ruby-identifier">product</span> - (<span class="ruby-identifier">including</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">physical</span> <span class="ruby-identifier">distribution</span> <span class="ruby-identifier">medium</span>), <span class="ruby-identifier">accompanied</span> <span class="ruby-identifier">by</span> <span class="ruby-identifier">the</span> - <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span> <span class="ruby-identifier">fixed</span> <span class="ruby-identifier">on</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">durable</span> <span class="ruby-identifier">physical</span> <span class="ruby-identifier">medium</span> - <span class="ruby-identifier">customarily</span> <span class="ruby-identifier">used</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">software</span> <span class="ruby-identifier">interchange</span>. - - <span class="ruby-identifier">b</span>) <span class="ruby-constant">Convey</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-keyword">in</span>, <span class="ruby-keyword">or</span> <span class="ruby-identifier">embodied</span> <span class="ruby-keyword">in</span>, <span class="ruby-identifier">a</span> <span class="ruby-identifier">physical</span> <span class="ruby-identifier">product</span> - (<span class="ruby-identifier">including</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">physical</span> <span class="ruby-identifier">distribution</span> <span class="ruby-identifier">medium</span>), <span class="ruby-identifier">accompanied</span> <span class="ruby-identifier">by</span> <span class="ruby-identifier">a</span> - <span class="ruby-identifier">written</span> <span class="ruby-identifier">offer</span>, <span class="ruby-identifier">valid</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">at</span> <span class="ruby-identifier">least</span> <span class="ruby-identifier">three</span> <span class="ruby-identifier">years</span> <span class="ruby-keyword">and</span> <span class="ruby-identifier">valid</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">as</span> - <span class="ruby-identifier">long</span> <span class="ruby-identifier">as</span> <span class="ruby-identifier">you</span> <span class="ruby-identifier">offer</span> <span class="ruby-identifier">spare</span> <span class="ruby-identifier">parts</span> <span class="ruby-keyword">or</span> <span class="ruby-identifier">customer</span> <span class="ruby-identifier">support</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">that</span> <span class="ruby-identifier">product</span> - <span class="ruby-identifier">model</span>, <span class="ruby-identifier">to</span> <span class="ruby-identifier">give</span> <span class="ruby-identifier">anyone</span> <span class="ruby-identifier">who</span> <span class="ruby-identifier">possesses</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">either</span> (<span class="ruby-value">1</span>) <span class="ruby-identifier">a</span> - <span class="ruby-identifier">copy</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">the</span> <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">all</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">software</span> <span class="ruby-keyword">in</span> <span class="ruby-identifier">the</span> - <span class="ruby-identifier">product</span> <span class="ruby-identifier">that</span> <span class="ruby-identifier">is</span> <span class="ruby-identifier">covered</span> <span class="ruby-identifier">by</span> <span class="ruby-identifier">this</span> <span class="ruby-constant">License</span>, <span class="ruby-identifier">on</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">durable</span> <span class="ruby-identifier">physical</span> - <span class="ruby-identifier">medium</span> <span class="ruby-identifier">customarily</span> <span class="ruby-identifier">used</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">software</span> <span class="ruby-identifier">interchange</span>, <span class="ruby-keyword">for</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">price</span> <span class="ruby-identifier">no</span> - <span class="ruby-identifier">more</span> <span class="ruby-identifier">than</span> <span class="ruby-identifier">your</span> <span class="ruby-identifier">reasonable</span> <span class="ruby-identifier">cost</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">physically</span> <span class="ruby-identifier">performing</span> <span class="ruby-identifier">this</span> - <span class="ruby-identifier">conveying</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">source</span>, <span class="ruby-keyword">or</span> (<span class="ruby-value">2</span>) <span class="ruby-identifier">access</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">copy</span> <span class="ruby-identifier">the</span> - <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span> <span class="ruby-identifier">from</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">network</span> <span class="ruby-identifier">server</span> <span class="ruby-identifier">at</span> <span class="ruby-identifier">no</span> <span class="ruby-identifier">charge</span>. - - <span class="ruby-identifier">c</span>) <span class="ruby-constant">Convey</span> <span class="ruby-identifier">individual</span> <span class="ruby-identifier">copies</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">with</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">copy</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">the</span> - <span class="ruby-identifier">written</span> <span class="ruby-identifier">offer</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">provide</span> <span class="ruby-identifier">the</span> <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span>. <span class="ruby-constant">This</span> - <span class="ruby-identifier">alternative</span> <span class="ruby-identifier">is</span> <span class="ruby-identifier">allowed</span> <span class="ruby-identifier">only</span> <span class="ruby-identifier">occasionally</span> <span class="ruby-keyword">and</span> <span class="ruby-identifier">noncommercially</span>, <span class="ruby-keyword">and</span> - <span class="ruby-identifier">only</span> <span class="ruby-keyword">if</span> <span class="ruby-identifier">you</span> <span class="ruby-identifier">received</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">with</span> <span class="ruby-identifier">such</span> <span class="ruby-identifier">an</span> <span class="ruby-identifier">offer</span>, <span class="ruby-keyword">in</span> <span class="ruby-identifier">accord</span> - <span class="ruby-identifier">with</span> <span class="ruby-identifier">subsection</span> <span class="ruby-value">6</span><span class="ruby-identifier">b</span>. - - <span class="ruby-identifier">d</span>) <span class="ruby-constant">Convey</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">by</span> <span class="ruby-identifier">offering</span> <span class="ruby-identifier">access</span> <span class="ruby-identifier">from</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">designated</span> - <span class="ruby-identifier">place</span> (<span class="ruby-identifier">gratis</span> <span class="ruby-keyword">or</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">charge</span>), <span class="ruby-keyword">and</span> <span class="ruby-identifier">offer</span> <span class="ruby-identifier">equivalent</span> <span class="ruby-identifier">access</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">the</span> - <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span> <span class="ruby-keyword">in</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">same</span> <span class="ruby-identifier">way</span> <span class="ruby-identifier">through</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">same</span> <span class="ruby-identifier">place</span> <span class="ruby-identifier">at</span> <span class="ruby-identifier">no</span> - <span class="ruby-identifier">further</span> <span class="ruby-identifier">charge</span>. <span class="ruby-constant">You</span> <span class="ruby-identifier">need</span> <span class="ruby-keyword">not</span> <span class="ruby-identifier">require</span> <span class="ruby-identifier">recipients</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">copy</span> <span class="ruby-identifier">the</span> - <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span> <span class="ruby-identifier">along</span> <span class="ruby-identifier">with</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span>. <span class="ruby-constant">If</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">place</span> <span class="ruby-identifier">to</span> - <span class="ruby-identifier">copy</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">is</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">network</span> <span class="ruby-identifier">server</span>, <span class="ruby-identifier">the</span> <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span> - <span class="ruby-identifier">may</span> <span class="ruby-identifier">be</span> <span class="ruby-identifier">on</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">different</span> <span class="ruby-identifier">server</span> (<span class="ruby-identifier">operated</span> <span class="ruby-identifier">by</span> <span class="ruby-identifier">you</span> <span class="ruby-keyword">or</span> <span class="ruby-identifier">a</span> <span class="ruby-identifier">third</span> <span class="ruby-identifier">party</span>) - <span class="ruby-identifier">that</span> <span class="ruby-identifier">supports</span> <span class="ruby-identifier">equivalent</span> <span class="ruby-identifier">copying</span> <span class="ruby-identifier">facilities</span>, <span class="ruby-identifier">provided</span> <span class="ruby-identifier">you</span> <span class="ruby-identifier">maintain</span> - <span class="ruby-identifier">clear</span> <span class="ruby-identifier">directions</span> <span class="ruby-keyword">next</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">saying</span> <span class="ruby-identifier">where</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">find</span> <span class="ruby-identifier">the</span> - <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span>. <span class="ruby-constant">Regardless</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">what</span> <span class="ruby-identifier">server</span> <span class="ruby-identifier">hosts</span> <span class="ruby-identifier">the</span> - <span class="ruby-constant">Corresponding</span> <span class="ruby-constant">Source</span>, <span class="ruby-identifier">you</span> <span class="ruby-identifier">remain</span> <span class="ruby-identifier">obligated</span> <span class="ruby-identifier">to</span> <span class="ruby-keyword">ensure</span> <span class="ruby-identifier">that</span> <span class="ruby-identifier">it</span> <span class="ruby-identifier">is</span> - <span class="ruby-identifier">available</span> <span class="ruby-keyword">for</span> <span class="ruby-identifier">as</span> <span class="ruby-identifier">long</span> <span class="ruby-identifier">as</span> <span class="ruby-identifier">needed</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">satisfy</span> <span class="ruby-identifier">these</span> <span class="ruby-identifier">requirements</span>. - - <span class="ruby-identifier">e</span>) <span class="ruby-constant">Convey</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">using</span> <span class="ruby-identifier">peer</span><span class="ruby-operator">-</span><span class="ruby-identifier">to</span><span class="ruby-operator">-</span><span class="ruby-identifier">peer</span> <span class="ruby-identifier">transmission</span>, <span class="ruby-identifier">provided</span> - <span class="ruby-identifier">you</span> <span class="ruby-identifier">inform</span> <span class="ruby-identifier">other</span> <span class="ruby-identifier">peers</span> <span class="ruby-identifier">where</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span> <span class="ruby-keyword">and</span> <span class="ruby-constant">Corresponding</span> - <span class="ruby-constant">Source</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">work</span> <span class="ruby-identifier">are</span> <span class="ruby-identifier">being</span> <span class="ruby-identifier">offered</span> <span class="ruby-identifier">to</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">general</span> <span class="ruby-identifier">public</span> <span class="ruby-identifier">at</span> <span class="ruby-identifier">no</span> - <span class="ruby-identifier">charge</span> <span class="ruby-identifier">under</span> <span class="ruby-identifier">subsection</span> <span class="ruby-value">6</span><span class="ruby-identifier">d</span>. - -<span class="ruby-constant">A</span> <span class="ruby-identifier">separable</span> <span class="ruby-identifier">portion</span> <span class="ruby-identifier">of</span> <span class="ruby-identifier">the</span> <span class="ruby-identifier">object</span> <span class="ruby-identifier">code</span>, <span class="ruby-identifier">whose</span> <span class="ruby-identifier">source</span> <span class="ruby-identifier">code</span> <span class="ruby-identifier">is</span> <span class="ruby-identifier">excluded</span> -</pre> - -<p>from the Corresponding Source as a System Library, need not be included in -conveying the object code work.</p> - -<pre>A &quot;User Product&quot; is either (1) a &quot;consumer product&quot;, which means any</pre> - -<p>tangible personal property which is normally used for personal, family, or -household purposes, or (2) anything designed or sold for incorporation into -a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, “normally used” refers to a typical -or common use of that class of product, regardless of the status of the -particular user or of the way in which the particular user actually uses, -or expects or is expected to use, the product. A product is a consumer -product regardless of whether the product has substantial commercial, -industrial or non-consumer uses, unless such uses represent the only -significant mode of use of the product.</p> - -<pre>&quot;Installation Information&quot; for a User Product means any methods,</pre> - -<p>procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that <a -href="User.html">User</a> Product from a modified version of its -Corresponding Source. The information must suffice to ensure that the -continued functioning of the modified object code is in no case prevented -or interfered with solely because modification has been made.</p> - -<pre>If you convey an object code work under this section in, or with, or</pre> - -<p>specifically for use in, a <a href="User.html">User</a> Product, and the -conveying occurs as part of a transaction in which the right of possession -and use of the <a href="User.html">User</a> Product is transferred to the -recipient in perpetuity or for a fixed term (regardless of how the -transaction is characterized), the Corresponding Source conveyed under this -section must be accompanied by the Installation Information. But this -requirement does not apply if neither you nor any third party retains the -ability to install modified object code on the <a href="User.html">User</a> -Product (for example, the work has been installed in ROM).</p> - -<pre>The requirement to provide Installation Information does not include a</pre> - -<p>requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for the -<a href="User.html">User</a> Product in which it has been modified or -installed. Access to a network may be denied when the modification itself -materially and adversely affects the operation of the network or violates -the rules and protocols for communication across the network.</p> - -<pre>Corresponding Source conveyed, and Installation Information provided,</pre> - -<p>in accord with this section must be in a format that is publicly documented -(and with an implementation available to the public in source code form), -and must require no special password or key for unpacking, reading or -copying.</p> - -<pre>7. Additional Terms. - -&quot;Additional permissions&quot; are terms that supplement the terms of this</pre> - -<p>License by making exceptions from one or more of its conditions. Additional -permissions that are applicable to the entire Program shall be treated as -though they were included in this License, to the extent that they are -valid under applicable law. If additional permissions apply only to part -of the Program, that part may be used separately under those permissions, -but the entire Program remains governed by this License without regard to -the additional permissions.</p> - -<pre>When you convey a copy of a covered work, you may at your option</pre> - -<p>remove any additional permissions from that copy, or from any part of it. -(Additional permissions may be written to require their own removal in -certain cases when you modify the work.) You may place additional -permissions on material, added by you to a covered work, for which you have -or can give appropriate copyright permission.</p> - -<pre>Notwithstanding any other provision of this License, for material you</pre> - -<p>add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms:</p> - -<pre> a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - -All other non-permissive additional terms are considered &quot;further</pre> - -<p>restrictions“ within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further restriction, -you may remove that term. If a license document contains a further -restriction but permits relicensing or conveying under this License, you -may add to a covered work material governed by the terms of that license -document, provided that the further restriction does not survive such -relicensing or conveying.</p> - -<pre>If you add terms to a covered work in accord with this section, you</pre> - -<p>must place, in the relevant source files, a statement of the additional -terms that apply to those files, or a notice indicating where to find the -applicable terms.</p> - -<pre>Additional terms, permissive or non-permissive, may be stated in the</pre> - -<p>form of a separately written license, or stated as exceptions; the above -requirements apply either way.</p> - -<pre>8. Termination. - -You may not propagate or modify a covered work except as expressly</pre> - -<p>provided under this License. Any attempt otherwise to propagate or modify -it is void, and will automatically terminate your rights under this License -(including any patent licenses granted under the third paragraph of section -11).</p> - -<pre>However, if you cease all violation of this License, then your</pre> - -<p>license from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally terminates -your license, and (b) permanently, if the copyright holder fails to notify -you of the violation by some reasonable means prior to 60 days after the -cessation.</p> - -<pre>Moreover, your license from a particular copyright holder is</pre> - -<p>reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after your -receipt of the notice.</p> - -<pre>Termination of your rights under this section does not terminate the</pre> - -<p>licenses of parties who have received copies or rights from you under this -License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10.</p> - -<pre>9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or</pre> - -<p>run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission to -receive a copy likewise does not require acceptance. However, nothing -other than this License grants you permission to propagate or modify any -covered work. These actions infringe copyright if you do not accept this -License. Therefore, by modifying or propagating a covered work, you -indicate your acceptance of this License to do so.</p> - -<pre>10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically</pre> - -<p>receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible for -enforcing compliance by third parties with this License.</p> - -<pre>An &quot;entity transaction&quot; is a transaction transferring control of an</pre> - -<p>organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered work -results from an entity transaction, each party to that transaction who -receives a copy of the work also receives whatever licenses to the work the -party’s predecessor in interest had or could give under the previous -paragraph, plus a right to possession of the Corresponding Source of the -work from the predecessor in interest, if the predecessor has it or can get -it with reasonable efforts.</p> - -<pre>You may not impose any further restrictions on the exercise of the</pre> - -<p>rights granted or affirmed under this License. For example, you may not -impose a license fee, royalty, or other charge for exercise of rights -granted under this License, and you may not initiate litigation (including -a cross-claim or counterclaim in a lawsuit) alleging that any patent claim -is infringed by making, using, selling, offering for sale, or importing the -Program or any portion of it.</p> - -<pre>11. Patents. - -A &quot;contributor&quot; is a copyright holder who authorizes use under this</pre> - -<p>License of the Program or a work on which the Program is based. The work -thus licensed is called the contributor’s “contributor version”.</p> - -<pre>A contributor's &quot;essential patent claims&quot; are all patent claims</pre> - -<p>owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted by -this License, of making, using, or selling its contributor version, but do -not include claims that would be infringed only as a consequence of further -modification of the contributor version. For purposes of this definition, -“control” includes the right to grant patent sublicenses in a manner -consistent with the requirements of this License.</p> - -<pre>Each contributor grants you a non-exclusive, worldwide, royalty-free</pre> - -<p>patent license under the contributor’s essential patent claims, to make, -use, sell, offer for sale, import and otherwise run, modify and propagate -the contents of its contributor version.</p> - -<pre>In the following three paragraphs, a &quot;patent license&quot; is any express</pre> - -<p>agreement or commitment, however denominated, not to enforce a patent (such -as an express permission to practice a patent or covenant not to sue for -patent infringement). To “grant” such a patent license to a party means to -make such an agreement or commitment not to enforce a patent against the -party.</p> - -<pre>If you convey a covered work, knowingly relying on a patent license,</pre> - -<p>and the Corresponding Source of the work is not available for anyone to -copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, then -you must either (1) cause the Corresponding Source to be so available, or -(2) arrange to deprive yourself of the benefit of the patent license for -this particular work, or (3) arrange, in a manner consistent with the -requirements of this License, to extend the patent license to downstream -recipients. “Knowingly relying” means you have actual knowledge that, but -for the patent license, your conveying the covered work in a country, or -your recipient’s use of the covered work in a country, would infringe one -or more identifiable patents in that country that you have reason to -believe are valid.</p> - -<pre>If, pursuant to or in connection with a single transaction or</pre> - -<p>arrangement, you convey, or propagate by procuring conveyance of, a covered -work, and grant a patent license to some of the parties receiving the -covered work authorizing them to use, propagate, modify or convey a -specific copy of the covered work, then the patent license you grant is -automatically extended to all recipients of the covered work and works -based on it.</p> - -<pre>A patent license is &quot;discriminatory&quot; if it does not include within</pre> - -<p>the scope of its coverage, prohibits the exercise of, or is conditioned on -the non-exercise of one or more of the rights that are specifically granted -under this License. You may not convey a covered work if you are a party -to an arrangement with a third party that is in the business of -distributing software, under which you make payment to the third party -based on the extent of your activity of conveying the work, and under which -the third party grants, to any of the parties who would receive the covered -work from you, a discriminatory patent license (a) in connection with -copies of the covered work conveyed by you (or copies made from those -copies), or (b) primarily for and in connection with specific products or -compilations that contain the covered work, unless you entered into that -arrangement, or that patent license was granted, prior to 28 March 2007.</p> - -<pre>Nothing in this License shall be construed as excluding or limiting</pre> - -<p>any implied license or other defenses to infringement that may otherwise be -available to you under applicable patent law.</p> - -<pre>12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or</pre> - -<p>otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program.</p> - -<pre>13. Remote Network Interaction; Use with the GNU General Public License. - -Notwithstanding any other provision of this License, if you modify the</pre> - -<p>Program, your modified version must prominently offer all users interacting -with it remotely through a computer network (if your version supports such -interaction) an opportunity to receive the Corresponding Source of your -version by providing access to the Corresponding Source from a network -server at no charge, through some standard or customary means of -facilitating copying of software. This Corresponding Source shall include -the Corresponding Source for any work covered by version 3 of the GNU -General Public License that is incorporated pursuant to the following -paragraph.</p> - -<pre>Notwithstanding any other provision of this License, you have</pre> - -<p>permission to link or combine any covered work with a work licensed under -version 3 of the GNU General Public License into a single combined work, -and to convey the resulting work. The terms of this License will continue -to apply to the part which is the covered work, but the work with which it -is combined will remain governed by version 3 of the GNU General Public -License.</p> - -<pre>14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of</pre> - -<p>the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail -to address new problems or concerns.</p> - -<pre>Each version is given a distinguishing version number. If the</pre> - -<p>Program specifies that a certain numbered version of the GNU Affero General -Public License “or any later version” applies to it, you have the option of -following the terms and conditions either of that numbered version or of -any later version published by the Free Software Foundation. If the -Program does not specify a version number of the GNU Affero General Public -License, you may choose any version ever published by the Free Software -Foundation.</p> - -<pre>If the Program specifies that a proxy can decide which future</pre> - -<p>versions of the GNU Affero General Public License can be used, that proxy’s -public statement of acceptance of a version permanently authorizes you to -choose that version for the Program.</p> - -<pre>Later license versions may give you additional or different</pre> - -<p>permissions. However, no additional obligations are imposed on any author -or copyright holder as a result of your choosing to follow a later version.</p> - -<pre>15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY</pre> - -<p>APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</p> - -<pre>16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING</pre> - -<p>WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES.</p> - -<pre>17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided</pre> - -<p>above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates an -absolute waiver of all civil liability in connection with the Program, -unless a warranty or assumption of liability accompanies a copy of the -Program in return for a fee.</p> - -<pre> END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest</pre> - -<p>possible use to the public, the best way to achieve this is to make it free -software which everyone can redistribute and change under these terms.</p> - -<pre>To do so, attach the following notices to the program. It is safest</pre> - -<p>to attach them to the start of each source file to most effectively state -the exclusion of warranty; and each file should have at least the -“copyright” line and a pointer to where the full notice is found.</p> - -<pre>&lt;one line to give the program's name and a brief idea of what it does.&gt; -Copyright (C) &lt;year&gt; &lt;name of author&gt; - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.</pre> - -<p>Also add information on how to contact you by electronic and paper mail.</p> - -<pre>If your software can interact with users remotely through a computer</pre> - -<p>network, you should also make sure that it provides a way for users to get -its source. For example, if your program is a web application, its -interface could display a “Source” link that leads users to an archive of -the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements.</p> - -<pre>You should also get your employer (if you work as a programmer) or school,</pre> - -<p>if any, to sign a “copyright disclaimer” for the program, if necessary. For -more information on this, and how to apply and follow the GNU AGPL, see -&lt;<a href="http://www.gnu.org/licenses/">www.gnu.org/licenses/</a>&gt;.</p> - -</div> - - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/Object.html b/doc/Object.html deleted file mode 100644 index 79f846d73..000000000 --- a/doc/Object.html +++ /dev/null @@ -1,192 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class Object - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>script/rails - <li>spec/support/devise.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">BasicObject - -</nav> - - <!-- Included Modules --> -<nav id="includes-section" class="section"> - <h3 class="section-header">Included Modules</h3> - - <ul class="link-list"> - - - <li><span class="include">Devise::TestHelpers</span> - - - </ul> -</nav> - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class Object</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - <!-- Constants --> - <section id="constants-list" class="section"> - <h3 class="section-header">Constants</h3> - <dl> - - <dt id="APP_PATH">APP_PATH - - <dd class="description"><p>This command will automatically be run when you run “rails” with Rails 3 -gems installed from the root of your application.</p> - - - </dl> - </section> - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/Rakefile.html b/doc/Rakefile.html deleted file mode 100644 index 1b4769a89..000000000 --- a/doc/Rakefile.html +++ /dev/null @@ -1,129 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>Rakefile - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body class="file"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation" class="description"> - -<p>#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks -ending in .rake, # for example lib/tasks/capistrano.rake, and they will -automatically be available to Rake.</p> - -<p>require File.expand_path(‘../config/application’, __FILE__)</p> - -<p>Growstuff::Application.load_tasks</p> - -</div> - - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/User.html b/doc/User.html deleted file mode 100644 index 91cd994ae..000000000 --- a/doc/User.html +++ /dev/null @@ -1,237 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class User - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>app/models/user.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link">ActiveRecord::Base - -</nav> - - - <!-- Method Quickref --> -<nav id="method-list-section" class="section"> - <h3 class="section-header">Methods</h3> - - <ul class="link-list"> - - <li><a href="#method-c-find_first_by_auth_conditions">::find_first_by_auth_conditions</a> - - </ul> -</nav> - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class User</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - <!-- Attributes --> - <section id="attribute-method-details" class="method-section section"> - <h3 class="section-header">Attributes</h3> - - - <div id="attribute-i-login" class="method-detail"> - <div class="method-heading attribute-method-heading"> - <span class="method-name">login</span><span - class="attribute-access-type">[RW]</span> - </div> - - <div class="method-description"> - - <p>Virtual attribute for authenticating by either username or email This is in -addition to a real persisted field like ‘username’</p> - - </div> - </div> - - </section><!-- attribute-method-details --> - - - <!-- Methods --> - - <section id="public-class-5Buntitled-5D-method-details" class="method-section section"> - <h3 class="section-header">Public Class Methods</h3> - - - <div id="method-c-find_first_by_auth_conditions" class="method-detail "> - - <div class="method-heading"> - <span class="method-name">find_first_by_auth_conditions</span><span - class="method-args">(warden_conditions)</span> - <span class="method-click-advice">click to toggle source</span> - </div> - - - <div class="method-description"> - - <p>allow login via either username or email address</p> - - - - <div class="method-source-code" id="find_first_by_auth_conditions-source"> - <pre><span class="ruby-comment"># File app/models/user.rb, line 18</span> -<span class="ruby-keyword">def</span> <span class="ruby-keyword">self</span>.<span class="ruby-identifier">find_first_by_auth_conditions</span>(<span class="ruby-identifier">warden_conditions</span>) - <span class="ruby-identifier">conditions</span> = <span class="ruby-identifier">warden_conditions</span>.<span class="ruby-identifier">dup</span> - <span class="ruby-keyword">if</span> <span class="ruby-identifier">login</span> = <span class="ruby-identifier">conditions</span>.<span class="ruby-identifier">delete</span>(<span class="ruby-value">:login</span>) - <span class="ruby-identifier">where</span>(<span class="ruby-identifier">conditions</span>).<span class="ruby-identifier">where</span>([<span class="ruby-string">&quot;lower(username) = :value OR lower(email) = :value&quot;</span>, { <span class="ruby-value">:value</span> =<span class="ruby-operator">&gt;</span> <span class="ruby-identifier">login</span>.<span class="ruby-identifier">downcase</span> }]).<span class="ruby-identifier">first</span> - <span class="ruby-keyword">else</span> - <span class="ruby-identifier">where</span>(<span class="ruby-identifier">conditions</span>).<span class="ruby-identifier">first</span> - <span class="ruby-keyword">end</span> -<span class="ruby-keyword">end</span></pre> - </div><!-- find_first_by_auth_conditions-source --> - - </div> - - - - - </div><!-- find_first_by_auth_conditions-method --> - - - </section><!-- public-class-method-details --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/UserTest.html b/doc/UserTest.html deleted file mode 100644 index d16c3c0ce..000000000 --- a/doc/UserTest.html +++ /dev/null @@ -1,165 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>class UserTest - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body id="top" class="class"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="file-metadata"> - <nav id="file-list-section" class="section"> - <h3 class="section-header">Defined In</h3> - <ul> - <li>test/unit/user_test.rb - </ul> -</nav> - - - </div> - - <div id="class-metadata"> - - <nav id="parent-class-section" class="section"> - <h3 class="section-header">Parent</h3> - - <p class="link"><a href="ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - -</nav> - - - - </div> - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation"> - <h1 class="class">class UserTest</h1> - - <div id="description" class="description"> - - </div><!-- description --> - - - - - <section id="5Buntitled-5D" class="documentation-section"> - - - - - - - - - <!-- Methods --> - - </section><!-- 5Buntitled-5D --> - -</div><!-- documentation --> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/created.rid b/doc/created.rid deleted file mode 100644 index c17c2b2fb..000000000 --- a/doc/created.rid +++ /dev/null @@ -1,54 +0,0 @@ -Mon, 03 Sep 2012 12:42:01 +0100 -./app/controllers/application_controller.rb Mon, 03 Sep 2012 10:17:55 +0100 -./app/controllers/home_controller.rb Mon, 03 Sep 2012 10:17:55 +0100 -./app/helpers/application_helper.rb Mon, 03 Sep 2012 10:17:55 +0100 -./app/helpers/home_helper.rb Mon, 03 Sep 2012 10:17:55 +0100 -./app/models/user.rb Mon, 03 Sep 2012 12:32:00 +0100 -./config/application.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/boot.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/environment.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/environments/development.rb Mon, 03 Sep 2012 10:25:16 +0100 -./config/environments/production.rb Mon, 03 Sep 2012 10:27:39 +0100 -./config/environments/test.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/initializers/backtrace_silencers.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/initializers/devise.rb Mon, 03 Sep 2012 12:37:19 +0100 -./config/initializers/inflections.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/initializers/mime_types.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/initializers/secret_token.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/initializers/session_store.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/initializers/wrap_parameters.rb Mon, 03 Sep 2012 10:17:55 +0100 -./config/routes.rb Mon, 03 Sep 2012 10:29:56 +0100 -./db/migrate/20120903092956_devise_create_users.rb Mon, 03 Sep 2012 10:29:56 +0100 -./db/migrate/20120903112806_add_username_to_users.rb Mon, 03 Sep 2012 12:28:06 +0100 -./db/schema.rb Mon, 03 Sep 2012 12:28:15 +0100 -./db/seeds.rb Mon, 03 Sep 2012 10:17:55 +0100 -./Gemfile Mon, 03 Sep 2012 11:35:02 +0100 -./LICENSE.txt Mon, 03 Sep 2012 10:17:55 +0100 -./public/robots.txt Mon, 03 Sep 2012 10:17:55 +0100 -./Rakefile Mon, 03 Sep 2012 10:17:55 +0100 -./script/rails Mon, 03 Sep 2012 10:17:55 +0100 -./spec/model/user_spec.rb Mon, 03 Sep 2012 12:27:07 +0100 -./spec/spec_helper.rb Mon, 03 Sep 2012 11:19:30 +0100 -./spec/support/devise.rb Mon, 03 Sep 2012 11:18:19 +0100 -./spec/view/index_spec.rb Mon, 03 Sep 2012 12:24:04 +0100 -./test/functional/home_controller_test.rb Mon, 03 Sep 2012 10:17:55 +0100 -./test/performance/browsing_test.rb Mon, 03 Sep 2012 10:17:55 +0100 -./test/test_helper.rb Mon, 03 Sep 2012 11:15:56 +0100 -./test/unit/helpers/home_helper_test.rb Mon, 03 Sep 2012 10:17:55 +0100 -./test/unit/user_test.rb Mon, 03 Sep 2012 10:29:56 +0100 -./tmp/cache/assets/C34/BA0/sprockets%2F3992b52a62c593115eec391978853396 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/C62/1D0/sprockets%2F1638f87db345b13872695759502b54fe Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/C7A/170/sprockets%2Ff1663d34d4b6003379113df98f1433a5 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/CC5/AC0/sprockets%2F73cf352579b646a784a2780873ee5ea8 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/CE4/F40/sprockets%2F46ef378397125ec6711c3a9782ba5ca2 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/CEE/7F0/sprockets%2F9f1a01ee23ff1422e22013be03e41f32 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/D62/210/sprockets%2F0c9f5a68e10c76b2d39acc37bb242e22 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/DB9/220/sprockets%2Facdac6ea3d5d531eb5d58140810d1f1e Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/DD7/950/sprockets%2F3665aa30fefe8e40fe1f86cc5f90de35 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af Mon, 03 Sep 2012 10:31:06 +0100 -./tmp/cache/assets/EC0/B20/sprockets%2Fd193fe0f1b8f97fdd6cdaabecded43a5 Mon, 03 Sep 2012 10:31:06 +0100 diff --git a/doc/images/add.png b/doc/images/add.png deleted file mode 100755 index 6332fefea4be19eeadf211b0b202b272e8564898..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 733 zcmV<30wVp1P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!Z%IT!R5;6} zlj}>9VHk(~TedF+gQSL8D5xnVSSWAVY>J9b+m>@{iq7_KE}go~11+5s4;8hc+i0Xa zI1j@EX5!S+Me6HNqKzU5YQwL;-W5$p%ZMKMeR<%zp69-~?<4?8|C8S?bklXr4v&Ov zb&06v2|-x?qB`90yn>Qi%Sh2^G4n)$ZdyvTPf9}1)_buUT7>`e2G&2VU@~Bb(o+Mz zi4)>IxlSY${Dj4k={-9RzU^W5g9|2V5RZ2Zu<x6&^l=W_1sO_5@*~{AJR(k@osu$W zIOyKBVDIZThPU;2xYmJgUn>lL9s2xQbZ@r6eP9Ra5u(s|C0Nj#&4>wTSkb?%#=9?@ z^oxDy-O@tyN{L@by(WWvQ3%CyEu8x{+#Jb4-h&K9Owi)2p<DqQ{Z%tMS5;_RkwZ2s ziU|ZZE*fUaAe}14z#AR(OU=04okqn3igEs-_q_}KZ*?@>gg+heWDyked|3R$$kL@A z#sp1v-r+=G4B8D6DqsDH0@7OztA7aT9qc1Py{()w`m``?Y0&gi2=ROcc-9+nU^I6< zT=e_Y=<?pf1$FXK3F5I5#ceAAN5BHvd?h5_(jPS+7l@o3)VYh{*frx)Pb%2=Sw~G2 znu{1!PYZ*jM}To^G}f>vSnG@?3Ue{BW5ONFttcE!R-R_W4O01|0-|K-YNXLo2`4Qv z`r1LxR6#yf3FB%T95gJnaKKivA~Z}S9A(ZxE<qS_Liq!k=vg3Per<qx!vKx95m{X+ zQ8;uQPPk0h^qI`uo^&$1^CFd@NeQR5pADaHJwu&SvB+fQDdu0d@n7>DK}O3T04USJ P00000NkvXXu0mjf^IS-S diff --git a/doc/images/brick.png b/doc/images/brick.png deleted file mode 100644 index 7851cf34c946e5667221e3478668503eb1cd733f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 452 zcmV;#0XzPQP)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzR!KxbR2Ufr z!9Pe;0RRW!@7<lIl3o%R*`Lh}3L0v44WhBhrLn=H)^JZ~X)lPPp*Ym3y&;EiD#3^_ z;}R2s(2l&aJKy{DeMJDQEOc(}O)|dId@(Z*AR=OUqSZ^1Bx&_tPDVr&v9i#a-8-w+ zG-X<R@>Pdwe5?6tW?r-ok|b$oDQj8FV%kZPq;(MWOV8?8;<)(iP}>h<N+(r5>NMU> z7fbz%jjlr7h8uuoQ~J6}n}@Y@PdTk=)PxO{%7zmL?dchpZX*~n;I{!C>*(8cU;q(~ zAS%Po_@naEU!xidrBXD?;hN|x^%W|Ij)0y*r5vi|?W&Fub(NqJ@z0o=O<Ao~*SBxI z`SL+wOarB!5l6MHf7`vAYen#UBKI<P{@6LCZG|0M`+1T_cfL~u@bp6FGkdxCW!wI8 u9^8Hp6am2FtGS=SJe+$CKoI}{0Kk9KAGswB^FYP`0000<MNUMnLSTXd=EXk% diff --git a/doc/images/brick_link.png b/doc/images/brick_link.png deleted file mode 100644 index 9ebf013a23a56653655a736a7e149deb7365ea03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 764 zcmV<Y0t5YtP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!j!8s8R5;6R zQq4=^Q55~b!2Ag<W<3P62<AVC7)g*?REu`A(sFPWZK_2?9|KY?Dk3XVN)jw9B3evR z^aU<T#iERcK19Qmte-!p$x=J>&SR3v>A``^efOSo-hEdApp;^Jd;9y!%1UfzX6Bh- z%-mbG|0Na{7Ruai_Y+DEb1s+b!<z(u6)%KZK>*9k%Q!whMxjtZKA*?o;i1g&jy0@( zaU=-@d-h+o%gal6JRXEXA&L3<d6_G5l$8XFi;E5c9334=MZ)LvVRdyCPN!3XF>`d2 z%jIxzZ~*p9O-;EJp_Ds0If38rM<5W8ic~K>FOK&2_p!CLg^i63OioVb6k$)zWHLx3 z5;!|M!}<9+#QSi1dRlbEcxPt^;cysUuU8@%3}RwpLRIGG<|IKnoyP6$Eh3SKw7a*r zSDXP=IYc&YZf;7@?fCe($^l9ORaJ3wbAx0uiC8QqRr$2t-Cfy8%XCI3B%pxJW>XdM zw~zPt_s}#A@pxQ5Ly)4szaMtH9lgE1SXx@b+S(fW`ub$fYPE8J7#bSNDzme*Ub07{ zQKV8SjEs!%0@v5ql8ggm!@$6Rbi^E8vBqpRM-}l+@5OSMrl+TWj*gC^qoV@>u{fQb zov5v?g~?>X@bEC&+uLPaQ&Ypn-y~^mZA}+f(&2EFH8eE%dU|@ENpN*_1-)L6_4Rc* zFuq@`IjX9vp1QiaK9ZojyZhnQURP99d=u;%37VRkpwsD4U0sd3x;hEQB&e^i|3QN0 z=H|Os1fRqaw!?#igLmS4HE!G3*ce(`TF}<kCS&HrU5YiVeML}SUjDAKvhuURV7M?E ujlV1w%Y(pUV`Jl2fiEJ~nByI5So;SIr}_C=u4<M50000<MNUMnLSTaX=VJK) diff --git a/doc/images/bug.png b/doc/images/bug.png deleted file mode 100644 index 2d5fb90ec6ee08f53947e0266a87b03f75893446..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 774 zcmV+h1Nr=kP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!m`OxIR5;6> zlgUq0Q544c8(ae&UR$8ps&snq6^bPY3v3xAmMW74Di$h~GCH6E3TaYs2#6A<7K*gC z777H71_Wa;(dfp+g-drPCSWu)#PInZi72LJ;o?i~$-U=y&UbQ89Dul3%3P+Axkzc* zbH-y;QF=hR{qLItf%ci2_&e5wNo0gnVatG?ul6Zw=o$I9Ljfn*ic3`U?>IfEim3g{ zujU&$-hy6wn;w(xme|zJm;lWJxtTFfM)q0`kX!Vu0+d${$}LCddK1<^htTe-fUYL3 zB`SdNsZD>RgvLj1<^@h6_+cDRK2Brcr2~>%$*5S)hyV33PV^teac3%|4lz@8p4?)5 z?t5o^?q+%^%)Yygo~I^U4VR!bTnWuE35hcWrfCDR3q+sxJ79e7Fg`&)RCqLA^2^y^ z0laVfadW90_Fz8Brm|r47sB^u1VgI>kanj)Z4`zMSfHlm8>CwXa$JVM`$2RrmZB-3 zN10m-!;BvH*Br3V8t`DH7m`jf#2upVDXl{5ff<mavWU@F1lMyb8vQzyPGq6ja~k(n zyJ4LwMAcX>18_pzCPK1Zu$$CKKvd8FGeFf)+K<|x33pc7P&S#3GZT4mEw;nr(Ze*F z3&*?-4U-lm*#t<gCyuZ=gqeG7A3)z#^rg8Z=vrz+@wg7Ig$`KUI#iD2pl8ko>ber5 z%S_ceqB`b3ko6r~BbvDwdohTvP(3a(pq{x#T$yQsu#OKwEe}KuH^Mh@nxg_(Nw136 zq#a^3xNBke)In+!?<ES&ylN&<%d1G-<d!CYb1t5U3SgCL7rB$H{GWnLOhq#n{3_0? zSPK_-*fz4_U*K%;$`ipopFsQ<fEC9WzmsT)-->qk3%4wB69{pF`Tzg`07*qoM6N<$ Eg55P&8UO$Q diff --git a/doc/images/bullet_black.png b/doc/images/bullet_black.png deleted file mode 100644 index 57619706d10d9736b1849a83f2c5694fbe09c53b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$h^>lFz(Kw&{<9vg>5sw~gS5O!4 zr|{HuUFIBKiQyL}eBJ-L{`UVT|6_O~L{G%N{Wbre{kQtZ_0LvEh<e1m{W1TG{hRu` z^=xmrTBL;jnKLdu{NFfXdsB4R+Qx@k0%istytgJC;bWA(_^)`{*9T!h3mH6J{an^L HB{Ts5vOrN_ diff --git a/doc/images/bullet_toggle_minus.png b/doc/images/bullet_toggle_minus.png deleted file mode 100644 index b47ce55f685dac56ee9d63f2c3d426bfc4c9e31a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$h^mK6y(Kw&{<9vg>(S^W+6Zii9 z|Nhthr~iNb*Z!}6uiN$Dz5neG3a-`baBX8yz1H+_;eX)`ni0%X8XBDc-`=Ph(Uan2 zYsR{H!kvIN--9isvHznRs<i)4|8qXx+(6ND8xzBc$?{dfrt4dQmN9s``njxgN@xNA D;VV%` diff --git a/doc/images/bullet_toggle_plus.png b/doc/images/bullet_toggle_plus.png deleted file mode 100644 index 9ab4a89664eee1aa81a51ca127f64cb3ac6fa918..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 209 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$h_H=O!(Kw&{<9vg>(S^W+6Zii9 z|Nhthr~iNb*Z!}6uiN$Dz5neG3a-`baBX8yz4q@v|B?28{s)#N@CGn3@%_y|zAV9T z66e<&B4?b6oF&azg|C(V&1ZbI_D}pL`}(^FT2yXwG1Ph~$Q@h8mJYOz!PC{xWt~$( F699+YQR)By diff --git a/doc/images/date.png b/doc/images/date.png deleted file mode 100644 index 783c83357fdf90a1c7c024358e1d768b5c09c135..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 626 zcmV-&0*(ENP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!1W80eR5;6( zQf+G*K@fiKywr>5OC%H;f`~O(q$Q#t2<^v$A>fbmv%e#dKTwK=Ku{5lS|}<-`a#7b zzTCOnnT>at)D}AMFuOZ5&%EqFN(lyumd$2ASF6=;nM~%2?gqc@U=#|4PqkX@EBo-9 z7pD#bO_RUa>*faM`8<YqcMz2dUgJsF7QDU1o6Af5yuZih`dZo2Qfj(J!cJB&2rCxD zcDqG59>;MYfVi$JnB-zcBFc6gjl$d!bF98Q!!!(Z1_R~P?e!pt#6CHJ9S&n_n&@=9 z%GP;!@Co4c*at+6vNz7o(6en^Q1%qHrc;1)9IRaz-$@S$Z-qdC^ds3X0NvQH;KS)D z-dh&rW&@X;1cS(45z)J&BV<Txgy3R<M<VsBsMqV#b=|+haU8i^E)`h#jbk|0zyVpW z*J!ufNT<{2_xq~%`FxIYx$J$pFZv+@4jzq0h|uJ7xg1id6hfg8D6<cb=ox|cn5SLv z7KPDjwNy0@50t8-R{WShT6kIFY&L_S$#d{zGErxS=XY=h`()e>t+tv&GMVJ%!EiW) zLBGZW)#Z+gl-Lih&?>X3SS-S#ujQ;9JRXmIB7X)8`d6ETj)D#Q2+$s|<_b7-B9Xvq zwNfqlEp%y3$uY`h{Y$(Gn5@}sqEsq95lpAkFO5dyBmP6^H-51G4J|rN2Ujt<`2YX_ M07*qoM6N<$f<j&%R{#J2 diff --git a/doc/images/delete.png b/doc/images/delete.png deleted file mode 100755 index 08f249365afd29594b51210c6e21ba253897505d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 715 zcmV;+0yO=JP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!T}ebiR5;6} zllx0kVHn5#Tecsf1c`2hgi%nK^D=kV+T5njvrLa$EMjSnone|mjm2E}L#U;8)yiKo zO>C4}Mrzlg<+1Y8PEBfUp0jJpx4B>@E+cy3`^(Gw`Mf+2&yxZm<$to~Vpgvg&QKNR z_f#1(r6svZt%iF?s+n<8X?B&!h3g9Dbb8_=MX}!;HiQSAh`bp^WMl~Z-44teO7W_Y zV4thSL{h;rJY7!l3%5J4H1!tIzB`Dv+YxO(haWeausGZYkI8^hWj6mzo=L0{%;<E2 z80_Y*w_}NMA$su)e0B@`wrYegSP*HT5w@N{_}&f79VIb*XrKGBY>yxzh{5!Htr?51 zvG|W62MzC8BZ76hRpCyO2zOn<%e)K>NHge!-~)Ap33OdWw6hsLYbCxGNt0%wk_2z7 zfyYvXheSG)5HRK1VB~%mq7Dmurw#bi@hEcOr3&G1ZiF*$M=&9nB#VNf&Q^r$4G5kp zTURh&s)E0%5&hyVD}sp<72~zmAY`Y(9aqO6CXF%=zFHGzO-A&I(pE}v70YQxCPJ{Y z4L+?5-crdLn3ZRPEs!A4ehEY3ZRpL~w9>@aMN+{F4dI@v&>(QDHQum!mG~E^$OS8l z!7?%Uwib*ROP67Hw`ika)gX-(<Pal@1N`)16#~~<@x7jghg9OTS^;mJ8T{oIOsMnG zla<QHU?S-#Kb7w%o*dlEj!JgnOSKW+hV$`!syc>8Ia`-u_IEhxG7U<13kSsMW+$<e xd62)I>lbb2dUMm5p6pa}cjgA+U$^mJ^AjD?&bdi)8~y+Q002ovPDHLkV1g8IMc@Dc diff --git a/doc/images/find.png b/doc/images/find.png deleted file mode 100644 index 1547479646722bda4647df52cf3e8bc9b77428c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 659 zcmV;E0&M+>P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!B}qg<R5;6H z`2YVu10|S&>IO9T&v~?D!=C@G6X*U1@h2}>2WE%HrrsjTfQsh6N9%SR25A5rkWp0g zzi;-6|3HJE;58sAyX1e@^d<jmJE#6{n>7EwiKQLb00%dp|5+t<{|l;G!D3eSuFDma zRCxr2MVY_`ELgLXqo}ssqp5E;*r|opZT~&|!~VN?1^mw`Yxp0VmiIp*r|Ey~#AW|W zTBd;IxVd?%*x1<_!3Ip2yP9Rn!u1aqt=siKx4a3At0%7dKV|u@|9wlg|7x7R;eT!K z{QuFp&Huxb3&AdAW?^~2z`(!^HUQ{cR*=op7H|BYU0VMi<mZ&!^8eAvIsetP^uRWP zTmlo1OD_QGV)zHdtY8Du3MyIsLSuef*gF093XJ$~Y+?IfTi^J<tb+1W5ixNCNg27V zViHpSf&Tt4DlYk-myiEHBO~KGpaD$qK=cWUIt&W9U5C&7U$y1H|Lz&fKmoP_B&VvW zv!b$P!vD294uR!BF~Y|$co-!X+<b!n<X1F<4FJ06e|$#ae?4RKs~|a`%~wG&05Tvo zzv6#-VfB9wPOd*F1~|I={)ZU=Q>3A-|5H&#ol!zs_8lnTU<QD~f|Zr+KZ*g?j&9(1 z1|=Yn0#IP;8kzkE`k!A(RpScC08lK!<bYv-Y5*vK7?@as0}tru|3I7nOUo+!2L_@P t(7(4ixw!uW;~f-zpcnvS+GQqEF#w)2XpIh9Jaqs7002ovPDHLkV1l;eHv<3w diff --git a/doc/images/loadingAnimation.gif b/doc/images/loadingAnimation.gif deleted file mode 100644 index 82290f48334c81272ff5991962951758137a08ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5886 zcmajjXHXMNw+HYfB!MJ=D4~f36H2I3m2PMP3Q|QxK|qw=qzI7`I)vU!XwnHCsVYcE z=`Hl$dsProxjgTe`_9~X=I-a+ncbP+{LeY7ta@Ku!ejtI184&P&d$zGPEL-Fj`sHU zHa9m{S63Go7iVW@CnqOIM@NT-hWh&Yy1KgB+uNI)n;RM$>g(&PtE($2Dhdk=&(F^R z|KGZGj(DV`tD_*NsU$2QNCCXqf9n(sfdh~LzJJdCa}5CGoUI+JZJBOCDz({abl~fE zw*5kfzVoR6cNi2r#C!ZEH0O<?=na-Gi{TCI{a6Jn2ArW<+UV98bAnZ^;a+`lkM_j) zfe61h)F=FMYqJVF9VO*kKTUU^ARmOsL)F0GkkGJ*NW$}nvB89}aO%h?t>;NW@rIh| zlqsqSSs9s#;sV;-@|>77A1W_O_DV`91Pq4Kz`Z(PaO&pn=GOMkuU$ROkc5GuVd!Y* zcn`UMY<Og<W2T$@p!3@zm^`quJ-N}bwKzFHy*;;wSZ$)>kYq7V07o@rsi~>-ziMLT zG+?a49zQWzia{TFcs{FKj#dh}e#z5@`O3omC>ELXboP2cR7WT?J@&ao#fn-I;sJ*F zD;=5p9?%y~V{F{q4^{|Zlt~d?*Ve!iWj&E%8@h^*gN$V29v5mAsN{O(ULD=kFMd^> zzLGLp)CZ#Qm6Q%3+`@kXtfre9GnE->Ai(oKKDoxtH@hRaB&C1e=IHR>I8;havNP_A z5Rq#nPVBdI5VpJ;S&et6>VVp>c?LwQ)tZWlq#H^i>)VP@16GREXU98`irCrvkEecY zkv~S7^T>M0*)Mb{LvE6`M77!t_ZXXI^`uU6W|L`YE-^~uca*s^)=F=9o*rxs>$qx+ zN_$rAd`ahYK<ux!QM!M>2^cpF)HkQ1(Vq|Urh;b~<55D)DL$EUNo=p_A6VQ1A+M~) zfa$>U0O5Rbu4r3$+|O$+gUQaOR@{dPsf3U1Dln%z0(Y0xq^w4=AKW8UMLXPC9RL7* zZ3?i~&mg|kvE%&Q2{D=<{q^E0^^<esUTaCLynFqpE!V)v6~>uNwISF-V^g!SN_6Pp zHm8=*qyzo0O&|aW=mQ}BV^c}pv_6$imk>cA#v4GgKI?F@S#sYw42|o9Jp1uLDt+Ls z2-H#~>q=LQWTF;nU7xJYKH2KCI4{O5B$T{{EgN}dE+rE|#F+n@O!gj|u;Xxe?Su03 z2tWqC_4M@)#<@OoQ{pg&@m`>d=YYXNQlKHoj2tjT2nB<`FCZcENCi2SLd5c#<U&$$ zes)7<ZCOJ}D<q_%qxP#%MmO&j1TTopWl2R16&g+QLQIW-#=It{mxfmsIA)wz7b|wR zP&;!+$kC06z3uhGwdLcPBCh=eb`Ox*KnT~Pg}e3~Ah5$a6;KgJ|AR$xpF2>Iz{+w= zQMis*31e?RPgP7h#4AOzY&hE#R4n&Ii?x5Yq0)?J7KNcBj@<EaVi{D7;GIOB6>XdX zlWZ;>n^k?`V`54w4oMu!H=JW%u_9}!!vS4^ZMC2#K+@g2!t)G5*y)(xiYlL_px35D zIhY0lK348EIpV!%r-=F;O(7xbv>oQP6>|(>Opp4COU-9M>Q6ub0PdDCFo(En#x&eN zGni{g@pt^Yi&Zk-WUSBg%!GQT&imw!)F&}=v0^+<x5$D|Y}u;@=M{@}e^xD7BBD;q z`0H+^xwo$}$<Tjle`vw+Tn7763=S7Pcd4yE{1SMulji50A@-tk8qoAYBDNv%g<O)l zbEf?B$Ulip4-t#iNuDS=HmuDES&?Wt&0MSHvc^nzgBNJ})5MYUYUR*7G$U7*+T}`> zPAeQFDhtKVnUuxMHpDJZ^)IYcqn3l$E3tGu>6%O0JW{Qd&uUAT_CJz)Db-2{$Z4Cq zibD~-93PZJRMP~xt4_LEY#WADM=C$k2DOim8}|&T7PflIw)ySUdh%=c{&;)e+r`Hd z>F)2L5sYyl@Pwfv-Z+Q9(~d^Q%E@BrXlV!+zKk$1SUf5lN)jz7MS>v}FnGm>Qbf5( zWmQ8>Y4OMAhWe&Lk?b!b?Oi<reF8bvwH*Wb-_mJlTm?WN9{USnBJ+zbh}F3T1a50> z7q@cwX@48D4*Plhd-GIrduvP}Ef)tlzfP@U!q&vPH#vyU*UZF+Z1UXs%zV%z6LOs+ zcaVxUJ2&!|`1z(BM}Lk=9HZd_-+C?1s|j<U@oN*p%WC17xc6HDfpXvCWW}xvT18@f zAxwxXhF2Ljbn^s2{!Z{}9!QDmAl6pWu2|R2=x(5AiYsQ4g-!+9<Q{WN{NwE%cvF$6 z(P;hEcE){WI!oiLxYJ3wU=(1!1$6VCO}==z;WsWyZynvCeEk?6kv;$*6RY2tqpl;( zgsomrE#-eNuyfk9J$_qmFK5DCY<topGX3$G>(*3pM}K)5P_O^ZvgjpgCOOIH^P=rz zrnafS&0I?@i8t47Fuv>lf<z7s6K@<oN!#SQ8@Tt^?sq`X#+b?UfaAMUl(=5<i(fr1 zFN_Yy7t@8kDUajFUqDAXo>^b<WqfZBS<KUQK$<esJJ3kxEvVD@IxScqrMW?0p!CLL zgIJU4|0SI`w4+4Rsoe>*BgG?Gr8}Rx=$^MeEIq58C~R;2W5b2+Z6DSOmY&y?jM>PP z<M}3<!=@Y*kezH0(Aj*rp%z#%Z09(|7c@Mm8g%t}|DKY)D$*P2^BCvohUO;ty+DDX zktn!-bSxrX5DE21rU<4HGkLQFbI@Q&dI365wFFg^onFROK`O5H7mh?wQwoBJ$O|Yf zNSNbS-qvpCUatP07Q}#c=jgccWZ#(G?C|{Hw`u$$8niqyw7KoEE4+pTnK*t2LG`S= zTF!p{J)l^!R|4Q)!yhTsLDQ)`DS+I%ol(Y(1pGB^apsDFwjgGi^fR%hNlx(ftsl9H z65CUG6x0S0@9MFupAv=C1E;DY@_K`~TYu}iSLQtukudD2xOjTG{r48+xE_ranI0(1 zXdp<=)GO5Ob`Ss9G}Bu4s@L#wP|cQT)$3byAkEB|g1BoGrJ2@GcwaOi&Wx&x2Gfn1 zz3FLPvl&oJ?F197n<NOU!ORz#{<YW{vX=Z%?1%+7;iNs_tU@U^=`OO>mCH(!b;p5a z08~hSk!QD03@!sbLen@ur<E_xxFN6+5qIroT9wTL?55d{<Tq{@1r>U{Gbn>9K(ikm zl#3h~9C5N=ig9Rs_qtTd=#qk`!ZGs7NvnMZ+uzd@j(?Rvpko)yuH)l~lSKOGS)aBD z7_OmZBdg=SE=0ln<WZNt8fZIo8VVolRK#>y&|8m4WGI#J|9B<cTiCi#Y@MlWb!V7A ztkpT9_Unz_v9Y&KIHZTIUx2T-hL*D>J}fBGEjm<G2+j>h_+3QFV-yUQn(l{$5#`e$ znfciyaIqFV2bzbhDu?7{<$RLQFC=|ws^?CtX)4I8sO>-(eMb1ar-sUdK)fzgqvMk> zZ^Rh)#8kxW$|S;j1HHPvzPz`!bA(!5h*+9K{Bl4}FHo45&3%yp?rDAP3~x@+ME*8G z&}mIK2Y`4+qxB<9rNt@5hlZ)HG`HKZFPtZ(CdCW@wfOGs!rX<kdt?O3)w<|6^L>e8 z-mBDPnj{HhE4Ayk=DMsy6c5sbcY=`3>S0gZ@AO)^Sd)t$p13pA3PJ#dmLDTD1s}Wz z02ItQF~<A-)Ao*rvpY$NT!t>53Ov+wZ2P`n_U4VAJGo_<)CMpqJ3n-|`KmS8<a-VP zfb}6-9lHLtxlA-4pw!z`EWxpF_GW8^%G#coZchrGR~?^D-r4b_JQ8KY#@ifJ84r>^ z<6NCKAuP(yrPRXiqft#MxAk}%PIb2CItemH*OUB$_E1dAyieI6EigfeNusQvXT~9L zwllbU*O+j+W5Qti)3H?p?*D`9lDN^-b^Q#pv$U8g4>1<DX92|~7EkcZPwN)$-WZL3 za_5#WGQ%>bxARs=rK5^IfwL5Y4H4Pl{I}`^(PH1gYU{*wqe@3$h1OCneK4J4!&MRe zOI%s;fxPp5H9Bx6x{Qq<VT;a>EsK*Hpw`q|yBo$$v_ZDvLxN=kn=g9|eG|t{-cBCa zWSp2ev%7lwBK@tsaE^R7fx&OwUGQ#^arcni@_`qa0+Ih<3e19Mf+3k%g+)@Z0>QL0 z!HU9+@@y$mUhU^<UKk>$zNMt8xbj1@av;@3!U%#u{N{thykrE-duU`-05?CiI5){L zy%f8$xwgE)K0S*=93sE3FU*{+{yF$b=Jm0O!B_#^eoI(9dVeEu^GYSFGhk6VM2eP; zSzH6(dYAFYJ=IMG-RZ%6^E|!yINDStfqn3^nx(_a*MMt-QOJ6FngYP6Flzi8{}M1u z?#m8_6qlhH0|2mB*E(B$x{iH!qh!(v^CX*om>t8m-!J2T%OyrE@fg!+W!rCupnGfE zR%c(5_C1*?Q|=SfK?@c3?d{0gfIk6Qne%2NAR%5!D1e2lrEA=#=314|^y}mlbdU!h zPIxs%P{lm;bYgjBs1qyXxkN6UD66G>mRl#Xr4z~PvG$je@$TcPPQN{YiFfsV4Ahz{ z;nj44T{SOdcs1301%HU_N_w4#jyn9@;-ar3_x<_h`fhkmBj(Iby8UQuwZ@CP3EK}j zbXm^OyhBqkWQ~AeVy^iVB)4Wh<htyd@(`|fWUQJdfiVaGs(ocqzV%-d+<$k;WX&tD zv>)+=b5--vjbtrvx4823+e>fN%<h7hn*;MqAO3V=Q*THW3AUECZ%jCB8__OBbU4W= z3B3-Yu;rGl9kMQEDRE_H*`=TEb6r+{_x7R;+0gg}ya{r4)U*xrAOySI_lmmr1ZE;~ z0mhFL?w6J!mv!gDOG;*85iyl?2bRyD<A<m!sTBEGkjoD%PED^9_{7&-T?6@c=Rds* z2z1+GaG0MwW{<o80~@~pvpmT)Z8os7Il7ITSeU`hZE#L6&F-GA{r-trS5UT}3ju2k zEoU({L9UxDTx*m745li|AZb>unKd+&T&~@;LSp8#I-|*I=U2LzE0($<|LW%XsA_XQ z3>6@ct56W8`Y2>d{!pjH=F?<22mf_ejVWx&mfsLml615hA!(-FDBnc-jDQv_NKXNy z(=8#eu15MT`JMYUW~~vr%z{`z9S|~|_VAY6Ov4M7#Wa(*<q~zX79fCTCI%FDYcPv% zBkpxqSFEi;9^ade#g4RR9s;B!p^fP5w+0bmmsfo(IC=$oC5cA16WW$*H4UtH^=7|# zzI;wM)wbx}>O#3EWzRYv@&_zy|0i*@_46?BhYPPEpVGD|(a((4@b>fF)l-3jQvCcv z{o)yqMWo1gDT<tpxw7K9n_g#s(-`}_V`!3MJ)j_!D_4B%_04Jh@SpF*7-qhI9|HR~ zCQ1?!?CEF@G0Z<qV&0sMVk{>G1vWp=_AJoP5UPxA^qrdn6*;Qh%^sB8>DcX5d2bXh zu<5X$-n2+RVUy$k%$jmfMxgu4ZWTs$Oy{Q?try<h-NDr}$mWr=Rj^z5yH~Q*FkcKT z+3P}y7ZL_1re(a(07LjQQ%E_aV*UbBCL$Zc$6t;q$jz!KP0IUHTUK4tR^M2Wj^LuE z%6Nr<@?SV{7#P8fj=NA#an1D3Eudy#LBr&+4VSIyouK(`#BRj*gX7Ir<kHcfzwt%N zkNiP!Kicgj#-{f7YUHboabThJR!N3-RcZhESqUK1b(b*am@H*)Hm>u(5>W>)zs2)w zHL}wWPpTzwL2MM8=lkwHp3#jyMe3%J0Av0)*ixKl2lMvu@{j$n91n^pNe|jd``l0N z0RU<<It)3DPhm_srp&Qn2ClXYrDSHN*$@D5v+qycK(ygyw;%V5Sq1l0Yv280(M!}f zpY~V;SMkfg&QU5(K+pAf2w;Knq8fpJert*XhFrvTm}#z>BSv#yWY}G&Kb9IUxK2(l z!4Sz=T3g)J1mqFu!`seMX@O}Bp}gyZ@I7GK*7vWYuax&DJ=8$)<N%c0_@&zceiZy! zCsVh#-SK{xc=_fWWR!mG2{A?x*)q*ey|w6=@joYOr@a7Kc!2Oj=w=*|owV>{{tXS> z7+}lu)M-J126vy;?q&^}iM1!NCf1I@E@@H~O-PIlsM7kknVdsATr@pmBo(C~$G6gS z02;)2O@0&~`#fHDeC1eCZZs;s2N)@A;Z!v}6IRW@+w4GRSlrsuorBjfJ?y*o(0gj> zt+<hF<g`m2uFAOoM8(U(+1eH57GN7lNs0Fm0z(b>;DN~K1pX*UvM(B(Di$9F6+&eT z#bhNzlMA>q^N?j+@1IqnYvK};_)_77Ts{!elaGqJg{uwb(1mX6u=pkfLJYkfX+`v! zOm>eolNV<DQKK`%tH{B{sg1XYt+pmK^sB=@3TS#N1ca7fU)1Bz{9*#NUVoVw^-UfR z;=Yzyu8D1xLY^C5(|62n3!_3m+79NI`@&3xnyb`te?u%xm{-dceWjk)mEvGg`5fEx zs?YJZJt-CTJ-`2Quc9z!zbxs4Bci>>Nz$A&<O0N!itfAch8AnB$UScRJ~Lcqu+)oo zGt~|Yx)B;J+U}aHEX05a-iE7}6$gWU_kH`6xZg|<0AEUx62HF~tZ6JiRVZTdDqBcM z!z&rKmfD>W8YqkN#cU|#i6j>Ox+Eu4*8Myq{Eq?u*kn+nT<lKCb_n3J$zBJ)qovNG zc4TY`<MeU)Q`;<Me@v**)yB2myxzvdy7mTqH7hNJjG6PE<Cc8rv)SEQx_<^{*#G$> z<cKi)uRlW3Z-R8Xl5rz(p#gSlQAxfMi&6g13D;=z3ks#-xtiPo54sFuXV#f`p+-fo z_c3Q5_#eT*0i8Lb%(<;5c8zN%&S-ltcASTcKRVDy$JH$WkB#sS@{Ns(4-Y|wauJbf z!I6aMEDkCdG8+w{v_-=zijWtX@<HcAbuC8)ybcWcPyuWD%wEvWQQeXc`_kQ9S2@^; zE*R(@E$`~?ZJ1ym;)KZ!Qih?bSHJc{IY1ypVE`5N9{a&jKItd=*LRf}Kd#%ER1y?N z48LRd=j2srlmKSy8jA`(4BTq|Nsj3IzV->Q@k8?r`Isov^UI2=T{#K~skC)fRP-aj zcrJyQmQ!u>p5&{_zp7xOM(Q%smb6M%g6o4s^>A8#L41?8Ox^e7CM$W~*3!e8F7P`S zK9!26tqJVBt`?fLxM^Gf`xAacdcbz&)u<6pKM?qA_ms76BOQWg0Le^W#?SMIT$jE7 zyw1!lG*$#k#iqZyl9~L_CjIwBb}$%9+e2Vw!1@$nfpvj1y2o4hJabo7^;(V}>++Tz z{|NtdydBeFpKnv*Vg9BTu3P)+)3J?9`*6t|c{b*k>-L!PvY`#5^i1^XCnxh<!V5(> zky})0T&rp6<lxuP3(LBj-UJ`+CQVB;OWswVlUo*SiY9Q5x){6wmOSOytv)`jp#?L> zJFwUVv-;Dzt2_z1)}rtpHBQH#<-`N0%%UP1TF^VNx2@~Zh_4nbMMxj7zeHTrB&q)a Dl)1NK diff --git a/doc/images/macFFBgHack.png b/doc/images/macFFBgHack.png deleted file mode 100644 index c6473b324ee1dae1faaacc0826639833f551116c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIEX7WqAsj$Z!;#Vf<Z~8yL>4nJ za0`Jj<E6WGe}IBAC9V-A!TD(=<%vb942~)JNvR5MnMJAP`9;~q3eLgCGp@NY1J#Lw z)HxTWCYEI8=P86_=B6^3>l>Qs8<<Be|IZCnpyBD_7~;|S_N*f#g8>JF;+Fd5q0wCR k?u=~bH}2*0f`J3<krx?0%Kw?)3e>~k>FVdQ&MBb@0BAfpf&c&j diff --git a/doc/images/package.png b/doc/images/package.png deleted file mode 100644 index da3c2a2d74bab159ba0f65d7db601768258afcb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 853 zcmV-b1FHOqP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!=Sf6CR5;6h zlS^n^bsUAiduQ&<oixKtrjnE?i9(+-KGJSn)Z#|65V0w>5TQ^(M5v$(QKVE?W+9X! z*o}&~6c?_FreF)9NJB7b5Nbn{G0n4+%uJhR9(V5R|NFTpb|HgjefT!tIhLx@DR+N) zV+fHiR5Yt19}k|KnCsND{tH-`IMJ)3AE?OtyZ4>Un|6(d%h#JK`i&a7^xW9>`yBy` zS4SOHeOpC7$?hH5-#7Rswiue_8Ju*2N@<cu>$58=a#2OTA3png`w3v->gWif7t%e$ z$NLVS!tFT#8WL|Wa&K~+{%4P2cRfwes<Ks@rBR)qR%6DGzB_%zx;t^=2Sm<3wSUZV z3(w;>YV1_!F=3OaRVHl(>=`%&{x*s30c<x*y}ZCyX%=sL<;SENIdkyU%U0B=(JW6Q zVV$&B#jBU8uWfL2>}#CNE@&;ItrAv!f!)Oy$Q9t$uS=(sD$-J{T*^(8Eez1E-l3}} zPrfHZ1`qsIFe&gipuL8-IZbo<Chjw*P$R9RihuxC3MY2}C!NKw%plDQP690NZ?Khc z^BRr%BjQXRQfV}bQ4x}AgvTBXXaLDwxV=Y+QyE<U0kZiUJ5ht!?Z%|Dh!;YvNEoBW zV5>2Yg{lFGKs?ZZWcOaOdk*3`5T;$?AjbG1#`B510Er^h2)2r3Y{!8_2Gj=$KzuN5 zaErtW8W_Y2iJJjY)5pmTVJoPJYpanPOE<ovy}b(QA|$%NgrE^1Xe1avB=$lQsR43_ zP9r8mX=Vx)nNGYn+W%9qv$9rOyfzrN8-;A&VeCvVNCRSzM&&jVx^(xyj^z|szdgs+ zy~&l>uYHclM^C1F>${hFRpdi8a<2H|Xudf78bm(zwJ9`K%6I<a!RpPXihQdgmyUn7 zw9)ZQe_TE?q(;uG$XokfdB<v3mbpLwCvkiIXB8cP`9f*#f8Y3TrY;VRsK}>?q*Ua~ fW9JvIbn5*B+_J)rUMBs>00000NkvXXu0mjfH&TkY diff --git a/doc/images/page_green.png b/doc/images/page_green.png deleted file mode 100644 index de8e003f9fb8752c09e7f3655d5d8664b5c62fc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621 zcmV-z0+RiSP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz|4BqaR2Ufr z!9R@Oa~ucY=l%J9e!uJW>QqUjAtB;_Vvt6}AS_5YgM`Uqu`yva+H8^=4U$e4gHb}u zAQ2N{V3A%pO|?Pv?tb6z=jC}SiRa$G^v3q?*6XcYz$p|cq{uLj@#~Fi`J(>5{@&&N zy%T^+;>8cXx%|o77anP?&W1?<NFc8MyK(Wm8~*t1_`jci`1#z|`+LXGp-uJBx=&|+ z{L|E`C^A?w%yZn_ES`Pf7EeCdeLJq6&UpCxGjDx<+gBgGbljk-&Lo-EQ?qLHVroo9 z6-{9{zq_{g#CiALzqQ?WcRq#&p`f7^txBuXs<bL?D!>1A(>-T49z9pyeCl@7YI+Si zKti7=B~``}TImz(G{0PnlQA3P#MAd}sorMjkP!50B7$nAkU^%#nl{Q9lW0@}9fE-> zN(q7tRuiC_T1r|BBtVBTlQ2+70$Rf;eF`Z;lx46Cpu-rEgb)EBKq(b^W8l<^We(`D z43?0=01z<3G6+UUv6`CsWCk6^93!#+<;ws7007{zS3k2k9-zZKFO~(k`>s0y006+1 zgF_jyIhsL-`FM<ZjG(R@90C9U0O-Aqc^NZw*c>f~JL~C=cV75(CrJ|q;MVO961G=O zm9d)YpJg5g(4i_HKL75eSE}mq$Y}r}hyVdcV~p>6a}oXr80q`oj%+s700000NkvXX Hu0mjfPs|!l diff --git a/doc/images/page_white_text.png b/doc/images/page_white_text.png deleted file mode 100644 index 813f712f726c935f9adf8d2f2dd0d7683791ef11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-%6;pyTSA|c6o&@eC9QG)Hj&ExYL zO&oVL^)+cM^qd@ApywS>pwx0H@RDN}hq;7mU-SKczYQ-hnrr=;iDAQMZQ+*g=YOM= z!QlMQEn7FbaD->uKAYgo_j9)W&$$zS*W9}m(ey0q$&7l-XEWO0Y(9M=SnhLbwy<xp z<-V!w{rtjKUv+o*|6m()ceab>;d>@~SY$Ku*0xPvIOQeV1x7u_z-2-X>_74(yfh7C znXL|3GZ+d2`3re2hs?MK<V&+KGL$sTUzRzG=^m3+$2Td42G30@X|lcy44dbP=-$#! h7h_<U68r8SGxL_I=i_|Jgn@o$@O1TaS?83{1OPX3f*b$< diff --git a/doc/images/page_white_width.png b/doc/images/page_white_width.png deleted file mode 100644 index 1eb880947ddf3e745c29e8d9dc90f09c7e6e323c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6SkfJR9T^zbpD<_bdI{u9mbgZg z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$R?&;zfqH(@;q9b3Efq-lM(nr^( z=EYR73-9e)UYMWsXy%?aZsD68Yyv^2$~6QgEcljw%kx>O(f-gQ?@fOOx3A-0+Qw?O zRx~W)kn~Qe2d6f9nMG#g9Q04Mk==M~N!Dglvxk!fgVh#w@ZV$IY1+Xc`d{d2UcaP~ zfWp)_Ivqj}l2SPy^9ZWy6rG9Yx4v67_uA&&9|XA~5-#3)W3%em1peD8RWH^#O%XoM zxMPud%}GTj#~*+7JMxTd!`{^Q+>(D3*|@KV`*G2;{QnANOxu1$r2xIe;OXk;vd$@? F2>@zac~<}c diff --git a/doc/images/plugin.png b/doc/images/plugin.png deleted file mode 100644 index 6187b15aec001b7080b51a5f944f07591f26cc15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 591 zcmV-V0<iswP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz;Ymb6R5;6} zl08V3aTLaXuVw{)TtAV-EQp(VZ&;&yOGCA^HyY7UV+BnWv^BakMQc+OB&0Z27AkkL zxPppG-9jbH?t7kd8eTEus*7hi@aLT8f1cktNknjvIJDd2gQ>eEcNHZMNv|IbJ-M`( zKwWL~opzjJe^WpCmV9E;(0&ut2;4va_(#>M8)>9$R5viQnf(Nkh~VM$y>J(jqb$cj z+nL1Nm|mV)Gm|9MnHf*7Ja4OEAQz__^LRKOLEwqpiGV^^A*T=#&inGm-62Xs;dnSp zKj&H9T*boh2<no0+=ElOJZ83sfQeG2L`y@C?#3Rz{i>i)W+(n27l!C)>fq|L%VB1i ziC4p;NwV_}ZjW7$LRW#(_bKF#hp=!IqNO26Z*w2+LEwx{PVnZ&Sn}T;m<fe2Wm#Lz za$)sCo<WYS+HDKB-VTSEXg%7B+({$Nn7A;{*z`8{F+2a6g_Xssq{Gq<0W(qT9zX<h z>tzb<gWDVNaLQ|6kUI-2i;UhJKT-oRHNy~7Uq5lS^Bm=sIhHERNTM^>$;qA*nT@@+ zV5uQ@iXDTPoTbV#FRr~z04|PPh`wXTNoCm9*tG&?e3+fYl>K6+&3|Cc$KOpL`ER+_ dcRl5U#9zn6ZO}GF<yZg!002ovPDHLkV1oCe4!{5a diff --git a/doc/images/ruby.png b/doc/images/ruby.png deleted file mode 100644 index f763a16880751445e3c4a8802a28d3cf8b5787c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 592 zcmV-W0<ZmvP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz;z>k7R5;7c zlif>`Q5?tj7Yw@ZCMtTF^Q|ZedeJhM%QPCR*bs8V79p$QTo7e94yQNXRs<plf_Ncf zR5#^~QWPqv1!JZS9OCvor@4Ig;`4kuTUS9u;f)7Am*4L>-{0?hOn_-8n0AMO@u1Ts zNl8QzJs1#rz%RBt?ux>l+amAvh+J!{$lkaqv}+Erb-6j2xp>K4GLQnNB*W`hFg*?P z^AL@~(h~Z+w<L#$QHF*{4-TR}eMWp>fcWEXHqV^Tq-#z$7Y#o0;yFxA!00F}F2dX# zjE$iOgT#G4*1TR6kB1Gnn@>$meCh2a>c5YuIvFn-R2W@>4@M*m@-|jiDV?b)bccgA zyPfsMM!rjy>+1O2)5Eg29Z_*2p&qGnmS!OH?vZ(4>QB01d>j%9n4QINxkyT(Dos?I zjaWF$*IQmh`SF-?xU%xMEfjq1=6qY*<aQ_oGc&NTfFYkQ>g&lgG_cXv$BGoIWyfO5 zp>pdV*O+y=&6@N2WWFo(%RtT`Q(H^6zn^a%epE~Kx^mEJ{c8`luC$nc*z9j|4<BKS zj8KR~Q0@hgc{SxgS<62aY4hBLz3EeDqq#sQniR{tT57+=`Cm;G)ioBG_r1)!lG=Cl e>Ms8aJK-la<J!Mps^iK40000<MNUMnLSTYHVh<bu diff --git a/doc/images/tag_blue.png b/doc/images/tag_blue.png deleted file mode 100755 index 3f02b5f8f8bf7c89b60ff70437fb7df6bd95e327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1880 zcmZ8g2{hFE9{<ZSj1i&*6Ju<p%w!8=Oc;b~-x`xU8fMZkTQehD)?PPD$PhIm(&Hj) z$rjmP>dKL<Lx`c?JaV(YtKJ*kZs(o%J?H!Tet+Nd`7WRDIo}iyx3h9!RWJYma!!tR zULvcor_xfQS7YrzM3IpQv2nElfSMO(lBpj7KwO=I#d<hlu`mx7<06Gh27pMh>pnAK z!yd|CC&>l1b7`m$MH$ScEIP@XgT41O>|DzL{-38CH68OyX#u=G?d7;y&_o&o)f@3U z2(tr%Ok88caOL`xiQA8o;Vzr-$A$SOu6o|$&0DQAJ1Z7?OACaeoy+)PWu&~aueW<| z*KW^(^2}#30u*~<_mXScFNd6U&sxh5*GGMNytZGxkIGqL%v6329^u`FD6T?b?K!4B z@Hzh?O2Au=((Gu;rvgLMt^pS|u1rEkBgC8$oH%zgT`TvZiK#VDrVG?-i~6a_+WZb> zc1>>lb)xcuo^Cl8k%q3c_d*It_Vtj>RSovF&w;hS=6uYrT2e@-@l@P~uBN`zu!v>e zTm(is&jcQ6vuP?|;!e+(n8w)-Xjd!hwk@r2D0i00ygdKo2Xvs?&w_lajj5DHS@9I! z;_&ji2e<aV$#WB9|F!vLkJ}PL!`_X}pB&a(+Q#@<*`Rq-Oxf|#c<ZliTN|uYK@@jY z_(VN@Wc@rB<;UkQ43uhh_e6M}5qGpr%7BuN&1Ts`h`ivQifxxeDsbnAZzvp}sCx5J z%VXKO%h0ot{ek<N2!}S#*Eem>{!uusGnVn};Pu|dl5x-FhQyC8^-4Uo_;BLiOXzcE z&4PS2TBWSC=hsw0og;z#(mly@Ed2E1E$_VDaM?kloE4ob2XK&<N2`QweJa@f`r)p; z=Wk!UepjXVD&g`;Ol6_>K;OS~-nhIGlA4~UZrJu6*|}wi#TT?|yWUH+_&n($t0xta zBwTzSfE)uAw*L0>+`pTps}L-$jIP5Q_E$Am+l|{XfsKr0Vi~`Em?SJQ#0y)8vsxb1 zMdxJl^){_CDwI^}>)Pw${G?Ajc@P}x{Fvhoi0jbY^427?KPmoA_G)sqK}u$2(79Xg zC%}xm5JDcrsm5^vQEQpGEdJDc^yfuNAlqV1pZQVkOSceV<|{=|=@?=o4i_1RFUZth zC7cu<<ZGY{8xyiD`N36IG-68{#;=mkL7Z0?Yn87Yxj$GjGQG4AFB@61D^;!q9_=a) zG!k8o4Aaq{4FIxAdnyL-?i~RDF%rcG$HBR}pn@4R1d+rDA|oPcOc5FY(2*#SOCxiL zut*w}&PGLA!M`C;B7ZN8gu}i;IAK<BoT~>6%V3dVCI}P6DL4iUgTc@&(nXY)ox}HZ z(a#EgiNj%{kjRLL2t<T2g24(w8d_LbAWs<~jf@OL2m^K$okNT?ptH69Eb><!J2E?% zMPYI%3_5JDE-{D^&ar~S_Zt0>?{m_aKN`{5-&u+HAtQ-Qq#@!I@<(M+B3i@|g=LY6 z90tpW!JuMn_Lcy1q7g&LUSuLE3XS}K#P^nH<lY?rGuNM_zePo?F<?>VUmL`L)dbP| z0bt(+Cp#M-bH!LM*DzJ0Lfn;eTBV@|JvGS<eou7qp<dvmGN`R0ZwkKbxjH$c_(A@I zrgZf0UYdF84<(#!(t6>gpdoc1RhhV>(G-2(vE|>MrVgA9+?+0m4OzUqbT>-U-jg|v zLZMntq`r?fy1UCMh>z2Koi1SL-~N2ZrIf+dZW|;SWszsde}Dl!HOMc1Fa>K9)e&RI z)A?<RcDUSA?(fFOt&b^tr8V$rIj;m+4kZ)m|CrZckUIJuO6q$1+Xrq$`sOlG5@>aK zcviCdKDUg_%#u7YAE`A`Y3$(P4&m^@fEWAvjAwVmRWeUnmkrxA;E!fKoc{9Vi=<M& zfYn^i%Fi~trU}hWD2H`E0&(y~Z<Ojv&&@_mNo_drKWzm*!B@#+?$>lvFL}KmoS;g* zdjL?Y!VHUFq63aLj6VZE+tHts?Z1pFkiO9^k*<WVfcn~^3Z5#Yi4|82#PGSR&7i4b zj}+^8*TlKxg>5pGpFpU&5#5G4ATd{t>a&9zKBVB9=Ns^HF<WGGo%qagiZk^?C)j?H ze*6|$<x1<(&I=yy*-?#6Adz(UlY*=)EwLp-xP)~?f=2MHU=$KIX&9z!-VxeRUp4k? ze0(5;`$}+x)R7nycSa)gL+~r*i<FyQ<!_7GY8yel!VRu_M^Yy=*`}#uXUBp!pj#M4 z8uvS>U|DTGH8C+Xr2UqOU`Zxe)!|%j4=-QojGePq)pRGe;!f)Czk!u3vP_Jxu8(e6 zf4Q`F$Qio2Jw@N*E@k?c`+Sw}AYQjkT+x)OAe6eq(AT!iRuksKQn%Ao_Ac1T-p#Js I_CnHs0qX}mlmGw# diff --git a/doc/images/tag_green.png b/doc/images/tag_green.png deleted file mode 100644 index 83ec984bd73364134da0f98d27a800c5d3264180..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 613 zcmV-r0-F7aP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz_en%SR5;6p zlSz*fVGxG<Ps~-rzaZ)Od*)(BWAv&{G)7Hy+-~mi#?mc4>^5T)AZ%#@G{_P{NCN^P z(J0zvSn~SSm(Ur);-M~8^*;61*VRI`T1BN&LAhK;sZ>I-SVW;vfUfJv=ko^ugnc0x zhJodBxe>iyk3%w<%wC8<lSyEjCZF8tbZiLam<uG6$(;~^EK@U0qeL#3(<hS&=JPqv z6EK}lL({aa5P_zy8kzW<SvIHBDMmlLh#NAmwcBkZ5(&g&u?>holUJ4(iv>tL{`DQt zPOsyUbO_Cmc&*iHkqbm3ku`|GcC^OhF>jj9W*GkH;^g!iUVpib_h<L;oPk!5X0u5y z5C{Zph>*=@udp4h(P+e*zL_~ZmJjh(y^BxULwq>9zXoYE8sq{#pN~U0C6!8vY)5N2 z9P*}mw}7X<BfL|OZODmogjmVN>$O^qTtJef1ACWvJT9^wt-)Zh0r~j#0bT`f;-zv6 z^Tmw22!%rMcs!TaUX<-8s;X-B`+Xbo<nvX$@ZZ3j=rR8M{e4mFuvfSk(P&f*hr<?K zBDZVmOne8^bITUH-EN2dh}(`M7z~Pjzu%%uTzz5^v;3NPCxAjx6eoLWiw>+_uWuFa z1yIPc?DTrQ7KvRhmt*TG|L=EYQ=LqFX;=Lp`4}jx6BE-@00000NkvXXu0mjf=s_29 diff --git a/doc/images/transparent.png b/doc/images/transparent.png deleted file mode 100644 index d665e179efd797451084235f105425247fea0a14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;bAV5X>;M1%mmiTn0pv241o;Is pI6S+N2ITN~x;Tb#$R;N!@B(=T42&&nK2`x)44$rjF6*2UngG277DE64 diff --git a/doc/images/wrench.png b/doc/images/wrench.png deleted file mode 100644 index 5c8213fef5ab969f03189d4367e32e597e38bd7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610 zcmV-o0-gPdP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz^hrcPR5;6( zlgn=sQ5ePfU;JB)8e@zrS1w(P(S@XGn&CAJ9Uu+`XB?mlZLpTs7)o14)NzKF$>^jb z4`0v}DG1te)wmeb(>p90leRz?_mO+^JKy=v&2<29Od6?F%9%(c8los#f*@G`-%W&* z$)uBj2i<NLrfDJ`kK<ed1a5RX9rXKs<nwt%qtWvT@H}tX)pcD5$8m~%77mBOvMhqZ zpfyo|SpL^)wNNUR5Q#+Ig+d`UlgXgjY=Wlg@dB|}>@u-@SgX}gtyWPe6d*|w6h%R? zScK2#Yn%$sum0cy>90DmY*i{1XqpClEtktsRTZ)<!|U}PxZQ5&37A>l<r4Wm_>CUe z<<Bq-1G!ud48t7SmPu91<MA{oigKO^k+$K@9t!#&NFPnGYaPsp1_FV=U%%gf;WW3$ z8((^t6A1i@fN6PvC$E1ZTm22{=?-o&#{08!8cm+19gbh`LHb}~>FogV^*tm>8*AlX za4oiR!&85LrobG57qUHUX#{>Vz(RHpB5|@>9O6N$jqB8>%($0wxE5R3)b>Y~xtCo$ zCgEk&A?_#IxHdN)9tqre^o{ho4{?hmPuf@^@I3-wncaRd%|~<Ge)vVRgIZCBcqR{V wSNM)gGt<GxA>O3xbrKY=&TiwPYkJroM{;WUQTuMY8vp<R07*qoM6N<$g5pFD$^ZZW diff --git a/doc/images/wrench_orange.png b/doc/images/wrench_orange.png deleted file mode 100644 index 565a9330e0a156dff5bed2c9fad8c95a44344ba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 584 zcmV-O0=NB%P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz+DSw~R5;6( zle=qEQ5?tfe>g}f4o)2%U3C;eEDoiEh?94d(rV57VIF#8VqzW$HrDC|#U`x@QDbgi zVl)t9GGz&YY#D?gc%>hISA+_EBpnXt#pnC`p6@xw0$8TCbULjhlgVx(kuc)%xbgqq zR5+DNDFRN0!y)7Gm}oT0i39}h4h928qY?Rho^UvPGJ#kuW|-Amtrn`Pmd&+bFo@sp z$LI4IQw7BG?|#2ewOS<<3VjL$0=lMY^m;wqZujv5kx1l%Sl;V&Iy4#$ip3&@LV<F* zOr=u6>2!7vhhN=PCz%^9v24`qb(+m4W?!q-&~=?ssf5GfnAmJK<siIuU1iq_*)@J_ z-lE-Zlgs7sdcAw5WvOb|?e?zKYE=$~xM{+7;oBB`eF2)n_{kC;kLRb$<(fat?fxXQ ztg^Evuz(*Q!2A2qcnTZK{BocBd{j>V;3bvpDm0(NhahZ=&^sqo6Odj6>)Dq_3p~4~ zvb`d3Mydwjt&Df<pvNJ371qya$K8~L)Mfax0Y$O=<~HQ+2^<sn3f!63ro9sjk&955 zv+hdO>^hVmLtI2x=U&h9(JVYX-!y~z3zi;1>=LY;o(bL$(Yf$lf)dMf0-u^0HrpTG Wk@)HE*94aU0000<MNUMnLSTXwQVY`n diff --git a/doc/images/zoom.png b/doc/images/zoom.png deleted file mode 100644 index 908612e394525fc2e52a7e9b94689c25ce167381..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 692 zcmV;l0!#ggP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!MoC0LR5;6x z(o0CxK^(yG@64(#mWoThLp|674WUDA(q?t;P?ivh8yG1>m+BBgry{~j2fHLegbHP( zrgXNbr0}2;^nywdjLjZe?uxtrd3D(pZH@fFFc0{BW_~jxoO1w7-VX;6vK@ROA$$R6 zEmo;Ht-Mj|>5jUy{bQ^V5@53LRI8AgLpUm|m+15sqcz@QtVSo|oz7ArM8?pIn+>gN z0b=4_b5O|4A*;Q+vc9Vqr~%3V155*NV~@gTz}KSUiKB-uJzjMZ>5%Q#n24<J>H!V{ zTY(LLAE*NAHZ}C#wnj%Bw5OFIkRhkkAW#kDC3j9Wm0YXRaXlyyp>#mVfYG)eC;@ab zDb=T-BCAY4LI(Z@GOTr2V_A{pRwSmz+8Be>CjAw(=gnbVWAeguvZa93JmL(EDxv1m z0OP4q=fpAK1Mq!C2`OkEn37o;m#wF#(t(8Pu#S?2f#x<~4EO{@fmm`p9veD6RZ_jp z@Au4};q&`XuKEYgIiB4((kgxOs#YdqJw0fY>9^K_agEu5+$#k;w#%I2N>n_?)YIqu z`tq&#_^p?-%K*U0^}|7+9U(&k0?s;=r=uCZ%)H9_edH8wK}gB(nUB1FFk+2Ol%BXV zHoFY`D~2x|2<MlV_LK*M_;d3~#G=f;nc&I5tIrih8*Bdy01TcBh4V@ad&+k0#3A|l z={;ZHP0x8ts$yLLz{9q^VYQ@tHdhs}Ff$+VJXPD&J^o|e06<^M?m#TAFJ@<nYfaq~ aaezN5sVn;(iAePT0000<MNUMnLSTaSf<5^F diff --git a/doc/index.html b/doc/index.html deleted file mode 100644 index cae097cae..000000000 --- a/doc/index.html +++ /dev/null @@ -1,118 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="./index.html">Home</a> - <a href="./table_of_contents.html#classes">Classes</a> - <a href="./table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="./Gemfile.html">Gemfile</a> - - <li class="file"><a href="./LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="./Rakefile.html">Rakefile</a> - - <li class="file"><a href="./public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="./ActiveSupport.html">ActiveSupport</a> - - <li><a href="./ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="./ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="./ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="./Growstuff.html">Growstuff</a> - - <li><a href="./Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="./AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="./ApplicationController.html">ApplicationController</a> - - <li><a href="./ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="./BrowsingTest.html">BrowsingTest</a> - - <li><a href="./DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="./HomeController.html">HomeController</a> - - <li><a href="./HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="./HomeHelper.html">HomeHelper</a> - - <li><a href="./HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="./Object.html">Object</a> - - <li><a href="./User.html">User</a> - - <li><a href="./UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<p>This is the API documentation for RDoc Documentation. - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/js/darkfish.js b/doc/js/darkfish.js deleted file mode 100644 index 4be722fac..000000000 --- a/doc/js/darkfish.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * - * Darkfish Page Functions - * $Id: darkfish.js 53 2009-01-07 02:52:03Z deveiant $ - * - * Author: Michael Granger <mgranger@laika.com> - * - */ - -/* Provide console simulation for firebug-less environments */ -if (!("console" in window) || !("firebug" in console)) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", - "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; - - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -}; - - -/** - * Unwrap the first element that matches the given @expr@ from the targets and return them. - */ -$.fn.unwrap = function( expr ) { - return this.each( function() { - $(this).parents( expr ).eq( 0 ).after( this ).remove(); - }); -}; - - -function showSource( e ) { - var target = e.target; - var codeSections = $(target). - parents('.method-detail'). - find('.method-source-code'); - - $(target). - parents('.method-detail'). - find('.method-source-code'). - slideToggle(); -}; - -function hookSourceViews() { - $('.method-heading').click( showSource ); -}; - -function toggleDebuggingSection() { - $('.debugging-section').slideToggle(); -}; - -function hookDebuggingToggle() { - $('#debugging-toggle img').click( toggleDebuggingSection ); -}; - -function hookTableOfContentsToggle() { - $('.indexpage li .toc-toggle').each( function() { - $(this).click( function() { - $(this).toggleClass('open'); - }); - - var section = $(this).next(); - - $(this).click( function() { - section.slideToggle(); - }); - }); -} - -function hookSearch() { - var input = $('#search-field').eq(0); - var result = $('#search-results').eq(0); - $(result).show(); - - var search_section = $('#search-section').get(0); - $(search_section).show(); - - var search = new Search(search_data, input, result); - - search.renderItem = function(result) { - var li = document.createElement('li'); - var html = ''; - - // TODO add relative path to <script> per-page - html += '<p class="search-match"><a href="' + rdoc_rel_prefix + result.path + '">' + this.hlt(result.title); - if (result.params) - html += '<span class="params">' + result.params + '</span>'; - html += '</a>'; - - - if (result.namespace) - html += '<p class="search-namespace">' + this.hlt(result.namespace); - - if (result.snippet) - html += '<div class="search-snippet">' + result.snippet + '</div>'; - - li.innerHTML = html; - - return li; - } - - search.select = function(result) { - var result_element = result.get(0); - window.location.href = result_element.firstChild.firstChild.href; - } - - search.scrollIntoView = search.scrollInWindow; -}; - -function highlightTarget( anchor ) { - console.debug( "Highlighting target '%s'.", anchor ); - - $("a[name=" + anchor + "]").each( function() { - if ( !$(this).parent().parent().hasClass('target-section') ) { - console.debug( "Wrapping the target-section" ); - $('div.method-detail').unwrap( 'div.target-section' ); - $(this).parent().wrap( '<div class="target-section"></div>' ); - } else { - console.debug( "Already wrapped." ); - } - }); -}; - -function highlightLocationTarget() { - console.debug( "Location hash: %s", window.location.hash ); - if ( ! window.location.hash || window.location.hash.length == 0 ) return; - - var anchor = window.location.hash.substring(1); - console.debug( "Found anchor: %s; matching %s", anchor, "a[name=" + anchor + "]" ); - - highlightTarget( anchor ); -}; - -function highlightClickTarget( event ) { - console.debug( "Highlighting click target for event %o", event.target ); - try { - var anchor = $(event.target).attr( 'href' ).substring(1); - console.debug( "Found target anchor: %s", anchor ); - highlightTarget( anchor ); - } catch ( err ) { - console.error( "Exception while highlighting: %o", err ); - }; -}; - - -$(document).ready( function() { - hookSourceViews(); - hookDebuggingToggle(); - hookSearch(); - highlightLocationTarget(); - hookTableOfContentsToggle(); - - $('ul.link-list a').bind( "click", highlightClickTarget ); -}); diff --git a/doc/js/jquery.js b/doc/js/jquery.js deleted file mode 100644 index 48590ecb9..000000000 --- a/doc/js/jquery.js +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * jQuery JavaScript Library v1.6.2 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Jun 30 14:16:56 2011 -0400 - */ -(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function bZ(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bY(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bC.test(a)?d(a,e):bY(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bY(a+"["+e+"]",b[e],c,d);else d(a,b)}function bX(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bR,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bX(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bX(a,c,d,e,"*",g));return l}function bW(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bN),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bA(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bv:bw;if(d>0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bg(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(x,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(H)return H.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:|^on/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(o);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(o);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(n," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. -shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,N(a.origType,a.selector),f.extend({},a,{handler:M,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,N(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?E:D):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=E;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=E;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=E,this.stopPropagation()},isDefaultPrevented:D,isPropagationStopped:D,isImmediatePropagationStopped:D};var F=function(a){var b=a.relatedTarget,c=!1,d=a.type;a.type=a.data,b!==this&&(b&&(c=f.contains(this,b)),c||(f.event.handle.apply(this,arguments),a.type=d))},G=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?G:F,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?G:F)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&K("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&K("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var H,I=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var L={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||D,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=x.exec(h),k="",j&&(k=j[0],h=h.replace(x,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,L[h]?(a.push(L[h]+k),h=h+k):h=(L[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+N(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+N(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=T.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/<tbody/i,ba=/<|&#?\w+;/,bb=/<(?:script|object|embed|option|style)/i,bc=/checked\s*(?:[^=]|=\s*.checked.)/i,bd=/\/(java|ecma)script/i,be=/^\s*<!(?:\[CDATA\[|\-\-)/,bf={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bc.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bg(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bm)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i;b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bb.test(a[0])&&(f.support.checkClone||!bc.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j -)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1></$2>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bl(k[i]);else bl(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bd.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bn=/alpha\([^)]*\)/i,bo=/opacity=([^)]*)/,bp=/([A-Z]|^ms)/g,bq=/^-?\d+(?:px)?$/i,br=/^-?\d/,bs=/^[+\-]=/,bt=/[^+\-\.\de]+/g,bu={position:"absolute",visibility:"hidden",display:"block"},bv=["Left","Right"],bw=["Top","Bottom"],bx,by,bz;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bx(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bs.test(d)&&(d=+d.replace(bt,"")+parseFloat(f.css(a,c)),h="number"),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bx)return bx(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bA(a,b,d);f.swap(a,bu,function(){e=bA(a,b,d)});return e}},set:function(a,b){if(!bq.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cs(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cr("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cr("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cs(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cj.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=ck.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cr("show",1),slideUp:cr("hide",1),slideToggle:cr("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cn||cp(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!cl&&(co?(cl=!0,g=function(){cl&&(co(g),e.tick())},co(g)):cl=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cn||cp(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cl),cl=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var ct=/^t(?:able|d|h)$/i,cu=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cv(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!ct.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/doc/js/navigation.js b/doc/js/navigation.js deleted file mode 100644 index e41268123..000000000 --- a/doc/js/navigation.js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Navigation allows movement using the arrow keys through the search results. - * - * When using this library you will need to set scrollIntoView to the - * appropriate function for your layout. Use scrollInWindow if the container - * is not scrollable and scrollInElement if the container is a separate - * scrolling region. - */ -Navigation = new function() { - this.initNavigation = function() { - var _this = this; - - $(document).keydown(function(e) { - _this.onkeydown(e); - }).keyup(function(e) { - _this.onkeyup(e); - }); - - this.navigationActive = true; - } - - this.setNavigationActive = function(state) { - this.navigationActive = state; - this.clearMoveTimeout(); - } - - this.onkeyup = function(e) { - if (!this.navigationActive) return; - - switch(e.keyCode) { - case 37: //Event.KEY_LEFT: - case 38: //Event.KEY_UP: - case 39: //Event.KEY_RIGHT: - case 40: //Event.KEY_DOWN: - this.clearMoveTimeout(); - break; - } - } - - this.onkeydown = function(e) { - if (!this.navigationActive) return; - switch(e.keyCode) { - case 37: //Event.KEY_LEFT: - if (this.moveLeft()) e.preventDefault(); - break; - case 38: //Event.KEY_UP: - if (e.keyCode == 38 || e.ctrlKey) { - if (this.moveUp()) e.preventDefault(); - this.startMoveTimeout(false); - } - break; - case 39: //Event.KEY_RIGHT: - if (this.moveRight()) e.preventDefault(); - break; - case 40: //Event.KEY_DOWN: - if (e.keyCode == 40 || e.ctrlKey) { - if (this.moveDown()) e.preventDefault(); - this.startMoveTimeout(true); - } - break; - case 13: //Event.KEY_RETURN: - if (this.$current) - e.preventDefault(); - this.select(this.$current); - break; - } - if (e.ctrlKey && e.shiftKey) this.select(this.$current); - } - - this.clearMoveTimeout = function() { - clearTimeout(this.moveTimeout); - this.moveTimeout = null; - } - - this.startMoveTimeout = function(isDown) { - if (!$.browser.mozilla && !$.browser.opera) return; - if (this.moveTimeout) this.clearMoveTimeout(); - var _this = this; - - var go = function() { - if (!_this.moveTimeout) return; - _this[isDown ? 'moveDown' : 'moveUp'](); - _this.moveTimout = setTimeout(go, 100); - } - this.moveTimeout = setTimeout(go, 200); - } - - this.moveRight = function() { - } - - this.moveLeft = function() { - } - - this.move = function(isDown) { - } - - this.moveUp = function() { - return this.move(false); - } - - this.moveDown = function() { - return this.move(true); - } - - /* - * Scrolls to the given element in the scrollable element view. - */ - this.scrollInElement = function(element, view) { - var offset, viewHeight, viewScroll, height; - offset = element.offsetTop; - height = element.offsetHeight; - viewHeight = view.offsetHeight; - viewScroll = view.scrollTop; - - if (offset - viewScroll + height > viewHeight) { - view.scrollTop = offset - viewHeight + height; - } - if (offset < viewScroll) { - view.scrollTop = offset; - } - } - - /* - * Scrolls to the given element in the window. The second argument is - * ignored - */ - this.scrollInWindow = function(element, ignored) { - var offset, viewHeight, viewScroll, height; - offset = element.offsetTop; - height = element.offsetHeight; - viewHeight = window.innerHeight; - viewScroll = window.scrollY; - - if (offset - viewScroll + height > viewHeight) { - window.scrollTo(window.scrollX, offset - viewHeight + height); - } - if (offset < viewScroll) { - window.scrollTo(window.scrollX, offset); - } - } -} - diff --git a/doc/js/search.js b/doc/js/search.js deleted file mode 100644 index dbdfdcbc4..000000000 --- a/doc/js/search.js +++ /dev/null @@ -1,94 +0,0 @@ -Search = function(data, input, result) { - this.data = data; - this.$input = $(input); - this.$result = $(result); - - this.$current = null; - this.$view = this.$result.parent(); - this.searcher = new Searcher(data.index); - this.init(); -} - -Search.prototype = $.extend({}, Navigation, new function() { - var suid = 1; - - this.init = function() { - var _this = this; - var observer = function() { - _this.search(_this.$input[0].value); - }; - this.$input.keyup(observer); - this.$input.click(observer); // mac's clear field - - this.searcher.ready(function(results, isLast) { - _this.addResults(results, isLast); - }) - - this.initNavigation(); - this.setNavigationActive(false); - } - - this.search = function(value, selectFirstMatch) { - value = jQuery.trim(value).toLowerCase(); - if (value) { - this.setNavigationActive(true); - } else { - this.setNavigationActive(false); - } - - if (value == '') { - this.lastQuery = value; - this.$result.empty(); - this.setNavigationActive(false); - } else if (value != this.lastQuery) { - this.lastQuery = value; - this.firstRun = true; - this.searcher.find(value); - } - } - - this.addResults = function(results, isLast) { - var target = this.$result.get(0); - if (this.firstRun && (results.length > 0 || isLast)) { - this.$current = null; - this.$result.empty(); - } - - for (var i=0, l = results.length; i < l; i++) { - target.appendChild(this.renderItem.call(this, results[i])); - }; - - if (this.firstRun && results.length > 0) { - this.firstRun = false; - this.$current = $(target.firstChild); - this.$current.addClass('current'); - } - if (jQuery.browser.msie) this.$element[0].className += ''; - } - - this.move = function(isDown) { - if (!this.$current) return; - var $next = this.$current[isDown ? 'next' : 'prev'](); - if ($next.length) { - this.$current.removeClass('current'); - $next.addClass('current'); - this.scrollIntoView($next[0], this.$view[0]); - this.$current = $next; - } - return true; - } - - this.hlt = function(html) { - return this.escapeHTML(html). - replace(/\u0001/g, '<em>'). - replace(/\u0002/g, '</em>'); - } - - this.escapeHTML = function(html) { - return html.replace(/[&<>]/g, function(c) { - return '&#' + c.charCodeAt(0) + ';'; - }); - } - -}); - diff --git a/doc/js/search_index.js b/doc/js/search_index.js deleted file mode 100644 index daa7ceee9..000000000 --- a/doc/js/search_index.js +++ /dev/null @@ -1 +0,0 @@ -var search_data = {"index":{"searchIndex":["activesupport","testcase","actioncontroller","testcase","addusernametousers","applicationcontroller","applicationhelper","browsingtest","devisecreateusers","growstuff","application","homecontroller","homecontrollertest","homehelper","homehelpertest","object","user","usertest","change()","change()","find_first_by_auth_conditions()","index()","test_homepage()","gemfile","license","rakefile","robots"],"longSearchIndex":["activesupport","activesupport::testcase","activesupport::testcase::actioncontroller","activesupport::testcase::actioncontroller::testcase","addusernametousers","applicationcontroller","applicationhelper","browsingtest","devisecreateusers","growstuff","growstuff::application","homecontroller","homecontrollertest","homehelper","homehelpertest","object","user","usertest","addusernametousers#change()","devisecreateusers#change()","user::find_first_by_auth_conditions()","homecontroller#index()","browsingtest#test_homepage()","","","",""],"info":[["ActiveSupport","","ActiveSupport.html","",""],["ActiveSupport::TestCase","","ActiveSupport/TestCase.html","",""],["ActiveSupport::TestCase::ActionController","","ActiveSupport/TestCase/ActionController.html","",""],["ActiveSupport::TestCase::ActionController::TestCase","","ActiveSupport/TestCase/ActionController/TestCase.html","","<p>Add more helper methods to be used by all tests here…\n"],["AddUsernameToUsers","","AddUsernameToUsers.html","",""],["ApplicationController","","ApplicationController.html","",""],["ApplicationHelper","","ApplicationHelper.html","",""],["BrowsingTest","","BrowsingTest.html","",""],["DeviseCreateUsers","","DeviseCreateUsers.html","",""],["Growstuff","","Growstuff.html","",""],["Growstuff::Application","","Growstuff/Application.html","",""],["HomeController","","HomeController.html","",""],["HomeControllerTest","","HomeControllerTest.html","",""],["HomeHelper","","HomeHelper.html","",""],["HomeHelperTest","","HomeHelperTest.html","",""],["Object","","Object.html","",""],["User","","User.html","",""],["UserTest","","UserTest.html","",""],["change","AddUsernameToUsers","AddUsernameToUsers.html#method-i-change","()",""],["change","DeviseCreateUsers","DeviseCreateUsers.html#method-i-change","()",""],["find_first_by_auth_conditions","User","User.html#method-c-find_first_by_auth_conditions","(warden_conditions)","<p>allow login via either username or email address\n"],["index","HomeController","HomeController.html#method-i-index","()",""],["test_homepage","BrowsingTest","BrowsingTest.html#method-i-test_homepage","()","<p>Refer to the documentation for all available options self.profile_options =\n{ :runs =&gt; 5, :metrics …\n"],["Gemfile","","Gemfile.html","","<p>source ‘rubygems.org’\n<p>gem ‘bundler’, ‘&gt;=1.1.5’\n<p>gem ‘rails’, ‘3.2.8’\n"],["LICENSE","","LICENSE_txt.html","","\n<pre> GNU AFFERO GENERAL PUBLIC LICENSE\n Version 3, 19 November 2007 ...</pre>\n"],["Rakefile","","Rakefile.html","","<p>#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks\nending in .rake, # for example …\n"],["robots","","public/robots_txt.html","","<p>See www.robotstxt.org/wc/norobots.html for documentation on how to use the\nrobots.txt file\n<p>To ban all …\n"]]}} \ No newline at end of file diff --git a/doc/js/searcher.js b/doc/js/searcher.js deleted file mode 100644 index f854b541d..000000000 --- a/doc/js/searcher.js +++ /dev/null @@ -1,228 +0,0 @@ -Searcher = function(data) { - this.data = data; - this.handlers = []; -} - -Searcher.prototype = new function() { - // search is performed in chunks of 1000 for non-blocking user input - var CHUNK_SIZE = 1000; - // do not try to find more than 100 results - var MAX_RESULTS = 100; - var huid = 1; - var suid = 1; - var runs = 0; - - this.find = function(query) { - var queries = splitQuery(query); - var regexps = buildRegexps(queries); - var highlighters = buildHilighters(queries); - var state = { from: 0, pass: 0, limit: MAX_RESULTS, n: suid++}; - var _this = this; - - this.currentSuid = state.n; - - if (!query) return; - - var run = function() { - // stop current search thread if new search started - if (state.n != _this.currentSuid) return; - - var results = - performSearch(_this.data, regexps, queries, highlighters, state); - var hasMore = (state.limit > 0 && state.pass < 4); - - triggerResults.call(_this, results, !hasMore); - if (hasMore) { - setTimeout(run, 2); - } - runs++; - }; - runs = 0; - - // start search thread - run(); - } - - /* ----- Events ------ */ - this.ready = function(fn) { - fn.huid = huid; - this.handlers.push(fn); - } - - /* ----- Utilities ------ */ - function splitQuery(query) { - return jQuery.grep(query.split(/(\s+|::?|\(\)?)/), function(string) { - return string.match(/\S/) - }); - } - - function buildRegexps(queries) { - return jQuery.map(queries, function(query) { - return new RegExp(query.replace(/(.)/g, '([$1])([^$1]*?)'), 'i') - }); - } - - function buildHilighters(queries) { - return jQuery.map(queries, function(query) { - return jQuery.map(query.split(''), function(l, i) { - return '\u0001$' + (i*2+1) + '\u0002$' + (i*2+2); - }).join(''); - }); - } - - // function longMatchRegexp(index, longIndex, regexps) { - // for (var i = regexps.length - 1; i >= 0; i--){ - // if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) return false; - // }; - // return true; - // } - - - /* ----- Mathchers ------ */ - - /* - * This record matches if the index starts with queries[0] and the record - * matches all of the regexps - */ - function matchPassBeginning(index, longIndex, queries, regexps) { - if (index.indexOf(queries[0]) != 0) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) - return false; - }; - return true; - } - - /* - * This record matches if the longIndex starts with queries[0] and the - * longIndex matches all of the regexps - */ - function matchPassLongIndex(index, longIndex, queries, regexps) { - if (longIndex.indexOf(queries[0]) != 0) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!longIndex.match(regexps[i])) - return false; - }; - return true; - } - - /* - * This record matches if the index contains queries[0] and the record - * matches all of the regexps - */ - function matchPassContains(index, longIndex, queries, regexps) { - if (index.indexOf(queries[0]) == -1) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) - return false; - }; - return true; - } - - /* - * This record matches if regexps[0] matches the index and the record - * matches all of the regexps - */ - function matchPassRegexp(index, longIndex, queries, regexps) { - if (!index.match(regexps[0])) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) - return false; - }; - return true; - } - - - /* ----- Highlighters ------ */ - function highlightRegexp(info, queries, regexps, highlighters) { - var result = createResult(info); - for (var i=0, l = regexps.length; i < l; i++) { - result.title = result.title.replace(regexps[i], highlighters[i]); - result.namespace = result.namespace.replace(regexps[i], highlighters[i]); - }; - return result; - } - - function hltSubstring(string, pos, length) { - return string.substring(0, pos) + '\u0001' + string.substring(pos, pos + length) + '\u0002' + string.substring(pos + length); - } - - function highlightQuery(info, queries, regexps, highlighters) { - var result = createResult(info); - var pos = 0; - var lcTitle = result.title.toLowerCase(); - - pos = lcTitle.indexOf(queries[0]); - if (pos != -1) { - result.title = hltSubstring(result.title, pos, queries[0].length); - } - - result.namespace = result.namespace.replace(regexps[0], highlighters[0]); - for (var i=1, l = regexps.length; i < l; i++) { - result.title = result.title.replace(regexps[i], highlighters[i]); - result.namespace = result.namespace.replace(regexps[i], highlighters[i]); - }; - return result; - } - - function createResult(info) { - var result = {}; - result.title = info[0]; - result.namespace = info[1]; - result.path = info[2]; - result.params = info[3]; - result.snippet = info[4]; - return result; - } - - /* ----- Searching ------ */ - function performSearch(data, regexps, queries, highlighters, state) { - var searchIndex = data.searchIndex; - var longSearchIndex = data.longSearchIndex; - var info = data.info; - var result = []; - var i = state.from; - var l = searchIndex.length; - var togo = CHUNK_SIZE; - var matchFunc, hltFunc; - - while (state.pass < 4 && state.limit > 0 && togo > 0) { - if (state.pass == 0) { - matchFunc = matchPassBeginning; - hltFunc = highlightQuery; - } else if (state.pass == 1) { - matchFunc = matchPassLongIndex; - hltFunc = highlightQuery; - } else if (state.pass == 2) { - matchFunc = matchPassContains; - hltFunc = highlightQuery; - } else if (state.pass == 3) { - matchFunc = matchPassRegexp; - hltFunc = highlightRegexp; - } - - for (; togo > 0 && i < l && state.limit > 0; i++, togo--) { - if (info[i].n == state.n) continue; - if (matchFunc(searchIndex[i], longSearchIndex[i], queries, regexps)) { - info[i].n = state.n; - result.push(hltFunc(info[i], queries, regexps, highlighters)); - state.limit--; - } - }; - if (searchIndex.length <= i) { - state.pass++; - i = state.from = 0; - } else { - state.from = i; - } - } - return result; - } - - function triggerResults(results, isLast) { - jQuery.each(this.handlers, function(i, fn) { - fn.call(this, results, isLast) - }) - } -} - diff --git a/doc/public/robots_txt.html b/doc/public/robots_txt.html deleted file mode 100644 index 501292289..000000000 --- a/doc/public/robots_txt.html +++ /dev/null @@ -1,128 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>robots - RDoc Documentation</title> - -<link type="text/css" media="screen" href="../rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "../"; -</script> - -<script type="text/javascript" charset="utf-8" src="../js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="../js/darkfish.js"></script> - - -<body class="file"> -<nav id="metadata"> - <nav id="home-section" class="section"> - <h3 class="section-header"> - <a href="../index.html">Home</a> - <a href="../table_of_contents.html#classes">Classes</a> - <a href="../table_of_contents.html#methods">Methods</a> - </h3> -</nav> - - - <nav id="search-section" class="section project-section" class="initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <h3 class="section-header"> - <input type="text" name="search" placeholder="Search" id="search-field" - title="Type to search, Up and Down to navigate, Enter to load"> - </h3> - </form> - - <ul id="search-results" class="initially-hidden"></ul> -</nav> - - - <div id="project-metadata"> - <nav id="fileindex-section" class="section project-section"> - <h3 class="section-header">Pages</h3> - - <ul> - - <li class="file"><a href="../Gemfile.html">Gemfile</a> - - <li class="file"><a href="../LICENSE_txt.html">LICENSE</a> - - <li class="file"><a href="../Rakefile.html">Rakefile</a> - - <li class="file"><a href="../public/robots_txt.html">robots</a> - - </ul> -</nav> - - <nav id="classindex-section" class="section project-section"> - <h3 class="section-header">Class and Module Index</h3> - - <ul class="link-list"> - - <li><a href="../ActiveSupport.html">ActiveSupport</a> - - <li><a href="../ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - - <li><a href="../ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - - <li><a href="../ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - - <li><a href="../Growstuff.html">Growstuff</a> - - <li><a href="../Growstuff/Application.html">Growstuff::Application</a> - - <li><a href="../AddUsernameToUsers.html">AddUsernameToUsers</a> - - <li><a href="../ApplicationController.html">ApplicationController</a> - - <li><a href="../ApplicationHelper.html">ApplicationHelper</a> - - <li><a href="../BrowsingTest.html">BrowsingTest</a> - - <li><a href="../DeviseCreateUsers.html">DeviseCreateUsers</a> - - <li><a href="../HomeController.html">HomeController</a> - - <li><a href="../HomeControllerTest.html">HomeControllerTest</a> - - <li><a href="../HomeHelper.html">HomeHelper</a> - - <li><a href="../HomeHelperTest.html">HomeHelperTest</a> - - <li><a href="../Object.html">Object</a> - - <li><a href="../User.html">User</a> - - <li><a href="../UserTest.html">UserTest</a> - - </ul> -</nav> - - </div> -</nav> - -<div id="documentation" class="description"> - -<p>See <a -href="http://www.robotstxt.org/wc/norobots.html">www.robotstxt.org/wc/norobots.html</a> -for documentation on how to use the robots.txt file</p> - -<p>To ban all spiders from the entire site uncomment the next two lines: -User-Agent: * Disallow: /</p> - -</div> - - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - diff --git a/doc/rdoc.css b/doc/rdoc.css deleted file mode 100644 index 755b5464d..000000000 --- a/doc/rdoc.css +++ /dev/null @@ -1,543 +0,0 @@ -/* - * "Darkfish" Rdoc CSS - * $Id: rdoc.css 54 2009-01-27 01:09:48Z deveiant $ - * - * Author: Michael Granger <ged@FaerieMUD.org> - * - */ - -/* Base Green is: #6C8C22 */ - -* { padding: 0; margin: 0; } - -body { - background: #efefef; - font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif; - margin-left: 40px; -} -body.file-popup { - font-size: 90%; - margin-left: 0; -} - -h1 { - font-size: 300%; - text-shadow: rgba(135,145,135,0.65) 2px 2px 3px; - color: #6C8C22; -} -h2,h3,h4 { margin-top: 1.5em; } - -:link, -:visited { - color: #6C8C22; - text-decoration: none; -} -:link:hover, -:visited:hover { - border-bottom: 1px dotted #6C8C22; -} - -pre { - background: #ddd; - padding: 0.5em 0; -} - -/* @group Generic Classes */ - -.initially-hidden { - display: none; -} - -#search-field { - width: 98%; - background: #eee; - border: none; - height: 1.5em; - -webkit-border-radius: 4px; -} -#search-field:focus { - background: #f1edba; -} -#search-field:-moz-placeholder, -#search-field::-webkit-input-placeholder { - font-weight: bold; - color: #666; -} - -.missing-docs { - font-size: 120%; - background: white url(images/wrench_orange.png) no-repeat 4px center; - color: #ccc; - line-height: 2em; - border: 1px solid #d00; - opacity: 1; - padding-left: 20px; - text-indent: 24px; - letter-spacing: 3px; - font-weight: bold; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; -} - -.target-section { - border: 2px solid #dcce90; - border-left-width: 8px; - padding: 0 1em; - background: #fff3c2; -} - -/* @end */ - -/* @group Index Page, Standalone file pages */ -.indexpage ul { - line-height: 160%; - list-style: none; -} -.indexpage ul :link, -.indexpage ul :visited { - font-size: 16px; -} - -.indexpage li { - padding-left: 20px; -} - -.indexpage ul > li { - background: url(images/bullet_black.png) no-repeat left 4px; -} -.indexpage li.method { - background: url(images/plugin.png) no-repeat left 4px; -} -.indexpage li.module { - background: url(images/package.png) no-repeat left 4px; -} -.indexpage li.class { - background: url(images/ruby.png) no-repeat left 4px; -} -.indexpage li.file { - background: url(images/page_white_text.png) no-repeat left 4px; -} -.indexpage li li { - background: url(images/tag_blue.png) no-repeat left 4px; -} -.indexpage li .toc-toggle { - width: 16px; - height: 16px; - background: url(images/add.png) no-repeat; -} - -.indexpage li .toc-toggle.open { - background: url(images/delete.png) no-repeat; -} - -/* @end */ - -/* @group Top-Level Structure */ - -#metadata { - float: left; - width: 260px; -} - -#documentation { - margin: 2em 1em 5em 300px; - min-width: 340px; -} - -#validator-badges { - clear: both; - margin: 1em 1em 2em; - font-size: smaller; -} - -/* @end */ - -/* @group Metadata Section */ -#metadata .section { - background-color: #dedede; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border: 1px solid #aaa; - margin: 0 8px 8px; - font-size: 90%; - overflow: hidden; -} -#metadata h3.section-header { - margin: 0; - padding: 2px 8px; - background: #ccc; - color: #666; - -moz-border-radius-topleft: 4px; - -moz-border-radius-topright: 4px; - -webkit-border-top-left-radius: 4px; - -webkit-border-top-right-radius: 4px; - border-bottom: 1px solid #aaa; -} -#metadata #home-section h3.section-header { - border-bottom: 0; -} - -#metadata ul, -#metadata dl, -#metadata p { - padding: 8px; - list-style: none; -} - -#file-metadata { - margin-top: 2em; -} - -#file-metadata ul { - padding-left: 28px; - list-style-image: url(images/page_green.png); -} - -dl.svninfo { - color: #666; - margin: 0; -} -dl.svninfo dt { - font-weight: bold; -} - -ul.link-list li { - white-space: nowrap; -} -ul.link-list .type { - font-size: 8px; - text-transform: uppercase; - color: white; - background: #969696; - padding: 2px 4px; - -webkit-border-radius: 5px; -} - -/* @end */ - -/* @group Class Metadata Section */ -#class-metadata { - margin-top: 2em; -} -/* @end */ - -/* @group Project Metadata Section */ -#project-metadata { - margin-top: 2em; -} - -#project-metadata .section { - border: 1px solid #aaa; -} -#project-metadata h3.section-header { - border-bottom: 1px solid #aaa; - position: relative; -} - -#project-metadata form { - color: #777; - background: #ccc; -} - -/* @end */ - -/* @group Documentation Section */ -.description { - font-size: 100%; - color: #333; -} - -.description p { - margin: 1em 0.4em; -} - -.description li p { - margin: 0; -} - -.description ol, -.description ul { - margin-left: 1.5em; -} -.description ol li, -.description ul li { - line-height: 1.4em; -} - -.note-list { - margin: 8px 0; -} - -.label-list { - margin: 8px 1.5em; - border: 1px solid #ccc; -} -.description .label-list { - font-size: 14px; -} - -.note-list dt { - font-weight: bold; -} -.note-list dd { - padding: 0 12px; -} - -.label-list dt { - padding: 2px 4px; - font-weight: bold; - background: #ddd; -} -.label-list dd { - padding: 2px 12px; -} -.label-list dd + dt, -.note-list dd + dt { - margin-top: 0.7em; -} - -#documentation .section { - font-size: 90%; -} - -#documentation h2.section-header { - margin-top: 1em; - padding: 0.25em 0.5em; - background: #ccc; - color: #333; - font-size: 175%; - border: 1px solid #bbb; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; -} - -.documentation-section-title { - position: relative; -} -.documentation-section-title .section-click-top { - position: absolute; - top: 6px; - right: 12px; - font-size: 10px; - color: #9b9877; - visibility: hidden; - padding-right: 0.5px; -} - -.documentation-section-title:hover .section-click-top { - visibility: visible; -} - -#documentation h3.section-header { - margin-top: 1em; - padding: 0.25em 0.5em; - background-color: #dedede; - color: #333; - font-size: 150%; - border: 1px solid #bbb; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; -} - -#constants-list > dl, -#attributes-list > dl { - margin: 1em 0 2em; - border: 0; -} -#constants-list > dl dt, -#attributes-list > dl dt { - padding-left: 0; - font-weight: bold; - font-family: Monaco, "Andale Mono"; - background: inherit; -} -#constants-list > dl dt a, -#attributes-list > dl dt a { - color: inherit; -} -#constants-list > dl dd, -#attributes-list > dl dd { - margin: 0 0 1em 0; - padding: 0; - color: #666; -} - -.documentation-section h2 { - position: relative; -} - -.documentation-section h2 a { - position: absolute; - top: 8px; - right: 10px; - font-size: 12px; - color: #9b9877; - visibility: hidden; -} - -.documentation-section h2:hover a { - visibility: visible; -} - -/* @group Method Details */ - -#documentation .method-source-code { - display: none; -} - -#documentation .method-detail { - margin: 0.5em 0; - padding: 0.5em 0; - cursor: pointer; -} -#documentation .method-detail:hover { - background-color: #f1edba; -} -#documentation .method-heading { - position: relative; - padding: 2px 4px 0 20px; - font-size: 125%; - font-weight: bold; - color: #333; - background: url(images/brick.png) no-repeat left bottom; -} -#documentation .method-heading :link, -#documentation .method-heading :visited { - color: inherit; -} -#documentation .method-click-advice { - position: absolute; - top: 2px; - right: 5px; - font-size: 10px; - color: #9b9877; - visibility: hidden; - padding-right: 20px; - line-height: 20px; - background: url(images/zoom.png) no-repeat right top; -} -#documentation .method-heading:hover .method-click-advice { - visibility: visible; -} - -#documentation .method-alias .method-heading { - color: #666; - background: url(images/brick_link.png) no-repeat left bottom; -} - -#documentation .method-description, -#documentation .aliases { - margin: 0 20px; - color: #666; -} - -#documentation .method-description p, -#documentation .aliases p { - line-height: 1.2em; -} - -#documentation .aliases { - padding-top: 4px; - font-style: italic; - cursor: default; -} -#documentation .method-description p { - margin-bottom: 0.5em; -} -#documentation .method-description ul { - margin-left: 1.5em; -} -pre { - margin: 0.5em 0; -} - -#documentation .attribute-method-heading { - background: url(images/tag_green.png) no-repeat left bottom; -} -#documentation #attribute-method-details .method-detail:hover { - background-color: transparent; - cursor: default; -} -#documentation .attribute-access-type { - font-size: 60%; - text-transform: uppercase; - vertical-align: super; - padding: 0 2px; -} -/* @end */ - -/* @end */ - -/* @group Source Code */ - -pre { - overflow: auto; - background: #262626; - color: white; - border: 1px dashed #999; - padding: 0.5em; -} - -.description pre { - margin: 0 0.4em; -} - -.ruby-constant { color: #7fffd4; background: transparent; } -.ruby-keyword { color: #00ffff; background: transparent; } -.ruby-ivar { color: #eedd82; background: transparent; } -.ruby-operator { color: #00ffee; background: transparent; } -.ruby-identifier { color: #ffdead; background: transparent; } -.ruby-node { color: #ffa07a; background: transparent; } -.ruby-comment { color: #dc0000; font-weight: bold; background: transparent; } -.ruby-regexp { color: #ffa07a; background: transparent; } -.ruby-value { color: #7fffd4; background: transparent; } - -/* @end */ - - -/* @group search results */ -#search-results h1 { - font-size: 1em; - font-weight: normal; - text-shadow: none; -} - -#search-results .current { - background: #ccc; - border-bottom: 1px solid transparent; -} - -#search-results li { - list-style: none; - border-bottom: 1px solid #aaa; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - margin-bottom: 0.5em; -} - -#search-results li:last-child { - border-bottom: none; - margin-bottom: 0; -} - -#search-results li p { - padding: 0; - margin: 0.5em; -} - -#search-results .search-namespace { - font-weight: bold; -} - -#search-results li em { - background: yellow; - font-style: normal; -} - -#search-results pre { - margin: 0.5em; -} - -/* @end */ - diff --git a/doc/table_of_contents.html b/doc/table_of_contents.html deleted file mode 100644 index 047788584..000000000 --- a/doc/table_of_contents.html +++ /dev/null @@ -1,123 +0,0 @@ -<!DOCTYPE html> - -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>Table of Contents - RDoc Documentation</title> - -<link type="text/css" media="screen" href="./rdoc.css" rel="stylesheet"> - -<script type="text/javascript"> - var rdoc_rel_prefix = "./"; -</script> - -<script type="text/javascript" charset="utf-8" src="./js/jquery.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/navigation.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search_index.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/search.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/searcher.js"></script> -<script type="text/javascript" charset="utf-8" src="./js/darkfish.js"></script> - - -<body class="indexpage"> -<h1>Table of Contents - RDoc Documentation</h1> - -<h2>Pages</h2> -<ul> - <li class="file"> - <a href="Gemfile.html">Gemfile</a> - </li> - <li class="file"> - <a href="LICENSE_txt.html">LICENSE</a> - </li> - <li class="file"> - <a href="Rakefile.html">Rakefile</a> - </li> - <li class="file"> - <a href="public/robots_txt.html">robots</a> - </li> - -</ul> - -<h2 id="classes">Classes/Modules</h2> -<ul> - <li class="module"> - <a href="ActiveSupport.html">ActiveSupport</a> - </li> - <li class="class"> - <a href="ActiveSupport/TestCase.html">ActiveSupport::TestCase</a> - </li> - <li class="module"> - <a href="ActiveSupport/TestCase/ActionController.html">ActiveSupport::TestCase::ActionController</a> - </li> - <li class="class"> - <a href="ActiveSupport/TestCase/ActionController/TestCase.html">ActiveSupport::TestCase::ActionController::TestCase</a> - </li> - <li class="module"> - <a href="Growstuff.html">Growstuff</a> - </li> - <li class="class"> - <a href="Growstuff/Application.html">Growstuff::Application</a> - </li> - <li class="class"> - <a href="AddUsernameToUsers.html">AddUsernameToUsers</a> - </li> - <li class="class"> - <a href="ApplicationController.html">ApplicationController</a> - </li> - <li class="module"> - <a href="ApplicationHelper.html">ApplicationHelper</a> - </li> - <li class="class"> - <a href="BrowsingTest.html">BrowsingTest</a> - </li> - <li class="class"> - <a href="DeviseCreateUsers.html">DeviseCreateUsers</a> - </li> - <li class="class"> - <a href="HomeController.html">HomeController</a> - </li> - <li class="class"> - <a href="HomeControllerTest.html">HomeControllerTest</a> - </li> - <li class="module"> - <a href="HomeHelper.html">HomeHelper</a> - </li> - <li class="class"> - <a href="HomeHelperTest.html">HomeHelperTest</a> - </li> - <li class="class"> - <a href="Object.html">Object</a> - </li> - <li class="class"> - <a href="User.html">User</a> - </li> - <li class="class"> - <a href="UserTest.html">UserTest</a> - </li> - -</ul> - -<h2 id="methods">Methods</h2> -<ul> - - <li class="method"><a href="User.html#method-c-find_first_by_auth_conditions">::find_first_by_auth_conditions &mdash; User</a> - - <li class="method"><a href="DeviseCreateUsers.html#method-i-change">#change &mdash; DeviseCreateUsers</a> - - <li class="method"><a href="AddUsernameToUsers.html#method-i-change">#change &mdash; AddUsernameToUsers</a> - - <li class="method"><a href="HomeController.html#method-i-index">#index &mdash; HomeController</a> - - <li class="method"><a href="BrowsingTest.html#method-i-test_homepage">#test_homepage &mdash; BrowsingTest</a> - -</ul> - - -<footer id="validator-badges"> - <p><a href="http://validator.w3.org/check/referer">[Validate]</a> - <p>Generated by <a href="https://github.com/rdoc/rdoc">RDoc</a> 3.12. - <p>Generated with the <a href="http://deveiate.org/projects/Darkfish-Rdoc/">Darkfish Rdoc Generator</a> 3. -</footer> - From f1b57410234d150ab37b1d3601b5f05ba833f04c Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 31 May 2013 12:55:39 +1000 Subject: [PATCH 086/184] added doc/ to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9b5a1cf4b..c6a731582 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /log/* +/doc/* /db/*.sqlite3 /tmp/* .pt From b1b24c432340bd1f70f0b7e4a1f1fc486cdffa5d Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Fri, 31 May 2013 11:28:59 +0100 Subject: [PATCH 087/184] Delete unused photos_helper. --- app/helpers/photos_helper.rb | 2 -- spec/helpers/photos_helper_spec.rb | 15 --------------- 2 files changed, 17 deletions(-) delete mode 100644 app/helpers/photos_helper.rb delete mode 100644 spec/helpers/photos_helper_spec.rb diff --git a/app/helpers/photos_helper.rb b/app/helpers/photos_helper.rb deleted file mode 100644 index 0a10d472b..000000000 --- a/app/helpers/photos_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module PhotosHelper -end diff --git a/spec/helpers/photos_helper_spec.rb b/spec/helpers/photos_helper_spec.rb deleted file mode 100644 index c4b452740..000000000 --- a/spec/helpers/photos_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the PhotosHelper. For example: -# -# describe PhotosHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end -describe PhotosHelper do - pending "add some examples to (or delete) #{__FILE__}" -end From b2916e28f3390ac3dff804bb0f8a890d30223d84 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Fri, 31 May 2013 11:48:24 +0100 Subject: [PATCH 088/184] Don't recreate photos, but update metadata. --- app/controllers/photos_controller.rb | 5 +++-- app/views/photos/show.html.haml | 2 -- spec/controllers/photos_controller_spec.rb | 11 +++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 960fd36bc..f20d8ed77 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -46,13 +46,14 @@ class PhotosController < ApplicationController # POST /photos # POST /photos.json def create - @photo = Photo.new(params[:photo]) + @photo = Photo.find_by_flickr_photo_id(params[:photo][:flickr_photo_id]) || + Photo.new(params[:photo]) @photo.owner_id = current_member.id @photo.set_flickr_metadata respond_to do |format| if @photo.save - format.html { redirect_to @photo, notice: 'Photo was successfully created.' } + format.html { redirect_to @photo, notice: 'Photo was successfully added.' } format.json { render json: @photo, status: :created, location: @photo } else format.html { render action: "new" } diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 912ca5113..6306f9994 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -1,5 +1,3 @@ -%p#notice= notice - = image_tag(@photo.fullsize_url, :alt => 'A plant', :class => 'img-rounded') %p diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index c61c7e244..4355c5bd9 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -91,6 +91,17 @@ describe PhotosController do end end + describe "for the second time" do + it "does not add a photo twice" do + expect { + post :create, {:photo => { :flickr_photo_id => 1 } } + }.to change(Photo, :count).by(1) + expect { + post :create, {:photo => { :flickr_photo_id => 1 } } + }.to change(Photo, :count).by(0) + end + end + describe "with invalid params" do it "assigns a newly created but unsaved photo as @photo" do # Trigger the behavior that occurs when invalid params are submitted From 44a236d90d066b92ceed2639a15d58f8f0333858 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Fri, 31 May 2013 12:05:09 +0100 Subject: [PATCH 089/184] Display all photos on index page. --- app/controllers/photos_controller.rb | 2 +- app/models/photo.rb | 2 ++ app/views/photos/index.html.haml | 43 ++++++++++++----------- spec/views/photos/index.html.haml_spec.rb | 33 +++++++---------- 4 files changed, 38 insertions(+), 42 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index f20d8ed77..91680624d 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -3,7 +3,7 @@ class PhotosController < ApplicationController # GET /photos # GET /photos.json def index - @photos = Photo.all + @photos = Photo.paginate(:page => params[:page]) respond_to do |format| format.html # index.html.erb diff --git a/app/models/photo.rb b/app/models/photo.rb index 84b22bcdd..a38d509a0 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -3,6 +3,8 @@ class Photo < ActiveRecord::Base :license_url, :thumbnail_url, :fullsize_url, :link_url belongs_to :owner, :class_name => 'Member' + default_scope order("created_at desc") + # This is split into a side-effect free method and a side-effecting method # for easier stubbing and testing. def flickr_metadata diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml index 3f16c5677..09a8e1bd7 100644 --- a/app/views/photos/index.html.haml +++ b/app/views/photos/index.html.haml @@ -1,25 +1,26 @@ -%h1 Listing photos +- content_for :title, "Photos" -%table - %tr - %th Owner - %th Flickr photo - %th Thumbnail url - %th Fullsize url - %th - %th - %th +%div.pagination + = page_entries_info @photos, :model => "photos" + = will_paginate @photos - - @photos.each do |photo| - %tr - %td= photo.owner_id - %td= photo.flickr_photo_id - %td= photo.thumbnail_url - %td= photo.fullsize_url - %td= link_to 'Show', photo - %td= link_to 'Edit', edit_photo_path(photo) - %td= link_to 'Destroy', photo, :method => :delete, :data => { :confirm => 'Are you sure?' } +- c = 0 +%ul.thumbnails + .row + - @photos.each do |p| + - c += 1 + %li.span2 + .thumbnail(style='height: 220px') + = link_to image_tag(p.thumbnail_url, :alt => p.title, :class => 'img-rounded'), p + %p + = link_to p.title, p + by + = link_to p.owner, p.owner + - if (c % 6) == 0 + .row + +%div.pagination + = page_entries_info @photos, :model => "photos" + = will_paginate @photos -%br -= link_to 'New Photo', new_photo_path diff --git a/spec/views/photos/index.html.haml_spec.rb b/spec/views/photos/index.html.haml_spec.rb index 8dc87e540..bf4699c48 100644 --- a/spec/views/photos/index.html.haml_spec.rb +++ b/spec/views/photos/index.html.haml_spec.rb @@ -2,28 +2,21 @@ require 'spec_helper' describe "photos/index" do before(:each) do - assign(:photos, [ - stub_model(Photo, - :owner_id => 1, - :flickr_photo_id => 2, - :thumbnail_url => "Thumbnail Url", - :fullsize_url => "Fullsize Url" - ), - stub_model(Photo, - :owner_id => 1, - :flickr_photo_id => 2, - :thumbnail_url => "Thumbnail Url", - :fullsize_url => "Fullsize Url" - ) - ]) + page = 1 + per_page = 2 + total_entries = 2 + photos = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + pager.replace([ + FactoryGirl.create(:photo), + FactoryGirl.create(:photo) + ]) + end + assign(:photos, photos) end - it "renders a list of photos" do + it "renders a gallery of photos" do render - # Run the generator again with the --webrat flag if you want to use webrat matchers - assert_select "tr>td", :text => 1.to_s, :count => 2 - assert_select "tr>td", :text => 2.to_s, :count => 2 - assert_select "tr>td", :text => "Thumbnail Url".to_s, :count => 2 - assert_select "tr>td", :text => "Fullsize Url".to_s, :count => 2 + assert_select ".thumbnail", :count => 2 + assert_select "img", :count => 2 end end From c734f621895d6db7b5a888983b4040a39d18016f Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Fri, 31 May 2013 12:47:21 +0100 Subject: [PATCH 090/184] Plantings HABTM photos. --- app/models/photo.rb | 1 + app/models/planting.rb | 6 ++---- db/migrate/20130531110729_add_photos_plantings_table.rb | 8 ++++++++ db/schema.rb | 7 ++++++- spec/models/planting_spec.rb | 9 +++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20130531110729_add_photos_plantings_table.rb diff --git a/app/models/photo.rb b/app/models/photo.rb index a38d509a0..ac67273ab 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -2,6 +2,7 @@ class Photo < ActiveRecord::Base attr_accessible :flickr_photo_id, :owner_id, :title, :license_name, :license_url, :thumbnail_url, :fullsize_url, :link_url belongs_to :owner, :class_name => 'Member' + has_and_belongs_to_many :plantings default_scope order("created_at desc") diff --git a/app/models/planting.rb b/app/models/planting.rb index d5c477957..d88668acb 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -7,6 +7,7 @@ class Planting < ActiveRecord::Base belongs_to :garden belongs_to :crop + has_and_belongs_to_many :photos default_scope order("created_at desc") @@ -16,6 +17,7 @@ class Planting < ActiveRecord::Base :plantings_count, :to => :crop, :prefix => true + delegate :owner, :to => :garden default_scope order("created_at desc") @@ -33,10 +35,6 @@ class Planting < ActiveRecord::Base return "#{garden.owner.login_name}'s #{garden}" end - def owner - return garden.owner - end - def planted_at_string if planted_at planted_at.strftime("%F") diff --git a/db/migrate/20130531110729_add_photos_plantings_table.rb b/db/migrate/20130531110729_add_photos_plantings_table.rb new file mode 100644 index 000000000..07040ed93 --- /dev/null +++ b/db/migrate/20130531110729_add_photos_plantings_table.rb @@ -0,0 +1,8 @@ +class AddPhotosPlantingsTable < ActiveRecord::Migration + def change + create_table :photos_plantings, :id => false do |t| + t.integer :photo_id + t.integer :planting_id + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 426d275d0..6f99218f9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130518002942) do +ActiveRecord::Schema.define(:version => 20130531110729) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -171,6 +171,11 @@ ActiveRecord::Schema.define(:version => 20130518002942) do t.string "link_url", :null => false end + create_table "photos_plantings", :id => false, :force => true do |t| + t.integer "photo_id" + t.integer "planting_id" + end + create_table "plantings", :force => true do |t| t.integer "garden_id", :null => false t.integer "crop_id", :null => false diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index ca3c67c0c..8a9a75e93 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -86,4 +86,13 @@ describe Planting do @planting.errors[:sunniness].should include("not valid is not a valid sunniness value") end + context 'photos' do + it 'has a photo' do + @planting = FactoryGirl.create(:planting) + @photo = FactoryGirl.create(:photo) + @planting.photos << @photo + @planting.photos.first.should eq @photo + end + end + end From e072883bb9a4d3a45982fa2cda87785e6c12cbb3 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Fri, 31 May 2013 13:09:21 +0100 Subject: [PATCH 091/184] Connect plantings to photos. --- app/controllers/photos_controller.rb | 7 +++++++ app/views/photos/new.html.haml | 2 +- app/views/plantings/show.html.haml | 3 +++ spec/controllers/photos_controller_spec.rb | 12 ++++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 91680624d..0280734a1 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -26,6 +26,7 @@ class PhotosController < ApplicationController # GET /photos/new.json def new @photo = Photo.new + @planting_id = params[:planting_id] @flickr_auth = current_member.auth('flickr') if @flickr_auth @@ -50,6 +51,12 @@ class PhotosController < ApplicationController Photo.new(params[:photo]) @photo.owner_id = current_member.id @photo.set_flickr_metadata + if params[:planting_id] + planting = Planting.find_by_id(params[:planting_id]) + if planting + @photo.plantings << planting + end + end respond_to do |format| if @photo.save diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index 8bfdc7ab9..ca363dc26 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -15,7 +15,7 @@ - c += 1 %li.span2 .thumbnail(style='height: 220px') - = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }), :method => :post + = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :planting_id => @planting_id), :method => :post %p =p.title - if (c % 6) == 0 diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index f8e375166..f18585951 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -51,6 +51,9 @@ - (1..6).each do .span2 = image_tag('http://placehold.it/150x150', :alt => '', :class => 'img-rounded') +- if can? :create, Photo and can? :edit, @planting + %p + = link_to "Add photo", new_photo_path(:planting_id => @planting.id) %h2 Notes diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 4355c5bd9..569463c02 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -51,6 +51,11 @@ describe PhotosController do get :new, {} assigns(:photo).should be_a_new(Photo) end + + it "assigns a planting id" do + get :new, { :planting_id => 5 } + assigns(:planting_id).should eq "5" + end end describe "GET edit" do @@ -89,6 +94,13 @@ describe PhotosController do post :create, {:photo => { :flickr_photo_id => 1 } } response.should redirect_to(Photo.last) end + + it "attaches the photo to a planting" do + planting = FactoryGirl.create(:planting) + post :create, {:photo => { :flickr_photo_id => 1 }, + :planting_id => planting.id } + Photo.last.plantings.first.should eq planting + end end describe "for the second time" do From d635d58bf1e4cfbc91b07398d05b65b129021afd Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 31 May 2013 22:56:32 +1000 Subject: [PATCH 092/184] show photos on planting page --- .gitignore | 1 + app/views/plantings/show.html.haml | 30 +++++++++---- db/schema.rb | 47 --------------------- spec/views/plantings/show.html.haml_spec.rb | 18 ++++++++ 4 files changed, 41 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 9b5a1cf4b..7801d8f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /log/* +/doc /db/*.sqlite3 /tmp/* .pt diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index f18585951..a4a3b3a36 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -46,14 +46,28 @@ = pluralize(@planting.crop_plantings_count, "time") by #{Growstuff::Application.config.site_name} members -%h2 Pictures -.row - - (1..6).each do - .span2 - = image_tag('http://placehold.it/150x150', :alt => '', :class => 'img-rounded') -- if can? :create, Photo and can? :edit, @planting - %p - = link_to "Add photo", new_photo_path(:planting_id => @planting.id) +- if @planting.photos.count > 0 or (can? :edit, @planting and can? :create, Photo) + %h2 Pictures + +- c = 0 +%ul.thumbnails + .row + - @planting.photos.each do |p| + - c += 1 + %li.span2 + .thumbnail(style='height: 220px') + = link_to image_tag(p.thumbnail_url, :alt => p.title, :class => 'img-rounded'), p + %p + = link_to p.title, p + by + = link_to p.owner, p.owner + - if (c % 6) == 0 + .row + - if can? :create, Photo and can? :edit, @planting + %li.span2 + .thumbnail(style='height: 220px') + %p{:style => 'text-align: center; padding-top: 50px'} + = link_to "Add photo", new_photo_path(:planting_id => @planting.id), :class => 'btn btn-primary' %h2 Notes diff --git a/db/schema.rb b/db/schema.rb index 6f99218f9..8831606cf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -13,22 +13,6 @@ ActiveRecord::Schema.define(:version => 20130531110729) do - create_table "account_types", :force => true do |t| - t.string "name", :null => false - t.boolean "is_paid" - t.boolean "is_permanent_paid" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "accounts", :force => true do |t| - t.integer "member_id", :null => false - t.integer "account_type_id" - t.datetime "paid_until" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - create_table "authentications", :force => true do |t| t.integer "member_id", :null => false t.string "provider", :null => false @@ -137,27 +121,6 @@ ActiveRecord::Schema.define(:version => 20130531110729) do t.datetime "updated_at", :null => false end - create_table "order_items", :force => true do |t| - t.integer "order_id" - t.integer "product_id" - t.integer "price" - t.integer "quantity" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - - create_table "orders", :force => true do |t| - t.integer "member_id", :limit => 255, :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.datetime "completed_at" - end - - create_table "orders_products", :id => false, :force => true do |t| - t.integer "order_id" - t.integer "product_id" - end - create_table "photos", :force => true do |t| t.integer "owner_id", :null => false t.integer "flickr_photo_id", :null => false @@ -203,16 +166,6 @@ ActiveRecord::Schema.define(:version => 20130531110729) do add_index "posts", ["created_at", "author_id"], :name => "index_updates_on_created_at_and_user_id" add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true - create_table "products", :force => true do |t| - t.string "name", :null => false - t.string "description", :null => false - t.integer "min_price", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "account_type_id" - t.integer "paid_months" - end - create_table "roles", :force => true do |t| t.string "name", :null => false t.text "description" diff --git a/spec/views/plantings/show.html.haml_spec.rb b/spec/views/plantings/show.html.haml_spec.rb index 414209f1d..71c6c94d1 100644 --- a/spec/views/plantings/show.html.haml_spec.rb +++ b/spec/views/plantings/show.html.haml_spec.rb @@ -29,6 +29,24 @@ describe "plantings/show" do rendered.should_not contain 'sun' end + it "shows photos" do + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + @p = create_planting_for(@member) + @photo = FactoryGirl.create(:photo, :owner => @member) + @p.photos << @photo + render + assert_select "img[src=#{@photo.thumbnail_url}]" + end + + it "shows a link to add photos" do + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + @p = create_planting_for(@member) + render + rendered.should contain "Add photo" + end + context "no location set" do before(:each) do controller.stub(:current_user) { nil } From cab7348a3522ebce9cc295fc97050d697ec71540 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 31 May 2013 23:13:47 +1000 Subject: [PATCH 093/184] show plantings on photo page --- app/models/planting.rb | 4 +++ app/views/photos/show.html.haml | 37 +++++++++++++++--------- spec/views/photos/show.html.haml_spec.rb | 21 ++++++++++++-- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/app/models/planting.rb b/app/models/planting.rb index d88668acb..1535c798b 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -46,4 +46,8 @@ class Planting < ActiveRecord::Base def planted_at_string=(str) self.planted_at = str == '' ? nil : Time.parse(str) end + + def to_s + self.crop_system_name + " in " + self.location + end end diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 6306f9994..cde879ecc 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -1,15 +1,26 @@ -= image_tag(@photo.fullsize_url, :alt => 'A plant', :class => 'img-rounded') +-content_for :title, @photo.title -%p - = link_to @photo.title, @photo.link_url - by - = succeed "." do - = link_to @photo.owner, @photo.owner - License: - - if @photo.license_url - = succeed "." do - = link_to @photo.license_name, @photo.license_url - - else - = succeed "." do - = @photo.license_name +.row + .span6 + %p + %strong Posted by: + = link_to @photo.owner, @photo.owner + %p + %strong License: + - if @photo.license_url + = link_to @photo.license_name, @photo.license_url + - else + = succeed "." do + = @photo.license_name + %p + = link_to "View on Flickr", @photo.link_url + + .span6 + - if @photo.plantings.count > 0 + %p This photo depicts: + %ul + - @photo.plantings.each do |p| + %li= link_to p, p + +%p= image_tag(@photo.fullsize_url, :alt => 'A plant', :class => 'img-rounded') diff --git a/spec/views/photos/show.html.haml_spec.rb b/spec/views/photos/show.html.haml_spec.rb index 5b8cebfbf..fa03e08d6 100644 --- a/spec/views/photos/show.html.haml_spec.rb +++ b/spec/views/photos/show.html.haml_spec.rb @@ -20,8 +20,8 @@ describe "photos/show" do :text => @photo.license_name end - it "shows the title as a link to the original image" do - assert_select "a", :href => @photo.link_url, :text => @photo.title + it "shows a link to the original image" do + assert_select "a", :href => @photo.link_url, :text => "View on Flickr" end end @@ -34,6 +34,21 @@ describe "photos/show" do it "contains the phrase 'All rights reserved'" do rendered.should contain "All rights reserved" end -end + end + + context "linked to a planting" do + before(:each) do + @photo = FactoryGirl.create(:photo) + @planting = FactoryGirl.create(:planting) + @planting.photos << @photo + @photo = assign(:photo, @photo) + render + end + + it "shows link to planting" do + assert_select "a[href=#{planting_path(@planting)}]" + end + + end end From c63eb64565064ddbefbe2ec48166e32e518797dc Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 31 May 2013 23:35:07 +1000 Subject: [PATCH 094/184] photo and planting owners must match --- app/controllers/photos_controller.rb | 9 +++++- spec/controllers/photos_controller_spec.rb | 32 ++++++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 0280734a1..41348cb1e 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -51,10 +51,17 @@ class PhotosController < ApplicationController Photo.new(params[:photo]) @photo.owner_id = current_member.id @photo.set_flickr_metadata + if params[:planting_id] planting = Planting.find_by_id(params[:planting_id]) if planting - @photo.plantings << planting + if planting.owner.id == current_member.id + @photo.plantings << planting + else + flash[:alert] = "You must own both the planting and the photo." + end + else + flash[:alert] = "Couldn't find planting to connect to photo." end end diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 569463c02..efb7a651d 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -96,8 +96,12 @@ describe PhotosController do end it "attaches the photo to a planting" do - planting = FactoryGirl.create(:planting) - post :create, {:photo => { :flickr_photo_id => 1 }, + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + garden = FactoryGirl.create(:garden, :owner => member) + planting = FactoryGirl.create(:planting, :garden => garden) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, :planting_id => planting.id } Photo.last.plantings.first.should eq planting end @@ -114,6 +118,30 @@ describe PhotosController do end end + describe "with matching owners" do + it "creates the planting/photo link" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + garden = FactoryGirl.create(:garden, :owner => member) + planting = FactoryGirl.create(:planting, :garden => garden) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + Photo.last.plantings.first.should eq planting + end + end + + describe "with mismatched owners" do + it "creates the planting/photo link" do + # members will be auto-created, and different + planting = FactoryGirl.create(:planting) + photo = FactoryGirl.create(:photo) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + Photo.last.plantings.first.should_not eq planting + end + end + describe "with invalid params" do it "assigns a newly created but unsaved photo as @photo" do # Trigger the behavior that occurs when invalid params are submitted From cd111f4cc24886b2bf5752d7c9393b3c67a84b0c Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 31 May 2013 23:37:48 +1000 Subject: [PATCH 095/184] added alt tag --- app/views/photos/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index cde879ecc..47a2b7ecc 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -22,5 +22,5 @@ - @photo.plantings.each do |p| %li= link_to p, p -%p= image_tag(@photo.fullsize_url, :alt => 'A plant', :class => 'img-rounded') +%p= image_tag(@photo.fullsize_url, :alt => @photo.title, :class => 'img-rounded') From 77bb0080836e460676ec0088d9351c133299fa0d Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Sat, 1 Jun 2013 11:21:17 +1000 Subject: [PATCH 096/184] changed flickr_photo_id to string ... because integer worked fine in dev (on sqlite3) but broke on staging (with postgres). Turns out flickr photo IDs are quite LARGE integers. But really they could be anything, so string seems safest. --- .../20130601011725_change_flickr_photo_id_to_string.rb | 8 ++++++++ db/schema.rb | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20130601011725_change_flickr_photo_id_to_string.rb diff --git a/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb b/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb new file mode 100644 index 000000000..2dd847ac4 --- /dev/null +++ b/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb @@ -0,0 +1,8 @@ +class ChangeFlickrPhotoIdToString < ActiveRecord::Migration + def up + change_column :photos, :flickr_photo_id, :string + end + def down + change_column :photos, :flickr_photo_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 8831606cf..6e91ac8cb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130531110729) do +ActiveRecord::Schema.define(:version => 20130601011725) do create_table "authentications", :force => true do |t| t.integer "member_id", :null => false @@ -123,7 +123,7 @@ ActiveRecord::Schema.define(:version => 20130531110729) do create_table "photos", :force => true do |t| t.integer "owner_id", :null => false - t.integer "flickr_photo_id", :null => false + t.string "flickr_photo_id", :null => false t.string "thumbnail_url", :null => false t.string "fullsize_url", :null => false t.datetime "created_at", :null => false From 10fdabfe3715d40fc4888612fcf08975493ef550 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Sat, 1 Jun 2013 11:32:49 +1000 Subject: [PATCH 097/184] rearranged notes/pics on plantings page --- app/views/plantings/show.html.haml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index a4a3b3a36..137ffaaa4 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -46,6 +46,16 @@ = pluralize(@planting.crop_plantings_count, "time") by #{Growstuff::Application.config.site_name} members +%h2 + Notes + +:markdown + #{ @planting.description != "" ? @planting.description : "No description given." } + +- if can? :edit, @planting + = link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-mini' + + - if @planting.photos.count > 0 or (can? :edit, @planting and can? :create, Photo) %h2 Pictures @@ -68,12 +78,3 @@ .thumbnail(style='height: 220px') %p{:style => 'text-align: center; padding-top: 50px'} = link_to "Add photo", new_photo_path(:planting_id => @planting.id), :class => 'btn btn-primary' - -%h2 - Notes - -:markdown - #{ @planting.description != "" ? @planting.description : "No description given." } - -- if can? :edit, @planting - = link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-mini' From 6ecbf749eb2845a27e5a3a948bc14b15a2358c51 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Sat, 1 Jun 2013 12:07:17 +1000 Subject: [PATCH 098/184] Avoid duplicate photos on plantings (or vice versa) Strictly speaking this doesn't prevent you adding them if you really try, but the validation for that was beyond what I could figure out (the docs don't help, and all SO/blog posts are outdated and didn't work). However, if you do somehow manage to add dups, you will never see them again thanks to the :uniq => true in the model. That's good enough for me. --- app/controllers/photos_controller.rb | 2 +- app/models/photo.rb | 2 +- app/models/planting.rb | 2 +- spec/controllers/photos_controller_spec.rb | 14 ++++++++++++++ spec/models/planting_spec.rb | 8 ++++++++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index 41348cb1e..f9d6bf4c5 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -56,7 +56,7 @@ class PhotosController < ApplicationController planting = Planting.find_by_id(params[:planting_id]) if planting if planting.owner.id == current_member.id - @photo.plantings << planting + @photo.plantings << planting unless @photo.plantings.include?(planting) else flash[:alert] = "You must own both the planting and the photo." end diff --git a/app/models/photo.rb b/app/models/photo.rb index ac67273ab..81e0029c8 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -2,7 +2,7 @@ class Photo < ActiveRecord::Base attr_accessible :flickr_photo_id, :owner_id, :title, :license_name, :license_url, :thumbnail_url, :fullsize_url, :link_url belongs_to :owner, :class_name => 'Member' - has_and_belongs_to_many :plantings + has_and_belongs_to_many :plantings, :uniq => true default_scope order("created_at desc") diff --git a/app/models/planting.rb b/app/models/planting.rb index 1535c798b..cf31a82b3 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -7,7 +7,7 @@ class Planting < ActiveRecord::Base belongs_to :garden belongs_to :crop - has_and_belongs_to_many :photos + has_and_belongs_to_many :photos, :uniq => true default_scope order("created_at desc") diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index efb7a651d..d31edabc4 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -56,6 +56,7 @@ describe PhotosController do get :new, { :planting_id => 5 } assigns(:planting_id).should eq "5" end + end describe "GET edit" do @@ -105,6 +106,19 @@ describe PhotosController do :planting_id => planting.id } Photo.last.plantings.first.should eq planting end + + it "doesn't attach a photo to a planting twice" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + garden = FactoryGirl.create(:garden, :owner => member) + planting = FactoryGirl.create(:planting, :garden => garden) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + Photo.last.plantings.count.should eq 1 + end end describe "for the second time" do diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index 8a9a75e93..bb0838c9d 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -93,6 +93,14 @@ describe Planting do @planting.photos << @photo @planting.photos.first.should eq @photo end + + it "doesn't return duplicate photos" do + @planting = FactoryGirl.create(:planting) + @photo = FactoryGirl.create(:photo) + @planting.photos << @photo + @planting.photos << @photo + @planting.photos.count.should eq 1 + end end end From 2a301d352bd0f7439782a5f3cb9c026a989101b9 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Sat, 1 Jun 2013 12:25:43 +1000 Subject: [PATCH 099/184] Reverted uniqueness limits for photo/planting association It seems this interacts badly with the default_scope on postgres (but not on sqlite3). Error message from the logs: 2013-06-01T02:21:05.312099+00:00 app[web.1]: ActiveRecord::StatementInvalid (PG::Error: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list 2013-06-01T02:21:05.312099+00:00 app[web.1]: LINE 1: ...photo_id" = 2 AND "plantings"."id" = 181 ORDER BY created_at... 2013-06-01T02:21:05.312099+00:00 app[web.1]: app/controllers/photos_controller.rb:59:in `create' 2013-06-01T02:21:05.312099+00:00 app[web.1]: ^ 2013-06-01T02:21:05.312099+00:00 app[web.1]: : SELECT DISTINCT 1 AS one FROM "plantings" INNER JOIN "photos_plantings" ON "plantings"."id" = "photos_plantings"."planting_id" WHERE "photos_plantings"."photo_id" = 2 AND "plantings"."id" = 181 ORDER BY created_at desc LIMIT 1): For now, we'll just have to rely on the controller (which adds the association) to keep things unique. --- app/models/photo.rb | 2 +- app/models/planting.rb | 2 +- spec/models/planting_spec.rb | 8 -------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/app/models/photo.rb b/app/models/photo.rb index 81e0029c8..ac67273ab 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -2,7 +2,7 @@ class Photo < ActiveRecord::Base attr_accessible :flickr_photo_id, :owner_id, :title, :license_name, :license_url, :thumbnail_url, :fullsize_url, :link_url belongs_to :owner, :class_name => 'Member' - has_and_belongs_to_many :plantings, :uniq => true + has_and_belongs_to_many :plantings default_scope order("created_at desc") diff --git a/app/models/planting.rb b/app/models/planting.rb index cf31a82b3..1535c798b 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -7,7 +7,7 @@ class Planting < ActiveRecord::Base belongs_to :garden belongs_to :crop - has_and_belongs_to_many :photos, :uniq => true + has_and_belongs_to_many :photos default_scope order("created_at desc") diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index bb0838c9d..8a9a75e93 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -93,14 +93,6 @@ describe Planting do @planting.photos << @photo @planting.photos.first.should eq @photo end - - it "doesn't return duplicate photos" do - @planting = FactoryGirl.create(:planting) - @photo = FactoryGirl.create(:photo) - @planting.photos << @photo - @planting.photos << @photo - @planting.photos.count.should eq 1 - end end end From 5ba023ad011688563892f1f87d64ad9a4e906e8f Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 3 Jun 2013 14:34:29 +1000 Subject: [PATCH 100/184] added deploy tasks script --- script/deploy-tasks.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 script/deploy-tasks.sh diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh new file mode 100755 index 000000000..dc18a2390 --- /dev/null +++ b/script/deploy-tasks.sh @@ -0,0 +1,14 @@ +# tasks to run at deploy time, usually after 'rake db:migrate' + +# When adding tasks, do so in chronological order, and note the date +# when it was added. This will help us know which ones have been run +# and can safely be commented out or removed. + +echo "2013-05-?? - Replacing empty post/notification subjects with '(no subject)'" +rake growstuff:oneoff:empty_subjects + +echo "2013-06-03 - replace empty garden names with 'Garden'" +rake growstuff:oneoff:empty_garden_names + + + From 43798231dd103a18863cb5c5aa4050244e4c7f37 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 12:22:31 +1000 Subject: [PATCH 101/184] added crop photos and a new placeholder image --- app/assets/images/placeholder_150.png | Bin 0 -> 2501 bytes app/models/crop.rb | 5 ++ app/models/planting.rb | 4 ++ app/views/crops/_thumbnail.html.haml | 5 +- app/views/plantings/_thumbnail.html.haml | 59 ++++++++++++----------- app/views/plantings/show.html.haml | 35 ++++++++------ spec/models/crop_spec.rb | 10 ++++ spec/models/planting_spec.rb | 9 +++- spec/views/crops/index.html.haml_spec.rb | 15 ++++-- spec/views/crops/show.html.haml_spec.rb | 8 +++ 10 files changed, 98 insertions(+), 52 deletions(-) create mode 100644 app/assets/images/placeholder_150.png diff --git a/app/assets/images/placeholder_150.png b/app/assets/images/placeholder_150.png new file mode 100644 index 0000000000000000000000000000000000000000..c3d7f500c7aae1afefe290f43da7ba47b8dc802e GIT binary patch literal 2501 zcmcImc{J4T8voiFq?BwSOCpI%7|N2eHyVt}7W31fEMqXkU@X5dA|wevgi@lLXzYxL zXh>!>$uh%F)=3ymV{Bu&qx;vr=l*%`dC&7cpZ7WM@|@?K&+~p#9H7?wMCC*Q0N7`1 zV*%s&oLv*%!<&uyLMC~R4-K=v1XPk0=6H)eHzC#*z|QV?^0q9C7ZC}zaYX~b-b1^_ z2joAN<pn_)+bfo!u|0zP{4)Ip%jvw(VT`2<#vFx2`UGJB^H3izjE}c6)(>-A+1mDs zgY%vJGQ7K#Z7nWdji4=#(w!bL_(-g(>PDhgnUG4kaC`}$;6)LFile+l$%kWLTV21v zz<{$abVJV1S(Y^v&MEa1p<iAWLUrp3?V`K_%B<2pD9cF+9V>Zk9IUQPGEo}#1myWd zK)K$?*y7%qF-A;c)e@W8VG8+yBMTi6A(U=NCwEBT)kGl{UMahv<jrs3=<*wAX#EC~ zIlJJfC}bBL+}$mi^FKNEiz6mJ^_+6G_{obV(Oi;kwLZRhG1mElEndHd;%^Td3Kc3q zCw3>>z$Ba}*j(9KPFCQRiI<zO>uRNhw{Oxm!z?#h@HW*0{o;<039Wi$RFVKiiC@`N zJ=35lCUwhbd}7h0?cEe7czU{cZXrP6om|!%4YVPSBl$I5=KVHEhRGHq;<RdX{AmbB z^}R--y|PC*XhLhKBL(h?&HwQN;-z%Gk-~o36ba946#dhS^knex7y=LdW7AiYreL%f zaAk4G&!0%{Bde!ns(WfgW<O#bIBs|&oa||0i9?<IV51sTmHH`r{zMP`;&ZRVx$>rE zPdRcxf-3eLELQf<MIlPH<_RPeVfU5#xtoAi5`udN(wD^yn9=R8D}B>??OGgunZqQm zCq)-cC{K>T-*O6qTRyqleU_{|y{%bvUtX`)^VzW_?y;^562|1Iu&e?{vygBR*H`HB z%}J-IJ<Y?ll^Sa|%INJNr8fIcs<0<+<G7@^$%mxc8{~mU=q>1xro%$d)k`xQ!gaI5 z%52jhzB^=Ww>vS<1v2`W<kF1!#sA<yflYXn;<bGYR>at}cVLMjqlXYV7;|~a%wT=K zJ`q#oCdvpYZT|YzZoLaJUD0#g$OJ_ldaaY<UEw0r^0dL~c>kC2bBk8kez2J<E7d~o zXu>Me9;sP6A7rGDZgh72E*tWh)h?sz#2Bd)W)Hv&Q&W0dNDP&avX*|IgF*+VDX}8= z&zzByEo`}3?{R!k!eB9u^J7{c7+nZ_{{8b(%wpqHSp?14dMh<*KR!(95XvAie#}Qz z=ae69>V?lNORal7J?-FrMn6pUk~CUxe{KwI3YvikY^v^zQ$-}cKPP!z_%*yRb<Agq z>9F*i<W5AT$Czl}qE0#GmgCb{$UY~pzFGxCBoNVZir<Nu&79#x0>L<s_J*TCbS&y8 zOGGWwC^@4Z7CR^FS!@tdbnjL11H#QCBXjZzB&$5F<&4o8=<1aPsQXXRv&A~Iy7n1E zN2rlWscIY(M#pRe@|OR{mqE?R^eMSGie}QMY}W<aMuIIVYz)cxJ|cd&dSBYxiVRos z9h19j-`2RxVi=)f?#Gpw_SG7NQ$|vC$NiR&UEX?AtmdA}eZB!qu-HNPnE!PHk4Za@ zNObJ!o<YfanMavL56@NUsAE>Kp|z7rV&t<FXKYK4s>sPpV0*mUPQ&u*znI@4@n2hj z3ZlQ^+ezz>;)jF<hn-izfD+vB@>rkT=CjtwrGu>XI!dEMTsV{qG477UbVUiL878T3 zwU44R+B&xjmw->WjO5o%^x-x(10BUocx>%lard1)wypQ!i3~M&Cu!rqqgQex9<Vhk z&fH4V`%&<u)B)4w2DFWn7#yilW{?*K^Qb*DcCf<5+LfiqBV1Bo!X{@;DY%iuOsR&| zL`HJudEDC$pR`}jSU!lj?*kv9TNEH#!?o`AlHuE2oRz|RU^8kQr4$jnDk^Bz<N&|u z3<r5^Ep}RUIl0y1Xs!En4~L6|yx0&lujjK8;M;s=danl3-KWtn70~+e)%}p|RLYv; z9N*v3Ol6An($np5?QCaz0fdsrg*7U!(`_>4tlKbmVE)~3ne+aIa~2h9_jxVp=qcLy zc^6)X0l_!b9MvJ+v)I$bULFV27sgi3gWwV3U(V{roxWhV6FgqQAQ&N|gEwPfS=4nF z8bn(0@79_gZi=H0gnM}P<@V{skKEoLR7)_`2f19rq2^6iE!4qPaSC_oP{=LwpRD>K zdZ3$oV$ba&wo8(*fV7jH4Z}c%rT~eqm6GM&j+x8xh=^9yoQG1Pias_w0a;+1k-r>; z&5*r5+IV&ymOkH3nONU4bvrMA2QY0zQA5@P44fQs4{5W#-le_*4%nJ}sy1=fWbo#v zrPxV?M6wPklt}a$L=@0a-&Pvq=~7pc>pJlq+#xUd9F?OFc?sP>Hl@+$eYbura20QX ztq3aGxf}E_F*?Y#S&!d5(TiFQ9wJC1Vs%^jJ$&=&u<zA~bX(yOQ|a;b(l^yiJ!`0< z?={%@)2O(F^gO(-qWfRh#01OzDog5{?!{M-q>X>)@;C_ed3Ge%GqtDVO^imx1?Y|g zK8&!f6_OJnT7A#g1gw9qa3-W@0PM_~F~bJC_u$j&WZ4DH*UWfzU)vYMtf2JW$iq{X zX0AK4YpcH2-Eeead-{uIHnyA^ox;i6webJi7}fQx<1sGpWDW0C3*Zn}6IdfvNp7E5 zg|hSD<Ndj{7(GFfQP_;3h)!?hQ530yaiS+KU*j1*>)MV;&<P;8bb=quuo+I0%5k^s z7zU?eo88L&TD!-dp0=&8d>2?7T*j4Ne2aCdGuB@F2Wnw$=Bfew=Fk+bZ1LHdTml(x zWbVWr7H=zkmO1cTPXQ{wW4e0KeC6U}MbMGAuf1Q#O7aRyW)rCqcTDVCI<i0P9jL$R z7&c`rqLb3HHXYD@qyz_QzGa-DL+#}K0vmU^<>gz6N%c|rMnON5_3_&B^+`w<8qq6r zq!6q%`+aG&&ApB4>OJ07{%=a){~`(gQ-G|8Z;4Ns5$^?*XSDGI6<})#wWz%GNBlnl Dbc?e@ literal 0 HcmV?d00001 diff --git a/app/models/crop.rb b/app/models/crop.rb index 105afe0b5..acb04d6d5 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -5,6 +5,7 @@ class Crop < ActiveRecord::Base has_many :scientific_names has_many :plantings + has_many :photos, :through => :plantings belongs_to :parent, :class_name => 'Crop' has_many :varieties, :class_name => 'Crop', :foreign_key => 'parent_id' @@ -38,4 +39,8 @@ class Crop < ActiveRecord::Base return plantings.count end + def default_photo + return photos.first + end + end diff --git a/app/models/planting.rb b/app/models/planting.rb index 1535c798b..7b68a272b 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -50,4 +50,8 @@ class Planting < ActiveRecord::Base def to_s self.crop_system_name + " in " + self.location end + + def default_photo + return photos.first + end end diff --git a/app/views/crops/_thumbnail.html.haml b/app/views/crops/_thumbnail.html.haml index 5e4c6cb63..1337b8f0d 100644 --- a/app/views/crops/_thumbnail.html.haml +++ b/app/views/crops/_thumbnail.html.haml @@ -1,6 +1,6 @@ .thumbnail(style='height: 220px') - if crop - = link_to image_tag('http://placehold.it/150x150', :alt => '', :class => 'img-rounded'), crop + = link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => crop.system_name, :class => 'img-rounded'), crop %p = link_to crop.system_name, crop - if crop.scientific_names.count > 0 @@ -12,6 +12,3 @@ %small Planted = pluralize(crop.plantings_count, "time") - - else - = image_tag('http://placehold.it/150x150', :alt => '', :class => 'img-rounded') - Sample crop diff --git a/app/views/plantings/_thumbnail.html.haml b/app/views/plantings/_thumbnail.html.haml index 2e6147f02..362cd03a5 100644 --- a/app/views/plantings/_thumbnail.html.haml +++ b/app/views/plantings/_thumbnail.html.haml @@ -1,32 +1,37 @@ .well - %h4 - - if defined?(title) && title == 'owner' - = link_to planting.owner, planting.owner - - else - = link_to planting.crop.system_name, planting + .row-fluid + .span3 + = link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), planting + + .span9 + %h4 + - if defined?(title) && title == 'owner' + = link_to planting.owner, planting.owner + - else + = link_to planting.crop.system_name, planting - %p - Planted - - if planting.planted_at - = planting.planted_at - in - = link_to planting.location, planting.garden + %p + Planted + - if planting.planted_at + = planting.planted_at + in + = link_to planting.location, planting.garden - %p - - if planting.quantity - Quantity: - = planting.quantity - - else - &nbsp; + %p + - if planting.quantity + Quantity: + = planting.quantity + - else + &nbsp; - - if planting.description && ! defined?(hide_description) - %div - :markdown - #{ planting.description } + - if planting.description && ! defined?(hide_description) + %div + :markdown + #{ planting.description } - - if can? :edit, planting or can? :destroy, planting - %p - - if can? :edit, planting - =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-mini' - - if can? :destroy, planting - =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + - if can? :edit, planting or can? :destroy, planting + %p + - if can? :edit, planting + =link_to 'Edit', edit_planting_path(planting), :class => 'btn btn-mini' + - if can? :destroy, planting + =link_to 'Delete', planting, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 137ffaaa4..9d33ecf17 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -30,24 +30,27 @@ .span6 .well - %h3 - = link_to @planting.crop, @planting.crop - - if can? :edit, @planting - = link_to 'Plant another', new_planting_path, :class => 'btn btn-primary' - - elsif can? :create, Planting - = link_to 'Plant this', new_planting_path, :class => 'btn btn-primary' + .row-fluid + .span4 + = link_to image_tag((@planting.crop.default_photo ? @planting.crop.default_photo.thumbnail_url : 'placeholder_150.png'), :alt => '', :class => 'img-rounded'), @planting.crop + .span8 + %h3 + = link_to @planting.crop, @planting.crop + - if can? :edit, @planting + = link_to 'Plant another', new_planting_path, :class => 'btn btn-primary' + - elsif can? :create, Planting + = link_to 'Plant this', new_planting_path, :class => 'btn btn-primary' - %p - %b Scientific name: - = @planting.crop_default_scientific_name - %p - %b - Planted - = pluralize(@planting.crop_plantings_count, "time") - by #{Growstuff::Application.config.site_name} members + %p + %b Scientific name: + = @planting.crop_default_scientific_name + %p + %b + Planted + = pluralize(@planting.crop_plantings_count, "time") + by #{Growstuff::Application.config.site_name} members -%h2 - Notes +%h2 Notes :markdown #{ @planting.description != "" ? @planting.description : "No description given." } diff --git a/spec/models/crop_spec.rb b/spec/models/crop_spec.rb index bea69ed01..3184d236e 100644 --- a/spec/models/crop_spec.rb +++ b/spec/models/crop_spec.rb @@ -69,4 +69,14 @@ describe Crop do @tomato.varieties.should eq [@roma] end end + + context 'photos' do + it 'has a default photo' do + @crop = FactoryGirl.create(:tomato) + @planting = FactoryGirl.create(:planting, :crop => @crop) + @photo = FactoryGirl.create(:photo) + @planting.photos << @photo + @crop.default_photo.should be_an_instance_of Photo + end + end end diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index 8a9a75e93..538d70fcd 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -87,12 +87,19 @@ describe Planting do end context 'photos' do - it 'has a photo' do + before(:each) do @planting = FactoryGirl.create(:planting) @photo = FactoryGirl.create(:photo) @planting.photos << @photo + end + + it 'has a photo' do @planting.photos.first.should eq @photo end + + it 'has a default photo' do + @planting.default_photo.should eq @photo + end end end diff --git a/spec/views/crops/index.html.haml_spec.rb b/spec/views/crops/index.html.haml_spec.rb index 8f926129a..d61d108be 100644 --- a/spec/views/crops/index.html.haml_spec.rb +++ b/spec/views/crops/index.html.haml_spec.rb @@ -6,11 +6,10 @@ describe "crops/index" do page = 1 per_page = 2 total_entries = 2 + @tomato = FactoryGirl.create(:tomato) + @maize = FactoryGirl.create(:maize) crops = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| - pager.replace([ - FactoryGirl.create(:tomato), - FactoryGirl.create(:maize) - ]) + pager.replace([ @tomato, @maize ]) end assign(:crops, crops) end @@ -21,6 +20,14 @@ describe "crops/index" do assert_select "a", :text => "Tomato" end + it "shows photos where available" do + @planting = FactoryGirl.create(:planting, :crop => @tomato) + @photo = FactoryGirl.create(:photo) + @planting.photos << @photo + render + assert_select "img", :src => @photo.thumbnail_url + end + it "linkifies crop images" do render assert_select "img", :src => :tomato diff --git a/spec/views/crops/show.html.haml_spec.rb b/spec/views/crops/show.html.haml_spec.rb index 0b34be50e..b1bfe26da 100644 --- a/spec/views/crops/show.html.haml_spec.rb +++ b/spec/views/crops/show.html.haml_spec.rb @@ -44,6 +44,14 @@ describe "crops/show" do rendered.should contain "Springfield Community Garden" end + it "shows photos where available" do + @planting = FactoryGirl.create(:planting, :crop => @crop) + @photo = FactoryGirl.create(:photo) + @planting.photos << @photo + render + assert_select "img", :src => @photo.thumbnail_url + end + context 'varieties' do before(:each) do @popcorn = FactoryGirl.create(:popcorn, :parent_id => @crop.id) From 2839d6bf665eee2e519243d94286a8c92067a3d3 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 12:24:53 +1000 Subject: [PATCH 102/184] Fixed RSS bug where comments were showing up as "edited" Tried many ways to test this (assert_select, response.has_tag, etc), but couldn't get it to work. Have tested manually and it looks good. --- app/views/comments/index.rss.haml | 2 +- app/views/posts/show.rss.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/comments/index.rss.haml b/app/views/comments/index.rss.haml index 5e4b227c5..db2f7a54f 100644 --- a/app/views/comments/index.rss.haml +++ b/app/views/comments/index.rss.haml @@ -20,4 +20,4 @@ %pubDate= comment.created_at.to_s(:rfc822) %link= post_url(comment.post) - %guid= post_url(comment.post) + %guid= comment_url(comment) diff --git a/app/views/posts/show.rss.haml b/app/views/posts/show.rss.haml index 4f260a038..d5867dfb4 100644 --- a/app/views/posts/show.rss.haml +++ b/app/views/posts/show.rss.haml @@ -20,4 +20,4 @@ %pubDate= comment.created_at.to_s(:rfc822) %link= post_url(@post) - %guid= post_url(@post) + %guid= comment_url(comment) From a3d0f15fd5ae21fb2bec38056beb44089fd3d85d Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 13:02:29 +1000 Subject: [PATCH 103/184] Delete photo from photos/show page --- app/models/photo.rb | 2 ++ app/models/planting.rb | 2 ++ app/views/photos/index.html.haml | 4 +++- app/views/photos/show.html.haml | 3 +++ spec/models/planting_spec.rb | 13 ++++++++++++- spec/views/photos/show.html.haml_spec.rb | 12 +++++++++++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/models/photo.rb b/app/models/photo.rb index ac67273ab..e65617c87 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -2,7 +2,9 @@ class Photo < ActiveRecord::Base attr_accessible :flickr_photo_id, :owner_id, :title, :license_name, :license_url, :thumbnail_url, :fullsize_url, :link_url belongs_to :owner, :class_name => 'Member' + has_and_belongs_to_many :plantings + before_destroy {|photo| photo.plantings.clear} default_scope order("created_at desc") diff --git a/app/models/planting.rb b/app/models/planting.rb index 1535c798b..0bf5f6eb6 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -7,7 +7,9 @@ class Planting < ActiveRecord::Base belongs_to :garden belongs_to :crop + has_and_belongs_to_many :photos + before_destroy {|planting| planting.photos.clear} default_scope order("created_at desc") diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml index 09a8e1bd7..94ff1539d 100644 --- a/app/views/photos/index.html.haml +++ b/app/views/photos/index.html.haml @@ -1,4 +1,6 @@ -- content_for :title, "Photos" +- content_for :title, "Photo Gallery" + +%p This page shows all your photos. %div.pagination = page_entries_info @photos, :model => "photos" diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 47a2b7ecc..d9166cfa1 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -15,6 +15,9 @@ %p = link_to "View on Flickr", @photo.link_url + - if can? :destroy, @photo + %p= link_to 'Delete Photo', @photo, method: :delete, data: { confirm: 'Are you sure?' }, :class => 'btn btn-mini' + .span6 - if @photo.plantings.count > 0 %p This photo depicts: diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index 8a9a75e93..e0d04d569 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -86,13 +86,24 @@ describe Planting do @planting.errors[:sunniness].should include("not valid is not a valid sunniness value") end + # we decided that all the tests for the planting/photo association would + # be done on this side, not on the photos side context 'photos' do - it 'has a photo' do + before(:each) do @planting = FactoryGirl.create(:planting) @photo = FactoryGirl.create(:photo) @planting.photos << @photo + end + + it 'has a photo' do @planting.photos.first.should eq @photo end + + it 'deletes association with photos when photo is deleted' do + @photo.destroy + @planting.reload + @planting.photos.should be_empty + end end end diff --git a/spec/views/photos/show.html.haml_spec.rb b/spec/views/photos/show.html.haml_spec.rb index fa03e08d6..18a201590 100644 --- a/spec/views/photos/show.html.haml_spec.rb +++ b/spec/views/photos/show.html.haml_spec.rb @@ -1,9 +1,14 @@ require 'spec_helper' describe "photos/show" do + before(:each) do + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + end + context "CC-licensed photo" do before(:each) do - @photo = assign(:photo, FactoryGirl.create(:photo)) + @photo = assign(:photo, FactoryGirl.create(:photo, :owner => @member)) render end @@ -23,6 +28,10 @@ describe "photos/show" do it "shows a link to the original image" do assert_select "a", :href => @photo.link_url, :text => "View on Flickr" end + + it "has a delete button" do + assert_select "a[href=#{photo_path(@photo)}]", 'Delete Photo' + end end context "unlicensed photo" do @@ -34,6 +43,7 @@ describe "photos/show" do it "contains the phrase 'All rights reserved'" do rendered.should contain "All rights reserved" end + end context "linked to a planting" do From 4919bc967046566a012e5636528dd366ba96ac0e Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 19:44:23 +1000 Subject: [PATCH 104/184] added reminder to deploy tasks to set paypal details --- script/deploy-tasks.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index dc18a2390..beb92956e 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -4,11 +4,16 @@ # when it was added. This will help us know which ones have been run # and can safely be commented out or removed. -echo "2013-05-?? - Replacing empty post/notification subjects with '(no subject)'" -rake growstuff:oneoff:empty_subjects - -echo "2013-06-03 - replace empty garden names with 'Garden'" -rake growstuff:oneoff:empty_garden_names +# echo "2013-05-?? - Replacing empty post/notification subjects with '(no subject)'" +# rake growstuff:oneoff:empty_subjects +# echo "2013-06-03 - replace empty garden names with 'Garden'" +# rake growstuff:oneoff:empty_garden_names + +echo "2013-06-04 - set up Paypal API" +echo "You must run:" +echo " heroku config:set PAYPAL_USERNAME=..." +echo " heroku config:set PAYPAL_PASSWORD=..." +echo " heroku config:set PAYPAL_SIGNATURE=..." From 660d70645e58bb3411e9a4d36364e6b2f4d71360 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 20:21:59 +1000 Subject: [PATCH 105/184] Added oneoff task to set up shop/products/etc --- lib/tasks/growstuff.rake | 47 ++++++++++++++++++++++++++++++++++++---- script/deploy-tasks.sh | 32 +++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 89f4b0e8f..23d946622 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -40,14 +40,53 @@ namespace :growstuff do end end - task :account_details => :environment do - desc "Give each member an account_detail record" + task :setup_shop => :environment do + puts "Adding account types..." + AccountType.find_or_create_by_name( + :name => "Free", + :is_paid => false, + :is_permanent_paid => false + ) + @paid_account = AccountType.find_or_create_by_name( + :name => "Paid", + :is_paid => true, + :is_permanent_paid => false + ) + @seed_account = AccountType.find_or_create_by_name( + :name => "Seed", + :is_paid => true, + :is_permanent_paid => true + ) + AccountType.find_or_create_by_name( + :name => "Staff", + :is_paid => true, + :is_permanent_paid => true + ) + + puts "Adding products..." + Product.find_or_create_by_name( + :name => "Annual subscription", + :description => "An annual subscription gives you access to paid account features for one year. Does not auto-renew.", + :min_price => 3000, + :account_type_id => @paid_account.id, + :paid_months => 12 + ) + Product.find_or_create_by_name( + :name => "Seed account", + :description => "A seed account helps Growstuff grow in its early days. It gives you all the features of a paid account, in perpetuity. This account type never expires.", + :min_price => 15000, + :account_type_id => @seed_account.id, + ) + + puts "Giving each member an account record..." Member.all.each do |m| - unless m.account_detail - AccountDetail.create(:member_id => m.id) + unless m.account + Account.create(:member_id => m.id) end end + + puts "Done setting up shop." end end diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index beb92956e..38db646cd 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # tasks to run at deploy time, usually after 'rake db:migrate' # When adding tasks, do so in chronological order, and note the date @@ -10,10 +12,32 @@ # echo "2013-06-03 - replace empty garden names with 'Garden'" # rake growstuff:oneoff:empty_garden_names +# you MUST set up the paypal API environment variables before doing +# anything else, or things will fail all over the place. Stupid paypal :( + echo "2013-06-04 - set up Paypal API" -echo "You must run:" -echo " heroku config:set PAYPAL_USERNAME=..." -echo " heroku config:set PAYPAL_PASSWORD=..." -echo " heroku config:set PAYPAL_SIGNATURE=..." +if [ ! $PAYPAL_USERNAME ] +then + echo "You must run:" + echo " heroku config:set PAYPAL_USERNAME=..." + exit +fi + +if [ ! $PAYPAL_PASSWORD ] +then + echo "You must run:" + echo " heroku config:set PAYPAL_PASSWORD=..." + exit +fi + +if [ ! $PAYPAL_SIGNATURE ] +then + echo "You must run:" + echo " heroku config:set PAYPAL_SIGNATURE=..." + exit +fi + +echo "2013-06-04 - set up shop/products/etc" +rake growstuff:oneoff:setup_shop From 39d1a747453aa2c688a623029864979c316481e5 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 20:27:56 +1000 Subject: [PATCH 106/184] misc tidying up --- app/controllers/admin/orders_controller.rb | 22 +++++++--------------- spec/helpers/account_types_helper_spec.rb | 15 --------------- spec/helpers/admin/orders_helper_spec.rb | 15 --------------- spec/models/account_type_spec.rb | 1 - spec/views/admin/orders/index_spec.rb | 16 ++++++++++++++++ 5 files changed, 23 insertions(+), 46 deletions(-) delete mode 100644 spec/helpers/account_types_helper_spec.rb delete mode 100644 spec/helpers/admin/orders_helper_spec.rb create mode 100644 spec/views/admin/orders/index_spec.rb diff --git a/app/controllers/admin/orders_controller.rb b/app/controllers/admin/orders_controller.rb index fb94cf1ae..dd99881b4 100644 --- a/app/controllers/admin/orders_controller.rb +++ b/app/controllers/admin/orders_controller.rb @@ -7,33 +7,25 @@ class Admin::OrdersController < ApplicationController end def search + authorize! :manage, :all @orders = nil if params[:search_text] case params[:search_by] when "member" - begin - @member = Member.find(params[:search_text]) + @member = Member.find_by_id(params[:search_text]) + if @member @orders = @member.orders - rescue ActiveRecord::RecordNotFound - flash[:alert] = "Couldn't find member with name #{params[:search_text]}" end when "order_id" - begin - @orders = [ Order.find(params[:search_text]) ] - rescue ActiveRecord::RecordNotFound - flash[:alert] = "Couldn't find order with id #{params[:search_text]}" - end + @orders = [ Order.find_by_id(params[:search_text]) ] when "paypal_token" @orders = [ Order.find_by_paypal_express_token(params[:search_text]) ] - if @orders.nil? - flash[:alert] = "Couldn't find order with paypal token #{params[:search_text]}" - end when "paypal_payer_id" @orders = [ Order.find_by_paypal_express_payer_id(params[:search_text]) ] - if @orders.nil? - flash[:alert] = "Couldn't find order with paypal payer id #{params[:search_text]}" - end + end + if @orders.nil? + flash[:alert] = "Couldn't find order with #{params[:search_by]} = #{params[:search_text]}" end end diff --git a/spec/helpers/account_types_helper_spec.rb b/spec/helpers/account_types_helper_spec.rb deleted file mode 100644 index bb01d3c45..000000000 --- a/spec/helpers/account_types_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the AccountTypesHelper. For example: -# -# describe AccountTypesHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end -describe AccountTypesHelper do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/helpers/admin/orders_helper_spec.rb b/spec/helpers/admin/orders_helper_spec.rb deleted file mode 100644 index a80e748a5..000000000 --- a/spec/helpers/admin/orders_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the Admin::OrdersHelper. For example: -# -# describe Admin::OrdersHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end -describe Admin::OrdersHelper do - pending "add some examples to (or delete) #{__FILE__}" -end diff --git a/spec/models/account_type_spec.rb b/spec/models/account_type_spec.rb index c9bf250d9..8dfe24bf7 100644 --- a/spec/models/account_type_spec.rb +++ b/spec/models/account_type_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' describe AccountType do - pending "add some examples to (or delete) #{__FILE__}" end diff --git a/spec/views/admin/orders/index_spec.rb b/spec/views/admin/orders/index_spec.rb new file mode 100644 index 000000000..ef0cc3030 --- /dev/null +++ b/spec/views/admin/orders/index_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe 'admin/orders/index.html.haml', :type => "view" do + before(:each) do + @member = FactoryGirl.create(:admin_member) + sign_in @member + controller.stub(:current_user) { @member } + render + end + + it "includes a search form for orders" do + assert_select "form" + assert_select "input#search_text" + assert_select "select#search_by" + end +end From 36a959c92eade6eb476772ecf6325bf4cd540a4d Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 20:35:50 +1000 Subject: [PATCH 107/184] Give Skud a staff account as part of oneoff shop setup --- lib/tasks/growstuff.rake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 23d946622..1050c803d 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -58,7 +58,7 @@ namespace :growstuff do :is_paid => true, :is_permanent_paid => true ) - AccountType.find_or_create_by_name( + @staff_account = AccountType.find_or_create_by_name( :name => "Staff", :is_paid => true, :is_permanent_paid => true @@ -86,6 +86,13 @@ namespace :growstuff do end end + puts "Making Skud a staff account..." + @skud = Member.find_by_login_name('Skud') + if @skud + @skud.account.account_type = @staff_account + @skud.account.save + end + puts "Done setting up shop." end From 6c8f2b477fca18fe6e3e9d31a9a1a2f13832b54c Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 21:15:29 +1000 Subject: [PATCH 108/184] minor tweakage to admin/order/search --- app/controllers/admin/orders_controller.rb | 25 +++++++++++++++------- app/views/admin/orders/search.html.haml | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/controllers/admin/orders_controller.rb b/app/controllers/admin/orders_controller.rb index dd99881b4..8a4704f7a 100644 --- a/app/controllers/admin/orders_controller.rb +++ b/app/controllers/admin/orders_controller.rb @@ -8,23 +8,32 @@ class Admin::OrdersController < ApplicationController def search authorize! :manage, :all - @orders = nil + @orders = [] if params[:search_text] case params[:search_by] when "member" - @member = Member.find_by_id(params[:search_text]) - if @member - @orders = @member.orders + member = Member.find_by_login_name(params[:search_text]) + if member + @orders = member.orders end when "order_id" - @orders = [ Order.find_by_id(params[:search_text]) ] + order = Order.find_by_id(params[:search_text]) + if order + @orders = [order] + end when "paypal_token" - @orders = [ Order.find_by_paypal_express_token(params[:search_text]) ] + order = Order.find_by_paypal_express_token(params[:search_text]) + if order + @orders = [order] + end when "paypal_payer_id" - @orders = [ Order.find_by_paypal_express_payer_id(params[:search_text]) ] + order = Order.find_by_paypal_express_payer_id(params[:search_text]) + if order + @orders = [order] + end end - if @orders.nil? + if @orders.empty? flash[:alert] = "Couldn't find order with #{params[:search_by]} = #{params[:search_text]}" end end diff --git a/app/views/admin/orders/search.html.haml b/app/views/admin/orders/search.html.haml index b553bec5d..75474eba6 100644 --- a/app/views/admin/orders/search.html.haml +++ b/app/views/admin/orders/search.html.haml @@ -2,7 +2,7 @@ =render "admin/orders/searchform" -- if @orders +- unless @orders.empty? %h2 Found = pluralize(@orders.count, "result") From 6fed41351b1bb0e7a5b6aa701ad2dca312eb066f Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 4 Jun 2013 12:24:03 +0100 Subject: [PATCH 109/184] Show community features on logged-in homepage. Don't show interesting members if there are none --- app/views/home/index.html.haml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index 7b33f1506..e0dc9c150 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -54,17 +54,17 @@ .visible-phone = render :partial => 'blurb' - - if @interesting_members - %h2 Some of our members - %ul.thumbnails - - @interesting_members.each do |m| - %li.span2 - = render :partial => "members/thumbnail", :locals => { :member => m } +- if !@interesting_members.blank? + %h2 Some of our members + %ul.thumbnails + - @interesting_members.each do |m| + %li.span2 + = render :partial => "members/thumbnail", :locals => { :member => m } - .row - .span6 - %h2 Recent plantings - = render :partial => 'shared/recent_plantings' - .span6 - %h2 Recent posts - = render :partial => 'shared/recent_posts' +.row + .span6 + %h2 Recent plantings + = render :partial => 'shared/recent_plantings' + .span6 + %h2 Recent posts + = render :partial => 'shared/recent_posts' From 266575252f9427fbe95f869fbd714b2a0fc58008 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 4 Jun 2013 21:44:32 +1000 Subject: [PATCH 110/184] minor improvement to paypal API env var checks --- script/deploy-tasks.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index 38db646cd..c57d4ec51 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -20,20 +20,25 @@ if [ ! $PAYPAL_USERNAME ] then echo "You must run:" echo " heroku config:set PAYPAL_USERNAME=..." - exit + failed=true fi if [ ! $PAYPAL_PASSWORD ] then echo "You must run:" echo " heroku config:set PAYPAL_PASSWORD=..." - exit + failed=true fi if [ ! $PAYPAL_SIGNATURE ] then echo "You must run:" echo " heroku config:set PAYPAL_SIGNATURE=..." + failed=true +fi + +if [ failed ] +then exit fi From a0c91ee8849e56ee54d7dcdbb587067b187134ca Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 4 Jun 2013 12:59:37 +0100 Subject: [PATCH 111/184] Changes to homepage - only show interesting members if logged out - don't show your recent posts/plantings - change limits on posts/plantings for better visual balance. --- app/controllers/home_controller.rb | 8 ++------ app/views/home/index.html.haml | 21 ++++++--------------- spec/views/home/index_spec.rb | 10 ---------- 3 files changed, 8 insertions(+), 31 deletions(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index c96b99f3a..389c7dfa2 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -12,12 +12,8 @@ class HomeController < ApplicationController # customise what we show on the homepage based on whether you're # logged in or not. @member = current_member - @plantings = current_member ? - current_member.plantings.limit(10) : - Planting.limit(10) - @posts = current_member ? - current_member.posts.limit(10) : - Post.limit(10) + @plantings = Planting.limit(15) + @posts = Post.limit(10) respond_to do |format| format.html # index.html.haml diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index e0dc9c150..85b53a253 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -38,15 +38,6 @@ - current_member.forums.each do |f| %li= link_to f.name, f - .row - .span6 - %h2 Your recent plantings - = render :partial => 'shared/recent_plantings' - - .span6 - %h2 Your recent posts - = render :partial => 'shared/recent_posts' - - else .visible-desktop.visible-tablet .hero-unit @@ -54,12 +45,12 @@ .visible-phone = render :partial => 'blurb' -- if !@interesting_members.blank? - %h2 Some of our members - %ul.thumbnails - - @interesting_members.each do |m| - %li.span2 - = render :partial => "members/thumbnail", :locals => { :member => m } + - if @interesting_members.present? + %h2 Some of our members + %ul.thumbnails + - @interesting_members.each do |m| + %li.span2 + = render :partial => "members/thumbnail", :locals => { :member => m } .row .span6 diff --git a/spec/views/home/index_spec.rb b/spec/views/home/index_spec.rb index e123927f5..558420a7c 100644 --- a/spec/views/home/index_spec.rb +++ b/spec/views/home/index_spec.rb @@ -64,16 +64,6 @@ describe 'home/index.html.haml', :type => "view" do assert_select "a[href=#{url_for(@member.gardens.first)}]", "Garden" end - it 'lists plantings' do - rendered.should contain "Your recent plantings" - assert_select "a[href=#{url_for(@planting)}]" - end - - it 'lists posts' do - rendered.should contain "Your recent posts" - assert_select "a[href=#{url_for(@post)}]" - end - it 'shows admin status' do rendered.should contain "You are an ADMIN USER" end From 9d624902e83d7052861e4f337f4e5925c0fb3f85 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 4 Jun 2013 13:25:23 +0100 Subject: [PATCH 112/184] Fix string/integer conversions in migrations. --- ...20130515054017_change_order_member_id_to_integer.rb | 10 ++++++++-- .../20130601011725_change_flickr_photo_id_to_string.rb | 7 ++++++- db/schema.rb | 8 ++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/db/migrate/20130515054017_change_order_member_id_to_integer.rb b/db/migrate/20130515054017_change_order_member_id_to_integer.rb index 9ebb3b96a..2905204d7 100644 --- a/db/migrate/20130515054017_change_order_member_id_to_integer.rb +++ b/db/migrate/20130515054017_change_order_member_id_to_integer.rb @@ -1,5 +1,11 @@ class ChangeOrderMemberIdToInteger < ActiveRecord::Migration - def change - change_column :orders, :member_id, :integer + def up + remove_column :orders, :member_id + add_column :orders, :member_id, :integer + end + + def down + remove_column :orders, :member_id + add_column :orders, :member_id, :string end end diff --git a/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb b/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb index 2dd847ac4..84cf7a7b4 100644 --- a/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb +++ b/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb @@ -1,8 +1,13 @@ class ChangeFlickrPhotoIdToString < ActiveRecord::Migration def up - change_column :photos, :flickr_photo_id, :string + remove_column :photos, :flickr_photo_id + add_column :photos, :flickr_photo_id, :string end def down + # Postgres doesn't allow you to cast strings to integers + # Hence there's no way of rolling back this migration without losing + # information. + remove_column :photos, :flickr_photo_id change_column :photos, :flickr_photo_id, :integer end end diff --git a/db/schema.rb b/db/schema.rb index fae5ca762..485524431 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -147,10 +147,10 @@ ActiveRecord::Schema.define(:version => 20130601011725) do end create_table "orders", :force => true do |t| - t.integer "member_id", :limit => 255, :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.datetime "completed_at" + t.integer "member_id" t.string "paypal_express_token" t.string "paypal_express_payer_id" end @@ -162,7 +162,6 @@ ActiveRecord::Schema.define(:version => 20130601011725) do create_table "photos", :force => true do |t| t.integer "owner_id", :null => false - t.string "flickr_photo_id", :null => false t.string "thumbnail_url", :null => false t.string "fullsize_url", :null => false t.datetime "created_at", :null => false @@ -171,6 +170,7 @@ ActiveRecord::Schema.define(:version => 20130601011725) do t.string "license_name", :null => false t.string "license_url" t.string "link_url", :null => false + t.string "flickr_photo_id" end create_table "photos_plantings", :id => false, :force => true do |t| From 5e271e6279f6caf77d709371ee7dc5313674c079 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 4 Jun 2013 13:37:54 +0100 Subject: [PATCH 113/184] Fix to deploy-tasks bash script. --- script/deploy-tasks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index c57d4ec51..347970ce4 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -37,7 +37,7 @@ then failed=true fi -if [ failed ] +if [ $failed ] then exit fi From 2901e0cb118e3dd97c400c7357352c642cce7dd0 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 6 Jun 2013 20:17:32 +1000 Subject: [PATCH 114/184] tweaked wording on photos/index --- app/views/photos/index.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml index 94ff1539d..1e2cadabd 100644 --- a/app/views/photos/index.html.haml +++ b/app/views/photos/index.html.haml @@ -1,6 +1,6 @@ -- content_for :title, "Photo Gallery" +- content_for :title, "Photos" -%p This page shows all your photos. +%p Most recent photos added to #{Growstuff::Application.config.site_name}. %div.pagination = page_entries_info @photos, :model => "photos" From 170315e4bd2c401140f6d613c933567d4aa79ec7 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 6 Jun 2013 20:28:33 +1000 Subject: [PATCH 115/184] added test for default_photo ordering --- spec/models/planting_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index e5c2f777e..02cc5a062 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -108,6 +108,12 @@ describe Planting do it 'has a default photo' do @planting.default_photo.should eq @photo end + + it 'chooses the most recent photo' do + @photo2 = FactoryGirl.create(:photo) + @planting.photos << @photo2 + @planting.default_photo.should eq @photo2 + end end end From 4332628d6fa57e455706b195e0232c1b2fb3bfd2 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 6 Jun 2013 20:36:35 +1000 Subject: [PATCH 116/184] improved wording on shop homepage --- app/views/shop/index.html.haml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index d7f182529..43a022c8a 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -2,16 +2,22 @@ %p Growstuff relies on your support to build and run this open source - platform for food growers. + platform for food growers. We do not have outside investment, and do + not accept ads. Instead, we offer paid memberships, which give you + access to premium features, and ensure that we focus our efforts on + keeping you, our members, happy. %p - We offer paid accounts which have access to exclusive features. You can - buy a monthly, yearly, or permanent ("seed") account. + We are currently developing a number of advanced features for paid + members. We will announce our progress on these in our + = link_to "Feedback and Support forum", "http://www.growstuff.org/forums/growstuff-feedback-support" + as well as via other channels. %p - All our accounts are priced on a sliding scale. You can choose how much - you want to pay. Remember, your subscription supports an open source - platform promoting sustainable lifestyles! + All our accounts are priced on a sliding scale. You can choose how + much you want to pay. Remember, your subscription supports an open + source, open data platform supporting home food growers and promoting + sustainable food systems! - if current_member && current_member.is_paid? %h2 Thank you for supporting Growstuff From 6cf64694f469b8aa7c874f91fe8fa33b7fa42c66 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 6 Jun 2013 21:07:54 +1000 Subject: [PATCH 117/184] added account status info in various places --- app/models/account.rb | 19 ++++++ app/views/home/index.html.haml | 2 +- app/views/layouts/_header.html.haml | 1 + app/views/orders/complete.html.haml | 4 +- app/views/orders/index.html.haml | 69 +++++++++++++--------- app/views/orders/show.html.haml | 2 +- app/views/shared/_account_status.html.haml | 13 ++++ app/views/shop/index.html.haml | 2 + spec/models/account_spec.rb | 14 +++++ spec/views/layouts/application_spec.rb | 4 ++ spec/views/orders/index.html.haml_spec.rb | 9 ++- 11 files changed, 106 insertions(+), 33 deletions(-) create mode 100644 app/views/shared/_account_status.html.haml diff --git a/app/models/account.rb b/app/models/account.rb index 25cfc05ba..f68c61988 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -7,4 +7,23 @@ class Account < ActiveRecord::Base :message => 'already has account details associated with it' } + def account_type_string + if account_type + return account_type.name + else + return "Free" + end + end + + def paid_until_string + if account_type + if account_type.is_permanent_paid + return "forever" + elsif account_type.is_paid + return paid_until.to_s + end + end + return nil + end + end diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index e1c681526..a5b0a1bdb 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -34,7 +34,7 @@ account %br/ - if current_member == current_member && !current_member.is_paid? - = link_to "Upgrade and Support Growstuff", shop_path, :class => 'btn btn-primary' + = link_to "Upgrade and Support #{Growstuff::Application.config.site_name}", shop_path, :class => 'btn btn-primary' - else .visible-desktop.visible-tablet diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 622244364..9f6bf78f9 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -24,6 +24,7 @@ %ul.dropdown-menu %li= link_to "Profile", member_path(current_member) %li= link_to "Settings", edit_member_registration_path + %li= link_to "Account", orders_path %li= link_to "Sign out", destroy_member_session_path, :method => :delete %li - if current_member.notifications.unread_count > 0 diff --git a/app/views/orders/complete.html.haml b/app/views/orders/complete.html.haml index de2c7df75..a9c4ae133 100644 --- a/app/views/orders/complete.html.haml +++ b/app/views/orders/complete.html.haml @@ -3,13 +3,15 @@ %p Thank you for your order. %p - Completed at: + %strong Completed at: = @order.completed_at %p %strong Order number: = @order.id += render "shared/account_status" + %h2 Order items %table.table.table-striped diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml index 6e1302765..d20214bf4 100644 --- a/app/views/orders/index.html.haml +++ b/app/views/orders/index.html.haml @@ -1,31 +1,44 @@ -- content_for :title, "Order History" +- content_for :title, "Your Account" -%p - Your order history shows what you have bought via our - =succeed "." do - =link_to "shop", shop_path += render "shared/account_status" -%table.table.table-striped - %tr - %th Order number - %th Date completed - %th Items - %th - - @orders.each do |order| + +%h2 Orders + + +- if current_member.orders.present? + + %p + Your order history shows what you have bought via our + =succeed "." do + =link_to "shop", shop_path + + %table.table.table-striped %tr - %td= order.id - %td - - if order.completed_at - = order.completed_at.to_s - - else - In progress - %td - - if order.order_items.count > 0 - - order.order_items.each do |o| - = o.quantity - x - = o.product.name - @ - = price_with_currency(o.price) - %br/ - %td= link_to 'Details', order, :class => 'btn btn-mini' + %th Order number + %th Date completed + %th Items + %th + - @orders.each do |order| + %tr + %td= order.id + %td + - if order.completed_at + = order.completed_at.to_s + - else + In progress + %td + - if order.order_items.count > 0 + - order.order_items.each do |o| + = o.quantity + x + = o.product.name + @ + = price_with_currency(o.price) + %br/ + %td= link_to 'Details', order, :class => 'btn btn-mini' +- else + %p + You have not made any orders. You can place an order via our + =succeed "." do + =link_to "shop", shop_path diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 8136c13ec..391701df2 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -57,7 +57,7 @@ = link_to 'Delete this order', @order, method: :delete, | data: { confirm: 'Are you sure?' }, :class => 'btn' - if can? :complete, @order - = link_to 'Checkout', checkout_order_path(@order), :class => 'btn btn-primary' + = link_to 'Checkout with PayPal', checkout_order_path(@order), :class => 'btn btn-primary' %p = link_to "View other orders/order history", orders_path diff --git a/app/views/shared/_account_status.html.haml b/app/views/shared/_account_status.html.haml new file mode 100644 index 000000000..d4f2f2a76 --- /dev/null +++ b/app/views/shared/_account_status.html.haml @@ -0,0 +1,13 @@ +%h2 Your current account status + +%p + %strong Account type: + = current_member.account.account_type_string + +- if current_member.account.paid_until_string + %p + %strong Paid until: + = current_member.account.paid_until_string + +- if ! current_member.is_paid? + = link_to "Upgrade and support #{Growstuff::Application.config.site_name}", shop_path, :class => 'btn btn-primary' diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 43a022c8a..808764f5f 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -24,6 +24,8 @@ %p You currently have a paid membership, and can't buy another one at this time. + = render "shared/account_status" + - elsif @order and @order.order_items.count > 0 %h2 Your current order diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 26d59116e..8b2acd470 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -13,4 +13,18 @@ describe Account do @details = Account.new(:member_id => @member.id) @details.should_not be_valid end + + it "formats the 'paid until' date nicely" do + @member.account.account_type = FactoryGirl.create(:account_type) + @member.account.paid_until_string.should eq nil + + @member.account.account_type = FactoryGirl.create(:permanent_paid_account_type) + @member.account.paid_until_string.should eq "forever" + + @member.account.account_type = FactoryGirl.create(:paid_account_type) + @time = Time.zone.now + @member.account.paid_until = @time + @member.account.paid_until_string.should eq @time.to_s + end + end diff --git a/spec/views/layouts/application_spec.rb b/spec/views/layouts/application_spec.rb index 48cd360ce..0637faf80 100644 --- a/spec/views/layouts/application_spec.rb +++ b/spec/views/layouts/application_spec.rb @@ -40,6 +40,10 @@ describe 'layouts/application.html.haml', :type => "view" do assert_select "a[href=/members/edit]", "Settings" end + it "should show settings link" do + assert_select "a[href=#{orders_path}]", "Account" + end + it 'should show logout link' do rendered.should contain 'Sign out' end diff --git a/spec/views/orders/index.html.haml_spec.rb b/spec/views/orders/index.html.haml_spec.rb index 139efab9b..2eb399abd 100644 --- a/spec/views/orders/index.html.haml_spec.rb +++ b/spec/views/orders/index.html.haml_spec.rb @@ -4,11 +4,16 @@ describe "orders/index" do before(:each) do @member = FactoryGirl.create(:member) sign_in @member - @order1 = FactoryGirl.create(:order) - @order2 = FactoryGirl.create(:completed_order) + @order1 = FactoryGirl.create(:order, :member => @member) + @order2 = FactoryGirl.create(:completed_order, :member => @member) assign(:orders, [ @order1, @order2 ]) end + it "shows your current account status" do + render + rendered.should contain "Your current account status" + end + it "renders a list of orders" do render # Run the generator again with the --webrat flag if you want to use webrat matchers From b85b533afa006af64a6f5da886393f19502e1cf1 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 6 Jun 2013 21:16:46 +1000 Subject: [PATCH 118/184] updated wording in support/faq --- app/views/support/index.html.haml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/views/support/index.html.haml b/app/views/support/index.html.haml index 24a41dd04..2b64fd000 100644 --- a/app/views/support/index.html.haml +++ b/app/views/support/index.html.haml @@ -30,9 +30,13 @@ ### What's the status of Growstuff? Is it in beta? - As of March 2013, we are using the term "soft launch" for Growstuff's status. What we mean by this is that although we're open for signups, we're still busily working on features and getting everything ready for our larger, more public launch, which will happen in a couple of months' time. You can read more about this in our [blog post about the soft launch](http://blog.growstuff.org/2013/03/21/a-soft-launch/). + We don't much like the word "beta" or the idea it represents. + Growstuff is in a constant state of change and improvement, and we + hope we will always be adding new features and making things better + for our members. - Beyond that, Growstuff will always be in development, and we hope to keep releasing features regularly. You should expect Growstuff to change and grow all the time, just like your garden. If a feature you want isn't available yet, check back soon! + If you find any bugs or problems, please let us know via our support + forum. If a feature you want isn't available yet, check back soon! ## Membership and payments @@ -48,7 +52,10 @@ ### What other types of accounts are there? - We will be releasing paid accounts soon, which will give you access to special or "premium" features in thanks for supporting the site's operations with your cash. + You can purchase a paid account which will give you access to special + features, and a warm glow of knowing you're supporting a + community-focused, open source project. You can see the current list + of paid account options in our [shop](/shop). ## Profile, settings, and privacy @@ -109,7 +116,10 @@ ### Is there a Growstuff API? - At present there is no official API, as our site is still changing so rapidly. However, we do expose some of our data via JSON and/or RSS feeds, and you're welcome to play with it, on the understanding that it may change under you. You can find API docs on our wiki, at http://wiki.growstuff.org/index.php/API + At present there is no official API, as our site is still changing so + rapidly. However, we do expose some of our data via JSON and/or RSS + feeds, and you're welcome to play with it, on the understanding that + it may change under you. You can find [API docs on our wiki](http://wiki.growstuff.org/index.php/API). Use of our API and data is subject to the [API and Data Use Policy](http://www.growstuff.org/policy/api). From 4c2971f67088aaf05aa978d726ce0941e871e851 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 6 Jun 2013 21:19:24 +1000 Subject: [PATCH 119/184] oops, fixed broken test for checkout button --- spec/views/orders/show.html.haml_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/views/orders/show.html.haml_spec.rb b/spec/views/orders/show.html.haml_spec.rb index 749fa9002..04670b567 100644 --- a/spec/views/orders/show.html.haml_spec.rb +++ b/spec/views/orders/show.html.haml_spec.rb @@ -33,7 +33,7 @@ describe "orders/show" do end it "shows a checkout button" do - assert_select "a", :text => "Checkout" + assert_select "a", :text => "Checkout with PayPal" end it "shows a delete order button" do From 786bf1547fe7c058d0bc55f224f62676b594937d Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 6 Jun 2013 22:21:07 +1000 Subject: [PATCH 120/184] We have to actually complete the purchase! --- app/controllers/orders_controller.rb | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb index f1ef42906..1a052b0b9 100644 --- a/app/controllers/orders_controller.rb +++ b/app/controllers/orders_controller.rb @@ -48,13 +48,24 @@ class OrdersController < ApplicationController def complete @order = Order.find(params[:id]) - @order.completed_at = Time.zone.now @order.save - if (params[:token]) - @order.record_paypal_details(params[:token]) + if (params[:token] && params['PayerID']) + purchase = EXPRESS_GATEWAY.purchase( + @order.total, + :currency => Growstuff::Application.config.currency, + :ip => request.remote_ip, + :payer_id => params['PayerID'], + :token => params[:token] + ) + if purchase.success? + @order.completed_at = Time.zone.now + @order.record_paypal_details(params[:token]) + else + flash[:alert] = "Could not complete your order. Please notify support." + end else - flash[:alert] = "PayPal didn't return a token for your order. Please notify support." + flash[:alert] = "PayPal didn't return a token or payer_id for your order. Please notify support." end @order.update_account # apply paid account benefits, etc. From 8046790bc763ed98f35570556ee258c5fa47eb3e Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 7 Jun 2013 09:16:18 +1000 Subject: [PATCH 121/184] converted product description to text (not string) --- app/views/products/_form.html.haml | 4 ++-- ...30606230333_change_product_description_to_text.rb | 9 +++++++++ db/schema.rb | 12 ++++++------ spec/factories/products.rb | 2 +- spec/views/products/edit.html.haml_spec.rb | 2 +- spec/views/products/new.html.haml_spec.rb | 2 +- spec/views/shop/index_spec.rb | 4 ++++ 7 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 db/migrate/20130606230333_change_product_description_to_text.rb diff --git a/app/views/products/_form.html.haml b/app/views/products/_form.html.haml index 798aa7208..ba05d9eb6 100644 --- a/app/views/products/_form.html.haml +++ b/app/views/products/_form.html.haml @@ -8,10 +8,10 @@ .field = f.label :name - = f.text_field :name + = f.text_field :name, :class => 'input-block-level' .field = f.label :description - = f.text_field :description + = f.text_area :description, :class => 'input-block-level' .field = f.label :min_price = f.text_field :min_price diff --git a/db/migrate/20130606230333_change_product_description_to_text.rb b/db/migrate/20130606230333_change_product_description_to_text.rb new file mode 100644 index 000000000..d20e44f64 --- /dev/null +++ b/db/migrate/20130606230333_change_product_description_to_text.rb @@ -0,0 +1,9 @@ +class ChangeProductDescriptionToText < ActiveRecord::Migration + def up + change_column :products, :description, :text + end + + def down + change_column :products, :description, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 485524431..5d5b92e78 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130601011725) do +ActiveRecord::Schema.define(:version => 20130606230333) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -206,11 +206,11 @@ ActiveRecord::Schema.define(:version => 20130601011725) do add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true create_table "products", :force => true do |t| - t.string "name", :null => false - t.string "description", :null => false - t.integer "min_price", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.string "name", :null => false + t.text "description", :limit => 255, :null => false + t.integer "min_price", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "account_type_id" t.integer "paid_months" end diff --git a/spec/factories/products.rb b/spec/factories/products.rb index 037350ece..d12723050 100644 --- a/spec/factories/products.rb +++ b/spec/factories/products.rb @@ -3,7 +3,7 @@ FactoryGirl.define do factory :product do name "annual subscription" - description "paid membership, renewing yearly" + description "paid membership, renewing yearly, *hurrah*" min_price "999" account_type paid_months 12 diff --git a/spec/views/products/edit.html.haml_spec.rb b/spec/views/products/edit.html.haml_spec.rb index 26cd392c5..edcd07245 100644 --- a/spec/views/products/edit.html.haml_spec.rb +++ b/spec/views/products/edit.html.haml_spec.rb @@ -15,7 +15,7 @@ describe "products/edit" do # Run the generator again with the --webrat flag if you want to use webrat matchers assert_select "form", :action => products_path(@product), :method => "post" do assert_select "input#product_name", :name => "product[name]" - assert_select "input#product_description", :name => "product[description]" + assert_select "textarea#product_description", :name => "product[description]" assert_select "input#product_min_price", :name => "product[min_price]" end end diff --git a/spec/views/products/new.html.haml_spec.rb b/spec/views/products/new.html.haml_spec.rb index a01ad897d..280f4d7ce 100644 --- a/spec/views/products/new.html.haml_spec.rb +++ b/spec/views/products/new.html.haml_spec.rb @@ -15,7 +15,7 @@ describe "products/new" do # Run the generator again with the --webrat flag if you want to use webrat matchers assert_select "form", :action => products_path, :method => "post" do assert_select "input#product_name", :name => "product[name]" - assert_select "input#product_description", :name => "product[description]" + assert_select "textarea#product_description", :name => "product[description]" assert_select "input#product_min_price", :name => "product[min_price]" assert_select "select#product_account_type_id", :name => "product[account_type_id]" assert_select "input#product_paid_months", :name => "product[paid_months]" diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index dbd678199..2b32fd196 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -26,6 +26,10 @@ describe 'shop/index.html.haml', :type => "view" do it 'displays the order form' do assert_select "form", :count => 2 end + + it 'renders markdown in product descriptions' do + assert_select "em", :text => 'hurrah', :count => 2 + end end context "is paid" do From cb3e0fb526e8926bf6842c36272d881ddd7477d4 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 7 Jun 2013 09:53:40 +1000 Subject: [PATCH 122/184] added optional recommended price to products --- app/models/product.rb | 2 +- app/views/products/_form.html.haml | 5 ++++- app/views/shop/index.html.haml | 6 +++++- ...130606233733_add_recommended_price_to_product.rb | 5 +++++ db/schema.rb | 13 +++++++------ spec/factories/products.rb | 4 ++++ spec/views/products/edit.html.haml_spec.rb | 1 + spec/views/products/new.html.haml_spec.rb | 1 + spec/views/shop/index_spec.rb | 6 +++++- 9 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20130606233733_add_recommended_price_to_product.rb diff --git a/app/models/product.rb b/app/models/product.rb index bc0d58fd1..70193ea7a 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -1,5 +1,5 @@ class Product < ActiveRecord::Base - attr_accessible :description, :min_price, :name, + attr_accessible :description, :min_price, :recommended_price, :name, :account_type_id, :paid_months has_and_belongs_to_many :orders diff --git a/app/views/products/_form.html.haml b/app/views/products/_form.html.haml index ba05d9eb6..539e4b54c 100644 --- a/app/views/products/_form.html.haml +++ b/app/views/products/_form.html.haml @@ -13,8 +13,11 @@ = f.label :description = f.text_area :description, :class => 'input-block-level' .field - = f.label :min_price + = f.label :min_price, "Minimum price (in cents)" = f.text_field :min_price + .field + = f.label :recommended_price, "Recommended price (in cents)" + = f.text_field :recommended_price .field = f.label :account_type = collection_select(:product, :account_type_id, AccountType.all, :id, diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 808764f5f..c21b05f47 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -55,13 +55,17 @@ Pay what you want, starting at =succeed "." do =price_with_currency(p.min_price) + - if p.recommended_price + Recommended price: + =succeed "." do + =price_with_currency(p.recommended_price) %div - if can? :create, Order = form_for @order_item do |f| .field - = f.text_field :price, :value => price_in_dollars(p.min_price) + = f.text_field :price, :value => price_in_dollars(p.recommended_price || p.min_price) .field = f.hidden_field :product_id, :value => p.id .field diff --git a/db/migrate/20130606233733_add_recommended_price_to_product.rb b/db/migrate/20130606233733_add_recommended_price_to_product.rb new file mode 100644 index 000000000..c1aff96d4 --- /dev/null +++ b/db/migrate/20130606233733_add_recommended_price_to_product.rb @@ -0,0 +1,5 @@ +class AddRecommendedPriceToProduct < ActiveRecord::Migration + def change + add_column :products, :recommended_price, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 5d5b92e78..aab2827b1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130606230333) do +ActiveRecord::Schema.define(:version => 20130606233733) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -206,13 +206,14 @@ ActiveRecord::Schema.define(:version => 20130606230333) do add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true create_table "products", :force => true do |t| - t.string "name", :null => false - t.text "description", :limit => 255, :null => false - t.integer "min_price", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.string "name", :null => false + t.text "description", :limit => 255, :null => false + t.integer "min_price", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "account_type_id" t.integer "paid_months" + t.integer "recommended_price" end create_table "roles", :force => true do |t| diff --git a/spec/factories/products.rb b/spec/factories/products.rb index d12723050..e34dedac8 100644 --- a/spec/factories/products.rb +++ b/spec/factories/products.rb @@ -7,5 +7,9 @@ FactoryGirl.define do min_price "999" account_type paid_months 12 + + factory :product_with_recommended_price do + recommended_price "1200" + end end end diff --git a/spec/views/products/edit.html.haml_spec.rb b/spec/views/products/edit.html.haml_spec.rb index edcd07245..875fab4b2 100644 --- a/spec/views/products/edit.html.haml_spec.rb +++ b/spec/views/products/edit.html.haml_spec.rb @@ -17,6 +17,7 @@ describe "products/edit" do assert_select "input#product_name", :name => "product[name]" assert_select "textarea#product_description", :name => "product[description]" assert_select "input#product_min_price", :name => "product[min_price]" + assert_select "input#product_recommended_price", :name => "product[recommended_price]" end end end diff --git a/spec/views/products/new.html.haml_spec.rb b/spec/views/products/new.html.haml_spec.rb index 280f4d7ce..769fbe6bd 100644 --- a/spec/views/products/new.html.haml_spec.rb +++ b/spec/views/products/new.html.haml_spec.rb @@ -17,6 +17,7 @@ describe "products/new" do assert_select "input#product_name", :name => "product[name]" assert_select "textarea#product_description", :name => "product[description]" assert_select "input#product_min_price", :name => "product[min_price]" + assert_select "input#product_recommended_price", :name => "product[recommended_price]" assert_select "select#product_account_type_id", :name => "product[account_type_id]" assert_select "input#product_paid_months", :name => "product[paid_months]" end diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index 2b32fd196..ca5b60aba 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'shop/index.html.haml', :type => "view" do before(:each) do @product1 = FactoryGirl.create(:product) - @product2 = FactoryGirl.create(:product) + @product2 = FactoryGirl.create(:product_with_recommended_price) assign(:products, [@product1, @product2]) assign(:order_item, OrderItem.new) end @@ -23,6 +23,10 @@ describe 'shop/index.html.haml', :type => "view" do rendered.should contain '9.99 AUD' end + it 'shows recommended price for products that have it' do + rendered.should contain '12.00 AUD' + end + it 'displays the order form' do assert_select "form", :count => 2 end From df60a7c4dcc49d59574d6fc4cdf7a948f9c395d2 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 7 Jun 2013 11:36:03 +1000 Subject: [PATCH 123/184] display recommended price on product admin pages --- app/views/products/index.html.haml | 2 ++ app/views/products/show.html.haml | 6 +++++- spec/views/products/show.html.haml_spec.rb | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/views/products/index.html.haml b/app/views/products/index.html.haml index 6a0cadb50..61940aaa8 100644 --- a/app/views/products/index.html.haml +++ b/app/views/products/index.html.haml @@ -5,6 +5,7 @@ %th Name %th Description %th Min price + %th Recommended price %th Account type %th Paid months %th @@ -16,6 +17,7 @@ %td= product.name %td= product.description %td= product.min_price + %td= product.recommended_price %td= product.account_type ? product.account_type.name : "" %td= product.paid_months %td= link_to 'Show', product diff --git a/app/views/products/show.html.haml b/app/views/products/show.html.haml index 874169c81..d8ea39456 100644 --- a/app/views/products/show.html.haml +++ b/app/views/products/show.html.haml @@ -5,10 +5,14 @@ = @product.name %p %b Description: - = @product.description + :markdown + #{@product.description} %p %b Min price: = @product.min_price +%p + %b Recommended price: + = @product.recommended_price %p %b Account type: = @product.account_type.name diff --git a/spec/views/products/show.html.haml_spec.rb b/spec/views/products/show.html.haml_spec.rb index 19d8256d7..12710a91f 100644 --- a/spec/views/products/show.html.haml_spec.rb +++ b/spec/views/products/show.html.haml_spec.rb @@ -9,7 +9,7 @@ describe "products/show" do render # Run the generator again with the --webrat flag if you want to use webrat matchers rendered.should contain @product.name - rendered.should contain @product.description rendered.should contain @product.min_price.to_s + rendered.should contain @product.recommended_price.to_s end end From 903f0c808cd3628d4d9dfd93f74680c7e310dd4f Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 7 Jun 2013 17:52:31 +1000 Subject: [PATCH 124/184] cache recent_posts and recent_plantings partials --- Gemfile | 5 +-- Gemfile.lock | 2 ++ app/controllers/plantings_controller.rb | 3 ++ app/controllers/posts_controller.rb | 3 ++ app/models/planting_sweeper.rb | 21 +++++++++++++ app/models/post_sweeper.rb | 16 ++++++++++ app/views/shared/_recent_plantings.html.haml | 22 ++++++++------ app/views/shared/_recent_posts.html.haml | 32 +++++++++++--------- config/environments/development.rb | 5 ++- config/environments/production.rb | 2 +- config/environments/staging.rb | 2 +- 11 files changed, 81 insertions(+), 32 deletions(-) create mode 100644 app/models/planting_sweeper.rb create mode 100644 app/models/post_sweeper.rb diff --git a/Gemfile b/Gemfile index 6c8ced112..14881d19c 100644 --- a/Gemfile +++ b/Gemfile @@ -17,14 +17,11 @@ gem 'activemerchant', '1.33.0', gem 'active_utils', '1.0.5', :path => 'vendor/gems/active_utils-1.0.5' - -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' - group :production, :staging do gem 'pg' gem 'newrelic_rpm' gem 'unicorn' + gem 'dalli' end # Gems used only for assets and not required diff --git a/Gemfile.lock b/Gemfile.lock index f45975c9b..2532e5a46 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -74,6 +74,7 @@ GEM compass (>= 0.12.2, < 0.14) daemon_controller (1.1.2) daemons (1.1.9) + dalli (2.6.4) debugger (1.5.0) columnize (>= 0.3.1) debugger-linecache (~> 1.2.0) @@ -256,6 +257,7 @@ DEPENDENCIES capistrano-ext coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) + dalli debugger devise diff-lcs diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index 7a7c61119..b8775d1e2 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -1,5 +1,8 @@ class PlantingsController < ApplicationController load_and_authorize_resource + + cache_sweeper :planting_sweeper + # GET /plantings # GET /plantings.json def index diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index de7c80091..4003d546a 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -1,5 +1,8 @@ class PostsController < ApplicationController load_and_authorize_resource + + cache_sweeper :post_sweeper + # GET /posts # GET /posts.json diff --git a/app/models/planting_sweeper.rb b/app/models/planting_sweeper.rb new file mode 100644 index 000000000..a2b259bf9 --- /dev/null +++ b/app/models/planting_sweeper.rb @@ -0,0 +1,21 @@ +class PlantingSweeper < ActionController::Caching::Sweeper + observe Planting + + def after_create(planting) + expire_cache_for(planting) + end + + def after_update(planting) + expire_cache_for(planting) + end + + def after_destroy(planting) + expire_cache_for(planting) + end + + private + def expire_cache_for(planting) + expire_fragment('recent_plantings') + end +end + diff --git a/app/models/post_sweeper.rb b/app/models/post_sweeper.rb new file mode 100644 index 000000000..e6ad03427 --- /dev/null +++ b/app/models/post_sweeper.rb @@ -0,0 +1,16 @@ +class PostSweeper < ActionController::Caching::Sweeper + observe Post + + def after_create(post) + expire_fragment('recent_posts') + end + + def after_update(post) + expire_fragment('recent_posts') + end + + def after_destroy(post) + expire_fragment('recent_posts') + end + +end diff --git a/app/views/shared/_recent_plantings.html.haml b/app/views/shared/_recent_plantings.html.haml index 4fda76254..1db77ca33 100644 --- a/app/views/shared/_recent_plantings.html.haml +++ b/app/views/shared/_recent_plantings.html.haml @@ -1,12 +1,14 @@ -- if @plantings - %ul - - @plantings.each do |p| - %li - = link_to "#{p.crop_system_name} in #{p.location}", p - - if p.planted_at - on - = p.planted_at -- else - %p None yet. +- cache('recent_plantings') do + - if @plantings + %ul + - @plantings.each do |p| + %li + = link_to "#{p.crop_system_name} in #{p.location}", p + - if p.planted_at + on + = p.planted_at + - else + %p None yet. + - if can? :create, Planting %p= link_to "Plant something", new_planting_path, :class => 'btn btn-primary' diff --git a/app/views/shared/_recent_posts.html.haml b/app/views/shared/_recent_posts.html.haml index 82d06a2d7..3107e8fc5 100644 --- a/app/views/shared/_recent_posts.html.haml +++ b/app/views/shared/_recent_posts.html.haml @@ -1,17 +1,19 @@ -- if @posts - %ul - - @posts.each do |p| - %li - = link_to p.subject, p - - unless @member - posted by - = link_to p.author, p.author - - if p.forum - in - = link_to p.forum.name, p.forum - on - = p.created_at.to_s(:date) -- else - %p None yet. +- cache('recent_posts') do + - if @posts + %ul + - @posts.each do |p| + %li + = link_to p.subject, p + - unless @member + posted by + = link_to p.author, p.author + - if p.forum + in + = link_to p.forum.name, p.forum + on + = p.created_at.to_s(:date) + - else + %p None yet. + - if can? :create, Post %p= link_to "Post something", new_post_path, :class => 'btn btn-primary' diff --git a/config/environments/development.rb b/config/environments/development.rb index b76b3ffb4..211170993 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -11,7 +11,10 @@ Growstuff::Application.configure do # Show full error reports and disable caching config.consider_all_requests_local = true - config.action_controller.perform_caching = false + + # cache for testing/experimentation - turn off for normal dev use + config.action_controller.perform_caching = true + config.cache_store = :memory_store # Don't care if the mailer can't send config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/production.rb b/config/environments/production.rb index 2cc74822a..08feba071 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -40,7 +40,7 @@ Growstuff::Application.configure do # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production - # config.cache_store = :mem_cache_store + config.cache_store = :dalli_store # Enable serving of images, stylesheets, and JavaScripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 9ed1a7ced..24f77f035 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -40,7 +40,7 @@ Growstuff::Application.configure do # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) # Use a different cache store in production - # config.cache_store = :mem_cache_store + config.cache_store = :dalli_store # Enable serving of images, stylesheets, and JavaScripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" From dc0b6fa9cb89168e0b8c1eb2fc74aed4c4b3a825 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 7 Jun 2013 18:11:50 +1000 Subject: [PATCH 125/184] added memcachier gem --- Gemfile | 1 + Gemfile.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index 14881d19c..0e86cc1fb 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ group :production, :staging do gem 'newrelic_rpm' gem 'unicorn' gem 'dalli' + gem 'memcachier' end # Gems used only for assets and not required diff --git a/Gemfile.lock b/Gemfile.lock index 2532e5a46..34a77efc5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -129,6 +129,7 @@ GEM i18n (>= 0.4.0) mime-types (~> 1.16) treetop (~> 1.4.8) + memcachier (0.0.2) mime-types (1.21) multi_json (1.7.1) net-scp (1.1.0) @@ -271,6 +272,7 @@ DEPENDENCIES jquery-rails json (~> 1.7.7) less-rails + memcachier newrelic_rpm omniauth omniauth-flickr From e3ef27e4018f38851bc4e03976f5f8e7bb1bd5f6 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 7 Jun 2013 18:20:45 +1000 Subject: [PATCH 126/184] turn caching off again in dev --- config/environments/development.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 211170993..50f3b1b96 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -13,7 +13,7 @@ Growstuff::Application.configure do config.consider_all_requests_local = true # cache for testing/experimentation - turn off for normal dev use - config.action_controller.perform_caching = true + config.action_controller.perform_caching = false config.cache_store = :memory_store # Don't care if the mailer can't send From 2ecbd8315dd4452952a610cbe01341e3703dd381 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 11 Jun 2013 16:07:09 +1000 Subject: [PATCH 127/184] bugfix: don't say 'not yet set' in planting form PT: https://www.pivotaltracker.com/story/show/51457917 Maco found this. The problem was that if you had a blank planting date, and then re-edited the planting, it would say "not yet set" in the form field, then die when it later tried to convert that to a date. I replaced Miles's planted_at_string stuff in the model with a simpler parse_date method in the application helper. --- app/controllers/application_controller.rb | 2 ++ app/controllers/plantings_controller.rb | 6 ++++-- app/helpers/application_helper.rb | 5 +++++ app/models/planting.rb | 14 +------------- app/views/plantings/_form.html.haml | 2 +- config/initializers/time_formats.rb | 2 ++ spec/helpers/application_helper.rb | 7 +++++++ spec/models/planting_spec.rb | 20 -------------------- spec/views/plantings/_form.html.haml_spec.rb | 2 +- 9 files changed, 23 insertions(+), 37 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3b4dac042..60a80a80c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,8 @@ class ApplicationController < ActionController::Base protect_from_forgery + include ApplicationHelper + # tweak CanCan defaults because we don't have a "current_user" method # this means that we use current_user in specs but current_member everywhere # else in the code. diff --git a/app/controllers/plantings_controller.rb b/app/controllers/plantings_controller.rb index b8775d1e2..9d4b3f0b9 100644 --- a/app/controllers/plantings_controller.rb +++ b/app/controllers/plantings_controller.rb @@ -30,8 +30,7 @@ class PlantingsController < ApplicationController # GET /plantings/new # GET /plantings/new.json def new - @planting = Planting.new - @planting.planted_at = Date.today + @planting = Planting.new('planted_at' => Date.today) # using find_by_id here because it returns nil, unlike find @crop = Crop.find_by_id(params[:crop_id]) || Crop.new @@ -46,6 +45,7 @@ class PlantingsController < ApplicationController # GET /plantings/1/edit def edit @planting = Planting.find(params[:id]) + # the following are needed to display the form but aren't used @crop = Crop.new @garden = Garden.new @@ -54,6 +54,7 @@ class PlantingsController < ApplicationController # POST /plantings # POST /plantings.json def create + params[:planted_at] = parse_date(params[:planted_at]) @planting = Planting.new(params[:planting]) respond_to do |format| @@ -71,6 +72,7 @@ class PlantingsController < ApplicationController # PUT /plantings/1.json def update @planting = Planting.find(params[:id]) + params[:planted_at] = parse_date(params[:planted_at]) respond_to do |format| if @planting.update_attributes(params[:planting]) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 51da9b2b3..b07e64dd2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -10,5 +10,10 @@ module ApplicationHelper Growstuff::Application.config.currency) end + def parse_date(str) + str ||= '' # Date.parse barfs on nil + return str == '' ? nil : Date.parse(str) + end + end diff --git a/app/models/planting.rb b/app/models/planting.rb index 08fe34d90..8bf6dda2c 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -3,7 +3,7 @@ class Planting < ActiveRecord::Base friendly_id :planting_slug, use: :slugged attr_accessible :crop_id, :description, :garden_id, :planted_at, - :quantity, :sunniness, :planted_at_string + :quantity, :sunniness belongs_to :garden belongs_to :crop @@ -37,18 +37,6 @@ class Planting < ActiveRecord::Base return "#{garden.owner.login_name}'s #{garden}" end - def planted_at_string - if planted_at - planted_at.strftime("%F") - else - "Not yet set" - end - end - - def planted_at_string=(str) - self.planted_at = str == '' ? nil : Time.parse(str) - end - def to_s self.crop_system_name + " in " + self.location end diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index 2a8906add..b21add0c9 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -15,7 +15,7 @@ Garden.where(:owner_id => current_member), :id, :name, :selected => @planting.garden_id || @garden.id) .control-group = f.label 'When?', :class => 'control-label' - .controls= f.text_field :planted_at_string, :class => 'add-datepicker' + .controls= f.text_field :planted_at, :value => @planting.planted_at ? @planting.planted_at.to_s(:ymd) : '', :class => 'add-datepicker' .control-group = f.label 'How many?', :class => 'control-label' .controls diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb index 0b26541d9..3f3adf454 100644 --- a/config/initializers/time_formats.rb +++ b/config/initializers/time_formats.rb @@ -4,4 +4,6 @@ Date::DATE_FORMATS[:default] = "%B %d, %Y" Time::DATE_FORMATS[:date] = "%B %d, %Y" Date::DATE_FORMATS[:date] = "%B %d, %Y" +Date::DATE_FORMATS[:ymd] = "%Y-%m-%d" + Time::DATE_FORMATS[:datetime] = '%B %d, %Y at %H:%M' diff --git a/spec/helpers/application_helper.rb b/spec/helpers/application_helper.rb index c65d0a8aa..912ffbfd5 100644 --- a/spec/helpers/application_helper.rb +++ b/spec/helpers/application_helper.rb @@ -5,4 +5,11 @@ describe ApplicationHelper do price_in_dollars(999).should eq '9.99' price_with_currency(999).should eq '9.99 AUD' end + + it "parses dates" do + parse_date(nil).should eq nil + parse_date('').should eq nil + parse_date('2012-05-12').should eq Date.new(2012, 5, 12) + parse_date('may 12th 2012').should eq Date.new(2012, 5, 12) + end end diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index 02cc5a062..cfadcf9cd 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -29,26 +29,6 @@ describe Planting do @planting.slug.should match /^member\d+-springfield-community-garden-tomato$/ end - it "should accept ISO-format dates" do - @planting.planted_at_string = "2013-03-01" - @planting.planted_at.should == Time.local(2013, 03, 01) - end - - it "should accept DD Month YY format dates" do - @planting.planted_at_string = "1st March 13" # Dydd Gŵyl Dewi Hapus! - @planting.planted_at.should == Time.local(2013, 03, 01) - end - - it 'should accept blank dates' do - @planting.planted_at_string = '' - @planting.planted_at.should == nil - end - - it "should output dates in ISO format" do - @planting.planted_at = Time.local(2013, 03, 01) - @planting.planted_at_string.should == "2013-03-01" - end - it 'should sort in reverse creation order' do @planting2 = FactoryGirl.create(:planting) Planting.first.should eq @planting2 diff --git a/spec/views/plantings/_form.html.haml_spec.rb b/spec/views/plantings/_form.html.haml_spec.rb index 2b6813ada..225b08256 100644 --- a/spec/views/plantings/_form.html.haml_spec.rb +++ b/spec/views/plantings/_form.html.haml_spec.rb @@ -18,7 +18,7 @@ describe "plantings/_form" do end it "has a free-form text field containing the planting date in ISO format" do - assert_select 'input#planting_planted_at_string[type=text][value=2013-03-01]' + assert_select 'input#planting_planted_at[type=text][value=2013-03-01]' end context "logged in" do From a70a19871a4a84ef2704860bc6361ecc2849101e Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Wed, 12 Jun 2013 09:07:41 +1000 Subject: [PATCH 128/184] Use github version of twitter-bootstrap-rails to fix firefox new tab problem --- Gemfile | 2 +- Gemfile.lock | 16 +++++++--- .../bootstrap_and_overrides.css.less | 32 +++++++++++++------ 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/Gemfile b/Gemfile index 0e86cc1fb..8e25c271e 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,7 @@ group :assets do # long term, we'll probably want node.js for performance, but this will do for now as it's easier for new people to install gem 'therubyracer', '~> 0.10.2', :platforms => :ruby gem "less-rails" - gem "twitter-bootstrap-rails", '~> 2.2.2' + gem "twitter-bootstrap-rails", :git => 'https://github.com/seyhunak/twitter-bootstrap-rails.git' gem 'uglifier', '>= 1.0.3' diff --git a/Gemfile.lock b/Gemfile.lock index 34a77efc5..2f9b7f6ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,13 @@ +GIT + remote: https://github.com/seyhunak/twitter-bootstrap-rails.git + revision: 2c7c527c354d9068ce49346d4fd8389328d32ce6 + specs: + twitter-bootstrap-rails (2.2.7) + actionpack (>= 3.1) + execjs + rails (>= 3.1) + railties (>= 3.1) + PATH remote: vendor/gems/active_utils-1.0.5 specs: @@ -222,10 +232,6 @@ GEM treetop (1.4.12) polyglot polyglot (>= 0.3.1) - twitter-bootstrap-rails (2.2.6) - actionpack (>= 3.1) - execjs - railties (>= 3.1) tzinfo (0.3.37) uglifier (1.3.0) execjs (>= 0.3.0) @@ -288,7 +294,7 @@ DEPENDENCIES sqlite3 therubyracer (~> 0.10.2) thin - twitter-bootstrap-rails (~> 2.2.2) + twitter-bootstrap-rails! uglifier (>= 1.0.3) unicorn watchr diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 00aa25784..089d5b1cd 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -1,21 +1,33 @@ @import "twitter/bootstrap/bootstrap"; -body { padding-top: 60px; } @import "twitter/bootstrap/responsive"; // Set the correct sprite paths -@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings.png"); -@iconWhiteSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings-white.png"); +@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings"); +@iconWhiteSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings-white"); // Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines) -// Note: If you use asset_path() here, your compiled bootstrap_and_overrides.css will not -// have the proper paths. So for now we use the absolute path. -@fontAwesomeEotPath: asset-path("fontawesome-webfont.eot?v=3.0.2"); -@fontAwesomeEotPath_iefix: asset-path("fontawesome-webfont.eot?#iefix&v=3.0.2"); -@fontAwesomeWoffPath: asset-path("fontawesome-webfont.woff?v=3.0.2"); -@fontAwesomeTtfPath: asset-path("fontawesome-webfont.ttf?v=3.0.2"); +@fontAwesomeEotPath: asset-url("fontawesome-webfont.eot"); +@fontAwesomeEotPath_iefix: asset-url("fontawesome-webfont.eot#iefix"); +@fontAwesomeWoffPath: asset-url("fontawesome-webfont.woff"); +@fontAwesomeTtfPath: asset-url("fontawesome-webfont.ttf"); +@fontAwesomeSvgPath: asset-url("fontawesome-webfont.svg#fontawesomeregular"); // Font Awesome -@import "fontawesome"; +@import "fontawesome/font-awesome"; + +// Glyphicons +//@import "twitter/bootstrap/sprites.less"; + +// Your custom LESS stylesheets goes here +// +// Since bootstrap was imported above you have access to its mixins which +// you may use and inherit here +// +// If you'd like to override bootstrap's own variables, you can do so here as well +// See http://twitter.github.com/bootstrap/customize.html#variables for their names and documentation +// +// Example: +// @linkColor: #ff0000; // Base colours From 66a6f53ae7f6e1457f764621e1b0f8e34a1fd364 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Wed, 12 Jun 2013 13:03:05 +0100 Subject: [PATCH 129/184] Set up Coveralls integration for test coverage. --- .gitignore | 1 + Gemfile | 1 + Gemfile.lock | 14 ++++++++++++++ README.md | 1 + spec/spec_helper.rb | 2 ++ 5 files changed, 19 insertions(+) diff --git a/.gitignore b/.gitignore index 7801d8f9e..495db9c03 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /doc /db/*.sqlite3 /tmp/* +/coverage .pt .*.sw* *~ diff --git a/Gemfile b/Gemfile index 0e86cc1fb..92de75bcf 100644 --- a/Gemfile +++ b/Gemfile @@ -103,4 +103,5 @@ group :development, :test do gem 'webrat' gem 'watchr' gem 'factory_girl_rails', '~> 4.0' + gem 'coveralls', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 34a77efc5..d6bcfde90 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,6 +64,7 @@ GEM coffee-script-source execjs coffee-script-source (1.6.2) + colorize (0.5.8) columnize (0.3.6) commonjs (0.2.6) compass (0.12.2) @@ -72,6 +73,12 @@ GEM sass (~> 3.1) compass-rails (1.0.3) compass (>= 0.12.2, < 0.14) + coveralls (0.6.7) + colorize + multi_json (~> 1.3) + rest-client + simplecov (>= 0.7) + thor daemon_controller (1.1.2) daemons (1.1.9) dalli (2.6.4) @@ -187,6 +194,8 @@ GEM rake (10.0.3) rdoc (3.12.2) json (~> 1.4) + rest-client (1.6.7) + mime-types (>= 1.16) rspec-core (2.12.2) rspec-expectations (2.12.1) diff-lcs (~> 1.1.3) @@ -205,6 +214,10 @@ GEM railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) + simplecov (0.7.1) + multi_json (~> 1.0) + simplecov-html (~> 0.7.1) + simplecov-html (0.7.1) sprockets (2.2.2) hike (~> 1.2) multi_json (~> 1.0) @@ -258,6 +271,7 @@ DEPENDENCIES capistrano-ext coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) + coveralls dalli debugger devise diff --git a/README.md b/README.md index 68334e30b..40911c0e8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Growstuff [![Build Status](https://travis-ci.org/Growstuff/growstuff.png)](https://travis-ci.org/Growstuff/growstuff) +[![Coverage Status](https://coveralls.io/repos/Growstuff/growstuff/badge.png)](https://coveralls.io/r/Growstuff/growstuff) Welcome to the Growstuff project. diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9fcfe41b7..4f3c6804a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,8 @@ ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' +require 'coveralls' +Coveralls.wear! # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. From 4b9bd4b5dec8191d66d721bcee0aa4eb01c84cf8 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Thu, 13 Jun 2013 07:58:54 +1000 Subject: [PATCH 130/184] added specific commit to twitter-bootstrap-rails in Gemfile --- Gemfile | 4 +++- Gemfile.lock | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 8e25c271e..ba593f2ea 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,9 @@ group :assets do # long term, we'll probably want node.js for performance, but this will do for now as it's easier for new people to install gem 'therubyracer', '~> 0.10.2', :platforms => :ruby gem "less-rails" - gem "twitter-bootstrap-rails", :git => 'https://github.com/seyhunak/twitter-bootstrap-rails.git' + gem "twitter-bootstrap-rails", + :git => 'https://github.com/seyhunak/twitter-bootstrap-rails.git', + :ref => '2c7c52' gem 'uglifier', '>= 1.0.3' diff --git a/Gemfile.lock b/Gemfile.lock index 2f9b7f6ce..e63142054 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GIT remote: https://github.com/seyhunak/twitter-bootstrap-rails.git revision: 2c7c527c354d9068ce49346d4fd8389328d32ce6 + ref: 2c7c52 specs: twitter-bootstrap-rails (2.2.7) actionpack (>= 3.1) From 033703e9b949a393dbe4e7549e9f19ae6fae7735 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Thu, 13 Jun 2013 20:29:02 +0100 Subject: [PATCH 131/184] Set up Rails coverage groups. --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4f3c6804a..fab2ff8d2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,7 +4,7 @@ require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' require 'coveralls' -Coveralls.wear! +Coveralls.wear!('rails') # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. From 1f5c81fdca653e88c91a64395a3075058254467d Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Thu, 13 Jun 2013 20:38:26 +0100 Subject: [PATCH 132/184] Filter spec files out of coverage reports. --- spec/spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fab2ff8d2..8fa87587a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,10 @@ require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' require 'coveralls' +require 'simplecov' +SimpleCov.configure do + add_filter 'spec/' +end Coveralls.wear!('rails') # Requires supporting ruby files with custom matchers and macros, etc, From efb5515b0f4cb73d0118087ca70394ae387e6ed1 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 25 Jun 2013 14:56:24 +0100 Subject: [PATCH 133/184] Remove unused gems; don't load assets group in CI. --- .travis.yml | 2 +- Gemfile | 8 -------- Gemfile.lock | 35 +++-------------------------------- 3 files changed, 4 insertions(+), 41 deletions(-) diff --git a/.travis.yml b/.travis.yml index 569581efd..3154bb568 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ --- language: ruby -bundler_args: --without development +bundler_args: --without development assets rvm: - 1.9.3 script: diff --git a/Gemfile b/Gemfile index c39a77a8b..6d0e03dba 100644 --- a/Gemfile +++ b/Gemfile @@ -56,11 +56,6 @@ gem 'flickraw' # Use unicorn as the app server # gem 'unicorn' -# Deploy with Capistrano -gem 'capistrano' -gem 'rvm-capistrano' -gem 'capistrano-ext' - # To use debugger group :development do # Installation of the debugger gem fails on Travis CI, @@ -94,10 +89,7 @@ gem 'omniauth' gem 'omniauth-twitter' gem 'omniauth-flickr' -# for phusion passenger (i.e. mod_rails) on the server -gem 'passenger' gem 'rake', '>= 10.0.0' -gem 'cape', '~> 1.5.0' gem 'diff-lcs' diff --git a/Gemfile.lock b/Gemfile.lock index 33649e631..7a1407922 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,15 +58,6 @@ GEM railties (>= 3.0) builder (3.0.4) cancan (1.6.9) - cape (1.5.0) - capistrano (2.14.2) - highline - net-scp (>= 1.0.0) - net-sftp (>= 2.0.0) - net-ssh (>= 2.0.14) - net-ssh-gateway (>= 1.1.0) - capistrano-ext (1.2.1) - capistrano (>= 1.0.0) chunky_png (1.2.7) coffee-rails (3.2.2) coffee-script (>= 2.2.0) @@ -90,7 +81,6 @@ GEM rest-client simplecov (>= 0.7) thor - daemon_controller (1.1.2) daemons (1.1.9) dalli (2.6.4) debugger (1.5.0) @@ -114,7 +104,6 @@ GEM factory_girl_rails (4.2.1) factory_girl (~> 4.2.0) railties (>= 3.0.0) - fastthread (1.0.7) flickraw (0.9.6) friendly_id (4.0.9) fssm (0.2.10) @@ -128,7 +117,6 @@ GEM haml (>= 3.1, < 4.1) railties (>= 3.1, < 4.1) hashie (1.2.0) - highline (1.6.16) hike (1.2.1) i18n (0.6.1) journey (1.0.4) @@ -149,16 +137,11 @@ GEM treetop (~> 1.4.8) memcachier (0.0.2) mime-types (1.21) + mini_portile (0.5.0) multi_json (1.7.1) - net-scp (1.1.0) - net-ssh (>= 2.6.5) - net-sftp (2.1.1) - net-ssh (>= 2.6.5) - net-ssh (2.6.6) - net-ssh-gateway (1.2.0) - net-ssh (>= 2.6.5) newrelic_rpm (3.5.8.72) - nokogiri (1.5.8) + nokogiri (1.6.0) + mini_portile (~> 0.5.0) oauth (0.4.7) omniauth (1.1.3) hashie (~> 1.2) @@ -172,11 +155,6 @@ GEM multi_json (~> 1.3) omniauth-oauth (~> 1.0) orm_adapter (0.4.0) - passenger (3.0.19) - daemon_controller (>= 1.0.0) - fastthread (>= 1.0.1) - rack - rake (>= 0.8.1) pg (0.14.1) polyglot (0.3.3) rack (1.4.5) @@ -218,8 +196,6 @@ GEM rspec-core (~> 2.12.0) rspec-expectations (~> 2.12.0) rspec-mocks (~> 2.12.0) - rvm-capistrano (1.2.7) - capistrano (>= 2.0.0) sass (3.2.7) sass-rails (3.2.6) railties (~> 3.2.0) @@ -273,9 +249,6 @@ DEPENDENCIES bootstrap-datepicker-rails bundler (>= 1.1.5) cancan - cape (~> 1.5.0) - capistrano - capistrano-ext coffee-rails (~> 3.2.1) compass-rails (~> 1.0.3) coveralls @@ -298,13 +271,11 @@ DEPENDENCIES omniauth omniauth-flickr omniauth-twitter - passenger pg rack (~> 1.4.5) rails (= 3.2.13) rake (>= 10.0.0) rspec-rails (~> 2.12.1) - rvm-capistrano sass-rails (~> 3.2.3) sqlite3 therubyracer (~> 0.10.2) From 9ee0e032779fe083ad63d79e91c2c97d4b8e82f9 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 25 Jun 2013 18:12:01 +0100 Subject: [PATCH 134/184] Remove explicit load of diff-lcs. It's a dependency of rspec-expectations. --- Gemfile | 2 -- Gemfile.lock | 1 - 2 files changed, 3 deletions(-) diff --git a/Gemfile b/Gemfile index 6d0e03dba..7920050cd 100644 --- a/Gemfile +++ b/Gemfile @@ -91,8 +91,6 @@ gem 'omniauth-flickr' gem 'rake', '>= 10.0.0' -gem 'diff-lcs' - group :development, :test do gem 'thin' gem 'sqlite3' diff --git a/Gemfile.lock b/Gemfile.lock index 7a1407922..bef30dee2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -255,7 +255,6 @@ DEPENDENCIES dalli debugger devise - diff-lcs factory_girl_rails (~> 4.0) flickraw friendly_id From 0368f52ca8c2137731cd1ad6d4525a6769bee464 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 18:10:15 +0100 Subject: [PATCH 135/184] Only use the 'debugger' gem in development. Installation was failing on Travis CI causing spurious failed builds, so we don't want it under the test environment. I reckon we don't want it in the production environment anyway. --- Gemfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c1ca55b38..c39a77a8b 100644 --- a/Gemfile +++ b/Gemfile @@ -62,7 +62,11 @@ gem 'rvm-capistrano' gem 'capistrano-ext' # To use debugger -gem 'debugger' +group :development do + # Installation of the debugger gem fails on Travis CI, + # so we don't use it in the test environment + gem 'debugger' +end # Markdown formatting for updates etc gem 'bluecloth' From 756ebd3025fadd53276f15af34e855fda55544f9 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 25 Jun 2013 18:18:00 +0100 Subject: [PATCH 136/184] Use unicorn instead of Thin webserver This is in the name of eliminating unnecessary differences between development and production. You must invoke unicorn with `unicorn` rather than `rails s`. By default it listens on port 8080. --- Gemfile | 3 +-- Gemfile.lock | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 7920050cd..6157e668a 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ gem 'rails', '3.2.13' gem 'rack', '~>1.4.5' gem 'json', '~>1.7.7' gem 'haml' +gem 'unicorn' # http server gem 'cancan' @@ -20,7 +21,6 @@ gem 'active_utils', '1.0.5', group :production, :staging do gem 'pg' gem 'newrelic_rpm' - gem 'unicorn' gem 'dalli' gem 'memcachier' end @@ -92,7 +92,6 @@ gem 'omniauth-flickr' gem 'rake', '>= 10.0.0' group :development, :test do - gem 'thin' gem 'sqlite3' gem 'haml-rails' gem 'rspec-rails', '~> 2.12.1' diff --git a/Gemfile.lock b/Gemfile.lock index bef30dee2..d5d22729b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,7 +81,6 @@ GEM rest-client simplecov (>= 0.7) thor - daemons (1.1.9) dalli (2.6.4) debugger (1.5.0) columnize (>= 0.3.1) @@ -96,7 +95,6 @@ GEM warden (~> 1.2.1) diff-lcs (1.1.3) erubis (2.7.0) - eventmachine (1.0.3) execjs (1.4.0) multi_json (~> 1.0) factory_girl (4.2.0) @@ -213,10 +211,6 @@ GEM sqlite3 (1.3.7) therubyracer (0.10.2) libv8 (~> 3.3.10) - thin (1.5.1) - daemons (>= 1.0.9) - eventmachine (>= 0.12.6) - rack (>= 1.0.0) thor (0.17.0) tilt (1.3.6) treetop (1.4.12) @@ -278,7 +272,6 @@ DEPENDENCIES sass-rails (~> 3.2.3) sqlite3 therubyracer (~> 0.10.2) - thin twitter-bootstrap-rails! uglifier (>= 1.0.3) unicorn From 1ad3828a3b64bf0ac50cdcd9c910d2fcaecb2c12 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 25 Jun 2013 14:25:19 +0100 Subject: [PATCH 137/184] Don't load the "development" gem group on Travis CI. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0928a1e53..569581efd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ --- language: ruby +bundler_args: --without development rvm: - 1.9.3 script: From 3ce246ef5adfbb4097c51d29cda93e4abe891721 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Sun, 23 Jun 2013 17:26:09 +0100 Subject: [PATCH 138/184] Allowing Miles to drive. --- spec/factories/account_types.rb | 5 +++++ spec/models/member_spec.rb | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/spec/factories/account_types.rb b/spec/factories/account_types.rb index cfae7caf8..832ddf0ce 100644 --- a/spec/factories/account_types.rb +++ b/spec/factories/account_types.rb @@ -18,6 +18,11 @@ FactoryGirl.define do is_permanent_paid true end + factory :staff_account_type do + name "Staff" + is_paid true + is_permanent_paid true + end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 4d349b1a1..bfbdee303 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -238,6 +238,12 @@ describe 'member' do Member.interesting.should eq [ @member3, @member2, @member1 ] end + + it 'does not include staff members' do + @member1 = FactoryGirl.create(:member) + + Member.interesting.should_not include @member1 + end end context 'orders' do From c6eb835771598af0e1171b8548de9ea121badc6b Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Sun, 23 Jun 2013 18:24:33 +0100 Subject: [PATCH 139/184] Tests and sketch implementation of not_staff scope. It doesn't work and we don't yet know why :-( --- app/models/member.rb | 7 ++++++- spec/models/member_spec.rb | 26 +++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index efbd19eeb..bb48fda7c 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -26,10 +26,15 @@ class Member < ActiveRecord::Base scope :confirmed, where('confirmed_at IS NOT NULL') scope :located, where('location IS NOT NULL') scope :recently_signed_in, reorder('updated_at DESC') + scope :not_staff, joins('inner join accounts + on members.id = accounts.member_id + inner join account_types + on accounts.account_type_id = account_types.id'). + where('account_types.name != "Staff"') # this is used on the signed-out homepage so we're basically # just trying to select some members who look good. - scope :interesting, confirmed.located.recently_signed_in + scope :interesting, confirmed.located.recently_signed_in.not_staff # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index bfbdee303..9a18db0d6 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -239,10 +239,30 @@ describe 'member' do end - it 'does not include staff members' do + it 'should not include staff members' do + @member1 = FactoryGirl.create(:south_pole_member) + @member1.account.account_type = FactoryGirl.create(:staff_account_type) + @member1.updated_at = 3.days.ago + Member.interesting.should eq [] + end + end + + context 'not_staff scope' do + it 'should not include staff members' do + @member1 = FactoryGirl.create(:south_pole_member) + @member1.account.account_type = FactoryGirl.create(:staff_account_type) + Member.not_staff.should_not include @member1 + end + + it 'should include free members' do @member1 = FactoryGirl.create(:member) - - Member.interesting.should_not include @member1 + Member.not_staff.should include @member1 + end + + it 'should_include paid members' do + @member1 = FactoryGirl.create(:member) + @member1.account.account_type = FactoryGirl.create(:paid_account_type) + Member.not_staff.should include @member1 end end From 6ccf8f397f3bead7a97d8ce1ab6c3630137e1ac8 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Mon, 24 Jun 2013 11:57:01 +0100 Subject: [PATCH 140/184] Give new users a free account. --- app/models/account.rb | 6 ++++++ spec/models/member_spec.rb | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/models/account.rb b/app/models/account.rb index f68c61988..143883fc1 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -7,6 +7,12 @@ class Account < ActiveRecord::Base :message => 'already has account details associated with it' } + before_create do |account| + unless account.account_type + account.account_type = AccountType.find_by_name("Free") + end + end + def account_type_string if account_type return account_type.name diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 9a18db0d6..79eebbc91 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -34,6 +34,12 @@ describe 'member' do @member.account.should be_an_instance_of Account end + it "should have a free account by default" do + FactoryGirl.create(:account_type) + @member.save + @member.account.account_type.name.should eq "Free" + end + it "doesn't show email by default" do @member.save @member.show_email.should be_false From 0412fe0bc5db4c6f2de1f868b511007375b6f7a6 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Mon, 24 Jun 2013 11:57:41 +0100 Subject: [PATCH 141/184] Updated .gitignore to include coverage files anywhere in the directory tree. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 495db9c03..e5f43f5d4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ /doc /db/*.sqlite3 /tmp/* -/coverage +coverage .pt .*.sw* *~ @@ -11,3 +11,4 @@ credentials.sh Pathogen: custom_plan.rb zeus.json +.bundle From 0a8aaf0ee64bd1444170c681d4f70b8439f5a4ed Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 12:39:22 +0100 Subject: [PATCH 142/184] Create free account type if it doesn't exist; fix tests. --- app/models/account.rb | 2 +- app/models/member.rb | 12 ++++-------- app/views/accounts/show.html.haml | 5 +---- spec/controllers/account_types_controller_spec.rb | 5 ++++- spec/models/member_spec.rb | 2 +- spec/models/order_spec.rb | 1 - spec/views/accounts/show.html.haml_spec.rb | 2 +- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 143883fc1..6506bf4f4 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -9,7 +9,7 @@ class Account < ActiveRecord::Base before_create do |account| unless account.account_type - account.account_type = AccountType.find_by_name("Free") + account.account_type = AccountType.find_or_create_by_name("Free") end end diff --git a/app/models/member.rb b/app/models/member.rb index bb48fda7c..e17fddcf0 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -126,14 +126,10 @@ class Member < ActiveRecord::Base end def is_paid? - if account.account_type # it might be nil if you've never had one - if account.account_type.is_permanent_paid - return true - elsif account.account_type.is_paid and account.paid_until >= Time.zone.now - return true - else - return false - end + if account.account_type.is_permanent_paid + return true + elsif account.account_type.is_paid and account.paid_until >= Time.zone.now + return true else return false end diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 8a9b0e639..5c5288674 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -5,10 +5,7 @@ = @account.member_id %p %b Account type: - - if @account.account_type - = @account.account_type.name - - else - Free account + = @account.account_type.name %p %b Paid until: = @account.paid_until diff --git a/spec/controllers/account_types_controller_spec.rb b/spec/controllers/account_types_controller_spec.rb index 070252672..7067fbd26 100644 --- a/spec/controllers/account_types_controller_spec.rb +++ b/spec/controllers/account_types_controller_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe AccountTypesController do + # This automatically creates a "Free" account type login_member(:admin_member) def valid_attributes @@ -12,7 +13,9 @@ describe AccountTypesController do it "assigns all account_types as @account_types" do account_type = AccountType.create! valid_attributes get :index, {} - assigns(:account_types).should eq([account_type]) + assigns(:account_types).sort.should eq( + [account_type, subject.current_member.account_type].sort + ) end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 79eebbc91..d6fef1fe3 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -35,9 +35,9 @@ describe 'member' do end it "should have a free account by default" do - FactoryGirl.create(:account_type) @member.save @member.account.account_type.name.should eq "Free" + @member.is_paid?.should be_false end it "doesn't show email by default" do diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index ffa3e2f12..1a04d4e69 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -28,7 +28,6 @@ describe Order do @order_item = FactoryGirl.create(:order_item, :order_id => @order.id, :product_id => @product.id) - @member.account.account_type.should be_nil @member.account.paid_until.should be_nil @order.update_account diff --git a/spec/views/accounts/show.html.haml_spec.rb b/spec/views/accounts/show.html.haml_spec.rb index cd8c0bd60..5b429e13e 100644 --- a/spec/views/accounts/show.html.haml_spec.rb +++ b/spec/views/accounts/show.html.haml_spec.rb @@ -10,7 +10,7 @@ describe "accounts/show" do render # Run the generator again with the --webrat flag if you want to use webrat matchers rendered.should contain @account.member_id.to_s - rendered.should contain 'Free account' + rendered.should contain 'Free' rendered.should contain @account.paid_until.to_s end end From f6073ab01c93f065b0e60099df1c0cbfcf6524c9 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 12:46:31 +0100 Subject: [PATCH 143/184] Remove "if account_type" checks in Account. - remove account_type_string method - simplify paid_until_string. --- app/models/account.rb | 19 ++++--------------- app/views/shared/_account_status.html.haml | 2 +- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 6506bf4f4..b4a0b1d67 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -13,23 +13,12 @@ class Account < ActiveRecord::Base end end - def account_type_string - if account_type - return account_type.name - else - return "Free" - end - end - def paid_until_string - if account_type - if account_type.is_permanent_paid - return "forever" - elsif account_type.is_paid - return paid_until.to_s - end + if account_type.is_permanent_paid + return "forever" + elsif account_type.is_paid + return paid_until.to_s end - return nil end end diff --git a/app/views/shared/_account_status.html.haml b/app/views/shared/_account_status.html.haml index d4f2f2a76..a4b3119c7 100644 --- a/app/views/shared/_account_status.html.haml +++ b/app/views/shared/_account_status.html.haml @@ -2,7 +2,7 @@ %p %strong Account type: - = current_member.account.account_type_string + = current_member.account_type.name - if current_member.account.paid_until_string %p From 5bcd4c2edd7d5cf393c587e26a687f388d15aa7b Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 13:14:58 +0100 Subject: [PATCH 144/184] Use ActiveRecord methods for not_staff join code. - set member.account_type directly in tests, because changes to member.account.account_type weren't being persisted. --- app/models/member.rb | 7 ++----- spec/models/member_spec.rb | 6 +++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index e17fddcf0..993f2e96c 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -26,11 +26,8 @@ class Member < ActiveRecord::Base scope :confirmed, where('confirmed_at IS NOT NULL') scope :located, where('location IS NOT NULL') scope :recently_signed_in, reorder('updated_at DESC') - scope :not_staff, joins('inner join accounts - on members.id = accounts.member_id - inner join account_types - on accounts.account_type_id = account_types.id'). - where('account_types.name != "Staff"') + scope :not_staff, joins(:account => :account_type). + where('account_types.name != "Staff"') # this is used on the signed-out homepage so we're basically # just trying to select some members who look good. diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index d6fef1fe3..54b87d78a 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -247,7 +247,7 @@ describe 'member' do it 'should not include staff members' do @member1 = FactoryGirl.create(:south_pole_member) - @member1.account.account_type = FactoryGirl.create(:staff_account_type) + @member1.account_type = FactoryGirl.create(:staff_account_type) @member1.updated_at = 3.days.ago Member.interesting.should eq [] end @@ -256,7 +256,7 @@ describe 'member' do context 'not_staff scope' do it 'should not include staff members' do @member1 = FactoryGirl.create(:south_pole_member) - @member1.account.account_type = FactoryGirl.create(:staff_account_type) + @member1.account_type = FactoryGirl.create(:staff_account_type) Member.not_staff.should_not include @member1 end @@ -267,7 +267,7 @@ describe 'member' do it 'should_include paid members' do @member1 = FactoryGirl.create(:member) - @member1.account.account_type = FactoryGirl.create(:paid_account_type) + @member1.account_type = FactoryGirl.create(:paid_account_type) Member.not_staff.should include @member1 end end From 0040ee8727fad0ab9221f4700a71b4cc9946da5c Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 13:28:31 +0100 Subject: [PATCH 145/184] Rake task to give everyone free accounts; fix descriptions. --- lib/tasks/growstuff.rake | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake index 1050c803d..aa8ef4a24 100644 --- a/lib/tasks/growstuff.rake +++ b/lib/tasks/growstuff.rake @@ -11,11 +11,11 @@ namespace :growstuff do end + desc "One-off tasks needed at various times and kept for posterity" namespace :oneoff do - desc "One-off tasks needed at various times and kept for posterity" + desc "May 2013: replace any empty notification subjects with (no subject)" task :empty_subjects => :environment do - desc "May 2013: replace any empty notification subjects with (no subject)" # this is inefficient as it checks every Notification, but the # site is small and there aren't many of them, so it shouldn't matter @@ -26,8 +26,8 @@ namespace :growstuff do end end + desc "May 2013: replace any empty garden names with Garden" task :empty_garden_names => :environment do - desc "May 2013: replace any empty garden names with Garden" # this is inefficient as it checks every Garden, but the # site is small and there aren't many of them, so it shouldn't matter @@ -41,6 +41,7 @@ namespace :growstuff do end + desc "June 2013: create account types and products." task :setup_shop => :environment do puts "Adding account types..." AccountType.find_or_create_by_name( @@ -96,6 +97,20 @@ namespace :growstuff do puts "Done setting up shop." end + desc "June 2013: replace nil account_types with free accounts" + task :nil_account_type => :environment do + + free = AccountType.find_by_name("Free") + raise "Free account type not found: run rake growstuff:oneoff:setup_shop"\ + unless free + Account.all.each do |a| + unless a.account_type + a.account_type = free + a.save + end + end + end + end end From 8dceb985a19decc0eeda4c7924feb266f7700d26 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 14:53:24 +0100 Subject: [PATCH 146/184] Fix pending tests in views/shop/index. We needed to stub out current_member, not current_user. --- spec/views/shop/index_spec.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index ca5b60aba..17e9c86f3 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -41,8 +41,7 @@ describe 'shop/index.html.haml', :type => "view" do @member = FactoryGirl.create(:member) @member.account.account_type = FactoryGirl.create(:paid_account_type) @member.account.paid_until = Time.zone.now + 1.year - @member.save - controller.stub(:current_user) { @member } + controller.stub(:current_member) { @member } end it "recognises the paid member" do @@ -50,13 +49,11 @@ describe 'shop/index.html.haml', :type => "view" do end it "tells you you have a paid membership" do - pending "can't set up a paid member for some reason" render rendered.should contain "You currently have a paid" end it "doesn't show shop" do - pending "can't set up a paid member for some reason" render assert_select "form", false end From d26dd4499c93c1b8c2075794e0aa212e560b545d Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 14:57:30 +0100 Subject: [PATCH 147/184] Deleted unused admin_helper_spec.rb --- spec/helpers/admin_helper_spec.rb | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 spec/helpers/admin_helper_spec.rb diff --git a/spec/helpers/admin_helper_spec.rb b/spec/helpers/admin_helper_spec.rb deleted file mode 100644 index 3870aa933..000000000 --- a/spec/helpers/admin_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the AdminHelper. For example: -# -# describe AdminHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end -describe AdminHelper do - pending "add some examples to (or delete) #{__FILE__}" -end From 2d8128e54d79c39a16845f418be7843b728df41e Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 17:59:17 +0100 Subject: [PATCH 148/184] Slightly simplify tests for not_staff scope. There's no reason to set a location on the test member. --- spec/models/member_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 54b87d78a..d0ae3eb3d 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -255,7 +255,7 @@ describe 'member' do context 'not_staff scope' do it 'should not include staff members' do - @member1 = FactoryGirl.create(:south_pole_member) + @member1 = FactoryGirl.create(:member) @member1.account_type = FactoryGirl.create(:staff_account_type) Member.not_staff.should_not include @member1 end From 24af32970ce84429a7e1d855e1daa48575520a6b Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 25 Jun 2013 18:21:46 +0100 Subject: [PATCH 149/184] Remove watchr continuous-testing gem. Nobody was using it :-( --- .watchr | 26 -------------------------- Gemfile | 1 - Gemfile.lock | 2 -- lib/tasks/watchr.rake | 7 ------- 4 files changed, 36 deletions(-) delete mode 100644 .watchr delete mode 100644 lib/tasks/watchr.rake diff --git a/.watchr b/.watchr deleted file mode 100644 index e93b39abd..000000000 --- a/.watchr +++ /dev/null @@ -1,26 +0,0 @@ -def run_spec(file) - unless File.exist?(file) - puts "#{file} does not exist" - return - end - - puts "Running #{file}" - system "bundle exec rspec #{file}" - puts -end - -watch("spec/.*/*_spec.rb") do |match| - run_spec match[0] -end - -watch("app/(.*/.*).rb") do |match| - run_spec %{spec/#{match[1]}_spec.rb} -end - -watch("app/(.*/.*).html.haml") do |match| - run_spec %{spec/#{match[1]}_spec.rb} -end - -watch("app/(.*/.*).html.erb") do |match| - run_spec %{spec/#{match[1]}_spec.rb} -end diff --git a/Gemfile b/Gemfile index 6157e668a..4f3805ed9 100644 --- a/Gemfile +++ b/Gemfile @@ -96,7 +96,6 @@ group :development, :test do gem 'haml-rails' gem 'rspec-rails', '~> 2.12.1' gem 'webrat' - gem 'watchr' gem 'factory_girl_rails', '~> 4.0' gem 'coveralls', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index d5d22729b..e92b7af5f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -226,7 +226,6 @@ GEM raindrops (~> 0.7) warden (1.2.1) rack (>= 1.0) - watchr (0.7) webrat (0.7.3) nokogiri (>= 1.2.0) rack (>= 1.0) @@ -275,6 +274,5 @@ DEPENDENCIES twitter-bootstrap-rails! uglifier (>= 1.0.3) unicorn - watchr webrat will_paginate (~> 3.0) diff --git a/lib/tasks/watchr.rake b/lib/tasks/watchr.rake deleted file mode 100644 index 4f48a3761..000000000 --- a/lib/tasks/watchr.rake +++ /dev/null @@ -1,7 +0,0 @@ -begin - desc "Run watchr" - task :watchr do - sh %{bundle exec watchr .watchr} - end -rescue LoadError -end From 4cdadfb3856ac7f6d852d54a3e8045f806ff6b23 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Tue, 25 Jun 2013 18:22:57 +0100 Subject: [PATCH 150/184] Document what CanCan does in Gemfile. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 4f3805ed9..b3b1d181e 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem 'json', '~>1.7.7' gem 'haml' gem 'unicorn' # http server -gem 'cancan' +gem 'cancan' # for checking member privileges # vendored activemerchant for testing- needed for bogus paypal # gateway monkeypatch From 2f93982ae68acaf42af7d13f672dad8d7a79f9e8 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Wed, 26 Jun 2013 11:32:30 +0100 Subject: [PATCH 151/184] Document the :assets group in Gemfile. I think we can probably delete some of it (in particular compass-rails), but don't know how to test it. Halp plz? --- Gemfile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index b3b1d181e..35fe54433 100644 --- a/Gemfile +++ b/Gemfile @@ -28,20 +28,26 @@ end # Gems used only for assets and not required # in production environments by default. group :assets do + # CSS preprocessor, used for app/assets/stylesheets/application.css gem 'sass-rails', '~> 3.2.3' + # CoffeeScript is a Python-like language that compiles to JavaScript gem 'coffee-rails', '~> 3.2.1' + # less-rails depends on a JavaScript engine; we use therubyracer. # See https://github.com/sstephenson/execjs#readme for more supported runtimes - # long term, we'll probably want node.js for performance, but this will do for now as it's easier for new people to install + # long term, we'll probably want node.js for performance, but this will do + # for now as it's easier for new people to install gem 'therubyracer', '~> 0.10.2', :platforms => :ruby + # Another CSS preprocessor, used for Bootstrap overrides gem "less-rails" + # CSS framework gem "twitter-bootstrap-rails", :git => 'https://github.com/seyhunak/twitter-bootstrap-rails.git', :ref => '2c7c52' - gem 'uglifier', '>= 1.0.3' + gem 'uglifier', '>= 1.0.3' # JavaScript compressor - gem 'compass-rails', '~> 1.0.3' + gem 'compass-rails', '~> 1.0.3' # Yet Another CSS framework end gem 'jquery-rails' From 3d42844d6277992162802af5340ce85118a5ec00 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Wed, 26 Jun 2013 12:00:56 +0100 Subject: [PATCH 152/184] Document gems in test group --- Gemfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 35fe54433..ff04a29c3 100644 --- a/Gemfile +++ b/Gemfile @@ -98,10 +98,10 @@ gem 'omniauth-flickr' gem 'rake', '>= 10.0.0' group :development, :test do - gem 'sqlite3' - gem 'haml-rails' - gem 'rspec-rails', '~> 2.12.1' - gem 'webrat' - gem 'factory_girl_rails', '~> 4.0' - gem 'coveralls', require: false + gem 'sqlite3' # database engine + gem 'haml-rails' # HTML templating language + gem 'rspec-rails', '~> 2.12.1' # unit testing framework + gem 'webrat' # provides HTML matchers for view tests + gem 'factory_girl_rails', '~> 4.0' # for creating test data + gem 'coveralls', require: false # coverage analysis end From 5b061877ff8d1a656c9a35c8d3b4c68072b71292 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Wed, 26 Jun 2013 12:09:39 +0100 Subject: [PATCH 153/184] Upgrade debugger to 1.6.0 This should now include the headers for Ruby 1.9.3-p429 whose absence was causing our builds to fail. Better late than never... --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e92b7af5f..1f460549e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,12 +82,12 @@ GEM simplecov (>= 0.7) thor dalli (2.6.4) - debugger (1.5.0) + debugger (1.6.0) columnize (>= 0.3.1) debugger-linecache (~> 1.2.0) - debugger-ruby_core_source (~> 1.2.0) + debugger-ruby_core_source (~> 1.2.1) debugger-linecache (1.2.0) - debugger-ruby_core_source (1.2.0) + debugger-ruby_core_source (1.2.2) devise (2.2.3) bcrypt-ruby (~> 3.0) orm_adapter (~> 0.1) From 9f65aad5c6a8afceaaf3cc8a0d0b78b42dc623a8 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Wed, 3 Jul 2013 13:04:44 +1000 Subject: [PATCH 154/184] prettified some of the error messages --- config/locales/en.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/locales/en.yml b/config/locales/en.yml index 5380039f2..4a9658fd1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5,5 +5,10 @@ en: unauthorized: read: notification: "You must be signed in to view notifications." + create: + planting: "Please sign in or sign up to plant something." + post: "Please sign in or sign up to post." + notification: "Please sign in to send a message." + all: "You don't have permission to create a %{subject}." manage: all: "Not authorized to %{action} %{subject}." From 1b0a708b0406674f2e1aede0f7bbc656969b5029 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 10:51:55 +0100 Subject: [PATCH 155/184] Documented reason for using south_pole_member in tests. --- spec/models/member_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index d0ae3eb3d..6f6196063 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -246,6 +246,7 @@ describe 'member' do end it 'should not include staff members' do + # member needs a location in order to be included in 'interesting' scope @member1 = FactoryGirl.create(:south_pole_member) @member1.account_type = FactoryGirl.create(:staff_account_type) @member1.updated_at = 3.days.ago From 650f24099d0bcd44fa09be515183ed34464682d5 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 11:04:42 +0100 Subject: [PATCH 156/184] Added default account type of "Free" in config/applications.rb --- app/models/account.rb | 4 +++- config/application.rb | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/account.rb b/app/models/account.rb index b4a0b1d67..c0348e611 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -9,7 +9,9 @@ class Account < ActiveRecord::Base before_create do |account| unless account.account_type - account.account_type = AccountType.find_or_create_by_name("Free") + account.account_type = AccountType.find_or_create_by_name( + Growstuff::Application.config.default_account_type + ) end end diff --git a/config/application.rb b/config/application.rb index 04451a75d..0648a5322 100644 --- a/config/application.rb +++ b/config/application.rb @@ -37,6 +37,9 @@ module Growstuff # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" + # Configure a default account type + config.default_account_type = "Free" + # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] From 278db17108fab736b1873489210053fb179fcbe3 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 11:15:31 +0100 Subject: [PATCH 157/184] Removed redundant logic for free account type in apps/views A couple of pages were testing for an account type and displaying "Free" if account type was nil. Now that we've ensured every account has an account type, this is no longer necessary. --- app/views/home/index.html.haml | 2 +- app/views/members/show.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index a5b0a1bdb..fba2dfe12 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -30,7 +30,7 @@ %p Your account: %strong - = current_member.account_type || "Free" + = current_member.account_type account %br/ - if current_member == current_member && !current_member.is_paid? diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index 9f4c0d12a..6c7d64428 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -17,7 +17,7 @@ %p %strong Account type: - = @member.account_type || "Free" + = @member.account_type account - if @member == current_member && !@member.is_paid? From 9ce85e2f738ee9fdf145ba0732a1314946535c49 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 11:17:18 +0100 Subject: [PATCH 158/184] Updated tests to test for the default account type. Tests used to check that the default account type was always "Free". Now that we have a default account type as a config variable, the tests test to see the default account type matches the value of that variable. --- spec/models/member_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 6f6196063..824f78246 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -34,9 +34,10 @@ describe 'member' do @member.account.should be_an_instance_of Account end - it "should have a free account by default" do + it "should have a default-type account by default" do @member.save - @member.account.account_type.name.should eq "Free" + @member.account.account_type.name.should eq Growstuff::Application.config.default_account_type + @member.is_paid?.should be_false end From e7b0165628a25c3b831db19f6e037ac2b0a9ce61 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 11:22:47 +0100 Subject: [PATCH 159/184] Updated script/deploy-tasks.sh to include nil_account_type task. We added a rake task to ensure all account have a Free account type. This ensures the new nil_account_type rake task will be run at deploy time. --- script/deploy-tasks.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh index 347970ce4..42a7fd038 100755 --- a/script/deploy-tasks.sh +++ b/script/deploy-tasks.sh @@ -45,4 +45,5 @@ fi echo "2013-06-04 - set up shop/products/etc" rake growstuff:oneoff:setup_shop - +echo "2013-07-03 - set up Free account types for all members" +rake growstuff:oneoff:nil_account_type From 6469ed9418b6148d9a06476e3d5ac491199e4249 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Mon, 24 Jun 2013 15:50:30 +0100 Subject: [PATCH 160/184] Delete unused helpers and tests for same. --- app/helpers/account_types_helper.rb | 2 -- app/helpers/admin/orders_helper.rb | 2 -- app/helpers/admin_helper.rb | 2 -- app/helpers/order_items_helper.rb | 2 -- app/helpers/orders_helper.rb | 2 -- app/helpers/products_helper.rb | 2 -- spec/helpers/admin_helper_spec.rb | 15 --------------- 7 files changed, 27 deletions(-) delete mode 100644 app/helpers/account_types_helper.rb delete mode 100644 app/helpers/admin/orders_helper.rb delete mode 100644 app/helpers/admin_helper.rb delete mode 100644 app/helpers/order_items_helper.rb delete mode 100644 app/helpers/orders_helper.rb delete mode 100644 app/helpers/products_helper.rb delete mode 100644 spec/helpers/admin_helper_spec.rb diff --git a/app/helpers/account_types_helper.rb b/app/helpers/account_types_helper.rb deleted file mode 100644 index c56061a3a..000000000 --- a/app/helpers/account_types_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module AccountTypesHelper -end diff --git a/app/helpers/admin/orders_helper.rb b/app/helpers/admin/orders_helper.rb deleted file mode 100644 index 863374ff6..000000000 --- a/app/helpers/admin/orders_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module Admin::OrdersHelper -end diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb deleted file mode 100644 index d5c6d3555..000000000 --- a/app/helpers/admin_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module AdminHelper -end diff --git a/app/helpers/order_items_helper.rb b/app/helpers/order_items_helper.rb deleted file mode 100644 index e197528ae..000000000 --- a/app/helpers/order_items_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module OrderItemsHelper -end diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb deleted file mode 100644 index 443227fd4..000000000 --- a/app/helpers/orders_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module OrdersHelper -end diff --git a/app/helpers/products_helper.rb b/app/helpers/products_helper.rb deleted file mode 100644 index ab5c42b32..000000000 --- a/app/helpers/products_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module ProductsHelper -end diff --git a/spec/helpers/admin_helper_spec.rb b/spec/helpers/admin_helper_spec.rb deleted file mode 100644 index 3870aa933..000000000 --- a/spec/helpers/admin_helper_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the AdminHelper. For example: -# -# describe AdminHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# helper.concat_strings("this","that").should == "this that" -# end -# end -# end -describe AdminHelper do - pending "add some examples to (or delete) #{__FILE__}" -end From 80ed2179e29a74d95ba8a298795c63a95766b481 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 12:32:38 +0100 Subject: [PATCH 161/184] Proof of concept for adding a foreign exchange link to the shop. Added a function that returns a link for a foreign exchange site and showed it on the Shop page. Also included a test to make sure it works. --- app/helpers/application_helper.rb | 5 +++++ app/views/shop/index.html.haml | 2 ++ spec/views/shop/index_spec.rb | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b07e64dd2..922ebd0f7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -15,5 +15,10 @@ module ApplicationHelper return str == '' ? nil : Date.parse(str) end + def forex_link() + return link_to "See exchange rates", + "http://www.xe.com/currency/aud-australian-dollar?c=CAD", + :target => "_blank" + end end diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index c21b05f47..ff8a2c4eb 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -55,6 +55,8 @@ Pay what you want, starting at =succeed "." do =price_with_currency(p.min_price) + =succeed "." do + =forex_link() - if p.recommended_price Recommended price: =succeed "." do diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index ca5b60aba..5fff275de 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -23,6 +23,10 @@ describe 'shop/index.html.haml', :type => "view" do rendered.should contain '9.99 AUD' end + it 'should contain an exchange rate link' do + assert_select("a[href=http://www.xe.com/currency/aud-australian-dollar?c=CAD]") + end + it 'shows recommended price for products that have it' do rendered.should contain '12.00 AUD' end From 67c457dfcd94ea9eafa9936ef2d56acc74d660b3 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 12:59:49 +0100 Subject: [PATCH 162/184] Modified the foreign exchange link to convert the right price. Changed the forex site to Wolfram Alpha from XE, which allow us to convert the right number of AUD with one click. Upated tests to reflect this change. --- app/helpers/application_helper.rb | 7 +++++-- app/views/shop/index.html.haml | 2 +- spec/views/shop/index_spec.rb | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 922ebd0f7..a2e14987b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -15,10 +15,13 @@ module ApplicationHelper return str == '' ? nil : Date.parse(str) end - def forex_link() + def forex_link(price) + pid = price_in_dollars(price) + link = "http://www.wolframalpha.com/input/?i=#{pid}+aud" return link_to "See exchange rates", - "http://www.xe.com/currency/aud-australian-dollar?c=CAD", + link, :target => "_blank" end + end diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index ff8a2c4eb..802ca7fe6 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -56,7 +56,7 @@ =succeed "." do =price_with_currency(p.min_price) =succeed "." do - =forex_link() + =forex_link(p.min_price) - if p.recommended_price Recommended price: =succeed "." do diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index 5fff275de..67dcdb081 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -24,7 +24,7 @@ describe 'shop/index.html.haml', :type => "view" do end it 'should contain an exchange rate link' do - assert_select("a[href=http://www.xe.com/currency/aud-australian-dollar?c=CAD]") + assert_select("a[href=http://www.wolframalpha.com/input/?i=9.99+aud]") end it 'shows recommended price for products that have it' do From 5c92bb04d32613dc58d0e316a14ea2a2eabb1527 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 13:04:43 +0100 Subject: [PATCH 163/184] Updated the foreign exchange link to use configured currency. --- app/helpers/application_helper.rb | 3 ++- spec/views/shop/index_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a2e14987b..fcd8e1cdc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -17,7 +17,8 @@ module ApplicationHelper def forex_link(price) pid = price_in_dollars(price) - link = "http://www.wolframalpha.com/input/?i=#{pid}+aud" + currency = Growstuff::Application.config.currency + link = "http://www.wolframalpha.com/input/?i=#{pid}+#{currency}" return link_to "See exchange rates", link, :target => "_blank" diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index 67dcdb081..ced2fee99 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -24,7 +24,8 @@ describe 'shop/index.html.haml', :type => "view" do end it 'should contain an exchange rate link' do - assert_select("a[href=http://www.wolframalpha.com/input/?i=9.99+aud]") + currency = Growstuff::Application.config.currency + assert_select("a[href=http://www.wolframalpha.com/input/?i=9.99+#{currency}]") end it 'shows recommended price for products that have it' do From e674a6572edd91fef97241e0195e475ec8b5b3af Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 13:16:36 +0100 Subject: [PATCH 164/184] Added conversion links for recommended prices. The paragraphs were getting a bit verbose so we shortened the link text as well. Added an additional test to make sure the link is working here too. --- app/helpers/application_helper.rb | 2 +- app/views/shop/index.html.haml | 2 +- spec/views/shop/index_spec.rb | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fcd8e1cdc..ff8132be8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -19,7 +19,7 @@ module ApplicationHelper pid = price_in_dollars(price) currency = Growstuff::Application.config.currency link = "http://www.wolframalpha.com/input/?i=#{pid}+#{currency}" - return link_to "See exchange rates", + return link_to "(convert)", link, :target => "_blank" end diff --git a/app/views/shop/index.html.haml b/app/views/shop/index.html.haml index 802ca7fe6..d955c9579 100644 --- a/app/views/shop/index.html.haml +++ b/app/views/shop/index.html.haml @@ -55,12 +55,12 @@ Pay what you want, starting at =succeed "." do =price_with_currency(p.min_price) - =succeed "." do =forex_link(p.min_price) - if p.recommended_price Recommended price: =succeed "." do =price_with_currency(p.recommended_price) + =forex_link(p.recommended_price) %div - if can? :create, Order diff --git a/spec/views/shop/index_spec.rb b/spec/views/shop/index_spec.rb index ced2fee99..de8f011d8 100644 --- a/spec/views/shop/index_spec.rb +++ b/spec/views/shop/index_spec.rb @@ -32,6 +32,11 @@ describe 'shop/index.html.haml', :type => "view" do rendered.should contain '12.00 AUD' end + it 'should contain an exchange rate link for recommended price' do + currency = Growstuff::Application.config.currency + assert_select("a[href=http://www.wolframalpha.com/input/?i=12.00+#{currency}]") + end + it 'displays the order form' do assert_select "form", :count => 2 end From 778d7a68c3f62c23be0360b0200b75be1e004e36 Mon Sep 17 00:00:00 2001 From: Lucy <lucymclaughlin.code@gmx.com> Date: Wed, 3 Jul 2013 14:10:37 +0100 Subject: [PATCH 165/184] Fix bug in order total; add conversion link to order show. The old order total method did not account for quantities. The view test for order totals was not catching this, so we've strengthened it. We also added a conversion link to the order summary page and created an additional test to check that orders with more than one item were generating the correct total. --- app/models/order.rb | 7 ++++++- app/views/orders/show.html.haml | 1 + spec/models/order_spec.rb | 18 ++++++++++++++++-- spec/views/orders/show.html.haml_spec.rb | 7 ++++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/models/order.rb b/app/models/order.rb index 6d79878f9..651c81fb5 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -8,7 +8,12 @@ class Order < ActiveRecord::Base # total price of an order def total - order_items.to_a.sum(&:price) + sum = 0 + for i in order_items do + subtotal = i.price * i.quantity + sum += subtotal + end + return sum end # return items in the format ActiveMerchant/PayPal want them diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml index 391701df2..0176c12c0 100644 --- a/app/views/orders/show.html.haml +++ b/app/views/orders/show.html.haml @@ -51,6 +51,7 @@ %td %strong = price_with_currency(@order.total) + = forex_link(@order.total) %p - if can? :destroy, @order diff --git a/spec/models/order_spec.rb b/spec/models/order_spec.rb index ffa3e2f12..0ff666151 100644 --- a/spec/models/order_spec.rb +++ b/spec/models/order_spec.rb @@ -46,9 +46,23 @@ describe Order do # we force an order to only have one item at present. Add more if wanted # later. @order_item1 = FactoryGirl.create(:order_item, - :order_id => @order.id, :product_id => @product.id, :price => 1111) + :order_id => @order.id, :product_id => @product.id, :price => 1111, :quantity => 1) - @order.total.should eq 1111 + @order.total.should eq 1111 + end + + it "gives the correct total for quantities more than 1" do + @member = FactoryGirl.create(:member) + @order = FactoryGirl.create(:order, :member => @member) + @product = FactoryGirl.create(:product, + :min_price => 1000 + ) + # we force an order to only have one item at present. Add more if wanted + # later. + @order_item1 = FactoryGirl.create(:order_item, + :order_id => @order.id, :product_id => @product.id, :price => 1111, :quantity => 2) + + @order.total.should eq 2222 end it "formats order items for activemerchant" do diff --git a/spec/views/orders/show.html.haml_spec.rb b/spec/views/orders/show.html.haml_spec.rb index 04670b567..9032ecdea 100644 --- a/spec/views/orders/show.html.haml_spec.rb +++ b/spec/views/orders/show.html.haml_spec.rb @@ -29,7 +29,12 @@ describe "orders/show" do it "shows the total" do rendered.should contain "Total:" - rendered.should contain "198.00" + assert_select "strong", /198.00/ + end + + it "shows a foreign exchange link for the total" do + currency = Growstuff::Application.config.currency + assert_select("a[href=http://www.wolframalpha.com/input/?i=198.00+#{currency}]") end it "shows a checkout button" do From ffacf272e71e6f2b1c4210ca0bd667a1fdf47ae9 Mon Sep 17 00:00:00 2001 From: Miles Gould <miles@assyrian.org.uk> Date: Thu, 4 Jul 2013 13:46:36 +0100 Subject: [PATCH 166/184] Fix "uninitialized constant Rake::DSL" on Heroku. From http://stackoverflow.com/questions/6181312/how-to-fix-the-uninitialized-constant-rakedsl-problem-on-heroku/6263256#6263256 --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 10e405202..7e5e8eed3 100644 --- a/Rakefile +++ b/Rakefile @@ -2,6 +2,7 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. +require 'rake/dsl_definition' require File.expand_path('../config/application', __FILE__) Growstuff::Application.load_tasks From b71ac3decb343f4b38ff5827fc0dc01b62cb06aa Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 5 Jul 2013 20:53:11 +1000 Subject: [PATCH 167/184] added planted_from (eg seed, seedling) --- app/models/planting.rb | 18 +++++++- ...0705104238_add_planted_from_to_planting.rb | 5 +++ db/schema.rb | 11 ++--- spec/factories/planting.rb | 1 + spec/models/planting_spec.rb | 45 ++++++++++++++----- 5 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20130705104238_add_planted_from_to_planting.rb diff --git a/app/models/planting.rb b/app/models/planting.rb index 8bf6dda2c..8d1eb70b9 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -3,7 +3,7 @@ class Planting < ActiveRecord::Base friendly_id :planting_slug, use: :slugged attr_accessible :crop_id, :description, :garden_id, :planted_at, - :quantity, :sunniness + :quantity, :sunniness, :planted_from belongs_to :garden belongs_to :crop @@ -29,6 +29,22 @@ class Planting < ActiveRecord::Base :allow_nil => true, :allow_blank => true + PLANTED_FROM_VALUES = [ + 'seed', + 'seedling', + 'cutting', + 'root division', + 'runner', + 'bare root plant', + 'advanced plant', + 'graft', + 'layering' + ] + validates :planted_from, :inclusion => { :in => PLANTED_FROM_VALUES, + :message => "%{value} is not a valid planting method" }, + :allow_nil => true, + :allow_blank => true + def planting_slug "#{owner.login_name}-#{garden}-#{crop}".downcase.gsub(' ', '-') end diff --git a/db/migrate/20130705104238_add_planted_from_to_planting.rb b/db/migrate/20130705104238_add_planted_from_to_planting.rb new file mode 100644 index 000000000..7eb362a85 --- /dev/null +++ b/db/migrate/20130705104238_add_planted_from_to_planting.rb @@ -0,0 +1,5 @@ +class AddPlantedFromToPlanting < ActiveRecord::Migration + def change + add_column :plantings, :planted_from, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index aab2827b1..bf9e10438 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130606233733) do +ActiveRecord::Schema.define(:version => 20130705104238) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -179,15 +179,16 @@ ActiveRecord::Schema.define(:version => 20130606233733) do end create_table "plantings", :force => true do |t| - t.integer "garden_id", :null => false - t.integer "crop_id", :null => false + t.integer "garden_id", :null => false + t.integer "crop_id", :null => false t.date "planted_at" t.integer "quantity" t.text "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "slug" t.string "sunniness" + t.string "planted_from" end add_index "plantings", ["slug"], :name => "index_plantings_on_slug", :unique => true diff --git a/spec/factories/planting.rb b/spec/factories/planting.rb index 06499b4a4..963b7a2b0 100644 --- a/spec/factories/planting.rb +++ b/spec/factories/planting.rb @@ -6,5 +6,6 @@ FactoryGirl.define do quantity 33 description "This is a *really* good plant." sunniness 'sun' + planted_from 'seed' end end diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index cfadcf9cd..ca2d4e02d 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -49,21 +49,44 @@ describe Planting do end end - it 'should have a sunniness value' do - @planting.sunniness.should eq 'sun' - end + context 'sunniness' do + it 'should have a sunniness value' do + @planting.sunniness.should eq 'sun' + end - it 'all three valid sunniness values should work' do - ['sun', 'shade', 'semi-shade', nil, ''].each do |s| - @planting = FactoryGirl.build(:planting, :sunniness => s) - @planting.should be_valid + it 'all three valid sunniness values should work' do + ['sun', 'shade', 'semi-shade', nil, ''].each do |s| + @planting = FactoryGirl.build(:planting, :sunniness => s) + @planting.should be_valid + end + end + + it 'should refuse invalid sunniness values' do + @planting = FactoryGirl.build(:planting, :sunniness => 'not valid') + @planting.should_not be_valid + @planting.errors[:sunniness].should include("not valid is not a valid sunniness value") end end - it 'should refuse invalid sunniness values' do - @planting = FactoryGirl.build(:planting, :sunniness => 'not valid') - @planting.should_not be_valid - @planting.errors[:sunniness].should include("not valid is not a valid sunniness value") + context 'planted from' do + it 'should have a planted_from value' do + @planting.planted_from.should eq 'seed' + end + + it 'all valid planted_from values should work' do + ['seed', 'seedling', 'cutting', 'root division', + 'runner', 'bare root plant', 'advanced plant', + 'graft', 'layering', nil, ''].each do |p| + @planting = FactoryGirl.build(:planting, :planted_from => p) + @planting.should be_valid + end + end + + it 'should refuse invalid planted_from values' do + @planting = FactoryGirl.build(:planting, :planted_from => 'not valid') + @planting.should_not be_valid + @planting.errors[:planted_from].should include("not valid is not a valid planting method") + end end # we decided that all the tests for the planting/photo association would From 1689d7fdb130583505c5104c782840d27036519a Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 5 Jul 2013 20:57:14 +1000 Subject: [PATCH 168/184] added planted_from to planting form --- app/views/plantings/_form.html.haml | 4 ++++ spec/views/plantings/edit.html.haml_spec.rb | 2 ++ spec/views/plantings/new.html.haml_spec.rb | 1 + 3 files changed, 7 insertions(+) diff --git a/app/views/plantings/_form.html.haml b/app/views/plantings/_form.html.haml index b21add0c9..29709d153 100644 --- a/app/views/plantings/_form.html.haml +++ b/app/views/plantings/_form.html.haml @@ -20,6 +20,10 @@ = f.label 'How many?', :class => 'control-label' .controls = f.number_field :quantity, :class => 'input-small' + .control-group + = f.label 'Planted from:', :class => 'control-label' + .controls + = f.select(:planted_from, Planting::PLANTED_FROM_VALUES, {:include_blank => true}) .control-group = f.label 'Sun or shade?', :class => 'control-label' .controls diff --git a/spec/views/plantings/edit.html.haml_spec.rb b/spec/views/plantings/edit.html.haml_spec.rb index 51f993549..3965c0e03 100644 --- a/spec/views/plantings/edit.html.haml_spec.rb +++ b/spec/views/plantings/edit.html.haml_spec.rb @@ -33,6 +33,8 @@ describe "plantings/edit" do assert_select "form", :action => plantings_path(@planting), :method => "post" do assert_select "input#planting_quantity", :name => "planting[quantity]" assert_select "textarea#planting_description", :name => "planting[description]" + assert_select "select#planting_sunniness", :name => "planting[sunniness]" + assert_select "select#planting_planted_from", :name => "planting[planted_from]" end end diff --git a/spec/views/plantings/new.html.haml_spec.rb b/spec/views/plantings/new.html.haml_spec.rb index a07273297..03aa8fe7f 100644 --- a/spec/views/plantings/new.html.haml_spec.rb +++ b/spec/views/plantings/new.html.haml_spec.rb @@ -34,6 +34,7 @@ describe "plantings/new" do assert_select "input#planting_quantity", :name => "planting[quantity]" assert_select "textarea#planting_description", :name => "planting[description]" assert_select "select#planting_sunniness", :name => "planting[sunniness]" + assert_select "select#planting_planted_from", :name => "planting[planted_from]" end end From 902b82a29544e1717ad2020c688a67c1a6f68521 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 5 Jul 2013 21:09:21 +1000 Subject: [PATCH 169/184] display planted_from on planting page --- app/views/plantings/show.html.haml | 5 ++ spec/views/plantings/show.html.haml_spec.rb | 69 ++++++++++++--------- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index 9d33ecf17..b04d90244 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -15,6 +15,11 @@ %b Quantity: = @planting.quantity != 0 ? @planting.quantity : "not specified" + - if ! @planting.planted_from.blank? + %p + %b Planted from: + = @planting.planted_from + - if ! @planting.sunniness.blank? %p %b Sun or shade? diff --git a/spec/views/plantings/show.html.haml_spec.rb b/spec/views/plantings/show.html.haml_spec.rb index 71c6c94d1..ddd8db657 100644 --- a/spec/views/plantings/show.html.haml_spec.rb +++ b/spec/views/plantings/show.html.haml_spec.rb @@ -9,30 +9,46 @@ describe "plantings/show" do ) end - it "shows the sunniness" do - controller.stub(:current_user) { nil } - @member = FactoryGirl.create(:member) - create_planting_for(@member) - render - rendered.should contain 'Sun or shade?' - rendered.should contain 'sun' - end - - it "doesn't show sunniness if blank" do - controller.stub(:current_user) { nil } - @member = FactoryGirl.create(:member) - @p = create_planting_for(@member) - @p.sunniness = '' - @p.save - render - rendered.should_not contain 'Sun or shade?' - rendered.should_not contain 'sun' - end - - it "shows photos" do + before (:each) do @member = FactoryGirl.create(:member) controller.stub(:current_user) { @member } @p = create_planting_for(@member) + end + + context 'sunniness' do + + it "shows the sunniness" do + render + rendered.should contain 'Sun or shade?' + rendered.should contain 'sun' + end + + it "doesn't show sunniness if blank" do + @p.sunniness = '' + @p.save + render + rendered.should_not contain 'Sun or shade?' + rendered.should_not contain 'sun' + end + end + + context 'planted from' do + it "shows planted_from" do + render + rendered.should contain 'Planted from:' + rendered.should contain 'seed' + end + + it "doesn't show planted_from if blank" do + @p.planted_from = '' + @p.save + render + rendered.should_not contain 'Planted from:' + rendered.should_not contain 'seed' + end + end + + it "shows photos" do @photo = FactoryGirl.create(:photo, :owner => @member) @p.photos << @photo render @@ -40,18 +56,12 @@ describe "plantings/show" do end it "shows a link to add photos" do - @member = FactoryGirl.create(:member) - controller.stub(:current_user) { @member } - @p = create_planting_for(@member) render rendered.should contain "Add photo" end context "no location set" do before(:each) do - controller.stub(:current_user) { nil } - @member = FactoryGirl.create(:member) - create_planting_for(@member) render end @@ -74,9 +84,8 @@ describe "plantings/show" do context "location set" do before(:each) do - controller.stub(:current_user) { nil } - @member = FactoryGirl.create(:london_member) - create_planting_for(@member) + @member.location = 'Greenwich, UK' + @member.save render end From 8bee9fcd1be1c489e31f7090a7bb58da75b18691 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 5 Jul 2013 21:11:07 +1000 Subject: [PATCH 170/184] use blank instead of != 0 for qty --- app/views/plantings/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index b04d90244..203325a09 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -13,7 +13,7 @@ = "(#{@planting.owner.location})" %p %b Quantity: - = @planting.quantity != 0 ? @planting.quantity : "not specified" + = @planting.quantity.blank? ? "not specified" : @planting.quantity - if ! @planting.planted_from.blank? %p From db16ad34f7c117b5b4a0602fa7f3dfaf78434573 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Fri, 5 Jul 2013 22:07:55 +1000 Subject: [PATCH 171/184] view/select older flickr photos --- app/controllers/photos_controller.rb | 9 ++++++++- app/models/member.rb | 8 ++++++-- app/views/photos/new.html.haml | 6 +++++- spec/views/photos/new.html.haml_spec.rb | 8 +++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb index f9d6bf4c5..75ce5297e 100644 --- a/app/controllers/photos_controller.rb +++ b/app/controllers/photos_controller.rb @@ -28,9 +28,16 @@ class PhotosController < ApplicationController @photo = Photo.new @planting_id = params[:planting_id] + page = params[:page] || 1 + @flickr_auth = current_member.auth('flickr') if @flickr_auth - @photos = current_member.flickr_photos + photos = current_member.flickr_photos(page) + total = photos.instance_of?(FlickRaw::ResponseList) ? photos.total : 0 + + @photos = WillPaginate::Collection.create(page, 30, total) do |pager| + pager.replace photos.to_a + end end respond_to do |format| diff --git a/app/models/member.rb b/app/models/member.rb index 993f2e96c..19aff56ba 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -150,8 +150,12 @@ class Member < ActiveRecord::Base return @flickr end - def flickr_photos - return flickr.people.getPhotos(:user_id => 'me', :per_page => 30) + def flickr_photos(page_num=1) + return flickr.people.getPhotos( + :user_id => 'me', + :page => page_num, + :per_page => 30 + ) end protected diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml index ca363dc26..811bba956 100644 --- a/app/views/photos/new.html.haml +++ b/app/views/photos/new.html.haml @@ -5,8 +5,12 @@ Connected to Flickr as = succeed "." do = link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}" + Please select a photo from your recent uploads. - %p Select a photo from your recent uploads: + + %div.pagination + = page_entries_info @photos, :model => "photos" + = will_paginate @photos - c = 0 %ul.thumbnails diff --git a/spec/views/photos/new.html.haml_spec.rb b/spec/views/photos/new.html.haml_spec.rb index 1ea5a34d9..9eb15eab7 100644 --- a/spec/views/photos/new.html.haml_spec.rb +++ b/spec/views/photos/new.html.haml_spec.rb @@ -4,7 +4,13 @@ describe "photos/new" do before(:each) do @member = FactoryGirl.create(:member) controller.stub(:current_user) { @member } - assign(:photos, []) + page = 1 + per_page = 2 + total_entries = 2 + photos = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + pager.replace([]) + end + assign(:photos, photos) assign(:flickr_auth, FactoryGirl.create(:flickr_authentication, :member => @member)) end From c17ef8e566f910859e124658dd06d449f55b1143 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 10:59:31 +1000 Subject: [PATCH 172/184] attempt to get travis to test on psql not sqlite --- .travis.yml | 15 +++++++++------ config/database.yml | 6 ++++++ config/environments/travis.rb | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) create mode 120000 config/environments/travis.rb diff --git a/.travis.yml b/.travis.yml index 3154bb568..03dbc4735 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,14 @@ --- language: ruby bundler_args: --without development assets +env: RAILS_ENV=travis rvm: -- 1.9.3 + - 1.9.3 +before_script: + - psql -c 'create database growstuff_test;' -U postgres script: -- bundle exec rake db:migrate --trace -- bundle exec rspec spec/ + - bundle exec rake db:migrate --trace + - bundle exec rspec spec/ # after_success: # - if [[ "$TRAVIS_BRANCH" == "dev" ]]; then git remote add heroku git@heroku.com:growstuff-dev.git @@ -21,6 +24,6 @@ script: # - heroku restart # - fi -env: - global: - secure: "QFQbCdNGyjeatp/H0j0y0oGiue45fpG2w6eA2QAbq2RmvhabgXbd5WIobN90\ndrae3S7TRxPDpMpus90icykX6EzOTLXCEvaC4rh9pCcRktj3SZqq5b9rVTvs\n1MvlS6HhtsVqsrKjQUb0WmPpnganIzTs0RtGaQspo2joPJO18A4=" +# env: +# global: +# secure: "QFQbCdNGyjeatp/H0j0y0oGiue45fpG2w6eA2QAbq2RmvhabgXbd5WIobN90\ndrae3S7TRxPDpMpus90icykX6EzOTLXCEvaC4rh9pCcRktj3SZqq5b9rVTvs\n1MvlS6HhtsVqsrKjQUb0WmPpnganIzTs0RtGaQspo2joPJO18A4=" diff --git a/config/database.yml b/config/database.yml index ebdfe92a9..c1d97ad03 100644 --- a/config/database.yml +++ b/config/database.yml @@ -10,6 +10,12 @@ test: pool: 5 timeout: 5000 +# should be identical to test apart from running on postgres +travis: + adapter: postgresql + database: myapp_test + username: postgres + production: adapter: postgresql database: growstuff_prod diff --git a/config/environments/travis.rb b/config/environments/travis.rb new file mode 120000 index 000000000..2e82190dd --- /dev/null +++ b/config/environments/travis.rb @@ -0,0 +1 @@ +test.rb \ No newline at end of file From 8314851a8e667b18c6348684a47264c070997da0 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 11:05:46 +1000 Subject: [PATCH 173/184] fixed c&p error in database.yml --- config/database.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/database.yml b/config/database.yml index c1d97ad03..8dd0a9fba 100644 --- a/config/database.yml +++ b/config/database.yml @@ -13,7 +13,7 @@ test: # should be identical to test apart from running on postgres travis: adapter: postgresql - database: myapp_test + database: growstuff_test username: postgres production: From 68923d49fe3e91bbf5e036e1c69ed8ccd42f28ba Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 11:15:47 +1000 Subject: [PATCH 174/184] load test gems in travis environment too --- Gemfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index ff04a29c3..04f2ea9b9 100644 --- a/Gemfile +++ b/Gemfile @@ -99,6 +99,9 @@ gem 'rake', '>= 10.0.0' group :development, :test do gem 'sqlite3' # database engine +end + +group :development, :test, :travis do gem 'haml-rails' # HTML templating language gem 'rspec-rails', '~> 2.12.1' # unit testing framework gem 'webrat' # provides HTML matchers for view tests From 706881dbdb413ae0b5bd9355f048aeb4e814316a Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 11:30:17 +1000 Subject: [PATCH 175/184] set up test users in travis environment --- db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index f307bb94a..1c4811fd7 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -18,7 +18,7 @@ CSV.foreach(Rails.root.join('db', 'seeds', 'crops.csv')) do |row| end puts "Finished loading crops" -if Rails.env.development? or Rails.env.test? +if Rails.env.development? or Rails.env.test? or Rails.env.travis? puts "Loading test users..." (1..3).each do |i| @user = Member.create( From 878af074194f6774c45236abffbfd6b2fb177b4c Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 11:54:55 +1000 Subject: [PATCH 176/184] gave up on not_staff, shuffling interesting members instead --- app/controllers/home_controller.rb | 3 ++- app/models/member.rb | 4 +--- spec/models/member_spec.rb | 26 -------------------------- 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 389c7dfa2..7ad499d9a 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -7,7 +7,8 @@ class HomeController < ApplicationController @planting_count = Planting.count @garden_count = Garden.count - @interesting_members = Member.interesting.limit(6) + # choose 6 recently-signed-in members sort of at random + @interesting_members = Member.interesting.limit(30).shuffle.first(6) # customise what we show on the homepage based on whether you're # logged in or not. diff --git a/app/models/member.rb b/app/models/member.rb index 993f2e96c..f29010ad1 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -26,12 +26,10 @@ class Member < ActiveRecord::Base scope :confirmed, where('confirmed_at IS NOT NULL') scope :located, where('location IS NOT NULL') scope :recently_signed_in, reorder('updated_at DESC') - scope :not_staff, joins(:account => :account_type). - where('account_types.name != "Staff"') # this is used on the signed-out homepage so we're basically # just trying to select some members who look good. - scope :interesting, confirmed.located.recently_signed_in.not_staff + scope :interesting, confirmed.located.recently_signed_in # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 824f78246..df646040f 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -246,32 +246,6 @@ describe 'member' do end - it 'should not include staff members' do - # member needs a location in order to be included in 'interesting' scope - @member1 = FactoryGirl.create(:south_pole_member) - @member1.account_type = FactoryGirl.create(:staff_account_type) - @member1.updated_at = 3.days.ago - Member.interesting.should eq [] - end - end - - context 'not_staff scope' do - it 'should not include staff members' do - @member1 = FactoryGirl.create(:member) - @member1.account_type = FactoryGirl.create(:staff_account_type) - Member.not_staff.should_not include @member1 - end - - it 'should include free members' do - @member1 = FactoryGirl.create(:member) - Member.not_staff.should include @member1 - end - - it 'should_include paid members' do - @member1 = FactoryGirl.create(:member) - @member1.account_type = FactoryGirl.create(:paid_account_type) - Member.not_staff.should include @member1 - end end context 'orders' do From 05088373032699f942321d2f5b12a12a300d0d56 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 12:00:45 +1000 Subject: [PATCH 177/184] Can't assume we have a member with id = 1 this works on sqlite but not on postgres (which we now test on, on travis) --- spec/controllers/comments_controller_spec.rb | 3 ++- spec/controllers/gardens_controller_spec.rb | 4 +++- spec/controllers/photos_controller_spec.rb | 4 +++- spec/controllers/posts_controller_spec.rb | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index 128bcdd4e..bf0d5da87 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -6,7 +6,8 @@ describe CommentsController do def valid_attributes @post = FactoryGirl.create(:post) - { :post_id => @post.id, :author_id => 1, :body => "some text" } + @member = FactoryGirl.create(:member) + { :post_id => @post.id, :author_id => @member.id, :body => "some text" } end describe "GET index" do diff --git a/spec/controllers/gardens_controller_spec.rb b/spec/controllers/gardens_controller_spec.rb index d96bc2baf..654cb4665 100644 --- a/spec/controllers/gardens_controller_spec.rb +++ b/spec/controllers/gardens_controller_spec.rb @@ -4,8 +4,10 @@ describe GardensController do login_member + @member = FactoryGirl.create(:member) + def valid_attributes - {:name => 'My Garden', :owner_id => 1 } + {:name => 'My Garden', :owner_id => @member.id } end describe "GET index" do diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index d31edabc4..b496fe2a7 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -4,9 +4,11 @@ describe PhotosController do login_member + @member = FactoryGirl.create(:member) + def valid_attributes { - "owner_id" => "1", + "owner_id" => @member.id, "flickr_photo_id" => 1, "title" => "Photo", "license_name" => "CC-BY", diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index bdae1ce48..a3de27dde 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -4,8 +4,10 @@ describe PostsController do login_member + @member = FactoryGirl.create(:member) + def valid_attributes - { :author_id => 1, :subject => "blah", :body => "blah blah" } + { :author_id => @member.id, :subject => "blah", :body => "blah blah" } end describe "GET index" do From ec80b01c7a8b63418dfe27786515786d30159122 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 12:18:51 +1000 Subject: [PATCH 178/184] cleaning up tests on postgres - still trying to make sure we explicitly use a real member, not just assume there's one with id = 1 - also it looks like the tests for post activity are passing for obscure reasons on sqlite when they shouldn't be. this is a known bug (https://www.pivotaltracker.com/story/show/51280861) apparently invisible to us under sqlite. --- app/models/post.rb | 2 +- spec/controllers/gardens_controller_spec.rb | 5 ++--- spec/controllers/photos_controller_spec.rb | 4 ++-- spec/controllers/posts_controller_spec.rb | 5 ++--- spec/models/forum_spec.rb | 4 ++-- spec/models/post_spec.rb | 16 ++++++++++------ 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/app/models/post.rb b/app/models/post.rb index 6622969c7..2666e0956 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -26,7 +26,7 @@ class Post < ActiveRecord::Base end def recent_activity - self.comments.last ? self.comments.last.created_at : self.created_at + self.comments.reorder.last ? self.comments.reorder.last.created_at : self.created_at end def Post.recently_active diff --git a/spec/controllers/gardens_controller_spec.rb b/spec/controllers/gardens_controller_spec.rb index 654cb4665..ac64c28e7 100644 --- a/spec/controllers/gardens_controller_spec.rb +++ b/spec/controllers/gardens_controller_spec.rb @@ -4,10 +4,9 @@ describe GardensController do login_member - @member = FactoryGirl.create(:member) - def valid_attributes - {:name => 'My Garden', :owner_id => @member.id } + member = FactoryGirl.create(:member) + {:name => 'My Garden', :owner_id => member.id } end describe "GET index" do diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index b496fe2a7..59325c302 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -4,11 +4,11 @@ describe PhotosController do login_member - @member = FactoryGirl.create(:member) def valid_attributes + member = FactoryGirl.create(:member) { - "owner_id" => @member.id, + "owner_id" => member.id, "flickr_photo_id" => 1, "title" => "Photo", "license_name" => "CC-BY", diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index a3de27dde..cb26599c4 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -4,10 +4,9 @@ describe PostsController do login_member - @member = FactoryGirl.create(:member) - def valid_attributes - { :author_id => @member.id, :subject => "blah", :body => "blah blah" } + member = FactoryGirl.create(:member) + { :author_id => member.id, :subject => "blah", :body => "blah blah" } end describe "GET index" do diff --git a/spec/models/forum_spec.rb b/spec/models/forum_spec.rb index 6cfe3ea65..98ce1c24a 100644 --- a/spec/models/forum_spec.rb +++ b/spec/models/forum_spec.rb @@ -24,8 +24,8 @@ describe Forum do end it "orders posts in reverse chron order" do - @post1 = FactoryGirl.create(:forum_post, :forum => @forum) - @post2 = FactoryGirl.create(:forum_post, :forum => @forum) + @post1 = FactoryGirl.create(:forum_post, :forum => @forum, :created_at => 2.days.ago) + @post2 = FactoryGirl.create(:forum_post, :forum => @forum, :created_at => 1.day.ago) @forum.posts.first.should eq @post2 end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 8e7f91083..ea278ea39 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -52,7 +52,7 @@ describe Post do @post = FactoryGirl.build(:post, :subject => "") @post.should_not be_valid end - + it "doesn't allow a subject with only spaces" do @post = FactoryGirl.build(:post, :subject => " ") @post.should_not be_valid @@ -61,7 +61,7 @@ describe Post do context "recent activity" do before(:each) do Time.stub(:now => Time.now) - @post = FactoryGirl.create(:post) + @post = FactoryGirl.create(:post, :created_at => 1.day.ago) end it "sets recent activity to post time" do @@ -69,16 +69,20 @@ describe Post do end it "sets recent activity to comment time" do - @comment = FactoryGirl.create(:comment, :post => @post) + @comment = FactoryGirl.create(:comment, :post => @post, + :created_at => 1.hour.ago) @post.recent_activity.to_i.should eq @comment.created_at.to_i end - it "sorts recently active" do + it "shiny new post is recently active" do # create a shiny new post - @post2 = FactoryGirl.create(:post) + @post2 = FactoryGirl.create(:post, :created_at => 1.minute.ago) Post.recently_active.first.should eq @post2 + end + + it "new comment on old post is recently active" do # now comment on an older post - @comment = FactoryGirl.create(:comment, :post => @post) + @comment2 = FactoryGirl.create(:comment, :post => @post, :created_at => 1.second.ago) Post.recently_active.first.should eq @post end end From 1ff2211c0f56f10eb1da8030508c3dbcafdfe273 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 13:09:37 +1000 Subject: [PATCH 179/184] removed boring tests from controllers the tests generated by 'rails g scaffold...' are boring and brittle. they don't actually test anything other than the rails framework, and they were causing us all kinds of trouble. we've started to blow them away (and raised a PT chore to remove them from other controllers in due course). --- spec/controllers/gardens_controller_spec.rb | 127 ------------------ spec/controllers/photos_controller_spec.rb | 122 +---------------- spec/controllers/posts_controller_spec.rb | 138 -------------------- spec/models/member_spec.rb | 2 +- 4 files changed, 2 insertions(+), 387 deletions(-) diff --git a/spec/controllers/gardens_controller_spec.rb b/spec/controllers/gardens_controller_spec.rb index ac64c28e7..242ec6ffa 100644 --- a/spec/controllers/gardens_controller_spec.rb +++ b/spec/controllers/gardens_controller_spec.rb @@ -9,131 +9,4 @@ describe GardensController do {:name => 'My Garden', :owner_id => member.id } end - describe "GET index" do - it "assigns all gardens as @gardens" do - gardens = Garden.all - get :index, {} - assigns(:gardens).should eq(gardens) - end - end - - describe "GET show" do - it "assigns the requested garden as @garden" do - garden = Garden.create! valid_attributes - get :show, {:id => garden.to_param} - assigns(:garden).should eq(garden) - end - end - - describe "GET new" do - it "assigns a new garden as @garden" do - get :new, {} - assigns(:garden).should be_a_new(Garden) - end - end - - describe "GET edit" do - it "assigns the requested garden as @garden" do - garden = Garden.create! valid_attributes - get :edit, {:id => garden.to_param} - assigns(:garden).should eq(garden) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Garden" do - expect { - post :create, {:garden => valid_attributes} - }.to change(Garden, :count).by(1) - end - - it "assigns a newly created garden as @garden" do - post :create, {:garden => valid_attributes} - assigns(:garden).should be_a(Garden) - assigns(:garden).should be_persisted - end - - it "redirects to the created garden" do - post :create, {:garden => valid_attributes} - response.should redirect_to(Garden.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved garden as @garden" do - # Trigger the behavior that occurs when invalid params are submitted - Garden.any_instance.stub(:save).and_return(false) - post :create, {:garden => {}} - assigns(:garden).should be_a_new(Garden) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Garden.any_instance.stub(:save).and_return(false) - post :create, {:garden => {}} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested garden" do - garden = Garden.create! valid_attributes - # Assuming there are no other gardens in the database, this - # specifies that the Garden created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Garden.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) - put :update, {:id => garden.to_param, :garden => {'these' => 'params'}} - end - - it "assigns the requested garden as @garden" do - garden = Garden.create! valid_attributes - put :update, {:id => garden.to_param, :garden => valid_attributes} - assigns(:garden).should eq(garden) - end - - it "redirects to the garden" do - garden = Garden.create! valid_attributes - put :update, {:id => garden.to_param, :garden => valid_attributes} - response.should redirect_to(garden) - end - end - - describe "with invalid params" do - it "assigns the garden as @garden" do - garden = Garden.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Garden.any_instance.stub(:save).and_return(false) - put :update, {:id => garden.to_param, :garden => {}} - assigns(:garden).should eq(garden) - end - - it "re-renders the 'edit' template" do - garden = Garden.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Garden.any_instance.stub(:save).and_return(false) - put :update, {:id => garden.to_param, :garden => {}} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested garden" do - garden = Garden.create! valid_attributes - expect { - delete :destroy, {:id => garden.to_param} - }.to change(Garden, :count).by(-1) - end - - it "redirects to the gardens list" do - garden = Garden.create! valid_attributes - delete :destroy, {:id => garden.to_param} - response.should redirect_to(garden.owner) - end - end - end diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb index 59325c302..cbf17e2f6 100644 --- a/spec/controllers/photos_controller_spec.rb +++ b/spec/controllers/photos_controller_spec.rb @@ -22,22 +22,6 @@ describe PhotosController do {} end - describe "GET index" do - it "assigns all photos as @photos" do - photo = Photo.create! valid_attributes - get :index, {} - assigns(:photos).should eq([photo]) - end - end - - describe "GET show" do - it "assigns the requested photo as @photo" do - photo = Photo.create! valid_attributes - get :show, {:id => photo.to_param} - assigns(:photo).should eq(photo) - end - end - describe "GET new" do it "assigns the flickr auth as @flickr_auth" do @member = FactoryGirl.create(:member) @@ -49,11 +33,6 @@ describe PhotosController do assigns(:flickr_auth).should be_an_instance_of(Authentication) end - it "assigns a new photo as @photo" do - get :new, {} - assigns(:photo).should be_a_new(Photo) - end - it "assigns a planting id" do get :new, { :planting_id => 5 } assigns(:planting_id).should eq "5" @@ -61,14 +40,6 @@ describe PhotosController do end - describe "GET edit" do - it "assigns the requested photo as @photo" do - photo = Photo.create! valid_attributes - get :edit, {:id => photo.to_param} - assigns(:photo).should eq(photo) - end - end - describe "POST create" do before(:each) do Photo.any_instance.stub(:flickr_metadata).and_return( { @@ -80,23 +51,8 @@ describe PhotosController do :link_url => "http://example.com" }) end + describe "with valid params" do - it "creates a new Photo" do - expect { - post :create, {:photo => { :flickr_photo_id => 1 } } - }.to change(Photo, :count).by(1) - end - - it "assigns a newly created photo as @photo" do - post :create, {:photo => { :flickr_photo_id => 1 } } - assigns(:photo).should be_a(Photo) - assigns(:photo).should be_persisted - end - - it "redirects to the created photo" do - post :create, {:photo => { :flickr_photo_id => 1 } } - response.should redirect_to(Photo.last) - end it "attaches the photo to a planting" do member = FactoryGirl.create(:member) @@ -157,81 +113,5 @@ describe PhotosController do Photo.last.plantings.first.should_not eq planting end end - - describe "with invalid params" do - it "assigns a newly created but unsaved photo as @photo" do - # Trigger the behavior that occurs when invalid params are submitted - Photo.any_instance.stub(:save).and_return(false) - post :create, {:photo => { "owner_id" => "invalid value" }} - assigns(:photo).should be_a_new(Photo) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Photo.any_instance.stub(:save).and_return(false) - post :create, {:photo => { "owner_id" => "invalid value" }} - response.should render_template("new") - end - end end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested photo" do - photo = Photo.create! valid_attributes - # Assuming there are no other photos in the database, this - # specifies that the Photo created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Photo.any_instance.should_receive(:update_attributes).with({ "owner_id" => "1" }) - put :update, {:id => photo.to_param, :photo => { "owner_id" => "1" }} - end - - it "assigns the requested photo as @photo" do - photo = Photo.create! valid_attributes - put :update, {:id => photo.to_param, :photo => valid_attributes} - assigns(:photo).should eq(photo) - end - - it "redirects to the photo" do - photo = Photo.create! valid_attributes - put :update, {:id => photo.to_param, :photo => valid_attributes} - response.should redirect_to(photo) - end - end - - describe "with invalid params" do - it "assigns the photo as @photo" do - photo = Photo.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Photo.any_instance.stub(:save).and_return(false) - put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }} - assigns(:photo).should eq(photo) - end - - it "re-renders the 'edit' template" do - photo = Photo.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Photo.any_instance.stub(:save).and_return(false) - put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested photo" do - photo = Photo.create! valid_attributes - expect { - delete :destroy, {:id => photo.to_param} - }.to change(Photo, :count).by(-1) - end - - it "redirects to the photos list" do - photo = Photo.create! valid_attributes - delete :destroy, {:id => photo.to_param} - response.should redirect_to(photos_url) - end - end - end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index cb26599c4..bbb8bdccb 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -9,14 +9,6 @@ describe PostsController do { :author_id => member.id, :subject => "blah", :body => "blah blah" } end - describe "GET index" do - it "assigns all posts as @posts" do - post = Post.create! valid_attributes - get :index, {} - assigns(:posts).should eq([post]) - end - end - describe "GET RSS feed" do it "returns an RSS feed" do get :index, :format => "rss" @@ -26,14 +18,6 @@ describe PostsController do end end - describe "GET show" do - it "assigns the requested post as @post" do - post = Post.create! valid_attributes - get :show, {:id => post.slug} - assigns(:post).should eq(post) - end - end - describe "GET RSS feed for individual post" do it "returns an RSS feed" do post = Post.create! valid_attributes @@ -44,126 +28,4 @@ describe PostsController do end end - describe "GET new" do - it "assigns a new post as @post" do - get :new, {} - assigns(:post).should be_a_new(Post) - end - - it "picks up forum from params" do - forum = FactoryGirl.create(:forum) - get :new, {:forum_id => forum.id} - assigns(:forum).should eq(forum) - end - - it "doesn't die if no forum specified" do - get :new, {} - assigns(:forum).should eq nil - end - end - - describe "GET edit" do - it "assigns the requested post as @post" do - post = Post.create! valid_attributes - get :edit, {:id => post.slug} - assigns(:post).should eq(post) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Post" do - expect { - post :create, {:post => valid_attributes} - }.to change(Post, :count).by(1) - end - - it "assigns a newly created post as @post" do - post :create, {:post => valid_attributes} - assigns(:post).should be_a(Post) - assigns(:post).should be_persisted - end - - it "redirects to the created post" do - post :create, {:post => valid_attributes} - response.should redirect_to(Post.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved post as @post" do - # Trigger the behavior that occurs when invalid params are submitted - Post.any_instance.stub(:save).and_return(false) - post :create, {:post => {}} - assigns(:post).should be_a_new(Post) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Post.any_instance.stub(:save).and_return(false) - post :create, {:post => {}} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested post" do - post = Post.create! valid_attributes - # Assuming there are no other posts in the database, this - # specifies that the Post created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Post.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) - put :update, {:id => post.slug, :post => {'these' => 'params'}} - end - - it "assigns the requested post as @post" do - post = Post.create! valid_attributes - put :update, {:id => post.slug, :post => valid_attributes} - assigns(:post).should eq(post) - end - - it "redirects to the post" do - post = Post.create! valid_attributes - put :update, {:id => post.slug, :post => valid_attributes} - response.should redirect_to(post) - end - end - - describe "with invalid params" do - it "assigns the post as @post" do - post = Post.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Post.any_instance.stub(:save).and_return(false) - put :update, {:id => post.slug, :post => {}} - assigns(:post).should eq(post) - end - - it "re-renders the 'edit' template" do - post = Post.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Post.any_instance.stub(:save).and_return(false) - put :update, {:id => post.slug, :post => {}} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested post" do - post = Post.create! valid_attributes - expect { - delete :destroy, {:id => post.slug} - }.to change(Post, :count).by(-1) - end - - it "redirects to the posts list" do - post = Post.create! valid_attributes - delete :destroy, {:id => post.slug} - response.should redirect_to(posts_url) - end - end - end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index df646040f..86f1cece1 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -37,7 +37,7 @@ describe 'member' do it "should have a default-type account by default" do @member.save @member.account.account_type.name.should eq Growstuff::Application.config.default_account_type - + @member.is_paid?.should be_false end From 923f01cd72f22e8a63686b2cce18f6af44a28500 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 13:24:26 +1000 Subject: [PATCH 180/184] cleaned up boring controller tests --- .../account_types_controller_spec.rb | 129 --------------- spec/controllers/accounts_controller_spec.rb | 75 +-------- .../authentications_controller_spec.rb | 59 ------- spec/controllers/comments_controller_spec.rb | 108 +------------ spec/controllers/crops_controller_spec.rb | 127 --------------- spec/controllers/forums_controller_spec.rb | 127 --------------- spec/controllers/member_controller_spec.rb | 4 - .../notifications_controller_spec.rb | 47 ------ .../order_items_controller_spec.rb | 73 ++------- spec/controllers/orders_controller_spec.rb | 31 +--- spec/controllers/plantings_controller_spec.rb | 125 --------------- spec/controllers/products_controller_spec.rb | 127 --------------- spec/controllers/roles_controller_spec.rb | 119 -------------- .../scientific_names_controller_spec.rb | 147 ------------------ 14 files changed, 20 insertions(+), 1278 deletions(-) diff --git a/spec/controllers/account_types_controller_spec.rb b/spec/controllers/account_types_controller_spec.rb index 7067fbd26..6beee69b6 100644 --- a/spec/controllers/account_types_controller_spec.rb +++ b/spec/controllers/account_types_controller_spec.rb @@ -9,133 +9,4 @@ describe AccountTypesController do { "name" => "MyString" } end - describe "GET index" do - it "assigns all account_types as @account_types" do - account_type = AccountType.create! valid_attributes - get :index, {} - assigns(:account_types).sort.should eq( - [account_type, subject.current_member.account_type].sort - ) - end - end - - describe "GET show" do - it "assigns the requested account_type as @account_type" do - account_type = AccountType.create! valid_attributes - get :show, {:id => account_type.to_param} - assigns(:account_type).should eq(account_type) - end - end - - describe "GET new" do - it "assigns a new account_type as @account_type" do - get :new, {} - assigns(:account_type).should be_a_new(AccountType) - end - end - - describe "GET edit" do - it "assigns the requested account_type as @account_type" do - account_type = AccountType.create! valid_attributes - get :edit, {:id => account_type.to_param} - assigns(:account_type).should eq(account_type) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new AccountType" do - expect { - post :create, {:account_type => valid_attributes} - }.to change(AccountType, :count).by(1) - end - - it "assigns a newly created account_type as @account_type" do - post :create, {:account_type => valid_attributes} - assigns(:account_type).should be_a(AccountType) - assigns(:account_type).should be_persisted - end - - it "redirects to the created account_type" do - post :create, {:account_type => valid_attributes} - response.should redirect_to(AccountType.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved account_type as @account_type" do - # Trigger the behavior that occurs when invalid params are submitted - AccountType.any_instance.stub(:save).and_return(false) - post :create, {:account_type => { "name" => "invalid value" }} - assigns(:account_type).should be_a_new(AccountType) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - AccountType.any_instance.stub(:save).and_return(false) - post :create, {:account_type => { "name" => "invalid value" }} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested account_type" do - account_type = AccountType.create! valid_attributes - # Assuming there are no other account_types in the database, this - # specifies that the AccountType created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - AccountType.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) - put :update, {:id => account_type.to_param, :account_type => { "name" => "MyString" }} - end - - it "assigns the requested account_type as @account_type" do - account_type = AccountType.create! valid_attributes - put :update, {:id => account_type.to_param, :account_type => valid_attributes} - assigns(:account_type).should eq(account_type) - end - - it "redirects to the account_type" do - account_type = AccountType.create! valid_attributes - put :update, {:id => account_type.to_param, :account_type => valid_attributes} - response.should redirect_to(account_type) - end - end - - describe "with invalid params" do - it "assigns the account_type as @account_type" do - account_type = AccountType.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - AccountType.any_instance.stub(:save).and_return(false) - put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }} - assigns(:account_type).should eq(account_type) - end - - it "re-renders the 'edit' template" do - account_type = AccountType.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - AccountType.any_instance.stub(:save).and_return(false) - put :update, {:id => account_type.to_param, :account_type => { "name" => "invalid value" }} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested account_type" do - account_type = AccountType.create! valid_attributes - expect { - delete :destroy, {:id => account_type.to_param} - }.to change(AccountType, :count).by(-1) - end - - it "redirects to the account_types list" do - account_type = AccountType.create! valid_attributes - delete :destroy, {:id => account_type.to_param} - response.should redirect_to(account_types_url) - end - end - end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index f04a8cc69..31ac5b014 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -11,81 +11,10 @@ describe AccountsController do def create_account # account details are automatically created when you create a new # member; creating them manually will just cause errors as only one is - # allowed. - + # allowed. This method has been left here in case it's useful in + # future. member = FactoryGirl.create(:member) return member.account end - describe "GET index" do - it "assigns all accounts as @accounts" do - account = create_account - get :index, {} - # can't match the whole list because it includes the one for the - # logged in admin member, sigh - assigns(:accounts).should include(account) - assigns(:accounts).count.should eq 2 - end - end - - describe "GET show" do - it "assigns the requested account as @account" do - account = create_account - get :show, {:id => account.to_param} - assigns(:account).should eq(account) - end - end - - describe "GET edit" do - it "assigns the requested account as @account" do - account = create_account - get :edit, {:id => account.to_param} - assigns(:account).should eq(account) - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested account" do - account = create_account - # Assuming there are no other account in the database, this - # specifies that the Account created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Account.any_instance.should_receive(:update_attributes).with({ "member_id" => "1" }) - put :update, {:id => account.to_param, :account => { "member_id" => "1" }} - end - - it "assigns the requested account as @account" do - account = create_account - put :update, {:id => account.to_param, :account => valid_attributes} - assigns(:account).should eq(account) - end - - it "redirects to the account" do - account = create_account - put :update, {:id => account.to_param, :account => valid_attributes} - response.should redirect_to(account) - end - end - - describe "with invalid params" do - it "assigns the account as @account" do - account = create_account - # Trigger the behavior that occurs when invalid params are submitted - Account.any_instance.stub(:save).and_return(false) - put :update, {:id => account.to_param, :account => { "member_id" => "invalid value" }} - assigns(:account).should eq(account) - end - - it "re-renders the 'edit' template" do - account = create_account - # Trigger the behavior that occurs when invalid params are submitted - Account.any_instance.stub(:save).and_return(false) - put :update, {:id => account.to_param, :account => { "member_id" => "invalid value" }} - response.should render_template("edit") - end - end - end - end diff --git a/spec/controllers/authentications_controller_spec.rb b/spec/controllers/authentications_controller_spec.rb index 393f0509a..8add74c8c 100644 --- a/spec/controllers/authentications_controller_spec.rb +++ b/spec/controllers/authentications_controller_spec.rb @@ -15,63 +15,4 @@ describe AuthenticationsController do } end - describe "GET index" do - it "assigns all authentications as @authentications" do - get :index, {} - assigns(:authentications).should eq([@auth]) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Authentication" do - expect { - post :create, {:authentication => @auth} - }.to change(Authentication, :count).by(1) - end - - it "assigns a newly created authentication as @authentication" do - post :create, {:authentication => @auth} - assigns(:authentication).should be_a(Authentication) - assigns(:authentication).should be_persisted - end - - it "redirects to the settings page" do - post :create, {:authentication => @auth } - response.should redirect_to(edit_member_registration_path) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved authentication as @authentication" do - # Trigger the behavior that occurs when invalid params are submitted - Authentication.any_instance.stub(:save).and_return(false) - post :create, {:authentication => { "member_id" => "invalid value" }} - assigns(:authentication).should be_a_new(Authentication) - end - - it "redirects to settings page" do - # Trigger the behavior that occurs when invalid params are submitted - Authentication.any_instance.stub(:save).and_return(false) - post :create, {:authentication => { "member_id" => "invalid value" }} - response.should redirect_to(edit_member_registration_path) - end - end - end - - describe "DELETE destroy" do - it "destroys the requested authentication" do - authentication = @auth - expect { - delete :destroy, {:id => authentication.to_param} - }.to change(Authentication, :count).by(-1) - end - - it "redirects to the settings page" do - authentication = @auth - delete :destroy, {:id => authentication.to_param} - response.should redirect_to(edit_member_registration_path) - end - end - end diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb index bf0d5da87..219c21cb9 100644 --- a/spec/controllers/comments_controller_spec.rb +++ b/spec/controllers/comments_controller_spec.rb @@ -10,14 +10,6 @@ describe CommentsController do { :post_id => @post.id, :author_id => @member.id, :body => "some text" } end - describe "GET index" do - it "assigns all comments as @comments" do - comment = Comment.create! valid_attributes - get :index, {} - assigns(:comments).should eq([comment]) - end - end - describe "GET RSS feed" do it "returns an RSS feed" do get :index, :format => "rss" @@ -27,20 +19,7 @@ describe CommentsController do end end - describe "GET show" do - it "assigns the requested comment as @comment" do - comment = Comment.create! valid_attributes - get :show, {:id => comment.to_param} - assigns(:comment).should eq(comment) - end - end - describe "GET new" do - it "assigns a new comment as @comment" do - get :new, {} - assigns(:comment).should be_a_new(Comment) - end - it "picks up post from params" do post = FactoryGirl.create(:post) get :new, {:post_id => post.id} @@ -61,12 +40,6 @@ describe CommentsController do end describe "GET edit" do - it "assigns the requested comment as @comment" do - comment = Comment.create! valid_attributes - get :edit, {:id => comment.to_param} - assigns(:comment).should eq(comment) - end - it "assigns previous comments as @comments" do post = FactoryGirl.create(:post) old_comment = FactoryGirl.create(:comment, :post => post) @@ -74,98 +47,19 @@ describe CommentsController do get :edit, {:id => comment.to_param} assigns(:comments).should eq([comment, old_comment]) end - - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Comment" do - expect { - post :create, {:comment => valid_attributes} - }.to change(Comment, :count).by(1) - end - - it "assigns a newly created comment as @comment" do - post :create, {:comment => valid_attributes} - assigns(:comment).should be_a(Comment) - assigns(:comment).should be_persisted - end - - it "redirects to the created comment" do - post :create, {:comment => valid_attributes} - response.should redirect_to(Comment.last.post) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved comment as @comment" do - # Trigger the behavior that occurs when invalid params are submitted - Comment.any_instance.stub(:save).and_return(false) - post :create, {:comment => { "post_id" => "invalid value" }} - assigns(:comment).should be_a_new(Comment) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Comment.any_instance.stub(:save).and_return(false) - post :create, {:comment => { "post_id" => "invalid value" }} - response.should render_template("new") - end - end end describe "PUT update" do describe "with valid params" do - it "updates the requested comment" do - comment = Comment.create! valid_attributes - # Assuming there are no other comments in the database, this - # specifies that the Comment created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Comment.any_instance.should_receive(:update_attributes).with({ "body" => "some text" }) - put :update, {:id => comment.to_param, :comment => { "body" => "some text" }} - end - - it "assigns the requested comment as @comment" do - comment = Comment.create! valid_attributes - put :update, {:id => comment.to_param, :comment => valid_attributes} - assigns(:comment).should eq(comment) - end - - it "redirects to the comment" do + it "redirects to the comment's post" do comment = Comment.create! valid_attributes put :update, {:id => comment.to_param, :comment => valid_attributes} response.should redirect_to(comment.post) end end - - describe "with invalid params" do - it "assigns the comment as @comment" do - comment = Comment.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Comment.any_instance.stub(:save).and_return(false) - put :update, {:id => comment.to_param, :comment => { "post_id" => "invalid value" }} - assigns(:comment).should eq(comment) - end - - it "re-renders the 'edit' template" do - comment = Comment.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Comment.any_instance.stub(:save).and_return(false) - put :update, {:id => comment.to_param, :comment => { "post_id" => "invalid value" }} - response.should render_template("edit") - end - end end describe "DELETE destroy" do - it "destroys the requested comment" do - comment = Comment.create! valid_attributes - expect { - delete :destroy, {:id => comment.to_param} - }.to change(Comment, :count).by(-1) - end - it "redirects to the post the comment was on" do comment = Comment.create! valid_attributes post = comment.post diff --git a/spec/controllers/crops_controller_spec.rb b/spec/controllers/crops_controller_spec.rb index f56c7ee3b..281882e29 100644 --- a/spec/controllers/crops_controller_spec.rb +++ b/spec/controllers/crops_controller_spec.rb @@ -11,14 +11,6 @@ describe CropsController do } end - describe "GET index" do - it "assigns all crops as @crops" do - crop = Crop.create! valid_attributes - get :index, {} - assigns(:crops).should eq([crop]) - end - end - describe "GET RSS feed" do it "returns an RSS feed" do get :index, :format => "rss" @@ -28,123 +20,4 @@ describe CropsController do end end - describe "GET show" do - it "assigns the requested crop as @crop" do - crop = Crop.create! valid_attributes - get :show, {:id => crop.to_param} - assigns(:crop).should eq(crop) - end - end - - describe "GET new" do - it "assigns a new crop as @crop" do - get :new, {} - assigns(:crop).should be_a_new(Crop) - end - end - - describe "GET edit" do - it "assigns the requested crop as @crop" do - crop = Crop.create! valid_attributes - get :edit, {:id => crop.to_param} - assigns(:crop).should eq(crop) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Crop" do - expect { - post :create, {:crop => valid_attributes} - }.to change(Crop, :count).by(1) - end - - it "assigns a newly created crop as @crop" do - post :create, {:crop => valid_attributes} - assigns(:crop).should be_a(Crop) - assigns(:crop).should be_persisted - end - - it "redirects to the created crop" do - post :create, {:crop => valid_attributes} - response.should redirect_to(Crop.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved crop as @crop" do - # Trigger the behavior that occurs when invalid params are submitted - Crop.any_instance.stub(:save).and_return(false) - post :create, {:crop => {}} - assigns(:crop).should be_a_new(Crop) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Crop.any_instance.stub(:save).and_return(false) - post :create, {:crop => {}} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested crop" do - crop = Crop.create! valid_attributes - # Assuming there are no other crops in the database, this - # specifies that the Crop created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Crop.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) - put :update, {:id => crop.to_param, :crop => {'these' => 'params'}} - end - - it "assigns the requested crop as @crop" do - crop = Crop.create! valid_attributes - put :update, {:id => crop.to_param, :crop => valid_attributes} - assigns(:crop).should eq(crop) - end - - it "redirects to the crop" do - crop = Crop.create! valid_attributes - put :update, {:id => crop.to_param, :crop => valid_attributes} - response.should redirect_to(crop) - end - end - - describe "with invalid params" do - it "assigns the crop as @crop" do - crop = Crop.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Crop.any_instance.stub(:save).and_return(false) - put :update, {:id => crop.to_param, :crop => {}} - assigns(:crop).should eq(crop) - end - - it "re-renders the 'edit' template" do - crop = Crop.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Crop.any_instance.stub(:save).and_return(false) - put :update, {:id => crop.to_param, :crop => {}} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested crop" do - crop = Crop.create! valid_attributes - expect { - delete :destroy, {:id => crop.to_param} - }.to change(Crop, :count).by(-1) - end - - it "redirects to the crops list" do - crop = Crop.create! valid_attributes - delete :destroy, {:id => crop.to_param} - response.should redirect_to(crops_url) - end - end - end diff --git a/spec/controllers/forums_controller_spec.rb b/spec/controllers/forums_controller_spec.rb index 8d851d4cf..324c46120 100644 --- a/spec/controllers/forums_controller_spec.rb +++ b/spec/controllers/forums_controller_spec.rb @@ -16,131 +16,4 @@ describe ForumsController do {} end - describe "GET index" do - it "assigns all forums as @forums" do - forum = Forum.create! valid_attributes - get :index, {} - assigns(:forums).should eq([forum]) - end - end - - describe "GET show" do - it "assigns the requested forum as @forum" do - forum = Forum.create! valid_attributes - get :show, {:id => forum.to_param} - assigns(:forum).should eq(forum) - end - end - - describe "GET new" do - it "assigns a new forum as @forum" do - get :new, {} - assigns(:forum).should be_a_new(Forum) - end - end - - describe "GET edit" do - it "assigns the requested forum as @forum" do - forum = Forum.create! valid_attributes - get :edit, {:id => forum.to_param} - assigns(:forum).should eq(forum) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Forum" do - expect { - post :create, {:forum => valid_attributes} - }.to change(Forum, :count).by(1) - end - - it "assigns a newly created forum as @forum" do - post :create, {:forum => valid_attributes} - assigns(:forum).should be_a(Forum) - assigns(:forum).should be_persisted - end - - it "redirects to the created forum" do - post :create, {:forum => valid_attributes} - response.should redirect_to(Forum.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved forum as @forum" do - # Trigger the behavior that occurs when invalid params are submitted - Forum.any_instance.stub(:save).and_return(false) - post :create, {:forum => { "name" => "invalid value" }} - assigns(:forum).should be_a_new(Forum) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Forum.any_instance.stub(:save).and_return(false) - post :create, {:forum => { "name" => "invalid value" }} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested forum" do - forum = Forum.create! valid_attributes - # Assuming there are no other forums in the database, this - # specifies that the Forum created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Forum.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) - put :update, {:id => forum.to_param, :forum => { "name" => "MyString" }} - end - - it "assigns the requested forum as @forum" do - forum = Forum.create! valid_attributes - put :update, {:id => forum.to_param, :forum => valid_attributes} - assigns(:forum).should eq(forum) - end - - it "redirects to the forum" do - forum = Forum.create! valid_attributes - put :update, {:id => forum.to_param, :forum => valid_attributes} - response.should redirect_to(forum) - end - end - - describe "with invalid params" do - it "assigns the forum as @forum" do - forum = Forum.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Forum.any_instance.stub(:save).and_return(false) - put :update, {:id => forum.to_param, :forum => { "name" => "invalid value" }} - assigns(:forum).should eq(forum) - end - - it "re-renders the 'edit' template" do - forum = Forum.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Forum.any_instance.stub(:save).and_return(false) - put :update, {:id => forum.to_param, :forum => { "name" => "invalid value" }} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested forum" do - forum = Forum.create! valid_attributes - expect { - delete :destroy, {:id => forum.to_param} - }.to change(Forum, :count).by(-1) - end - - it "redirects to the forums list" do - forum = Forum.create! valid_attributes - delete :destroy, {:id => forum.to_param} - response.should redirect_to(forums_url) - end - end - end diff --git a/spec/controllers/member_controller_spec.rb b/spec/controllers/member_controller_spec.rb index f15cd049c..5fa13042d 100644 --- a/spec/controllers/member_controller_spec.rb +++ b/spec/controllers/member_controller_spec.rb @@ -24,10 +24,6 @@ describe MembersController do end describe "GET show" do - it "assigns the requested member as @member" do - get :show, {:id => @member.id} - assigns(:member).should eq(@member) - end it "does NOT provide JSON for member profile" do get :show, { :id => @member.id , :format => 'json' } diff --git a/spec/controllers/notifications_controller_spec.rb b/spec/controllers/notifications_controller_spec.rb index be3fe5400..99add1cf4 100644 --- a/spec/controllers/notifications_controller_spec.rb +++ b/spec/controllers/notifications_controller_spec.rb @@ -76,11 +76,6 @@ describe NotificationsController do end describe "GET new" do - it "assigns a new notification as @notification" do - get :new, {} - assigns(:notification).should be_a_new(Notification) - end - it "assigns a recipient" do @recipient = FactoryGirl.create(:member) get :new, {:recipient_id => @recipient.id } @@ -88,35 +83,8 @@ describe NotificationsController do end end - describe "DELETE destroy" do - it "destroys the requested notification" do - notification = FactoryGirl.create(:notification, :recipient_id => subject.current_member.id) - expect { - delete :destroy, {:id => notification.to_param} - }.to change(Notification, :count).by(-1) - end - - it "redirects to the notifications page" do - notification = FactoryGirl.create(:notification, :recipient_id => subject.current_member.id) - delete :destroy, {:id => notification.to_param} - response.should redirect_to(notifications_url) - end - end - describe "POST create" do describe "with valid params" do - it "creates a new notification" do - expect { - post :create, {:notification => valid_attributes_for_sender} - }.to change(Notification, :count).by(1) - end - - it "assigns a newly created notification as @notification" do - post :create, {:notification => valid_attributes_for_sender} - assigns(:notification).should be_a(Notification) - assigns(:notification).should be_persisted - end - it "redirects to the recipient's profile" do @recipient = FactoryGirl.create(:member) post :create, { :notification => { :recipient_id => @recipient.id, :subject => 'foo' } } @@ -124,20 +92,5 @@ describe NotificationsController do end end - describe "with invalid params" do - it "assigns a newly created but unsaved notification as @notification" do - # Trigger the behavior that occurs when invalid params are submitted - Notification.any_instance.stub(:save).and_return(false) - post :create, {:notification => {}} - assigns(:notification).should be_a_new(Notification) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Notification.any_instance.stub(:save).and_return(false) - post :create, {:notification => {}} - response.should render_template("new") - end - end end end diff --git a/spec/controllers/order_items_controller_spec.rb b/spec/controllers/order_items_controller_spec.rb index 0ba6fbf43..a33c55cc3 100644 --- a/spec/controllers/order_items_controller_spec.rb +++ b/spec/controllers/order_items_controller_spec.rb @@ -18,52 +18,27 @@ describe OrderItemsController do describe "POST create" do - describe "with valid params" do - it "creates a new OrderItem" do - @order = FactoryGirl.create(:order, :member => @member) - expect { - post :create, {:order_item => { - :order_id => @order.id, - :product_id => @product.id, - :price => @product.min_price - }} - }.to change(OrderItem, :count).by(1) - end + it "redirects to order" do + @order = FactoryGirl.create(:order, :member => @member) + post :create, {:order_item => { + :order_id => @order.id, + :product_id => @product.id, + :price => @product.min_price + }} + response.should redirect_to(OrderItem.last.order) + end - it "assigns a newly created order_item as @order_item" do - @order = FactoryGirl.create(:order, :member => @member) + it 'creates an order for you' do + @member = FactoryGirl.create(:member) + sign_in @member + @product = FactoryGirl.create(:product) + expect { post :create, {:order_item => { - :order_id => @order.id, :product_id => @product.id, :price => @product.min_price }} - assigns(:order_item).should be_a(OrderItem) - assigns(:order_item).should be_persisted - end - - it "redirects to order" do - @order = FactoryGirl.create(:order, :member => @member) - post :create, {:order_item => { - :order_id => @order.id, - :product_id => @product.id, - :price => @product.min_price - }} - response.should redirect_to(OrderItem.last.order) - end - - it 'creates an order for you' do - @member = FactoryGirl.create(:member) - sign_in @member - @product = FactoryGirl.create(:product) - expect { - post :create, {:order_item => { - :product_id => @product.id, - :price => @product.min_price - }} - }.to change(Order, :count).by(1) - OrderItem.last.order.should be_an_instance_of Order - end - + }.to change(Order, :count).by(1) + OrderItem.last.order.should be_an_instance_of Order end describe "with non-int price" do @@ -80,21 +55,5 @@ describe OrderItemsController do OrderItem.last.price.should eq 333 end end - - describe "with invalid params" do - it "assigns a newly created but unsaved order_item as @order_item" do - # Trigger the behavior that occurs when invalid params are submitted - OrderItem.any_instance.stub(:save).and_return(false) - post :create, {:order_item => { "order_id" => "invalid value" }} - assigns(:order_item).should be_a_new(OrderItem) - end - - it "sends you back to the shop" do - # Trigger the behavior that occurs when invalid params are submitted - OrderItem.any_instance.stub(:save).and_return(false) - post :create, {:order_item => { "order_id" => "invalid value" }} - response.should redirect_to(shop_path) - end - end end end diff --git a/spec/controllers/orders_controller_spec.rb b/spec/controllers/orders_controller_spec.rb index 75e7b4346..cf2eae35d 100644 --- a/spec/controllers/orders_controller_spec.rb +++ b/spec/controllers/orders_controller_spec.rb @@ -12,26 +12,6 @@ describe OrdersController do {} end - describe "GET index" do - it "assigns all orders as @orders" do - member = FactoryGirl.create(:member) - sign_in member - order = Order.create!(:member_id => member.id) - get :index, {} - assigns(:orders).should eq([order]) - end - end - - describe "GET show" do - it "assigns the requested order as @order" do - member = FactoryGirl.create(:member) - sign_in member - order = Order.create!(:member_id => member.id) - get :show, {:id => order.to_param} - assigns(:order).should eq(order) - end - end - describe "GET checkout" do it "redirects to Paypal" do member = FactoryGirl.create(:member) @@ -54,16 +34,7 @@ describe OrdersController do end describe "DELETE destroy" do - it "destroys the requested order" do - member = FactoryGirl.create(:member) - sign_in member - order = Order.create!(:member_id => member.id) - expect { - delete :destroy, {:id => order.id} - }.to change(Order, :count).by(-1) - end - - it "redirects to the posts list" do + it "redirects to the shop" do member = FactoryGirl.create(:member) sign_in member order = Order.create!(:member_id => member.id) diff --git a/spec/controllers/plantings_controller_spec.rb b/spec/controllers/plantings_controller_spec.rb index fec82c7e8..dff4e2909 100644 --- a/spec/controllers/plantings_controller_spec.rb +++ b/spec/controllers/plantings_controller_spec.rb @@ -11,27 +11,7 @@ describe PlantingsController do } end - describe "GET index" do - it "assigns all plantings as @plantings" do - planting = Planting.create! valid_attributes - get :index, {} - assigns(:plantings).should eq([planting]) - end - end - - describe "GET show" do - it "assigns the requested planting as @planting" do - planting = Planting.create! valid_attributes - get :show, {:id => planting.to_param} - assigns(:planting).should eq(planting) - end - end - describe "GET new" do - it "assigns a new planting as @planting" do - get :new, {} - assigns(:planting).should be_a_new(Planting) - end it "picks up crop from params" do crop = FactoryGirl.create(:crop) @@ -63,109 +43,4 @@ describe PlantingsController do end - describe "GET edit" do - it "assigns the requested planting as @planting" do - planting = Planting.create! valid_attributes - get :edit, {:id => planting.to_param} - assigns(:planting).should eq(planting) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Planting" do - expect { - post :create, {:planting => valid_attributes} - }.to change(Planting, :count).by(1) - end - - it "assigns a newly created planting as @planting" do - post :create, {:planting => valid_attributes} - assigns(:planting).should be_a(Planting) - assigns(:planting).should be_persisted - end - - it "redirects to the created planting" do - post :create, {:planting => valid_attributes} - response.should redirect_to(Planting.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved planting as @planting" do - # Trigger the behavior that occurs when invalid params are submitted - Planting.any_instance.stub(:save).and_return(false) - post :create, {:planting => {}} - assigns(:planting).should be_a_new(Planting) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Planting.any_instance.stub(:save).and_return(false) - post :create, {:planting => {}} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested planting" do - planting = Planting.create! valid_attributes - # Assuming there are no other plantings in the database, this - # specifies that the Planting created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Planting.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) - put :update, {:id => planting.to_param, :planting => {'these' => 'params'}} - end - - it "assigns the requested planting as @planting" do - planting = Planting.create! valid_attributes - put :update, {:id => planting.to_param, :planting => valid_attributes} - assigns(:planting).should eq(planting) - end - - it "redirects to the planting" do - planting = Planting.create! valid_attributes - put :update, {:id => planting.to_param, :planting => valid_attributes} - response.should redirect_to(planting) - end - end - - describe "with invalid params" do - it "assigns the planting as @planting" do - planting = Planting.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Planting.any_instance.stub(:save).and_return(false) - put :update, {:id => planting.to_param, :planting => {}} - assigns(:planting).should eq(planting) - end - - it "re-renders the 'edit' template" do - planting = Planting.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Planting.any_instance.stub(:save).and_return(false) - put :update, {:id => planting.to_param, :planting => {}} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested planting" do - planting = Planting.create! valid_attributes - expect { - delete :destroy, {:id => planting.to_param} - }.to change(Planting, :count).by(-1) - end - - it "redirects to the garden" do - planting = Planting.create! valid_attributes - garden = planting.garden - delete :destroy, {:id => planting.to_param} - response.should redirect_to(garden) - end - end - end diff --git a/spec/controllers/products_controller_spec.rb b/spec/controllers/products_controller_spec.rb index 13f77f195..f2daad383 100644 --- a/spec/controllers/products_controller_spec.rb +++ b/spec/controllers/products_controller_spec.rb @@ -16,131 +16,4 @@ describe ProductsController do {} end - describe "GET index" do - it "assigns all products as @products" do - product = Product.create! valid_attributes - get :index, {} - assigns(:products).should eq([product]) - end - end - - describe "GET show" do - it "assigns the requested product as @product" do - product = Product.create! valid_attributes - get :show, {:id => product.to_param} - assigns(:product).should eq(product) - end - end - - describe "GET new" do - it "assigns a new product as @product" do - get :new, {} - assigns(:product).should be_a_new(Product) - end - end - - describe "GET edit" do - it "assigns the requested product as @product" do - product = Product.create! valid_attributes - get :edit, {:id => product.to_param} - assigns(:product).should eq(product) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Product" do - expect { - post :create, {:product => valid_attributes} - }.to change(Product, :count).by(1) - end - - it "assigns a newly created product as @product" do - post :create, {:product => valid_attributes} - assigns(:product).should be_a(Product) - assigns(:product).should be_persisted - end - - it "redirects to the created product" do - post :create, {:product => valid_attributes} - response.should redirect_to(Product.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved product as @product" do - # Trigger the behavior that occurs when invalid params are submitted - Product.any_instance.stub(:save).and_return(false) - post :create, {:product => { "name" => "invalid value" }} - assigns(:product).should be_a_new(Product) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Product.any_instance.stub(:save).and_return(false) - post :create, {:product => { "name" => "invalid value" }} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested product" do - product = Product.create! valid_attributes - # Assuming there are no other products in the database, this - # specifies that the Product created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Product.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) - put :update, {:id => product.to_param, :product => { "name" => "MyString" }} - end - - it "assigns the requested product as @product" do - product = Product.create! valid_attributes - put :update, {:id => product.to_param, :product => valid_attributes} - assigns(:product).should eq(product) - end - - it "redirects to the product" do - product = Product.create! valid_attributes - put :update, {:id => product.to_param, :product => valid_attributes} - response.should redirect_to(product) - end - end - - describe "with invalid params" do - it "assigns the product as @product" do - product = Product.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Product.any_instance.stub(:save).and_return(false) - put :update, {:id => product.to_param, :product => { "name" => "invalid value" }} - assigns(:product).should eq(product) - end - - it "re-renders the 'edit' template" do - product = Product.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Product.any_instance.stub(:save).and_return(false) - put :update, {:id => product.to_param, :product => { "name" => "invalid value" }} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested product" do - product = Product.create! valid_attributes - expect { - delete :destroy, {:id => product.to_param} - }.to change(Product, :count).by(-1) - end - - it "redirects to the products list" do - product = Product.create! valid_attributes - delete :destroy, {:id => product.to_param} - response.should redirect_to(products_url) - end - end - end diff --git a/spec/controllers/roles_controller_spec.rb b/spec/controllers/roles_controller_spec.rb index 315a45fd6..0665101b4 100644 --- a/spec/controllers/roles_controller_spec.rb +++ b/spec/controllers/roles_controller_spec.rb @@ -17,123 +17,4 @@ describe RolesController do end end - describe "GET show" do - it "assigns the requested role as @role" do - role = Role.create! valid_attributes - get :show, {:id => role.to_param} - assigns(:role).should eq(role) - end - end - - describe "GET new" do - it "assigns a new role as @role" do - get :new, {} - assigns(:role).should be_a_new(Role) - end - end - - describe "GET edit" do - it "assigns the requested role as @role" do - role = Role.create! valid_attributes - get :edit, {:id => role.to_param} - assigns(:role).should eq(role) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new Role" do - expect { - post :create, {:role => valid_attributes} - }.to change(Role, :count).by(1) - end - - it "assigns a newly created role as @role" do - post :create, {:role => valid_attributes} - assigns(:role).should be_a(Role) - assigns(:role).should be_persisted - end - - it "redirects to the created role" do - post :create, {:role => valid_attributes} - response.should redirect_to(Role.last) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved role as @role" do - # Trigger the behavior that occurs when invalid params are submitted - Role.any_instance.stub(:save).and_return(false) - post :create, {:role => { "name" => "invalid value" }} - assigns(:role).should be_a_new(Role) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - Role.any_instance.stub(:save).and_return(false) - post :create, {:role => { "name" => "invalid value" }} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested role" do - role = Role.create! valid_attributes - # Assuming there are no other roles in the database, this - # specifies that the Role created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - Role.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) - put :update, {:id => role.to_param, :role => { "name" => "MyString" }} - end - - it "assigns the requested role as @role" do - role = Role.create! valid_attributes - put :update, {:id => role.to_param, :role => valid_attributes} - assigns(:role).should eq(role) - end - - it "redirects to the role" do - role = Role.create! valid_attributes - put :update, {:id => role.to_param, :role => valid_attributes} - response.should redirect_to(role) - end - end - - describe "with invalid params" do - it "assigns the role as @role" do - role = Role.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Role.any_instance.stub(:save).and_return(false) - put :update, {:id => role.to_param, :role => { "name" => "invalid value" }} - assigns(:role).should eq(role) - end - - it "re-renders the 'edit' template" do - role = Role.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - Role.any_instance.stub(:save).and_return(false) - put :update, {:id => role.to_param, :role => { "name" => "invalid value" }} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested role" do - role = Role.create! valid_attributes - expect { - delete :destroy, {:id => role.to_param} - }.to change(Role, :count).by(-1) - end - - it "redirects to the roles list" do - role = Role.create! valid_attributes - delete :destroy, {:id => role.to_param} - response.should redirect_to(roles_url) - end - end - end diff --git a/spec/controllers/scientific_names_controller_spec.rb b/spec/controllers/scientific_names_controller_spec.rb index 0cf431a4a..254ebf1c8 100644 --- a/spec/controllers/scientific_names_controller_spec.rb +++ b/spec/controllers/scientific_names_controller_spec.rb @@ -12,158 +12,11 @@ describe ScientificNamesController do { :scientific_name => 'Solanum lycopersicum', :crop_id => @crop.id } end - describe "GET index" do - it "assigns all scientific_names as @scientific_names" do - scientific_name = ScientificName.create! valid_attributes - get :index, {} - assigns(:scientific_names).should eq([scientific_name]) - end - end - - describe "GET show" do - it "assigns the requested scientific_name as @scientific_name" do - scientific_name = ScientificName.create! valid_attributes - get :show, {:id => scientific_name.to_param} - assigns(:scientific_name).should eq(scientific_name) - end - end - describe "GET new" do - it "assigns a new scientific_name as @scientific_name" do - get :new, {} - assigns(:scientific_name).should be_a_new(ScientificName) - end - it "assigns crop if specified" do get :new, { :crop_id => 1 } assigns(:crop).should be_an_instance_of Crop end - it "assigns crop if specified" do - end end - - describe "GET edit" do - it "assigns the requested scientific_name as @scientific_name" do - scientific_name = ScientificName.create! valid_attributes - get :edit, {:id => scientific_name.to_param} - assigns(:scientific_name).should eq(scientific_name) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new ScientificName" do - expect { - post :create, {:scientific_name => valid_attributes} - }.to change(ScientificName, :count).by(1) - end - end - end - - describe "GET edit" do - it "assigns the requested scientific_name as @scientific_name" do - scientific_name = ScientificName.create! valid_attributes - get :edit, {:id => scientific_name.to_param} - assigns(:scientific_name).should eq(scientific_name) - end - end - - describe "POST create" do - describe "with valid params" do - it "creates a new ScientificName" do - expect { - post :create, {:scientific_name => valid_attributes} - }.to change(ScientificName, :count).by(1) - end - - it "assigns a newly created scientific_name as @scientific_name" do - post :create, {:scientific_name => valid_attributes} - assigns(:scientific_name).should be_a(ScientificName) - assigns(:scientific_name).should be_persisted - end - - it "redirects to the created scientific_name" do - post :create, {:scientific_name => valid_attributes} - response.should redirect_to(ScientificName.last.crop) - end - end - - describe "with invalid params" do - it "assigns a newly created but unsaved scientific_name as @scientific_name" do - # Trigger the behavior that occurs when invalid params are submitted - ScientificName.any_instance.stub(:save).and_return(false) - post :create, {:scientific_name => {}} - assigns(:scientific_name).should be_a_new(ScientificName) - end - - it "re-renders the 'new' template" do - # Trigger the behavior that occurs when invalid params are submitted - ScientificName.any_instance.stub(:save).and_return(false) - post :create, {:scientific_name => {}} - response.should render_template("new") - end - end - end - - describe "PUT update" do - describe "with valid params" do - it "updates the requested scientific_name" do - scientific_name = ScientificName.create! valid_attributes - # Assuming there are no other scientific_names in the database, this - # specifies that the ScientificName created on the previous line - # receives the :update_attributes message with whatever params are - # submitted in the request. - ScientificName.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) - put :update, {:id => scientific_name.to_param, :scientific_name => {'these' => 'params'}} - end - - it "assigns the requested scientific_name as @scientific_name" do - scientific_name = ScientificName.create! valid_attributes - put :update, {:id => scientific_name.to_param, :scientific_name => valid_attributes} - assigns(:scientific_name).should eq(scientific_name) - end - - it "redirects to the scientific_name" do - scientific_name = ScientificName.create! valid_attributes - put :update, {:id => scientific_name.to_param, :scientific_name => valid_attributes} - response.should redirect_to(scientific_name.crop) - end - end - - describe "with invalid params" do - it "assigns the scientific_name as @scientific_name" do - scientific_name = ScientificName.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - ScientificName.any_instance.stub(:save).and_return(false) - put :update, {:id => scientific_name.to_param, :scientific_name => {}} - assigns(:scientific_name).should eq(scientific_name) - end - - it "re-renders the 'edit' template" do - scientific_name = ScientificName.create! valid_attributes - # Trigger the behavior that occurs when invalid params are submitted - ScientificName.any_instance.stub(:save).and_return(false) - put :update, {:id => scientific_name.to_param, :scientific_name => {}} - response.should render_template("edit") - end - end - end - - describe "DELETE destroy" do - it "destroys the requested scientific_name" do - scientific_name = ScientificName.create! valid_attributes - expect { - delete :destroy, {:id => scientific_name.to_param} - }.to change(ScientificName, :count).by(-1) - end - - it "redirects to the scientific_names list" do - scientific_name = ScientificName.create! valid_attributes - crop = scientific_name.crop - delete :destroy, {:id => scientific_name.to_param} - response.should redirect_to(crop) - end - end - end From c2be06e68f7c64f9eab36953487999a4e343620b Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 8 Jul 2013 13:28:59 +1000 Subject: [PATCH 181/184] Fixed failing timestamp test on postgres --- spec/models/post_spec.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index ea278ea39..441b9c6a9 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -6,8 +6,16 @@ describe Post do end it "should be sorted in reverse order" do - FactoryGirl.create(:post, :subject => 'first entry', :author => @member) - FactoryGirl.create(:post, :subject => 'second entry', :author => @member) + FactoryGirl.create(:post, + :subject => 'first entry', + :author => @member, + :created_at => 2.days.ago + ) + FactoryGirl.create(:post, + :subject => 'second entry', + :author => @member, + :created_at => 1.day.ago + ) Post.first.subject.should == "second entry" end From 9df5feaf1c4665e04ef588bfe1e52165920986c7 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 9 Jul 2013 20:41:05 +1000 Subject: [PATCH 182/184] Removed test user seeding from test/travis envs We don't actually use these for testing (and shouldn't). --- db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index 1c4811fd7..a11b664c7 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -18,7 +18,7 @@ CSV.foreach(Rails.root.join('db', 'seeds', 'crops.csv')) do |row| end puts "Finished loading crops" -if Rails.env.development? or Rails.env.test? or Rails.env.travis? +if Rails.env.development puts "Loading test users..." (1..3).each do |i| @user = Member.create( From e3cddf183e2f91e2ac98618bc5e2b0058b607c60 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Tue, 9 Jul 2013 21:02:01 +1000 Subject: [PATCH 183/184] Cleaned up seeds.rb, added methods etc. Also ensured that things that are needed in production are created in production: staff and free account types, admin and wrangler roles. --- db/seeds.rb | 88 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index a11b664c7..91ba3823a 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -10,15 +10,61 @@ require 'csv' -puts "Loading crops..." -CSV.foreach(Rails.root.join('db', 'seeds', 'crops.csv')) do |row| - system_name,scientific_name,en_wikipedia_url = row - @crop = Crop.create(:system_name => system_name, :en_wikipedia_url => en_wikipedia_url) - @crop.scientific_names.create(:scientific_name => scientific_name) -end -puts "Finished loading crops" +def load_data + # for all Growstuff sites, including production ones + load_crops + load_roles + load_basic_account_types -if Rails.env.development + # for development environments only + if Rails.env.development? + load_test_users + load_admin_users + load_paid_account_types + load_products + end + + puts "Done!" +end + + +def load_crops + puts "Loading crops..." + CSV.foreach(Rails.root.join('db', 'seeds', 'crops.csv')) do |row| + system_name,scientific_name,en_wikipedia_url = row + @crop = Crop.create( + :system_name => system_name, + :en_wikipedia_url => en_wikipedia_url + ) + @crop.scientific_names.create( + :scientific_name => scientific_name + ) + end + puts "Finished loading crops" +end + +def load_roles + puts "Creating admin role..." + @admin = Role.create(:name => 'Admin') + puts "Creating crop wrangler role..." + @wrangler = Role.create(:name => 'Crop Wrangler') +end + +def load_basic_account_types + puts "Adding 'free' and 'staff' account types..." + AccountType.create!( + :name => "Free", + :is_paid => false, + :is_permanent_paid => false + ) + AccountType.create!( + :name => "Staff", + :is_paid => true, + :is_permanent_paid => true + ) +end + +def load_test_users puts "Loading test users..." (1..3).each do |i| @user = Member.create( @@ -31,12 +77,9 @@ if Rails.env.development @user.save! end puts "Finished loading test users" +end - puts "Creating admin role..." - @admin = Role.create(:name => 'Admin') - puts "Creating crop wrangler role..." - @wrangler = Role.create(:name => 'Crop Wrangler') - +def load_admin_users puts "Adding admin and crop wrangler members..." @admin_user = Member.create( :login_name => "admin1", @@ -57,13 +100,10 @@ if Rails.env.development @wrangler_user.confirm! @wrangler_user.roles << @wrangler @wrangler_user.save! +end - puts "Adding account types..." - AccountType.create!( - :name => "Free", - :is_paid => false, - :is_permanent_paid => false - ) +def load_paid_account_types + puts "Adding 'paid' and 'seed' account types..." @paid_account = AccountType.create!( :name => "Paid", :is_paid => true, @@ -74,12 +114,9 @@ if Rails.env.development :is_paid => true, :is_permanent_paid => true ) - AccountType.create!( - :name => "Staff", - :is_paid => true, - :is_permanent_paid => true - ) +end +def load_products puts "Adding products..." Product.create!( :name => "Annual subscription", @@ -96,5 +133,4 @@ if Rails.env.development ) end -puts "Done!" - +load_data From b9a3dec6cc7c32585a5312545508159720fe8ad3 Mon Sep 17 00:00:00 2001 From: Skud <skud@infotrope.net> Date: Mon, 15 Jul 2013 10:27:54 +1000 Subject: [PATCH 184/184] fixed broken CSS that was hiding headings --- app/assets/stylesheets/bootstrap_and_overrides.css.less | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 089d5b1cd..328ac5b25 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -78,6 +78,7 @@ @dropdownLinkBackgroundHover: lighten(@green, 50%); body { + padding-top: @navbarHeight + 10px; padding-bottom: @navbarHeight + 10px; } ul.inline > li.first {