killbill-firstdata_e4 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ module Killbill #:nodoc:
2
+ module FirstdataE4 #:nodoc:
3
+ class FirstdataE4Response < ::Killbill::Plugin::ActiveMerchant::ActiveRecord::Response
4
+
5
+ self.table_name = 'firstdata_e4_responses'
6
+
7
+ has_one :firstdata_e4_transaction
8
+
9
+ def self.from_response(api_call, kb_account_id, kb_payment_id, kb_payment_transaction_id, transaction_type, payment_processor_account_id, kb_tenant_id, response, extra_params = {}, model = ::Killbill::FirstdataE4::FirstdataE4Response)
10
+ super(api_call,
11
+ kb_account_id,
12
+ kb_payment_id,
13
+ kb_payment_transaction_id,
14
+ transaction_type,
15
+ payment_processor_account_id,
16
+ kb_tenant_id,
17
+ response,
18
+ {
19
+ :params_amount_requested => extract(response, 'amount_requested'),
20
+ :params_authorization_num => extract(response, 'authorization_num'),
21
+ :params_avs => extract(response, 'avs'),
22
+ :params_bank_message => extract(response, 'bank_message'),
23
+ :params_bank_resp_code => extract(response, 'bank_resp_code'),
24
+ :params_bank_resp_code_2 => extract(response, 'bank_resp_code_2'),
25
+ :params_card_cost => extract(response, 'card_cost'),
26
+ :params_card_holders_name => extract(response, 'card_holders_name'),
27
+ :params_card_type => extract(response, 'card_type'),
28
+ :params_cavv => extract(response, 'cavv'),
29
+ :params_cavv_algorithm => extract(response, 'cavv_algorithm'),
30
+ :params_cavv_response => extract(response, 'cavv_response'),
31
+ :params_client_email => extract(response, 'client_email'),
32
+ :params_client_ip => extract(response, 'client_ip'),
33
+ :params_ctr => extract(response, 'ctr'),
34
+ :params_currency => extract(response, 'currency'),
35
+ :params_current_balance => extract(response, 'current_balance'),
36
+ :params_customer_ref => extract(response, 'customer_ref'),
37
+ :params_cvd_presence_ind => extract(response, 'cvd_presence_ind'),
38
+ :params_cvv2 => extract(response, 'cvv2'),
39
+ :params_dollar_amount => extract(response, 'dollar_amount'),
40
+ :params_ean => extract(response, 'ean'),
41
+ :params_ecommerce_flag => extract(response, 'ecommerce_flag'),
42
+ :params_error_description => extract(response, 'error_description'),
43
+ :params_exact_id => extract(response, 'exact_id'),
44
+ :params_error_number => extract(response, 'error_number'),
45
+ :params_exact_message => extract(response, 'exact_message'),
46
+ :params_exact_resp_code => extract(response, 'exact_resp_code'),
47
+ :params_language => extract(response, 'language'),
48
+ :params_merchant_address => extract(response, 'merchant_address'),
49
+ :params_merchant_city => extract(response, 'merchant_city'),
50
+ :params_merchant_country => extract(response, 'merchant_country'),
51
+ :params_merchant_name => extract(response, 'merchant_name'),
52
+ :params_merchant_postal => extract(response, 'merchant_postal'),
53
+ :params_merchant_province => extract(response, 'merchant_province'),
54
+ :params_merchant_url => extract(response, 'merchant_url'),
55
+ :params_pan => extract(response, 'pan'),
56
+ :params_partial_redemption => extract(response, 'partial_redemption'),
57
+ :params_password => extract(response, 'password'),
58
+ :params_previous_balance => extract(response, 'previous_balance'),
59
+ :params_reference_3 => extract(response, 'reference_3'),
60
+ :params_reference_no => extract(response, 'reference_no'),
61
+ :params_retrieval_ref_no => extract(response, 'retrieval_ref_no'),
62
+ :params_secure_auth_required => extract(response, 'secure_auth_required'),
63
+ :params_secure_auth_result => extract(response, 'secure_auth_result'),
64
+ :params_sequence_no => extract(response, 'sequence_no'),
65
+ :params_surcharge_amount => extract(response, 'surcharge_amount'),
66
+ :params_tax1_amount => extract(response, 'tax1_amount'),
67
+ :params_tax1_number => extract(response, 'tax1_number'),
68
+ :params_tax2_amount => extract(response, 'tax2_amount'),
69
+ :params_tax2_number => extract(response, 'tax2_number'),
70
+ :params_track1 => extract(response, 'track1'),
71
+ :params_track2 => extract(response, 'track2'),
72
+ :params_transaction_approved => extract(response, 'transaction_approved'),
73
+ :params_transaction_error => extract(response, 'transaction_error'),
74
+ :params_transaction_tag => extract(response, 'transaction_tag'),
75
+ :params_transaction_type => extract(response, 'transaction_type'),
76
+ :params_transarmor_token => extract(response, 'transarmor_token'),
77
+ :params_verification_str1 => extract(response, 'verification_str1'),
78
+ :params_virtual_card => extract(response, 'virtual_card'),
79
+ :params_xid => extract(response, 'xid'),
80
+ :params_zip_code => extract(response, 'zip_code')
81
+ }.merge!(extra_params),
82
+ model)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,11 @@
1
+ module Killbill #:nodoc:
2
+ module FirstdataE4 #:nodoc:
3
+ class FirstdataE4Transaction < ::Killbill::Plugin::ActiveMerchant::ActiveRecord::Transaction
4
+
5
+ self.table_name = 'firstdata_e4_transactions'
6
+
7
+ belongs_to :firstdata_e4_response
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Killbill #:nodoc:
2
+ module FirstdataE4 #:nodoc:
3
+ class PrivatePaymentPlugin < ::Killbill::Plugin::ActiveMerchant::PrivatePaymentPlugin
4
+ def initialize(session = {})
5
+ super(:firstdata_e4,
6
+ ::Killbill::FirstdataE4::FirstdataE4PaymentMethod,
7
+ ::Killbill::FirstdataE4::FirstdataE4Transaction,
8
+ ::Killbill::FirstdataE4::FirstdataE4Response,
9
+ session)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+
5
+ <%= @form %>
6
+
7
+ </body>
8
+ </html>
@@ -0,0 +1,24 @@
1
+ require 'openssl'
2
+ require 'action_controller'
3
+ require 'active_record'
4
+ require 'action_view'
5
+ require 'active_merchant'
6
+ require 'active_support'
7
+ require 'bigdecimal'
8
+ require 'money'
9
+ require 'monetize'
10
+ require 'offsite_payments'
11
+ require 'pathname'
12
+ require 'sinatra'
13
+ require 'singleton'
14
+ require 'yaml'
15
+
16
+ require 'killbill'
17
+ require 'killbill/helpers/active_merchant'
18
+
19
+ require 'firstdata_e4/api'
20
+ require 'firstdata_e4/private_api'
21
+
22
+ require 'firstdata_e4/models/payment_method'
23
+ require 'firstdata_e4/models/response'
24
+ require 'firstdata_e4/models/transaction'
data/pom.xml ADDED
@@ -0,0 +1,44 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ ~ Copyright 2014-2015 The Billing Project, LLC
4
+ ~
5
+ ~ The Billing Project licenses this file to you under the Apache License, version 2.0
6
+ ~ (the "License"); you may not use this file except in compliance with the
7
+ ~ License. You may obtain a copy of the License at:
8
+ ~
9
+ ~ http://www.apache.org/licenses/LICENSE-2.0
10
+ ~
11
+ ~ Unless required by applicable law or agreed to in writing, software
12
+ ~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
+ ~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
+ ~ License for the specific language governing permissions and limitations
15
+ ~ under the License.
16
+ -->
17
+
18
+ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
19
+ <parent>
20
+ <groupId>org.sonatype.oss</groupId>
21
+ <artifactId>oss-parent</artifactId>
22
+ <version>5</version>
23
+ </parent>
24
+ <modelVersion>4.0.0</modelVersion>
25
+ <groupId>org.kill-bill.billing.plugin.ruby</groupId>
26
+ <artifactId>firstdata_e4-plugin</artifactId>
27
+ <packaging>pom</packaging>
28
+ <version>0.1.0</version>
29
+ <name>firstdata_e4-plugin</name>
30
+ <url>http://github.com/killbill/killbill-firstdata_e4-plugin</url>
31
+ <description>Plugin for accessing FirstdataE4 as a payment gateway</description>
32
+ <licenses>
33
+ <license>
34
+ <name>Apache License 2.0</name>
35
+ <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
36
+ <distribution>repo</distribution>
37
+ </license>
38
+ </licenses>
39
+ <scm>
40
+ <connection>scm:git:git://github.com/killbill/killbill-firstdata_e4-plugin.git</connection>
41
+ <url>https://github.com/killbill/killbill-firstdata_e4-plugin/</url>
42
+ <developerConnection>scm:git:git@github.com:killbill/killbill-firstdata_e4-plugin.git</developerConnection>
43
+ </scm>
44
+ </project>
data/release.sh ADDED
@@ -0,0 +1,61 @@
1
+ set -e
2
+
3
+ BUNDLE=${BUNDLE-"bundle exec"}
4
+ MVN=${MVN-"mvn"}
5
+
6
+ if [ 'GNU' != "$(tar --help | grep GNU | head -1 | awk '{print $1}')" ]; then
7
+ echo 'Unable to release: make sure to use GNU tar'
8
+ exit 1
9
+ fi
10
+
11
+ if $(ruby -e'require "java"'); then
12
+ # Good
13
+ echo 'Detected JRuby'
14
+ else
15
+ echo 'Unable to release: make sure to use JRuby'
16
+ exit 1
17
+ fi
18
+
19
+ VERSION=`grep -E '<version>([0-9]+\.[0-9]+\.[0-9]+)</version>' pom.xml | sed 's/[\t \n]*<version>\(.*\)<\/version>[\t \n]*/\1/'`
20
+ if [[ -z "$NO_RELEASE" && "$VERSION" != "$(cat $PWD/VERSION)" ]]; then
21
+ echo 'Unable to release: make sure the versions in pom.xml and VERSION match'
22
+ exit 1
23
+ fi
24
+
25
+ echo 'Cleaning up'
26
+ $BUNDLE rake killbill:clean
27
+
28
+ echo 'Building gem'
29
+ $BUNDLE rake build
30
+
31
+ if [[ -z "$NO_RELEASE" ]]; then
32
+ echo 'Pushing the gem to Rubygems'
33
+ $BUNDLE rake release
34
+ fi
35
+
36
+ echo 'Building artifact'
37
+ $BUNDLE rake killbill:package
38
+
39
+ ARTIFACT="$PWD/pkg/killbill-firstdata_e4-$VERSION.tar.gz"
40
+ echo "Pushing $ARTIFACT to Maven Central"
41
+
42
+ if [[ -z "$NO_RELEASE" ]]; then
43
+ GOAL=gpg:sign-and-deploy-file
44
+ REPOSITORY_ID=ossrh-releases
45
+ URL=https://oss.sonatype.org/service/local/staging/deploy/maven2/
46
+ else
47
+ GOAL=deploy:deploy-file
48
+ REPOSITORY_ID=sonatype-nexus-snapshots
49
+ URL=https://oss.sonatype.org/content/repositories/snapshots/
50
+ VERSION="$VERSION-SNAPSHOT"
51
+ fi
52
+
53
+ $MVN $GOAL \
54
+ -DgroupId=org.kill-bill.billing.plugin.ruby \
55
+ -DartifactId=firstdata-e4-plugin \
56
+ -Dversion=$VERSION \
57
+ -Dpackaging=tar.gz \
58
+ -DrepositoryId=$REPOSITORY_ID \
59
+ -Durl=$URL \
60
+ -Dfile=$ARTIFACT \
61
+ -DpomFile=pom.xml
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Killbill::FirstdataE4::PaymentPlugin do
4
+
5
+ include ::Killbill::Plugin::ActiveMerchant::RSpec
6
+
7
+ before(:each) do
8
+ Dir.mktmpdir do |dir|
9
+ file = File.new(File.join(dir, 'firstdata_e4.yml'), 'w+')
10
+ file.write(<<-eos)
11
+ :firstdata_e4:
12
+ :test: true
13
+ # As defined by spec_helper.rb
14
+ :database:
15
+ :adapter: 'sqlite3'
16
+ :database: 'test.db'
17
+ eos
18
+ file.close
19
+
20
+ @plugin = build_plugin(::Killbill::FirstdataE4::PaymentPlugin, 'firstdata_e4', File.dirname(file))
21
+
22
+ # Start the plugin here - since the config file will be deleted
23
+ @plugin.start_plugin
24
+ end
25
+ end
26
+
27
+ it 'should start and stop correctly' do
28
+ @plugin.stop_plugin
29
+ end
30
+
31
+ it 'should generate forms correctly' do
32
+ kb_account_id = SecureRandom.uuid
33
+ kb_tenant_id = SecureRandom.uuid
34
+ context = @plugin.kb_apis.create_context(kb_tenant_id)
35
+ fields = @plugin.hash_to_properties({
36
+ :order_id => '1234',
37
+ :account_id => '5678',
38
+ :amount => 10
39
+ })
40
+ form = @plugin.build_form_descriptor kb_account_id, fields, [], context
41
+
42
+ form.kb_account_id.should == kb_account_id
43
+ form.form_method.should == 'POST'
44
+ form.form_url.should == 'https://checkout.globalgatewaye4.firstdata.com/payment'
45
+
46
+ form_fields = @plugin.properties_to_hash(form.form_fields)
47
+ end
48
+
49
+ # TODO: non-standard acknowledge API
50
+ xit 'should receive notifications correctly' do
51
+ description = 'description'
52
+
53
+ kb_tenant_id = SecureRandom.uuid
54
+ context = @plugin.kb_apis.create_context(kb_tenant_id)
55
+ properties = @plugin.hash_to_properties({ :description => description })
56
+
57
+ notification = ""
58
+ gw_notification = @plugin.process_notification notification, properties, context
59
+ end
60
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ ActiveMerchant::Billing::Base.mode = :test
4
+
5
+ describe Killbill::FirstdataE4::PaymentPlugin do
6
+
7
+ include ::Killbill::Plugin::ActiveMerchant::RSpec
8
+
9
+ before(:each) do
10
+ ::Killbill::FirstdataE4::FirstdataE4PaymentMethod.delete_all
11
+ ::Killbill::FirstdataE4::FirstdataE4Response.delete_all
12
+ ::Killbill::FirstdataE4::FirstdataE4Transaction.delete_all
13
+
14
+ @plugin = build_plugin(::Killbill::FirstdataE4::PaymentPlugin, 'firstdata_e4')
15
+ @plugin.start_plugin
16
+
17
+ @call_context = build_call_context
18
+
19
+ @properties = []
20
+ @pm = create_payment_method(::Killbill::FirstdataE4::FirstdataE4PaymentMethod, nil, @call_context.tenant_id, @properties)
21
+ @amount = BigDecimal.new('100')
22
+ @currency = 'USD'
23
+
24
+ kb_payment_id = SecureRandom.uuid
25
+ 1.upto(6) do
26
+ @kb_payment = @plugin.kb_apis.proxied_services[:payment_api].add_payment(kb_payment_id)
27
+ end
28
+ end
29
+
30
+ after(:each) do
31
+ @plugin.stop_plugin
32
+ end
33
+
34
+ it 'should be able to charge a Credit Card directly' do
35
+ properties = build_pm_properties
36
+
37
+ # We created the payment method, hence the rows
38
+ Killbill::FirstdataE4::FirstdataE4Response.all.size.should == 1
39
+ Killbill::FirstdataE4::FirstdataE4Transaction.all.size.should == 0
40
+
41
+ payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, properties, @call_context)
42
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
43
+ payment_response.amount.should == @amount
44
+ payment_response.transaction_type.should == :PURCHASE
45
+
46
+ responses = Killbill::FirstdataE4::FirstdataE4Response.all
47
+ responses.size.should == 2
48
+ responses[0].api_call.should == 'add_payment_method'
49
+ responses[0].message.should == 'Transaction Normal - Approved'
50
+ responses[1].api_call.should == 'purchase'
51
+ responses[1].message.should == 'Transaction Normal - Approved'
52
+ transactions = Killbill::FirstdataE4::FirstdataE4Transaction.all
53
+ transactions.size.should == 1
54
+ transactions[0].api_call.should == 'purchase'
55
+ end
56
+
57
+ it 'should be able to charge and refund' do
58
+ payment_response = @plugin.purchase_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
59
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
60
+ payment_response.amount.should == @amount
61
+ payment_response.transaction_type.should == :PURCHASE
62
+
63
+ # Try a full refund
64
+ refund_response = @plugin.refund_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
65
+ refund_response.status.should eq(:PROCESSED), refund_response.gateway_error
66
+ refund_response.amount.should == @amount
67
+ refund_response.transaction_type.should == :REFUND
68
+ end
69
+
70
+ it 'should be able to auth, capture and refund' do
71
+ payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
72
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
73
+ payment_response.amount.should == @amount
74
+ payment_response.transaction_type.should == :AUTHORIZE
75
+
76
+ # Try multiple partial captures
77
+ partial_capture_amount = BigDecimal.new('10')
78
+ 1.upto(3) do |i|
79
+ payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[i].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
80
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
81
+ payment_response.amount.should == partial_capture_amount
82
+ payment_response.transaction_type.should == :CAPTURE
83
+ end
84
+
85
+ # TODO Doesn't go through: Bad Request (69) - Invalid Transaction Tag
86
+ # Try a partial refund
87
+ #refund_response = @plugin.refund_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[4].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
88
+ #refund_response.status.should eq(:PROCESSED), refund_response.gateway_error
89
+ #refund_response.amount.should == partial_capture_amount
90
+ #refund_response.transaction_type.should == :REFUND
91
+
92
+ # Try to capture again
93
+ payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[5].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
94
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
95
+ payment_response.amount.should == partial_capture_amount
96
+ payment_response.transaction_type.should == :CAPTURE
97
+ end
98
+
99
+ it 'should be able to auth and void' do
100
+ payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
101
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
102
+ payment_response.amount.should == @amount
103
+ payment_response.transaction_type.should == :AUTHORIZE
104
+
105
+ payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, @properties, @call_context)
106
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
107
+ payment_response.transaction_type.should == :VOID
108
+ end
109
+
110
+ it 'should be able to auth, partial capture and void' do
111
+ payment_response = @plugin.authorize_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[0].id, @pm.kb_payment_method_id, @amount, @currency, @properties, @call_context)
112
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
113
+ payment_response.amount.should == @amount
114
+ payment_response.transaction_type.should == :AUTHORIZE
115
+
116
+ partial_capture_amount = BigDecimal.new('10')
117
+ payment_response = @plugin.capture_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[1].id, @pm.kb_payment_method_id, partial_capture_amount, @currency, @properties, @call_context)
118
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
119
+ payment_response.amount.should == partial_capture_amount
120
+ payment_response.transaction_type.should == :CAPTURE
121
+
122
+ payment_response = @plugin.void_payment(@pm.kb_account_id, @kb_payment.id, @kb_payment.transactions[2].id, @pm.kb_payment_method_id, @properties, @call_context)
123
+ payment_response.status.should eq(:PROCESSED), payment_response.gateway_error
124
+ payment_response.transaction_type.should == :VOID
125
+ end
126
+ end
@@ -0,0 +1,24 @@
1
+ require 'bundler'
2
+ require 'firstdata_e4'
3
+ require 'killbill/helpers/active_merchant/killbill_spec_helper'
4
+
5
+ require 'logger'
6
+
7
+ require 'rspec'
8
+
9
+ RSpec.configure do |config|
10
+ config.color_enabled = true
11
+ config.tty = true
12
+ config.formatter = 'documentation'
13
+ end
14
+
15
+ require 'active_record'
16
+ ActiveRecord::Base.establish_connection(
17
+ :adapter => 'sqlite3',
18
+ :database => 'test.db'
19
+ )
20
+ # For debugging
21
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
22
+ # Create the schema
23
+ require File.expand_path(File.dirname(__FILE__) + '../../db/schema.rb')
24
+