مرکز ابزارها، افزونه ها و مثال های اتصال

در این صفحه بسته های آماده برای اتصال به سرویس پرداخت، افزونه های کمکی و مثال های اجرایی را مثل یک ریپازیتوری مرور می کنید: فایل ها را می بینید، README را می خوانید و کل بسته را با یک دانلود می گیرید.

CONTROLLED RELEASE

انتشار کنترل شده از پنل رسمی

هر بسته از مسیر مدیریتی رسمی پی اکسا منتشر می شود و قبل از دانلود، اطلاعات کلیدی آن به صورت شفاف نمایش داده می شود.

PRE-DOWNLOAD VISIBILITY

شفافیت محتوا پیش از دریافت

پیش نمایش README، مرور ساختار فایل و نمایش متادیتای بسته کمک می کند قبل از دانلود، ارزیابی دقیق تری از محتوا داشته باشید.

TRUST PROFILE

نمای رسمی تر برای انتخاب ابزار

صفحه بر پایه شفافیت انتشار، نمایش منبع README، تاریخ بروزرسانی و آمار دانلود چیده شده تا حس ارزیابی حرفه ای تری منتقل کند.

مرجع حقوقی و هویتی کنار دسترس

ساختار صفحه طوری چیده شده که اطلاعات انتشار، محتوای فایل ها و جزئیات هر بسته بدون شلوغی اضافه در دسترس باشند.

مثال repository / wallet-top-up-ruby-sinatra

شارژ كيف پول

اين پروژه يک نمونه آموزشي براي اتصال درگاه Payexa به اپليکيشن Ruby (با Sinatra) است.

دانلود بسته
8 فایل payexa-ruby-sinatra.zip README: README.md

دانلود ثبت شده

48

آخرین بروزرسانی

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 / توضیحات

منبع: README.md

Payexa Wallet Demo - Ruby (Sinatra)

اين پروژه يک نمونه آموزشي براي اتصال درگاه Payexa به اپليکيشن Ruby (با Sinatra) است.

هدف اين مثال اين است که منطق پرداخت را به صورت کامل و قابل فهم نشان دهد، نه اينکه جايگزين يک محصول نهايي production باشد.

اين مثال چه کاربردي دارد؟

اين مثال براي موارد زير مناسب است:

  • آموزش تيم توسعه براي درک جريان پرداخت Payexa
  • ساخت PoC (نمونه اوليه سريع) قبل از پياده سازي در Rails يا سرويس اصلي
  • تست عملي callback و verify در محيط sandbox
  • الگوبرداري از منطق امن افزايش موجودي کيف پول

اگر بخواهي در سايت واقعي پرداخت ريالي پياده کني، اين مثال يک نقطه شروع خوب براي معماري و منطق تراکنش است.

جريان پرداخت در اين مثال

  1. کاربر مبلغ شارژ را ثبت مي کند.
  2. برنامه سفارش شارژ را ايجاد مي کند (pending).
  3. برنامه به request در Payexa درخواست مي دهد.
  4. کاربر به صفحه پرداخت (startPay) هدايت مي شود.
  5. درگاه callback را به اپليکيشن برمي گرداند.
  6. برنامه پرداخت را با endpoint verify دوباره بررسي مي کند.
  7. فقط در صورت verify موفق، موجودي کيف پول افزايش پيدا مي کند.

چرا اين مثال مهم است؟

  • callback به تنهايي براي شارژ کيف پول کافي نيست.
  • وضعيت نهايي بايد با 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 يا production
  • PAYEXA_API_KEY: کليد API مرچنت
  • PAYEXA_STARTPAY_PATH: مسير شروع پرداخت (مثل /startPay)
  • PAYEXA_REQUEST_ENDPOINT: اندپوینت request
  • PAYEXA_VERIFY_ENDPOINT: اندپوینت verify
  • PAYEXA_CALLBACK_PATH: مسير callback اپ
  • PAYEXA_PUBLIC_CALLBACK_URL: callback کامل براي ارسال به درگاه (اختياري)

نکته مهم براي لوکال:

  • Sinatra در اين مثال با WEBrick اجرا مي شود (تک ترد).
  • بعضي درگاه ها هنگام request، callback را همان لحظه چک مي کنند.
  • اگر callback روي همين سرور باشد، ممکن است request قفل شود و ريدایرکت انجام نشود.
  • در اين حالت PAYEXA_PUBLIC_CALLBACK_URL را ست کن (مثلا http://127.0.0.1:5052/payments/callback يا يک URL عمومي).

نحوه تست سريع

  1. صفحه اصلي را باز کن: http://127.0.0.1:5052/
  2. يک مبلغ وارد کن و روي دکمه شارژ بزن.
  3. اگر request موفق باشد، به صفحه پرداخت هدايت مي شوي.
  4. پس از callback، نتيجه verify را در صفحه نتيجه پرداخت مي بيني.
  5. در صفحه اصلي، ليست سفارش ها و وضعيت credited قابل مشاهده است.

محدوديت هاي اين نسخه

  • داده ها در حافظه نگهداري مي شوند (با ري استارت برنامه پاک مي شوند).
  • براي دمو ساخته شده و هنوز logging/monitoring حرفه اي ندارد.
  • براي production بايد DB واقعي، مدل تراکنش اتميک، و مديريت خطاي کامل اضافه شود.

مناسب براي چه کسي؟

  • توسعه دهنده Ruby که مي خواهد Payexa را سريع تست کند.
  • تيم محصول که مي خواهد flow پرداخت را قبل از توسعه نهايي ببيند.
  • پروژه اي که مي خواهد از اين کد به عنوان reference implementation استفاده کند.

نکات امنيتي

  • API Key را هرگز در فرانت اند قرار نده.
  • .env را commit نکن.
  • افزايش موجودي را فقط بعد از verify موفق انجام بده.
  • callback هاي تکراري را با idempotency کنترل کن.
PAYEXA ASSISTANT

من دستیار پی اکسا هستم، شما را در تمام مسیر راهنمایی می‌کنم و اگر به من نیازی ندارید با ۲ بار کلیک خاموش می‌شوم.