DPSDPS Apps@duabalabs/dps-client SDK

@duabalabs/dps-client SDK

Single npm package every DPS-consuming app uses to talk to the platform. Add new checkout types here — every consumer benefits without re-implementing payload boilerplate.

API surface

Generic clients

import {
  createDpsClient,         // browser — uses Parse JS SDK
  createDpsServerClient,   // server  — uses parse/node + master key
  withTenant,              // wraps a client with tenant scope
  type DpsClient,
  type TenantClient,
  type SubscriptionStatus,
} from '@duabalabs/dps-client';

Both clients implement the same DpsClient interface:

MethodPurpose
createCheckout(payload)Low-level — calls dps_checkout_create
verifyPayment(reference)Calls dps_checkout_verify
getOrder(orderId)Reads an Order
listOrders(filter)Lists Orders for the tenant
checkSubscription({ email })Calls dps_subscriptions_check

Tenant-bound helpers

withTenant(client, { appId, storeId, channelToken?, referencePrefix?, defaultCurrency? }) returns a TenantClient with strongly-typed methods for each checkout type:

MethodFlowUse case
createDonation({ amount, customer, campaign?, ... })directOne-off donation (Duabanti)
startSubscription({ tier, planCode, amount, customer, ... })direct + paystackPlanCodeRecurring billing (Duabaconnect, ProxyFidelity)
createInvoice({ reference, amount, customer, description?, ... })vendure if channelToken set, else directAd-hoc pay-link (Duabatrade /pay)
createProductCheckout({ lineItems, customer, ... })vendureCatalog commerce (Sellub-storefront)
verifyPayment(reference)Thank-you page verification
hasActiveSubscription({ email })Auth-gate helper

Browser-direct pattern

For static-export Next.js sites (Duabanti, Duabaconnect, Duabatrade):

// src/lib/dps/client.ts
import Parse from 'parse';
import { createDpsClient, withTenant, type TenantClient } from '@duabalabs/dps-client';
 
const DPS_PARSE_URL = process.env.NEXT_PUBLIC_DPS_PARSE_URL || '';
const DPS_APP_ID    = process.env.NEXT_PUBLIC_DPS_APP_ID    || '';
const DPS_JS_KEY    = process.env.NEXT_PUBLIC_DPS_JS_KEY    || '';
 
let cached: TenantClient | null = null;
 
export function getDps(): TenantClient {
  if (cached) return cached;
  Parse.initialize(DPS_APP_ID, DPS_JS_KEY || undefined);
  Parse.serverURL = DPS_PARSE_URL;
  cached = withTenant(createDpsClient({ Parse: Parse as any }), {
    appId:           process.env.NEXT_PUBLIC_DPS_APP_ID_TAG || 'duabanti',
    storeId:         process.env.NEXT_PUBLIC_DPS_STORE_ID   || 'duabanti',
    referencePrefix: 'DNTI',
    defaultCurrency: 'GHS',
  });
  return cached;
}

Anonymous Parse session required. dps_checkout_create rejects callers with no user and no master key. Before the first checkout from a public landing:

if (!Parse.User.current()) {
  // @ts-ignore
  await Parse.AnonymousUtils.logIn();
}

Server-side pattern

For Next.js apps with API routes (Sellub-storefront) or Type-4 integrators (ProxyFidelity backend):

import Parse from 'parse/node';
import { createDpsServerClient, withTenant } from '@duabalabs/dps-client';
 
Parse.initialize(DPS_APP_ID, DPS_JS_KEY, DPS_MASTER_KEY);
Parse.serverURL = 'https://api.platform.duabalabs.com/parse';
 
const dps = withTenant(
  createDpsServerClient({
    serverUrl: 'https://api.platform.duabalabs.com/parse',
    appId:     DPS_APP_ID,
    masterKey: DPS_MASTER_KEY,
  }),
  { appId: 'sellub', storeId: 'sellub', channelToken: VENDURE_CHANNEL_TOKEN }
);
 
const session = await dps.createProductCheckout({ lineItems, customer });
return Response.redirect(session.checkoutUrl);

Adding a new checkout type

  1. Add the typed helper to packages/dps-client/src/tenant.ts.
  2. If it needs a new server-side handler, add a cloud function under dps-server/src/parse-server/cloud/dps-checkout/.
  3. Bump the SDK version, publish, and update consumer apps.

This is the only place where checkout-flow domain logic should live on the client side. Per-app src/lib/dps/client.ts files should be ~30 lines: just an env-driven getDps() factory.