@sixtyninecommerce/admin 0.6.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/README.md +359 -2
- package/dist/apps/assertion.d.ts +30 -3
- package/dist/apps/assertion.js +40 -3
- package/dist/apps/config.d.ts +39 -0
- package/dist/apps/config.js +22 -0
- package/dist/apps/credential.d.ts +5 -0
- package/dist/apps/credential.js +55 -4
- package/dist/apps/index.d.ts +11 -0
- package/dist/client.d.ts +54 -1
- package/dist/client.js +34 -1
- package/dist/custom-objects/index.d.ts +66 -0
- package/dist/custom-objects/index.js +48 -0
- package/dist/customers/index.d.ts +225 -0
- package/dist/customers/index.js +279 -0
- package/dist/documents/index.d.ts +432 -0
- package/dist/documents/index.js +289 -0
- package/dist/index.d.ts +34 -2
- package/dist/index.js +16 -1
- package/dist/jobs/index.d.ts +143 -0
- package/dist/jobs/index.js +132 -0
- package/dist/metafields/index.d.ts +291 -0
- package/dist/metafields/index.js +141 -0
- package/dist/orders/index.d.ts +106 -0
- package/dist/orders/index.js +115 -0
- package/dist/orders/surcharges.d.ts +121 -0
- package/dist/orders/surcharges.js +110 -0
- package/dist/payments/index.d.ts +244 -0
- package/dist/payments/index.js +103 -0
- package/dist/payments/providers.d.ts +281 -0
- package/dist/payments/providers.js +205 -0
- package/dist/platform/provision-store.d.ts +9 -26
- package/dist/platform/provision-store.js +8 -66
- package/dist/tax/index.d.ts +128 -0
- package/dist/tax/index.js +101 -0
- package/dist/tenant/capabilities.d.ts +15 -0
- package/dist/types.d.ts +20 -3
- package/dist/uploads/index.d.ts +146 -0
- package/dist/uploads/index.js +72 -0
- package/package.json +3 -3
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A surcharge: an adjustment on a PLACED order that is not a line — a delivery
|
|
5
|
+
* charge, a negotiated discount, a handling fee.
|
|
6
|
+
*
|
|
7
|
+
* Money is integer MINOR UNITS of the order's currency, and `price` is NEGATIVE
|
|
8
|
+
* for a discount. `priceIncludesTax` says which basis the amount is stated in;
|
|
9
|
+
* there is no default, because guessing it is a 21% error.
|
|
10
|
+
*/
|
|
11
|
+
export interface SurchargeInput {
|
|
12
|
+
description: string;
|
|
13
|
+
sku?: string | null;
|
|
14
|
+
/** Minor units. Negative for a discount agreed after the order was placed. */
|
|
15
|
+
price: number;
|
|
16
|
+
/** Whether `price` is gross. Required: the engine will not guess a basis. */
|
|
17
|
+
priceIncludesTax: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Percentage (21 for 21%). Omit it for no tax line at all — and never send it
|
|
20
|
+
* together with `taxClass`, which is refused rather than silently resolved
|
|
21
|
+
* one way.
|
|
22
|
+
*/
|
|
23
|
+
taxRate?: number | null;
|
|
24
|
+
/**
|
|
25
|
+
* Label for the tax line. Refused together with `taxClass`: a class-priced
|
|
26
|
+
* line is named by the store's own tax setup — including names that are a
|
|
27
|
+
* policy statement, like `EU Reverse Charge (0%)` — and relabelling one would
|
|
28
|
+
* put on an invoice something the line is not.
|
|
29
|
+
*/
|
|
30
|
+
taxDescription?: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** Options for a surcharge write. */
|
|
33
|
+
export interface SurchargeCall extends Call {
|
|
34
|
+
/**
|
|
35
|
+
* Price the surcharge from one of the store's tax classes (spec §8.6)
|
|
36
|
+
* instead of from an explicit `taxRate`.
|
|
37
|
+
*
|
|
38
|
+
* Resolved PER ORDER, which is the half of the platform's tax-class decision
|
|
39
|
+
* that is not write-time: a surcharge is money the platform composes, with no
|
|
40
|
+
* product variant behind it to have been priced already.
|
|
41
|
+
*
|
|
42
|
+
* **Which country, and which zone — they are two different questions.** The
|
|
43
|
+
* order's shipping COUNTRY picks the class's mapping (that is the store's own
|
|
44
|
+
* "what kind of thing is this" decision, one row per country). The order's
|
|
45
|
+
* tax ZONE picks the rate for the category that mapping names. The two can
|
|
46
|
+
* legitimately differ: a store only moves a country into its own zone by
|
|
47
|
+
* listing it in the tax-exclusive countries setting, so a Belgian order on a
|
|
48
|
+
* store that has not listed BE is still priced in the store's default zone
|
|
49
|
+
* while the BE mapping decides the category. Map a country and configure that
|
|
50
|
+
* zone's rates, or the write is refused rather than silently untaxed.
|
|
51
|
+
*
|
|
52
|
+
* **The order's own tax policy still wins.** A tax-removed order and an EU
|
|
53
|
+
* reverse charge override whatever the class resolved to, because the
|
|
54
|
+
* resolved rate is run through the same tax-line strategy the order's lines
|
|
55
|
+
* go through. The class chooses which rate is looked up, never what the line
|
|
56
|
+
* finally says. A per-line rate imported from an ERP is the one policy that
|
|
57
|
+
* does NOT carry over: it belongs to a line the ERP sent, never to a charge
|
|
58
|
+
* the merchant adds afterwards.
|
|
59
|
+
*
|
|
60
|
+
* Refusals: `TAX_CLASS_NOT_FOUND` (→ `NotFoundError`) for a key this store
|
|
61
|
+
* does not have; `TAX_CLASS_UNMAPPED` (→ `ValidationError`) when the class
|
|
62
|
+
* says nothing about the order's country and has no `*` fallback; and
|
|
63
|
+
* `TAX_CLASS_RATE_MISSING` (→ `ValidationError`) when it is mapped but that
|
|
64
|
+
* category has no rate in the order's zone. Sending it together with
|
|
65
|
+
* `input.taxRate` or `input.taxDescription` is refused as bad input.
|
|
66
|
+
*/
|
|
67
|
+
taxClass?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The order the mutation answers with, as much of it as this surface projects.
|
|
71
|
+
*
|
|
72
|
+
* The totals are re-derived and PERSISTED by every one of these calls, so the
|
|
73
|
+
* numbers here are the order's new state rather than a projection of the
|
|
74
|
+
* request.
|
|
75
|
+
*/
|
|
76
|
+
export interface SurchargedOrder {
|
|
77
|
+
id: string;
|
|
78
|
+
code: string;
|
|
79
|
+
currencyCode: string;
|
|
80
|
+
/** Minor units. */
|
|
81
|
+
total: number;
|
|
82
|
+
totalWithTax: number;
|
|
83
|
+
surcharges: Array<{
|
|
84
|
+
id: string;
|
|
85
|
+
description: string;
|
|
86
|
+
sku: string | null;
|
|
87
|
+
price: number;
|
|
88
|
+
priceWithTax: number;
|
|
89
|
+
taxRate: number;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Surcharges on a placed order.
|
|
94
|
+
*
|
|
95
|
+
* These exist beside Vendure's `modifyOrder`, which hard-requires a refund
|
|
96
|
+
* input for ANY downward price delta — so it cannot express "remove this
|
|
97
|
+
* discount" at all on an order with no payment to refund against. Needs
|
|
98
|
+
* `UpdateOrder`; refused once the order's invoice has been approved.
|
|
99
|
+
*/
|
|
100
|
+
export interface SurchargesModule {
|
|
101
|
+
/** Add a surcharge and re-total the order. */
|
|
102
|
+
add(orderId: string, input: SurchargeInput, options?: SurchargeCall): Promise<SurchargedOrder>;
|
|
103
|
+
/**
|
|
104
|
+
* Replace a surcharge in place and re-total the order. The surcharge keeps
|
|
105
|
+
* its id, so a delivery charge stays linked to the fulfillment that created
|
|
106
|
+
* it, and an omitted `sku` is left alone rather than cleared.
|
|
107
|
+
*
|
|
108
|
+
* TAX IS NOT LIKE `sku`: this is a whole replacement, so omitting BOTH
|
|
109
|
+
* `options.taxClass` and `input.taxRate` writes a surcharge with no tax line
|
|
110
|
+
* at all and lowers the order total. Re-send whichever of the two the
|
|
111
|
+
* surcharge should carry — a `taxClass` is then resolved afresh, so the rate
|
|
112
|
+
* that applies now is the one written.
|
|
113
|
+
*/
|
|
114
|
+
update(orderId: string, surchargeId: string, input: SurchargeInput, options?: SurchargeCall): Promise<SurchargedOrder>;
|
|
115
|
+
/** Remove a surcharge entirely and re-total the order. */
|
|
116
|
+
remove(orderId: string, surchargeId: string, options?: Call): Promise<SurchargedOrder>;
|
|
117
|
+
}
|
|
118
|
+
export declare const ADD_ORDER_SURCHARGE_MUTATION = "\n mutation AddOrderSurcharge(\n $orderId: ID!\n $input: SurchargeInput!\n $taxClass: String\n $idempotencyKey: String\n ) {\n addOrderSurcharge(\n orderId: $orderId\n input: $input\n taxClass: $taxClass\n idempotencyKey: $idempotencyKey\n ) {\n \n id\n code\n currencyCode\n total\n totalWithTax\n surcharges {\n id\n description\n sku\n price\n priceWithTax\n taxRate\n }\n\n }\n }\n";
|
|
119
|
+
export declare const UPDATE_ORDER_SURCHARGE_MUTATION = "\n mutation UpdateOrderSurcharge(\n $orderId: ID!\n $surchargeId: ID!\n $input: SurchargeInput!\n $taxClass: String\n $idempotencyKey: String\n ) {\n updateOrderSurcharge(\n orderId: $orderId\n surchargeId: $surchargeId\n input: $input\n taxClass: $taxClass\n idempotencyKey: $idempotencyKey\n ) {\n \n id\n code\n currencyCode\n total\n totalWithTax\n surcharges {\n id\n description\n sku\n price\n priceWithTax\n taxRate\n }\n\n }\n }\n";
|
|
120
|
+
export declare const REMOVE_ORDER_SURCHARGE_MUTATION = "\n mutation RemoveOrderSurcharge($orderId: ID!, $surchargeId: ID!, $idempotencyKey: String) {\n removeOrderSurcharge(\n orderId: $orderId\n surchargeId: $surchargeId\n idempotencyKey: $idempotencyKey\n ) {\n \n id\n code\n currencyCode\n total\n totalWithTax\n surcharges {\n id\n description\n sku\n price\n priceWithTax\n taxRate\n }\n\n }\n }\n";
|
|
121
|
+
export declare function createSurchargesModule(transport: Transport): SurchargesModule;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const SURCHARGED_ORDER_FIELDS = /* GraphQL */ `
|
|
2
|
+
id
|
|
3
|
+
code
|
|
4
|
+
currencyCode
|
|
5
|
+
total
|
|
6
|
+
totalWithTax
|
|
7
|
+
surcharges {
|
|
8
|
+
id
|
|
9
|
+
description
|
|
10
|
+
sku
|
|
11
|
+
price
|
|
12
|
+
priceWithTax
|
|
13
|
+
taxRate
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
export const ADD_ORDER_SURCHARGE_MUTATION = /* GraphQL */ `
|
|
17
|
+
mutation AddOrderSurcharge(
|
|
18
|
+
$orderId: ID!
|
|
19
|
+
$input: SurchargeInput!
|
|
20
|
+
$taxClass: String
|
|
21
|
+
$idempotencyKey: String
|
|
22
|
+
) {
|
|
23
|
+
addOrderSurcharge(
|
|
24
|
+
orderId: $orderId
|
|
25
|
+
input: $input
|
|
26
|
+
taxClass: $taxClass
|
|
27
|
+
idempotencyKey: $idempotencyKey
|
|
28
|
+
) {
|
|
29
|
+
${SURCHARGED_ORDER_FIELDS}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
export const UPDATE_ORDER_SURCHARGE_MUTATION = /* GraphQL */ `
|
|
34
|
+
mutation UpdateOrderSurcharge(
|
|
35
|
+
$orderId: ID!
|
|
36
|
+
$surchargeId: ID!
|
|
37
|
+
$input: SurchargeInput!
|
|
38
|
+
$taxClass: String
|
|
39
|
+
$idempotencyKey: String
|
|
40
|
+
) {
|
|
41
|
+
updateOrderSurcharge(
|
|
42
|
+
orderId: $orderId
|
|
43
|
+
surchargeId: $surchargeId
|
|
44
|
+
input: $input
|
|
45
|
+
taxClass: $taxClass
|
|
46
|
+
idempotencyKey: $idempotencyKey
|
|
47
|
+
) {
|
|
48
|
+
${SURCHARGED_ORDER_FIELDS}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
export const REMOVE_ORDER_SURCHARGE_MUTATION = /* GraphQL */ `
|
|
53
|
+
mutation RemoveOrderSurcharge($orderId: ID!, $surchargeId: ID!, $idempotencyKey: String) {
|
|
54
|
+
removeOrderSurcharge(
|
|
55
|
+
orderId: $orderId
|
|
56
|
+
surchargeId: $surchargeId
|
|
57
|
+
idempotencyKey: $idempotencyKey
|
|
58
|
+
) {
|
|
59
|
+
${SURCHARGED_ORDER_FIELDS}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
/**
|
|
64
|
+
* `undefined` rather than `null` for an absent tax class: a variable explicitly
|
|
65
|
+
* set to null is still a value the engine reads, and this argument's whole
|
|
66
|
+
* contract is "omit it and the legacy `taxRate` path is untouched".
|
|
67
|
+
*/
|
|
68
|
+
function taxClassVariable(options) {
|
|
69
|
+
const value = options?.taxClass;
|
|
70
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
71
|
+
}
|
|
72
|
+
export function createSurchargesModule(transport) {
|
|
73
|
+
return {
|
|
74
|
+
async add(orderId, input, options) {
|
|
75
|
+
const data = await transport.graphql({
|
|
76
|
+
api: 'admin',
|
|
77
|
+
query: ADD_ORDER_SURCHARGE_MUTATION,
|
|
78
|
+
variables: {
|
|
79
|
+
orderId,
|
|
80
|
+
input,
|
|
81
|
+
taxClass: taxClassVariable(options),
|
|
82
|
+
idempotencyKey: options?.idempotencyKey,
|
|
83
|
+
},
|
|
84
|
+
}, options);
|
|
85
|
+
return data.addOrderSurcharge;
|
|
86
|
+
},
|
|
87
|
+
async update(orderId, surchargeId, input, options) {
|
|
88
|
+
const data = await transport.graphql({
|
|
89
|
+
api: 'admin',
|
|
90
|
+
query: UPDATE_ORDER_SURCHARGE_MUTATION,
|
|
91
|
+
variables: {
|
|
92
|
+
orderId,
|
|
93
|
+
surchargeId,
|
|
94
|
+
input,
|
|
95
|
+
taxClass: taxClassVariable(options),
|
|
96
|
+
idempotencyKey: options?.idempotencyKey,
|
|
97
|
+
},
|
|
98
|
+
}, options);
|
|
99
|
+
return data.updateOrderSurcharge;
|
|
100
|
+
},
|
|
101
|
+
async remove(orderId, surchargeId, options) {
|
|
102
|
+
const data = await transport.graphql({
|
|
103
|
+
api: 'admin',
|
|
104
|
+
query: REMOVE_ORDER_SURCHARGE_MUTATION,
|
|
105
|
+
variables: { orderId, surchargeId, idempotencyKey: options?.idempotencyKey },
|
|
106
|
+
}, options);
|
|
107
|
+
return data.removeOrderSurcharge;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { Call } from '../types.js';
|
|
3
|
+
import { type ProvidersModule } from './providers.js';
|
|
4
|
+
/**
|
|
5
|
+
* What an order has actually been paid, in minor units of its own currency.
|
|
6
|
+
*
|
|
7
|
+
* Derived from the order's payments on every read, never stored — a stored copy
|
|
8
|
+
* is a second source of truth that drifts the first time a refund lands.
|
|
9
|
+
*/
|
|
10
|
+
export interface OrderPaymentPosition {
|
|
11
|
+
/** The order's own gross total, carrying the order's rounding rule. */
|
|
12
|
+
total: number;
|
|
13
|
+
/** Settled plus authorized, less refunds: what is accounted for. */
|
|
14
|
+
covered: number;
|
|
15
|
+
/** Money actually taken, less refunds. This is what `orders.settle` requires. */
|
|
16
|
+
settled: number;
|
|
17
|
+
/** Money a payment handler may take and has not. */
|
|
18
|
+
authorized: number;
|
|
19
|
+
refunded: number;
|
|
20
|
+
/** `total - covered`, floored at zero. */
|
|
21
|
+
outstanding: number;
|
|
22
|
+
/**
|
|
23
|
+
* `covered - total`, floored at zero. Over-payment is refused at the door, so
|
|
24
|
+
* this is non-zero only for imported history, rows predating the guard, or a
|
|
25
|
+
* refund that landed after a second payment.
|
|
26
|
+
*/
|
|
27
|
+
overpaid: number;
|
|
28
|
+
/** What a further payment may take. */
|
|
29
|
+
availableToCharge: number;
|
|
30
|
+
}
|
|
31
|
+
/** Money the merchant HAS, recorded against an order. */
|
|
32
|
+
export interface RecordPaymentInput {
|
|
33
|
+
/**
|
|
34
|
+
* Minor units of the order's currency. Greater than zero, and at most the
|
|
35
|
+
* order's `outstanding` — an over-payment is refused rather than accepted,
|
|
36
|
+
* and the refusal carries the position so a till can correct itself without a
|
|
37
|
+
* second round trip.
|
|
38
|
+
*/
|
|
39
|
+
amount: number;
|
|
40
|
+
/** A payment method code configured on this channel. */
|
|
41
|
+
method: string;
|
|
42
|
+
/**
|
|
43
|
+
* Free-form detail stored on the payment, MINUS two keys the platform writes
|
|
44
|
+
* itself: `attribution` (the vouched-for staff, device and register refs) and
|
|
45
|
+
* `isDeposit`. Sending either is refused with `METADATA_KEY_RESERVED` rather
|
|
46
|
+
* than merged, so no caller can invent a till or a member of staff that every
|
|
47
|
+
* later report would believe.
|
|
48
|
+
*
|
|
49
|
+
* Anything you put under `public` is visible to the storefront — Vendure's
|
|
50
|
+
* Shop API exposes `metadata.public`. The platform never writes there.
|
|
51
|
+
*/
|
|
52
|
+
metadata?: Record<string, unknown>;
|
|
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
|
+
}
|
|
140
|
+
/** The order, as much of it as this surface projects. */
|
|
141
|
+
export interface PaidOrder {
|
|
142
|
+
id: string;
|
|
143
|
+
code: string;
|
|
144
|
+
state: string;
|
|
145
|
+
currencyCode: string;
|
|
146
|
+
total: number;
|
|
147
|
+
totalWithTax: number;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Payments: money against an order (spec §8.2), and the provider accounts
|
|
151
|
+
* it runs through (spec §8.9).
|
|
152
|
+
*
|
|
153
|
+
* `providers` is payment-provider onboarding, terminals and readiness. The
|
|
154
|
+
* rest of this namespace is the money itself.
|
|
155
|
+
*
|
|
156
|
+
* `record` writes money the merchant HAS, which lands the payment `Settled`.
|
|
157
|
+
* There is no payment state to choose: an AUTHORIZATION is a payment method
|
|
158
|
+
* handler's business — the gateway decides whether a card may be charged later
|
|
159
|
+
* — so one cannot be created here. Authorized payments that handlers DO create
|
|
160
|
+
* are counted by `position()` all the same, under `authorized`; only the write
|
|
161
|
+
* side is narrowed.
|
|
162
|
+
*
|
|
163
|
+
* Needs `UpdateOrder` to record, `ReadOrder` to read the position.
|
|
164
|
+
*/
|
|
165
|
+
export interface PaymentsModule {
|
|
166
|
+
/** Provider onboarding, terminals and readiness (spec §8.9). */
|
|
167
|
+
providers: ProvidersModule;
|
|
168
|
+
/**
|
|
169
|
+
* Record part (or all) of what an order owes, and return the order.
|
|
170
|
+
*
|
|
171
|
+
* `options.idempotencyKey` is honoured: the Admin API takes it as a mutation
|
|
172
|
+
* argument, so a retry under the same key returns the first call's order
|
|
173
|
+
* rather than taking the money twice. Use a fresh key per intended payment.
|
|
174
|
+
*
|
|
175
|
+
* Refusals: `AMOUNT_EXCEEDS_OUTSTANDING` (over-payment, with the position
|
|
176
|
+
* attached), `AMOUNT_NOT_POSITIVE`, `ORDER_NOT_RECORDABLE` (the order's state
|
|
177
|
+
* cannot take a payment), `DEPOSITS_NOT_ENABLED` — a PARTIAL payment on a
|
|
178
|
+
* store that has not turned deposits on; paying the whole outstanding balance
|
|
179
|
+
* is not a deposit and always works — `METADATA_KEY_RESERVED`,
|
|
180
|
+
* `PAYMENT_METHOD_NOT_FOUND`, `ORDER_NOT_FOUND`.
|
|
181
|
+
*/
|
|
182
|
+
record(orderId: string, input: RecordPaymentInput, options?: Call): Promise<PaidOrder>;
|
|
183
|
+
/** Read an order's payment position. */
|
|
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>;
|
|
238
|
+
}
|
|
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";
|
|
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";
|
|
243
|
+
export declare function createPaymentsModule(transport: Transport): PaymentsModule;
|
|
244
|
+
export type { ProviderConnection, ProviderCredentialPath, ProviderOnboardingStart, ProviderReadiness, ProvidersModule, ProviderTerminal, ProviderTerminalsModule, ProvisionTerminalInput, ReadinessOptions, } from './providers.js';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createProvidersModule } from './providers.js';
|
|
2
|
+
const PAID_ORDER_FIELDS = /* GraphQL */ `
|
|
3
|
+
id
|
|
4
|
+
code
|
|
5
|
+
state
|
|
6
|
+
currencyCode
|
|
7
|
+
total
|
|
8
|
+
totalWithTax
|
|
9
|
+
`;
|
|
10
|
+
const POSITION_FIELDS = /* GraphQL */ `
|
|
11
|
+
total
|
|
12
|
+
covered
|
|
13
|
+
settled
|
|
14
|
+
authorized
|
|
15
|
+
refunded
|
|
16
|
+
outstanding
|
|
17
|
+
overpaid
|
|
18
|
+
availableToCharge
|
|
19
|
+
`;
|
|
20
|
+
export const RECORD_ORDER_PAYMENT_MUTATION = /* GraphQL */ `
|
|
21
|
+
mutation RecordOrderPayment($orderId: ID!, $input: RecordPaymentInput!, $idempotencyKey: String) {
|
|
22
|
+
recordOrderPayment(orderId: $orderId, input: $input, idempotencyKey: $idempotencyKey) {
|
|
23
|
+
${PAID_ORDER_FIELDS}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
export const ORDER_PAYMENT_POSITION_QUERY = /* GraphQL */ `
|
|
28
|
+
query OrderPaymentPosition($orderId: ID!) {
|
|
29
|
+
orderPaymentPosition(orderId: $orderId) {
|
|
30
|
+
${POSITION_FIELDS}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
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
|
+
`;
|
|
67
|
+
export function createPaymentsModule(transport) {
|
|
68
|
+
return {
|
|
69
|
+
providers: createProvidersModule(transport),
|
|
70
|
+
async record(orderId, input, options) {
|
|
71
|
+
const data = await transport.graphql({
|
|
72
|
+
api: 'admin',
|
|
73
|
+
query: RECORD_ORDER_PAYMENT_MUTATION,
|
|
74
|
+
// Forwarded as a VARIABLE, not a header: the Admin API has no header
|
|
75
|
+
// for it, which is why the mutation takes the key as an argument.
|
|
76
|
+
// `undefined`, never an explicit null — `JSON.stringify` drops it, so
|
|
77
|
+
// an omitted key stays off the wire entirely rather than arriving as a
|
|
78
|
+
// value a future server could read as one.
|
|
79
|
+
variables: { orderId, input, idempotencyKey: options?.idempotencyKey },
|
|
80
|
+
}, options);
|
|
81
|
+
return data.recordOrderPayment;
|
|
82
|
+
},
|
|
83
|
+
async position(orderId, options) {
|
|
84
|
+
const data = await transport.graphql({ api: 'admin', query: ORDER_PAYMENT_POSITION_QUERY, variables: { orderId } }, options);
|
|
85
|
+
return data.orderPaymentPosition;
|
|
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
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|