mirror of
https://github.com/System-End/theseus.git
synced 2026-04-19 16:38:18 +00:00
Add HCB payment integration for indicia purchases
- USPS::IndiciumPolicy to check can_use_indicia? - HCB::IndiciumPurchaseService for disbursement + purchase flow - Add hcb_payment_account to letter_queues and usps_indicia - Wire HCB payment into LettersController#buy_indicia - Wire HCB payment into Letter::InstantQueue
This commit is contained in:
parent
b574d57a80
commit
11c5f53074
12 changed files with 214 additions and 39 deletions
46
.idea/workspace.xml
generated
46
.idea/workspace.xml
generated
|
|
@ -5,10 +5,12 @@
|
|||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="d1cc2402-3c65-4046-bb1c-ec3e5b4aef52" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/Gemfile" beforeDir="false" afterPath="$PROJECT_DIR$/Gemfile" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Gemfile.lock" beforeDir="false" afterPath="$PROJECT_DIR$/Gemfile.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/models/public/user.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/models/public/user.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/models/user.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/models/user.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/controllers/letters_controller.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/controllers/letters_controller.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/models/hcb/oauth_connection.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/models/hcb/oauth_connection.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/models/hcb/payment_account.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/models/hcb/payment_account.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/models/letter/instant_queue.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/models/letter/instant_queue.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/models/letter/queue.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/models/letter/queue.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/app/models/usps/indicium.rb" beforeDir="false" afterPath="$PROJECT_DIR$/app/models/usps/indicium.rb" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/db/schema.rb" beforeDir="false" afterPath="$PROJECT_DIR$/db/schema.rb" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
|
|
@ -40,24 +42,24 @@
|
|||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"com.intellij.lang.ruby.rbs.tools.collection.workspace.sync.RbsCollectionUpdateProjectActivity#LAST_UPDATE_TIMESTAMP": "1766085193730",
|
||||
"git-widget-placeholder": "main",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "yarn",
|
||||
"ruby.structure.view.model.defaults.configured": "true",
|
||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"com.intellij.lang.ruby.rbs.tools.collection.workspace.sync.RbsCollectionUpdateProjectActivity#LAST_UPDATE_TIMESTAMP": "1766085193730",
|
||||
"git-widget-placeholder": "main",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "yarn",
|
||||
"ruby.structure.view.model.defaults.configured": "true",
|
||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
}</component>
|
||||
<component name="RubyModuleManagerSettings">
|
||||
<option name="blackListedRootsPaths">
|
||||
<list>
|
||||
|
|
@ -161,7 +163,7 @@
|
|||
<workItem from="1765302419304" duration="4201000" />
|
||||
<workItem from="1765488873945" duration="19000" />
|
||||
<workItem from="1765488900213" duration="5988000" />
|
||||
<workItem from="1766085135057" duration="1297000" />
|
||||
<workItem from="1766085135057" duration="1617000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
|
|
|
|||
|
|
@ -177,18 +177,32 @@ class LettersController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
payment_account = USPS::PaymentAccount.find_by(id: params[:usps_payment_account_id])
|
||||
if payment_account.nil?
|
||||
redirect_to @letter, alert: "Please select a valid payment account."
|
||||
usps_payment_account = USPS::PaymentAccount.find_by(id: params[:usps_payment_account_id])
|
||||
if usps_payment_account.nil?
|
||||
redirect_to @letter, alert: "Please select a valid USPS payment account."
|
||||
return
|
||||
end
|
||||
|
||||
indicium = USPS::Indicium.new(letter: @letter, payment_account: payment_account)
|
||||
begin
|
||||
indicium.buy!
|
||||
redirect_to @letter, notice: "Indicia purchased successfully."
|
||||
rescue => e
|
||||
redirect_to @letter, alert: "Failed to purchase indicia: #{e.message}"
|
||||
indicium = USPS::Indicium.new(letter: @letter, payment_account: usps_payment_account)
|
||||
|
||||
hcb_payment_account = current_user.hcb_payment_accounts.find_by(id: params[:hcb_payment_account_id])
|
||||
|
||||
if hcb_payment_account.present?
|
||||
service = HCB::IndiciumPurchaseService.new(indicium: indicium, hcb_payment_account: hcb_payment_account)
|
||||
if service.call
|
||||
redirect_to @letter, notice: "Indicia purchased successfully (charged to #{hcb_payment_account.organization_name})."
|
||||
else
|
||||
redirect_to @letter, alert: service.errors.join(", ")
|
||||
end
|
||||
elsif current_user.can_use_indicia?
|
||||
begin
|
||||
indicium.buy!
|
||||
redirect_to @letter, notice: "Indicia purchased successfully."
|
||||
rescue => e
|
||||
redirect_to @letter, alert: "Failed to purchase indicia: #{e.message}"
|
||||
end
|
||||
else
|
||||
redirect_to @letter, alert: "You must select an HCB payment account to purchase indicia."
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,23 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: hcb_oauth_connections
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# access_token_ciphertext :text
|
||||
# expires_at :datetime
|
||||
# refresh_token_ciphertext :text
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# user_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_hcb_oauth_connections_on_user_id (user_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (user_id => users.id)
|
||||
#
|
||||
class HCB::OauthConnection < ApplicationRecord
|
||||
belongs_to :user
|
||||
has_many :payment_accounts, dependent: :destroy
|
||||
|
|
|
|||
|
|
@ -1,3 +1,25 @@
|
|||
# == Schema Information
|
||||
#
|
||||
# Table name: hcb_payment_accounts
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# organization_name :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# hcb_oauth_connection_id :bigint not null
|
||||
# organization_id :string
|
||||
# user_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_hcb_payment_accounts_on_hcb_oauth_connection_id (hcb_oauth_connection_id)
|
||||
# index_hcb_payment_accounts_on_user_id (user_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (hcb_oauth_connection_id => hcb_oauth_connections.id)
|
||||
# fk_rails_... (user_id => users.id)
|
||||
#
|
||||
class HCB::PaymentAccount < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :oauth_connection, class_name: "HCB::OauthConnection", foreign_key: :hcb_oauth_connection_id
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
# user_facing_title :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# hcb_payment_account_id :bigint
|
||||
# letter_mailer_id_id :bigint
|
||||
# letter_return_address_id :bigint
|
||||
# user_id :bigint not null
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_letter_queues_on_hcb_payment_account_id (hcb_payment_account_id)
|
||||
# index_letter_queues_on_letter_mailer_id_id (letter_mailer_id_id)
|
||||
# index_letter_queues_on_letter_return_address_id (letter_return_address_id)
|
||||
# index_letter_queues_on_type (type)
|
||||
|
|
@ -33,6 +35,7 @@
|
|||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (hcb_payment_account_id => hcb_payment_accounts.id)
|
||||
# fk_rails_... (letter_mailer_id_id => usps_mailer_ids.id)
|
||||
# fk_rails_... (letter_return_address_id => return_addresses.id)
|
||||
# fk_rails_... (user_id => users.id)
|
||||
|
|
@ -83,22 +86,27 @@ class Letter::InstantQueue < Letter::Queue
|
|||
if indicia?
|
||||
Rails.logger.info("Creating indicia for letter #{letter.id}")
|
||||
begin
|
||||
payment_account = USPS::PaymentAccount.find(usps_payment_account_id)
|
||||
Rails.logger.info("Found payment account #{payment_account.id}")
|
||||
usps_payment_account = USPS::PaymentAccount.find(usps_payment_account_id)
|
||||
Rails.logger.info("Found USPS payment account #{usps_payment_account.id}")
|
||||
|
||||
# Create and save the indicium first
|
||||
indicium = USPS::Indicium.create!(
|
||||
letter: letter,
|
||||
payment_account: payment_account,
|
||||
payment_account: usps_payment_account,
|
||||
mailing_date: letter.mailing_date,
|
||||
)
|
||||
Rails.logger.info("Created indicium #{indicium.id} for letter #{letter.id}")
|
||||
|
||||
# Then buy the indicium
|
||||
indicium.buy!
|
||||
if hcb_payment_account.present?
|
||||
Rails.logger.info("Using HCB payment account #{hcb_payment_account.id} for letter #{letter.id}")
|
||||
service = HCB::IndiciumPurchaseService.new(indicium: indicium, hcb_payment_account: hcb_payment_account)
|
||||
unless service.call
|
||||
raise "HCB payment failed: #{service.errors.join(', ')}"
|
||||
end
|
||||
else
|
||||
indicium.buy!
|
||||
end
|
||||
Rails.logger.info("Successfully bought indicium for letter #{letter.id}")
|
||||
|
||||
# Reload the letter to ensure we have the latest indicium association
|
||||
letter.reload
|
||||
if letter.usps_indicium.present?
|
||||
Rails.logger.info("Verified indicium #{letter.usps_indicium.id} is associated with letter #{letter.id}")
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
# user_facing_title :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# hcb_payment_account_id :bigint
|
||||
# letter_mailer_id_id :bigint
|
||||
# letter_return_address_id :bigint
|
||||
# user_id :bigint not null
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_letter_queues_on_hcb_payment_account_id (hcb_payment_account_id)
|
||||
# index_letter_queues_on_letter_mailer_id_id (letter_mailer_id_id)
|
||||
# index_letter_queues_on_letter_return_address_id (letter_return_address_id)
|
||||
# index_letter_queues_on_type (type)
|
||||
|
|
@ -33,6 +35,7 @@
|
|||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (hcb_payment_account_id => hcb_payment_accounts.id)
|
||||
# fk_rails_... (letter_mailer_id_id => usps_mailer_ids.id)
|
||||
# fk_rails_... (letter_return_address_id => return_addresses.id)
|
||||
# fk_rails_... (user_id => users.id)
|
||||
|
|
|
|||
|
|
@ -14,16 +14,20 @@
|
|||
# usps_sku :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# hcb_payment_account_id :bigint
|
||||
# hcb_transfer_id :string
|
||||
# letter_id :bigint
|
||||
# usps_payment_account_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_usps_indicia_on_hcb_payment_account_id (hcb_payment_account_id)
|
||||
# index_usps_indicia_on_letter_id (letter_id)
|
||||
# index_usps_indicia_on_usps_payment_account_id (usps_payment_account_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (hcb_payment_account_id => hcb_payment_accounts.id)
|
||||
# fk_rails_... (letter_id => letters.id)
|
||||
# fk_rails_... (usps_payment_account_id => usps_payment_accounts.id)
|
||||
#
|
||||
|
|
|
|||
23
app/policies/usps/indicium_policy.rb
Normal file
23
app/policies/usps/indicium_policy.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
class USPS::IndiciumPolicy < ApplicationPolicy
|
||||
def create?
|
||||
user&.can_use_indicia?
|
||||
end
|
||||
|
||||
def index?
|
||||
user_is_admin
|
||||
end
|
||||
|
||||
def show?
|
||||
user_is_admin
|
||||
end
|
||||
|
||||
class Scope < ApplicationPolicy::Scope
|
||||
def resolve
|
||||
if user&.admin?
|
||||
scope.all
|
||||
else
|
||||
scope.none
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
61
app/services/hcb/indicium_purchase_service.rb
Normal file
61
app/services/hcb/indicium_purchase_service.rb
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
class HCB::IndiciumPurchaseService
|
||||
class DisbursementError < StandardError; end
|
||||
|
||||
attr_reader :indicium, :hcb_payment_account, :errors
|
||||
|
||||
def initialize(indicium:, hcb_payment_account:)
|
||||
@indicium = indicium
|
||||
@hcb_payment_account = hcb_payment_account
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def call
|
||||
return failure("No HCB payment account provided") unless hcb_payment_account
|
||||
return failure("No letter attached to indicium") unless indicium.letter
|
||||
|
||||
estimated_cost_cents = estimate_cost_cents
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
transfer = create_disbursement!(estimated_cost_cents)
|
||||
indicium.hcb_payment_account = hcb_payment_account
|
||||
indicium.hcb_transfer_id = transfer.id
|
||||
indicium.save!
|
||||
|
||||
indicium.buy!
|
||||
end
|
||||
|
||||
true
|
||||
rescue HCBV4::APIError => e
|
||||
failure("HCB disbursement failed: #{e.message}")
|
||||
rescue => e
|
||||
failure("Purchase failed: #{e.message}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_disbursement!(amount_cents)
|
||||
hcb_payment_account.create_disbursement!(
|
||||
amount_cents: amount_cents,
|
||||
memo: disbursement_memo,
|
||||
)
|
||||
end
|
||||
|
||||
def estimate_cost_cents
|
||||
letter = indicium.letter
|
||||
base_cents = if letter.processing_category == "flat"
|
||||
150
|
||||
else
|
||||
73
|
||||
end
|
||||
(base_cents * 1.0).ceil
|
||||
end
|
||||
|
||||
def disbursement_memo
|
||||
"Theseus postage: #{indicium.letter.public_id}"
|
||||
end
|
||||
|
||||
def failure(message)
|
||||
@errors << message
|
||||
false
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
class AddHCBPaymentAccountToUSPSIndicia < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_reference :usps_indicia, :hcb_payment_account, null: true, foreign_key: true
|
||||
add_column :usps_indicia, :hcb_transfer_id, :string
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class AddHCBPaymentAccountToLetterQueues < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_reference :letter_queues, :hcb_payment_account, null: true, foreign_key: true
|
||||
end
|
||||
end
|
||||
9
db/schema.rb
generated
9
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_12_18_193953) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_12_18_194459) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "citext"
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
|
|
@ -310,6 +310,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_18_193953) do
|
|||
t.bigint "usps_payment_account_id"
|
||||
t.boolean "include_qr_code", default: true
|
||||
t.date "letter_mailing_date"
|
||||
t.bigint "hcb_payment_account_id"
|
||||
t.index ["hcb_payment_account_id"], name: "index_letter_queues_on_hcb_payment_account_id"
|
||||
t.index ["letter_mailer_id_id"], name: "index_letter_queues_on_letter_mailer_id_id"
|
||||
t.index ["letter_return_address_id"], name: "index_letter_queues_on_letter_return_address_id"
|
||||
t.index ["type"], name: "index_letter_queues_on_type"
|
||||
|
|
@ -455,6 +457,9 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_18_193953) do
|
|||
t.jsonb "raw_json_response"
|
||||
t.boolean "flirted"
|
||||
t.decimal "fees"
|
||||
t.bigint "hcb_payment_account_id"
|
||||
t.string "hcb_transfer_id"
|
||||
t.index ["hcb_payment_account_id"], name: "index_usps_indicia_on_hcb_payment_account_id"
|
||||
t.index ["letter_id"], name: "index_usps_indicia_on_letter_id"
|
||||
t.index ["usps_payment_account_id"], name: "index_usps_indicia_on_usps_payment_account_id"
|
||||
end
|
||||
|
|
@ -602,6 +607,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_18_193953) do
|
|||
add_foreign_key "hcb_oauth_connections", "users"
|
||||
add_foreign_key "hcb_payment_accounts", "hcb_oauth_connections"
|
||||
add_foreign_key "hcb_payment_accounts", "users"
|
||||
add_foreign_key "letter_queues", "hcb_payment_accounts"
|
||||
add_foreign_key "letter_queues", "return_addresses", column: "letter_return_address_id"
|
||||
add_foreign_key "letter_queues", "users"
|
||||
add_foreign_key "letter_queues", "usps_mailer_ids", column: "letter_mailer_id_id"
|
||||
|
|
@ -618,6 +624,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_12_18_193953) do
|
|||
add_foreign_key "return_addresses", "users"
|
||||
add_foreign_key "users", "return_addresses", column: "home_return_address_id"
|
||||
add_foreign_key "users", "usps_mailer_ids", column: "home_mid_id"
|
||||
add_foreign_key "usps_indicia", "hcb_payment_accounts"
|
||||
add_foreign_key "usps_indicia", "letters"
|
||||
add_foreign_key "usps_indicia", "usps_payment_accounts"
|
||||
add_foreign_key "usps_iv_mtr_events", "letters", on_delete: :nullify
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue