killbill-firstdata_e4 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+