parsbank 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 99f934eb7c7f86c5672690ea579ec6e1e5839500ec0c75268a49a1878bc50973
4
+ data.tar.gz: be36aa6271f70befb1ab12486bfa996757032807bd3ca39a43df2cb47dc7efbe
5
+ SHA512:
6
+ metadata.gz: 10d5222300d787982406ac0d8f260609f3fc6aa14ab8f2dc7317be84755c70cf79e49aab330dfc569ab970636c42efec1e3cc60d5f57308274db123b07f1933d
7
+ data.tar.gz: 03d92ce711d799e2d8cb268c8fb9c176926cc69eadc6af4030c1730fe50ba0c42bbefb0dddcdac46a7ce39f7ab971c8284fddc8703e650467eb98883551dd7b3
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-12-05
4
+
5
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # ParsBank
2
+ ==============
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/parsbank.svg)](https://rubygems.org/gems/parsbank)
5
+ ![Build](https://github.com/abrfanet/ParsBank/workflows/CI/badge.svg)
6
+
7
+
8
+ ParsBank Gateway
9
+
10
+ An Ruby Gem Library for integrate with WSDL and JSON of Persian Banks, In this Gem we use soap and faraday lib as main dependency also we tunned soap/faraday for multile retries when failed connections or request, in the end we work on proxy wrapper for connct to core bank with MITM server
11
+
12
+ ## Installation
13
+
14
+ Install the gem and add to the application's Gemfile by executing:
15
+
16
+ $ bundle add ParsBank
17
+
18
+ If bundler is not being used to manage dependencies, install the gem by executing:
19
+
20
+ $ gem install ParsBank
21
+
22
+ In sinatra application just add `gem "ParsBank"` on your Gemfile and install with `bundle install`
23
+
24
+ ## Usage
25
+
26
+ First step:
27
+
28
+ in config/initilizers create new config file:
29
+ ```
30
+ #config/initilizers/pars_bank.rb
31
+
32
+
33
+ ParsBank.configuration do |config|
34
+
35
+ config.callback_url = 'YOUR CALLBACK LOCATION LIKE https://example.com/CallBack'
36
+
37
+ config.debug = false # Enable Log Tracking with Rails.log and STDOUT
38
+
39
+ config.sandbox = false # Enable Simulation for your requst also approve callback without verification
40
+
41
+ config.webhook = "https://yoursite.com/income-webhook?title=TITLE&message=MESSAGE" # Webhook for notify any success transactions or errors on cominiucate with Core Bank
42
+ config.webhook_method = 'GET' # or POST
43
+
44
+ config.mitm_server = 'YOUR_MITM_SERVER_LOCATION as HTTP or HTTPS'
45
+
46
+ config.secrets_path = Rails.root.join('config/bank_secrets.yaml') #PATH OF YOUR BANKS CREDITS like merchant id, username, password or token
47
+
48
+ config.min_amount = '10000' # as rials
49
+
50
+ # WebPanel Config
51
+ config.webpanel_path = '/parsbank'
52
+ ## Basic Authentication
53
+ config.username = ENV['PARSBANK_USERNAME']
54
+ config.password = ENV['PARSBANK_PASSWORD']
55
+ ## Authetication With IP source
56
+ config.allowed_ips = ['192.168.10.10'] # add * to allow all ip
57
+ ## Authentication with rails model
58
+ config.allow_when = User.find_by(username: USERNAME).authenticate(PASSWORD) && User.find_by(username: USERNAME).admin?
59
+
60
+ # Secure by captcha
61
+ config.captcha = false
62
+
63
+ # Model for store transactions
64
+ # Transaction model should have amount, status, bank_name, callback_url, authority_code or anything you need
65
+ config.model = Transaction
66
+
67
+
68
+ end
69
+
70
+ ```
71
+
72
+
73
+ Inside of your controller call Token action and get url for redirect user to Gateway page
74
+
75
+ ```
76
+ class ApplicationController > Cart
77
+ def redirect_to_ParsBank
78
+ form = ParsBank.get_redirect_from(amount: 100000, description: 'Charge Account')
79
+ render html: form
80
+ end
81
+ end
82
+ ```
83
+
84
+
85
+
86
+ # ParsBank Amazing Web
87
+ With ParsBank Web You Can Access To Your Transactions and Config Files Visualy! Also You Get Beautifull Dashboard with Canva Graph For Analysis Your Transaction And Improve Your Campiagn And Important Decisions ⭐
88
+
89
+ ```
90
+ Important Note: When Use ParsBank Web you should apply CIS rules and all harening rules for secure your credentials of banks and virtuals account like binance.
91
+ ```
92
+
93
+ Get Ready For ParsBank Web Gem:
94
+
95
+ ## Method 1 (Isolated Dockerfile)
96
+ Requrements:
97
+ - Docker
98
+ - Nginx or Apache Reverse Proxy for forward trafik to specific port
99
+ - ParsBank Web use sinatra with Concurency so needs considerable resource like RAM, CPU or next-gen of Hard Drive
100
+ in first step clone git repository `git clone https://github.com/Abrfanet/parsbank-web`
101
+
102
+
103
+ ## Method 2 (Inside of Rails App)
104
+
105
+ ## Development
106
+
107
+ We don't accept any pull request, just use issue section
108
+
109
+ ## Contributing
110
+
111
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ParsBank.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,34 @@
1
+
2
+ class Configuration
3
+ attr_accessor :callback_url, :debug, :sandbox,
4
+ :webhook,
5
+ :webhook_method,
6
+ :mitm_server,
7
+ :secrets_path,
8
+ :min_amount,
9
+ :webpanel_path,
10
+ :username,
11
+ :password,
12
+ :allowed_ips,
13
+ :allow_when,
14
+ :captcha,
15
+ :model
16
+
17
+ def initialize
18
+ @callback_url = nil
19
+ @debug = false
20
+ @sandbox = false
21
+ @webhook = nil
22
+ @webhook_method = "POST"
23
+ @mitm_server = nil
24
+ @secrets_path = nil
25
+ @min_amount = 10000
26
+ @webpanel_path = nil
27
+ @username = nil
28
+ @password = nil
29
+ @allowed_ips = ['*']
30
+ @allow_when = -> { true }
31
+ @captcha = false
32
+ @model = nil
33
+ end
34
+ end
@@ -0,0 +1,95 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ require 'openssl'
4
+ require 'base64'
5
+
6
+ module Parsbank
7
+ class BscBitcoin
8
+
9
+ attr_accessor :api_key, :secret_key, :endpoint
10
+
11
+ def initialize(args = {})
12
+ @api_key = args.fetch(:api_key, default_config(:api_key))
13
+ @secret_key = args.fetch(:secret_key, default_config(:secret_key))
14
+ @connection = Faraday.new(default_config(:endpoint)) do |conn|
15
+ conn.request :json
16
+ conn.response :json, content_type: 'application/json'
17
+ conn.adapter Faraday.default_adapter
18
+ end
19
+ end
20
+
21
+ # Generate a payment address for a given cryptocurrency
22
+ def generate_payment_address(asset:, network: nil)
23
+ endpoint = '/sapi/v1/capital/deposit/address'
24
+ params = {
25
+ asset: asset,
26
+ network: network
27
+ }
28
+ response = signed_request(:get, endpoint, params)
29
+ response_body(response)
30
+ end
31
+
32
+ # Verify a transaction by checking its status
33
+ def verify_transaction(tx_id:, asset:)
34
+ endpoint = '/sapi/v1/capital/deposit/hisrec'
35
+ params = {
36
+ txId: tx_id,
37
+ asset: asset
38
+ }
39
+ response = signed_request(:get, endpoint, params)
40
+ transactions = response_body(response)
41
+
42
+ transaction = transactions.find { |t| t['txId'] == tx_id }
43
+ transaction && transaction['status'] == 1
44
+ end
45
+
46
+ # Get the latest transactions for a given asset
47
+ def latest_transactions(asset:, limit: 10)
48
+ endpoint = '/sapi/v1/capital/deposit/hisrec'
49
+ params = {
50
+ asset: asset,
51
+ limit: limit
52
+ }
53
+ response = signed_request(:get, endpoint, params)
54
+ response_body(response)
55
+ end
56
+
57
+ private
58
+
59
+ def default_config(key)
60
+ Parsbank.load_secrets_yaml[self.class.name.split('::').last.downcase][key.to_s]
61
+ end
62
+ # Helper method to handle signed requests
63
+ def signed_request(http_method, endpoint, params = {})
64
+ params[:timestamp] = current_timestamp
65
+ query_string = URI.encode_www_form(params)
66
+ signature = generate_signature(query_string)
67
+ headers = {
68
+ 'X-MBX-APIKEY' => @api_key
69
+ }
70
+
71
+ @connection.send(http_method) do |req|
72
+ req.url endpoint
73
+ req.params = params.merge(signature: signature)
74
+ req.headers = headers
75
+ end
76
+ end
77
+
78
+ # Generate HMAC SHA256 signature
79
+ def generate_signature(query_string)
80
+ OpenSSL::HMAC.hexdigest('SHA256', @secret_key, query_string)
81
+ end
82
+
83
+ # Helper method to parse response
84
+ def response_body(response)
85
+ raise "Binance API Error: #{response.status} - #{response.body}" unless response.success?
86
+
87
+ JSON.parse(response.body)
88
+ end
89
+
90
+ # Current timestamp in milliseconds
91
+ def current_timestamp
92
+ (Time.now.to_f * 1000).to_i
93
+ end
94
+ end
95
+ end
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 126.61 126.61" xmlns="http://www.w3.org/2000/svg"><g fill="#f3ba2f"><path d="m38.73 53.2 24.59-24.58 24.6 24.6 14.3-14.31-38.9-38.91-38.9 38.9z"/><path d="m0 63.31 14.3-14.31 14.31 14.31-14.31 14.3z"/><path d="m38.73 73.41 24.59 24.59 24.6-24.6 14.31 14.29-38.9 38.91-38.91-38.88z"/><path d="m98 63.31 14.3-14.31 14.31 14.3-14.31 14.32z"/><path d="m77.83 63.3-14.51-14.52-10.73 10.73-1.24 1.23-2.54 2.54 14.51 14.5 14.51-14.47z"/></g></svg>
@@ -0,0 +1,100 @@
1
+ module Parsbank
2
+ class Mellat
3
+ attr_accessor :order_id, :amount, :local_date, :local_time, :additional_data, :payer_id, :callback_url
4
+ attr_reader :response, :status, :status_message, :ref_id
5
+
6
+ def initialize(args = {})
7
+ @order_id = args.fetch(:orderId)
8
+ @amount = args.fetch(:amount)
9
+ @local_date = args.fetch(:localDate, Time.now.strftime('%Y%m%d'))
10
+ @local_time = args.fetch(:localTime, Time.now.strftime('%H%M%S'))
11
+ @additional_data = args.fetch(:additionalData, ' ')
12
+ @payer_id = args.fetch(:payerId, 0)
13
+ @callback_url = args.fetch(:callBackUrl, default_config(:callback_url))
14
+ @terminal_id = args.fetch(:terminalId, default_config(:terminal_id))
15
+ @username = args.fetch(:userName, default_config(:username))
16
+ @user_password = args.fetch(:userPassword, default_config(:password))
17
+ @wsdl = create_wsdl_client
18
+ rescue KeyError => e
19
+ raise ArgumentError, "Missing required argument: #{e.message}"
20
+ end
21
+
22
+ def validate(response = nil)
23
+ @response = response
24
+ perform_validation
25
+ self
26
+ end
27
+
28
+ def valid?
29
+ @valid
30
+ end
31
+
32
+ def ref_id
33
+ @ref_id.to_s
34
+ end
35
+
36
+ def call
37
+ response = @wsdl.call(:bp_pay_request, message: build_request_message)
38
+ validate(response.body)
39
+ rescue Savon::Error => e
40
+ raise "SOAP request failed: #{e.message}"
41
+ end
42
+
43
+ def redirect_form
44
+ `
45
+ <script type="text/javascript" charset="utf-8">
46
+ function postRefId (refIdValue) {
47
+ var form = document.createElement('form');
48
+ form.setAttribute('method', 'POST');
49
+ form.setAttribute('action', 'https://bpm.shaparak.ir/pgwchannel/startpay.mellat');
50
+ form.setAttribute("target', '_self");
51
+ var hiddenField = document.createElement("input");
52
+ hiddenField.setAttribute('name', 'RefId');
53
+ hiddenField.setAttribute('value', refIdValue);
54
+ form.appendChild(hiddenField);
55
+ document.body.appendChild(form);
56
+ form.submit();
57
+ document.body.removeChild(form);
58
+ }
59
+
60
+
61
+ postRefId('#{ref_id}') %>')
62
+ </script>
63
+ `
64
+ end
65
+
66
+ private
67
+
68
+ def default_config(key)
69
+ Parsbank.load_secrets_yaml[self.class.name.split("::").last.downcase][key.to_s]
70
+ end
71
+
72
+ def create_wsdl_client
73
+ Savon.client(
74
+ wsdl: default_config(:wsdl) || 'https://bpm.shaparak.ir/pgwchannel/services/pgw?wsdl',
75
+ pretty_print_xml: true,
76
+ namespace: 'http://interfaces.core.sw.bps.com/'
77
+ )
78
+ end
79
+
80
+ def build_request_message
81
+ {
82
+ 'terminalId' => @terminal_id,
83
+ 'userName' => @username,
84
+ 'userPassword' => @user_password,
85
+ 'orderId' => @order_id,
86
+ 'amount' => @amount,
87
+ 'localDate' => @local_date,
88
+ 'localTime' => @local_time,
89
+ 'additionalData' => @additional_data,
90
+ 'payerId' => @payer_id,
91
+ 'callBackUrl' => @callback_url
92
+ }
93
+ end
94
+
95
+ def perform_validation
96
+ # Logic for validation should be implemented here.
97
+ # Update @valid, @status, and @status_message based on @response.
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ module Parsbank
6
+ class Nobitex
7
+ attr_accessor :amount, :description, :email, :mobile, :merchant, :callbackUrl, :orderId, :allowedCards, :ledgerId,
8
+ :nationalCode, :checkMobileWithCard
9
+
10
+ attr_reader :response, :status, :status_message, :ref_id, :logo
11
+
12
+ def initialize(args = {})
13
+ @amount = args.fetch(:amount)
14
+ @description = args.fetch(:description, nil)
15
+ @email = args.fetch(:email, nil)
16
+ @mobile = args.fetch(:mobile, nil)
17
+ @merchant = args.fetch(:merchant, default_config(:merchant))
18
+ @callbackUrl = args.fetch(:callbackUrl, (default_config(:callback_url) || Parsbank.configuration.callback_url))
19
+ @orderId = args.fetch(:orderId, nil)
20
+ @allowedCards = args.fetch(:allowedCards, nil)
21
+ @ledgerId = args.fetch(:ledgerId, nil)
22
+ @nationalCode = args.fetch(:nationalCode, nil)
23
+ @checkMobileWithCard = args.fetch(:checkMobileWithCard, nil)
24
+ rescue KeyError => e
25
+ raise ArgumentError, "Missing required argument: #{e.message}"
26
+ end
27
+
28
+ def self.logo
29
+ file_path = "#{__dir__}/logo.svg"
30
+ return [404, { 'Content-Type' => 'text/plain' }, ['File not found']] unless File.exist?(file_path)
31
+
32
+ [
33
+ 200,
34
+ { 'Content-Type' => 'image/svg+xml' },
35
+ File.open(file_path, 'r')
36
+ ]
37
+ end
38
+
39
+ def validate(response = nil)
40
+ @response = response
41
+ @ref_id = @response['trackId']
42
+ @status = @response['result'].present? ? @response['result'] : 'FAILED'
43
+
44
+ perform_validation
45
+ self
46
+ end
47
+
48
+ def valid?
49
+ @valid
50
+ end
51
+
52
+ def ref_id
53
+ @ref_id.to_s
54
+ end
55
+
56
+ def call
57
+ create_rest_client
58
+ rescue Savon::Error => e
59
+ raise "SOAP request failed: #{e.message}"
60
+ end
61
+
62
+ def redirect_form
63
+ "
64
+ <script type='text/javascript' charset='utf-8'>
65
+ function postRefId (refIdValue) {
66
+ var form = document.createElement('form');
67
+ form.setAttribute('method', 'POST');
68
+ form.setAttribute('action', 'https://gateway.zibal.ir/start/#{ref_id}');
69
+ form.setAttribute('target', '_self');
70
+ var hiddenField = document.createElement('input');
71
+ hiddenField.setAttribute('name', 'RefId');
72
+ hiddenField.setAttribute('value', refIdValue);
73
+ form.appendChild(hiddenField);
74
+ document.body.appendChild(form);
75
+ form.submit();
76
+ document.body.removeChild(form);
77
+ }
78
+
79
+
80
+ postRefId('#{ref_id}') %>')
81
+ </script>
82
+ "
83
+ end
84
+
85
+ private
86
+
87
+ def default_config(key)
88
+ Parsbank.load_secrets_yaml[self.class.name.split('::').last.downcase][key.to_s]
89
+ end
90
+
91
+ def create_rest_client
92
+ connection = Parsbank::Restfull.new(
93
+ endpoint: default_config(:endpoint) || 'https://gateway.zibal.ir',
94
+ action: '/v1/request',
95
+ headers: {
96
+ 'Content-Type' => 'application/json',
97
+ 'Authorization' => "Bearer #{default_config(:access_token)}"
98
+ },
99
+ request_message: build_request_message,
100
+ http_method: :post,
101
+ response_type: :json
102
+ )
103
+
104
+ response = connection.call
105
+
106
+ Rails.logger.info "Received response with status: #{response.status}, body: #{response.body.inspect}"
107
+
108
+ if response.valid?
109
+ validate(response.body)
110
+ else
111
+ @valid = false
112
+ Rails.logger.error "POST request to #{BASE_URL}/#{endpoint} failed with status: #{response.status}, error: #{response.body.inspect}"
113
+ raise "API request failed with status #{response.status}: #{response.body}"
114
+ end
115
+ rescue Faraday::ConnectionFailed => e
116
+ Rails.logger.error "Connection failed: #{e.message}"
117
+ raise "Connection to API failed: #{e.message}"
118
+ rescue Faraday::TimeoutError => e
119
+ Rails.logger.error "Request timed out: #{e.message}"
120
+ raise "API request timed out: #{e.message}"
121
+ rescue StandardError => e
122
+ Rails.logger.error "An error occurred: #{e.message}"
123
+ raise "An unexpected error occurred: #{e.message}"
124
+
125
+ JSON.parse response.body
126
+ end
127
+
128
+ def build_request_message
129
+ {
130
+ 'amount' => @amount,
131
+ 'description' => @description,
132
+ 'email' => @email,
133
+ 'mobile' => @mobile,
134
+ 'merchant' => @merchant,
135
+ 'callbackUrl' => @callbackUrl,
136
+ 'orderId' => @orderId,
137
+ 'allowedCards' => @allowedCards,
138
+ 'ledgerId' => @ledgerId,
139
+ 'nationalCode' => @nationalCode,
140
+ 'checkMobileWithCard' => @checkMobileWithCard
141
+ }
142
+ end
143
+
144
+ def perform_validation
145
+ # Logic for validation should be implemented here.
146
+ # Update @valid, @status, and @status_message based on @response.
147
+ @valid = @response['result'] == '100'
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,135 @@
1
+ module Parsbank
2
+ class Restfull
3
+ attr_accessor :connection
4
+
5
+ def initialize(args = {})
6
+ @endpoint = args.fetch(:endpoint)
7
+ @action = args.fetch(:action)
8
+ @headers = args.fetch(:headers, {})
9
+ @request_message = args.fetch(:request_message, {})
10
+ @http_method = args.fetch(:http_method)
11
+ @response_type = args.fetch(:response_type, :json)
12
+
13
+ setup_connection
14
+ end
15
+
16
+ def call
17
+ response = send_request
18
+
19
+ Rails.logger.info("Received response with status: #{response.status}, body: #{response.body.inspect}")
20
+
21
+ if response.success?
22
+ response
23
+ else
24
+ log_and_raise_error(response)
25
+ end
26
+ rescue Faraday::ConnectionFailed => e
27
+ handle_error("Connection failed: #{e.message}", e)
28
+ rescue Faraday::TimeoutError => e
29
+ handle_error("Request timed out: #{e.message}", e)
30
+ rescue StandardError => e
31
+ handle_error("An unexpected error occurred: #{e.message}", e)
32
+ end
33
+
34
+ private
35
+
36
+ def webhook(_message)
37
+ webhook_url = Parsbank.configuration.webhook
38
+
39
+ webhook_url.gsub!('MESSAGE', _message) if Parsbank.configuration.webhook_method == :get
40
+ webhook_url.gsub!('TITLE', "Webhook of Connection Error at #{Time.now}") if Parsbank.configuration.webhook_method == :get
41
+
42
+ connection = Faraday.new() do |conn|
43
+ conn.request :json if @response_type == :json # Automatically converts payload to JSON
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
51
+
52
+ case Parsbank.configuration.webhook_method
53
+ when :post
54
+ connection.post('&parsbank') do |req|
55
+ req.headers = headers
56
+ req.body = {}
57
+ end
58
+
59
+ when :get
60
+ connection.get('&parsbank')
61
+ 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
+ end
69
+
70
+ def setup_connection
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
+
85
+ case @http_method
86
+ when :post
87
+ perform_post(headers)
88
+ when :get
89
+ perform_get(headers)
90
+ when :options
91
+ perform_options(headers)
92
+ else
93
+ raise ArgumentError, "HTTP Method Not Allowed: #{@http_method}"
94
+ end
95
+ end
96
+
97
+ def perform_post(headers)
98
+ @connection.post(@action) do |req|
99
+ req.headers = headers
100
+ req.body = @request_message
101
+ end
102
+ end
103
+
104
+ def perform_get(headers)
105
+ @connection.get(@action) do |req|
106
+ req.headers = headers
107
+ req.params = @request_message
108
+ end
109
+ end
110
+
111
+ def perform_options(headers)
112
+ @connection.options(@action) do |req|
113
+ req.headers = headers
114
+ end
115
+ end
116
+
117
+ def default_headers
118
+ {
119
+ 'Content-Type' => 'application/json',
120
+ 'Parsbank-RubyGem' => Parsbank::VERSION
121
+ }
122
+ end
123
+
124
+ def log_and_raise_error(response)
125
+ Rails.logger.error("Request to #{@endpoint}/#{@action} failed with status: #{response.status}, error: #{response.body.inspect}")
126
+ raise "API request failed with status #{response.status}: #{response.body}"
127
+ end
128
+
129
+ def handle_error(message, exception)
130
+ Rails.logger.error(message)
131
+ webhook(message) if Parsbank.configuration.webhook.present?
132
+ raise exception
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Parsbank
4
+ VERSION = '0.0.4'
5
+ end
@@ -0,0 +1,56 @@
1
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1575 350">
2
+ <defs>
3
+ <style>
4
+ .cls-1 {
5
+ fill: none;
6
+ stroke: #fff;
7
+ stroke-miterlimit: 10;
8
+ stroke-width: 20px;
9
+ }
10
+
11
+ .cls-2 {
12
+ fill: #19191a;
13
+ fill-rule: evenodd;
14
+ }
15
+
16
+ .cls-3 {
17
+ fill: url(#linear-gradient);
18
+ }
19
+
20
+ .cls-4 {
21
+ fill: #0a33ff;
22
+ }
23
+
24
+ .cls-5 {
25
+ fill: url(#linear-gradient-2);
26
+ }
27
+ </style>
28
+ <linearGradient id="linear-gradient" x1="1464.6051" y1="719.8056" x2="1370.1251" y2="469.4806" gradientTransform="matrix(1, 0, 0, -1, 0, 770)" gradientUnits="userSpaceOnUse">
29
+ <stop offset="0" stop-color="#ffd60a"/>
30
+ <stop offset="1" stop-color="#f5be09"/>
31
+ </linearGradient>
32
+ <linearGradient id="linear-gradient-2" x1="1411.0288" y1="693.8954" x2="1357.4613" y2="560.3856" gradientTransform="matrix(1, 0, 0, -1, 0, 770)" gradientUnits="userSpaceOnUse">
33
+ <stop offset="0" stop-color="#051a80"/>
34
+ <stop offset="1" stop-color="#0a33ff"/>
35
+ </linearGradient>
36
+ </defs>
37
+ <polygon class="cls-1" points="1975 4650 -7025 4650 -7025 2100 -13975 2100 -13975 -6600 1975 -6600 1975 4650"/>
38
+ <g>
39
+ <g>
40
+ <path class="cls-2" d="M300,0a25,25,0,0,1,25,25V150a25,25,0,0,0,25,25h75a25,25,0,0,0,25-25V125a25,25,0,0,1,50,0v25a74.9994,74.9994,0,0,1-75,75H350a74.9994,74.9994,0,0,1-75-75V25A25,25,0,0,1,300,0Z"/>
41
+ <path class="cls-2" d="M350,275a25,25,0,0,1,25-25h75a25,25,0,0,1,25,25v25a25,25,0,0,1-50,0H375A25,25,0,0,1,350,275Z"/>
42
+ <path class="cls-2" d="M225,0a25,25,0,0,1,25,25V199.6432C250,254.8725,205.218,300,149.99,300S50,254.8725,50,199.6432V150a25,25,0,0,1,50,0v49.6432c0,27.6143,22.3752,50.3561,49.9895,50.3561S200,227.2575,200,199.6432V25A25,25,0,0,1,225,0Z"/>
43
+ <path class="cls-2" d="M1150.0025,75a25.0267,25.0267,0,1,0-17.6725-7.33A24.9938,24.9938,0,0,0,1150.0025,75Z"/>
44
+ <path class="cls-2" d="M625.0025,75A25.0267,25.0267,0,1,0,607.33,67.67,24.9938,24.9938,0,0,0,625.0025,75Z"/>
45
+ <path class="cls-2" d="M1150,100a25,25,0,0,0-25,25v90.625c0,13.1548-3.7,21.8985-7.8925,26.889A21.0264,21.0264,0,0,1,1100,250a25,25,0,0,0,0,50,70.9509,70.9509,0,0,0,55.3925-25.3275C1168.7,258.83,1175,237.8868,1175,215.625V125a25,25,0,0,0-25-25Z"/>
46
+ <path class="cls-2" d="M1075,125a25,25,0,0,0-50,0v90.625c0,13.1548-3.7,21.8985-7.8925,26.889A21.0264,21.0264,0,0,1,1000,250a25,25,0,0,0,0,50,70.9509,70.9509,0,0,0,55.3925-25.3275C1068.7,258.83,1075,237.8868,1075,215.625Z"/>
47
+ <path class="cls-2" d="M925,250H850a25,25,0,0,0,0,50h75a25,25,0,0,0,0-50Z"/>
48
+ <path class="cls-2" d="M975,125a25,25,0,0,0-50,0v27.5c0,12.152-9.4775,22.5-25,22.5H750c-15.5225,0-25-10.348-25-22.5V125a25,25,0,0,0-50,0v75a50,50,0,0,1-100,0V125a25,25,0,0,0-50,0v75a100.0094,100.0094,0,0,0,197.9475,20.2393A79.1518,79.1518,0,0,0,750,225H900c39.705,0,75-29.4235,75-72.5Z"/>
49
+ </g>
50
+ <g>
51
+ <path class="cls-3" d="M1387.89,61.0007A17.6259,17.6259,0,0,1,1404.2551,50h113.1024a17.5138,17.5138,0,0,1,16.365,23.9993L1447.11,289a17.6274,17.6274,0,0,1-16.3651,11H1317.6425a17.5144,17.5144,0,0,1-16.365-24Z"/>
52
+ <path class="cls-4" d="M1340,200a75,75,0,1,0-75-75A74.9994,74.9994,0,0,0,1340,200Z"/>
53
+ <path class="cls-5" d="M1415,125a74.9992,74.9992,0,0,1-75,75,75.9137,75.9137,0,0,1-7.9375-.415L1385.98,65.7435A74.8675,74.8675,0,0,1,1415,125Z"/>
54
+ </g>
55
+ </g>
56
+ </svg>
@@ -0,0 +1,111 @@
1
+ module Parsbank
2
+ class Zarinpal
3
+ attr_accessor :amount, :description, :email, :mobile, :merchant_id
4
+ attr_reader :response, :status, :status_message, :ref_id, :logo
5
+
6
+ def initialize(args = {})
7
+ @mobile = args.fetch(:mobile, nil)
8
+ @email = args.fetch(:email, nil)
9
+ @amount = args.fetch(:amount)
10
+ @description = args.fetch(:description, ' ')
11
+ @callback_url = args.fetch(:callback_url,
12
+ (default_config(:callback_url) || Parsbank.configuration.callback_url))
13
+ @merchant_id = args.fetch(:merchant_id, default_config(:merchant_id))
14
+ @wsdl = create_wsdl_client
15
+ rescue KeyError => e
16
+ raise ArgumentError, "Missing required argument: #{e.message}"
17
+ end
18
+
19
+ def self.logo
20
+ file_path = "#{__dir__}/logo.svg"
21
+ return [404, { 'Content-Type' => 'text/plain' }, ['File not found']] unless File.exist?(file_path)
22
+
23
+ [
24
+ 200,
25
+ { 'Content-Type' => 'image/svg+xml' },
26
+ File.open(file_path, 'r')
27
+ ]
28
+ end
29
+
30
+ def validate(response = nil)
31
+ @response = response[:payment_request_response] || response[:payment_verification_response] || response
32
+ @ref_id = @response[:authority]
33
+ @status = @response[:status].present? ? @response[:status] : 'FAILED'
34
+
35
+ perform_validation
36
+ self
37
+ end
38
+
39
+ def valid?
40
+ @valid
41
+ end
42
+
43
+ def ref_id
44
+ @ref_id.to_s
45
+ end
46
+
47
+ def call
48
+ response = @wsdl.call(:payment_request, message: build_request_message)
49
+ validate(response.body)
50
+ rescue Savon::Error => e
51
+ raise "SOAP request failed: #{e.message}"
52
+ end
53
+
54
+ def redirect_form
55
+ "
56
+ <script type='text/javascript' charset='utf-8'>
57
+ function postRefId (refIdValue) {
58
+ var form = document.createElement('form');
59
+ form.setAttribute('method', 'POST');
60
+ form.setAttribute('action', 'https://www.zarinpal.com/pg/StartPay/#{ref_id}');
61
+ form.setAttribute('target', '_self');
62
+ var hiddenField = document.createElement('input');
63
+ hiddenField.setAttribute('name', 'RefId');
64
+ hiddenField.setAttribute('value', refIdValue);
65
+ form.appendChild(hiddenField);
66
+ document.body.appendChild(form);
67
+ form.submit();
68
+ document.body.removeChild(form);
69
+ }
70
+
71
+
72
+ postRefId('#{ref_id}') %>')
73
+ </script>
74
+ "
75
+ end
76
+
77
+ private
78
+
79
+ def default_config(key)
80
+ Parsbank.load_secrets_yaml[self.class.name.split('::').last.downcase][key.to_s]
81
+ end
82
+
83
+ def create_wsdl_client
84
+ Savon.client(
85
+ wsdl: default_config(:wsdl) || 'https://de.zarinpal.com/pg/services/WebGate/wsdl',
86
+ pretty_print_xml: (Parsbank.configuration.debug ? true : false),
87
+ namespace: 'http://interfaces.core.sw.bps.com/',
88
+ log: (Parsbank.configuration.debug ? true : false),
89
+ logger: Rails.logger,
90
+ log_level: (Parsbank.configuration.debug ? :debug : :fatal)
91
+ )
92
+ end
93
+
94
+ def build_request_message
95
+ {
96
+ 'MerchantID' => @merchant_id,
97
+ 'Mobile' => @mobile,
98
+ 'Email' => @email,
99
+ 'Amount' => @amount,
100
+ 'Description' => @description,
101
+ 'CallbackURL' => @callback_url
102
+ }
103
+ end
104
+
105
+ def perform_validation
106
+ # Logic for validation should be implemented here.
107
+ # Update @valid, @status, and @status_message based on @response.
108
+ @valid = @response[:status] == '100'
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,42 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 470.9 223.6" style="enable-background:new 0 0 470.9 223.6;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{display:none;opacity:0.15;fill-rule:evenodd;clip-rule:evenodd;fill:none;}
7
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#3A3AE4;}
8
+ .st2{fill:#172B4D;}
9
+ </style>
10
+ <g>
11
+ <path class="st0" d="M470.9,0H0v50.1h0v123.8v5.8v44h470.9v-44v-5.8V50.1h0V0z M420.8,173.9H49.9V50.1h370.8V173.9z"/>
12
+ <g>
13
+ <g>
14
+ <g>
15
+ <path class="st1" d="M391.8,76.8l27.7,27.7c1.7,1.7,1.7,4.4,0,6L375,155.1c-1.7,1.7-4.4,1.7-6,0l-27.7-27.7
16
+ c-1.7-1.7-1.7-4.4,0-6l44.5-44.6C387.5,75.1,390.2,75.1,391.8,76.8z"/>
17
+ <path class="st1" d="M332.9,135.9l10.8,10.9c1.7,1.7,1.7,4.4,0,6L324.4,172c-1.7,1.7-4.4,1.7-6,0l-10.8-10.9
18
+ c-1.7-1.7-1.7-4.4,0-6l19.3-19.3C328.5,134.2,331.2,134.2,332.9,135.9z"/>
19
+ <path class="st1" d="M293.1,146.7L265.4,119c-1.7-1.7-1.7-4.4,0-6L310,68.3c1.7-1.7,4.4-1.7,6,0l27.7,27.7c1.7,1.7,1.7,4.4,0,6
20
+ l-44.5,44.6C297.5,148.4,294.8,148.4,293.1,146.7z"/>
21
+ <path class="st1" d="M352.1,87.6l-10.8-10.9c-1.7-1.7-1.7-4.4,0-6l19.3-19.3c1.7-1.7,4.4-1.7,6,0l10.8,10.9c1.7,1.7,1.7,4.4,0,6
22
+ l-19.3,19.3C356.5,89.3,353.8,89.3,352.1,87.6z"/>
23
+ </g>
24
+ </g>
25
+ <g>
26
+ <path class="st2" d="M243.6,66.6h-8c-2.4,0-4.3,1.9-4.3,4.3v8c0,2.4,1.9,4.3,4.3,4.3h8c2.4,0,4.3-1.9,4.3-4.3v-8
27
+ C247.8,68.5,245.9,66.6,243.6,66.6z"/>
28
+ <path class="st2" d="M161.3,157.4h-8c-2.4,0-4.3,1.9-4.3,4.3v8c0,2.4,1.9,4.3,4.3,4.3h8c2.4,0,4.3-1.9,4.3-4.3v-8
29
+ C165.6,159.3,163.7,157.4,161.3,157.4z"/>
30
+ <path class="st2" d="M218.8,157.4h-24.5c-2.4,0-4.3,1.9-4.3,4.3v8c0,2.4,1.9,4.3,4.3,4.3h24.5c2.4,0,4.3-1.9,4.3-4.3v-8
31
+ C223.1,159.3,221.2,157.4,218.8,157.4z"/>
32
+ <path class="st2" d="M243.6,91.3h-8c-2.4,0-4.3,1.9-4.3,4.3v37h-28.7c-2.4,0-4.3,1.9-4.3,4.3v8c0,2.4,1.9,4.3,4.3,4.3h31.6h6.7
33
+ h2.7c2.4,0,4.3-1.9,4.3-4.3V95.6C247.8,93.2,245.9,91.3,243.6,91.3z"/>
34
+ <path class="st2" d="M111.7,50.1h-8c-2.4,0-4.3,1.9-4.3,4.3v78.2h-33v-37c0-2.4-1.9-4.3-4.3-4.3h-8c-2.4,0-4.3,1.9-4.3,4.3v49.2
35
+ c0,2.4,1.9,4.3,4.3,4.3H61h2.5h38.8h3.9h5.4c2.4,0,4.3-1.9,4.3-4.3V54.3C115.9,52,114,50.1,111.7,50.1z"/>
36
+ <path class="st2" d="M218.8,124.2c2.4,0,4.3-1.9,4.3-4.3v-8c0-2.4-1.9-4.3-4.3-4.3h-28.7V95.6c0-2.4-1.9-4.3-4.3-4.3h-8
37
+ c-2.4,0-4.3,1.9-4.3,4.3v37h-33V54.3c0-2.4-1.9-4.3-4.3-4.3h-8c-2.4,0-4.3,1.9-4.3,4.3v81.1v9.4c0,2.4,1.9,4.3,4.3,4.3h9.4h38.8
38
+ h2.5h6.9c2.4,0,4.3-1.9,4.3-4.3v-20.6H218.8z"/>
39
+ </g>
40
+ </g>
41
+ </g>
42
+ </svg>
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ module Parsbank
6
+ class Zibal
7
+ attr_accessor :amount, :description, :email, :mobile, :merchant, :callbackUrl, :orderId, :allowedCards, :ledgerId,
8
+ :nationalCode, :checkMobileWithCard
9
+
10
+ attr_reader :response, :status, :status_message, :ref_id, :logo
11
+
12
+ def initialize(args = {})
13
+ @amount = args.fetch(:amount)
14
+ @description = args.fetch(:description, nil)
15
+ @email = args.fetch(:email, nil)
16
+ @mobile = args.fetch(:mobile, nil)
17
+ @merchant = args.fetch(:merchant, default_config(:merchant))
18
+ @callbackUrl = args.fetch(:callbackUrl, (default_config(:callback_url) || Parsbank.configuration.callback_url))
19
+ @orderId = args.fetch(:orderId, nil)
20
+ @allowedCards = args.fetch(:allowedCards, nil)
21
+ @ledgerId = args.fetch(:ledgerId, nil)
22
+ @nationalCode = args.fetch(:nationalCode, nil)
23
+ @checkMobileWithCard = args.fetch(:checkMobileWithCard, nil)
24
+ rescue KeyError => e
25
+ raise ArgumentError, "Missing required argument: #{e.message}"
26
+ end
27
+
28
+ def self.logo
29
+ file_path = "#{__dir__}/logo.svg"
30
+ return [404, { 'Content-Type' => 'text/plain' }, ['File not found']] unless File.exist?(file_path)
31
+
32
+ [
33
+ 200,
34
+ { 'Content-Type' => 'image/svg+xml' },
35
+ File.open(file_path, 'r')
36
+ ]
37
+ end
38
+
39
+ def validate(response = nil)
40
+ @response = response
41
+ @ref_id = @response['trackId']
42
+ @status = @response['result'].present? ? @response['result'] : 'FAILED'
43
+
44
+ perform_validation
45
+ self
46
+ end
47
+
48
+ def valid?
49
+ @valid
50
+ end
51
+
52
+ def ref_id
53
+ @ref_id.to_s
54
+ end
55
+
56
+ def call
57
+ create_rest_client
58
+ rescue Savon::Error => e
59
+ raise "SOAP request failed: #{e.message}"
60
+ end
61
+
62
+ def redirect_form
63
+ "
64
+ <script type='text/javascript' charset='utf-8'>
65
+ function postRefId (refIdValue) {
66
+ var form = document.createElement('form');
67
+ form.setAttribute('method', 'POST');
68
+ form.setAttribute('action', 'https://gateway.zibal.ir/start/#{ref_id}');
69
+ form.setAttribute('target', '_self');
70
+ var hiddenField = document.createElement('input');
71
+ hiddenField.setAttribute('name', 'RefId');
72
+ hiddenField.setAttribute('value', refIdValue);
73
+ form.appendChild(hiddenField);
74
+ document.body.appendChild(form);
75
+ form.submit();
76
+ document.body.removeChild(form);
77
+ }
78
+
79
+
80
+ postRefId('#{ref_id}') %>')
81
+ </script>
82
+ "
83
+ end
84
+
85
+ private
86
+
87
+ def default_config(key)
88
+ Parsbank.load_secrets_yaml[self.class.name.split('::').last.downcase][key.to_s]
89
+ end
90
+
91
+ def create_rest_client
92
+ connection = Parsbank::Restfull.new(
93
+ endpoint: default_config(:endpoint) || 'https://gateway.zibal.ir',
94
+ action: '/v1/request',
95
+ headers: {
96
+ 'Content-Type' => 'application/json',
97
+ 'Authorization' => "Bearer #{default_config(:access_token)}"
98
+ },
99
+ request_message: build_request_message,
100
+ http_method: :post,
101
+ response_type: :json
102
+ )
103
+
104
+ response = connection.call
105
+
106
+ Rails.logger.info "Received response with status: #{response.status}, body: #{response.body.inspect}"
107
+
108
+ if response.valid?
109
+ validate(response.body)
110
+ else
111
+ @valid = false
112
+ Rails.logger.error "POST request to #{BASE_URL}/#{endpoint} failed with status: #{response.status}, error: #{response.body.inspect}"
113
+ raise "API request failed with status #{response.status}: #{response.body}"
114
+ end
115
+ rescue Faraday::ConnectionFailed => e
116
+ Rails.logger.error "Connection failed: #{e.message}"
117
+ raise "Connection to API failed: #{e.message}"
118
+ rescue Faraday::TimeoutError => e
119
+ Rails.logger.error "Request timed out: #{e.message}"
120
+ raise "API request timed out: #{e.message}"
121
+ rescue StandardError => e
122
+ Rails.logger.error "An error occurred: #{e.message}"
123
+ raise "An unexpected error occurred: #{e.message}"
124
+
125
+ JSON.parse response.body
126
+ end
127
+
128
+ def build_request_message
129
+ {
130
+ 'amount' => @amount,
131
+ 'description' => @description,
132
+ 'email' => @email,
133
+ 'mobile' => @mobile,
134
+ 'merchant' => @merchant,
135
+ 'callbackUrl' => @callbackUrl,
136
+ 'orderId' => @orderId,
137
+ 'allowedCards' => @allowedCards,
138
+ 'ledgerId' => @ledgerId,
139
+ 'nationalCode' => @nationalCode,
140
+ 'checkMobileWithCard' => @checkMobileWithCard
141
+ }
142
+ end
143
+
144
+ def perform_validation
145
+ # Logic for validation should be implemented here.
146
+ # Update @valid, @status, and @status_message based on @response.
147
+ @valid = @response['result'] == '100'
148
+ end
149
+ end
150
+ end
data/lib/parsbank.rb ADDED
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'savon'
5
+ require 'parsbank/version'
6
+ require 'parsbank/restfull'
7
+ require 'parsbank/bsc-bitcoin/bsc-bitcoin'
8
+ require 'parsbank/mellat/mellat'
9
+ require 'parsbank/zarinpal/zarinpal'
10
+ require 'parsbank/zibal/zibal'
11
+ require 'configuration'
12
+
13
+ # Main Module
14
+ module Parsbank
15
+ class Error < StandardError; end
16
+
17
+ $SUPPORTED_PSP = [
18
+ 'asanpardakht': {
19
+ 'name': 'Asan Pardakht CO.',
20
+ 'website': 'http://asanpardakht.ir',
21
+ 'tags': %w[iranian-psp ir rial]
22
+ },
23
+ 'damavand': {
24
+ 'name': 'Electronic Card Damavand CO.',
25
+ 'website': 'http://ecd-co.ir',
26
+ 'tags': %w[iranian-psp ir rial]
27
+ },
28
+ 'mellat': {
29
+ 'name': 'Behpardakht Mellat CO.',
30
+ 'website': 'http://behpardakht.com',
31
+ 'tags': %w[iranian-psp ir rial]
32
+ },
33
+ 'pep': {
34
+ 'name': 'Pasargad CO.',
35
+ 'website': 'http://pep.co.ir',
36
+ 'tags': %w[iranian-psp ir rial]
37
+ },
38
+
39
+ 'sep': {
40
+ 'name': 'Saman Bank CO.',
41
+ 'website': 'http://sep.ir',
42
+ 'tags': %w[iranian-psp ir rial]
43
+ },
44
+ 'pna': {
45
+ 'name': 'Pardakht Novin Arian CO.',
46
+ 'website': 'http://pna.co.ir',
47
+ 'tags': %w[iranian-psp ir rial]
48
+ },
49
+ 'pec': {
50
+ 'name': 'Parsian Bank CO.',
51
+ 'website': 'http://pec.ir',
52
+ 'tags': %w[iranian-psp ir rial]
53
+ },
54
+
55
+ 'sadad': {
56
+ 'name': 'Sadad Bank CO.',
57
+ 'website': 'http://sadadco.‌com',
58
+ 'tags': %w[iranian-psp ir rial]
59
+ },
60
+ 'sayan': {
61
+ 'name': 'Sayan Card CO.',
62
+ 'website': 'http://sayancard.ir',
63
+ 'tags': %w[iranian-psp ir rial]
64
+ },
65
+
66
+ 'fanava': {
67
+ 'name': 'Fan Ava Card CO.',
68
+ 'website': 'http://fanavacard.com',
69
+ 'tags': %w[iranian-psp ir rial]
70
+ },
71
+ 'kiccc': {
72
+ 'name': 'IranKish CO.',
73
+ 'website': 'http://kiccc.com',
74
+ 'tags': %w[iranian-psp ir rial]
75
+ },
76
+
77
+ 'sepehr': {
78
+ 'name': 'Sepehr Bank CO.',
79
+ 'website': 'http://www.sepehrpay.com',
80
+ 'tags': %w[iranian-psp ir rial]
81
+ },
82
+
83
+ 'zarinpal': {
84
+ 'name': 'Zarinpal',
85
+ 'website': 'http://www.sepehrpay.com',
86
+ 'tags': %w[iranian-psp ir rial]
87
+ },
88
+ 'zibal': {
89
+ 'name': 'Zibal',
90
+ 'website': 'http://zibal.ir',
91
+ 'tags': %w[iranian-psp ir rial]
92
+ },
93
+ 'bscbitcoin': {
94
+ 'name': 'Binance Bitcoin',
95
+ 'website': 'https://bitcoin.org',
96
+ 'tags': %w[btc bitcoin binance bsc crypto]
97
+ }
98
+ ]
99
+ class << self
100
+ attr_accessor :configuration
101
+ end
102
+
103
+ def self.configure
104
+ self.configuration ||= Configuration.new
105
+ yield configuration
106
+ end
107
+
108
+ def self.gateways_list
109
+ load_secrets_yaml
110
+ end
111
+
112
+ def self.available_gateways_list
113
+ load_secrets_yaml.select { |_, value| value['enabled'] }
114
+ end
115
+
116
+
117
+ def self.redirect_to_gateway(args = {})
118
+ amount = args.fetch(:amount)
119
+ bank = args.fetch(:bank, 'random-irr-gates')
120
+ description = args.fetch(:description, '')
121
+
122
+ selected_bank = available_gateways_list.select { |k| k == bank }
123
+ raise "Bank not enabled or not exists on bank_secrets.yml: #{bank}" unless selected_bank.present?
124
+
125
+ default_callback = Parsbank.configuration.callback_url + "&bank_name=#{bank}"
126
+
127
+ case bank
128
+ when 'mellat'
129
+ mellat_klass = Parsbank::Mellat.new(
130
+ amount: amount,
131
+ additional_data: description,
132
+ callback_url: selected_bank['mellat']['callback_url'] || default_callback,
133
+ orderId: rand(1...9999)
134
+ )
135
+ mellat_klass.call
136
+ result = mellat_klass.redirect_form
137
+
138
+ when 'zarinpal'
139
+ zarinpal_klass = Parsbank::Zarinpal.new(
140
+ amount: amount,
141
+ additional_data: description,
142
+ callback_url: selected_bank['zarinpal']['callback_url'] || default_callback
143
+ )
144
+ zarinpal_klass.call
145
+ result = zarinpal_klass.redirect_form
146
+
147
+ when 'zibal'
148
+ Parsbank::Zibal.new(
149
+ amount: amount,
150
+ additional_data: description,
151
+ callback_url: selected_bank['zibal']['callback_url'] || default_callback
152
+ )
153
+ zarinpal_klass.call
154
+ result = zarinpal_klass.redirect_form
155
+ when 'bscbitcoin'
156
+ bscbitcoin_klass = Parsbank::BscBitcoin.new(
157
+ additional_data: description
158
+ )
159
+ result = bscbitcoin_klass.generate_payment_address(amount: amount)
160
+ end
161
+
162
+ result
163
+ end
164
+
165
+ def self.load_secrets_yaml
166
+ # Load the YAML file specified by the secrets_path
167
+ secrets = YAML.load_file(Parsbank.configuration.secrets_path)
168
+
169
+ unless secrets.is_a?(Hash)
170
+ raise "Error: Invalid format in #{Parsbank.configuration.secrets_path}. Expected a hash of bank secrets."
171
+ end
172
+
173
+ supported_banks = $SUPPORTED_PSP[0].keys
174
+
175
+ secrets.each_key do |bank_key|
176
+ unless supported_banks.include?(bank_key.to_sym)
177
+ raise "#{bank_key.capitalize} in #{Parsbank.configuration.secrets_path} is not supported by ParsBank. \nSupported Banks: #{supported_banks.join(', ')}"
178
+ end
179
+ end
180
+
181
+ secrets
182
+ rescue Errno::ENOENT
183
+ raise "Error: Secrets file not found at #{Parsbank.configuration.secrets_path}."
184
+ rescue Psych::SyntaxError => e
185
+ raise "Error: YAML syntax issue in #{Parsbank.configuration.secrets_path}: #{e.message}"
186
+ end
187
+
188
+
189
+ def self.gateways_list_shortcode
190
+ banks_list = available_gateways_list.keys.map { |bank| render_bank_list_item(bank) }.join
191
+ "<ul class='parsbank_selector'>#{banks_list}</ul>"
192
+ end
193
+
194
+ private
195
+ def self.render_bank_list_item(bank)
196
+ bank_klass=Object.const_get("Parsbank::#{bank.capitalize}")
197
+ status, headers, body = bank_klass.logo rescue nil
198
+ <<~HTML
199
+ <li class='parsbank_radio_wrapper #{bank}_wrapper'>
200
+
201
+ <input type='radio' id='#{bank}' name='bank' value='#{bank}' />
202
+ <label for='#{bank}'>#{File.read(body) rescue ''} #{bank.upcase}</label>
203
+ </li>
204
+ HTML
205
+ end
206
+
207
+ end
data/sig/parsbank.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Parsbank
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parsbank
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Mohammad Mahmoodi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday_middleware
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: savon
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Focus on your ecommerce we handle your payments.
70
+ email:
71
+ - mm580486@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - CHANGELOG.md
77
+ - README.md
78
+ - Rakefile
79
+ - lib/configuration.rb
80
+ - lib/parsbank.rb
81
+ - lib/parsbank/bsc-bitcoin/bsc-bitcoin.rb
82
+ - lib/parsbank/bsc-bitcoin/bsc-bitcoin.svg
83
+ - lib/parsbank/mellat/mellat.rb
84
+ - lib/parsbank/nobitex/nobitex.rb
85
+ - lib/parsbank/restfull.rb
86
+ - lib/parsbank/version.rb
87
+ - lib/parsbank/zarinpal/logo.svg
88
+ - lib/parsbank/zarinpal/zarinpal.rb
89
+ - lib/parsbank/zibal/logo.svg
90
+ - lib/parsbank/zibal/zibal.rb
91
+ - sig/parsbank.rbs
92
+ homepage: https://github.com/Abrfanet
93
+ licenses:
94
+ - WTFPL
95
+ metadata:
96
+ homepage_uri: https://github.com/Abrfanet
97
+ source_code_uri: https://github.com/Abrfanet/parsbank
98
+ changelog_uri: https://changelog.md/ParsBank
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 3.0.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.4.20
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: An powerfull gem for lunch your smart gateways
118
+ test_files: []