Skip to main content

Payment

Source: src/data/payment/

Service

createPaymentService / PaymentService

Factory: createPaymentService(repository: PaymentRepository, events?: PaymentEventEmitter) — returns PaymentService.

Provides payment lifecycle operations for bot order flow and P2P admin approval.

MethodParametersReturnsDescription
createPending(ctx, input: { orderId, method, amountCents, currency, referenceText? })Promise<PaymentRecord>Creates a pending payment; idempotent for same order id
confirmByOrder(ctx, input: { orderId, confirmedBy, providerRef? })Promise<PaymentRecord>Legacy: confirms by order id, marks PAID
getByOrder(ctx, orderId)Promise<PaymentRecord | null>Reads payment by order
submitForApproval(ctx, paymentId)Promise<PaymentRecord>Transitions pending_payment → awaiting_admin_approval. Idempotent.
confirmPayment(ctx, paymentId, approverUserId, referenceNote?)Promise<PaymentRecord>Transitions awaiting_admin_approval → confirmed. Emits payment.confirmed. Idempotent.
rejectPayment(ctx, paymentId, approverUserId, reason)Promise<PaymentRecord>Transitions awaiting_admin_approval → rejected. Emits payment.rejected. Idempotent.

PaymentEventEmitter

type PaymentEventEmitter = { emit: (event: string, payload: unknown) => void }

Passed as optional second arg to createPaymentService. When provided, payment.confirmed and payment.rejected are emitted on the respective transitions.

Repository

PaymentRepository

MethodParametersReturns
getById(ctx, id)Promise<PaymentRecord | null>
findByOrderId(ctx, orderId)Promise<PaymentRecord | null>
listByTenant(ctx)Promise<PaymentRecord[]>
create(ctx, input)Promise<PaymentRecord>
update(ctx, id, patch)Promise<PaymentRecord>
delete(ctx, id)Promise<void>

PaymentRecord

FieldTypeDescription
idstring
tenantIdstring
orderIdstring
methodPaymentMethod
statusPaymentStatus
amountCentsnumberOriginal order total in cents
refundedAmountCentsnumberRunning total of all refunded amounts (default 0). 0 < n < amountCentsPARTIAL_REFUND; n = amountCentsREFUNDED.
currencystring
providerRefstring (optional)
referenceTextstring (optional)
confirmedBystring (optional)Legacy field
confirmedAtDate (optional)Legacy field
approverUserIdstring (optional)Admin who confirmed or rejected
approvedAtDate (optional)When confirmed
rejectedAtDate (optional)When rejected
rejectionReasonstring (optional)Reason for rejection
customerReferenceNotestring (optional)Venmo/CashApp note admin saw
createdAtDate
updatedAtDate

PaymentMethod

One of: PAYPAL, CASHAPP_PAY, BTC_INVOICE, VENMO_P2P, CASHAPP_P2P, ZELLE, CHIME, PAYPAL_MANUAL, BTC_MANUAL, BTC_NOWPAYMENTS

Each value maps to a PaymentMethodDescriptor in src/data/payment-methods/payment-methods.registry.ts. The descriptor carries displayName, kind (p2p | processor | manual), icon, and flags for requiresProcessor, supportsAutomaticConfirmation, and supportsRefund. Use getPaymentMethodDescriptor(method) or getEnabledDescriptors(enabledMethods) from payment-methods.service.ts to look up human-readable labels instead of formatting raw enum strings.

PaymentStatus

Legacy values: PENDING, AUTHORIZED, PAID, FAILED, REFUNDED, PARTIAL_REFUND, CANCELLED

P2P approval flow values: pending_payment, awaiting_admin_approval, confirmed, rejected

PARTIAL_REFUND is set automatically when refundedAmountCents > 0 and refundedAmountCents < amountCents after a partial refund. It transitions to REFUNDED once refundedAmountCents = amountCents.

Events

payment.confirmed

Emitted when confirmPayment transitions a payment to confirmed.

type PaymentConfirmedEvent = {
tenantId: string
paymentId: string
orderId: string
approverUserId: string
referenceNote?: string
confirmedAt: Date
}

payment.rejected

Emitted when rejectPayment transitions a payment to rejected.

type PaymentRejectedEvent = {
tenantId: string
paymentId: string
orderId: string
approverUserId: string
reason: string
rejectedAt: Date
}

PaymentNowPaymentsService

Source: src/data/payment/payment-nowpayments.service.ts

Factory: createPaymentNowPaymentsService(deps) — returns PaymentNowPaymentsService.

Bridges the CryptoPaymentPort (NOWPayments adapter) with the Payment repository to create BTC invoices for the BTC_NOWPAYMENTS checkout flow. Only instantiated when NOWPAYMENTS_API_KEY is configured; otherwise undefined at all call sites.

MethodParametersReturnsDescription
createInvoice(ctx, { paymentId, amountCents, currency, customerEmail?, callbackUrl? })Promise<NowPaymentsInvoiceResult>Creates a NOWPayments hosted invoice (BTC). On success, persists providerRef + five nowpayments* fields to the Payment record. Persist failure logs a warning but still returns the invoice result.
getInvoiceStatus(invoiceId)Promise<NowPaymentsStatus>Fetches current status from NOWPayments and maps to 'pending' | 'finished' | 'failed' | 'expired'.

NowPaymentsInvoiceResult

FieldTypeDescription
invoiceIdstringNOWPayments invoice ID
hostedUrlstringCustomer-facing hosted payment page URL
depositAddressstringOn-chain BTC deposit address
depositAmountCryptostringExpected BTC amount (decimal string)
cryptoCurrencystringAlways 'btc' for this service
expiresAtDate (optional)Invoice expiry timestamp

NowPaymentsStatus

One of: 'pending', 'finished', 'failed', 'expired'

Maps from the CryptoPaymentPort status values: pendingpending, partially_paidpending, finishedfinished, failedfailed, expiredexpired. Unknown values fall back to 'pending'.


Notification helper

buildApprovalNotification

Source: src/data/payment/payment.notification.ts

Builds the Discord embed + button row for a payment awaiting admin approval.

buildApprovalNotification(order, payment, customer): { embeds, components }

Used by the Notifications agent to post the approval embed to the admin channel when an order with a P2P payment is created.

Processor-backed vs manual-only methods

The pay-by-invoice page (GET /orders/invoice/:token) displays only processor-backed methods — methods that can be completed by the customer without staff intervention.

Excluded (manual-only): VENMO_P2P, CASHAPP_P2P, ZELLE, CHIME, PAYPAL_MANUAL, BTC_MANUAL

Included (processor-backed): PAYPAL, CASHAPP_PAY, BTC_INVOICE, BTC_NOWPAYMENTS, and any future processor methods.

The filter is applied in order-invoice.endpoint.ts using the MANUAL_ONLY_METHODS set.

Validators

See src/data/payment/payment.validators.ts for the complete Zod schema. New optional fields: approverUserId, approvedAt, rejectedAt, rejectionReason, customerReferenceNote.