Revenue & Payer Integrations
Recipes for the Wave-3 surfaces: external payment recording, hosted payment links, claims monitoring, and the ZATCA compliance stream. Scope tier for all of these is financial (rendered with a money icon in the consent flow); every financial list requires a from/to window of at most 366 days.
Recording payments from a POS or gateway (payments:write)
You collected the money outside Dentolize; record it against the invoice so clinic totals, treasuries and reports stay true:
const payment = await client.payments.create(
{ invoiceId, amount, type: 'CARD', reference: gatewayTxId },
{ idempotencyKey: gatewayTxId } // your own stable key — survives process restarts
);
- Idempotency + double-charge safety.
Idempotency-Keyis mandatory. Beyond the 24h response-replay layer, the platform derives the payment id deterministically from your key — a retry that slips past replay collides on the id and returns the already-recorded payment instead of charging twice. Use the gateway transaction id as your key. - Full money path. The write reuses the platform payment core: treasury routing via the branch's defaults (or pass
treasuryId), invoice/patient totals, and the normalpayment.created/invoice.paidevents — indistinguishable from a payment recorded in the app. - Provenance. The payment's user attribution is the installing user (clinic owner fallback); your plugin identity is retained in the API request log for audit.
BALANCE/INSURANCEtypes are rejected. - Handle the typed errors:
400 amount_exceeds_pendingreturnsdetails.pendingAmount— re-read the invoice rather than retrying blindly;409 invoice_already_paidmeans nothing is owed;409 treasury_requiredmeans this clinic mandates an explicittreasuryId.
Generating payment links (paymentlinks:write)
Let a patient pay remotely on the clinic's own gateway:
const link = await client.paymentLinks.create({ invoiceId }); // amount defaults to pending
await sendWhatsApp(patientPhone, link.paymentLink);
Then react to webhooks: onlinepayment.succeeded (its data.paymentId is the recorded payment — the platform records it for you, do not also call payments.create), onlinepayment.failed, onlinepayment.refunded. A clinic without a usable gateway yields 409 payment_provider_not_configured; gateway-side rejections surface as 502 payment_provider_error.
Claims monitoring dashboard (claims:read + insurance:read)
Build a payer-facing view of claim pipelines:
// Directory once (cache it): resolve insurer ids to names
const insurers = new Map();
for await (const company of client.insuranceCompanies.iterate()) insurers.set(company.id, company);
// Claims whose period overlaps the quarter
for await (const claim of client.claims.iterate({ from, to })) {
track(insurers.get(claim.insuranceCompanyId)?.name, claim.status, claim.totalInsuranceAmount,
claim.rejectedAmount, claim.remainingAmount, claim.totalReceivedAmount);
}
- Subscribe to
claim.created,claim.status_changed(all transitionsPLANNING → IN_REVIEW → PARTIALLY_CLAIMED → FULLY_CLAIMEDand the revert toPLANNING), andclaim.rejected_amount_updatedfor live updates; useupdatedAfterfor delta re-syncs. claims.get(id)addsinvoiceIds(member invoices, ids only — pair withinvoices:readfor amounts).- Pre-auth tracking:
treatments.list({ approvalRequired: true, approved: false })plus thetreatment.approval_requested/treatment.approvedevents. - Patient coverage (
patients:read.piionly): the patient payload's nestedinsuranceblock carries number, policy/class ids, percentage and limits.
ZATCA compliance stream (einvoices:read)
Feed a compliance dashboard without touching XML or certificates:
const failing = await client.einvoiceSubmissions.list({ from, to, status: 'REJECTED' });
for (const submission of failing.data) alert(submission.invoiceId, submission.errorMessages[0]);
- Events:
einvoice.submitted(simplified/B2C reporting),einvoice.cleared(standard/B2B clearance),einvoice.rejected(with a 200-charerrorMessage). einvoice.clearednuance: its payloadstatusisSUBMITTED— theCLEAREDenum is never persisted; treat the event type (or the REST row'sclearanceTime) as the clearance signal.- Submissions hang off invoices or claims (
invoiceId/claimId); reconcile against your invoice stream viainvoiceIdand re-check stuckPENDING/IN_PROGRESSrows withupdatedAfterpolls.