workarea-worldpay_xml 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +20 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
  4. data/.github/ISSUE_TEMPLATE/documentation-request.md +17 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.gitignore +19 -0
  7. data/.rails-rubocop.yml +130 -0
  8. data/.rubocop.yml +14 -0
  9. data/CHANGELOG.md +21 -0
  10. data/CODE_OF_CONDUCT.md +3 -0
  11. data/CONTRIBUTING.md +3 -0
  12. data/Gemfile +10 -0
  13. data/LICENSE +52 -0
  14. data/README.md +63 -0
  15. data/Rakefile +60 -0
  16. data/app/models/workarea/payment/authorize/credit_card.decorator +36 -0
  17. data/app/models/workarea/payment/credit_card_data.rb +48 -0
  18. data/app/models/workarea/payment/purchase/credit_card.decorator +36 -0
  19. data/app/models/workarea/payment/refund/credit_card.decorator +28 -0
  20. data/app/models/workarea/payment/store_credit_card.decorator +27 -0
  21. data/app/models/workarea/payment/tender/credit_card.decorator +7 -0
  22. data/bin/rails +20 -0
  23. data/config/initializers/workarea.rb +1 -0
  24. data/lib/active_merchant/billing/bogus_worldpay_gateway.rb +65 -0
  25. data/lib/active_merchant/billing/worldpay_tokenize.rb +118 -0
  26. data/lib/workarea/worldpay_xml.rb +48 -0
  27. data/lib/workarea/worldpay_xml/engine.rb +8 -0
  28. data/lib/workarea/worldpay_xml/version.rb +5 -0
  29. data/test/dummy/Rakefile +6 -0
  30. data/test/dummy/app/assets/config/manifest.js +4 -0
  31. data/test/dummy/app/assets/images/.keep +0 -0
  32. data/test/dummy/app/assets/javascripts/application.js +13 -0
  33. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  34. data/test/dummy/app/controllers/application_controller.rb +3 -0
  35. data/test/dummy/app/controllers/concerns/.keep +0 -0
  36. data/test/dummy/app/helpers/application_helper.rb +2 -0
  37. data/test/dummy/app/jobs/application_job.rb +2 -0
  38. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  39. data/test/dummy/app/models/concerns/.keep +0 -0
  40. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  41. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  42. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  43. data/test/dummy/bin/bundle +3 -0
  44. data/test/dummy/bin/rails +4 -0
  45. data/test/dummy/bin/rake +4 -0
  46. data/test/dummy/bin/setup +30 -0
  47. data/test/dummy/bin/update +26 -0
  48. data/test/dummy/bin/yarn +11 -0
  49. data/test/dummy/config.ru +5 -0
  50. data/test/dummy/config/application.rb +31 -0
  51. data/test/dummy/config/boot.rb +5 -0
  52. data/test/dummy/config/cable.yml +10 -0
  53. data/test/dummy/config/environment.rb +5 -0
  54. data/test/dummy/config/environments/development.rb +51 -0
  55. data/test/dummy/config/environments/production.rb +88 -0
  56. data/test/dummy/config/environments/test.rb +44 -0
  57. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  58. data/test/dummy/config/initializers/assets.rb +14 -0
  59. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  61. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  62. data/test/dummy/config/initializers/inflections.rb +16 -0
  63. data/test/dummy/config/initializers/mime_types.rb +4 -0
  64. data/test/dummy/config/initializers/workarea.rb +5 -0
  65. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  66. data/test/dummy/config/locales/en.yml +33 -0
  67. data/test/dummy/config/puma.rb +56 -0
  68. data/test/dummy/config/routes.rb +5 -0
  69. data/test/dummy/config/secrets.yml +32 -0
  70. data/test/dummy/config/spring.rb +6 -0
  71. data/test/dummy/db/seeds.rb +2 -0
  72. data/test/dummy/lib/assets/.keep +0 -0
  73. data/test/dummy/log/.keep +0 -0
  74. data/test/dummy/package.json +5 -0
  75. data/test/models/workarea/payment/authorize/credit_card_test.decorator +40 -0
  76. data/test/models/workarea/payment/capture_test.decorator +10 -0
  77. data/test/models/workarea/payment/credit_card_integration_test.decorator +24 -0
  78. data/test/models/workarea/payment/purchase/credit_card_test.decorator +25 -0
  79. data/test/models/workarea/payment/refund/credit_card_test.decorator +24 -0
  80. data/test/models/workarea/payment/refund_test.decorator +10 -0
  81. data/test/models/workarea/payment/store_credit_card_test.decorator +11 -0
  82. data/test/teaspoon_env.rb +6 -0
  83. data/test/test_helper.rb +10 -0
  84. data/test/workers/workarea/send_refund_email_test.decorator +29 -0
  85. data/workarea-worldpay_xml.gemspec +17 -0
  86. metadata +141 -0
@@ -0,0 +1,36 @@
1
+ module Workarea
2
+ decorate Payment::Authorize::CreditCard, with: :worldpay_xml_api do
3
+ decorated do
4
+ include Payment::CreditCardData
5
+ delegate :address, to: :tender
6
+ end
7
+
8
+ def complete!
9
+ return unless Payment::StoreCreditCard.new(tender, options).save!
10
+
11
+ transaction.response = handle_active_merchant_errors do
12
+ gateway.authorize(
13
+ transaction.amount.cents,
14
+ tender.to_token_or_active_merchant,
15
+ transaction_options
16
+ )
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def transaction_options
23
+ {
24
+ order_id: order_id,
25
+ pay_by_token: true,
26
+ token: tender.token,
27
+ shopper_id: tender.profile.email,
28
+ email: tender.profile.email,
29
+ address: billing_address,
30
+ order_content: order_content,
31
+ currency: currency_code
32
+ }
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,48 @@
1
+ module Workarea
2
+ class Payment
3
+ module CreditCardData
4
+ def billing_address
5
+ {
6
+ name: "#{address.first_name} #{address.last_name}",
7
+ company: address.company,
8
+ address1: address.street,
9
+ city: address.city,
10
+ state: address.region,
11
+ country: address.country.try(:alpha2),
12
+ zip: address.postal_code,
13
+ phone: nil
14
+ }
15
+ end
16
+
17
+ def order
18
+ @order ||= Workarea::Order.find(tender.payment.id)
19
+ end
20
+
21
+ def order_content
22
+ @order_content ||= begin
23
+ contents = order.items.map { |oi| oi.sku }.join(',')
24
+ "Env: #{Rails.env}, Items: #{contents}"
25
+ end
26
+ end
27
+
28
+ def currency_code
29
+ @currency_code = order.total_price.currency.iso_code
30
+ end
31
+
32
+ # Worldpay requires a unique order code per transaction, even
33
+ # if the transaction was a failure and a new card is used.
34
+ # Appending the time stamp gives some historical context
35
+ # in the payment admin
36
+ #
37
+ # !!!! A duplicate order code could cause a users credit card
38
+ # to be charged multiple times. Make sure this method returns a unique
39
+ # value each time. !!!!
40
+ def order_id
41
+ @order_id ||= begin
42
+ stamp = DateTime.now.strftime('%Y_%m_%d_%H_%M_%S')
43
+ "#{tender.payment.id}_#{stamp}"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ module Workarea
2
+ decorate Payment::Purchase::CreditCard, with: :worldpay_xml_api do
3
+ decorated do
4
+ include Payment::CreditCardData
5
+ delegate :address, to: :tender
6
+ end
7
+
8
+ def complete!
9
+ return unless Workarea::Payment::StoreCreditCard.new(tender, options).save!
10
+
11
+ transaction.response = handle_active_merchant_errors do
12
+ gateway.purchase(
13
+ transaction.amount.cents,
14
+ tender.to_token_or_active_merchant,
15
+ transaction_options
16
+ )
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def transaction_options
23
+ {
24
+ order_id: order_id,
25
+ pay_by_token: true,
26
+ token: tender.token,
27
+ shopper_id: tender.profile.email,
28
+ email: tender.profile.email,
29
+ address: billing_address,
30
+ order_content: order_content,
31
+ currency: currency_code
32
+ }
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,28 @@
1
+ module Workarea
2
+ decorate Payment::Refund::CreditCard, with: :worldpay_xml_api do
3
+ decorated do
4
+ include Payment::CreditCardData
5
+ delegate :address, to: :tender
6
+ end
7
+
8
+ def complete!
9
+ validate_reference!
10
+
11
+ transaction.response = handle_active_merchant_errors do
12
+ gateway.refund(
13
+ transaction.amount.cents,
14
+ transaction.reference.response.authorization,
15
+ transaction_options
16
+ )
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def transaction_options
23
+ {
24
+ currency: currency_code
25
+ }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ module Workarea
2
+ decorate Payment::StoreCreditCard, with: :worldpay_xml_api do
3
+ decorated do
4
+ include Payment::CreditCardData
5
+ delegate :address, to: :tender
6
+ end
7
+
8
+ def perform!
9
+ return true if @credit_card.token.present?
10
+
11
+ response = handle_active_merchant_errors do
12
+ gateway.create_token(@credit_card.to_active_merchant, token_options)
13
+ end
14
+
15
+ @credit_card.token = response.params["payment_token_id"]
16
+ end
17
+
18
+ private
19
+
20
+ def token_options
21
+ {
22
+ shopper_id: @credit_card.profile.email,
23
+ order_id: @credit_card.profile.reference
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ module Workarea
2
+ decorate Payment::Tender::CreditCard, with: :worldpay_xml_api do
3
+ def to_token_or_active_merchant
4
+ token.presence || to_active_merchant
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
6
+ ENGINE_PATH = File.expand_path('../../lib/workarea/worldpay_xml/engine', __FILE__)
7
+ APP_PATH = File.expand_path('../../test/dummy/config/application', __FILE__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
11
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
12
+
13
+ require "action_controller/railtie"
14
+ require "action_view/railtie"
15
+ require "action_mailer/railtie"
16
+ require "rails/test_unit/railtie"
17
+ require "sprockets/railtie"
18
+ require 'teaspoon-mocha'
19
+
20
+ require 'rails/engine/commands'
@@ -0,0 +1 @@
1
+ Workarea::WorldpayXml.auto_initialize_gateway
@@ -0,0 +1,65 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+ class BogusWorldpayGateway < BogusGateway
4
+ def create_token(paysource, options= {})
5
+ case normalize(paysource)
6
+ when /1$/
7
+ Response.new(
8
+ true,
9
+ SUCCESS_MESSAGE,
10
+ { payment_token_id: SecureRandom.hex(10).upcase + '1' },
11
+ :test => true,
12
+ :authorization => AUTHORIZATION
13
+ )
14
+ when /2$/
15
+ Response.new(
16
+ false,
17
+ FAILURE_MESSAGE,
18
+ { payment_token_id: nil, error: FAILURE_MESSAGE },
19
+ :test => true,
20
+ :error_code => STANDARD_ERROR_CODE[:processing_error]
21
+ )
22
+ else
23
+ raise Error, error_message(paysource)
24
+ end
25
+ end
26
+
27
+ def authorize_swipe(money, paysource, options = {})
28
+ money = amount(money)
29
+ case normalize(paysource)
30
+ when /2$/
31
+ Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
32
+ when /3$/
33
+ Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
34
+ else
35
+ Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION )
36
+ end
37
+ end
38
+
39
+ def purchase_swipe(money, paysource, options = {})
40
+ money = amount(money)
41
+ case normalize(paysource)
42
+ when /3$/
43
+ raise Error, error_message(paysource)
44
+ when /2$/
45
+ Response.new(
46
+ false,
47
+ FAILURE_MESSAGE,
48
+ {:paid_amount => money, :error => FAILURE_MESSAGE },
49
+ :test => true,
50
+ :error_code => STANDARD_ERROR_CODE[:processing_error]
51
+ )
52
+ else
53
+ Response.new(
54
+ true,
55
+ SUCCESS_MESSAGE,
56
+ {:paid_amount => money},
57
+ :test => true,
58
+ :authorization => AUTHORIZATION
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,118 @@
1
+ ActiveMerchant::Billing::WorldpayGateway.class_eval do
2
+ def create_token(payment_method, options = {})
3
+ requires!(options, :order_id)
4
+ create_token_request(payment_method, options)
5
+ end
6
+
7
+ private
8
+
9
+ def create_token_request(payment_method, options)
10
+ commit('payment_token_create', build_create_token_request(payment_method, options), nil)
11
+ end
12
+
13
+ def build_create_token_request(payment_method, options)
14
+ build_request do |xml|
15
+ xml.tag! 'submit' do
16
+ xml.tag! 'paymentTokenCreate' do
17
+ xml.tag! 'authenticatedShopperID', options[:shopper_id]
18
+ add_token_details(xml, options)
19
+ add_token_payment_method(xml, payment_method, options)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def add_token_details(xml, options)
26
+ return unless options[:order_id]
27
+ xml.tag! 'createToken' do
28
+ xml.tag! 'tokenEventReference', options[:order_id]
29
+ xml.tag! 'tokenReason', 'Saved payment method'
30
+ end
31
+ end
32
+
33
+ def add_token_payment_method(xml, payment_method, options)
34
+ return unless options[:order_id]
35
+ xml.tag! 'paymentInstrument' do
36
+ xml.tag! 'cardDetails' do
37
+ xml.tag! 'cardNumber', payment_method.number
38
+ xml.tag! 'expiryDate' do
39
+ xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits)
40
+ end
41
+ xml.tag! 'cardHolderName', payment_method.name
42
+ xml.tag! 'cvc', payment_method.verification_value
43
+ end
44
+ add_address(xml, (options[:billing_address] || options[:address]))
45
+ end
46
+ end
47
+
48
+ # success_criteria can be:
49
+ # - a string or an array of strings (if one of many responses)
50
+ # - An array of strings if one of many responses could be considered a
51
+ # success.
52
+ # - if empty then look for error codes
53
+ def success_and_message_from(raw, success_criteria)
54
+ success = if success_criteria.compact.empty?
55
+ !(raw[:error].present? && raw[:error_code].present?)
56
+ else
57
+ (success_criteria.include?(raw[:last_event]) || raw[:ok].present?)
58
+ end
59
+
60
+ if success
61
+ message = "SUCCESS"
62
+ else
63
+ message = (raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria))
64
+ end
65
+
66
+ [ success, message ]
67
+ end
68
+
69
+ def add_payment_method(xml, amount, payment_method, options)
70
+ if payment_method.is_a?(String)
71
+ if options[:merchant_code]
72
+ xml.tag! 'payAsOrder', 'orderCode' => payment_method, 'merchantCode' => options[:merchant_code] do
73
+ add_amount(xml, amount, options)
74
+ end
75
+ elsif options[:pay_by_token]
76
+ xml.tag! 'paymentDetails' do
77
+ xml.tag! 'TOKEN-SSL', 'tokenScope' => 'shopper' do
78
+ xml.tag! 'paymentTokenID', payment_method
79
+ end
80
+ end
81
+ else
82
+ xml.tag! 'payAsOrder', 'orderCode' => payment_method do
83
+ add_amount(xml, amount, options)
84
+ end
85
+ end
86
+ else
87
+ xml.tag! 'paymentDetails' do
88
+ xml.tag! CARD_CODES[card_brand(payment_method)] do
89
+ xml.tag! 'cardNumber', payment_method.number
90
+ xml.tag! 'expiryDate' do
91
+ xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits)
92
+ end
93
+
94
+ xml.tag! 'cardHolderName', payment_method.name
95
+ xml.tag! 'cvc', payment_method.verification_value
96
+
97
+ add_address(xml, (options[:billing_address] || options[:address]))
98
+ end
99
+ if options[:ip] && options[:session_id]
100
+ xml.tag! 'session', 'shopperIPAddress' => options[:ip], 'id' => options[:session_id]
101
+ else
102
+ xml.tag! 'session', 'shopperIPAddress' => options[:ip] if options[:ip]
103
+ xml.tag! 'session', 'id' => options[:session_id] if options[:session_id]
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def add_email(xml, options)
110
+ return unless options[:email]
111
+ xml.tag! 'shopper' do
112
+ xml.tag! 'shopperEmailAddress', options[:email]
113
+ if options[:pay_by_token]
114
+ xml.tag! 'authenticatedShopperID', options[:shopper_id]
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,48 @@
1
+ require 'workarea'
2
+ require 'workarea/storefront'
3
+ require 'workarea/admin'
4
+
5
+ require 'workarea/worldpay_xml/engine'
6
+ require 'workarea/worldpay_xml/version'
7
+
8
+ require 'active_merchant/billing/bogus_worldpay_gateway'
9
+ require 'active_merchant/billing/worldpay_tokenize'
10
+
11
+
12
+ module Workarea
13
+ module WorldpayXml
14
+ # Credentials for WorldpayGateway from Rails secrets.
15
+ #
16
+ # @return [Hash]
17
+ def self.credentials
18
+ return {} unless Rails.application.secrets.worldpay.present?
19
+ Rails.application.secrets.worldpay.symbolize_keys
20
+ end
21
+
22
+ # Conditionally use the real gateway when secrets are present.
23
+ # Otherwise, use the bogus gateway.
24
+ #
25
+ # @return [ActiveMerchant::Billing::Gateway]
26
+ def self.gateway
27
+ Workarea.config.gateways.credit_card
28
+ end
29
+
30
+ def self.gateway=(gateway)
31
+ Workarea.config.gateways.credit_card = gateway
32
+ end
33
+
34
+ def self.auto_initialize_gateway
35
+ if credentials.present?
36
+ if ENV['HTTP_PROXY'].present?
37
+ uri = URI.parse(ENV['HTTP_PROXY'])
38
+ ActiveMerchant::Billing::WorldpayGateway.proxy_address = uri.host
39
+ ActiveMerchant::Billing::WorldpayGateway.proxy_port = uri.port
40
+ end
41
+
42
+ self.gateway = ActiveMerchant::Billing::WorldpayGateway.new credentials
43
+ else
44
+ self.gateway = ActiveMerchant::Billing::BogusWorldpayGateway.new
45
+ end
46
+ end
47
+ end
48
+ end