Skip to Content

Rails

QPay V2 payment integration for Ruby on Rails 7+. Built on top of the qpay  gem, this package provides a Rails engine with configuration, a client singleton, webhook controller, view helpers, and ActiveSupport notifications.


Installation

Add to your Gemfile:

gem "qpay-rails"

Then run:

bundle install

Run the install generator:

rails generate qpay:install

This will:

  1. Create config/initializers/qpay.rb with configuration
  2. Add mount QPay::Rails::Engine => "/qpay" to your routes

Configuration

Initializer

The generated config/initializers/qpay.rb:

QPay::Rails.configure do |config| config.base_url = ENV.fetch("QPAY_BASE_URL", "https://merchant.qpay.mn") config.username = ENV.fetch("QPAY_USERNAME") config.password = ENV.fetch("QPAY_PASSWORD") config.invoice_code = ENV.fetch("QPAY_INVOICE_CODE") config.callback_url = ENV.fetch("QPAY_CALLBACK_URL") end

Environment Variables

Set the following in your environment (or .env file with dotenv-rails):

QPAY_BASE_URL=https://merchant.qpay.mn QPAY_USERNAME=your_username QPAY_PASSWORD=your_password QPAY_INVOICE_CODE=your_invoice_code QPAY_CALLBACK_URL=https://yoursite.com/qpay/webhooks

Configuration Options

AttributeDefaultDescription
base_urlhttps://merchant.qpay.mnQPay API base URL
usernamefrom envMerchant username
passwordfrom envMerchant password
invoice_codefrom envDefault invoice code
callback_urlfrom envPayment callback URL

The Configuration class reads from environment variables by default if not explicitly set.


Quick Start

Route Setup

The install generator adds the engine mount to your config/routes.rb:

Rails.application.routes.draw do mount QPay::Rails::Engine => "/qpay" # ... end

This registers POST /qpay/webhooks as the webhook endpoint.

Create an Invoice

client = QPay::Rails.client invoice = client.create_simple_invoice( invoice_code: "YOUR_CODE", sender_invoice_no: "ORDER-001", amount: 10000, callback_url: "https://yoursite.com/qpay/webhooks" ) # invoice.invoice_id -- QPay invoice ID # invoice.qr_image -- Base64 QR code # invoice.qr_text -- QR code text # invoice.qpay_short_url -- Short payment URL # invoice.urls -- Bank deep links

Create Invoice

Simple Invoice

client = QPay::Rails.client invoice = client.create_simple_invoice( invoice_code: "YOUR_CODE", sender_invoice_no: "ORDER-#{order.id}", amount: order.total, callback_url: "https://yoursite.com/qpay/webhooks" )

Full Invoice

invoice = client.create_invoice( invoice_code: "YOUR_CODE", sender_invoice_no: "ORDER-#{order.id}", invoice_receiver_code: "receiver_001", invoice_description: "Payment for Order ##{order.id}", amount: order.total, callback_url: "https://yoursite.com/qpay/webhooks" )

Check Payment

result = client.check_payment( object_type: "INVOICE", object_id: invoice_id ) if result.rows&.any? puts "Payment confirmed!" end

Cancel Invoice

client.cancel_invoice(invoice_id)

Using in Controllers

class PaymentsController < ApplicationController def create client = QPay::Rails.client invoice = client.create_simple_invoice( invoice_code: "YOUR_CODE", sender_invoice_no: "ORDER-#{@order.id}", amount: @order.total, callback_url: "https://yoursite.com/qpay/webhooks" ) @order.update(qpay_invoice_id: invoice.invoice_id) render :show, locals: { invoice: invoice } end end

Webhook Handling

The engine registers a webhook controller at POST /qpay/webhooks (via the engine mount).

Webhook Flow

When QPay sends a callback:

  1. The WebhooksController extracts invoice_id from the request parameters
  2. Calls check_payment to verify the payment
  3. Instruments an ActiveSupport::Notifications event (payment_received.qpay) if payment rows are found
  4. Returns a JSON response with the status

Engine Routes

# config/routes.rb (inside the engine) QPay::Rails::Engine.routes.draw do post "webhooks", to: "webhooks#create" end

The controller inherits from ActionController::API so no CSRF verification is applied.


Events (ActiveSupport Notifications)

payment_received.qpay

Subscribe to payment notifications using ActiveSupport:

ActiveSupport::Notifications.subscribe("payment_received.qpay") do |name, start, finish, id, payload| invoice_id = payload[:invoice_id] result = payload[:result] # Update order status order = Order.find_by(qpay_invoice_id: invoice_id) order&.update(status: :paid) end

Using with ActiveSupport::Subscriber

class PaymentSubscriber < ActiveSupport::Subscriber attach_to :qpay def payment_received(event) invoice_id = event.payload[:invoice_id] result = event.payload[:result] Order.find_by(qpay_invoice_id: invoice_id)&.update(status: :paid) end end

In an Initializer

# config/initializers/qpay_events.rb ActiveSupport::Notifications.subscribe("payment_received.qpay") do |*args| event = ActiveSupport::Notifications::Event.new(*args) Rails.logger.info "QPay payment received: #{event.payload[:invoice_id]}" end

View Helpers

The engine provides view helpers for rendering QPay UI elements. The QPayHelper module is automatically loaded.

QR Code

<%= qpay_qr_code(invoice.qr_image) %> <%# With custom size %> <%= qpay_qr_code(invoice.qr_image, size: 200) %>

Renders a centered <div> containing an <img> tag with the base64-encoded QR code.

<%= qpay_payment_links(invoice.urls) %>

Renders a flex container with styled bank payment link buttons. Each button shows the bank logo and name.

Full View Example

<h1>Payment for Order #<%= @order.id %></h1> <div class="qpay-payment"> <%= qpay_qr_code(@invoice.qr_image) %> <h2>Pay with Bank App</h2> <%= qpay_payment_links(@invoice.urls) %> </div>

Testing

Mocking the Client

# spec/services/payment_service_spec.rb require "rails_helper" RSpec.describe PaymentService do let(:mock_client) { instance_double(QPay::Client) } let(:mock_invoice) { double(invoice_id: "inv_123", qr_image: "base64") } before do allow(QPay::Rails).to receive(:client).and_return(mock_client) allow(mock_client).to receive(:create_simple_invoice).and_return(mock_invoice) end it "creates an invoice" do result = described_class.new.create_payment(order) expect(result.invoice_id).to eq("inv_123") end end

Testing Webhook Notifications

require "rails_helper" RSpec.describe "QPay Webhook" do it "fires payment_received notification" do events = [] ActiveSupport::Notifications.subscribe("payment_received.qpay") do |*args| events << ActiveSupport::Notifications::Event.new(*args) end # Mock client mock_client = instance_double(QPay::Client) mock_result = double(rows: [{ payment_id: "pay_123" }]) allow(QPay::Rails).to receive(:client).and_return(mock_client) allow(mock_client).to receive(:check_payment).and_return(mock_result) post "/qpay/webhooks", params: { invoice_id: "inv_123" } expect(events.length).to eq(1) expect(events.first.payload[:invoice_id]).to eq("inv_123") end end

Resetting the Client

Between tests, reset the singleton client:

after do QPay::Rails::Client.reset! end

API Reference

Module Methods

MethodDescription
QPay::Rails.configure { |config| ... }Configure the integration
QPay::Rails.configurationGet the current configuration
QPay::Rails.clientGet the singleton QPay::Client instance

Configuration Attributes

AttributeTypeDescription
base_urlStringQPay API base URL
usernameStringMerchant username
passwordStringMerchant password
invoice_codeStringDefault invoice code
callback_urlStringPayment callback URL

View Helpers

HelperParametersDescription
qpay_qr_code(qr_image, size: 256)QR image string, optional sizeRenders QR code image
qpay_payment_links(urls)Array of bank link hashesRenders bank payment buttons

ActiveSupport Notifications

Event NamePayload KeysDescription
payment_received.qpay:invoice_id, :resultFired when payment is confirmed

Generator

rails generate qpay:install

Creates the initializer and mounts the engine routes.


Last updated on