شارژ كيف پول
اين پروژه يک نمونه آموزشي براي اتصال درگاه Payexa به اپليکيشن Ruby (با Sinatra) است.
دانلود ثبت شده
49
آخرین بروزرسانی
2026-09-25
حجم بسته
9.0 KB
منتشرکننده
مدیر ارشد سامانه
وضعیت نمایش
عمومی و فعال
در این صفحه بسته های آماده برای اتصال به سرویس پرداخت، افزونه های کمکی و مثال های اجرایی را مثل یک ریپازیتوری مرور می کنید: فایل ها را می بینید، README را می خوانید و کل بسته را با یک دانلود می گیرید.
CONTROLLED RELEASE
انتشار کنترل شده از پنل رسمی
هر بسته از مسیر مدیریتی رسمی پی اکسا منتشر می شود و قبل از دانلود، اطلاعات کلیدی آن به صورت شفاف نمایش داده می شود.
PRE-DOWNLOAD VISIBILITY
شفافیت محتوا پیش از دریافت
پیش نمایش README، مرور ساختار فایل و نمایش متادیتای بسته کمک می کند قبل از دانلود، ارزیابی دقیق تری از محتوا داشته باشید.
TRUST PROFILE
نمای رسمی تر برای انتخاب ابزار
صفحه بر پایه شفافیت انتشار، نمایش منبع README، تاریخ بروزرسانی و آمار دانلود چیده شده تا حس ارزیابی حرفه ای تری منتقل کند.
مرجع حقوقی و هویتی کنار دسترس
ساختار صفحه طوری چیده شده که اطلاعات انتشار، محتوای فایل ها و جزئیات هر بسته بدون شلوغی اضافه در دسترس باشند.
اين پروژه يک نمونه آموزشي براي اتصال درگاه Payexa به اپليکيشن Ruby (با Sinatra) است.
دانلود ثبت شده
49
آخرین بروزرسانی
2026-09-25
حجم بسته
9.0 KB
منتشرکننده
مدیر ارشد سامانه
وضعیت نمایش
عمومی و فعال
پوشه جاری را انتخاب کنید یا برای فایل های متنی پیش نمایش بگیرید.
| نام | نوع | اندازه |
|---|---|---|
| بازگشت به پوشه قبل | پوشه | - |
| views | پوشه | - |
| .env.example | دانلودی | 0.7 KB |
| app.rb | قابل پیش نمایش | 8.5 KB |
| Gemfile | دانلودی | 0.1 KB |
| Gemfile.lock | دانلودی | 0.5 KB |
| README.md | قابل پیش نمایش | 4.6 KB |
ruby-sinatra/app.rb
# frozen_string_literal: true
require "sinatra"
require "dotenv"
require "securerandom"
require "net/http"
require "json"
require "open3"
Dotenv.load(File.join(__dir__, ".env"), File.join(__dir__, ".env.example"))
set :bind, "127.0.0.1"
set :port, (ENV["PORT"] || "5052").to_i
CFG = {
base_url: (ENV["PAYEXA_BASE_URL"] || "https://sandbox.pexn.ir").sub(%r{/$}, ""),
api_key: ENV["PAYEXA_API_KEY"] || "",
startpay_path: ENV["PAYEXA_STARTPAY_PATH"] || "/startPay",
request_endpoint: ENV["PAYEXA_REQUEST_ENDPOINT"] || "/v1/payment/request",
verify_endpoint: ENV["PAYEXA_VERIFY_ENDPOINT"] || "/v1/payment/verify",
callback_path: ENV["PAYEXA_CALLBACK_PATH"] || "/payments/callback",
public_callback_url: ENV["PAYEXA_PUBLIC_CALLBACK_URL"] || "",
timeout_seconds: (ENV["PAYEXA_TIMEOUT_SECONDS"] || "12").to_i,
badge_script_url: ENV["PAYEXA_BADGE_SCRIPT_URL"] || ""
}.freeze
WALLET = { owner_name: "Demo User", balance: 0 }
ORDERS = []
helpers do
def safe_pretty_json(data)
JSON.pretty_generate(normalize_json_value(data))
rescue StandardError => e
JSON.pretty_generate({ "render_error" => "#{e.class}: #{e.message}", "raw" => data.to_s })
end
def normalize_json_value(value)
case value
when Hash
value.each_with_object({}) do |(k, v), h|
h[normalize_json_value(k).to_s] = normalize_json_value(v)
end
when Array
value.map { |x| normalize_json_value(x) }
when String
value.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?")
else
value
end
end
end
def build_startpay_url(authority)
path = CFG[:startpay_path].start_with?("/") ? CFG[:startpay_path] : "/#{CFG[:startpay_path]}"
"#{CFG[:base_url]}#{path}?authority=#{URI.encode_www_form_component(authority)}"
end
def create_order_ref
"TOPUP-#{SecureRandom.hex(6).upcase}"
end
def post_json(url, payload)
post_json_with_curl(url, payload)
rescue StandardError
# Fallback for environments where shell curl is not available.
post_json_with_net_http(url, payload)
end
def post_json_with_net_http(url, payload)
uri = URI(url)
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["X-API-KEY"] = CFG[:api_key]
req.body = payload.to_json
res = Net::HTTP.start(
uri.hostname,
uri.port,
use_ssl: uri.scheme == "https",
open_timeout: CFG[:timeout_seconds],
read_timeout: CFG[:timeout_seconds]
) do |http|
http.request(req)
end
body = begin
JSON.parse(res.body)
rescue StandardError
{ "raw" => res.body.to_s }
end
[res.code.to_i, body]
end
def post_json_with_curl(url, payload)
cmd = [
"curl",
"-sS",
"-X", "POST",
url,
"-H", "Content-Type: application/json",
"-H", "X-API-KEY: #{CFG[:api_key]}",
"--max-time", CFG[:timeout_seconds].to_s,
"--data", payload.to_json,
"-w", "\n%{http_code}"
]
stdout, stderr, status = Open3.capture3(*cmd)
raise "curl failed: #{stderr}" unless status.success?
lines = stdout.lines
status_line = lines.last.to_s.strip
response_body = lines[0...-1].join
status_code = status_line.to_i
parsed_body = begin
JSON.parse(response_body)
rescue StandardError
{ "raw" => response_body.to_s }
end
[status_code, parsed_body]
end
get "/" do
@wallet = WALLET
@orders = ORDERS.reverse.first(15)
@badge_script_url = CFG[:badge_script_url]
@flash = params["msg"].to_s
erb :index
end
post "/wallet/charge" do
amount = params["amount"].to_i
redirect "/?msg=#{URI.encode_www_form_component("مبلغ نامعتبر است.")}" if amount <= 0
order = {
order_ref: create_order_ref,
amount: amount,
status: "pending",
credited: false,
authority: "",
gateway_order_id: "",
gateway_token: "",
callback_payload: nil,
verify_response: nil
}
ORDERS << order
callback_url = if CFG[:public_callback_url].strip.empty?
"#{request.base_url}#{CFG[:callback_path]}"
else
CFG[:public_callback_url].strip
end
status_code, data = post_json("#{CFG[:base_url]}#{CFG[:request_endpoint]}", {
amount: amount,
callback_url: callback_url,
meta: { order_ref: order[:order_ref] }
})
unless [200, 201].include?(status_code)
order[:status] = "request_failed"
order[:verify_response] = data
redirect "/?msg=#{URI.encode_www_form_component("خطا در ايجاد پرداخت (#{status_code})")}"
end
order[:authority] = data["authority"].to_s.strip
order[:gateway_order_id] = data["order_id"].to_s.strip
order[:gateway_token] = data["amount_unique"].to_s if data.key?("amount_unique")
order[:status] = "requested"
if order[:authority].empty?
order[:status] = "request_invalid_response"
redirect "/?msg=#{URI.encode_www_form_component("پاسخ درگاه authority نداشت.")}"
end
redirect build_startpay_url(order[:authority])
rescue StandardError => e
order[:status] = "request_failed" if order
msg = "خطا در ارتباط با درگاه (#{e.class}: #{e.message})"
redirect "/?msg=#{URI.encode_www_form_component(msg)}"
end
["get", "post"].each do |m|
send(m, CFG[:callback_path]) do
payload = request.POST.empty? ? params : request.POST
callback_status = payload["status"].to_s.upcase.strip
authority = payload["authority"].to_s.strip
order_id = payload["order_id"].to_s.strip
token = payload["token"].to_s.strip
@wallet = WALLET
success_statuses = %w[OK SUCCESS]
if !callback_status.empty? && !success_statuses.include?(callback_status)
@title = "پرداخت ناموفق يا لغو شده"
@message = "وضعيت بازگشتي درگاه: #{callback_status}"
@payload = payload
@ok = false
return erb :payment_result
end
order = nil
order = ORDERS.find { |x| x[:gateway_order_id] == order_id } unless order_id.empty?
order ||= ORDERS.find { |x| x[:authority] == authority } unless authority.empty?
unless order
@title = "سفارش پيدا نشد"
@message = "سفارش مرتبط با callback يافت نشد."
@payload = payload
@ok = false
return erb :payment_result
end
order[:callback_payload] = payload
order[:gateway_token] = token unless token.empty?
if order[:credited]
@title = "پرداخت قبلا ثبت شده"
@message = "اين سفارش قبلا وريفاي و به کيف پول اضافه شده است."
@payload = payload
@ok = true
return erb :payment_result
end
final_order_id = order_id.empty? ? order[:gateway_order_id] : order_id
final_token = token.empty? ? order[:gateway_token] : token
if final_order_id.to_s.empty? || final_token.to_s.empty?
order[:status] = "callback_invalid"
@title = "callback ناقص"
@message = "order_id يا token موجود نيست."
@payload = payload
@ok = false
return erb :payment_result
end
status_code, data = post_json("#{CFG[:base_url]}#{CFG[:verify_endpoint]}", {
order_id: final_order_id,
token: final_token
})
order[:verify_response] = data
if status_code == 200 && !order[:credited]
order[:status] = "verified"
order[:credited] = true
WALLET[:balance] += order[:amount]
@title = "شارژ موفق کيف پول"
@message = "مبلغ #{order[:amount]} با موفقيت اضافه شد."
@payload = data
@ok = true
return erb :payment_result
end
if status_code == 201
order[:status] = "already_verified"
@title = "قبلا وريفاي شده"
@message = "اين پرداخت قبلا وريفاي شده است."
@payload = data
@ok = true
return erb :payment_result
end
order[:status] = "verify_failed"
@title = "وريفاي ناموفق"
@message = "در وريفاي خطا رخ داد. status=#{status_code}"
@payload = data
@ok = false
erb :payment_result
rescue StandardError
@title = "وريفاي ناموفق"
@message = "خطا در ارتباط با درگاه هنگام وريفاي پرداخت"
@payload = payload || {}
@ok = false
erb :payment_result
end
end
get "/orders/:order_ref" do
@order = ORDERS.find { |x| x[:order_ref] == params["order_ref"] }
halt 404, "Order not found" unless @order
@wallet = WALLET
erb :order_status
end
منبع: README.md
اين پروژه يک نمونه آموزشي براي اتصال درگاه Payexa به اپليکيشن Ruby (با Sinatra) است.
هدف اين مثال اين است که منطق پرداخت را به صورت کامل و قابل فهم نشان دهد، نه اينکه جايگزين يک محصول نهايي production باشد.
اين مثال براي موارد زير مناسب است:
اگر بخواهي در سايت واقعي پرداخت ريالي پياده کني، اين مثال يک نقطه شروع خوب براي معماري و منطق تراکنش است.
pending).request در Payexa درخواست مي دهد.startPay) هدايت مي شود.verify دوباره بررسي مي کند.credited استفاده شده است (idempotency).cd examples/ruby-sinatra
cp .env.example .env
bundle install
bundle exec ruby app.rb
آدرس پيش فرض:
http://127.0.0.1:5052/
فايل نمونه تنظيمات: .env.example
مهم ترين متغيرها:
PAYEXA_BASE_URL: آدرس sandbox يا productionPAYEXA_API_KEY: کليد API مرچنتPAYEXA_STARTPAY_PATH: مسير شروع پرداخت (مثل /startPay)PAYEXA_REQUEST_ENDPOINT: اندپوینت requestPAYEXA_VERIFY_ENDPOINT: اندپوینت verifyPAYEXA_CALLBACK_PATH: مسير callback اپPAYEXA_PUBLIC_CALLBACK_URL: callback کامل براي ارسال به درگاه (اختياري)نکته مهم براي لوکال:
request، callback را همان لحظه چک مي کنند.PAYEXA_PUBLIC_CALLBACK_URL را ست کن (مثلا http://127.0.0.1:5052/payments/callback يا يک URL عمومي).http://127.0.0.1:5052/credited قابل مشاهده است..env را commit نکن.من دستیار پی اکسا هستم، شما را در تمام مسیر راهنمایی میکنم و اگر به من نیازی ندارید با ۲ بار کلیک خاموش میشوم.