Adds on-chain Bitcoin payment support
Adds support for on-chain Bitcoin payments by: - Introducing a new `Assets:Bitcoin:OnChain` account. - Updating the `SettleReceivable` and `PayUser` models to include `txid` for storing transaction IDs. - Modifying the API endpoints to handle `btc_onchain` as a valid payment method and associate it with the new account. This allows tracking on-chain Bitcoin transactions separately from Lightning Network payments.
This commit is contained in:
parent
8b16ead5b1
commit
e2472d13a2
3 changed files with 75 additions and 39 deletions
|
|
@ -332,3 +332,34 @@ async def m008_rename_lightning_account(db):
|
|||
WHERE name = 'Assets:Lightning:Balance'
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def m009_add_onchain_bitcoin_account(db):
|
||||
"""
|
||||
Add Assets:Bitcoin:OnChain account for on-chain Bitcoin transactions.
|
||||
This allows tracking on-chain Bitcoin separately from Lightning Network payments.
|
||||
"""
|
||||
import uuid
|
||||
|
||||
# Check if the account already exists
|
||||
existing = await db.fetchone(
|
||||
"""
|
||||
SELECT id FROM accounts
|
||||
WHERE name = 'Assets:Bitcoin:OnChain'
|
||||
"""
|
||||
)
|
||||
|
||||
if not existing:
|
||||
# Create the on-chain Bitcoin asset account
|
||||
await db.execute(
|
||||
f"""
|
||||
INSERT INTO accounts (id, name, account_type, description, created_at)
|
||||
VALUES (:id, :name, :type, :description, {db.timestamp_now})
|
||||
""",
|
||||
{
|
||||
"id": str(uuid.uuid4()),
|
||||
"name": "Assets:Bitcoin:OnChain",
|
||||
"type": "asset",
|
||||
"description": "On-chain Bitcoin wallet"
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -188,11 +188,13 @@ class SettleReceivable(BaseModel):
|
|||
|
||||
user_id: str
|
||||
amount: Decimal # Amount in the specified currency (or satoshis if currency is None)
|
||||
payment_method: str # "cash", "bank_transfer", "lightning", "other"
|
||||
payment_method: str # "cash", "bank_transfer", "check", "lightning", "btc_onchain", "other"
|
||||
description: str # Description of the payment
|
||||
reference: Optional[str] = None # Optional reference (receipt number, transaction ID, etc.)
|
||||
currency: Optional[str] = None # If None, amount is in satoshis. Otherwise, fiat currency code (EUR, USD, etc.)
|
||||
amount_sats: Optional[int] = None # Equivalent amount in sats (for reference/conversion tracking)
|
||||
payment_hash: Optional[str] = None # For lightning payments
|
||||
txid: Optional[str] = None # For on-chain Bitcoin transactions
|
||||
|
||||
|
||||
class PayUser(BaseModel):
|
||||
|
|
@ -200,12 +202,13 @@ class PayUser(BaseModel):
|
|||
|
||||
user_id: str
|
||||
amount: Decimal # Amount in the specified currency (or satoshis if currency is None)
|
||||
payment_method: str # "cash", "bank_transfer", "lightning", "check", "other"
|
||||
payment_method: str # "cash", "bank_transfer", "check", "lightning", "btc_onchain", "other"
|
||||
description: Optional[str] = None # Description of the payment
|
||||
reference: Optional[str] = None # Optional reference (receipt number, transaction ID, etc.)
|
||||
currency: Optional[str] = None # If None, amount is in satoshis. Otherwise, fiat currency code (EUR, USD, etc.)
|
||||
amount_sats: Optional[int] = None # Equivalent amount in sats (for reference/conversion tracking)
|
||||
payment_hash: Optional[str] = None # For lightning payments
|
||||
txid: Optional[str] = None # For on-chain Bitcoin transactions
|
||||
|
||||
|
||||
class AssertionStatus(str, Enum):
|
||||
|
|
|
|||
76
views_api.py
76
views_api.py
|
|
@ -786,7 +786,7 @@ async def api_settle_receivable(
|
|||
)
|
||||
|
||||
# Validate payment method
|
||||
valid_methods = ["cash", "bank_transfer", "check", "other"]
|
||||
valid_methods = ["cash", "bank_transfer", "check", "lightning", "btc_onchain", "other"]
|
||||
if data.payment_method.lower() not in valid_methods:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
|
|
@ -803,6 +803,8 @@ async def api_settle_receivable(
|
|||
"cash": "Cash",
|
||||
"bank_transfer": "Bank Account",
|
||||
"check": "Bank Account",
|
||||
"lightning": "Assets:Bitcoin:Lightning",
|
||||
"btc_onchain": "Assets:Bitcoin:OnChain",
|
||||
"other": "Cash"
|
||||
}
|
||||
|
||||
|
|
@ -852,6 +854,14 @@ async def api_settle_receivable(
|
|||
amount_in_sats = int(data.amount)
|
||||
line_metadata = {}
|
||||
|
||||
# Add payment hash for lightning payments
|
||||
if data.payment_hash:
|
||||
line_metadata["payment_hash"] = data.payment_hash
|
||||
|
||||
# Add transaction ID for on-chain Bitcoin payments
|
||||
if data.txid:
|
||||
line_metadata["txid"] = data.txid
|
||||
|
||||
# Add meta information for audit trail
|
||||
entry_meta = {
|
||||
"source": "manual_settlement",
|
||||
|
|
@ -924,7 +934,7 @@ async def api_pay_user(
|
|||
)
|
||||
|
||||
# Validate payment method
|
||||
valid_methods = ["cash", "bank_transfer", "check", "lightning", "other"]
|
||||
valid_methods = ["cash", "bank_transfer", "check", "lightning", "btc_onchain", "other"]
|
||||
if data.payment_method.lower() not in valid_methods:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
|
|
@ -937,43 +947,31 @@ async def api_pay_user(
|
|||
)
|
||||
|
||||
# Get the appropriate asset account based on payment method
|
||||
if data.payment_method.lower() == "lightning":
|
||||
# For lightning, use the Lightning Wallet account
|
||||
payment_account = await get_account_by_name("Lightning Wallet")
|
||||
if not payment_account:
|
||||
# Create it if it doesn't exist
|
||||
payment_account = await create_account(
|
||||
CreateAccount(
|
||||
name="Lightning Wallet",
|
||||
account_type=AccountType.ASSET,
|
||||
description="Lightning Network wallet for Castle",
|
||||
),
|
||||
wallet.wallet.id,
|
||||
)
|
||||
else:
|
||||
# For cash/bank/check
|
||||
payment_account_map = {
|
||||
"cash": "Cash",
|
||||
"bank_transfer": "Bank Account",
|
||||
"check": "Bank Account",
|
||||
"other": "Cash"
|
||||
}
|
||||
account_name = payment_account_map.get(data.payment_method.lower(), "Cash")
|
||||
payment_account = await get_account_by_name(account_name)
|
||||
payment_account_map = {
|
||||
"cash": "Cash",
|
||||
"bank_transfer": "Bank Account",
|
||||
"check": "Bank Account",
|
||||
"lightning": "Assets:Bitcoin:Lightning",
|
||||
"btc_onchain": "Assets:Bitcoin:OnChain",
|
||||
"other": "Cash"
|
||||
}
|
||||
|
||||
if not payment_account:
|
||||
# Try to find any asset account
|
||||
all_accounts = await get_all_accounts()
|
||||
for acc in all_accounts:
|
||||
if acc.account_type == AccountType.ASSET and "receivable" not in acc.name.lower():
|
||||
payment_account = acc
|
||||
break
|
||||
account_name = payment_account_map.get(data.payment_method.lower(), "Cash")
|
||||
payment_account = await get_account_by_name(account_name)
|
||||
|
||||
if not payment_account:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail=f"Payment account '{account_name}' not found. Please create it first.",
|
||||
)
|
||||
if not payment_account:
|
||||
# Try to find any asset account that's not receivable
|
||||
all_accounts = await get_all_accounts()
|
||||
for acc in all_accounts:
|
||||
if acc.account_type == AccountType.ASSET and "receivable" not in acc.name.lower():
|
||||
payment_account = acc
|
||||
break
|
||||
|
||||
if not payment_account:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail=f"Payment account '{account_name}' not found. Please create it first.",
|
||||
)
|
||||
|
||||
# Determine the amount to record in the journal
|
||||
# IMPORTANT: Always record in satoshis to match the payable account balance
|
||||
|
|
@ -1003,6 +1001,10 @@ async def api_pay_user(
|
|||
if data.payment_hash:
|
||||
line_metadata["payment_hash"] = data.payment_hash
|
||||
|
||||
# Add transaction ID for on-chain Bitcoin payments
|
||||
if data.txid:
|
||||
line_metadata["txid"] = data.txid
|
||||
|
||||
# Create journal entry
|
||||
# DR Accounts Payable (liability decreased), CR Cash/Lightning/Bank (asset decreased)
|
||||
# This records that castle paid its debt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue