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'
|
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
|
user_id: str
|
||||||
amount: Decimal # Amount in the specified currency (or satoshis if currency is None)
|
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
|
description: str # Description of the payment
|
||||||
reference: Optional[str] = None # Optional reference (receipt number, transaction ID, etc.)
|
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.)
|
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)
|
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):
|
class PayUser(BaseModel):
|
||||||
|
|
@ -200,12 +202,13 @@ class PayUser(BaseModel):
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
amount: Decimal # Amount in the specified currency (or satoshis if currency is None)
|
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
|
description: Optional[str] = None # Description of the payment
|
||||||
reference: Optional[str] = None # Optional reference (receipt number, transaction ID, etc.)
|
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.)
|
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)
|
amount_sats: Optional[int] = None # Equivalent amount in sats (for reference/conversion tracking)
|
||||||
payment_hash: Optional[str] = None # For lightning payments
|
payment_hash: Optional[str] = None # For lightning payments
|
||||||
|
txid: Optional[str] = None # For on-chain Bitcoin transactions
|
||||||
|
|
||||||
|
|
||||||
class AssertionStatus(str, Enum):
|
class AssertionStatus(str, Enum):
|
||||||
|
|
|
||||||
76
views_api.py
76
views_api.py
|
|
@ -786,7 +786,7 @@ async def api_settle_receivable(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate payment method
|
# 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:
|
if data.payment_method.lower() not in valid_methods:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
|
@ -803,6 +803,8 @@ async def api_settle_receivable(
|
||||||
"cash": "Cash",
|
"cash": "Cash",
|
||||||
"bank_transfer": "Bank Account",
|
"bank_transfer": "Bank Account",
|
||||||
"check": "Bank Account",
|
"check": "Bank Account",
|
||||||
|
"lightning": "Assets:Bitcoin:Lightning",
|
||||||
|
"btc_onchain": "Assets:Bitcoin:OnChain",
|
||||||
"other": "Cash"
|
"other": "Cash"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -852,6 +854,14 @@ async def api_settle_receivable(
|
||||||
amount_in_sats = int(data.amount)
|
amount_in_sats = int(data.amount)
|
||||||
line_metadata = {}
|
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
|
# Add meta information for audit trail
|
||||||
entry_meta = {
|
entry_meta = {
|
||||||
"source": "manual_settlement",
|
"source": "manual_settlement",
|
||||||
|
|
@ -924,7 +934,7 @@ async def api_pay_user(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate payment method
|
# 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:
|
if data.payment_method.lower() not in valid_methods:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
|
@ -937,43 +947,31 @@ async def api_pay_user(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the appropriate asset account based on payment method
|
# Get the appropriate asset account based on payment method
|
||||||
if data.payment_method.lower() == "lightning":
|
payment_account_map = {
|
||||||
# For lightning, use the Lightning Wallet account
|
"cash": "Cash",
|
||||||
payment_account = await get_account_by_name("Lightning Wallet")
|
"bank_transfer": "Bank Account",
|
||||||
if not payment_account:
|
"check": "Bank Account",
|
||||||
# Create it if it doesn't exist
|
"lightning": "Assets:Bitcoin:Lightning",
|
||||||
payment_account = await create_account(
|
"btc_onchain": "Assets:Bitcoin:OnChain",
|
||||||
CreateAccount(
|
"other": "Cash"
|
||||||
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)
|
|
||||||
|
|
||||||
if not payment_account:
|
account_name = payment_account_map.get(data.payment_method.lower(), "Cash")
|
||||||
# Try to find any asset account
|
payment_account = await get_account_by_name(account_name)
|
||||||
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:
|
if not payment_account:
|
||||||
raise HTTPException(
|
# Try to find any asset account that's not receivable
|
||||||
status_code=HTTPStatus.NOT_FOUND,
|
all_accounts = await get_all_accounts()
|
||||||
detail=f"Payment account '{account_name}' not found. Please create it first.",
|
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
|
# Determine the amount to record in the journal
|
||||||
# IMPORTANT: Always record in satoshis to match the payable account balance
|
# IMPORTANT: Always record in satoshis to match the payable account balance
|
||||||
|
|
@ -1003,6 +1001,10 @@ async def api_pay_user(
|
||||||
if data.payment_hash:
|
if data.payment_hash:
|
||||||
line_metadata["payment_hash"] = 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
|
# Create journal entry
|
||||||
# DR Accounts Payable (liability decreased), CR Cash/Lightning/Bank (asset decreased)
|
# DR Accounts Payable (liability decreased), CR Cash/Lightning/Bank (asset decreased)
|
||||||
# This records that castle paid its debt
|
# This records that castle paid its debt
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue