Skip to content

0.6.0

Choose a tag to compare

@AlexanderBuzz AlexanderBuzz released this 15 Sep 07:58
· 46 commits to master since this release
76f9117

Partial payments add up. The payment-status contract from 0.5.0 tells the customer "X received, Y still outstanding" — this release makes sending Y actually settle the order. Purely additive: one new class, one new method, one deprecation, no port, signature or schema change. Adapters move with a one-line method swap.

The loop this closes (#6)

Order over 1.0 XRP, customer sends 0.3. The page says "0.3 received, send 0.7". They send 0.7. The core judged only the newest transaction on the tag, so the page then said "0.7 received, send 0.3" — and so on, with 1.3 XRP on the ledger and the order never paid. The only way to settle was a single transaction over the full amount, which on top of a partial payment is a double payment.

Until 0.5.0 this was a logged edge case. With the status contract it became an instruction to the customer, identical in four plugins, that could not be followed. Which transactions fulfill an intent is a core decision per INVARIANTS.md, so the fix is here, not in the adapters.

New: Fulfillment and SyncService::findFulfillmentFor()

findFulfillmentFor(PaymentIntent): ?Fulfillment returns every transaction on the tag in the quoted asset, newest first, together with their summed delivered amount:

  • "Quoted asset" is the rule SettlementPolicy::isWrongAsset() already states, used as a filter. For a native asset every native amount on the tag counts; for an issued currency only those with the quoted currency and issuer.
  • A foreign token becomes the fulfillment on its own only when nothing in the quoted asset has arrived — so wrong_asset stays reachable, with the delivered amount visible. As soon as one right payment exists, only those count and the foreign ones are logged.
  • The sum is exact via brick/math and comes back in the record shape: a float for native, a plain decimal string in the quoted envelope for issued. 0.1 + 0.2 reads "0.3", not 0.30000000000000004.
  • Class mismatch, non-payment and the ledger's "unavailable" are skipped and logged exactly as before.

Fulfillment carries transactions, amountPaid, hash() and ctid() of the newest contributing transaction, count(), and applyTo(PaymentIntent) — the one place an adapter gets all three values onto the intent. Schema v1 is unchanged: the intent records the newest contributing hash; the full list is on the Fulfillment and always recoverable via findTransactions().

Deprecated: findTransactionFor()

Behaves exactly as in 0.3–0.5 (its seven tests pass untouched) and is marked @deprecated since 0.6.0. An adapter that has not moved yet is not surprised — it just never adds partial payments up. Removed in 1.0.

What adapters do with this

Replace the three steps findTransactionFor() → getDeliveredAmount() → withFulfillment() with one line:

$fulfilled = $sync->findFulfillmentFor($intent)?->applyTo($intent);

Raise the constraint straight to ^0.6. What a platform does with several contributing hashes — one payment record with the leading hash, or one per transaction — is its own business, like everything after "paid". Order per the handover: PrestaShop first, then Shopware, WooCommerce, Magento.

Notes

INVARIANTS.md, "Settlement", now states the sum rule, the foreign-token rule and the deprecation; "Payment status" notes that amount_paid in partial is the sum. No time filter on the summed transactions: the intent has no creation timestamp, and the tag's uniqueness (random counter start since 0.4) is the guarantee. 154 tests, green on PHP 8.2 / 8.3 / 8.4 plus the lowest-dependency job.