rma-payment-gateway 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,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+
6
+ module Rma
7
+ module Payment
8
+ module Gateway
9
+ # Client class for RMA Payment Gateway.
10
+ # Handles all communication with the RMA Payment Gateway API.
11
+ #
12
+ # @example
13
+ # client = Rma::Payment::Gateway::Client.new
14
+ # client.authorization
15
+ # client.account_inquiry
16
+ # client.debit_request
17
+ #
18
+ # @param config [Rma::Payment::Gateway::Configuration] Configuration instance
19
+ class Client
20
+ attr_reader :config, :access_token, :private_key
21
+
22
+ def initialize(config = nil)
23
+ @config = config || Rma::Payment::Gateway.configuration
24
+ validate_configuration!
25
+ end
26
+
27
+ # Authorization methods
28
+ def authorization
29
+ @authorization ||= Authorization.new(self)
30
+ end
31
+
32
+ # Account Inquiry methods
33
+ def account_inquiry
34
+ @account_inquiry ||= AccountInquiry.new(self)
35
+ end
36
+
37
+ # Debit Request methods
38
+ def debit_request
39
+ @debit_request ||= DebitRequest.new(self)
40
+ end
41
+
42
+ # HTTP request methods
43
+ def post(body: {}, headers: {})
44
+ request(:post, body: body, headers: headers)
45
+ end
46
+
47
+ def get(headers: {})
48
+ request(:get, headers: headers)
49
+ end
50
+
51
+ private
52
+
53
+ def validate_configuration!
54
+ raise ConfigurationError, "Configuration is required" if config.nil?
55
+ return if config.valid?
56
+
57
+ raise ConfigurationError, "Missing required configuration fields: #{config.missing_fields.join(", ")}"
58
+ end
59
+
60
+ def request(method, body: {}, headers: {})
61
+ response = connection.send(method) do |req|
62
+ req.url "/BFSSecure/nvpapi"
63
+ req.headers = build_headers(headers)
64
+ req.body = body.to_query if method == :post && !body.empty?
65
+ end
66
+
67
+ handle_response(response)
68
+ rescue Faraday::Error => e
69
+ raise NetworkError, "Network error: #{e.message}"
70
+ end
71
+
72
+ def connection
73
+ @connection ||= Faraday.new(url: config.base_url) do |conn|
74
+ conn.request :url_encoded
75
+ conn.response :json, content_type: /\bjson$/
76
+ conn.adapter Faraday.default_adapter
77
+ conn.options.timeout = config.timeout
78
+ conn.options.open_timeout = config.open_timeout
79
+ end
80
+ end
81
+
82
+ def build_headers(custom_headers = {})
83
+ headers = { "Content-Type" => "application/x-www-form-urlencoded" }
84
+ headers.merge(custom_headers)
85
+ end
86
+
87
+ def handle_response(response)
88
+ case response.status
89
+ when 200..299
90
+ response.body
91
+ when 400..499
92
+ handle_client_error(response)
93
+ when 500..599
94
+ handle_server_error(response)
95
+ else
96
+ raise APIError, "Unexpected response status: #{response.status}"
97
+ end
98
+ end
99
+
100
+ def handle_client_error(response)
101
+ body = response.body || {}
102
+ error_message = body["result"]["bfs_responseDesc"] || "Client error"
103
+
104
+ raise InvalidParameterError.new(
105
+ error_message,
106
+ response_code: body["result"]["bfs_responseCode"],
107
+ response_detail: body["result"]["bfs_responseDesc"]
108
+ )
109
+ end
110
+
111
+ def handle_server_error(response)
112
+ body = response.body || {}
113
+ error_message = body["result"]["bfs_responseDesc"] || "Server error"
114
+
115
+ raise APIError.new(
116
+ error_message,
117
+ response_code: body["result"]["bfs_responseCode"],
118
+ response_description: body["result"]["bfs_responseDesc"]
119
+ )
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dotenv/load" # Load environment variables
4
+
5
+ module Rma
6
+ module Payment
7
+ module Gateway
8
+ # Configuration class for RMA Payment Gateway.
9
+ # Handles all configuration options required for the RMA Payment Gateway client.
10
+ #
11
+ # @example
12
+ # config = Rma::Payment::Gateway::Configuration.new
13
+ # config.base_url = 'https://api.example.com'
14
+ # config.rsa_key_path = '/path/to/rsa_key.pem'
15
+ # config.beneficiary_id = 'YOUR_BENEFICIARY_ID'
16
+ class Configuration
17
+ attr_accessor :base_url, :rsa_key_path, :beneficiary_id, :payment_description, :timeout, :open_timeout
18
+
19
+ def initialize
20
+ @base_url = ENV["RMA_BASE_URL"]
21
+ @rsa_key_path = ENV["RMA_RSA_KEY_PATH"]
22
+ @beneficiary_id = ENV["RMA_BENEFICIARY_ID"]
23
+ @payment_description = ENV["RMA_PAYMENT_DESCRIPTION"]
24
+ @timeout = 30
25
+ @open_timeout = 10
26
+ end
27
+
28
+ def valid?
29
+ required_fields.all? { |field| !send(field).nil? && !send(field).to_s.empty? }
30
+ end
31
+
32
+ def required_fields
33
+ %i[base_url rsa_key_path beneficiary_id payment_description]
34
+ end
35
+
36
+ def missing_fields
37
+ required_fields.reject { |field| !send(field).nil? && !send(field).to_s.empty? }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "securerandom"
5
+
6
+ module Rma
7
+ module Payment
8
+ module Gateway
9
+ # Authorization class for RMA Payment Gateway.
10
+ # Handles authentication and authorization for the RMA Payment Gateway client.
11
+ #
12
+ # @example
13
+ # auth = Rma::Payment::Gateway::Authorization.new(client)
14
+ # auth.call(order_no, amount, email)
15
+ #
16
+ # @param client [Rma::Payment::Gateway::Client] Client instance
17
+ class DebitRequest
18
+ attr_reader :client, :transaction_id, :otp
19
+
20
+ def initialize(client)
21
+ @client = client
22
+ end
23
+
24
+ # Fetch debit request
25
+ # Returns the debit request string
26
+ def call(transaction_id, otp)
27
+ @transaction_id = transaction_id
28
+ @otp = otp
29
+ response = client.post(body: debit_request_body)
30
+
31
+ validate_debit_request_response!(response)
32
+
33
+ response["result"]
34
+ rescue StandardError => e
35
+ raise AuthenticationError, "Failed to fetch debit request: #{e.message}"
36
+ end
37
+
38
+ private
39
+
40
+ def debit_request_body
41
+ params = {
42
+ bfs_bfsTxnId: transaction_id,
43
+ bfs_remitterOtp: otp,
44
+ bfs_benfId: client.config.beneficiary_id,
45
+ bfs_msgType: "DR"
46
+ }
47
+
48
+ # Convert to URL-encoded format
49
+ URI.encode_www_form(params)
50
+ end
51
+
52
+ def validate_debit_request_response!(response)
53
+ unless response.is_a?(Hash) && response["result"]["bfs_responseCode"] == "00"
54
+ error_detail = response["result"]["bfs_responseDesc"] || "Unknown error"
55
+ raise AuthenticationError, "Debit request failed: #{error_detail}"
56
+ end
57
+
58
+ return if response["result"]
59
+
60
+ raise AuthenticationError, "No response data in response"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rma
4
+ module Payment
5
+ module Gateway
6
+ class Error < StandardError; end
7
+
8
+ class ConfigurationError < Error; end
9
+
10
+ class AuthenticationError < Error; end
11
+
12
+ class InvalidParameterError < Error
13
+ attr_reader :response_code, :response_detail
14
+
15
+ def initialize(message, response_code: 422, response_detail: "Invalid parameters")
16
+ super(message)
17
+ @response_code = response_code
18
+ @response_detail = response_detail
19
+ end
20
+ end
21
+
22
+ class APIError < Error
23
+ attr_reader :response_code, :response_message, :response_description, :response_detail
24
+
25
+ def initialize(message, response_code: 422, response_message: "Invalid parameters",
26
+ response_description: "Invalid parameters", response_detail: "Invalid parameters")
27
+ super(message)
28
+ @response_code = response_code
29
+ @response_message = response_message
30
+ @response_description = response_description
31
+ @response_detail = response_detail
32
+ end
33
+ end
34
+
35
+ class NetworkError < Error; end
36
+
37
+ class SignatureError < Error; end
38
+
39
+ class TransactionError < APIError; end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "time"
5
+
6
+ module Rma
7
+ module Payment
8
+ module Gateway
9
+ module Utils
10
+ # Generate ISO 8601 timestamp
11
+ # @param time [Time] Optional time object (defaults to current time)
12
+ # @return [String] ISO 8601 formatted timestamp
13
+ def self.generate_timestamp(time = Time.now)
14
+ time.utc.strftime("%Y%m%d%H%M%S")
15
+ end
16
+
17
+ # Validate account number format
18
+ # @param account_number [String] Account number to validate
19
+ # @return [Boolean] True if valid format
20
+ def self.valid_account_number?(account_number)
21
+ return false if account_number.nil? || account_number.empty?
22
+
23
+ # Account numbers should be numeric and between 8-15 digits
24
+ account_number.to_s.match?(/^\d{8,15}$/)
25
+ end
26
+
27
+ # Validate phone number format (Bhutan)
28
+ # @param phone_number [String] Phone number to validate
29
+ # @return [Boolean] True if valid format
30
+ def self.valid_phone_number?(phone_number)
31
+ return false if phone_number.nil? || phone_number.empty?
32
+
33
+ # Bhutan phone numbers are typically 8 digits
34
+ phone_number.to_s.match?(/^\d{8}$/)
35
+ end
36
+
37
+ # Validate email format
38
+ # @param email [String] Email to validate
39
+ # @return [Boolean] True if valid format
40
+ def self.valid_email?(email)
41
+ return false if email.nil? || email.empty?
42
+
43
+ email.to_s.match?(/\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i)
44
+ end
45
+
46
+ # Validate amount
47
+ # @param amount [Numeric] Amount to validate
48
+ # @return [Boolean] True if valid
49
+ def self.valid_amount?(amount)
50
+ return false if amount.nil?
51
+
52
+ amount.is_a?(Numeric) && amount >= 0
53
+ end
54
+
55
+ # Format amount to 2 decimal places
56
+ # @param amount [Numeric] Amount to format
57
+ # @return [String] Formatted amount
58
+ def self.format_amount(amount)
59
+ format("%.2f", amount.to_f)
60
+ end
61
+
62
+ # Validate date format (YYYY-MM-DD)
63
+ # @param date_string [String] Date string to validate
64
+ # @return [Boolean] True if valid format
65
+ def self.valid_date_format?(date_string)
66
+ return false if date_string.nil? || date_string.empty?
67
+
68
+ date_string.to_s.match?(/^\d{4}-\d{2}-\d{2}$/)
69
+ end
70
+
71
+ # Parse date string
72
+ # @param date_string [String] Date string in YYYY-MM-DD format
73
+ # @return [Date, nil] Parsed date or nil if invalid
74
+ def self.parse_date(date_string)
75
+ return nil unless valid_date_format?(date_string)
76
+
77
+ Date.parse(date_string)
78
+ rescue ArgumentError
79
+ nil
80
+ end
81
+
82
+ # Sanitize string for API request
83
+ # @param str [String] String to sanitize
84
+ # @return [String] Sanitized string
85
+ def self.sanitize_string(str)
86
+ return "" if str.nil?
87
+
88
+ str.to_s.strip
89
+ end
90
+
91
+ # Mask sensitive data for logging
92
+ # @param data [String] Sensitive data to mask
93
+ # @param visible_chars [Integer] Number of characters to show at start and end
94
+ # @return [String] Masked string
95
+ def self.mask_sensitive(data, visible_chars = 4)
96
+ return "" if data.nil? || data.empty?
97
+
98
+ data_str = data.to_s
99
+ return data_str if data_str.length <= visible_chars * 2
100
+
101
+ "#{data_str[0...visible_chars]}#{"*" * (data_str.length - visible_chars * 2)}#{data_str[-visible_chars..]}"
102
+ end
103
+
104
+ # Bank codes mapping
105
+ BANK_CODES = {
106
+ "1010" => "Bank of Bhutan (BOBL)",
107
+ "1020" => "Bhutan National Bank (BNBL)",
108
+ "1030" => "Druk PNB Bank Limited (DPNBL)",
109
+ "1040" => "Tashi Bank (TBank)",
110
+ "1050" => "Bhutan Development Bank Limited (BDBL)",
111
+ "1060" => "Digital Kidu (DK Bank)"
112
+ }.freeze
113
+
114
+ # Get bank name from code
115
+ # @param bank_code [String] Bank code
116
+ # @return [String, nil] Bank name or nil if not found
117
+ def self.bank_name(bank_code)
118
+ BANK_CODES[bank_code.to_s]
119
+ end
120
+
121
+ # Validate bank code
122
+ # @param bank_code [String] Bank code to validate
123
+ # @return [Boolean] True if valid
124
+ def self.valid_bank_code?(bank_code)
125
+ BANK_CODES.key?(bank_code.to_s)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rma
4
+ module Payment
5
+ module Gateway
6
+ VERSION = "1.0.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gateway/version"
4
+
5
+ module Rma
6
+ module Payment
7
+ module Gateway
8
+ class Error < StandardError; end
9
+
10
+ class << self
11
+ attr_accessor :configuration
12
+ end
13
+
14
+ def self.configure
15
+ self.configuration ||= Configuration.new
16
+ yield(configuration)
17
+ end
18
+
19
+ def self.client
20
+ Client.new(configuration)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ module Rma
2
+ module Payment
3
+ module Gateway
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rma-payment-gateway
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tashi Dendup
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A Ruby gem for integrating with RMA Payment Gateway API, including Payment
13
+ Authorization, Debit payment request, and Payment OTP confirmation.
14
+ email:
15
+ - tashii.dendupp@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - CODE_OF_CONDUCT.md
22
+ - README.md
23
+ - Rakefile
24
+ - docs/API.md
25
+ - docs/EXAMPLES.md
26
+ - docs/FLOW_DIAGRAM.md
27
+ - docs/QUICK_REFERENCE.md
28
+ - docs/README.md
29
+ - docs/SECURITY.md
30
+ - docs/USAGE_GUIDE.md
31
+ - lib/rma/payment/gateway.rb
32
+ - lib/rma/payment/gateway/account_inquiry.rb
33
+ - lib/rma/payment/gateway/authorization.rb
34
+ - lib/rma/payment/gateway/client.rb
35
+ - lib/rma/payment/gateway/configuration.rb
36
+ - lib/rma/payment/gateway/debit_request.rb
37
+ - lib/rma/payment/gateway/errors.rb
38
+ - lib/rma/payment/gateway/utils.rb
39
+ - lib/rma/payment/gateway/version.rb
40
+ - sig/rma/payment/gateway.rbs
41
+ homepage: https://github.com/dcplbt/rma-payment-gateway
42
+ licenses: []
43
+ metadata:
44
+ allowed_push_host: https://rubygems.org
45
+ homepage_uri: https://github.com/dcplbt/rma-payment-gateway
46
+ source_code_uri: https://github.com/dcplbt/rma-payment-gateway
47
+ changelog_uri: https://github.com/dcplbt/rma-payment-gateway/blob/main/CHANGELOG.md
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 3.2.0
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.7.2
63
+ specification_version: 4
64
+ summary: RMA Payment Gateway
65
+ test_files: []