Add HCB OAuth and payment account controllers

- OAuth flow for linking HCB accounts
- CRUD for payment accounts (user/org pairs)
- Routes under /back_office/hcb/
- Use HCBV4::Client.from_credentials with proper token persistence
This commit is contained in:
24c02 2025-12-18 14:42:55 -05:00
parent c0071a7593
commit b574d57a80
5 changed files with 160 additions and 12 deletions

View file

@ -0,0 +1,57 @@
class HCB::OauthConnectionsController < ApplicationController
skip_after_action :verify_authorized, only: [:new, :callback, :destroy]
def new
redirect_to hcb_oauth_authorize_url, allow_other_host: true
end
def callback
code = params[:code]
if code.blank?
redirect_to root_path, alert: "HCB authorization failed"
return
end
token = hcb_oauth_client.auth_code.get_token(
code,
redirect_uri: hcb_oauth_callback_url,
)
connection = current_user.hcb_oauth_connection || current_user.build_hcb_oauth_connection
connection.update!(
access_token: token.token,
refresh_token: token.refresh_token,
expires_at: token.expires_at ? Time.at(token.expires_at) : nil,
)
redirect_to hcb_payment_accounts_path, notice: "HCB account linked! Now create a payment account."
end
def destroy
current_user.hcb_oauth_connection&.destroy
redirect_to root_path, notice: "HCB account unlinked"
end
private
def hcb_oauth_client
@hcb_oauth_client ||= OAuth2::Client.new(
Rails.application.credentials.dig(:hcb, :client_id),
Rails.application.credentials.dig(:hcb, :client_secret),
site: hcb_api_base,
authorize_url: "/oauth/authorize",
token_url: "/oauth/token",
)
end
def hcb_api_base
Rails.application.credentials.dig(:hcb, :api_base) || "https://hcb.hackclub.com"
end
def hcb_oauth_authorize_url
hcb_oauth_client.auth_code.authorize_url(
redirect_uri: hcb_oauth_callback_url,
scope: "read write",
)
end
end

View file

@ -0,0 +1,66 @@
class HCB::PaymentAccountsController < ApplicationController
before_action :require_hcb_connection
before_action :set_payment_account, only: [:show, :destroy]
def index
@payment_accounts = current_user.hcb_payment_accounts
@available_organizations = available_organizations
end
def new
@organizations = available_organizations
@payment_account = current_user.hcb_payment_accounts.build
end
def create
org = find_organization(params[:organization_id])
if org.nil?
redirect_to new_hcb_payment_account_path, alert: "Organization not found"
return
end
@payment_account = current_user.hcb_payment_accounts.build(
oauth_connection: current_user.hcb_oauth_connection,
organization_id: org.id,
organization_name: org.name,
)
if @payment_account.save
redirect_to hcb_payment_accounts_path, notice: "Payment account created for #{org.name}"
else
@organizations = available_organizations
render :new, status: :unprocessable_entity
end
end
def show
end
def destroy
@payment_account.destroy
redirect_to hcb_payment_accounts_path, notice: "Payment account removed"
end
private
def require_hcb_connection
unless current_user.hcb_connected?
redirect_to new_hcb_oauth_connection_path, alert: "Please link your HCB account first"
end
end
def set_payment_account
@payment_account = current_user.hcb_payment_accounts.find(params[:id])
end
def available_organizations
current_user.hcb_oauth_connection.organizations
rescue => e
Rails.logger.error "Failed to fetch HCB organizations: #{e.message}"
[]
end
def find_organization(org_id)
available_organizations.find { |o| o.id == org_id }
end
end

View file

@ -7,23 +7,38 @@ class HCB::OauthConnection < ApplicationRecord
validates :user_id, uniqueness: true
def client
@client ||= HCBV4::Client.new(
@client ||= HCBV4::Client.from_credentials(
client_id: Rails.application.credentials.dig(:hcb, :client_id),
client_secret: Rails.application.credentials.dig(:hcb, :client_secret),
access_token: access_token,
refresh_token: refresh_token,
expires_at: expires_at&.to_i,
on_token_refresh: ->(new_access, new_refresh, new_expires) {
update!(
access_token: new_access,
refresh_token: new_refresh,
expires_at: Time.at(new_expires),
)
},
base_url: hcb_api_base,
)
end
def organizations
client.user.organizations
result = client.organizations
persist_refreshed_token!
result
end
def persist_refreshed_token!
token = client.oauth_token
return unless token.respond_to?(:token)
if token.token != access_token || token.refresh_token != refresh_token
update!(
access_token: token.token,
refresh_token: token.refresh_token,
expires_at: token.expires_at ? Time.at(token.expires_at) : nil,
)
end
end
private
def hcb_api_base
Rails.application.credentials.dig(:hcb, :api_base) || "https://hcb.hackclub.com"
end
end

View file

@ -14,10 +14,13 @@ class HCB::PaymentAccount < ApplicationRecord
end
def create_disbursement!(amount_cents:, memo:)
organization.create_transfer(
result = client.create_disbursement(
event_id: organization_id,
to_organization_id: Rails.application.credentials.dig(:hcb, :recipient_org_id),
amount_cents: amount_cents,
recipient_organization_id: Rails.application.credentials.dig(:hcb, :recipient_org_id),
memo: memo,
name: memo,
)
oauth_connection.persist_refreshed_token!
result
end
end

View file

@ -529,6 +529,13 @@ Rails.application.routes.draw do
resources :payment_accounts
resources :mailer_ids
end
namespace :hcb do
resource :oauth_connection, only: [:new, :destroy] do
get :callback, on: :collection
end
resources :payment_accounts, only: [:index, :new, :create, :show, :destroy]
end
resources :source_tags
namespace :warehouse do
resources :templates