parsbank 0.0.4 → 0.0.8
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.
- checksums.yaml +4 -4
- data/README.md +150 -64
- data/lib/configuration.rb +3 -1
- data/lib/db_setup.rb +103 -0
- data/lib/locales/en.yml +18 -0
- data/lib/locales/fa.yml +19 -0
- data/lib/parsbank/{bsc-bitcoin/bsc-bitcoin.rb → binance/binance.rb} +2 -1
- data/lib/parsbank/gates.rb +16 -0
- data/lib/parsbank/mellat/logo.svg +1 -0
- data/lib/parsbank/mellat/mellat.rb +2 -6
- data/lib/parsbank/nobitex/logo.svg +6 -0
- data/lib/parsbank/nobitex/nobitex.rb +1 -15
- data/lib/parsbank/restfull.rb +130 -71
- data/lib/parsbank/soap.rb +106 -0
- data/lib/parsbank/transaction_request.rb +134 -0
- data/lib/parsbank/transaction_verify.rb +135 -0
- data/lib/parsbank/version.rb +1 -1
- data/lib/parsbank/zarinpal/logo.svg +1 -1
- data/lib/parsbank/zarinpal/zarinpal.rb +89 -60
- data/lib/parsbank/zibal/zibal.rb +1 -18
- data/lib/parsbank.rb +35 -156
- data/lib/psp.json +84 -0
- data/lib/tmpl/_loader.html.erb +105 -0
- data/lib/tmpl/bank_list.html.erb +215 -0
- metadata +17 -47
- /data/lib/parsbank/{bsc-bitcoin/bsc-bitcoin.svg → binance/logo.svg} +0 -0
data/lib/parsbank/restfull.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'json'
|
6
|
+
|
1
7
|
module Parsbank
|
2
8
|
class Restfull
|
3
9
|
attr_accessor :connection
|
4
10
|
|
11
|
+
MAX_RETRIES = 3
|
12
|
+
RETRY_INTERVAL = 5 # In seconds
|
13
|
+
|
5
14
|
def initialize(args = {})
|
6
15
|
@endpoint = args.fetch(:endpoint)
|
7
16
|
@action = args.fetch(:action)
|
@@ -16,16 +25,14 @@ module Parsbank
|
|
16
25
|
def call
|
17
26
|
response = send_request
|
18
27
|
|
19
|
-
|
28
|
+
log_response(response)
|
20
29
|
|
21
|
-
if response.
|
22
|
-
response
|
30
|
+
if response.is_a?(Net::HTTPSuccess)
|
31
|
+
parse_response(response)
|
23
32
|
else
|
24
33
|
log_and_raise_error(response)
|
25
34
|
end
|
26
|
-
rescue
|
27
|
-
handle_error("Connection failed: #{e.message}", e)
|
28
|
-
rescue Faraday::TimeoutError => e
|
35
|
+
rescue Timeout::Error => e
|
29
36
|
handle_error("Request timed out: #{e.message}", e)
|
30
37
|
rescue StandardError => e
|
31
38
|
handle_error("An unexpected error occurred: #{e.message}", e)
|
@@ -33,103 +40,155 @@ module Parsbank
|
|
33
40
|
|
34
41
|
private
|
35
42
|
|
36
|
-
def
|
37
|
-
|
43
|
+
def setup_connection
|
44
|
+
@uri = URI.parse(@endpoint)
|
45
|
+
@connection = Net::HTTP.new(@uri.host, @uri.port)
|
46
|
+
@connection.use_ssl = @uri.scheme == 'https'
|
47
|
+
@connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
48
|
+
@connection.set_debug_output($stdout) if Parsbank.configuration.debug
|
49
|
+
# Setting timeouts
|
50
|
+
@connection.open_timeout = 10 # Time to wait for the connection to open
|
51
|
+
@connection.read_timeout = 10 # Time to wait for a response
|
52
|
+
end
|
38
53
|
|
39
|
-
|
40
|
-
|
54
|
+
def send_request
|
55
|
+
retries = 0
|
56
|
+
begin
|
57
|
+
request = build_request
|
58
|
+
@connection.start do
|
59
|
+
response = @connection.request(request)
|
41
60
|
|
42
|
-
|
43
|
-
|
44
|
-
conn.response :json if @response_type == :json # Automatically parses JSON response
|
45
|
-
conn.adapter Faraday.default_adapter
|
46
|
-
conn.request :retry, max: 3, interval: 0.05,
|
47
|
-
interval_randomness: 0.5, backoff_factor: 2,
|
48
|
-
exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
|
49
|
-
conn.use FaradayMiddleware::FollowRedirects
|
50
|
-
end
|
61
|
+
# Handling redirects manually (max 5 redirects)
|
62
|
+
handle_redirects(response)
|
51
63
|
|
52
|
-
|
53
|
-
when :post
|
54
|
-
connection.post('&parsbank') do |req|
|
55
|
-
req.headers = headers
|
56
|
-
req.body = {}
|
64
|
+
return response
|
57
65
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
66
|
+
rescue Timeout::Error => e
|
67
|
+
retries += 1
|
68
|
+
raise "Request timed out after #{MAX_RETRIES} retries: #{e.message}" unless retries < MAX_RETRIES
|
69
|
+
|
70
|
+
log_to_rails("Timeout occurred. Retrying... (#{retries}/#{MAX_RETRIES})", :warn)
|
71
|
+
sleep(RETRY_INTERVAL)
|
72
|
+
retry
|
73
|
+
rescue StandardError => e
|
74
|
+
log_to_rails("Request failed: #{e.message}", :error)
|
75
|
+
raise e
|
61
76
|
end
|
62
|
-
rescue Faraday::ConnectionFailed => e
|
63
|
-
Rails.logger.error("Webhook Connection failed: #{e.message}", e)
|
64
|
-
rescue Faraday::TimeoutError => e
|
65
|
-
Rails.logger.error("Webhook Request timed out: #{e.message}", e)
|
66
|
-
rescue StandardError => e
|
67
|
-
Rails.logger.error("Webhook An unexpected error occurred: #{e.message}", e)
|
68
77
|
end
|
69
78
|
|
70
|
-
def
|
71
|
-
@connection = Faraday.new(@endpoint) do |conn|
|
72
|
-
conn.request :json if @response_type == :json # Automatically converts payload to JSON
|
73
|
-
conn.response :json if @response_type == :json # Automatically parses JSON response
|
74
|
-
conn.adapter Faraday.default_adapter
|
75
|
-
conn.request :retry, max: 3, interval: 0.05,
|
76
|
-
interval_randomness: 0.5, backoff_factor: 2,
|
77
|
-
exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
|
78
|
-
conn.use FaradayMiddleware::FollowRedirects
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def send_request
|
83
|
-
headers = default_headers.merge(@headers)
|
84
|
-
|
79
|
+
def build_request
|
85
80
|
case @http_method
|
86
81
|
when :post
|
87
|
-
|
82
|
+
build_post_request
|
88
83
|
when :get
|
89
|
-
|
84
|
+
build_get_request
|
90
85
|
when :options
|
91
|
-
|
86
|
+
build_options_request
|
92
87
|
else
|
93
88
|
raise ArgumentError, "HTTP Method Not Allowed: #{@http_method}"
|
94
89
|
end
|
95
90
|
end
|
96
91
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
92
|
+
def build_post_request
|
93
|
+
request = Net::HTTP::Post.new(@action, default_headers)
|
94
|
+
request['Connection'] = 'close' # Avoid HTTP/2 issues
|
95
|
+
request.body = JSON.generate(@request_message) if @request_message.any?
|
96
|
+
puts "Request Header: #{default_headers}" if Parsbank.configuration.debug
|
97
|
+
puts "Request Body: #{request.body}" if Parsbank.configuration.debug
|
98
|
+
request
|
102
99
|
end
|
103
100
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
101
|
+
def build_get_request
|
102
|
+
request = Net::HTTP::Get.new(@action, default_headers)
|
103
|
+
request.set_form_data(@request_message) if @request_message.any?
|
104
|
+
request
|
109
105
|
end
|
110
106
|
|
111
|
-
def
|
112
|
-
|
113
|
-
req.headers = headers
|
114
|
-
end
|
107
|
+
def build_options_request
|
108
|
+
Net::HTTP::Options.new(@action, default_headers)
|
115
109
|
end
|
116
110
|
|
117
111
|
def default_headers
|
118
112
|
{
|
119
|
-
'
|
120
|
-
'
|
121
|
-
|
113
|
+
'content-type' => 'application/json',
|
114
|
+
'accept' => 'application/json',
|
115
|
+
'user-agent' => 'ParsBank Ruby Gem',
|
116
|
+
'app-version' => Parsbank::VERSION
|
117
|
+
}.merge(@headers)
|
118
|
+
end
|
119
|
+
|
120
|
+
def log_response(response)
|
121
|
+
log_to_rails("Received response with status: #{response.code}, body: #{response.body.inspect}", :info)
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_response(response)
|
125
|
+
return response.body if @response_type == :raw
|
126
|
+
|
127
|
+
case @response_type
|
128
|
+
when :json
|
129
|
+
JSON.parse(response.body)
|
130
|
+
else
|
131
|
+
response.body
|
132
|
+
end
|
122
133
|
end
|
123
134
|
|
124
135
|
def log_and_raise_error(response)
|
125
|
-
|
126
|
-
|
136
|
+
log_to_rails(
|
137
|
+
"Request to #{@endpoint}/#{@action} failed with status: #{response.code}, error: #{response.body.inspect}", :error
|
138
|
+
)
|
139
|
+
raise "API request failed with status #{response.code}: #{response.body}"
|
127
140
|
end
|
128
141
|
|
129
142
|
def handle_error(message, exception)
|
130
|
-
|
143
|
+
log_to_rails(message, :error)
|
131
144
|
webhook(message) if Parsbank.configuration.webhook.present?
|
132
145
|
raise exception
|
133
146
|
end
|
147
|
+
|
148
|
+
def log_to_rails(message, level = :info)
|
149
|
+
case level
|
150
|
+
when :error
|
151
|
+
Rails.logger.error(message) if defined?(Rails)
|
152
|
+
when :warn
|
153
|
+
Rails.logger.warn(message) if defined?(Rails)
|
154
|
+
else
|
155
|
+
Rails.logger.info(message) if defined?(Rails)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def webhook(message)
|
160
|
+
webhook_url = Parsbank.configuration.webhook
|
161
|
+
webhook_url.gsub!('MESSAGE', message) if Parsbank.configuration.webhook_method == :get
|
162
|
+
if Parsbank.configuration.webhook_method == :get
|
163
|
+
webhook_url.gsub!('TITLE',
|
164
|
+
"Webhook of Connection Error at #{Time.now}")
|
165
|
+
end
|
166
|
+
|
167
|
+
uri = URI.parse(webhook_url)
|
168
|
+
connection = Net::HTTP.new(uri.host, uri.port)
|
169
|
+
connection.use_ssl = uri.scheme == 'https'
|
170
|
+
|
171
|
+
case Parsbank.configuration.webhook_method
|
172
|
+
when :post
|
173
|
+
request = Net::HTTP::Post.new(uri.path, default_headers)
|
174
|
+
request.body = {}.to_json
|
175
|
+
connection.request(request)
|
176
|
+
when :get
|
177
|
+
request = Net::HTTP::Get.new(uri.path, default_headers)
|
178
|
+
connection.request(request)
|
179
|
+
end
|
180
|
+
rescue StandardError => e
|
181
|
+
log_to_rails("Webhook Error: #{e.message}", :error)
|
182
|
+
end
|
183
|
+
|
184
|
+
def handle_redirects(response)
|
185
|
+
return unless response.is_a?(Net::HTTPRedirection)
|
186
|
+
|
187
|
+
location = response['Location']
|
188
|
+
log_to_rails("Redirecting to: #{location}", :warn)
|
189
|
+
redirect_uri = URI.parse(location)
|
190
|
+
request = Net::HTTP::Get.new(redirect_uri)
|
191
|
+
@connection.request(request)
|
192
|
+
end
|
134
193
|
end
|
135
194
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Parsbank
|
7
|
+
# SOAP Client Class - Standard SOAP generation with namespaces and headers
|
8
|
+
class SOAP
|
9
|
+
attr_reader :endpoint, :namespace, :logger
|
10
|
+
|
11
|
+
# Initialize SOAP Client with endpoint, namespace, and optional logger
|
12
|
+
def initialize(endpoint, namespace)
|
13
|
+
@endpoint = URI.parse(endpoint)
|
14
|
+
@namespace = namespace
|
15
|
+
@logger = Logger.new(STDOUT)
|
16
|
+
@logger.level = Parsbank.configuration.debug ? Logger::DEBUG : Logger::WARN
|
17
|
+
end
|
18
|
+
|
19
|
+
# Main method to make a SOAP request
|
20
|
+
def call(action, body, headers = {})
|
21
|
+
log_request(action, body)
|
22
|
+
|
23
|
+
xml = build_envelope(action, body.map { |k, v| "<s:#{k}>#{v}</#{k}>" }.join(''))
|
24
|
+
|
25
|
+
http = Net::HTTP.new(@endpoint.host, @endpoint.port)
|
26
|
+
http.use_ssl = (@endpoint.scheme == 'https')
|
27
|
+
|
28
|
+
request = Net::HTTP::Post.new(@endpoint.request_uri)
|
29
|
+
request.content_type = 'text/xml; charset=utf-8'
|
30
|
+
request['SOAPAction'] = "#{@namespace}/#{action}"
|
31
|
+
request.body = xml
|
32
|
+
|
33
|
+
headers.store('Parsbank-RubyGem', Parsbank::VERSION)
|
34
|
+
headers.each { |key, value| request[key] = value }
|
35
|
+
|
36
|
+
begin
|
37
|
+
response = http.request(request)
|
38
|
+
log_response(response)
|
39
|
+
parse_response(response)
|
40
|
+
rescue Timeout::Error => e
|
41
|
+
handle_error("Request timed out", e)
|
42
|
+
rescue SocketError => e
|
43
|
+
handle_error("Network connection failed", e)
|
44
|
+
rescue Errno::ECONNREFUSED => e
|
45
|
+
handle_error("Connection refused by server", e)
|
46
|
+
rescue StandardError => e
|
47
|
+
handle_error("Unexpected error: #{e.message}", e)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Build the SOAP XML Envelope
|
54
|
+
def build_envelope(action, body)
|
55
|
+
<<~XML
|
56
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
57
|
+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
|
58
|
+
xmlns:s="#{@namespace}">
|
59
|
+
<soap:Header/>
|
60
|
+
<soap:Body>
|
61
|
+
<s:#{action}>
|
62
|
+
#{body}
|
63
|
+
</s:#{action}>
|
64
|
+
</soap:Body>
|
65
|
+
</soap:Envelope>
|
66
|
+
XML
|
67
|
+
end
|
68
|
+
|
69
|
+
# Parse the XML response body
|
70
|
+
def parse_response(response)
|
71
|
+
if response.is_a?(Net::HTTPSuccess)
|
72
|
+
puts "Response Success"
|
73
|
+
puts response.body
|
74
|
+
parse_xml(response.body)
|
75
|
+
else
|
76
|
+
{ error: "HTTP Error #{response.code}: #{response.message}" }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Convert XML to Hash
|
81
|
+
def parse_xml(xml)
|
82
|
+
begin
|
83
|
+
doc = REXML::Document.new(xml)
|
84
|
+
{ success: true, body: doc }
|
85
|
+
rescue REXML::ParseException => e
|
86
|
+
{ success: false, error: "Invalid XML response: #{e.message}" }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Error handling and logging
|
91
|
+
def handle_error(message, exception)
|
92
|
+
@logger.error("#{message}: #{exception.message}")
|
93
|
+
{ success: false, error: message }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Log the SOAP request for debugging
|
97
|
+
def log_request(action, body)
|
98
|
+
@logger.debug("Sending SOAP request: Action = #{action}, Body = #{body}")
|
99
|
+
end
|
100
|
+
|
101
|
+
# Log the SOAP response for debugging
|
102
|
+
def log_response(response)
|
103
|
+
@logger.debug("Received SOAP response: Status = #{response.code}, Body = #{response.body}")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Parsbank
|
2
|
+
class TransactionRequest
|
3
|
+
def self.create(args = {})
|
4
|
+
bank = fetch_bank(args)
|
5
|
+
validate_bank(bank)
|
6
|
+
|
7
|
+
description = args.fetch(:description)
|
8
|
+
callback_url = generate_callback_url(bank, description)
|
9
|
+
|
10
|
+
crypto_amount, fiat_amount, real_amount = fetch_amounts(args)
|
11
|
+
validate_amounts(bank, crypto_amount, fiat_amount, real_amount)
|
12
|
+
|
13
|
+
transaction = initialize_transaction(args, description, fiat_amount, crypto_amount, bank, callback_url)
|
14
|
+
process_gateway(bank, transaction, description, callback_url, crypto_amount, fiat_amount, args)
|
15
|
+
|
16
|
+
{ transaction: transaction, html_form: @result }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.fetch_bank(args)
|
20
|
+
args.fetch(:bank, Parsbank.available_gateways_list.keys.sample)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.validate_bank(bank)
|
24
|
+
selected_bank = Parsbank.available_gateways_list[bank]
|
25
|
+
raise "Bank not enabled or not exists in #{Parsbank.configuration.secrets_path}: #{bank}" unless selected_bank
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.generate_callback_url(bank, _description)
|
29
|
+
selected_bank = Parsbank.available_gateways_list[bank]
|
30
|
+
"#{selected_bank['callback_url'] || Parsbank.configuration.callback_url}&bank_name=#{bank}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.fetch_amounts(args)
|
34
|
+
[
|
35
|
+
args.fetch(:crypto_amount, nil),
|
36
|
+
args.fetch(:fiat_amount, nil),
|
37
|
+
args.fetch(:real_amount, nil)
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.validate_amounts(bank, crypto_amount, fiat_amount, real_amount)
|
42
|
+
raise 'Amount fields are empty: crypto_amount OR fiat_amount OR real_amount' if [crypto_amount, fiat_amount,
|
43
|
+
real_amount].all?(&:nil?)
|
44
|
+
|
45
|
+
tags = Parsbank.supported_psp[bank]['tags']
|
46
|
+
if tags.include?('crypto') && crypto_amount.nil? && real_amount.nil?
|
47
|
+
raise "#{bank} needs crypto_amount or real_amount"
|
48
|
+
end
|
49
|
+
|
50
|
+
return unless tags.include?('rial') && fiat_amount.nil? && real_amount.nil?
|
51
|
+
|
52
|
+
raise "#{bank} needs fiat_amount or real_amount"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.initialize_transaction(args, description, fiat_amount, crypto_amount, bank, callback_url)
|
56
|
+
model_class = Parsbank.configuration.model || 'Transaction'
|
57
|
+
@transaction= Object.const_get(model_class).new(
|
58
|
+
description: description,
|
59
|
+
amount: fiat_amount || crypto_amount,
|
60
|
+
gateway: bank,
|
61
|
+
callback_url: callback_url,
|
62
|
+
status: 'start',
|
63
|
+
user_id: args.fetch(:user_id, nil),
|
64
|
+
cart_id: args.fetch(:cart_id, nil),
|
65
|
+
local_id: args.fetch(:local_id, nil),
|
66
|
+
ip: args.fetch(:ip, nil)
|
67
|
+
)
|
68
|
+
@transaction.save
|
69
|
+
|
70
|
+
@transaction
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.process_gateway(bank, transaction, description, callback_url, crypto_amount, fiat_amount, args)
|
74
|
+
case bank
|
75
|
+
when 'mellat'
|
76
|
+
process_mellat(transaction, description, callback_url, fiat_amount)
|
77
|
+
when 'zarinpal'
|
78
|
+
process_zarinpal(transaction, description, callback_url, fiat_amount)
|
79
|
+
when 'zibal'
|
80
|
+
process_zibal(description, callback_url, fiat_amount)
|
81
|
+
when 'bscbitcoin'
|
82
|
+
process_bscbitcoin(transaction, description, crypto_amount, args)
|
83
|
+
else
|
84
|
+
raise "Unsupported gateway: #{bank}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.process_mellat(transaction, description, callback_url, fiat_amount)
|
89
|
+
mellat = Parsbank::Mellat.new(
|
90
|
+
amount: fiat_amount,
|
91
|
+
additional_data: description,
|
92
|
+
callback_url: callback_url,
|
93
|
+
orderId: transaction.id
|
94
|
+
)
|
95
|
+
mellat.call
|
96
|
+
transaction.update!(gateway_response: mellat.response, unit: 'irr')
|
97
|
+
@result = mellat.redirect_form
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.process_zarinpal(transaction, description, callback_url, fiat_amount)
|
101
|
+
zarinpal = Parsbank::Zarinpal.new(
|
102
|
+
amount: fiat_amount,
|
103
|
+
additional_data: description,
|
104
|
+
callback_url: callback_url
|
105
|
+
)
|
106
|
+
zarinpal.call
|
107
|
+
transaction.update!(
|
108
|
+
gateway_response: zarinpal.response,
|
109
|
+
track_id: zarinpal.ref_id,
|
110
|
+
unit: 'irt'
|
111
|
+
)
|
112
|
+
@result = zarinpal.redirect_form
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.process_zibal(description, callback_url, fiat_amount)
|
116
|
+
zibal = Parsbank::Zibal.new(
|
117
|
+
amount: fiat_amount,
|
118
|
+
additional_data: description,
|
119
|
+
callback_url: callback_url
|
120
|
+
)
|
121
|
+
zibal.call
|
122
|
+
@result = zibal.redirect_form
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.process_bscbitcoin(transaction, description, crypto_amount, args)
|
126
|
+
bscbitcoin = Parsbank::BscBitcoin.new(
|
127
|
+
additional_data: description
|
128
|
+
)
|
129
|
+
convert_real_amount_to_assets if crypto_amount.nil? && args.key?(:real_amount)
|
130
|
+
@result = bscbitcoin.generate_payment_address(amount: crypto_amount)
|
131
|
+
transaction.update!(gateway_response: @result, unit: 'bitcoin')
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Parsbank
|
4
|
+
class TransactionVerify
|
5
|
+
def self.verify(args = {})
|
6
|
+
validate_params(args.params)
|
7
|
+
|
8
|
+
transaction = initialize_transaction(args, description, fiat_amount, crypto_amount, bank, callback_url)
|
9
|
+
process_gateway(bank, transaction, description, callback_url, crypto_amount, fiat_amount, args)
|
10
|
+
|
11
|
+
{ transaction: transaction, html_form: @result }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.validate_params(params)
|
15
|
+
model_class = Parsbank.configuration.model || 'Transaction'
|
16
|
+
tack_transaction = Object.const_get(model_class).find(params[:transaction_id].to_i)
|
17
|
+
{ status: 404, message: I18n.t('model.not_found') } unless tack_transaction.present?
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.fetch_bank(args)
|
21
|
+
args.fetch(:bank)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.validate_bank(bank)
|
25
|
+
selected_bank = Parsbank.available_gateways_list[bank]
|
26
|
+
raise "Bank not enabled or not exists in #{Parsbank.configuration.secrets_path}: #{bank}" unless selected_bank
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.generate_callback_url(bank, _description)
|
30
|
+
selected_bank = Parsbank.available_gateways_list[bank]
|
31
|
+
"#{selected_bank['callback_url'] || Parsbank.configuration.callback_url}&bank_name=#{bank}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.fetch_amounts(args)
|
35
|
+
[
|
36
|
+
args.fetch(:crypto_amount, nil),
|
37
|
+
args.fetch(:fiat_amount, nil),
|
38
|
+
args.fetch(:real_amount, nil)
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.validate_amounts(bank, crypto_amount, fiat_amount, real_amount)
|
43
|
+
raise 'Amount fields are empty: crypto_amount OR fiat_amount OR real_amount' if [crypto_amount, fiat_amount,
|
44
|
+
real_amount].all?(&:nil?)
|
45
|
+
|
46
|
+
tags = $SUPPORTED_PSP[bank]['tags']
|
47
|
+
if tags.include?('crypto') && crypto_amount.nil? && real_amount.nil?
|
48
|
+
raise "#{bank} needs crypto_amount or real_amount"
|
49
|
+
end
|
50
|
+
|
51
|
+
return unless tags.include?('rial') && fiat_amount.nil? && real_amount.nil?
|
52
|
+
|
53
|
+
raise "#{bank} needs fiat_amount or real_amount"
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.initialize_transaction(args, description, fiat_amount, crypto_amount, bank, callback_url)
|
57
|
+
model_class = Parsbank.configuration.model || 'Transaction'
|
58
|
+
@transaction = Object.const_get(model_class).new(
|
59
|
+
description: description,
|
60
|
+
amount: fiat_amount || crypto_amount,
|
61
|
+
gateway: bank,
|
62
|
+
callback_url: callback_url,
|
63
|
+
status: 'start',
|
64
|
+
user_id: args.fetch(:user_id, nil),
|
65
|
+
cart_id: args.fetch(:cart_id, nil),
|
66
|
+
local_id: args.fetch(:local_id, nil),
|
67
|
+
ip: args.fetch(:ip, nil)
|
68
|
+
)
|
69
|
+
@transaction.save
|
70
|
+
|
71
|
+
@transaction
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.process_gateway(bank, transaction, description, callback_url, crypto_amount, fiat_amount, args)
|
75
|
+
case bank
|
76
|
+
when 'mellat'
|
77
|
+
process_mellat(transaction, description, callback_url, fiat_amount)
|
78
|
+
when 'zarinpal'
|
79
|
+
process_zarinpal(transaction, description, callback_url, fiat_amount)
|
80
|
+
when 'zibal'
|
81
|
+
process_zibal(description, callback_url, fiat_amount)
|
82
|
+
when 'bscbitcoin'
|
83
|
+
process_bscbitcoin(transaction, description, crypto_amount, args)
|
84
|
+
else
|
85
|
+
raise "Unsupported gateway: #{bank}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.process_mellat(transaction, description, callback_url, fiat_amount)
|
90
|
+
mellat = Parsbank::Mellat.new(
|
91
|
+
amount: fiat_amount,
|
92
|
+
additional_data: description,
|
93
|
+
callback_url: callback_url,
|
94
|
+
orderId: transaction.id
|
95
|
+
)
|
96
|
+
mellat.call
|
97
|
+
transaction.update!(gateway_response: mellat.response, unit: 'irr')
|
98
|
+
@result = mellat.redirect_form
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.process_zarinpal(transaction, description, callback_url, fiat_amount)
|
102
|
+
zarinpal = Parsbank::Zarinpal.new(
|
103
|
+
amount: fiat_amount,
|
104
|
+
additional_data: description,
|
105
|
+
callback_url: callback_url
|
106
|
+
)
|
107
|
+
zarinpal.call
|
108
|
+
transaction.update!(
|
109
|
+
gateway_response: zarinpal.response,
|
110
|
+
track_id: zarinpal.ref_id,
|
111
|
+
unit: 'irt'
|
112
|
+
)
|
113
|
+
@result = zarinpal.redirect_form
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.process_zibal(description, callback_url, fiat_amount)
|
117
|
+
zibal = Parsbank::Zibal.new(
|
118
|
+
amount: fiat_amount,
|
119
|
+
additional_data: description,
|
120
|
+
callback_url: callback_url
|
121
|
+
)
|
122
|
+
zibal.call
|
123
|
+
@result = zibal.redirect_form
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.process_bscbitcoin(transaction, description, crypto_amount, args)
|
127
|
+
bscbitcoin = Parsbank::BscBitcoin.new(
|
128
|
+
additional_data: description
|
129
|
+
)
|
130
|
+
convert_real_amount_to_assets if crypto_amount.nil? && args.key?(:real_amount)
|
131
|
+
@result = bscbitcoin.generate_payment_address(amount: crypto_amount)
|
132
|
+
transaction.update!(gateway_response: @result, unit: 'bitcoin')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/parsbank/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
<svg
|
1
|
+
<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" viewBox="0 0 1575 350">
|
2
2
|
<defs>
|
3
3
|
<style>
|
4
4
|
.cls-1 {
|