@sixtyninecommerce/admin 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -65,7 +65,7 @@ export type { Metafield, MetafieldAttachment, MetafieldDefinition, MetafieldDefi
65
65
  export { METAFIELD_OWNER_TYPES, METAFIELD_SCALAR_TYPES } from './metafields/index.js';
66
66
  export type { OrderSettlement, OrdersModule } from './orders/index.js';
67
67
  export type { SurchargeCall, SurchargedOrder, SurchargeInput, SurchargesModule, } from './orders/surcharges.js';
68
- export type { OrderPaymentPosition, PaidOrder, PaymentsModule, ProviderConnection, ProviderCredentialPath, ProviderOnboardingStart, ProviderReadiness, ProvidersModule, ProviderTerminal, ProviderTerminalsModule, ProvisionTerminalInput, ReadinessOptions, RecordPaymentInput, } from './payments/index.js';
68
+ export type { IssuedRefund, OrderPaymentPosition, OrderRefundPosition, PaidOrder, PaymentsModule, ProviderConnection, ProviderCredentialPath, ProviderOnboardingStart, ProviderReadiness, ProvidersModule, ProviderTerminal, ProviderTerminalsModule, ProvisionTerminalInput, ReadinessOptions, RecordPaymentInput, RefundablePayment, RefundPaymentInput, } from './payments/index.js';
69
69
  export type { AppKey, PlatformAppsModule, RegisterAppResult, RegisteredApp, } from './platform/apps.js';
70
70
  export { requireHumanSession } from './platform/guard.js';
71
71
  export type { PlatformModule, ProvisionProfile, ProvisionRun, ProvisionRunStatus, ProvisionStep, ProvisionStepStatus, ProvisionStoreInput, ProvisionStoreResult, } from './platform/provision-store.js';
@@ -51,6 +51,92 @@ export interface RecordPaymentInput {
51
51
  */
52
52
  metadata?: Record<string, unknown>;
53
53
  }
54
+ /**
55
+ * One payment's contribution to an order's refund position.
56
+ */
57
+ export interface RefundablePayment {
58
+ paymentId: string;
59
+ /** Vendure's payment state. */
60
+ state: string;
61
+ /** The payment method code, or null when the payment carries no label. */
62
+ method: string | null;
63
+ /** Gross amount taken, minor units. */
64
+ amount: number;
65
+ /** Non-failed refunds already recorded against it. A failed refund never left. */
66
+ refunded: number;
67
+ /** `amount - refunded` when this payment took money, floored at zero; else zero. */
68
+ refundable: number;
69
+ /**
70
+ * Whether this payment took money at all.
71
+ *
72
+ * True for a settled payment and for an authorized one on a real gateway — a
73
+ * card sale at a terminal rests authorized until the webhook settles it, and
74
+ * that hold is real money. False for an authorized payment on an offline or
75
+ * net-terms method, where authorized means the money has NOT arrived.
76
+ */
77
+ tookMoney: boolean;
78
+ }
79
+ /**
80
+ * What an order may still hand back, in minor units of its own currency.
81
+ *
82
+ * A different question from {@link OrderPaymentPosition}, and the two disagree
83
+ * on purpose: that one nets refunds against every authorization because it
84
+ * answers "what is this order covered for", while this one counts only money
85
+ * that was actually TAKEN, because that is all a refund can return.
86
+ */
87
+ export interface OrderRefundPosition {
88
+ /** The order's currency. Every amount here is in its minor units. */
89
+ currencyCode: string;
90
+ /** Money already given back out of money that was taken. */
91
+ refunded: number;
92
+ /**
93
+ * What may still be handed back for this order: the sum of every payment's
94
+ * own remaining headroom, each floored at zero.
95
+ *
96
+ * There is deliberately no credit-note deduction. A credit note that moved
97
+ * money already has its refund on the payment; one that moved none has
98
+ * nothing to deduct.
99
+ */
100
+ refundable: number;
101
+ /** Every payment on the order, NEWEST FIRST, with its own headroom. */
102
+ perPayment: RefundablePayment[];
103
+ }
104
+ /** Money handed back to a customer. */
105
+ export interface RefundPaymentInput {
106
+ orderId: string;
107
+ /** Minor units of the order's currency. Greater than zero. */
108
+ amount: number;
109
+ /**
110
+ * The payment to return the money to.
111
+ *
112
+ * Omit it and the platform allocates across the order's paying payments
113
+ * NEWEST FIRST: it picks the most recent one whose remaining headroom can
114
+ * carry the WHOLE amount. A part-paid order settles its remainder last, so
115
+ * that is the money a merchant means by "give some of it back".
116
+ *
117
+ * **One call refunds against ONE payment.** When the order can return the
118
+ * amount but no single payment can, the call is refused with
119
+ * `REFUND_EXCEEDS_PAYMENT` and `perPayment` shows exactly how to split it —
120
+ * rather than the platform spilling silently onto a payment you did not name,
121
+ * which is what the engine would do.
122
+ */
123
+ paymentId?: string;
124
+ /** Free text stored on the refund. Shown to nobody automatically. */
125
+ reason?: string;
126
+ }
127
+ /** A refund, as this surface projects it. */
128
+ export interface IssuedRefund {
129
+ id: string;
130
+ /** Minor units. */
131
+ total: number;
132
+ /** `Pending`, `Settled` or `Failed`. */
133
+ state: string;
134
+ /** The payment method the refund was booked against. */
135
+ method: string | null;
136
+ /** The gateway's own receipt, when it gave one. */
137
+ transactionId: string | null;
138
+ reason: string | null;
139
+ }
54
140
  /** The order, as much of it as this surface projects. */
55
141
  export interface PaidOrder {
56
142
  id: string;
@@ -96,8 +182,63 @@ export interface PaymentsModule {
96
182
  record(orderId: string, input: RecordPaymentInput, options?: Call): Promise<PaidOrder>;
97
183
  /** Read an order's payment position. */
98
184
  position(orderId: string, options?: Call): Promise<OrderPaymentPosition>;
185
+ /**
186
+ * Hand money back to a customer, never more than the order took.
187
+ *
188
+ * The platform's own refund ceiling, and the one the four surfaces that issue
189
+ * refunds — returns settlement, credit notes, the till and the order importer
190
+ * — are measured against. Refuses `REFUND_EXCEEDS_SETTLED` when the amount is
191
+ * more than the whole ORDER can return, which is the check no per-payment
192
+ * arithmetic can make: another surface may have refunded the rest of it ten
193
+ * seconds ago.
194
+ *
195
+ * `options.idempotencyKey` is honoured — the Admin API takes it as a mutation
196
+ * argument — so a retry under the same key returns the FIRST call's refund
197
+ * instead of raising a second one. Use a fresh key per intended refund.
198
+ *
199
+ * **`IDEMPOTENCY_OUTCOME_UNKNOWN` is neither success nor refusal.** The engine
200
+ * calls the payment gateway from inside the transaction that writes the refund
201
+ * row, so a failure after that call leaves an outcome nobody can read off the
202
+ * database: the money may be on its way back with no row to show for it. You
203
+ * get this code on the FIRST call when that happens, and on every repeat under
204
+ * the same key — the key is spent rather than released, so the refund never
205
+ * runs again. Do NOT treat it as `REFUND_REFUSED`: read
206
+ * `payments.refundPosition(orderId)`, reconcile against the gateway, and only
207
+ * then decide. If the money did not move, issue a new request under a NEW key.
208
+ *
209
+ * (The server also attaches the position to that error, but `PlatformError`
210
+ * carries only `code`, `message`, `details` and `traceId`, so an SDK caller
211
+ * reads it with `refundPosition(orderId)` rather than off the error.)
212
+ *
213
+ * Only money that was TAKEN can come back: a settled payment, or an
214
+ * authorized one on a real gateway. An offline or net-terms authorization
215
+ * took nothing and is refused with `REFUND_PAYMENT_HOLDS_NO_MONEY`.
216
+ *
217
+ * **The refund you get back is one that moved money.** On an OFFLINE method —
218
+ * bank transfer, invoice, cash, an imported order's historical tender — it
219
+ * comes back `Settled`, because there the money moves by hand and recording
220
+ * it is the settlement. On a live gateway it comes back `Pending` and settles
221
+ * when the gateway calls back. A method whose handler cannot be resolved at
222
+ * all, because it is disabled or no longer on this store, is refused
223
+ * `REFUND_METHOD_UNAVAILABLE` before anything is created, with the payment and
224
+ * the method in `extensions.details`.
225
+ *
226
+ * Other refusals: `REFUND_EXCEEDS_PAYMENT` (the order can return this much,
227
+ * but no single payment can — split it, using `perPayment`),
228
+ * `REFUND_PAYMENT_NOT_FOUND`, `REFUND_AMOUNT_NOT_POSITIVE`,
229
+ * `REFUND_ORDER_NOT_FOUND`, and `REFUND_REFUSED` when the money was there and
230
+ * the engine or the gateway said no anyway. Every refusal raised once the
231
+ * order has been read carries that order's position in
232
+ * `extensions.refundPosition`, so a till can correct itself without a second
233
+ * round trip.
234
+ */
235
+ refund(input: RefundPaymentInput, options?: Call): Promise<IssuedRefund>;
236
+ /** Read what an order may still hand back. */
237
+ refundPosition(orderId: string, options?: Call): Promise<OrderRefundPosition>;
99
238
  }
100
239
  export declare const RECORD_ORDER_PAYMENT_MUTATION = "\n mutation RecordOrderPayment($orderId: ID!, $input: RecordPaymentInput!, $idempotencyKey: String) {\n recordOrderPayment(orderId: $orderId, input: $input, idempotencyKey: $idempotencyKey) {\n \n id\n code\n state\n currencyCode\n total\n totalWithTax\n\n }\n }\n";
101
240
  export declare const ORDER_PAYMENT_POSITION_QUERY = "\n query OrderPaymentPosition($orderId: ID!) {\n orderPaymentPosition(orderId: $orderId) {\n \n total\n covered\n settled\n authorized\n refunded\n outstanding\n overpaid\n availableToCharge\n\n }\n }\n";
241
+ export declare const ORDER_REFUND_POSITION_QUERY = "\n query OrderRefundPosition($orderId: ID!) {\n orderRefundPosition(orderId: $orderId) {\n \n currencyCode\n refunded\n refundable\n perPayment {\n paymentId\n state\n method\n amount\n refunded\n refundable\n tookMoney\n }\n\n }\n }\n";
242
+ export declare const REFUND_ORDER_PAYMENT_MUTATION = "\n mutation RefundOrderPayment($input: RefundPaymentInput!, $idempotencyKey: String) {\n refundOrderPayment(input: $input, idempotencyKey: $idempotencyKey) {\n id\n total\n state\n method\n transactionId\n reason\n }\n }\n";
102
243
  export declare function createPaymentsModule(transport: Transport): PaymentsModule;
103
244
  export type { ProviderConnection, ProviderCredentialPath, ProviderOnboardingStart, ProviderReadiness, ProvidersModule, ProviderTerminal, ProviderTerminalsModule, ProvisionTerminalInput, ReadinessOptions, } from './providers.js';
@@ -31,6 +31,39 @@ export const ORDER_PAYMENT_POSITION_QUERY = /* GraphQL */ `
31
31
  }
32
32
  }
33
33
  `;
34
+ const REFUND_POSITION_FIELDS = /* GraphQL */ `
35
+ currencyCode
36
+ refunded
37
+ refundable
38
+ perPayment {
39
+ paymentId
40
+ state
41
+ method
42
+ amount
43
+ refunded
44
+ refundable
45
+ tookMoney
46
+ }
47
+ `;
48
+ export const ORDER_REFUND_POSITION_QUERY = /* GraphQL */ `
49
+ query OrderRefundPosition($orderId: ID!) {
50
+ orderRefundPosition(orderId: $orderId) {
51
+ ${REFUND_POSITION_FIELDS}
52
+ }
53
+ }
54
+ `;
55
+ export const REFUND_ORDER_PAYMENT_MUTATION = /* GraphQL */ `
56
+ mutation RefundOrderPayment($input: RefundPaymentInput!, $idempotencyKey: String) {
57
+ refundOrderPayment(input: $input, idempotencyKey: $idempotencyKey) {
58
+ id
59
+ total
60
+ state
61
+ method
62
+ transactionId
63
+ reason
64
+ }
65
+ }
66
+ `;
34
67
  export function createPaymentsModule(transport) {
35
68
  return {
36
69
  providers: createProvidersModule(transport),
@@ -51,5 +84,20 @@ export function createPaymentsModule(transport) {
51
84
  const data = await transport.graphql({ api: 'admin', query: ORDER_PAYMENT_POSITION_QUERY, variables: { orderId } }, options);
52
85
  return data.orderPaymentPosition;
53
86
  },
87
+ async refund(input, options) {
88
+ const data = await transport.graphql({
89
+ api: 'admin',
90
+ query: REFUND_ORDER_PAYMENT_MUTATION,
91
+ // Forwarded as a VARIABLE, not a header: the Admin API has no header
92
+ // for it. `undefined`, never an explicit null — `JSON.stringify` drops
93
+ // it, so an omitted key stays off the wire entirely.
94
+ variables: { input, idempotencyKey: options?.idempotencyKey },
95
+ }, options);
96
+ return data.refundOrderPayment;
97
+ },
98
+ async refundPosition(orderId, options) {
99
+ const data = await transport.graphql({ api: 'admin', query: ORDER_REFUND_POSITION_QUERY, variables: { orderId } }, options);
100
+ return data.orderRefundPosition;
101
+ },
54
102
  };
55
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sixtyninecommerce/admin",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Back-office SDK for sixtynine-commerce: every capability of the platform as a method",
5
5
  "homepage": "https://github.com/SIXTYNINE-digital/sixtynine-commerce-engine/tree/main/packages/admin-client#readme",
6
6
  "repository": {