From 520ba15c6212afd2ef9ac1c4d69e89fe6b99f2d8 Mon Sep 17 00:00:00 2001 From: Bitcoin Error Log <18273620+BitcoinErrorLog@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:54:55 +0100 Subject: [PATCH] fix(commerce): vendor the PayPal refund and reversal-cancelled order edges The service records verified PayPal refunds from every state holding a confirmed payment (paypal_refund -> refunded_external) and returns the order to the replaced state when PayPal cancels a reversal (paypal_reversal_cancelled). Vendor only those transitions and mirror them in the client order and return tables. --- src/libs/commerce/contracts.contract.test.ts | 5 +- .../commerce/contracts/state-machines.json | 242 ++++++++++++++++++ src/libs/commerce/state-machines.test.ts | 14 + src/libs/commerce/state-machines.ts | 42 ++- 4 files changed, 290 insertions(+), 13 deletions(-) diff --git a/src/libs/commerce/contracts.contract.test.ts b/src/libs/commerce/contracts.contract.test.ts index cf4613f4d..24fbfd771 100644 --- a/src/libs/commerce/contracts.contract.test.ts +++ b/src/libs/commerce/contracts.contract.test.ts @@ -7,7 +7,10 @@ const ARTIFACT_HASHES = { 'contracts/samples/confirm.json': 'b78586366a46d7061e630f436afdc01779c24a8a9315be62413bfdbcd8977bbd', 'contracts/samples/resolve.json': '8a7a03c2713b61b08914cd50fa146307753f5175d49b87918ee11a58f568f6af', 'contracts/samples/projections.json': 'cf14b6ab8c83ee8d6e289c74610db5f3b409b2f5dfd84fde8b91137f1e1cfb99', - 'contracts/state-machines.json': '4996d49cb76e89a6fe5c5c62b6ed5f65dc584bdb612cea9a9012447f2a7d1491', + // Service c37481c plus only the `paypal_refund` / `paypal_reversal_cancelled` + // transitions of pubky-marketplace-service `cursor/paypal-refund-ipn-95c1` + // (4ba877cd). A full re-vendor also brings the offer `converted` state. + 'contracts/state-machines.json': '04f41c9fe35e12e181b39d216586c533964b7717310aa1b4aeefc1b5fa6d3077', } as const; describe('vendored marketplace contracts', () => { diff --git a/src/libs/commerce/contracts/state-machines.json b/src/libs/commerce/contracts/state-machines.json index 9bbd3f45b..24f2db71f 100644 --- a/src/libs/commerce/contracts/state-machines.json +++ b/src/libs/commerce/contracts/state-machines.json @@ -558,6 +558,10 @@ { "trigger": "command", "name": "refund.record_external" + }, + { + "trigger": "server", + "name": "paypal_refund" } ] }, @@ -568,6 +572,190 @@ { "trigger": "command", "name": "refund.record_external" + }, + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "paid", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "ready_for_pickup", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "shipped", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "delivered", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "completed", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "cancel_requested", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "return_requested", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "return_approved", + "to": "refunded_external", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "refunded_external", + "to": "paid", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "ready_for_pickup", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "shipped", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "delivered", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "completed", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "cancel_requested", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "cancelled", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "return_requested", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "return_approved", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded_external", + "to": "return_received", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" } ] }, @@ -760,6 +948,60 @@ { "trigger": "command", "name": "refund.record_external" + }, + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "requested", + "to": "refunded", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "approved", + "to": "refunded", + "via": [ + { + "trigger": "server", + "name": "paypal_refund" + } + ] + }, + { + "from": "refunded", + "to": "requested", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded", + "to": "approved", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" + } + ] + }, + { + "from": "refunded", + "to": "received", + "via": [ + { + "trigger": "server", + "name": "paypal_reversal_cancelled" } ] } diff --git a/src/libs/commerce/state-machines.test.ts b/src/libs/commerce/state-machines.test.ts index 2571fc407..217c25dce 100644 --- a/src/libs/commerce/state-machines.test.ts +++ b/src/libs/commerce/state-machines.test.ts @@ -148,6 +148,18 @@ describe('order state machine', () => { ['return_requested', 'return_approved'], ['return_approved', 'return_received'], ['return_received', 'refunded_external'], + // A verified PayPal refund reaching the order total (`paypal_refund`). + ['paid', 'refunded_external'], + ['ready_for_pickup', 'refunded_external'], + ['shipped', 'refunded_external'], + ['delivered', 'refunded_external'], + ['completed', 'refunded_external'], + ['cancel_requested', 'refunded_external'], + ['return_requested', 'refunded_external'], + ['return_approved', 'refunded_external'], + // PayPal cancelled the reversal (`paypal_reversal_cancelled`). + ['refunded_external', 'shipped'], + ['refunded_external', 'return_received'], ])('allows %s -> %s', (from, to) => { expect(canTransitionOrder(from, to)).toBe(true); }); @@ -162,6 +174,8 @@ describe('order state machine', () => { ['completed', 'closed'], ['closed', 'return_requested'], ['refunded_external', 'closed'], + ['pending_payment', 'refunded_external'], + ['refunded_external', 'pending_payment'], ])('rejects %s -> %s', (from, to) => { expect(canTransitionOrder(from, to)).toBe(false); }); diff --git a/src/libs/commerce/state-machines.ts b/src/libs/commerce/state-machines.ts index 017b55984..bdb7e8ce4 100644 --- a/src/libs/commerce/state-machines.ts +++ b/src/libs/commerce/state-machines.ts @@ -82,29 +82,47 @@ export const paymentTransitions = { * withdrawal) move `paid`/`ready_for_pickup` straight to `cancelled` through * `order.cancel_request`, which also gains the ordinary `ready_for_pickup -> * cancel_requested` edge. Shipped orders behave exactly as before. + * + * `paypal_refund` is the service's server trigger for a verified PayPal refund + * or reversal whose recorded refunds reach the order total: every state holding + * a confirmed payment (including an open cancel or return request) reaches + * `refunded_external`. `paypal_reversal_cancelled` returns the order to the + * state a reversal replaced when PayPal cancels that reversal. No client + * command drives these edges; a partial refund leaves the state unchanged. */ export const orderTransitions = { pending_payment: ['paid', 'cancelled'], - paid: ['shipped', 'ready_for_pickup', 'delivered', 'cancel_requested', 'cancelled'], - ready_for_pickup: ['delivered', 'cancel_requested', 'cancelled'], + paid: ['shipped', 'ready_for_pickup', 'delivered', 'cancel_requested', 'cancelled', 'refunded_external'], + ready_for_pickup: ['delivered', 'cancel_requested', 'cancelled', 'refunded_external'], processing: ['shipped', 'cancel_requested'], - shipped: ['delivered'], - delivered: ['return_requested', 'completed'], - completed: ['return_requested'], - cancel_requested: ['cancelled'], + shipped: ['delivered', 'refunded_external'], + delivered: ['return_requested', 'completed', 'refunded_external'], + completed: ['return_requested', 'refunded_external'], + cancel_requested: ['cancelled', 'refunded_external'], cancelled: ['paid', 'refunded_external'], - return_requested: ['return_approved'], - return_approved: ['return_received'], + return_requested: ['return_approved', 'refunded_external'], + return_approved: ['return_received', 'refunded_external'], return_received: ['refunded_external'], - refunded_external: [], + refunded_external: [ + 'paid', + 'ready_for_pickup', + 'shipped', + 'delivered', + 'completed', + 'cancel_requested', + 'cancelled', + 'return_requested', + 'return_approved', + 'return_received', + ], closed: [], } as const satisfies TransitionMap; export const returnTransitions = { - requested: ['approved'], - approved: ['received'], + requested: ['approved', 'refunded'], + approved: ['received', 'refunded'], received: ['refunded'], - refunded: [], + refunded: ['requested', 'approved', 'received'], } as const satisfies TransitionMap; export const dropTransitions = {