import { buildInvoiceBody, includesText, mutationPreview, normalizeInvoiceSummary, type MutationGuardInput, type PayPalAuthInput, } from './paypal-helpers.ts' import { paypalCreateInvoice, paypalGetInvoice, paypalListInvoices, paypalSearchInvoices, paypalSendInvoice, } from './paypal-core.ts' export type ListInvoicesInput = PayPalAuthInput & { page?: number pageSize?: number totalRequired?: boolean fields?: string status?: string includeRaw?: boolean } export type SearchInvoicesInput = PayPalAuthInput & { page?: number pageSize?: number totalRequired?: boolean invoiceNumber?: string recipientEmail?: string status?: string reference?: string currencyCode?: string amount?: string | number rawCriteria?: Record includeRaw?: boolean query?: string } export type GetInvoiceInput = PayPalAuthInput & { id: string includeRaw?: boolean } /** * List, search, create, and send PayPal invoices through saved PayPal credentials. * Prefer this export for invoice workflows; use `./transactions` for ledger search. * * @param input - Search/list filters or create/send fields depending on helper. * @returns Invoice summaries or mutation previews. * * @example * import invoices from 'kody:@kody/paypal/invoices' * const recent = await invoices({ pageSize: 5 }) */ export function invoiceMatches( summary: any, input: { amount?: string | number status?: string recipientEmail?: string query?: string currencyCode?: string }, ) { if (input.status && summary.status !== input.status) return false if (input.recipientEmail && !includesText(summary.recipient_email, input.recipientEmail)) return false if (input.currencyCode && summary.currency_code !== input.currencyCode) return false if (input.amount != null) { const expected = Number(input.amount) const amount = Number(summary.amount) if (!Number.isFinite(expected) || !Number.isFinite(amount) || Math.abs(Math.abs(amount) - Math.abs(expected)) > 0.005) { return false } } if (input.query) { const haystack = [ summary.id, summary.invoice_number, summary.status, summary.recipient_email, summary.recipient_name?.given_name, summary.recipient_name?.surname, summary.merchant_name, summary.reference, summary.memo, summary.amount, ].join(' ') if (!includesText(haystack, input.query)) return false } return true } function mapInvoiceItem(item: any, includeRaw?: boolean) { const summary = normalizeInvoiceSummary(item) return includeRaw ? { summary, raw: item } : summary } /** * List recent PayPal invoices (OpenAPI `invoices_list`). */ export async function listInvoices(input: ListInvoicesInput = {}) { const raw = await paypalListInvoices({ ...input, query: { page: input.page ?? 1, page_size: input.pageSize ?? 20, total_required: input.totalRequired ?? true, fields: input.fields, }, }) const items = Array.isArray((raw as any)?.items) ? (raw as any).items : [] const mapped = items.map((item: any) => mapInvoiceItem(item, input.includeRaw)) const filtered = input.status ? mapped.filter((item: any) => (input.includeRaw ? item.summary?.status : item?.status) === input.status) : mapped return { total_items: (raw as any)?.total_items ?? null, total_pages: (raw as any)?.total_pages ?? null, page: (raw as any)?.page ?? input.page ?? 1, items: filtered, } } /** * Read one PayPal invoice by invoice id (OpenAPI `invoices_get`). */ export async function getInvoice(input: GetInvoiceInput) { if (!input.id) throw new Error('getInvoice requires id.') const raw = await paypalGetInvoice({ ...input, invoiceId: input.id }) const summary = normalizeInvoiceSummary(raw) return input.includeRaw ? { summary, raw } : summary } /** * Search PayPal invoices by invoice number, recipient email, status, reference, currency, or amount * (OpenAPI `invoices_search_invoices`). */ export async function searchInvoices(input: SearchInvoicesInput = {}) { const body: Record = { ...(input.rawCriteria ?? {}) } if (input.invoiceNumber) body.invoice_number = input.invoiceNumber if (input.recipientEmail) body.recipient_email = input.recipientEmail if (input.status) body.status = input.status if (input.reference) body.reference = input.reference if (input.currencyCode) body.currency_code = input.currencyCode const raw = await paypalSearchInvoices({ ...input, query: { page: input.page ?? 1, page_size: input.pageSize ?? 20, total_required: input.totalRequired ?? true, }, body, }) const items = Array.isArray((raw as any)?.items) ? (raw as any).items : [] const mapped = items.map((item: any) => mapInvoiceItem(item, input.includeRaw)) const filtered = mapped.filter((item: any) => invoiceMatches(input.includeRaw ? item.summary : item, input)) return { total_items: (raw as any)?.total_items ?? null, total_pages: (raw as any)?.total_pages ?? null, page: (raw as any)?.page ?? input.page ?? 1, items: filtered, } } /** * Find a single PayPal invoice. If id is provided, this reads that invoice directly; otherwise it searches. */ export async function findInvoice(input: SearchInvoicesInput & { id?: string } = {}) { if (input.id) { const invoice = await getInvoice({ ...input, id: input.id }) return { match: invoice, source: 'id' } } const result = await searchInvoices(input) return { ...result, match: result.items[0] ?? null, source: 'search', } } /** * Return a compact invoice summary by id or search criteria. */ export async function summarizeInvoice(input: SearchInvoicesInput & { id?: string } = {}) { const result = await findInvoice(input) return result.match } export type CreateInvoiceInput = MutationGuardInput & { invoiceNumber?: string invoiceDate?: string currencyCode?: string memo?: string note?: string termType?: string recipientEmail: string recipientGivenName?: string recipientSurname?: string itemName: string itemDescription?: string amount: string | number send?: boolean includeRaw?: boolean } export { buildInvoiceBody } from './paypal-helpers.ts' /** * Create a PayPal invoice (draft unless `send` is true). * Pass `dryRun: true` to preview. Sending also requires `confirm: true`. */ export async function createInvoice(input: CreateInvoiceInput) { const body = buildInvoiceBody(input) const preview = mutationPreview(input, { action: input.send ? 'create and send invoice to ' + input.recipientEmail : 'create draft invoice', method: 'POST', path: '/v2/invoicing/invoices', body, requireConfirm: Boolean(input.send), }) if (preview) return preview const raw = await paypalCreateInvoice({ ...input, body }) const id = (raw as any)?.id if (input.send) { if (!id) throw new Error('PayPal create did not return an invoice id.') await paypalSendInvoice({ ...input, invoiceId: id }) return await getInvoice({ ...input, id, includeRaw: input.includeRaw }) } const summary = normalizeInvoiceSummary(raw) return input.includeRaw ? { summary, raw } : summary } export type SendInvoiceInput = MutationGuardInput & { id: string includeRaw?: boolean } /** * Send an existing PayPal draft invoice to the recipient. * Pass `dryRun: true` to preview. Live send requires `confirm: true`. */ export async function sendInvoice(input: SendInvoiceInput) { if (!input.id) throw new Error('sendInvoice requires id.') const preview = mutationPreview(input, { action: 'send invoice ' + input.id, method: 'POST', path: '/v2/invoicing/invoices/' + encodeURIComponent(input.id) + '/send', body: { send_to_recipient: true, send_to_invoicer: true }, requireConfirm: true, }) if (preview) return preview await paypalSendInvoice({ ...input, invoiceId: input.id }) return await getInvoice({ ...input, id: input.id, includeRaw: input.includeRaw }) } export default async function invoices(input: SearchInvoicesInput = {}) { return await searchInvoices(input) }