Coding

Implementing a Custom Payment Gateway in Rails for Subscriptions

This tutorial will guide you through setting up a custom payment gateway in Rails to handle subscriptions. Instead of using popular options like Stripe or PayPal, we’ll take a direct approach to integrate with a gateway that doesn’t require a dedicated gem. After searching for tutorials on setting up a Rails payment system, I found that most focus on Stripe and require a gem. This tutorial offers a gem-free, straightforward approach to help you understand the fundamentals ideal if you need to work with a custom or less common payment provider. 

This setup ensures secure and reliable payment handling with custom gateway integration and webhook support. By following these steps, you create a foundation that safeguards transactions and provides a seamless user experience. Please notice that I did not bother to run command to generate a model this is just to give a general idea thanks.

1. Setting Up Webhook Endpoints
  1. Define the Webhook Controller
    Create a controller to handle webhook events sent by the payment gateway.

# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token
  before_action :authenticate_webhook

  def payment_status
    event = JSON.parse(request.body.read)
    process_event(event)
    head :ok
  end

  private

  def authenticate_webhook
    provided_signature = request.headers['Webhook-Signature']
    payload = request.raw_post
    secret = ENV['WEBHOOK_SECRET'] 

    expected_signature = OpenSSL::HMAC.hexdigest("SHA256", secret, payload)
    head :unauthorized unless ActiveSupport::SecurityUtils.secure_compare(provided_signature, expected_signature)
  end

  def process_event(event)
    case event["type"]
    when "payment.success"
      handle_success(event)
    when "payment.failed"
      handle_failure(event)
    end
  end

  def handle_success(event)
    user = User.find_by(id: event["data"]["user_id"])
    if user
      user.subscription.update(status: 'active')
      # Record payment details as needed
    end
  end

  def handle_failure(event)
    user = User.find_by(id: event["data"]["user_id"])
    if user
      user.subscription.update(status: 'failed')
      # Additional failure handling logic
    end
  end
end

  1. Configuring Routes
    Add a route for the webhook.

  1. # config/routes.rb
    post 'webhooks/payment_status', to: 'webhooks#payment_status'

  1. Environment Variables for Webhook Security
    Add a WEBHOOK_SECRET in your .env file to secure the webhook endpoint.

WEBHOOK_SECRET="your_webhook_secret"

2. Securing Communications
  1. Enforce SSL
    Ensure your Rails application enforces SSL in production.

# config/environments/production.rb
config.force_ssl = true

  1. Use HMAC for Webhook Authentication
    In the controller above, we used HMAC for authenticating the webhook request. This method ensures that requests only come from a trusted source.

3. Validating Payment Data
  1. Verify Payment Details in the Service Class
    Add checks to validate data integrity when processing payments.

# app/services/payment_gateway_service.rb
def validate_payment(response)
  # Ensure response contains expected fields
  return false unless response["status"] == "success" && response["amount"] == @amount
  true
end

  1. Add Payment Model to Log Transactions
    Store a log of each transaction for later reference or troubleshooting.

# app/models/payment.rb
class Payment < ApplicationRecord
  belongs_to :user
  validates :status, presence: true
  validates :amount, numericality: { greater_than: 0 }
end

4. Confirm Payment Completion
  1. Mark Payment as Complete Only on Webhook
    Use the webhook as the final confirmation of payment status, rather than immediately marking a payment as successful upon user request.

# app/controllers/subscriptions_controller.rb
class SubscriptionsController < ApplicationController
  def create
    plan = SubscriptionPlan.find(params[:plan_id])
    service = PaymentGatewayService.new(current_user, plan.price)

    result = service.create_payment
    if result["status"] == "pending"
      # Only mark subscription active when webhook confirms payment
      Subscription.create!(user: current_user, subscription_plan: plan, status: 'pending')
      redirect_to dashboard_path, notice: "Payment initiated. Awaiting confirmation."
    else
      redirect_to new_subscription_path, alert: "Payment failed: #{result['error_message']}"
    end
  end
end

5. Additional Security Steps
  1. Add Rate Limiting for Webhook Endpoint
    Use Rack Attack or a similar gem to limit requests to your webhook endpoint, preventing denial-of-service (DoS) attacks.

# config/initializers/rack_attack.rb
Rack::Attack.throttle("webhooks/ip", limit: 5, period: 60) do |req|
  req.ip if req.path == "/webhooks/payment_status"
end

  1. Encrypt Sensitive Data
    Encrypt sensitive data fields in your database (e.g., Payment model fields) using Rails’s encryption features.

# app/models/payment.rb
class Payment < ApplicationRecord
  encrypts :card_number, :cvv, :expiry_date, deterministic: true
end

6. Testing the Webhook
  1. Use ngrok for Local Testing
    Use ngrok to expose your local server and receive webhook events for testing purposes.

ngrok http 3000

  1. Mock Webhook Requests
    Use a tool like Postman to simulate webhook requests, verifying your application handles them correctly.

No comment
Leave a Reply

Your email adress will not be published ,Requied fileds are marked*.

Hi, I'm Kenroy Reid

Hello there

More Form Aurthor
Categories

Get The Best Blog Stories into Your inbox!

Sign up for free and be the first to get notified about new posts.