container: hledger-flow: btcpayserver: support "Legacy Invoice Export" plugin, update "Wallets" impl
BTCPay Server v2.2.0 has a new "Invoices" export format that is more complex than the prior "Legacy Invoice Export" format. To help facilitate the transition to the new format, upstream has provided a plugin for backwards compatibility. However, this plugin happens to provide additional columns that must be supported. Additionaly, the "Wallets" format also has new columns with fee information, so the impl now supports tx fees (prior, fees required manual input). The "Wallets" impl also now defaults to a refund expense account for outgoing txs and adds tax rules & documentation for rationale.
This commit is contained in:
@@ -49,13 +49,15 @@ function btcpayserver::print_warning()
|
|||||||
#
|
#
|
||||||
# TL;DR:
|
# TL;DR:
|
||||||
#
|
#
|
||||||
|
# - As of v2.2.0, *MUST* install the "Legacy Invoice Export" plugin. TODO: use v2.2.0 format w/out plugin
|
||||||
|
#
|
||||||
# - *MUST* export both "Legacy Invoice" report and "Wallets" report for an accurate balance.
|
# - *MUST* export both "Legacy Invoice" report and "Wallets" report for an accurate balance.
|
||||||
# * The "Legacy Invoice" report currently does *not* include any outgoing txs (refunds or transfers).
|
# * The "Legacy Invoice" report currently does *not* include any outgoing txs (refunds or transfers).
|
||||||
#
|
#
|
||||||
# WARNING:
|
# WARNING:
|
||||||
#
|
#
|
||||||
# - All outgoing txs do *not* account for fees (they are lumped in with the total).
|
# - If using a watch-ony wallet, consider accounting entirely here (via btcpayserver) or
|
||||||
# * Until upstream changes this, any fees must be separated manually w/ custom rules.
|
# entirely via that other wallet (e.g., electrum). Mixing the two may cause tax accounting issues.
|
||||||
#
|
#
|
||||||
# CAUTION:
|
# CAUTION:
|
||||||
#
|
#
|
||||||
@@ -119,16 +121,19 @@ function btcpayserver::legacy()
|
|||||||
printf $16 OFS # InvoiceCurrency
|
printf $16 OFS # InvoiceCurrency
|
||||||
printf $17 OFS # InvoiceDue
|
printf $17 OFS # InvoiceDue
|
||||||
printf $18 OFS # InvoicePrice
|
printf $18 OFS # InvoicePrice
|
||||||
printf $19 OFS # InvoiceItemCode
|
printf $19 OFS # InvoiceTaxIncluded
|
||||||
|
printf $20 OFS # InvoiceTip
|
||||||
|
printf $21 OFS # InvoiceSubtotal
|
||||||
|
printf $22 OFS # InvoiceItemCode
|
||||||
|
|
||||||
# TODO: if description contains comma(s)?
|
# TODO: if description contains comma(s)?
|
||||||
printf $20 OFS # InvoiceItemDesc
|
printf $23 OFS # InvoiceItemDesc
|
||||||
|
|
||||||
printf $21 OFS # InvoiceFullStatus
|
printf $24 OFS # InvoiceFullStatus
|
||||||
printf $22 OFS # InvoiceStatus
|
printf $25 OFS # InvoiceStatus
|
||||||
printf $23 OFS # InvoiceExceptionStatus
|
printf $26 OFS # InvoiceExceptionStatus
|
||||||
printf $24 OFS # BuyerEmail
|
printf $27 OFS # BuyerEmail
|
||||||
printf $25 OFS # Accounted
|
printf $28 OFS # Accounted
|
||||||
|
|
||||||
# WARNING: appears to be always IN (see notes regarding "Wallets" report)
|
# WARNING: appears to be always IN (see notes regarding "Wallets" report)
|
||||||
printf "IN" OFS # Direction
|
printf "IN" OFS # Direction
|
||||||
@@ -176,6 +181,9 @@ function btcpayserver::wallets()
|
|||||||
if ($6 !~ /^-/)
|
if ($6 !~ /^-/)
|
||||||
next
|
next
|
||||||
|
|
||||||
|
# Strip sign from amount (using direction instead)
|
||||||
|
sub(/^-/, "", $6)
|
||||||
|
|
||||||
# Date (ReceivedDate w/ local timezone added)
|
# Date (ReceivedDate w/ local timezone added)
|
||||||
sub(/ /, "T", $1) # HACK: makes arg-friendly by removing space
|
sub(/ /, "T", $1) # HACK: makes arg-friendly by removing space
|
||||||
cmd = "date \"+%F %T %z\" --date="$1 | getline date
|
cmd = "date \"+%F %T %z\" --date="$1 | getline date
|
||||||
@@ -197,14 +205,25 @@ function btcpayserver::wallets()
|
|||||||
printf $2 OFS # Crypto (CryptoCode)
|
printf $2 OFS # Crypto (CryptoCode)
|
||||||
|
|
||||||
# BalanceChange (Paid)
|
# BalanceChange (Paid)
|
||||||
printf("%.8f", $6); printf OFS
|
# NOTE: must provide as subtotal, so subtract fee
|
||||||
|
printf("%.8f", $6 - $7); printf OFS
|
||||||
|
|
||||||
|
# Fee (NetworkFee)
|
||||||
|
printf("%.8f", $7); printf OFS
|
||||||
|
|
||||||
printf OFS # (NetworkFee)
|
|
||||||
printf OFS # (ConversionRate)
|
printf OFS # (ConversionRate)
|
||||||
printf OFS # (PaidCurrency)
|
|
||||||
printf OFS # (InvoiceCurrency)
|
# FeeRate (PaidCurrency)
|
||||||
|
printf("%.3f", $8); printf OFS
|
||||||
|
|
||||||
|
# Rate (XXX) (InvoiceCurrency)
|
||||||
|
printf("%.3f", $9); printf OFS
|
||||||
|
|
||||||
printf OFS # (InvoiceDue)
|
printf OFS # (InvoiceDue)
|
||||||
printf OFS # (InvoicePrice)
|
printf OFS # (InvoicePrice)
|
||||||
|
printf OFS # (InvoiceTaxIncluded)
|
||||||
|
printf OFS # (InvoiceTip)
|
||||||
|
printf OFS # (InvoiceSubtotal)
|
||||||
printf OFS # (InvoiceItemCode)
|
printf OFS # (InvoiceItemCode)
|
||||||
printf OFS # (InvoiceItemDesc)
|
printf OFS # (InvoiceItemDesc)
|
||||||
printf OFS # (InvoiceFullStatus)
|
printf OFS # (InvoiceFullStatus)
|
||||||
@@ -223,7 +242,7 @@ function btcpayserver::wallets()
|
|||||||
|
|
||||||
function btcpayserver::parse()
|
function btcpayserver::parse()
|
||||||
{
|
{
|
||||||
lib_preprocess::test_header "ReceivedDate,StoreId,OrderId,InvoiceId,InvoiceCreatedDate,InvoiceExpirationDate,InvoiceMonitoringDate,PaymentId,Destination,PaymentType,CryptoCode,Paid,NetworkFee,ConversionRate,PaidCurrency,InvoiceCurrency,InvoiceDue,InvoicePrice,InvoiceItemCode,InvoiceItemDesc,InvoiceFullStatus,InvoiceStatus,InvoiceExceptionStatus,BuyerEmail,Accounted" \
|
lib_preprocess::test_header "ReceivedDate,StoreId,OrderId,InvoiceId,InvoiceCreatedDate,InvoiceExpirationDate,InvoiceMonitoringDate,PaymentId,Destination,PaymentType,CryptoCode,Paid,NetworkFee,ConversionRate,PaidCurrency,InvoiceCurrency,InvoiceDue,InvoicePrice,InvoiceTaxIncluded,InvoiceTip,InvoiceSubtotal,InvoiceItemCode,InvoiceItemDesc,InvoiceFullStatus,InvoiceStatus,InvoiceExceptionStatus,BuyerEmail,Accounted" \
|
||||||
&& btcpayserver::legacy
|
&& btcpayserver::legacy
|
||||||
|
|
||||||
lib_preprocess::test_header "Date,InvoiceId,OrderId,Category,PaymentMethodId,Confirmed,Address,PaymentCurrency,PaymentAmount,PaymentMethodFee,LightningAddress,InvoiceCurrency,InvoiceCurrencyAmount,Rate" \
|
lib_preprocess::test_header "Date,InvoiceId,OrderId,Category,PaymentMethodId,Confirmed,Address,PaymentCurrency,PaymentAmount,PaymentMethodFee,LightningAddress,InvoiceCurrency,InvoiceCurrencyAmount,Rate" \
|
||||||
@@ -238,7 +257,9 @@ function btcpayserver::parse()
|
|||||||
lib_preprocess::test_header "Date,InvoiceId,State,AppId,Product,Quantity,CurrencyAmount,Currency" \
|
lib_preprocess::test_header "Date,InvoiceId,State,AppId,Product,Quantity,CurrencyAmount,Currency" \
|
||||||
&& btcpayserver::sales
|
&& btcpayserver::sales
|
||||||
|
|
||||||
lib_preprocess::test_header "Date,Crypto,TransactionId,InvoiceId,Confirmed,BalanceChange" \
|
# TODO: don't hardcode USD (upstream should make the currency in "Rate (XXX)" into a separate column).
|
||||||
|
#lib_preprocess::test_header "Date,Crypto,TransactionId,InvoiceId,Confirmed,BalanceChange,Fee,FeeRate,Rate (USD)" \
|
||||||
|
lib_preprocess::test_header "Date,Crypto,TransactionId,InvoiceId,Confirmed,BalanceChange,Fee,FeeRate" \
|
||||||
&& btcpayserver::wallets
|
&& btcpayserver::wallets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# TODO: currently, only supports "Legacy Invoice" and "Wallets" format (w/ additional custom columns)
|
# TODO: currently, only supports "Legacy Invoice" and "Wallets" format (w/ additional custom columns)
|
||||||
fields ReceivedDate,StoreId,OrderId,InvoiceId,InvoiceCreatedDate,InvoiceExpirationDate,InvoiceMonitoringDate,txid,index,Destination,PaymentType,CryptoCode,Paid,NetworkFee,ConversionRate,PaidCurrency,InvoiceCurrency,InvoiceDue,InvoicePrice,InvoiceItemCode,InvoiceItemDesc,InvoiceFullStatus,InvoiceStatus,InvoiceExceptionStatus,BuyerEmail,Accounted,direction,subaccount
|
fields ReceivedDate,StoreId,OrderId,InvoiceId,InvoiceCreatedDate,InvoiceExpirationDate,InvoiceMonitoringDate,txid,index,Destination,PaymentType,CryptoCode,Paid,NetworkFee,ConversionRate,PaidCurrency,InvoiceCurrency,InvoiceDue,InvoicePrice,InvoiceTaxIncluded,InvoiceTip,InvoiceSubtotal,InvoiceItemCode,InvoiceItemDesc,InvoiceFullStatus,InvoiceStatus,InvoiceExceptionStatus,BuyerEmail,Accounted,direction,subaccount
|
||||||
|
|
||||||
# NOTE: BTCPayServer exports to localtime
|
# NOTE: BTCPayServer exports to localtime
|
||||||
date-format %Y-%m-%d %H:%M:%S %z
|
date-format %Y-%m-%d %H:%M:%S %z
|
||||||
@@ -41,43 +41,78 @@ if %Accounted ^[^a-z]*$
|
|||||||
# NOTE: all IN assumed to be INCOME
|
# NOTE: all IN assumed to be INCOME
|
||||||
|
|
||||||
# Default comment
|
# Default comment
|
||||||
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, invoice_tax:%InvoiceTaxIncluded, invoice_tip:%InvoiceTip, invoice_subtotal:%InvoiceSubtotal, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
||||||
|
|
||||||
# Comment w/ item code
|
# Comment w/ item code
|
||||||
if %InvoiceItemCode [a-z0-9]
|
if %InvoiceItemCode [a-z0-9]
|
||||||
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, item_code:%InvoiceItemCode, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, invoice_tax:%InvoiceTaxIncluded, invoice_tip:%InvoiceTip, invoice_subtotal:%InvoiceSubtotal, item_code:%InvoiceItemCode, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
||||||
|
|
||||||
# Comment w/ buyer email
|
# Comment w/ buyer email
|
||||||
if %BuyerEmail [a-z0-9]
|
if %BuyerEmail [a-z0-9]
|
||||||
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, email:%BuyerEmail, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, invoice_tax:%InvoiceTaxIncluded, invoice_tip:%InvoiceTip, invoice_subtotal:%InvoiceSubtotal, email:%BuyerEmail, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
||||||
|
|
||||||
# Comment w/ both item code + buyer email
|
# Comment w/ both item code + buyer email
|
||||||
if %InvoiceItemCode [a-z0-9]
|
if %InvoiceItemCode [a-z0-9]
|
||||||
& %BuyerEmail [a-z0-9]
|
& %BuyerEmail [a-z0-9]
|
||||||
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, item_code:%InvoiceItemCode, email:%BuyerEmail, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
comment created:%InvoiceCreatedDate, expired:%InvoiceExpirationDate, store_id:%StoreId, order_id:%OrderId, invoice_id:%InvoiceId, invoice_tax:%InvoiceTaxIncluded, invoice_tip:%InvoiceTip, invoice_subtotal:%InvoiceSubtotal, item_code:%InvoiceItemCode, email:%BuyerEmail, type:%PaymentType, to_address:%Destination, txid:%txid, index:%index, status:%InvoiceStatus, direction:%direction, taxed_as:INCOME
|
||||||
|
|
||||||
# Comment for "Wallets" export
|
|
||||||
if %direction ^OUT$
|
|
||||||
comment txid:%txid, confirmed:%Accounted, direction:%direction
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Direction
|
# Direction
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# NOTE/TODO:
|
||||||
|
#
|
||||||
|
# InvoiceTaxIncluded, InvoiceTip and InvoiceSubtotal are not included
|
||||||
|
# as new accounts because they are static across rows and treated more
|
||||||
|
# like tags (i.e., if an invoice is not paid in full in a single tx,
|
||||||
|
# then these entries will be repeated across rows).
|
||||||
|
|
||||||
# Default income
|
# Default income
|
||||||
if %direction ^IN$
|
if %direction ^IN$
|
||||||
account2 income:btcpayserver:%subaccount:%CryptoCode
|
account2 income:btcpayserver:%subaccount:%CryptoCode
|
||||||
comment2 %ReceivedDate,INCOME,btcpayserver:%subaccount,%CryptoCode,%Paid,%InvoiceCurrency,%PaidCurrency,%InvoiceId
|
comment2 %ReceivedDate,INCOME,btcpayserver:%subaccount,%CryptoCode,%Paid,%InvoiceCurrency,%PaidCurrency,%InvoiceId
|
||||||
|
|
||||||
# Default equity transfer
|
# Default refund
|
||||||
|
#
|
||||||
|
# NOTE:
|
||||||
|
#
|
||||||
|
# - If the tx is not a refund, use custom rules for account2/comment2
|
||||||
|
# * If the tx also not an expense, remove comment tag taxed_as:SPEND
|
||||||
|
#
|
||||||
|
# - Since refunds may not be considered taxable events, and since the
|
||||||
|
# "Wallets" export only includes the market price of the tx at the
|
||||||
|
# time of sending (refunding), use custom rules to replace the
|
||||||
|
# sending tx's price to the time of receiving (the income tx).
|
||||||
|
#
|
||||||
|
# This way one can "negate" the original income event by "spending"
|
||||||
|
# the original amount at the original rate (the rate at which the
|
||||||
|
# invoice was paid):
|
||||||
|
#
|
||||||
|
# Example in custom rules (replace "MANUAL_ADJUSTMENT_NEEDED"):
|
||||||
|
#
|
||||||
|
# if %direction ^OUT$
|
||||||
|
# comment txid:%txid, confirmed:%Accounted, direction:%direction, taxed_as:SPEND
|
||||||
|
# comment2 %ReceivedDate,SPEND,btcpayserver:%subaccount:%CryptoCode,%CryptoCode,%Paid,USD,MANUAL_ADJUSTMENT_NEEDED,REFUND
|
||||||
|
#
|
||||||
|
# - Use different custom rules if your refund type is not the default
|
||||||
|
# provided option of 'price at the rate the invoice was paid'.
|
||||||
|
#
|
||||||
if %direction ^OUT$
|
if %direction ^OUT$
|
||||||
account2 equity:btcpayserver:%subaccount:deposit:%CryptoCode
|
account2 expenses:btcpayserver:%subaccount:refunds:%CryptoCode
|
||||||
|
amount -%Paid %CryptoCode
|
||||||
|
comment txid:%txid, confirmed:%Accounted, direction:%direction, taxed_as:SPEND
|
||||||
|
comment2 %ReceivedDate,SPEND,btcpayserver:%subaccount:%CryptoCode,%CryptoCode,%Paid,USD,,REFUND
|
||||||
|
# TODO: don't hardcode USD (upstream should make the "Rate (XXX)" currency column a separate column).
|
||||||
|
|
||||||
# TODO:
|
if %direction ^OUT$
|
||||||
#
|
& %NetworkFee [1-9]
|
||||||
# WARNING:
|
account3 assets:btcpayserver:%subaccount:%CryptoCode
|
||||||
#
|
amount3 -%NetworkFee %CryptoCode
|
||||||
# - All outgoing txs do *not* account for fees (they are lumped in with the total).
|
account4 expenses:electrum:%subaccount:fees:%CryptoCode
|
||||||
# * Until upstream changes this, any fees must be separated manually w/ custom rules.
|
amount4 %NetworkFee %CryptoCode
|
||||||
|
comment txid:%txid, confirmed:%Accounted, direction:%direction, taxed_as:SPEND
|
||||||
|
comment3 %ReceivedDate,SPEND,btcpayserver:%subaccount:%CryptoCode,%CryptoCode,%NetworkFee,USD,%PaidCurrency,FEE
|
||||||
|
# Using comment3 so a comment2 SPEND isn't overwritten (when applicable)
|
||||||
|
# TODO: don't hardcode USD (upstream should make the "Rate (XXX)" currency column a separate column).
|
||||||
|
|
||||||
# vim: sw=2 sts=2 si ai et
|
# vim: sw=2 sts=2 si ai et
|
||||||
|
|||||||
Reference in New Issue
Block a user