Requiply
TypeScript SDK

Продажі та товари на продаж

Отримуйте список товарів на продаж і їхніх залишків, читайте й записуйте продажі та продавайте товари разом із бронюванням через SDK.

Ці ресурси охоплюють модуль Продажі: товари на кшталт карт памʼяті, батарей чи гаферного скотчу, які орендний бізнес продає з обліком залишку поруч з орендою.

Організація має бути на тарифі Advanced з увімкненим Увімкнути продажі у Функції. Інакше кожен метод викидає RequiplyError зі status403 і reasonfeature_disabled (selling_disabled для публічного ключа).
МетодСекретний ключПублічний ключ
saleItems.list(), saleItems.get()ТакЛише активні товари, поки організація продає клієнтам
saleLines у pricing.quote() і bookings.create()ТакТак, у межах наявного залишку, поки організація продає клієнтам
sales.list(), sales.get(), sales.create()ТакНі

Товари на продаж зберігаються окремо від товарів для оренди: inventory.list(), доступність і categories.list() ніколи їх не повертають.

З публічним ключем

Вітрина може показувати товари на продаж і давати клієнтам додавати їх до запиту на бронювання так само, як це робить клієнтський портал на кроці Додати товари для покупки. Публічний ключ отримує до них доступ, лише поки організація продає клієнтам, для чого потрібні всі три умови:

Інакше виклик викидає 403 з reason selling_disabled, тож вітрина може приховати розділ товарів.

З публічним ключем:

  • saleItems.list() повертає лише активні товари, незалежно від filter. barcode і purchaseCost завжди null.
  • saleItems.get() повертає 404 для архівованого товару, а soldUnits30d, revenue30d, soldUnitsTotal дорівнюють 0.
  • saleLines не може запитувати більше за stockOnHand. Рядок понад залишок викидає 409 з reason out_of_stock; data.saleItemId називає товар, а data.availableQuantity показує, скільки залишилося (0, коли все продано).
try {
  await requiply.pricing.quote({ startDate, endDate, items, saleLines });
} catch (error) {
  if (error instanceof RequiplyError && error.reason === 'out_of_stock') {
    showNotice(`Only ${error.data?.availableQuantity} left`);
  } else if (error instanceof RequiplyError && error.reason === 'selling_disabled') {
    hideGoodsSection();
  } else {
    throw error;
  }
}

saleItems.list()

saleItems.list(
  params?: SaleItemsListParams,
  options?: RequiplyRequestOptions,
): Promise<SaleItemsListResponse>

Необовʼязкові параметри: search (назва, артикул чи штрихкод), category (ідентифікатор категорії), filter (за замовчуванням active, також archived або all; публічні ключі завжди отримують active), limit (від 1 до 100, за замовчуванням 25), cursor.

const { items, nextCursor } = await requiply.saleItems.list({ search: 'battery' });

for (const item of items) {
  console.log(item.name, item.salePrice, item.currency, item.stockOnHand);
}
ПолеЩо містить
id, name, sku, barcodeТовар. sku і barcode можуть бути null; для публічного ключа barcode завжди null.
categoryId, categoryName, imageUrlМожуть бути null.
salePrice, currencyФіксована ціна за одиницю у валюті організації.
stockOnHandОдиниці на полиці. Значення може бути відʼємним, коли продажі перевищили облікований залишок.
purchaseCostЧиста собівартість одиниці або null, якщо ключ не може керувати залишками. Для публічного ключа завжди null.
isArchivedАрхівовані товари продати не можна.

saleItems.get()

saleItems.get(
  saleItemId: string,
  options?: RequiplyRequestOptions,
): Promise<SaleItemResponse>

Повертає saleItem з наведеними вище полями, а також полями soldUnits30d, revenue30d, soldUnitsTotal.

const { saleItem } = await requiply.saleItems.get(itemId);
console.log(saleItem.soldUnits30d, saleItem.revenue30d);

sales.list()

sales.list(
  params?: SalesListParams,
  options?: RequiplyRequestOptions,
): Promise<SalesListResponse>

Перелічує продажі від найновіших, як окремі, так і повʼязані з бронюваннями. Необовʼязкові фільтри:

ПараметрДія
kindstandalone або booking.
statuscompleted або cancelled.
unpaidtrue для окремих продажів, за якими ще є заборгованість.
clientIdПродажі одного клієнта.
soldFrom, soldToДата й час у форматі ISO 8601.
searchНомер продажу, імʼя клієнта, email чи телефон, назва товару чи артикул.
limit, cursorРозмір сторінки (від 1 до 100, за замовчуванням 25) і nextCursor попередньої сторінки.
const { sales, currency } = await requiply.sales.list({ kind: 'standalone', unpaid: true });

Кожен продаж має такі поля: id; saleOrgId (номер продажу, який співробітники бачать у застосунку); status; soldAt; total; returnedTotal; net (сума мінус повернення); paidAmount; balance; paymentMethod; paidAt; note; клієнта; бронювання, до якого він належить (reservationId, reservationOrgId); itemCount; itemsLabel. balance показує, скільки ще винен окремий продаж. Для продажу в бронюванні це значення завжди 0, бо його оплачують через бронювання.

sales.get()

sales.get(
  id: string | number,
  params?: { idKind?: 'internal' | 'org' },
  options?: RequiplyRequestOptions,
): Promise<SaleResponse>

id є UUID продажу або номером продажу із застосунку з idKind: 'org'.

const { sale } = await requiply.sales.get(12, { idKind: 'org' });

Продаж також містить lines (товар, кількість, returnedQuantity, unitPrice, lineTotal), а також returns і payments. Для продажу в бронюванні payments належать бронюванню, бо бронювання стягує оренду й товари одним платежем.

sales.create()

sales.create(
  input: SaleCreateInput,
  options?: RequiplyRequestOptions,
): Promise<SaleCreateResponse>

Записує окремий продаж, як Новий продаж у застосунку.

const { sale, stockWarnings, payment } = await requiply.sales.create({
  lines: [{ saleItemId: itemId, quantity: 2 }],
  client: { name: 'Alex', email: 'customer@example.com' },
  paymentMethod: 'monobank',
  note: 'Counter sale',
});

if (payment?.paymentUrl) console.log('Pay here:', payment.paymentUrl);
ПолеПравила
linesОбовʼязкове. Від 1 до 100 товарів, кожен щонайбільше один раз, із цілою кількістю, більшою за нуль. Кожен рядок продається за salePrice товару.
clientId або clientНеобовʼязкове. client зіставляється за email, потім за телефоном, а якщо збігу немає, створюється новий.
paymentMethodpayOnPickup (за замовчуванням) або один з онлайн-способів (monobank, liqpay, wayforpay, stripe) з активною інтеграцією. liqpay потребує клієнта з email.
soldAtISO 8601. За замовчуванням поточний момент.
noteДо 2000 символів.

Залишок зменшується одразу. Продаж може опустити залишок нижче нуля: його все одно буде записано, а stockWarnings перелічує кожен такий товар із його stockOnHand і залишком after після продажу. payment є очікуваним платежем, з якого починається продаж, із paymentUrl для онлайн-способу оплати.

Відхилений продаж викидає RequiplyError з reason на кшталт sale_item_archived, payment_method_unavailable чи client_email_required. Повний перелік див. в Обробка помилок.

Товари в бронюванні

pricing.quote() і bookings.create() приймають saleLines, щоб продати товари разом з орендою:

const quote = await requiply.pricing.quote({
  startDate: '2026-07-01T10:00:00',
  endDate: '2026-07-03T18:00:00',
  items: [{ groupId: cameraKitId, quantity: 1 }],
  saleLines: [{ saleItemId: memoryCardId, quantity: 2 }],
});

console.log(quote.saleSubtotal, quote.lineSaleItems[0]?.stockAfter);
  • Секретний ключ може продавати у відʼємний залишок, як це можуть робити співробітники в застосунку. Публічний ключ не може: див. З публічним ключем.
  • Товари продаються за каталожною ціною, знижки та коригування до них ніколи не застосовуються. Їхня сума є saleSubtotal і вже входить у total.
  • lineSaleItems показує кожен товар зі stockOnHand і stockAfter, тож можна попередити, перш ніж залишок стане відʼємним.
  • У bookings.create() залишок зменшується під час створення бронювання і повертається, якщо бронювання скасують. Сума товарів додається до totalCost бронювання та його оплати.
  • bookings.get() повертає товари як saleItems, а їхній продаж як saleId.

Щоб змінити товари в наявному бронюванні, скористайтеся Продажем товарів у бронюванні в застосунку.

Copyright © 2026