Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/libs/commerce/contracts.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
242 changes: 242 additions & 0 deletions src/libs/commerce/contracts/state-machines.json
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,10 @@
{
"trigger": "command",
"name": "refund.record_external"
},
{
"trigger": "server",
"name": "paypal_refund"
}
]
},
Expand All @@ -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"
}
]
},
Expand Down Expand Up @@ -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"
}
]
}
Expand Down
14 changes: 14 additions & 0 deletions src/libs/commerce/state-machines.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand All @@ -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);
});
Expand Down
42 changes: 30 additions & 12 deletions src/libs/commerce/state-machines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrderState>;

export const returnTransitions = {
requested: ['approved'],
approved: ['received'],
requested: ['approved', 'refunded'],
approved: ['received', 'refunded'],
received: ['refunded'],
refunded: [],
refunded: ['requested', 'approved', 'received'],
} as const satisfies TransitionMap<ReturnState>;

export const dropTransitions = {
Expand Down
Loading