spree_veritrans 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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