rails-gp-webpay 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +41 -0
  3. data/Rakefile +6 -0
  4. data/app/controllers/gp_webpay/cards_controller.rb +11 -0
  5. data/app/controllers/gp_webpay/orders_controller.rb +11 -0
  6. data/app/controllers/gp_webpay_controller.rb +18 -0
  7. data/changelog.md +4 -0
  8. data/config/keys/cert.pem +8 -0
  9. data/config/keys/pkey.pem +13 -0
  10. data/config/routes.rb +4 -0
  11. data/config/wsdl/GPwebpayAdditionalInfoResponse_v1.xsd +194 -0
  12. data/config/wsdl/cws_v1.wsdl +2355 -0
  13. data/config/wsdl/swaref.xsd +59 -0
  14. data/lib/gp_webpay.rb +55 -0
  15. data/lib/gp_webpay/configuration.rb +65 -0
  16. data/lib/gp_webpay/engine.rb +7 -0
  17. data/lib/gp_webpay/error.rb +4 -0
  18. data/lib/gp_webpay/http/base_signed_request.rb +72 -0
  19. data/lib/gp_webpay/http/create_order.rb +24 -0
  20. data/lib/gp_webpay/http/external_url.rb +13 -0
  21. data/lib/gp_webpay/http/http_request.rb +63 -0
  22. data/lib/gp_webpay/http/http_response.rb +40 -0
  23. data/lib/gp_webpay/http/validate_result.rb +63 -0
  24. data/lib/gp_webpay/http/verify_card.rb +18 -0
  25. data/lib/gp_webpay/openssl_security.rb +16 -0
  26. data/lib/gp_webpay/response.rb +34 -0
  27. data/lib/gp_webpay/service.rb +15 -0
  28. data/lib/gp_webpay/version.rb +3 -0
  29. data/lib/gp_webpay/ws/base_signed_request.rb +69 -0
  30. data/lib/gp_webpay/ws/echo.rb +35 -0
  31. data/lib/gp_webpay/ws/services/get_master_payment_status.rb +32 -0
  32. data/lib/gp_webpay/ws/services/get_payment_status.rb +19 -0
  33. data/lib/gp_webpay/ws/services/get_token_status.rb +21 -0
  34. data/lib/gp_webpay/ws/services/process_cancel_capture.rb +20 -0
  35. data/lib/gp_webpay/ws/services/process_capture_reverse.rb +20 -0
  36. data/lib/gp_webpay/ws/services/process_card_on_file_payment.rb +40 -0
  37. data/lib/gp_webpay/ws/services/process_master_payment_revoke.rb +20 -0
  38. data/lib/gp_webpay/ws/services/process_recurring_payment.rb +20 -0
  39. data/lib/gp_webpay/ws/services/process_refund_payment.rb +20 -0
  40. data/lib/gp_webpay/ws/services/process_token_payment.rb +26 -0
  41. data/lib/gp_webpay/ws/services/process_token_revoke.rb +20 -0
  42. data/lib/gp_webpay/ws/services/process_usage_based_payment.rb +21 -0
  43. data/lib/gp_webpay/ws/validate_result.rb +48 -0
  44. data/lib/gp_webpay/ws/ws_request.rb +39 -0
  45. data/lib/gp_webpay/ws/ws_response.rb +54 -0
  46. metadata +135 -0
@@ -0,0 +1,59 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!--
3
+ Copyright (c) 2002-2004 by The Web Services-Interoperability Organization (WS-I) and
4
+ Certain of its Members. All Rights Reserved.
5
+
6
+ Notice
7
+ The material contained herein is not a license, either expressly or impliedly, to any
8
+ intellectual property owned or controlled by any of the authors or developers of this
9
+ material or WS-I. The material contained herein is provided on an "AS IS" basis and to
10
+ the maximum extent permitted by applicable law, this material is provided AS IS AND WITH
11
+ ALL FAULTS, and the authors and developers of this material and WS-I hereby disclaim all
12
+ other warranties and conditions, either express, implied or statutory, including, but not
13
+ limited to, any (if any) implied warranties, duties or conditions of merchantability,
14
+ of fitness for a particular purpose, of accuracy or completeness of responses, of results,
15
+ of workmanlike effort, of lack of viruses, and of lack of negligence. ALSO, THERE IS NO
16
+ WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT, QUIET POSSESSION, CORRESPONDENCE TO
17
+ DESCRIPTION OR NON-INFRINGEMENT WITH REGARD TO THIS MATERIAL.
18
+
19
+ IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THIS MATERIAL OR WS-I BE LIABLE TO ANY OTHER
20
+ PARTY FOR THE COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS OF USE,
21
+ LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT, INDIRECT, OR SPECIAL DAMAGES
22
+ WHETHER UNDER CONTRACT, TORT, WARRANTY, OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR
23
+ ANY OTHER AGREEMENT RELATING TO THIS MATERIAL, WHETHER OR NOT SUCH PARTY HAD ADVANCE
24
+ NOTICE OF THE POSSIBILITY OF SUCH DAMAGES.
25
+
26
+ WS-I License Information
27
+ Use of this WS-I Material is governed by the WS-I Test License and other licenses. Information on these
28
+ licenses are contained in the README.txt and ReleaseNotes.txt files. By downloading this file, you agree
29
+ to the terms of these licenses.
30
+
31
+ How To Provide Feedback
32
+ The Web Services-Interoperability Organization (WS-I) would like to receive input,
33
+ suggestions and other feedback ("Feedback") on this work from a wide variety of
34
+ industry participants to improve its quality over time.
35
+
36
+ By sending email, or otherwise communicating with WS-I, you (on behalf of yourself if
37
+ you are an individual, and your company if you are providing Feedback on behalf of the
38
+ company) will be deemed to have granted to WS-I, the members of WS-I, and other parties
39
+ that have access to your Feedback, a non-exclusive, non-transferable, worldwide, perpetual,
40
+ irrevocable, royalty-free license to use, disclose, copy, license, modify, sublicense or
41
+ otherwise distribute and exploit in any manner whatsoever the Feedback you provide regarding
42
+ the work. You acknowledge that you have no expectation of confidentiality with respect to
43
+ any Feedback you provide. You represent and warrant that you have rights to provide this
44
+ Feedback, and if you are providing Feedback on behalf of a company, you represent and warrant
45
+ that you have the rights to provide Feedback on behalf of your company. You also acknowledge
46
+ that WS-I is not required to review, discuss, use, consider or in any way incorporate your
47
+ Feedback into future versions of its work. If WS-I does incorporate some or all of your
48
+ Feedback in a future version of the work, it may, but is not obligated to include your name
49
+ (or, if you are identified as acting on behalf of your company, the name of your company) on
50
+ a list of contributors to the work. If the foregoing is not acceptable to you and any company
51
+ on whose behalf you are acting, please do not provide any Feedback.
52
+
53
+ Feedback on this document should be directed to wsi-test-comments@ws-i.org.
54
+ -->
55
+ <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://ws-i.org/profiles/basic/1.1/xsd" xmlns="http://ws-i.org/profiles/basic/1.1/xsd">
56
+ <xsd:simpleType name="swaRef">
57
+ <xsd:restriction base="xsd:anyURI"/>
58
+ </xsd:simpleType>
59
+ </xsd:schema>
data/lib/gp_webpay.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'nokogiri'
2
+ require 'savon'
3
+ require 'active_support/core_ext/hash'
4
+ require 'active_support/core_ext/object'
5
+
6
+ require 'gp_webpay/error'
7
+ require 'gp_webpay/version'
8
+ require 'gp_webpay/engine'
9
+ require 'gp_webpay/service'
10
+ require 'gp_webpay/openssl_security'
11
+ require 'gp_webpay/configuration'
12
+ require 'gp_webpay/response'
13
+
14
+ require 'gp_webpay/http/base_signed_request'
15
+ require 'gp_webpay/http/external_url'
16
+ require 'gp_webpay/http/http_response'
17
+ require 'gp_webpay/http/http_request'
18
+ require 'gp_webpay/http/create_order'
19
+ require 'gp_webpay/http/verify_card'
20
+ require 'gp_webpay/http/validate_result'
21
+
22
+ require 'gp_webpay/ws/ws_request'
23
+ require 'gp_webpay/ws/validate_result'
24
+ require 'gp_webpay/ws/ws_response'
25
+ require 'gp_webpay/ws/base_signed_request'
26
+ require 'gp_webpay/ws/echo'
27
+
28
+ require 'gp_webpay/ws/services/get_master_payment_status'
29
+ require 'gp_webpay/ws/services/get_payment_status'
30
+ require 'gp_webpay/ws/services/get_token_status'
31
+ require 'gp_webpay/ws/services/process_cancel_capture'
32
+ require 'gp_webpay/ws/services/process_capture_reverse'
33
+ require 'gp_webpay/ws/services/process_card_on_file_payment'
34
+ require 'gp_webpay/ws/services/process_master_payment_revoke'
35
+ require 'gp_webpay/ws/services/process_recurring_payment'
36
+ require 'gp_webpay/ws/services/process_refund_payment'
37
+ require 'gp_webpay/ws/services/process_token_payment'
38
+ require 'gp_webpay/ws/services/process_token_revoke'
39
+ require 'gp_webpay/ws/services/process_usage_based_payment'
40
+
41
+ module GpWebpay
42
+ @configuration = Configuration.new
43
+
44
+ def self.config
45
+ @configuration
46
+ end
47
+
48
+ def self.configure
49
+ yield(@configuration)
50
+ end
51
+
52
+ def self.root
53
+ File.dirname(__dir__)
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ module GpWebpay
2
+ class Configuration
3
+ attr_accessor :configurations, :parent_controller, :mount_at, :orders_controller, :cards_controller
4
+
5
+ def initialize
6
+ @configurations = {}
7
+ @parent_controller = 'AbstractController::Base'
8
+ @mount_at = '/gp_webpay'
9
+ @orders_controller = 'OrdersController'
10
+ @cards_controller = 'CardsController'
11
+ end
12
+
13
+ def default
14
+ @configurations[:default]
15
+ end
16
+
17
+ def [](config_name)
18
+ @configurations[config_name]
19
+ end
20
+
21
+ def add_configuration(merchant_number:, default: false)
22
+ @configurations[merchant_number] = MerchantConfig.new(merchant_number)
23
+ yield(@configurations[merchant_number])
24
+ @configurations[:default] = @configurations[merchant_number] if default || !@configurations[:default]
25
+ end
26
+
27
+ def remove_configuration(merchant_number:)
28
+ @configurations[merchant_number] = nil
29
+ @configurations[:default] = @configurations[@configurations.keys[0]]
30
+ end
31
+
32
+ class MerchantConfig
33
+ attr_accessor :merchant_number, :merchant_pem, :merchant_password, :gpe_pem, :wsdl_file, :provider, :enabled_methods, :production
34
+ attr_writer :http_url, :ws_url
35
+
36
+ DEFAULT_HTTP_URL = 'https://3dsecure.gpwebpay.com/pgw/order.do'.freeze
37
+ DEFAULT_HTTP_TEST_URL = 'https://test.3dsecure.gpwebpay.com/pgw/order.do'.freeze
38
+ DEFAULT_WS_URL = 'https://3dsecure.gpwebpay.com/pay-ws/v1/PaymentService'.freeze
39
+ DEFAULT_WS_TEST_URL = 'https://test.3dsecure.gpwebpay.com/pay-ws/v1/PaymentService'.freeze
40
+
41
+ def initialize(merchant_number)
42
+ @merchant_number = merchant_number
43
+ @production = false
44
+ @wsdl_file = File.read("#{GpWebpay.root}/config/wsdl/cws_v1.wsdl")
45
+ @enabled_methods = 'credit_card,transfer'
46
+ end
47
+
48
+ def http_url
49
+ if @http_url.nil?
50
+ production ? DEFAULT_HTTP_URL : DEFAULT_HTTP_TEST_URL
51
+ else
52
+ @http_url
53
+ end
54
+ end
55
+
56
+ def ws_url
57
+ if @ws_url.nil?
58
+ production ? DEFAULT_WS_URL : DEFAULT_WS_TEST_URL
59
+ else
60
+ @ws_url
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,7 @@
1
+ if Object.const_defined?(:Rails)
2
+ module GpWebpay
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace GpWebpay
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ module GpWebpay
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,72 @@
1
+ ##
2
+ # Service object signs request which can be send to GP Webpay payment gateway.
3
+ #
4
+ # 1. Use request value object to translate attributes to correct GP Webpay format and order.
5
+ # 2. Append generated digest to attributes.
6
+ # 3. prepare url to send for GP Webpay
7
+ #
8
+ # @param [Hash] optional attributes for GP Webpay
9
+ #
10
+ # @return [Hash] data needed to make request:
11
+ # full url for GET request or
12
+ # url_root and list of attributes for POST request.
13
+
14
+ module GpWebpay
15
+ module Http
16
+ class BaseSignedRequest < Service
17
+ attr_reader :attributes, :locale, :operation, :config, :url_attributes
18
+
19
+ def initialize(attributes, locale, operation, merchant_number: :default, url_attributes: {})
20
+ super()
21
+ @attributes = attributes
22
+ @locale = locale
23
+ @merchant_number = merchant_number
24
+ @operation = operation
25
+ @url_attributes = url_attributes
26
+ @config = GpWebpay.config[@merchant_number] || GpWebpay.config.default
27
+ end
28
+
29
+ def call
30
+ request = HttpRequest.new(
31
+ attributes.merge(
32
+ merchant_number: @config.merchant_number,
33
+ operation: operation,
34
+ url: callback_url
35
+ )
36
+ ).to_gpwebpay
37
+
38
+ attrs_with_digest = payment_attributes_with_digest(request)
39
+ uri = URI(@config.http_url)
40
+
41
+ ExternalUrl.new(
42
+ url: uri.to_s,
43
+ full_url: build_full_url(uri, attrs_with_digest),
44
+ params: attrs_with_digest
45
+ )
46
+ end
47
+
48
+ def callback_url
49
+ raise NotImplementedError
50
+ end
51
+
52
+ private
53
+
54
+ def build_full_url(uri, attrs)
55
+ uri.query = URI.encode_www_form(attrs)
56
+ uri.to_s
57
+ end
58
+
59
+ def digest_text(attrs)
60
+ attrs.values.join('|')
61
+ end
62
+
63
+ def payment_attributes_with_digest(attrs)
64
+ digest = OpensslSecurity.generate_digest(@config, digest_text(attrs))
65
+ attrs.merge(
66
+ 'DIGEST' => digest,
67
+ 'LANG' => locale
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,24 @@
1
+ ##
2
+ # Service object creates request data for GP Webpay CREATE_ORDER operation.
3
+ #
4
+ # Examples:
5
+ # => Create order which will remember credit card and send back TOKEN.
6
+ # > GpWebpay::Http::CreateOrder.call(order_number: 146, amount: 456, currency: 978, deposit_flag: 1, user_param1: 'T')
7
+ # => Use returned token to skip adding card again:
8
+ # > GpWebpay::Http::CreateOrder.call(order_number: 147, amount: 456, currency: 978, deposit_flag: 1, user_param1: 'S', token: 'TOKEN')
9
+
10
+ module GpWebpay
11
+ module Http
12
+ class CreateOrder < BaseSignedRequest
13
+ def initialize(attributes, locale, merchant_number: nil, url_attributes: {})
14
+ super(attributes, locale, 'CREATE_ORDER', merchant_number: merchant_number, url_attributes: url_attributes)
15
+ end
16
+
17
+ protected
18
+
19
+ def callback_url
20
+ GpWebpay::Engine.routes.url_helpers.gp_webpay_orders_path({ merchant_number: config.merchant_number, locale: locale }.merge(url_attributes))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module GpWebpay
2
+ module Http
3
+ class ExternalUrl
4
+ attr_accessor :url, :full_url, :params
5
+
6
+ def initialize(url:, full_url:, params:)
7
+ @url = url
8
+ @full_url = full_url
9
+ @params = params
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,63 @@
1
+ module GpWebpay
2
+ module Http
3
+ class HttpRequest
4
+ attr_accessor :attributes
5
+
6
+ ATTRS_TO_GP_MAPPER = {
7
+ 'MERCHANTNUMBER' => :merchant_number,
8
+ 'OPERATION' => :operation,
9
+ 'ORDERNUMBER' => :order_number,
10
+ 'AMOUNT' => :amount,
11
+ 'CURRENCY' => :currency,
12
+ 'DEPOSITFLAG' => :deposit_flag,
13
+ 'MERORDERNUM' => :mer_order_num,
14
+ 'URL' => :url,
15
+ 'DESCRIPTION' => :description,
16
+ 'MD' => :md,
17
+ 'USERPARAM1' => :user_param1,
18
+ 'FASTPAYID' => :fast_pay_id,
19
+ 'PAYMETHOD' => :paymethod,
20
+ 'DISABLEPAYMETHOD' => :disable_paymethod,
21
+ 'PAYMETHODS' => :paymethods,
22
+ 'EMAIL' => :email,
23
+ 'REFERENCENUMBER' => :reference_number,
24
+ 'ADDINFO' => :add_info_to_xml,
25
+ 'FASTTOKEN' => :fast_token
26
+ }.freeze
27
+
28
+ def initialize(attributes)
29
+ @attributes = attributes || {}
30
+ end
31
+
32
+ def to_gpwebpay
33
+ @to_gpwebpay ||= transform_to_gpwebpay
34
+ end
35
+
36
+ private
37
+
38
+ def transform_to_gpwebpay
39
+ result = ATTRS_TO_GP_MAPPER.each_with_object({}) do |(k, v), attrs|
40
+ attribute_value = attributes[v] || attributes[v.to_s]
41
+ attrs[k] = attribute_value if attribute_value
42
+ end
43
+ result = result.merge({ 'ADDINFO' => add_info_to_xml }) if attributes[:add_info] || attributes['add_info']
44
+ result
45
+ end
46
+
47
+ def add_info_to_xml
48
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
49
+ xml.additionalInfoRequest(xmlns: 'http://gpe.cz/gpwebpay/additionalInfo/request',
50
+ 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
51
+ version: 4.0) do
52
+ xml.requestReturnInfo do
53
+ xml.requestCardsDetails true
54
+ end
55
+ end
56
+ end
57
+ builder.to_xml(
58
+ save_with: Nokogiri::XML::Node::SaveOptions::AS_XML
59
+ ).strip.gsub("\n", '')
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ module GpWebpay
2
+ module Http
3
+ class HttpResponse < Response
4
+ GP_TO_ATTRS_MAPPER =
5
+ {
6
+ 'OPERATION' => :operation,
7
+ 'ORDERNUMBER' => :order_number,
8
+ 'MERORDERNUM' => :mer_order_num,
9
+ 'MD' => :md,
10
+ 'PRCODE' => :pr_code,
11
+ 'SRCODE' => :sr_code,
12
+ 'RESULTTEXT' => :result_text,
13
+ 'USERPARAM1' => :user_param1,
14
+ 'TOKEN' => :token,
15
+ 'EXPIRY' => :expiry,
16
+ 'ACSRES' => :acsres,
17
+ 'ACCODE' => :accode,
18
+ 'PANPATTERN' => :pan_pattern,
19
+ 'DAYTOCAPTURE' => :day_to_capture,
20
+ 'TOKENREGSTATUS' => :token_reg_status
21
+ }.freeze
22
+
23
+ def self.from_hash(hash, merchant_number)
24
+ params = GP_TO_ATTRS_MAPPER.each_with_object({}) do |(k, v), result|
25
+ value = hash[k.to_s] || hash[k.to_sym]
26
+ result[v] = value if value
27
+ end
28
+
29
+ params[:add_info] = Hash.from_xml(hash['ADDINFO']) if hash['ADDINFO']
30
+
31
+ new(original_response: hash, result_text: params[:result_text], token: params[:token], status: nil,
32
+ pr_code: params[:pr_code], sr_code: params[:sr_code], params: params, merchant_number: merchant_number)
33
+ end
34
+
35
+ def valid?
36
+ GpWebpay::Http::ValidateResult.call(original_response, config)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,63 @@
1
+ ##
2
+ # Service object validates result received from GP Webpay response redirect.
3
+ #
4
+ # 1. Use public cert of GP Webpay to verify it comes from GP Webpay.
5
+ # 2. Whitelist allowed attributes which are expected from GP Webpay.
6
+ # 3. Calculate digest and make sure it corresponds to received DIGEST.
7
+ # 3. Calculate digest1 and make sure it corresponds to received DIGEST1
8
+ # (which includes our merchant number for extra security).
9
+ #
10
+ # @param [Hash] Parameters hash received in response from GP Webpay.
11
+ #
12
+ # @return [Boolean] true if signature is valid for both digests.
13
+
14
+ module GpWebpay
15
+ module Http
16
+ class ValidateResult < Service
17
+ attr_reader :params, :config
18
+
19
+ def initialize(params, config)
20
+ super()
21
+ @params = params
22
+ @config = config
23
+ end
24
+
25
+ def call
26
+ params['DIGEST'] && params['DIGEST1'] && OpensslSecurity.validate_digests(
27
+ config,
28
+ params['DIGEST'] => digest_verification,
29
+ params['DIGEST1'] => digest1_verification
30
+ )
31
+ end
32
+
33
+ private
34
+
35
+ DIGEST_ALLOWED_ATTRIBUTES = %w[
36
+ OPERATION
37
+ ORDERNUMBER
38
+ MERORDERNUM
39
+ MD
40
+ PRCODE
41
+ SRCODE
42
+ RESULTTEXT
43
+ USERPARAM1
44
+ ADDINFO
45
+ TOKEN
46
+ EXPIRY
47
+ ACSRES
48
+ ACCODE
49
+ PANPATTERN
50
+ DAYTOCAPTURE
51
+ TOKENREGSTATUS
52
+ ].freeze
53
+
54
+ def digest_verification
55
+ @digest_verification ||= (DIGEST_ALLOWED_ATTRIBUTES & params.keys).map { |key| params[key] }.join('|')
56
+ end
57
+
58
+ def digest1_verification
59
+ "#{digest_verification}|#{config.merchant_number}"
60
+ end
61
+ end
62
+ end
63
+ end