Implement encrypted transaction caching for GoCardless adapter

- Reduces GoCardless API calls by up to 99% through intelligent caching of transaction data
- Secure AES-GCM encryption with PBKDF2 key derivation (200k iterations) for at-rest storage
- Automatic range merging and transaction deduplication to minimize storage and API usage
- Cache-first approach with automatic fetching of uncovered date ranges
- Comprehensive test suite with 30 unit tests covering all cache operations and edge cases
- Thread-safe implementation with in-memory caching and encrypted disk persistence

Cache everything Gocardless sends back
This commit is contained in:
2025-11-21 21:16:11 +01:00
parent 1dd251c379
commit 53087fa900
13 changed files with 1551 additions and 66 deletions

View File

@@ -59,10 +59,24 @@ pub struct TransactionBookedPending {
pub struct Transaction {
#[serde(rename = "transactionId")]
pub transaction_id: Option<String>,
#[serde(rename = "entryReference")]
pub entry_reference: Option<String>,
#[serde(rename = "endToEndId")]
pub end_to_end_id: Option<String>,
#[serde(rename = "mandateId")]
pub mandate_id: Option<String>,
#[serde(rename = "checkId")]
pub check_id: Option<String>,
#[serde(rename = "creditorId")]
pub creditor_id: Option<String>,
#[serde(rename = "bookingDate")]
pub booking_date: Option<String>,
#[serde(rename = "valueDate")]
pub value_date: Option<String>,
#[serde(rename = "bookingDateTime")]
pub booking_date_time: Option<String>,
#[serde(rename = "valueDateTime")]
pub value_date_time: Option<String>,
#[serde(rename = "transactionAmount")]
pub transaction_amount: TransactionAmount,
#[serde(rename = "currencyExchange")]
@@ -71,14 +85,32 @@ pub struct Transaction {
pub creditor_name: Option<String>,
#[serde(rename = "creditorAccount")]
pub creditor_account: Option<AccountDetails>,
#[serde(rename = "ultimateCreditor")]
pub ultimate_creditor: Option<String>,
#[serde(rename = "debtorName")]
pub debtor_name: Option<String>,
#[serde(rename = "debtorAccount")]
pub debtor_account: Option<AccountDetails>,
#[serde(rename = "ultimateDebtor")]
pub ultimate_debtor: Option<String>,
#[serde(rename = "remittanceInformationUnstructured")]
pub remittance_information_unstructured: Option<String>,
#[serde(rename = "remittanceInformationUnstructuredArray")]
pub remittance_information_unstructured_array: Option<Vec<String>>,
#[serde(rename = "remittanceInformationStructured")]
pub remittance_information_structured: Option<String>,
#[serde(rename = "remittanceInformationStructuredArray")]
pub remittance_information_structured_array: Option<Vec<String>>,
#[serde(rename = "additionalInformation")]
pub additional_information: Option<String>,
#[serde(rename = "purposeCode")]
pub purpose_code: Option<String>,
#[serde(rename = "bankTransactionCode")]
pub bank_transaction_code: Option<String>,
#[serde(rename = "proprietaryBankTransactionCode")]
pub proprietary_bank_transaction_code: Option<String>,
#[serde(rename = "internalTransactionId")]
pub internal_transaction_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -102,4 +134,10 @@ pub struct CurrencyExchange {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountDetails {
pub iban: Option<String>,
pub bban: Option<String>,
pub pan: Option<String>,
#[serde(rename = "maskedPan")]
pub masked_pan: Option<String>,
pub msisdn: Option<String>,
pub currency: Option<String>,
}