diff --git a/docs/payments.md b/docs/payments.md index 4de9177..0c13c3a 100644 --- a/docs/payments.md +++ b/docs/payments.md @@ -98,8 +98,9 @@ norbix payments integration save --provider Montonio --name "Montonio sandbox" - | `norbix payments order create --buyer --reference --key --line ... [--meta k=v]... [--currency EUR] [--price-version v1] [--file ]` | create an order for one buyer | `POST /orders` | | `norbix payments order checkout --integration --return-url [--method ] [--country ] [--provider ]` | start or reuse the checkout; answers the payment URL | `POST /orders/{Id}/checkout` | -The price is yours: Norbix adds up the lines (whole cents, EUR) and never -reads a price from the buyer. `--key` is the idempotency key: the same key with +The price is yours: Norbix adds up the lines (whole cents, EUR — at least 1 +cent per unit) and never reads a price from the buyer. Every line needs a title; +`--line` without one uses the sku. `--key` is the idempotency key: the same key with the same body returns the same order, with another body it is refused (409). Creating an order needs a key whose role has "Payments — full access" (or Administrator); a buyer can read and check out only their own orders. diff --git a/src/commands/payments/order/create.ts b/src/commands/payments/order/create.ts index ec47c1f..f9a2d54 100644 --- a/src/commands/payments/order/create.ts +++ b/src/commands/payments/order/create.ts @@ -7,7 +7,8 @@ export default class PaymentsOrderCreate extends BaseCommand { static description = `Create a payment order for one buyer The price is yours: give each line as --line sku:quantity:unitCents[:title] -(repeat for several); Norbix adds up the total in cents. --key is the +(repeat for several; without a title the sku is used as the title); Norbix +adds up the total in cents. --key is the idempotency key (8 characters or more): the same key with the same body returns the same order, the same key with another body is refused. Needs a key whose role may create orders (Payments — full access, or Administrator). diff --git a/src/lib/payments.ts b/src/lib/payments.ts index 5da6f12..8bd18c1 100644 --- a/src/lib/payments.ts +++ b/src/lib/payments.ts @@ -48,16 +48,17 @@ export const PAYMENT_PROVIDERS = [ /** * One order line from `--line sku:quantity:unitCents[:title]` — e.g. `star-day:21:30`. + * The server requires a title on every line: without one, the sku is the title. * A title with spaces goes in --file instead. */ export function parseOrderLine(raw: string): {sku: string; quantity: number; unitAmountMinor: number; title?: string} { const [sku, quantity, unit, ...title] = raw.split(':') const q = Number(quantity) const u = Number(unit) - if (!sku || !Number.isInteger(q) || q < 1 || !Number.isInteger(u) || u < 0) { - throw new Error(`--line "${raw}" must be sku:quantity:unitCents[:title], e.g. star-day:21:30 (quantity 1 or more, cents 0 or more)`) + if (!sku || !Number.isInteger(q) || q < 1 || !Number.isInteger(u) || u < 1) { + throw new Error(`--line "${raw}" must be sku:quantity:unitCents[:title], e.g. star-day:21:30 (quantity 1 or more, cents 1 or more)`) } - return title.length > 0 ? {sku, quantity: q, unitAmountMinor: u, title: title.join(':')} : {sku, quantity: q, unitAmountMinor: u} + return {sku, quantity: q, unitAmountMinor: u, title: title.length > 0 ? title.join(':') : sku} } /** `--meta key=value` (repeatable) → a string map. */ diff --git a/tests/payments-routes.test.ts b/tests/payments-routes.test.ts index 81252db..cde5d52 100644 --- a/tests/payments-routes.test.ts +++ b/tests/payments-routes.test.ts @@ -312,7 +312,7 @@ describe('payments order create', () => { currency: 'EUR', priceVersion: 'v1', lines: [ - {sku: 'star-day', quantity: 21, unitAmountMinor: 30}, + {sku: 'star-day', quantity: 21, unitAmountMinor: 30, title: 'star-day'}, {sku: 'highlight-day', quantity: 7, unitAmountMinor: 30, title: 'Highlight'}, ], metadata: {adId: 'ad_1042'}, @@ -335,6 +335,12 @@ describe('payments order create', () => { }) }) + it('refuses a 0-cent line (the server needs 1 cent or more)', async () => { + const {error} = await runCommand(['payments', 'order', 'create', '--line', 'star-day:21:0', ...globalArgs]) + expect(error?.message).toMatch(/cents 1 or more/) + expect(calls).toHaveLength(0) + }) + it('refuses a malformed --line before sending anything', async () => { const {error} = await runCommand(['payments', 'order', 'create', '--line', 'star-day:zero:30', ...globalArgs]) expect(error?.message).toMatch(/must be sku:quantity:unitCents/)