parsbank 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/README.md +111 -0
- data/Rakefile +4 -0
- data/lib/configuration.rb +34 -0
- data/lib/parsbank/bsc-bitcoin/bsc-bitcoin.rb +95 -0
- data/lib/parsbank/bsc-bitcoin/bsc-bitcoin.svg +1 -0
- data/lib/parsbank/mellat/mellat.rb +100 -0
- data/lib/parsbank/nobitex/nobitex.rb +150 -0
- data/lib/parsbank/restfull.rb +135 -0
- data/lib/parsbank/version.rb +5 -0
- data/lib/parsbank/zarinpal/logo.svg +56 -0
- data/lib/parsbank/zarinpal/zarinpal.rb +111 -0
- data/lib/parsbank/zibal/logo.svg +42 -0
- data/lib/parsbank/zibal/zibal.rb +150 -0
- data/lib/parsbank.rb +207 -0
- data/sig/parsbank.rbs +4 -0
- metadata +118 -0
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
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,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,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
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: []
|