Malonlin SDK Docs

Input, field, and event reference

This page is the field-level companion to Feature clients. ! in a GraphQL type means the server requires that field. A server may enforce additional conditional rules after GraphQL validation, as noted below. The backend schema is the source of truth; confirm these inputs against the version deployed at your endpoint.

SDK setup#

Option Type Required Description
endpoint string Yes Absolute GraphQL URL for the Malonlin API.
uploadEndpoint string No Same-origin multipart upload URL; defaults to the API origin's /upload route.
accessToken string No Initial user JWT. Prefer an in-memory token or getAccessToken callback in browser apps.
getAccessToken () => string | null | Promise<string | null> No Retrieve the current user token for requests.
apiKey string No API key for deployments that issue one. Never bundle a private key in a browser or mobile app.
csrfToken string | () => string | null No CSRF value when required by the deployment.
headers Record<string, string> No Extra headers; SDK-managed authorization and API-key headers take precedence.
fetch typeof fetch No Inject a compatible fetch implementation.
timeoutMs number No Positive request timeout in milliseconds; defaults to 30,000.
deviceId string No Stable per-install device identifier, sent as X-Device-Id. The server binds sessions and step-up checks to it. Use a value your app persists, not a fresh random value per launch.
getStepUpCode (message: string) => string | null | Promise<string | null> No Called when the server requires a fresh 2FA code for a sensitive operation. Return the user's 6-digit code, or null to cancel. See Step-up verification.

Authentication and two-factor authentication#

Login#

Field GraphQL type Required Description
email String! Yes Account email.
password String! Yes Account password.
twoFactorCode String No Current authenticator code when two-factor authentication is enabled.
ts
const session = await sdk.auth.login({
  email: 'you@example.com',
  password: passwordFromSecureInput,
  twoFactorCode, // omit when 2FA is not enabled
});

2FA operations#

SDK method Arguments Required / optional Description
generateTwoFactorSecret() None None Generates a setup secret for the authenticated account flow.
beginTwoFactorSetup(adminId, password?) adminId: string, password?: string adminId required Starts setup and returns the server's setup value. The server requires password while 2FA is off, so pass the account password when enabling 2FA.
confirmTwoFactorSetup(adminId, code) Both string Required Verifies a code and enables setup.
verifyTwoFactorCode(adminId, code) Both string Required Verifies a code for the account.
disableTwoFactor(adminId) adminId: string Required Disables 2FA for the owning account.

Keep setup secrets and authenticator recovery material private. Use the authenticated user's own ID for ID-based 2FA calls; the backend's ownership guard must reject attempts to operate on another account. Do not log setup responses, one-time codes, or login payloads.

Step-up verification#

Some sensitive operations require a fresh 2FA code even when the user is already signed in. These are the operations the server currently gates:

  • sdk.wallet.withdraw and sdk.wallet.transfer
  • The bank withdrawal (manualWithdrawalCreate)
  • Admin wallet withdrawals

When the server requires a code, it returns an error whose message starts with STEP_UP_REQUIRED. The SDK then calls getStepUpCode(message), retries the request with the X-Step-Up-Code header, and makes up to three attempts in total.

ts
import { MalonlinSDK, StepUpRequiredError } from 'malonlin-sdk';

const sdk = new MalonlinSDK({
  endpoint,
  getAccessToken,
  deviceId: await getOrCreateInstallId(),
  getStepUpCode: async (message) => askUserForAuthenticatorCode(message),
});

try {
  await sdk.wallet.withdraw({ amount, currency: 'ZAR' });
} catch (error) {
  if (error instanceof StepUpRequiredError && error.isNetworkChange) {
    // The network changed. Ask the user to sign in again with a 2FA code.
  }
}
  • If getStepUpCode is not set, or it returns null, the SDK throws StepUpRequiredError.
  • When isNetworkChange is true, the SDK does not prompt. The server has rejected the session because its network changed, so send the user through a fresh sign-in.
  • Retries keep the same idempotency key. The server rejects step-up before it writes anything to the ledger, so a retry is safe.

Cart and checkout#

Authenticated self-service inputs#

Input Field GraphQL type Required Description
SelfCheckoutInput cartId String! Yes Cart to check or submit. The backend checks cart ownership.
paymentMethod String! Yes Payment method understood by the deployed checkout service.
couponCode String No Coupon to validate and apply.
fulfillmentType FulfillmentType No Fulfillment choice; required by some product/fulfillment combinations.
shippingAddress ShippingAddressInput No Required by the backend when the selected fulfillment needs delivery.
ShippingAddressInput address String! Yes when supplied Address line or formatted delivery address.
city String No City or locality.
contactName String No Recipient name.
contactPhone String No Recipient contact number.
country String No Country.
postalCode String No Postal code.
state String No Province/state.

sdk.getOrCreateMyCart() and sdk.getMyCart() resolve the signed-in user's cart without accepting a caller-supplied user ID. sdk.myCheckoutReadiness(input) is a preview only; the server recalculates totals and validates ownership at checkout. sdk.checkoutMyCart(input) uses the self-scoped mutation and sends an idempotency key. A successful checkout response is authoritative only when the server says the order is paid.

Product create and update fields#

CreateProductDto and UpdateProductDto accept the following fields. GraphQL requires only name and price when creating a product; the backend additionally enforces conditional rules by productType. On update, every listed field is optional.

Field GraphQL type Create required? Description
name String! on create; String on update Yes on create Product display name.
price Float! on create; Float on update Yes on create Base price. For SERVICE, backend normalization derives the price from duration and the selected rate.
productType ProductType No; defaults to PHYSICAL One of PHYSICAL, PRE_ORDER, SERVICE, DIGITAL.
description String No Product details.
handle String No Storefront URL handle.
images [String!] No; defaults to [] Image URLs/keys.
tags [String!] No; defaults to [] Search and organization tags.
categories [String!] No; defaults to [] Product categories.
published Boolean No; defaults to false Whether the listing is published.
featured Boolean No; defaults to false Whether the listing is featured.
currencyCode String No ISO currency code. ZAR is the primary payment currency.
compareAtPrice Float No Optional reference price.
costPerItem Float No Seller-side item cost.
taxPercent Float No Tax percentage from 0 to 100.
discountType DiscountType No Discount calculation mode.
discountValue Float No Discount amount/value.
totalInventory Int No; defaults to 0 Physical stock or pre-order capacity. Service/digital inventory is normalized by the backend.
variants [CreateVariantDto!] No; defaults to [] Product variants.
shippingMode ProductShippingMode No Tangible product pickup/delivery mode.
shippingCostEntries [ShippingCostEntryInput!] No Seller-defined shipping labels and amounts.
hasRefundPolicy Boolean No; defaults to false Enable a custom refund policy.
refundPolicyDescription String No Human-readable refund terms.
refundWindowDays Int No; defaults to 30 Eligibility window; backend documents a 0-365 day range.
refundRequiresApproval Boolean No; defaults to true Require owner approval for refunds.
estimatedPrepTime Int No in SDL; conditionally required for PRE_ORDER Estimated preparation time in minutes; must be at least 1.
isAvailableForPreOrder Boolean No Whether pre-ordering is currently available.
allowPreOrderWaitlist Boolean No Allow overflow into the pre-order waitlist.
preOrderFulfillmentTypes [String!] No in SDL; conditionally required for PRE_ORDER Must include at least one supported type, such as pickup or delivery.
maxPreOrderQuantityPerOrder Int No Maximum quantity per order; normalized to at least 1 for pre-orders.
trackPreOrderCapacityByVariant Boolean No Track pre-order capacity at variant as well as product level.
requiresBooking Boolean No Booking flag; forced on for SERVICE.
serviceDuration Int No in SDL; conditionally required for SERVICE Estimated billable duration in minutes; must be at least 1.
serviceLocation String No Where the service takes place.
availableSlots [ServiceAvailabilitySlotInput!] No in SDL; conditionally required for SERVICE At least one valid service slot is required for a service listing.
maxBookingsPerSlot Int No Booking capacity per slot.
servicePricingModel ServicePricingModel No Duration-based or fixed-fee pricing mode.
serviceRates ServiceRateCardInput No in SDL; conditionally required for SERVICE Must include at least one positive hourly, daily, weekly, or monthly rate.
defaultServiceRateUnit String No Active rate unit; backend chooses a valid available rate when omitted/invalid.
serviceOperationalFlatRate Float No One-time operating cost added to a service estimate.
digitalFiles [DigitalFileInput!] No in SDL; conditionally required for DIGITAL At least one downloadable file; current backend guide limits files to PDF.

Nested input details:

Input Field Type Required Description
ServiceAvailabilitySlotInput startTime String! Yes Start time in HH:mm.
endTime String! Yes End time in HH:mm; must be later than the start.
dayOfWeek Int Conditional Recurring day, 0 (Sunday) through 6 (Saturday).
date String Conditional One-off date in YYYY-MM-DD. Supply date or dayOfWeek.
ServiceRateCardInput hourly, daily, weekly, monthly Float each At least one positive rate for SERVICE Billing-unit rates.
DigitalFileInput fileName, s3Key, mimeType String! each Yes Stored file metadata; use a server-issued key, not a client-supplied arbitrary object path.
fileSizeBytes Int! Yes File size in bytes.

See Product Types and Capabilities for type normalization and fulfillment behavior.

Wallet, payment, and subscription inputs#

Wallet transfer and withdrawal#

Input Field Type Required Description
TransferInput toUserId String! Yes Recipient account identifier.
amount Float! Yes Positive amount; server validates balance and limits.
currency String! Yes Source currency.
toCurrency String No Optional recipient currency.
description String No Transfer memo.
idempotencyKey String No in SDL SDK generates and reuses a stable key for the same retry.
WithdrawInput amount Float! Yes Requested amount.
currency String! Yes Withdrawal currency.
description String No Optional memo.
idempotencyKey String No in SDL SDK supplies a key when one is omitted.
metadata String No Optional serialized metadata.

The sdk.wallet.myWallet() and sdk.wallet.myTransactions() methods are self-scoped and do not accept a user ID. Prefer them over ID-based wallet queries in user-facing applications.

withdraw, transfer, and bank withdrawals (manualWithdrawalCreate) need a step-up 2FA code. Configure getStepUpCode before calling them; see Step-up verification.

Secure payment verification request#

Field Type Required Description
userId String! Yes Must match the authenticated user.
amount Float! Yes Positive payment amount.
type String! Yes Payment operation type understood by the backend.
idempotencyKey String! Yes at GraphQL boundary SDK generates/reuses a secure key if omitted from the client method input.
currency String! No; defaults to ZAR Payment currency.
description String No Optional payment description.
recipientId String No Recipient for transfer-like operations.

This creates a multi-party verification request; it is not a card gateway or proof that the payment has completed. Poll paymentVerificationStatus through an authenticated backend flow and act only on a terminal server status. The primary and website API deployments enforce that the request/status belongs to the authenticated user.

Ozow wallet deposit modal#

sdk.wallet.startOzowDepositModal(userId, input) calls the authenticated ozowPayInModalStart operation. The required userId must be the signed-in user's ID. Never calculate the Ozow hash or expose OZOW_PRIVATE_KEY in a client.

Field Type Required Description
GraphQL userId argument String! Yes Authenticated wallet owner; server ownership guard compares it with the JWT.
input.amount Float! Yes Positive ZAR top-up amount.
customer String No Customer display name.
customerCellphoneNumber String No Verified digits-only phone, excluded from the hash.
adminId, accountNumber String No Optional reconciliation identifiers.
optional1-optional5 String No Optional merchant metadata.
successUrl, cancelUrl, errorUrl, notifyUrl String No Callback overrides; production callbacks should normally be configured server-side.
isTest Boolean No Honored only by non-production server configuration.

The API returns paymentUrl, server-signed postData, transactionReference, and bankReference. Use Ozow's official browser integration (createPaymentModal or createPaymentFrame) to render its hosted payment UX; Malonlin's SDK itself is framework-agnostic and does not inject an iframe or control Ozow's payment page.

ts
const started = await sdk.wallet.startOzowDepositModal(userId, { amount: 250 });

// Load the official Ozow integration script in response to the user's action,
// then hand the server-generated values directly to that provider SDK.
const ozow = new window.Ozow();
ozow.createPaymentModal(started.paymentUrl, started.postData);

// Treat the browser callback as a UX signal only. Reconcile by reference with
// the backend before showing funds as available.
const verification = await sdk.wallet.ozowTransaction(started.transactionReference);

Do not build a custom frame for the payment form, rewrite provider parameters, trust a browser redirect as payment confirmation, or show wallet funds until the Ozow callback/reconciliation confirms completion. A payment frame is cross-origin/provider-controlled; CSP and script-loading policy belong to the consuming application.

Subscription purchase#

Input Field Type Required Description
PurchaseSubscriptionInput subjectId String! Yes User or store receiving the subscription.
subjectType SubscriptionSubjectType! Yes USER or STORE.
planCode String No in SDL Plan code; provide planCode or planId as required by the deployed resolver.
planId String No in SDL Plan ID; provide planId or planCode.
paymentRef String No External payment reference where supported.
startDate, endDate DateTime No Optional schedule dates.
PurchaseSubscriptionAddonInput subjectId String! Yes User or store receiving the add-on.
subjectType SubscriptionSubjectType! Yes USER or STORE.
addonCode String! Yes Add-on identifier.
quantity Int! Yes Number of add-ons.
paymentRef String No Payment reference where supported.

Use sdk.subscriptions.plans(), addons(), and the relevant self/store subscription context before purchase. The server checks eligibility and entitlements; a client-side plan display is not authorization.

Customer profile and address inputs#

Input Field Type Required Description
CreateCustomerInput email String! Yes Customer email.
firstName, lastName, phone, companyName String No Customer identity/contact data.
defaultBillingAddressId, defaultShippingAddressId String No Preferred address references.
metadata [MetadataInput!] No Optional key/value metadata.
CreateAddressInput addressName, address1, city, countryCode, postalCode String! Yes Address label and required postal location.
isDefaultBilling, isDefaultShipping Boolean! Yes Whether this address is the default for either purpose.
address2, company, firstName, lastName, phone, province String No Additional contact and location details.
metadata [MetadataInput!] No Optional key/value metadata.

Prefer myCustomer, myCustomerAddresses, createMyCustomer, and createMyCustomerAddress for the authenticated profile. Prefer myBillingAddresses/myShippingAddresses and their matching createMy*, updateMy*, and removeMy* methods for checkout addresses; these avoid carrying another user's raw ID through the application.

Ratings#

Create and update#

CreateRatingInput fields:

Field Type Required Description
rating Int! Yes Rating score. The server applies the allowed score range.
targetType RatingTargetType No in GraphQL STORE, PRODUCT, or SERVICE; backend resolves the target/source eligibility.
sourceType RatingSourceType No in GraphQL ORDER, DIGITAL_PURCHASE, or SERVICE_BOOKING.
sourceId, storeId, productId, ownerType, ownerId, userId String No in GraphQL Resource/actor references; authenticated identity and completed fulfillment are validated server-side.
title, comment String No Review text.
pros, cons, mediaUrls [String!] No Optional review details/media references.
wouldRecommend Boolean No Recommendation indicator.

UpdateRatingInput has optional rating, title, comment, pros, cons, mediaUrls, and wouldRecommend fields. A review is not accepted merely because these input fields are present: the server requires a completed eligible fulfillment and enforces one review per source. VoteRatingInput requires ratingId: String!, userId: String!, and isHelpful: Boolean!; the user must be the authenticated voter, and voting is single-use.

Webhook subscriber fields and event schemas#

Subscriber management#

Operation Field Type Required Description
Create storeId String Yes Store whose paid orders should produce events.
name String Yes Subscriber label, 1-120 characters.
endpointUrl String Yes Public HTTPS receiver URL; backend also blocks unsafe/private resolution.
eventTypes [String!] Yes Non-empty list; currently only payment.completed is supported.
Update id String Yes Subscription identifier.
name, endpointUrl, eventTypes, isActive String, String, [String!], Boolean No Only supplied values are updated.
Delete id String! Yes Removes the subscription.
Deliveries limit Int No Optional history limit (1-100 in the SDK).

The one-time webhook secret is returned only when the subscription is created. Store it server-side; never send it to a browser or mobile client.

Outbound payment.completed webhook#

Location Field Type Description
Header X-Malonlin-Event-Id string Stable delivery/event ID for deduplication.
Header X-Malonlin-Timestamp Unix seconds Timestamp included in the signature input.
Header X-Malonlin-Signature v1=<hex> HMAC-SHA256 signature of ${timestamp}.${exactRawBody}.
Envelope id string Same event identifier as the header.
Envelope createdAt ISO timestamp Event creation time.
Envelope type string Currently payment.completed.
data orderId, orderNumber string Paid order identifiers.
data amount number Paid amount.
data currency string Payment currency, typically ZAR.
data paidAt ISO timestamp Server-confirmed payment time.

The backend delivers only after isPaid: true, retries non-2xx/network failures up to eight times, and does not wait for the subscriber during checkout. See Events and webhooks for signature verification, raw-body handling, and idempotent receiver code.

Local SDK event map#

These are in-process events from sdk.on; they are not backend webhooks or GraphQL subscriptions.

Event Payload fields and types Description
cart:synced Cart (id: string, items: CartItem[], plus returned cart fields) Cart loaded or synchronized after a mutation.
cart:updated Same Cart payload Successful SDK cart change.
checkout:started cartId: string, idempotencyKey: string Checkout request begins.
checkout:completed cartId: string, idempotencyKey: string Backend confirms checkout response.
checkout:failed Checkout fields plus error: unknown Checkout request failed; original error is preserved.
request:started operation: string GraphQL request or upload starts.
request:completed operation: string, durationMs: number Request succeeds.
request:failed operation: string, durationMs: number, error: unknown Request fails; original error is preserved.

Event handlers run in the SDK process only. Avoid logging auth headers, personal payload data, secrets, or payment details from listeners.

Feature boundary cross-check#

This SDK covers the Malonlin API domains and feature flows that are represented by the deployed GraphQL schema and the website API surface. It does not turn every backend administrative or internal operation into a client feature; staff-only configuration, reconciliation, and maintenance actions remain behind their server authorization boundary.

The current checked-in malonlin-n and website frontend contain no mini-app API or mini-app route contract to expose. Mini-app support is therefore not currently an SDK feature; it should be added only after a backend schema and authorization flow exist. Likewise, browser framework hooks and presentation-only settings are app-specific, not framework-neutral SDK APIs.

Malonlin SDK documentation