Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.0.3] - 2026-09-21

### Changed

- **BREAKING:** `treatments().sync()` now requires `userAgent`; an empty or
missing value throws `TypeError`. Adds optional `payment` and `verification`
fields, with new exported types `TreatmentPaymentOptions`,
`TreatmentCardOptions`, `TreatmentVerificationOptions`, and
`TreatmentIdVerificationOptions`.
- Documented the optional `variant_id` cart-item field on `carts().create()`
and `carts().update()`, the optional `payment` field on an `OrderPlaced`
`checkoutEvents().update()` event, and the optional `verification` field on
`sessions().create()` and `sessions().update()`. All three were already
forwarded as pass-through payload; only the documentation changed.

## [0.0.2] - 2026-09-08

First public release of the Node.js SDK, at feature parity with the PHP SDK
Expand Down
4 changes: 3 additions & 1 deletion docs/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,9 @@ a debug log.
| `list(query?: QueryParams): Promise<Response<T>>` | GET | `/v1/sales/treatments/list` |
| `sync(options: TreatmentSyncOptions): Promise<Response<T>>` | POST | `/v1/sales/treatments/sync` |

`TreatmentSyncOptions` is `{ session, orderIds, utmSource?, userAgent? }`. Use
`TreatmentSyncOptions` is
`{ session, orderIds, userAgent, utmSource?, payment?, verification? }`.
`userAgent` is required - an empty or missing value throws `TypeError`. Use
`sync()` to push an order settled in an external CRM into AsterMD after
settlement happens outside the SDK.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@astermd-hq/sdk",
"version": "0.0.2",
"version": "0.0.3",
"description": "Official Node.js SDK for the AsterMD order-flow API.",
"keywords": [
"astermd",
Expand Down
12 changes: 11 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,17 @@ export type {
} from './resource/intake-submissions.js';
export { Patients, type SubmitHealthInformationOptions } from './resource/patients.js';
export { Opportunities } from './resource/opportunities.js';
export { Treatments, type TreatmentSyncOptions } from './resource/treatments.js';
export {
Treatments,
type TreatmentSyncOptions,
type TreatmentPaymentOptions,
type TreatmentCardOptions,
type TreatmentCardType,
type TreatmentPaymentType,
type TreatmentVerificationOptions,
type TreatmentIdVerificationOptions,
type TreatmentIdVerificationMethod,
} from './resource/treatments.js';
export { Channels } from './resource/channels.js';
export { DoctorsNetworks } from './resource/doctors-networks.js';
export { Categories } from './resource/categories.js';
Expand Down
7 changes: 4 additions & 3 deletions src/resource/carts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ export interface CartWriteOptions {
/** The session the cart belongs to. */
session: string;
/**
* The cart's full contents. Each entry identifies a product and quantity, and
* a variant where the product has them. This is the complete list, not a
* delta - see the API reference in your AsterMD dashboard for the fields.
* The cart's full contents. Each entry requires `product_id`, `name`, and
* `qty`, plus an optional `variant_id` when the product has variants. This is
* the complete list, not a delta - see the API reference in your AsterMD
* dashboard for the fields.
*/
items: Record<string, unknown>[];
}
Expand Down
8 changes: 7 additions & 1 deletion src/resource/checkout-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@ export class CheckoutEvents extends AbstractResource {
* Send one call per state the visitor reaches: an upsell offered, then accepted
* or declined, then the order placed or declined. The payload carries whatever
* context that state needs, but it can never override `event` - the state
* recorded is always the one you passed explicitly.
* recorded is always the one you passed explicitly. For an `OrderPlaced` event,
* `data` may include an optional `payment` object describing the settled
* payment method: `type` (`paypal` | `apple_pay` | `gpay` | `credit_card` |
* `pre_paid`), `pre_auth` (boolean), `pre_auth_qa` and `pre_auth_amount`
* (optional), and an optional `card` object (`type` - `amex` | `visa` |
* `mastercard` | `discover` | `diners_club` | `jcb`, optional `bin`, required
* `exp`).
*
* @param options The session, the new state, and any context for it.
* @returns The updated checkout-event record.
Expand Down
9 changes: 7 additions & 2 deletions src/resource/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ export interface SessionCreateOptions {
/**
* Attribution and context to record against the session - UTM parameters,
* referrer, landing page. Forwarded unmodified; see the API reference in your
* AsterMD dashboard for the recognised keys.
* AsterMD dashboard for the recognised keys. May include an optional
* `verification` object recording identity/contact verification already
* performed by the caller - every field inside it is optional too: `email`
* (boolean), `address` (boolean), and `id` (`verified` boolean, `method` -
* `ssn` | `dob` | `cross_check` | `document_upload`, `value` string).
*/
data?: Record<string, unknown>;
/** The visitor's user agent, forwarded as the `User-Agent` header. */
Expand Down Expand Up @@ -135,7 +139,8 @@ export class Sessions extends AbstractResource {
*
* @param session The session identifier to update.
* @param data Fields to record. Forwarded unmodified; see the API reference in
* your AsterMD dashboard for the recognised keys.
* your AsterMD dashboard for the recognised keys. Accepts the same optional
* `verification` object documented on {@link Sessions.create}.
* @returns The updated session.
* @throws {NotFoundError} If the session does not exist.
* @throws {ValidationError} If the payload is rejected.
Expand Down
110 changes: 103 additions & 7 deletions src/resource/treatments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,77 @@ import type { Transport } from '../http/transport.js';
import type { Response as ApiResponse } from '../response.js';
import { AbstractResource } from './abstract-resource.js';

/** The card network for {@link TreatmentCardOptions.type}. */
export type TreatmentCardType = 'amex' | 'visa' | 'mastercard' | 'discover' | 'diners_club' | 'jcb';

/** The settled payment method for {@link TreatmentPaymentOptions.type}. */
export type TreatmentPaymentType = 'paypal' | 'apple_pay' | 'gpay' | 'credit_card' | 'pre_paid';

/** The identity check performed for {@link TreatmentIdVerificationOptions.method}. */
export type TreatmentIdVerificationMethod = 'ssn' | 'dob' | 'cross_check' | 'document_upload';

/** Card details nested inside {@link TreatmentPaymentOptions.card}. */
export interface TreatmentCardOptions {
/** The card network. */
type: TreatmentCardType;
/** The card's bank identification number, when available. */
bin?: string;
/** The card's expiry, in the format your integration agreed with AsterMD. */
exp: string;
}

/** The settled payment method for {@link Treatments.sync}. */
export interface TreatmentPaymentOptions {
/** How the order was paid for. */
type: TreatmentPaymentType;
/** Whether the payment was a pre-authorisation rather than a capture. */
preAuth: boolean;
/** Whether the pre-authorisation was a quality-assurance hold rather than a real charge. */
preAuthQa?: boolean;
/** The pre-authorised amount, when `preAuth` is true. */
preAuthAmount?: number;
/** Card details, when the payment method is card-based. */
card?: TreatmentCardOptions;
}

/** The identity check nested inside {@link TreatmentVerificationOptions.id}. */
export interface TreatmentIdVerificationOptions {
/** Whether the check passed. */
verified: boolean;
/** Which identity check was performed. */
method: TreatmentIdVerificationMethod;
/** The value that was checked, e.g. the SSN or date of birth submitted. */
value: string;
}

/** Identity/contact verification already performed by the caller, for {@link Treatments.sync}. */
export interface TreatmentVerificationOptions {
/** Whether the patient's email was verified. */
email: boolean;
/** Whether the patient's address was verified. */
address: boolean;
/** The identity check performed, if any. */
id?: TreatmentIdVerificationOptions;
}

/** Options for {@link Treatments.sync}. */
export interface TreatmentSyncOptions {
/** The session the orders belong to. */
session: string;
/** Order identifiers from your own commerce system. */
orderIds: string[];
/**
* The visitor's user agent, forwarded as the required `User-Agent` header.
* Since the SDK runs server-to-server, only the consuming application knows
* the real value - read it from the incoming request and pass it here.
*/
userAgent: string;
/** Campaign attribution to record alongside the orders. */
utmSource?: string;
/** The visitor's user agent, forwarded as the `User-Agent` header. */
userAgent?: string;
/** The settled payment method for the order. */
payment?: TreatmentPaymentOptions;
/** Identity/contact verification already performed by the caller. */
verification?: TreatmentVerificationOptions;
}

/**
Expand Down Expand Up @@ -119,16 +180,25 @@ export class Treatments extends AbstractResource {
* The alternative to {@link Treatments.create} for flows where checkout happens
* outside your storefront: hand over the session and your order identifiers and
* the server creates the treatments and attributes them to that session's
* journey. Several orders can be reconciled in one call.
* journey. Several orders can be reconciled in one call. `payment`, when
* supplied, carries the settled payment method for the order; `verification`,
* when supplied, records identity/contact verification already performed by
* the caller.
*
* @param options The session, your order identifiers, and optional attribution.
* @param options The session, your order identifiers, the required user agent,
* and optional attribution, payment, and verification details.
* @returns The created treatments.
* @throws {TypeError} If `userAgent` is missing or empty.
* @throws {ValidationError} If the session or an order identifier is rejected.
* @throws {NotFoundError} If the session does not exist.
* @throws {ApiError} On any other non-2xx status.
* @throws {TransportError} If the request never completed.
*/
async sync<T = Record<string, unknown>>(options: TreatmentSyncOptions): Promise<ApiResponse<T>> {
if (options.userAgent === undefined || options.userAgent === '') {
throw new TypeError('Treatments.sync requires a non-empty userAgent.');
}

const body: Record<string, unknown> = {
session_id: options.session,
order_ids: options.orderIds,
Expand All @@ -138,15 +208,41 @@ export class Treatments extends AbstractResource {
body.utm_source = options.utmSource;
}

const headers =
options.userAgent !== undefined && options.userAgent !== '' ? { 'User-Agent': options.userAgent } : {};
if (options.payment !== undefined) {
const payment: Record<string, unknown> = {
type: options.payment.type,
pre_auth: options.payment.preAuth,
};

if (options.payment.preAuthQa !== undefined) {
payment.pre_auth_qa = options.payment.preAuthQa;
}

if (options.payment.preAuthAmount !== undefined) {
payment.pre_auth_amount = options.payment.preAuthAmount;
}

if (options.payment.card !== undefined) {
payment.card = options.payment.card;
}

body.payment = payment;
}

if (options.verification !== undefined) {
body.verification = {
email: options.verification.email,
address: options.verification.address,
...(options.verification.id !== undefined ? { id: options.verification.id } : {}),
};
}

return await this.transport.send<T>({
service: 'sales',
method: 'POST',
path: '/treatments/sync',
body,
headers,
headers: { 'User-Agent': options.userAgent },
});
}
}
Loading
Loading