From 7e9362da44a81698e93c7f758865417d38ae9f83 Mon Sep 17 00:00:00 2001 From: Sharfy Adamantine Date: Tue, 7 Jul 2026 17:11:24 +0200 Subject: [PATCH 1/2] feat(funding): add fx + refundOf to receipt; transactionId privacy note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three additions to org.hypercerts.funding.receipt from downstream integration (Ma Earth platform receipts for fiat + crypto donations): - fx (#fxInfo object: originalAmount/originalCurrency required; rate/rateSource/rateTimestamp optional): stamps the payment's original denomination + conversion rate at payment time. Receipts for one payment written by different parties in different currencies otherwise never reconcile, and amount/currency-based clustering cannot match them; related receipts copy the block verbatim. - refundOf (strongRef): represents partial/whole refunds as NEW receipts netted against the original by consumers — receipts are immutable once attested, so edits are not an option. Copying the original's fx lets partial refunds compute at the original rate without forex drift. - transactionId description gains privacy guidance for public repos: prefer opaque processor ids / internal references, avoid bank references embedding account details, and an onchain tx hash publicly reveals the sending wallet (significant when from was omitted for donor anonymity). Non-breaking: both fields optional; existing receipts remain valid. --- ...funding-receipt-fx-refunds-txid-privacy.md | 11 +++++ SCHEMAS.md | 42 ++++++++++++------ lexicons/org/hypercerts/funding/receipt.json | 44 ++++++++++++++++++- 3 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 .changeset/funding-receipt-fx-refunds-txid-privacy.md diff --git a/.changeset/funding-receipt-fx-refunds-txid-privacy.md b/.changeset/funding-receipt-fx-refunds-txid-privacy.md new file mode 100644 index 00000000..2ae2c956 --- /dev/null +++ b/.changeset/funding-receipt-fx-refunds-txid-privacy.md @@ -0,0 +1,11 @@ +--- +"@hypercerts-org/lexicon": minor +--- + +Funding receipt: add `fx` (foreign-exchange context) and `refundOf` (immutable refund representation), plus `transactionId` privacy guidance. + +**Non-breaking:** both new fields are optional; existing receipts remain valid. + +- **`fx`** (new `#fxInfo` object: required `originalAmount`/`originalCurrency`, optional `rate`/`rateSource`/`rateTimestamp`) stamps the payment's original denomination and conversion rate at payment time. Without it, receipts for the same payment written by different parties in different currencies never reconcile — cross-receipt math diverges as market rates move, and amount/currency-based payment clustering can't match them. Writers of related receipts copy the block verbatim so consumers compare deterministically. +- **`refundOf`** (strongRef) marks a receipt as a partial or whole refund of the referenced receipt. Receipts are immutable once published — attestations bind to their content — so refunds are new receipts, never edits: the refunded amount in the original denomination (copying the original's `fx` so partial refunds compute at the original rate, without forex drift), `from`/`to` mirrored, one receipt per partial refund. Aggregating consumers net refund receipts against the receipt they reference. +- **`transactionId`** description now carries privacy guidance for public repositories: prefer opaque processor IDs or internal references, avoid bank references embedding account details, and note that an onchain transaction hash publicly reveals the sending wallet — significant when the sender was deliberately kept anonymous by omitting `from`. diff --git a/SCHEMAS.md b/SCHEMAS.md index 1c16ca94..7ab0cad5 100644 --- a/SCHEMAS.md +++ b/SCHEMAS.md @@ -272,20 +272,22 @@ Overall score for an evaluation on a numeric scale. #### Properties -| Property | Type | Required | Description | Comments | -| ---------------- | -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | -| `from` | `union` | ❌ | The sender of the funds (a free-text string, an account DID, or a strong reference to a record). Optional — omit to represent anonymity. | | -| `to` | `union` | ✅ | The recipient of the funds (a free-text string, an account DID, or a strong reference to a record). | | -| `amount` | `string` | ✅ | Amount of funding received as a numeric string (e.g. '1000.50'). | maxLength: 50 | -| `currency` | `string` | ✅ | Currency of the payment (e.g. EUR, USD, ETH). | maxLength: 10 | -| `paymentRail` | `string` | ❌ | How the funds were transferred (e.g. bank_transfer, credit_card, onchain, cash, check, payment_processor). | maxLength: 50 | -| `paymentNetwork` | `string` | ❌ | Optional network within the payment rail (e.g. arbitrum, ethereum, sepa, visa, paypal). | maxLength: 50 | -| `transactionId` | `string` | ❌ | Identifier of the underlying payment transaction (e.g. bank reference, onchain transaction hash, or processor-specific ID). Use paymentNetwork to specify the network where applicable. | maxLength: 256 | -| `for` | `ref` | ❌ | Optional strong reference to the activity, project, or organization this funding relates to. | | -| `notes` | `string` | ❌ | Optional notes or additional context for this funding receipt. | maxLength: 500 | -| `occurredAt` | `string` | ❌ | Timestamp when the payment occurred. | | -| `createdAt` | `string` | ✅ | Client-declared timestamp when this receipt record was created. | | -| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | +| Property | Type | Required | Description | Comments | +| ---------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | +| `from` | `union` | ❌ | The sender of the funds (a free-text string, an account DID, or a strong reference to a record). Optional — omit to represent anonymity. | | +| `to` | `union` | ✅ | The recipient of the funds (a free-text string, an account DID, or a strong reference to a record). | | +| `amount` | `string` | ✅ | Amount of funding received as a numeric string (e.g. '1000.50'). | maxLength: 50 | +| `currency` | `string` | ✅ | Currency of the payment (e.g. EUR, USD, ETH). | maxLength: 10 | +| `fx` | `ref` | ❌ | Foreign-exchange context binding a receipt's amount/currency to the payment's original denomination at a point in time. | | +| `paymentRail` | `string` | ❌ | How the funds were transferred (e.g. bank_transfer, credit_card, onchain, cash, check, payment_processor). | maxLength: 50 | +| `paymentNetwork` | `string` | ❌ | Optional network within the payment rail (e.g. arbitrum, ethereum, sepa, visa, paypal). | maxLength: 50 | +| `transactionId` | `string` | ❌ | Identifier of the underlying payment transaction (e.g. bank reference, onchain transaction hash, or processor-specific ID). Use paymentNetwork to specify the network where applicable. Receipts are published to public repositories, so choose identifiers accordingly: prefer opaque processor IDs (dereferenceable only by the processor and the account holder) or internal payment references; avoid bank references that embed account details; and note that an onchain transaction hash publicly reveals the sending wallet — when the sender is deliberately anonymous (from omitted), consider whether publishing the hash is appropriate. | maxLength: 256 | +| `for` | `ref` | ❌ | Optional strong reference to the activity, project, or organization this funding relates to. | | +| `refundOf` | `ref` | ❌ | Optional strong reference marking this receipt as a refund — partial or whole — of the referenced receipt. Receipts are immutable once published (attestations bind to their content), so refunds are represented as new receipts rather than edits: amount/currency carry the refunded amount in the same denomination as the original (copy the original's fx block so partial refunds are computed at the original rate, without forex drift); from/to mirror the original reversed (when the original sender was anonymous, use the #text party for the refund's recipient). Multiple partial refunds are multiple refund receipts. Consumers aggregating funding should net refund receipts against the receipt they reference. | | +| `notes` | `string` | ❌ | Optional notes or additional context for this funding receipt. | maxLength: 500 | +| `occurredAt` | `string` | ❌ | Timestamp when the payment occurred. | | +| `createdAt` | `string` | ✅ | Client-declared timestamp when this receipt record was created. | | +| `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | #### Defs @@ -297,6 +299,18 @@ A free-text string value (e.g. a display name, wallet address, or other identifi | -------- | -------- | -------- | ----------------- | --------------- | | `value` | `string` | ✅ | The string value. | maxLength: 2048 | +##### `org.hypercerts.funding.receipt#fxInfo` + +Foreign-exchange context binding a receipt's amount/currency to the payment's original denomination at a point in time. + +| Property | Type | Required | Description | Comments | +| ------------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `originalAmount` | `string` | ✅ | Amount actually paid, as a numeric string, denominated in originalCurrency. | maxLength: 50 | +| `originalCurrency` | `string` | ✅ | Currency the payment was actually made in (e.g. EUR, CELO, USDC). | maxLength: 10 | +| `rate` | `string` | ❌ | Conversion rate applied at payment time, as a numeric string: the price of one unit of originalCurrency expressed in the receipt's currency, so amount = originalAmount x rate (up to rounding). Recording the rate explicitly removes rounding ambiguity and lets later computations (e.g. partial refunds) reuse the original rate instead of drifting with the market. | maxLength: 50 | +| `rateSource` | `string` | ❌ | Where the rate came from (e.g. stripe, ecb, coingecko). | maxLength: 50 | +| `rateTimestamp` | `string` | ❌ | When the rate was captured; usually the payment time. | | + --- ### `org.hypercerts.workscope.cel` diff --git a/lexicons/org/hypercerts/funding/receipt.json b/lexicons/org/hypercerts/funding/receipt.json index e0522e7c..312db22c 100644 --- a/lexicons/org/hypercerts/funding/receipt.json +++ b/lexicons/org/hypercerts/funding/receipt.json @@ -38,6 +38,11 @@ "description": "Currency of the payment (e.g. EUR, USD, ETH).", "maxLength": 10 }, + "fx": { + "type": "ref", + "ref": "#fxInfo", + "description": "Optional foreign-exchange context. Stamp at payment time whenever amount/currency is not the denomination the payment was actually made in (e.g. a platform recording a USD-normalized amount for a EUR card payment or an on-chain transfer). Receipts for the same payment written by other parties should copy this block verbatim from the receipt they reference, so consumers comparing or aggregating receipts reconcile deterministically instead of re-deriving rates that drift over time." + }, "paymentRail": { "type": "string", "description": "How the funds were transferred (e.g. bank_transfer, credit_card, onchain, cash, check, payment_processor).", @@ -50,7 +55,7 @@ }, "transactionId": { "type": "string", - "description": "Identifier of the underlying payment transaction (e.g. bank reference, onchain transaction hash, or processor-specific ID). Use paymentNetwork to specify the network where applicable.", + "description": "Identifier of the underlying payment transaction (e.g. bank reference, onchain transaction hash, or processor-specific ID). Use paymentNetwork to specify the network where applicable. Receipts are published to public repositories, so choose identifiers accordingly: prefer opaque processor IDs (dereferenceable only by the processor and the account holder) or internal payment references; avoid bank references that embed account details; and note that an onchain transaction hash publicly reveals the sending wallet — when the sender is deliberately anonymous (from omitted), consider whether publishing the hash is appropriate.", "maxLength": 256 }, "for": { @@ -58,6 +63,11 @@ "ref": "com.atproto.repo.strongRef", "description": "Optional strong reference to the activity, project, or organization this funding relates to." }, + "refundOf": { + "type": "ref", + "ref": "com.atproto.repo.strongRef", + "description": "Optional strong reference marking this receipt as a refund — partial or whole — of the referenced receipt. Receipts are immutable once published (attestations bind to their content), so refunds are represented as new receipts rather than edits: amount/currency carry the refunded amount in the same denomination as the original (copy the original's fx block so partial refunds are computed at the original rate, without forex drift); from/to mirror the original reversed (when the original sender was anonymous, use the #text party for the refund's recipient). Multiple partial refunds are multiple refund receipts. Consumers aggregating funding should net refund receipts against the receipt they reference." + }, "notes": { "type": "string", "description": "Optional notes or additional context for this funding receipt.", @@ -92,6 +102,38 @@ "maxLength": 2048 } } + }, + "fxInfo": { + "type": "object", + "description": "Foreign-exchange context binding a receipt's amount/currency to the payment's original denomination at a point in time.", + "required": ["originalAmount", "originalCurrency"], + "properties": { + "originalAmount": { + "type": "string", + "description": "Amount actually paid, as a numeric string, denominated in originalCurrency.", + "maxLength": 50 + }, + "originalCurrency": { + "type": "string", + "description": "Currency the payment was actually made in (e.g. EUR, CELO, USDC).", + "maxLength": 10 + }, + "rate": { + "type": "string", + "description": "Conversion rate applied at payment time, as a numeric string: the price of one unit of originalCurrency expressed in the receipt's currency, so amount = originalAmount x rate (up to rounding). Recording the rate explicitly removes rounding ambiguity and lets later computations (e.g. partial refunds) reuse the original rate instead of drifting with the market.", + "maxLength": 50 + }, + "rateSource": { + "type": "string", + "description": "Where the rate came from (e.g. stripe, ecb, coingecko).", + "maxLength": 50 + }, + "rateTimestamp": { + "type": "string", + "format": "datetime", + "description": "When the rate was captured; usually the payment time." + } + } } } } From 1b7b6a39dedbcfd1a1ce5e4abb59de990aa5408a Mon Sep 17 00:00:00 2001 From: Sharfy Adamantine Date: Tue, 7 Jul 2026 17:22:24 +0200 Subject: [PATCH 2/2] feat(funding): fees/netAmount, via, settledAt on receipt Extend the receipt with the reconciliation fields every major payment model carries (Stripe BalanceTransaction, Wise transfers, PayPal capture breakdowns, ISO 20022 pacs.008): - netAmount + fees[] (#fee: type knownValues [processor, platform, network, fx, tax, other], label, amount): the universal gross/fee/net triple. amount stays the gross the sender paid; netAmount is what the recipient received. Without both, a sender's gross receipt and a recipient's net receipt for the same payment never reconcile or cluster. No charge-bearer field: receipts are retrospective, the amounts encode who bore what. - via (same party union as from/to): names the facilitating intermediary (platform / payment servicer / broker) in the record instead of implying it from receipt authorship. - settledAt: settlement time distinct from occurredAt (bank rails settle days later; Stripe available_on / ISO settlement date). All optional; existing receipts remain valid. --- ...funding-receipt-fx-refunds-txid-privacy.md | 15 +++-- SCHEMAS.md | 14 +++++ lexicons/org/hypercerts/funding/receipt.json | 57 +++++++++++++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/.changeset/funding-receipt-fx-refunds-txid-privacy.md b/.changeset/funding-receipt-fx-refunds-txid-privacy.md index 2ae2c956..5a25254f 100644 --- a/.changeset/funding-receipt-fx-refunds-txid-privacy.md +++ b/.changeset/funding-receipt-fx-refunds-txid-privacy.md @@ -2,10 +2,15 @@ "@hypercerts-org/lexicon": minor --- -Funding receipt: add `fx` (foreign-exchange context) and `refundOf` (immutable refund representation), plus `transactionId` privacy guidance. +Funding receipt: reconciliation and completeness fields — `fx`, `fees`/`netAmount`, `refundOf`, `via`, `settledAt` — plus `transactionId` privacy guidance. -**Non-breaking:** both new fields are optional; existing receipts remain valid. +**Non-breaking:** all new fields are optional; existing receipts remain valid. -- **`fx`** (new `#fxInfo` object: required `originalAmount`/`originalCurrency`, optional `rate`/`rateSource`/`rateTimestamp`) stamps the payment's original denomination and conversion rate at payment time. Without it, receipts for the same payment written by different parties in different currencies never reconcile — cross-receipt math diverges as market rates move, and amount/currency-based payment clustering can't match them. Writers of related receipts copy the block verbatim so consumers compare deterministically. -- **`refundOf`** (strongRef) marks a receipt as a partial or whole refund of the referenced receipt. Receipts are immutable once published — attestations bind to their content — so refunds are new receipts, never edits: the refunded amount in the original denomination (copying the original's `fx` so partial refunds compute at the original rate, without forex drift), `from`/`to` mirrored, one receipt per partial refund. Aggregating consumers net refund receipts against the receipt they reference. -- **`transactionId`** description now carries privacy guidance for public repositories: prefer opaque processor IDs or internal references, avoid bank references embedding account details, and note that an onchain transaction hash publicly reveals the sending wallet — significant when the sender was deliberately kept anonymous by omitting `from`. +Informed by the payment models of Stripe (BalanceTransaction gross/fee/net, available_on), Wise (source/target amounts, rate), PayPal (gross/fee/net, platform_fees) and ISO 20022 pacs.008 (instructed vs settlement amount, settlement date, intermediary agents): + +- **`fx`** (new `#fxInfo` object: required `originalAmount`/`originalCurrency`, optional `rate`/`rateSource`/`rateTimestamp`) stamps the payment's original denomination and conversion rate at payment time, so receipts for one payment written by different parties in different currencies reconcile deterministically instead of re-deriving rates that drift. +- **`fees`** (array of new `#fee`: `type` with knownValues [processor, platform, network, fx, tax, other], `label`, required `amount`) and **`netAmount`** record the universal gross/fee/net triple: `amount` stays the gross the sender paid, `netAmount` is what the recipient actually received. Without them, a sender's "$100" and a recipient's "$96.80" receipt for the same payment can never reconcile or cluster. No charge-bearer field: a receipt is retrospective, the amounts already encode who bore what. +- **`refundOf`** (strongRef) marks a receipt as a partial or whole refund of the referenced receipt. Receipts are immutable once attested, so refunds are new receipts, never edits; copying the original's `fx` computes partial refunds at the original rate without forex drift; multiple partial refunds are multiple receipts, netted by consumers. +- **`via`** (same party union as `from`/`to`) names the intermediary that facilitated the payment (funding platform, payment servicer, broker) in the record itself, rather than leaving it implied by receipt authorship. +- **`settledAt`** records when funds actually settled to the recipient — may differ from `occurredAt` by days on delayed rails such as bank transfers. +- **`transactionId`** description now carries privacy guidance for public repositories: prefer opaque processor IDs or internal references, avoid bank references embedding account details, and note an onchain transaction hash publicly reveals the sending wallet — significant when `from` was omitted for sender anonymity. diff --git a/SCHEMAS.md b/SCHEMAS.md index 7ab0cad5..bf75368e 100644 --- a/SCHEMAS.md +++ b/SCHEMAS.md @@ -276,8 +276,11 @@ Overall score for an evaluation on a numeric scale. | ---------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | | `from` | `union` | ❌ | The sender of the funds (a free-text string, an account DID, or a strong reference to a record). Optional — omit to represent anonymity. | | | `to` | `union` | ✅ | The recipient of the funds (a free-text string, an account DID, or a strong reference to a record). | | +| `via` | `union` | ❌ | Optional intermediary that facilitated or processed the payment (funding platform, payment servicer, broker) — named in the record rather than merely implied by who authored the receipt. Distinct from paymentNetwork (the scheme or chain) and from the record author; lets consumers group receipts by facilitator regardless of author. | | | `amount` | `string` | ✅ | Amount of funding received as a numeric string (e.g. '1000.50'). | maxLength: 50 | | `currency` | `string` | ✅ | Currency of the payment (e.g. EUR, USD, ETH). | maxLength: 10 | +| `netAmount` | `string` | ❌ | Amount the recipient actually received after all fees, as a numeric string in the receipt's currency. When present, amount is the gross amount the sender paid, and amount - sum(fees[].amount) ≈ netAmount. Recording both sides prevents honest receipts for one payment from disagreeing: the sender attests the gross, the recipient the net. | maxLength: 50 | +| `fees` | `ref[]` | ❌ | Itemized deductions between the gross amount and netAmount, in the receipt's currency (e.g. platform commission, processor fee, network/gas cost). A receipt is retrospective, so no charge-bearer field is needed — the amounts already encode who bore what: a sender covering fees simply raises the gross above the intended contribution. | | | `fx` | `ref` | ❌ | Foreign-exchange context binding a receipt's amount/currency to the payment's original denomination at a point in time. | | | `paymentRail` | `string` | ❌ | How the funds were transferred (e.g. bank_transfer, credit_card, onchain, cash, check, payment_processor). | maxLength: 50 | | `paymentNetwork` | `string` | ❌ | Optional network within the payment rail (e.g. arbitrum, ethereum, sepa, visa, paypal). | maxLength: 50 | @@ -286,6 +289,7 @@ Overall score for an evaluation on a numeric scale. | `refundOf` | `ref` | ❌ | Optional strong reference marking this receipt as a refund — partial or whole — of the referenced receipt. Receipts are immutable once published (attestations bind to their content), so refunds are represented as new receipts rather than edits: amount/currency carry the refunded amount in the same denomination as the original (copy the original's fx block so partial refunds are computed at the original rate, without forex drift); from/to mirror the original reversed (when the original sender was anonymous, use the #text party for the refund's recipient). Multiple partial refunds are multiple refund receipts. Consumers aggregating funding should net refund receipts against the receipt they reference. | | | `notes` | `string` | ❌ | Optional notes or additional context for this funding receipt. | maxLength: 500 | | `occurredAt` | `string` | ❌ | Timestamp when the payment occurred. | | +| `settledAt` | `string` | ❌ | When the funds actually settled to the recipient (e.g. a processor's funds-available date, an interbank settlement date, or a block timestamp). May differ from occurredAt by days on delayed rails such as bank transfers. | | | `createdAt` | `string` | ✅ | Client-declared timestamp when this receipt record was created. | | | `signatures` | `ref` | ❌ | Optional cryptographic signatures attesting to this record's content. | | @@ -311,6 +315,16 @@ Foreign-exchange context binding a receipt's amount/currency to the payment's or | `rateSource` | `string` | ❌ | Where the rate came from (e.g. stripe, ecb, coingecko). | maxLength: 50 | | `rateTimestamp` | `string` | ❌ | When the rate was captured; usually the payment time. | | +##### `org.hypercerts.funding.receipt#fee` + +A single fee deducted from a payment. + +| Property | Type | Required | Description | Comments | +| -------- | -------- | -------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | +| `type` | `string` | ❌ | Category of the fee. knownValues is an open, advisory vocabulary. | maxLength: 30, Known values: `processor`, `platform`, `network`, `fx`, `tax`, `other` | +| `label` | `string` | ❌ | Human-readable description (e.g. 'Stripe processing fee', 'Ma Earth platform fee', 'gas'). | maxLength: 100 | +| `amount` | `string` | ✅ | Fee amount as a numeric string, in the receipt's currency. | maxLength: 50 | + --- ### `org.hypercerts.workscope.cel` diff --git a/lexicons/org/hypercerts/funding/receipt.json b/lexicons/org/hypercerts/funding/receipt.json index 312db22c..785eb800 100644 --- a/lexicons/org/hypercerts/funding/receipt.json +++ b/lexicons/org/hypercerts/funding/receipt.json @@ -28,6 +28,15 @@ "com.atproto.repo.strongRef" ] }, + "via": { + "type": "union", + "description": "Optional intermediary that facilitated or processed the payment (funding platform, payment servicer, broker) — named in the record rather than merely implied by who authored the receipt. Distinct from paymentNetwork (the scheme or chain) and from the record author; lets consumers group receipts by facilitator regardless of author.", + "refs": [ + "#text", + "app.certified.defs#did", + "com.atproto.repo.strongRef" + ] + }, "amount": { "type": "string", "description": "Amount of funding received as a numeric string (e.g. '1000.50').", @@ -38,6 +47,19 @@ "description": "Currency of the payment (e.g. EUR, USD, ETH).", "maxLength": 10 }, + "netAmount": { + "type": "string", + "description": "Amount the recipient actually received after all fees, as a numeric string in the receipt's currency. When present, amount is the gross amount the sender paid, and amount - sum(fees[].amount) ≈ netAmount. Recording both sides prevents honest receipts for one payment from disagreeing: the sender attests the gross, the recipient the net.", + "maxLength": 50 + }, + "fees": { + "type": "array", + "description": "Itemized deductions between the gross amount and netAmount, in the receipt's currency (e.g. platform commission, processor fee, network/gas cost). A receipt is retrospective, so no charge-bearer field is needed — the amounts already encode who bore what: a sender covering fees simply raises the gross above the intended contribution.", + "items": { + "type": "ref", + "ref": "#fee" + } + }, "fx": { "type": "ref", "ref": "#fxInfo", @@ -78,6 +100,11 @@ "format": "datetime", "description": "Timestamp when the payment occurred." }, + "settledAt": { + "type": "string", + "format": "datetime", + "description": "When the funds actually settled to the recipient (e.g. a processor's funds-available date, an interbank settlement date, or a block timestamp). May differ from occurredAt by days on delayed rails such as bank transfers." + }, "createdAt": { "type": "string", "format": "datetime", @@ -134,6 +161,36 @@ "description": "When the rate was captured; usually the payment time." } } + }, + "fee": { + "type": "object", + "description": "A single fee deducted from a payment.", + "required": ["amount"], + "properties": { + "type": { + "type": "string", + "description": "Category of the fee. knownValues is an open, advisory vocabulary.", + "knownValues": [ + "processor", + "platform", + "network", + "fx", + "tax", + "other" + ], + "maxLength": 30 + }, + "label": { + "type": "string", + "description": "Human-readable description (e.g. 'Stripe processing fee', 'Ma Earth platform fee', 'gas').", + "maxLength": 100 + }, + "amount": { + "type": "string", + "description": "Fee amount as a numeric string, in the receipt's currency.", + "maxLength": 50 + } + } } } }