spree_veritrans 1.0.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: efed8e2800e36f96917e53bb62db12e8e60b4b87
4
+ data.tar.gz: 9ed89efdcaeaca3e0c5e29d1231e41f58ed5db56
5
+ SHA512:
6
+ metadata.gz: b827e565a47257ad33e11a79951136e248bbd72ba39ef9604a7e3cf151aafbbaecd231cc29c9b159fee81a62aa3b7e1e86cc79dd125f8e7d8875cce6888bdbd5
7
+ data.tar.gz: 1ff654282e9e96bb8808540d518637e60af6829b7f358b9ea479b4931356d42f860fbe6098354d3a601db0f9d5c33a56135a73dcb25253b38905aae17169c7c0
@@ -0,0 +1,9 @@
1
+ \#*
2
+ *~
3
+ .#*
4
+ .DS_Store
5
+ tmp
6
+ spec/dummy
7
+ Gemfile.lock
8
+ .rvmrc
9
+ coverage
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'spree', github: 'spree/spree', branch: '3-0-stable'
4
+ gem 'excon', '~> 0.20'
5
+
6
+ gemspec
@@ -0,0 +1,26 @@
1
+ # Spree Veritrans
2
+
3
+ Veritrans Payment Gateway for Spree Commerce,
4
+ ( this is only to use VT-Direct methods with 3D Secure transactions / normal transactions )
5
+
6
+ ## How to use
7
+
8
+ ### Add gem spree_veritrans
9
+
10
+ ```ruby
11
+ gem 'spree_veritrans'
12
+ ```
13
+
14
+ bundle install
15
+
16
+ ### Configure Keys on Admin Section
17
+ Goto admin section page, create new Payment method and select
18
+ Spree::Gateway::VeritransGateway as the provider
19
+ click Save and add client_key, server_key and url_api, you
20
+ also can use the 3D secure by enable it on admin section.
21
+
22
+ #### Get help
23
+
24
+ * [Veritrans sandbox login](https://my.sandbox.veritrans.co.id/login)
25
+ * [Veritrans sandbox registration](https://my.sandbox.veritrans.co.id/register)
26
+ * [Veritrans documentation](http://docs.veritrans.co.id)
@@ -0,0 +1,130 @@
1
+ require 'base64'
2
+ require 'uri'
3
+ require 'excon'
4
+ require 'active_support/core_ext/hash/slice'
5
+
6
+ module ActiveMerchant
7
+ module Billing
8
+ class VeritransGateway < Gateway
9
+
10
+ # == Veritrans Error Codes
11
+ # 400 Validation error ( Validation Error, merchant send bad request data example; validation error )
12
+ # => invalid transaction type
13
+ # => invalid credit card format
14
+ #
15
+ # 401 Access Denied Invalid client key or server key
16
+ # 402 Access Denied Merchant doesn't have access for this payment type
17
+ # 406 Duplicate Order ID Order ID has been used before or transaction has been paid.
18
+ # 410 Account Inactive Merchant account is deactivated or not active for a long period
19
+ # 411 Token error Token id is missing, invalid, or timed out
20
+
21
+ self.supported_cardtypes = [:visa, :master]
22
+ self.display_name = 'Veritrans'
23
+
24
+ # POST /v2/charge { payment_type: "vtdirect" }
25
+ # Docs http://docs.veritrans.co.id/sandbox/charge.html
26
+ def charge(data, config)
27
+ make_request(:post, config[:url_api] + "/v2/charge", data, config[:server_key])
28
+ end
29
+
30
+ # POST /v2/{id}/cancel
31
+ # Docs http://docs.veritrans.co.id/en/api/methods.html#Cancel
32
+ def cancel(transaction_id, config)
33
+ make_request(:post, config[:url_api] + "/v2/#{URI.escape(transaction_id)}/cancel", {}, config[:server_key])
34
+ end
35
+
36
+ # POST /v2/{id}/approve
37
+ # Docs http://docs.veritrans.co.id/en/api/methods.html#Approve
38
+ def approve(payment_id, options = {})
39
+ #request_with_logging(:post, config.api_host + "/v2/#{URI.escape(payment_id)}/approve", options)
40
+ end
41
+
42
+ # POST /v2/capture
43
+ # Docs http://docs.veritrans.co.id/en/api/methods.html#Capture
44
+ def capture(transaction_id, gross_amount, config)
45
+ make_request(:post, config[:url_api] + "/v2/capture", { transaction_id: transaction_id, gross_amount: gross_amount }, config[:server_key])
46
+ end
47
+
48
+ private
49
+
50
+ def basic_auth_header(server_key)
51
+ key = Base64.strict_encode64(server_key + ":")
52
+ "Basic #{key}"
53
+ end
54
+
55
+ def make_request(method, url, data, server_key = SERVER_KEY)
56
+ default_options = {}
57
+
58
+ # Add authentication and content type
59
+ # Docs http://docs.veritrans.co.id/sandbox/introduction.html
60
+ request_options = {
61
+ :path => URI.parse(url).path,
62
+ :headers => {
63
+ :Authorization => basic_auth_header(server_key),
64
+ :Accept => "application/json",
65
+ :"Content-Type" => "application/json",
66
+ :"User-Agent" => "Spree Veritrans gateway engine"
67
+ }
68
+ }
69
+
70
+ if method.to_s.upcase == "GET"
71
+ request_options[:query] = URI.encode_www_form(data)
72
+ else
73
+ request_options[:body] = _json_encode(data)
74
+ end
75
+
76
+ connection_options = {
77
+ read_timeout: 40,
78
+ write_timeout: 40,
79
+ connect_timeout: 40
80
+ }.deep_merge(default_options)
81
+
82
+ request = Excon.new(url, connection_options)
83
+ response = request.send(method, request_options)
84
+
85
+ result = _json_decode(response.body)
86
+ result = result.inject({}){|res,(k,v)| res[k.to_sym] = v; res}
87
+ success = result[:status_code] == "200"
88
+ msg = result[:status_message]
89
+
90
+ Response.new(success, msg, result, {
91
+ fraud_review: result[:fraud_status],
92
+ authorization: result[:transaction_id],
93
+ error_code: success ? nil : result[:status_code]
94
+ })
95
+
96
+ rescue Excon::Errors::SocketError => error
97
+ msg = "Internal server error, no response from backend. Try again later"
98
+ error_response = Excon::Response.new(
99
+ body: "{'status_code': '500', 'status_message': #{msg} }",
100
+ status: '500'
101
+ )
102
+ result = _json_decode(error_response.body)
103
+ result = result.inject({}){|res,(k,v)| res[k.to_sym] = v; res}
104
+ Response.new(false, msg, result, {
105
+ error_code: "500"
106
+ })
107
+ end
108
+
109
+ # Failback for activesupport
110
+ def _json_encode(params)
111
+ if defined?(ActiveSupport) && defined?(ActiveSupport::JSON)
112
+ ActiveSupport::JSON.encode(params)
113
+ else
114
+ require 'json' unless defined?(JSON)
115
+ JSON.generate(params)
116
+ end
117
+ end
118
+
119
+ def _json_decode(params)
120
+ if defined?(ActiveSupport) && defined?(ActiveSupport::JSON)
121
+ ActiveSupport::JSON.decode(params)
122
+ else
123
+ require 'json' unless defined?(JSON)
124
+ JSON.parse(params)
125
+ end
126
+ end
127
+
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,88 @@
1
+ require "countries"
2
+ require "money"
3
+
4
+ module Spree
5
+ class Gateway::VeritransGateway < Gateway
6
+
7
+ preference :client_key, :string
8
+ preference :server_key, :string
9
+ preference :url_api, :string
10
+ preference :use_3d_secure, :boolean, :default => false
11
+
12
+ def method_type
13
+ 'veritrans'
14
+ end
15
+
16
+ def provider_class
17
+ ActiveMerchant::Billing::VeritransGateway
18
+ end
19
+
20
+ def purchase(money, creditcard, gateway_options)
21
+ money = veritrans_gross_amount(money)
22
+ provider.charge(create_data_for_auth_or_charge(money, creditcard, gateway_options), { url_api: preferred_url_api, server_key: preferred_server_key })
23
+ end
24
+
25
+ def authorize(money, creditcard, gateway_options)
26
+ money = veritrans_gross_amount(money)
27
+ provider.charge(create_data_for_auth_or_charge(money, creditcard, gateway_options, preferred_use_3d_secure ? nil : "authorize"), { url_api: preferred_url_api, server_key: preferred_server_key })
28
+ end
29
+
30
+ def capture(money, response_code, gateway_options)
31
+ money = veritrans_gross_amount(money)
32
+ provider.capture(response_code, money, { url_api: preferred_url_api, server_key: preferred_server_key })
33
+ end
34
+
35
+ def void(response_code, gateway_options)
36
+ provider.cancel(response_code, { url_api: preferred_url_api, server_key: preferred_server_key })
37
+ end
38
+
39
+ def cancel(response_code)
40
+ provider.cancel(response_code, { url_api: preferred_url_api, server_key: preferred_server_key })
41
+ end
42
+
43
+ private
44
+
45
+ def veritrans_gross_amount(money)
46
+ money = money.to_s
47
+ 2.times { money.chomp!("0") }
48
+ money.to_i
49
+ end
50
+
51
+ def create_data_for_auth_or_charge(money, creditcard, gateway_options, type = nil)
52
+ data = {}
53
+ data[:payment_type] = "credit_card"
54
+ data[:transaction_details] = { order_id: gateway_options[:order_id], gross_amount: money }
55
+ data[:credit_card] = {}
56
+ data[:credit_card][:token_id] = creditcard.gateway_payment_profile_id if creditcard.gateway_payment_profile_id
57
+ data[:credit_card][:type] = type unless type.nil?
58
+ data[:customer_details] = {}
59
+ data[:customer_details][:first_name] = creditcard.name
60
+ data[:customer_details][:email] = gateway_options[:email]
61
+ data[:customer_details][:billing_address] = {}
62
+ data[:customer_details][:billing_address][:first_name] = gateway_options[:billing_address][:name]
63
+ data[:customer_details][:billing_address][:last_name] = ""
64
+ data[:customer_details][:billing_address][:address] = "#{ gateway_options[:billing_address][:address1] } #{ gateway_options[:billing_address][:address2] }"
65
+ data[:customer_details][:billing_address][:city] = gateway_options[:billing_address][:city]
66
+ data[:customer_details][:billing_address][:postal_code] = gateway_options[:billing_address][:zip]
67
+ data[:customer_details][:billing_address][:phone] = gateway_options[:billing_address][:phone]
68
+ data[:customer_details][:billing_address][:country_code] = get_alpha3_country_code(gateway_options[:billing_address][:country])
69
+ data[:customer_details][:shipping_address] = {}
70
+ data[:customer_details][:shipping_address][:first_name] = gateway_options[:shipping_address][:name]
71
+ data[:customer_details][:shipping_address][:last_name] = ""
72
+ data[:customer_details][:shipping_address][:address] = "#{ gateway_options[:shipping_address][:address1] } #{ gateway_options[:billing_address][:address2] }"
73
+ data[:customer_details][:shipping_address][:city] = gateway_options[:shipping_address][:city]
74
+ data[:customer_details][:shipping_address][:postal_code] = gateway_options[:shipping_address][:zip]
75
+ data[:customer_details][:shipping_address][:phone] = gateway_options[:shipping_address][:phone]
76
+ data[:customer_details][:shipping_address][:country_code] = get_alpha3_country_code(gateway_options[:shipping_address][:country])
77
+ return data
78
+ end
79
+
80
+ def get_alpha3_country_code(country_code)
81
+ c = ISO3166::Country.new(country_code)
82
+ c.data["alpha3"]
83
+ rescue
84
+ return ""
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,12 @@
1
+ ---
2
+ en:
3
+ spree:
4
+ payment_has_been_cancelled: The payment has been cancelled.
5
+ log_entry:
6
+ veritrans:
7
+ message: Message
8
+ charge_id: Veritrans Charge ID
9
+ card_id: Veritrans Card ID
10
+ cvv_result: CVV Result
11
+ cvc_check: CVC Check
12
+ address_zip_check: Address ZIP check
@@ -0,0 +1 @@
1
+ //= require spree/frontend
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require spree/frontend
3
+ */
@@ -0,0 +1,4 @@
1
+ require 'spree_core'
2
+ require 'spree_veritrans/engine'
3
+ require 'coffee_script'
4
+ require 'sass/rails'
@@ -0,0 +1,42 @@
1
+ module SpreeVeritrans
2
+ class Engine < Rails::Engine
3
+ engine_name 'spree_veritrans'
4
+
5
+ config.autoload_paths += %W(#{config.root}/lib)
6
+
7
+ initializer "spree.gateway.payment_methods", :after => "spree.register.payment_methods" do |app|
8
+ app.config.spree.payment_methods << Spree::Gateway::VeritransGateway
9
+ end
10
+
11
+ def self.activate
12
+ if SpreeVeritrans::Engine.frontend_available?
13
+ Rails.application.config.assets.precompile += [
14
+ 'lib/assets/javascripts/spree/frontend/spree_veritrans.js',
15
+ 'lib/assets/javascripts/spree/frontend/spree_veritrans.css',
16
+ ]
17
+ Dir.glob(File.join(File.dirname(__FILE__), "../../controllers/frontend/*/*_decorator*.rb")) do |c|
18
+ Rails.configuration.cache_classes ? require(c) : load(c)
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.backend_available?
24
+ @@backend_available ||= ::Rails::Engine.subclasses.map(&:instance).map{ |e| e.class.to_s }.include?('Spree::Backend::Engine')
25
+ end
26
+
27
+ def self.frontend_available?
28
+ @@frontend_available ||= ::Rails::Engine.subclasses.map(&:instance).map{ |e| e.class.to_s }.include?('Spree::Frontend::Engine')
29
+ end
30
+
31
+ if self.backend_available?
32
+ paths["app/views"] << "lib/views/backend"
33
+ end
34
+
35
+ if self.frontend_available?
36
+ paths["app/controllers"] << "lib/controllers/frontend"
37
+ paths["app/views"] << "lib/views/frontend"
38
+ end
39
+
40
+ config.to_prepare &method(:activate).to_proc
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ <tr>
2
+ <td><%= Spree.t(:message, :scope => [:log_entry, :veritrans]) %></td>
3
+ <td><%= entry.parsed_details.message %></td>
4
+ </tr>
5
+ <tr>
6
+ <td><%= Spree.t(:charge_id, :scope => [:log_entry, :veritrans]) %></td>
7
+ <td><%= entry.parsed_details.params['id'] %></td>
8
+ </tr>
9
+ <% if card = entry.parsed_details.params['card'] %>
10
+ <tr>
11
+ <td><%= Spree.t(:card_id, :scope => [:log_entry, :veritrans]) %></td>
12
+ <td><%= card['id'] %></td>
13
+ </tr>
14
+
15
+ <tr>
16
+ <td><%= Spree.t(:cvc_check, :scope => [:log_entry, :veritrans]) %></td>
17
+ <td><%= card['cvc_check'] %></td>
18
+ </tr>
19
+
20
+ <tr>
21
+ <td><%= Spree.t(:address_zip_check, :scope => [:log_entry, :veritrans]) %></td>
22
+ <td><%= card['address_zip_check'] %></td>
23
+ </tr>
24
+ <% end %>
25
+ <tr>
26
+ <td><%= Spree.t(:cvv_result, :scope => [:log_entry, :veritrans]) %></td>
27
+ <td><%= entry.parsed_details.cvv_result['message'].to_s %></td>
28
+ </tr>
@@ -0,0 +1 @@
1
+ <%= render "spree/admin/payments/source_forms/gateway", payment_method: payment_method, previous_cards: payment_method.reusable_sources(@order) %>
@@ -0,0 +1 @@
1
+ <%= render "spree/admin/payments/source_views/gateway", payment: payment %>
@@ -0,0 +1,109 @@
1
+ <%= render "spree/checkout/payment/gateway", payment_method: payment_method %>
2
+
3
+ <%= javascript_include_tag "#{payment_method.preferred_url_api}/v2/assets/veritrans.js" %>
4
+
5
+ <script type="text/javascript">
6
+ Veritrans.url = "<%= payment_method.preferred_url_api %>/v2/token";
7
+ Veritrans.client_key = "<%= payment_method.preferred_client_key %>";
8
+ </script>
9
+
10
+ <script>
11
+ var param_map = {
12
+ card_number: '#card_number',
13
+ card_exp_month: '#card_expiry',
14
+ card_exp_year: '#card_expiry',
15
+ card_cvv: '#card_code'
16
+ }
17
+ <%- if payment_method.preferred_use_3d_secure -%>
18
+ // Open 3DSecure dialog box
19
+ function openDialog(url) {
20
+ $('.modal').on('shown.bs.modal',function(){
21
+ $(this).find('iframe').attr('src',url);
22
+ })
23
+ $('.modal').modal({show:true});
24
+ }
25
+
26
+ // Close 3DSecure dialog box
27
+ function closeDialog() {
28
+ $('.modal').modal('hide');
29
+ }
30
+ <%- end -%>
31
+
32
+ // data params
33
+ function VT_createTokenData() {
34
+ var expiration = $('.cardExpiry:visible').payment('cardExpiryVal');
35
+ return {
36
+ card_number: $('.cardNumber:visible').val(),
37
+ card_cvv: $('.cardCode:visible').val(),
38
+ card_exp_month: expiration.month || 0,
39
+ card_exp_year: expiration.year || 0,
40
+ <%- if payment_method.preferred_use_3d_secure -%>
41
+ secure: true,
42
+ <%- end -%>
43
+ gross_amount: <%= @order.total.to_i -%>
44
+ };
45
+ }
46
+
47
+ Spree.veritransPaymentMethod = $('#payment_method_' + <%= payment_method.id %>);
48
+
49
+ var veritransResponseHandler = function(response) {
50
+ var paymentMethodId, token_id;
51
+ <%- if payment_method.preferred_use_3d_secure -%>
52
+ if (response.redirect_url) {
53
+ Spree.veritransPaymentMethod.find('#card_number, #card_expiry, #card_code').prop("disabled", true);
54
+ openDialog(response.redirect_url);
55
+ }
56
+ else
57
+ <%- end -%>
58
+ if (response.status_code == '200') {
59
+ <%- if payment_method.preferred_use_3d_secure -%>
60
+ // success 3d secure or success normal
61
+ //close 3d secure dialog if any
62
+ closeDialog();
63
+ <%- end -%>
64
+ token_id = response.token_id;
65
+ paymentMethodId = Spree.veritransPaymentMethod.prop('id').split("_")[2];
66
+ // store token data in input #token_id and then submit form to merchant server
67
+ Spree.veritransPaymentMethod.append("<input type='hidden' class='veritransToken' name='payment_source[" + paymentMethodId + "][gateway_payment_profile_id]' value='" + token_id + "'/>");
68
+ return Spree.veritransPaymentMethod.parents("form").trigger('submit');
69
+ } else {
70
+ <%- if payment_method.preferred_use_3d_secure -%>
71
+ closeDialog();
72
+ <%- end -%>
73
+ $('#veritransError').html(response.status_message);
74
+ if (response.validation_messages) {
75
+ $.map( response.validation_messages, function( data, i ) {
76
+ $.each([ "card_number", "card_cvv", "card_exp_month", "card_exp_year" ], function( i, val ) {
77
+ if(data.indexOf(val) !== -1) {
78
+ Spree.veritransPaymentMethod.find(param_map[val]).addClass('error');
79
+ }
80
+ });
81
+ });
82
+ }
83
+ return $('#veritransError').show();
84
+ }
85
+ };
86
+
87
+ $(document).ready(function() {
88
+ Spree.veritransPaymentMethod.prepend("<div id='veritransError' class='errorExplanation' style='display:none'></div>");
89
+ return $('#checkout_form_payment [data-hook=buttons]').click(function() {
90
+ $('#veritransError').hide();
91
+ Spree.veritransPaymentMethod.find('#card_number, #card_expiry, #card_code').removeClass('error');
92
+ if (Spree.veritransPaymentMethod.is(':visible')) {
93
+ Veritrans.token(VT_createTokenData, veritransResponseHandler);
94
+ return false;
95
+ }
96
+ });
97
+ });
98
+ </script>
99
+
100
+ <%- if payment_method.preferred_use_3d_secure -%>
101
+ <div id="veritansModal" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
102
+ <div class="modal-dialog modal-lg" style="width:400px">
103
+ <div class="modal-content">
104
+ <iframe src="" frameborder="0" width="400px" height="420px"></iframe>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ <%- end -%>
109
+
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Veritrans checkout" do
4
+ let!(:country) { create(:country, :states_required => true) }
5
+ let!(:state) { create(:state, :country => country) }
6
+ let!(:shipping_method) { create(:shipping_method) }
7
+ let!(:stock_location) { create(:stock_location) }
8
+ let!(:mug) { create(:product, :name => "RoR Mug") }
9
+ let!(:stripe_payment_method) do
10
+ Spree::Gateway::VeritransGateway.create!(
11
+ :name => "Veritrans",
12
+ :preferred_client_key => "VT-client-BO3Lm5WmKlzxg9FI",
13
+ :preferred_server_key => "VT-server-HvU2kqGsVjV08_t79cN8yPiK",
14
+ :preferred_url_api => "https://api.sandbox.veritrans.co.id"
15
+ )
16
+ end
17
+
18
+ let!(:zone) { create(:zone) }
19
+
20
+ before do
21
+ user = create(:user)
22
+
23
+ order = OrderWalkthrough.up_to(:delivery)
24
+ order.stub :confirmation_required? => true
25
+
26
+ order.reload
27
+ order.user = user
28
+ order.update!
29
+
30
+ Spree::CheckoutController.any_instance.stub(:current_order => order)
31
+ Spree::CheckoutController.any_instance.stub(:try_spree_current_user => user)
32
+ Spree::CheckoutController.any_instance.stub(:skip_state_validation? => true)
33
+
34
+ visit spree.checkout_state_path(:payment)
35
+ end
36
+
37
+ # This will pass the CC data to the server and the VeritransGateway class handles it
38
+ it "cannot process a valid payment without tokenid" do
39
+ fill_in "Card Number", :with => "4011 1111 1111 1112"
40
+ fill_in "Card Code", :with => "123"
41
+ fill_in "Expiration", :with => "01 / 20"
42
+ click_button "Save and Continue"
43
+ page.current_url.should include("/checkout/confirm")
44
+ click_button "Place Order"
45
+ page.should have_content("Token id is missing, invalid, or timed out")
46
+ end
47
+
48
+ # This will fetch a token from Veritrans.com and then pass that to the webserver.
49
+ # The server then processes the payment using that token.
50
+ it "can process a valid payment (with JS)", :js => true do
51
+ fill_in "Card Number", :with => "4011 1111 1111 1112"
52
+ # Otherwise ccType field does not get updated correctly
53
+ page.execute_script("$('.cardNumber').trigger('change')")
54
+ fill_in "Card Code", :with => "123"
55
+ fill_in "Expiration", :with => "01 / 20"
56
+ click_button "Save and Continue"
57
+ sleep(5) # Wait for Veritrans API to return + form to submit
58
+ page.current_url.should include("/checkout/confirm")
59
+ click_button "Place Order"
60
+ page.should have_content("Your order has been processed successfully")
61
+ end
62
+
63
+ it "shows an error with an invalid credit card number", :js => true do
64
+ click_button "Save and Continue"
65
+ page.should have_content("Invalid card_number association")
66
+ page.should have_css('#card_number.error')
67
+ end
68
+
69
+ it "shows an error with invalid security fields", :js => true do
70
+ fill_in "Card Number", :with => "4011 1111 1111 1112"
71
+ fill_in "Expiration", :with => "01 / 20"
72
+ click_button "Save and Continue"
73
+ page.should have_content("card_cvv must be 3 digits")
74
+ page.should have_content("card_cvv is required")
75
+ page.should have_content("card_cvv does not match with regular expression")
76
+ page.should have_css('#card_code.error')
77
+ end
78
+
79
+ it "shows an error with invalid expiry month field", :js => true do
80
+ fill_in "Card Number", :with => "4011 1111 1111 1112"
81
+ fill_in "Expiration", :with => "00 / 20"
82
+ fill_in "Card Code", :with => "123"
83
+ click_button "Save and Continue"
84
+ page.should have_content("card_exp_month must be between 1 and 12")
85
+ page.should have_css('#card_expiry.error')
86
+ end
87
+
88
+ it "shows an error with invalid expiry year field", :js => true do
89
+ fill_in "Card Number", :with => "4011 1111 1111 1112"
90
+ fill_in "Expiration", :with => "12 / "
91
+ fill_in "Card Code", :with => "123"
92
+ click_button "Save and Continue"
93
+ page.should have_content("card_exp_year must be greater than this year")
94
+ page.should have_content("card_expire_month must be greater than this year's month")
95
+ page.should have_content("card_exp_year must be 4 digits")
96
+ page.should have_css('#card_expiry.error')
97
+ end
98
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Gateway::VeritransGateway do
4
+ let(:client_key) { 'XXXX-YYYY' }
5
+ let(:server_key) { 'YYYY-ZZZZ' }
6
+ let(:url_api) { 'https://api/url' }
7
+ let(:email) { 'customer@example.com' }
8
+ let(:source) { Spree::CreditCard.new(gateway_payment_profile_id: 'tok_profileid') }
9
+
10
+ let(:payment) {
11
+ double('Spree::Payment',
12
+ source: source,
13
+ order: double('Spree::Order',
14
+ email: email,
15
+ bill_address: bill_address
16
+ )
17
+ )
18
+ }
19
+
20
+ let(:provider) do
21
+ double('provider').tap do |p|
22
+ p.stub(:purchase)
23
+ p.stub(:authorize)
24
+ p.stub(:capture)
25
+ end
26
+ end
27
+
28
+ let(:gateway_options) {
29
+ {
30
+ order_id: "xxx",
31
+ email: email,
32
+ billing_address: {
33
+ name: "#{ Faker::Name.first_name } #{ Faker::Name.last_name }",
34
+ address1: Faker::Address.street_address,
35
+ address2: Faker::Address.street_name,
36
+ city: Faker::Address.city,
37
+ zip: Faker::AddressUS.zip_code,
38
+ phone: Faker::PhoneNumber.phone_number,
39
+ country: Faker::Address.country_code
40
+ },
41
+ shipping_address: {
42
+ name: "#{ Faker::Name.first_name } #{ Faker::Name.last_name }",
43
+ address1: Faker::Address.street_address,
44
+ address2: Faker::Address.street_name,
45
+ city: Faker::Address.city,
46
+ zip: Faker::AddressUS.zip_code,
47
+ phone: Faker::PhoneNumber.phone_number,
48
+ country: Faker::Address.country_code
49
+ }
50
+ }
51
+ }
52
+
53
+ before do
54
+ subject.preferences = { client_key: client_key, server_key: server_key, url_api: url_api }
55
+ subject.stub(:provider).and_return provider
56
+ end
57
+
58
+ context 'purchasing' do
59
+ after do
60
+ subject.purchase(19900000, source, gateway_options)
61
+ end
62
+
63
+ it 'send the payment to the provider' do
64
+ provider.should_receive(:charge).with(anything, { url_api: url_api, server_key: server_key })
65
+ end
66
+ end
67
+
68
+ context 'authorizing' do
69
+ after do
70
+ subject.authorize(19900000, source, gateway_options)
71
+ end
72
+
73
+ it 'send the authorization to the provider' do
74
+ provider.should_receive(:charge).with(anything, { url_api: url_api, server_key: server_key })
75
+ end
76
+ end
77
+
78
+ context 'capturing' do
79
+ after do
80
+ subject.capture(12000000, '1234', {})
81
+ end
82
+
83
+ it 'convert the amount to gross_amount' do
84
+ provider.should_receive(:capture).with('1234', 120000, { url_api: url_api, server_key: server_key })
85
+ end
86
+ end
87
+
88
+ context 'capture with payment class' do
89
+ let(:gateway) do
90
+ gateway = described_class.new(active: true)
91
+ gateway.set_preference :server_key, server_key
92
+ gateway.set_preference :url_api, url_api
93
+ gateway.stub(:provider).and_return provider
94
+ gateway.stub :source_required => true
95
+ gateway
96
+ end
97
+
98
+ let(:order) { Spree::Order.create }
99
+
100
+ let(:card) do
101
+ # mock_model(Spree::CreditCard, :gateway_customer_profile_id => 'cus_abcde',
102
+ # :imported => false)
103
+ create :credit_card, gateway_customer_profile_id: 'cus_abcde', imported: false
104
+ end
105
+
106
+ let(:payment) do
107
+ payment = Spree::Payment.new
108
+ payment.source = card
109
+ payment.order = order
110
+ payment.payment_method = gateway
111
+ payment.amount = 9855
112
+ payment.state = 'pending'
113
+ payment.response_code = '12345'
114
+ payment
115
+ end
116
+
117
+ let!(:success_response) do
118
+ double('success_response', :success? => true,
119
+ :authorization => '123',
120
+ :avs_result => { 'code' => 'avs-code' },
121
+ :cvv_result => { 'code' => 'cvv-code', 'message' => "CVV Result"})
122
+ end
123
+
124
+ after do
125
+ payment.capture!
126
+ end
127
+
128
+ it 'gets correct amount' do
129
+ provider.should_receive(:capture).with('12345', 9855,{ url_api: url_api, server_key: server_key }).and_return(success_response)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,50 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+
3
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
4
+
5
+ require 'rspec/rails'
6
+ require 'rspec/active_model/mocks'
7
+ require 'capybara/rspec'
8
+ require 'capybara/rails'
9
+ require 'capybara/poltergeist'
10
+ require 'database_cleaner'
11
+ require 'ffaker'
12
+ require 'pry'
13
+ require 'rspec/active_model/mocks'
14
+
15
+ Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
16
+
17
+ require 'spree/testing_support/factories'
18
+ require 'spree/testing_support/order_walkthrough'
19
+ require 'spree/testing_support/preferences'
20
+
21
+ FactoryGirl.find_definitions
22
+
23
+ RSpec.configure do |config|
24
+ config.infer_spec_type_from_file_location!
25
+ config.mock_with :rspec
26
+ config.raise_errors_for_deprecations!
27
+ config.use_transactional_fixtures = false
28
+ #config.filter_run focus: true
29
+ #config.filter_run_excluding slow: true
30
+
31
+ config.include FactoryGirl::Syntax::Methods
32
+ config.include Spree::TestingSupport::Preferences
33
+
34
+ config.before :suite do
35
+ DatabaseCleaner.strategy = :transaction
36
+ DatabaseCleaner.clean_with :truncation
37
+ end
38
+
39
+ config.before do
40
+ DatabaseCleaner.strategy = RSpec.current_example.metadata[:js] ? :truncation : :transaction
41
+ DatabaseCleaner.start
42
+ reset_spree_preferences
43
+ end
44
+
45
+ config.after do
46
+ DatabaseCleaner.clean
47
+ end
48
+
49
+ Capybara.javascript_driver = :poltergeist
50
+ end
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ Gem::Specification.new do |s|
3
+ s.platform = Gem::Platform::RUBY
4
+ s.name = 'spree_veritrans'
5
+ s.version = '1.0.0'
6
+ s.summary = 'Additional Payment Gateways for Spree Commerce using Veritrans'
7
+ s.description = s.summary
8
+
9
+ s.author = 'fajri fachriansyah'
10
+ s.email = 'fajri82@gmail.com'
11
+ s.homepage = 'http://www.spreecommerce.com'
12
+ s.license = %q{BSD-3}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- spec/*`.split("\n")
16
+ s.require_path = 'lib'
17
+ s.requirements << 'none'
18
+
19
+ s.add_dependency 'spree_core', '~> 3.0.0'
20
+ s.add_runtime_dependency 'excon', '~> 0.20'
21
+ s.add_runtime_dependency 'countries', '1.0.0'
22
+
23
+ s.add_development_dependency 'capybara'
24
+ s.add_development_dependency 'coffee-rails', '~> 4.0.0'
25
+ s.add_development_dependency 'database_cleaner', '1.2.0'
26
+ s.add_development_dependency 'factory_girl', '~> 4.4'
27
+ s.add_development_dependency 'ffaker'
28
+ s.add_development_dependency 'launchy'
29
+ s.add_development_dependency 'sqlite3'
30
+ s.add_development_dependency 'poltergeist', '~> 1.6.0'
31
+ s.add_development_dependency 'pry'
32
+ s.add_development_dependency 'rspec-activemodel-mocks'
33
+ s.add_development_dependency 'rspec-rails', '~> 2.99'
34
+ s.add_development_dependency 'sass-rails', '~> 4.0.2'
35
+ s.add_development_dependency 'selenium-webdriver'
36
+ end
metadata ADDED
@@ -0,0 +1,290 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_veritrans
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - fajri fachriansyah
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: spree_core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: excon
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.20'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.20'
41
+ - !ruby/object:Gem::Dependency
42
+ name: countries
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: capybara
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coffee-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 4.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 4.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: database_cleaner
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '='
88
+ - !ruby/object:Gem::Version
89
+ version: 1.2.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '='
95
+ - !ruby/object:Gem::Version
96
+ version: 1.2.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: factory_girl
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.4'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.4'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ffaker
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: launchy
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sqlite3
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: poltergeist
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 1.6.0
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 1.6.0
167
+ - !ruby/object:Gem::Dependency
168
+ name: pry
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rspec-activemodel-mocks
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rspec-rails
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '2.99'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '2.99'
209
+ - !ruby/object:Gem::Dependency
210
+ name: sass-rails
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: 4.0.2
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: 4.0.2
223
+ - !ruby/object:Gem::Dependency
224
+ name: selenium-webdriver
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ description: Additional Payment Gateways for Spree Commerce using Veritrans
238
+ email: fajri82@gmail.com
239
+ executables: []
240
+ extensions: []
241
+ extra_rdoc_files: []
242
+ files:
243
+ - ".gitignore"
244
+ - ".rspec"
245
+ - Gemfile
246
+ - README.md
247
+ - app/models/active_merchant/billing/veritrans_gateway.rb
248
+ - app/models/spree/gateway/veritrans_gateway.rb
249
+ - config/locales/en.yml
250
+ - lib/assets/javascripts/spree/frontend/spree_veritans.js
251
+ - lib/assets/stylesheets/spree/frontend/spree_veritrans.css
252
+ - lib/spree_veritrans.rb
253
+ - lib/spree_veritrans/engine.rb
254
+ - lib/views/backend/spree/admin/log_entries/_veritrans.html.erb
255
+ - lib/views/backend/spree/admin/payments/source_forms/_veritrans.html.erb
256
+ - lib/views/backend/spree/admin/payments/source_views/_veritrans.html.erb
257
+ - lib/views/frontend/spree/checkout/payment/_veritrans.html.erb
258
+ - spec/features/veritrans_checkout_spec.rb
259
+ - spec/models/gateway/veritrans_gateway_spec.rb
260
+ - spec/spec_helper.rb
261
+ - spree_veritrans.gemspec
262
+ homepage: http://www.spreecommerce.com
263
+ licenses:
264
+ - BSD-3
265
+ metadata: {}
266
+ post_install_message:
267
+ rdoc_options: []
268
+ require_paths:
269
+ - lib
270
+ required_ruby_version: !ruby/object:Gem::Requirement
271
+ requirements:
272
+ - - ">="
273
+ - !ruby/object:Gem::Version
274
+ version: '0'
275
+ required_rubygems_version: !ruby/object:Gem::Requirement
276
+ requirements:
277
+ - - ">="
278
+ - !ruby/object:Gem::Version
279
+ version: '0'
280
+ requirements:
281
+ - none
282
+ rubyforge_project:
283
+ rubygems_version: 2.4.8
284
+ signing_key:
285
+ specification_version: 4
286
+ summary: Additional Payment Gateways for Spree Commerce using Veritrans
287
+ test_files:
288
+ - spec/features/veritrans_checkout_spec.rb
289
+ - spec/models/gateway/veritrans_gateway_spec.rb
290
+ - spec/spec_helper.rb