@duabalabs/dps-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.js +80 -0
- package/dist/tenant.d.ts +105 -0
- package/dist/tenant.js +151 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @duabalabs/dps-client
|
|
2
|
+
|
|
3
|
+
Tiny typed client for the DPS unified checkout / commerce orchestration cloud functions.
|
|
4
|
+
|
|
5
|
+
Two consumption modes:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// 1) Browser (uses Parse JS SDK, public app id + js key only)
|
|
9
|
+
import Parse from "parse";
|
|
10
|
+
import { createDpsClient } from "@duabalabs/dps-client";
|
|
11
|
+
|
|
12
|
+
Parse.initialize(process.env.NEXT_PUBLIC_DPS_APP_ID!, process.env.NEXT_PUBLIC_DPS_JS_KEY!);
|
|
13
|
+
Parse.serverURL = process.env.NEXT_PUBLIC_DPS_SERVER_URL!;
|
|
14
|
+
|
|
15
|
+
const dps = createDpsClient({ Parse });
|
|
16
|
+
const session = await dps.createCheckout({
|
|
17
|
+
appId: "sellub:store-123",
|
|
18
|
+
storeId: "store-123",
|
|
19
|
+
flow: "vendure",
|
|
20
|
+
channelToken: "abc...",
|
|
21
|
+
customer: { email: "shopper@example.com" },
|
|
22
|
+
currency: "GHS",
|
|
23
|
+
lineItems: [{ productVariantId: "42", name: "T-shirt", quantity: 1, unitPrice: 50 }],
|
|
24
|
+
callbackUrl: "https://shop.example.com/checkout/return",
|
|
25
|
+
});
|
|
26
|
+
window.location.href = session.checkoutUrl;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// 2) Server (uses fetch + master key — never ship master key to the browser)
|
|
31
|
+
import { createDpsServerClient } from "@duabalabs/dps-client";
|
|
32
|
+
|
|
33
|
+
const dps = createDpsServerClient({
|
|
34
|
+
serverUrl: process.env.DPS_PARSE_URL!, // e.g. https://dps.example.com/parse
|
|
35
|
+
appId: process.env.DPS_APP_ID!,
|
|
36
|
+
masterKey: process.env.DPS_MASTER_KEY!,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const session = await dps.createCheckout({ ... });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Method surface is identical. Returns identical shapes.
|
|
43
|
+
|
|
44
|
+
## Methods
|
|
45
|
+
|
|
46
|
+
| Method | Cloud function |
|
|
47
|
+
| ------ | -------------- |
|
|
48
|
+
| `createCheckout(input)` | `dps_checkout_create` |
|
|
49
|
+
| `getCheckout(checkoutId)` | `dps_checkout_get` |
|
|
50
|
+
| `cancelCheckout(checkoutId)` | `dps_checkout_cancel` |
|
|
51
|
+
| `getOrder(orderId)` | `dps_orders_get` |
|
|
52
|
+
| `listOrders(input)` | `dps_orders_list` |
|
|
53
|
+
| `verifyPayment(reference)` | `dps_payments_verify` |
|
|
54
|
+
|
|
55
|
+
## Generic by design
|
|
56
|
+
|
|
57
|
+
`appId` is opaque (any app picks its own). `flow` is `"vendure"` or `"direct"`. App-specific concerns (donation `campaignId`, subscription `paystackPlanCode`, shipment payload, etc.) live in `metadata`/`paystackPlanCode`/`splitConfig` and are forwarded to the orchestrator.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @duabalabs/dps-client — typed client for DPS unified checkout cloud functions.
|
|
3
|
+
*
|
|
4
|
+
* Two consumption modes:
|
|
5
|
+
*
|
|
6
|
+
* 1. Browser / public clients (uses Parse JS SDK + a JS key):
|
|
7
|
+
* const dps = createDpsClient({ Parse });
|
|
8
|
+
* await dps.createCheckout({ ... });
|
|
9
|
+
*
|
|
10
|
+
* 2. Server-side / trusted callers (uses fetch + master key — never ship
|
|
11
|
+
* master key to the browser):
|
|
12
|
+
* const dps = createDpsServerClient({
|
|
13
|
+
* serverUrl: process.env.DPS_PARSE_URL,
|
|
14
|
+
* appId: process.env.DPS_APP_ID,
|
|
15
|
+
* masterKey: process.env.DPS_MASTER_KEY,
|
|
16
|
+
* });
|
|
17
|
+
* await dps.createCheckout({ ... });
|
|
18
|
+
*
|
|
19
|
+
* Both expose the same method surface and the same response types.
|
|
20
|
+
*/
|
|
21
|
+
export type CheckoutFlow = "vendure" | "direct";
|
|
22
|
+
export type CheckoutStatus = "pending" | "awaiting_payment" | "paid" | "fulfilled" | "cancelled" | "failed" | "refunded";
|
|
23
|
+
export type OrderStatus = CheckoutStatus;
|
|
24
|
+
export interface CheckoutCustomer {
|
|
25
|
+
email: string;
|
|
26
|
+
firstName?: string;
|
|
27
|
+
lastName?: string;
|
|
28
|
+
phone?: string;
|
|
29
|
+
parseUserId?: string;
|
|
30
|
+
vendureCustomerId?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface CheckoutLineItem {
|
|
33
|
+
productVariantId?: string;
|
|
34
|
+
sku?: string;
|
|
35
|
+
name: string;
|
|
36
|
+
quantity: number;
|
|
37
|
+
unitPrice: number;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
export interface PaystackSplitConfig {
|
|
41
|
+
type: "percentage" | "flat";
|
|
42
|
+
bearer?: "account" | "subaccount" | "all-proportional" | "all";
|
|
43
|
+
subaccounts: Array<{
|
|
44
|
+
subaccountCode: string;
|
|
45
|
+
share: number;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
export interface CreateCheckoutInput {
|
|
49
|
+
appId: string;
|
|
50
|
+
storeId: string;
|
|
51
|
+
flow: CheckoutFlow;
|
|
52
|
+
channelToken?: string;
|
|
53
|
+
customer: CheckoutCustomer;
|
|
54
|
+
currency: string;
|
|
55
|
+
amount?: number;
|
|
56
|
+
lineItems?: CheckoutLineItem[];
|
|
57
|
+
metadata?: Record<string, unknown>;
|
|
58
|
+
callbackUrl?: string;
|
|
59
|
+
splitConfig?: PaystackSplitConfig;
|
|
60
|
+
paystackPlanCode?: string;
|
|
61
|
+
referencePrefix?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface CheckoutSession {
|
|
64
|
+
checkoutId: string;
|
|
65
|
+
orderId: string;
|
|
66
|
+
paymentReference: string;
|
|
67
|
+
checkoutUrl: string;
|
|
68
|
+
accessCode?: string;
|
|
69
|
+
status: CheckoutStatus;
|
|
70
|
+
appId: string;
|
|
71
|
+
flow: CheckoutFlow;
|
|
72
|
+
storeId: string;
|
|
73
|
+
amount: number;
|
|
74
|
+
currency: string;
|
|
75
|
+
expiresAt?: string;
|
|
76
|
+
}
|
|
77
|
+
export interface OrderRecord {
|
|
78
|
+
id: string;
|
|
79
|
+
appId: string;
|
|
80
|
+
storeId: string;
|
|
81
|
+
flow: CheckoutFlow;
|
|
82
|
+
status: OrderStatus;
|
|
83
|
+
amount: number;
|
|
84
|
+
currency: string;
|
|
85
|
+
vendureOrderId?: string;
|
|
86
|
+
vendureOrderCode?: string;
|
|
87
|
+
channelToken?: string;
|
|
88
|
+
customerEmail: string;
|
|
89
|
+
userId?: string;
|
|
90
|
+
lineItems?: CheckoutLineItem[];
|
|
91
|
+
metadata?: Record<string, unknown>;
|
|
92
|
+
createdAt: string;
|
|
93
|
+
updatedAt: string;
|
|
94
|
+
}
|
|
95
|
+
export interface ListOrdersInput {
|
|
96
|
+
storeId: string;
|
|
97
|
+
appId?: string;
|
|
98
|
+
flow?: CheckoutFlow;
|
|
99
|
+
limit?: number;
|
|
100
|
+
skip?: number;
|
|
101
|
+
}
|
|
102
|
+
export interface PaymentVerification {
|
|
103
|
+
status: "success" | "failed" | "abandoned" | "pending" | string;
|
|
104
|
+
reference: string;
|
|
105
|
+
amount: number;
|
|
106
|
+
currency: string;
|
|
107
|
+
raw: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
export interface SubscriptionStatus {
|
|
110
|
+
active: boolean;
|
|
111
|
+
tier: string | null;
|
|
112
|
+
expiresAt: string | null;
|
|
113
|
+
orderId: string | null;
|
|
114
|
+
}
|
|
115
|
+
export interface DpsClient {
|
|
116
|
+
createCheckout(input: CreateCheckoutInput): Promise<CheckoutSession>;
|
|
117
|
+
getCheckout(checkoutId: string): Promise<CheckoutSession | null>;
|
|
118
|
+
cancelCheckout(checkoutId: string): Promise<CheckoutSession | null>;
|
|
119
|
+
getOrder(orderId: string): Promise<OrderRecord | null>;
|
|
120
|
+
listOrders(input: ListOrdersInput): Promise<OrderRecord[]>;
|
|
121
|
+
verifyPayment(reference: string): Promise<PaymentVerification>;
|
|
122
|
+
checkSubscription(input: {
|
|
123
|
+
appId: string;
|
|
124
|
+
email: string;
|
|
125
|
+
}): Promise<SubscriptionStatus>;
|
|
126
|
+
}
|
|
127
|
+
export interface ParseLike {
|
|
128
|
+
Cloud: {
|
|
129
|
+
run: (name: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export declare function createDpsClient(opts: {
|
|
133
|
+
Parse: ParseLike;
|
|
134
|
+
}): DpsClient;
|
|
135
|
+
export interface ServerClientOptions {
|
|
136
|
+
serverUrl: string;
|
|
137
|
+
appId: string;
|
|
138
|
+
masterKey: string;
|
|
139
|
+
/** Optional fetch impl (defaults to globalThis.fetch). */
|
|
140
|
+
fetch?: typeof fetch;
|
|
141
|
+
}
|
|
142
|
+
export declare function createDpsServerClient(opts: ServerClientOptions): DpsClient;
|
|
143
|
+
export { withTenant, type TenantConfig, type TenantClient, type DonationCheckoutInput, type SubscriptionCheckoutInput, type InvoiceCheckoutInput, type ProductCheckoutInput, } from "./tenant";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @duabalabs/dps-client — typed client for DPS unified checkout cloud functions.
|
|
3
|
+
*
|
|
4
|
+
* Two consumption modes:
|
|
5
|
+
*
|
|
6
|
+
* 1. Browser / public clients (uses Parse JS SDK + a JS key):
|
|
7
|
+
* const dps = createDpsClient({ Parse });
|
|
8
|
+
* await dps.createCheckout({ ... });
|
|
9
|
+
*
|
|
10
|
+
* 2. Server-side / trusted callers (uses fetch + master key — never ship
|
|
11
|
+
* master key to the browser):
|
|
12
|
+
* const dps = createDpsServerClient({
|
|
13
|
+
* serverUrl: process.env.DPS_PARSE_URL,
|
|
14
|
+
* appId: process.env.DPS_APP_ID,
|
|
15
|
+
* masterKey: process.env.DPS_MASTER_KEY,
|
|
16
|
+
* });
|
|
17
|
+
* await dps.createCheckout({ ... });
|
|
18
|
+
*
|
|
19
|
+
* Both expose the same method surface and the same response types.
|
|
20
|
+
*/
|
|
21
|
+
function unwrap(res) {
|
|
22
|
+
if (res &&
|
|
23
|
+
typeof res === "object" &&
|
|
24
|
+
"data" in res) {
|
|
25
|
+
return res.data;
|
|
26
|
+
}
|
|
27
|
+
return res;
|
|
28
|
+
}
|
|
29
|
+
export function createDpsClient(opts) {
|
|
30
|
+
const { Parse } = opts;
|
|
31
|
+
const run = async (name, params) => {
|
|
32
|
+
const res = (await Parse.Cloud.run(name, params));
|
|
33
|
+
return unwrap(res);
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
createCheckout: (input) => run("dps_checkout_create", input),
|
|
37
|
+
getCheckout: (checkoutId) => run("dps_checkout_get", { checkoutId }),
|
|
38
|
+
cancelCheckout: (checkoutId) => run("dps_checkout_cancel", { checkoutId }),
|
|
39
|
+
getOrder: (orderId) => run("dps_orders_get", { orderId }),
|
|
40
|
+
listOrders: (input) => run("dps_orders_list", input),
|
|
41
|
+
verifyPayment: (reference) => run("dps_payments_verify", { reference }), checkSubscription: (input) => run("dps_subscriptions_check", input),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function createDpsServerClient(opts) {
|
|
45
|
+
const fetchImpl = opts.fetch || globalThis.fetch;
|
|
46
|
+
if (!fetchImpl) {
|
|
47
|
+
throw new Error("createDpsServerClient: no fetch implementation available. Pass `fetch` explicitly.");
|
|
48
|
+
}
|
|
49
|
+
const baseUrl = opts.serverUrl.replace(/\/$/, "");
|
|
50
|
+
const run = async (name, params = {}) => {
|
|
51
|
+
const res = await fetchImpl(`${baseUrl}/functions/${name}`, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"X-Parse-Application-Id": opts.appId,
|
|
56
|
+
"X-Parse-Master-Key": opts.masterKey,
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(params),
|
|
59
|
+
});
|
|
60
|
+
const json = (await res.json().catch(() => ({})));
|
|
61
|
+
if (!res.ok || (json && "error" in json && json.error)) {
|
|
62
|
+
const msg = json?.error || `DPS ${name} failed (${res.status})`;
|
|
63
|
+
throw new Error(`@duabalabs/dps-client: ${msg}`);
|
|
64
|
+
}
|
|
65
|
+
return unwrap(json?.result);
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
createCheckout: (input) => run("dps_checkout_create", input),
|
|
69
|
+
getCheckout: (checkoutId) => run("dps_checkout_get", { checkoutId }),
|
|
70
|
+
cancelCheckout: (checkoutId) => run("dps_checkout_cancel", { checkoutId }),
|
|
71
|
+
getOrder: (orderId) => run("dps_orders_get", { orderId }),
|
|
72
|
+
listOrders: (input) => run("dps_orders_list", input),
|
|
73
|
+
verifyPayment: (reference) => run("dps_payments_verify", { reference }),
|
|
74
|
+
checkSubscription: (input) => run("dps_subscriptions_check", input),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
78
|
+
// Tenant-bound helpers (donation / subscription / invoice / product)
|
|
79
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
export { withTenant, } from "./tenant";
|
package/dist/tenant.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant-bound, domain-specific checkout helpers.
|
|
3
|
+
*
|
|
4
|
+
* These wrap the generic `DpsClient.createCheckout()` with strongly-typed
|
|
5
|
+
* helpers for each checkout type we support across the Duabalabs ecosystem
|
|
6
|
+
* (donations, subscriptions, invoices, …). New checkout types are added
|
|
7
|
+
* here so every consuming app benefits without re-implementing payload
|
|
8
|
+
* boilerplate.
|
|
9
|
+
*
|
|
10
|
+
* Usage (browser):
|
|
11
|
+
*
|
|
12
|
+
* const tenant = withTenant(createDpsClient({ Parse }), {
|
|
13
|
+
* appId: "duabanti",
|
|
14
|
+
* storeId: "duabanti",
|
|
15
|
+
* referencePrefix: "DNTI",
|
|
16
|
+
* });
|
|
17
|
+
* await tenant.createDonation({ amount: 50, customer: { email } });
|
|
18
|
+
*
|
|
19
|
+
* Usage (server):
|
|
20
|
+
*
|
|
21
|
+
* const tenant = withTenant(createDpsServerClient({ ... }), {
|
|
22
|
+
* appId: "duabaconnect",
|
|
23
|
+
* storeId: "duabaconnect",
|
|
24
|
+
* });
|
|
25
|
+
* await tenant.startSubscription({ tier: "starter", planCode, … });
|
|
26
|
+
*/
|
|
27
|
+
import type { DpsClient, CheckoutSession, CheckoutCustomer, CheckoutLineItem, CheckoutFlow, PaymentVerification, SubscriptionStatus, PaystackSplitConfig } from "./index";
|
|
28
|
+
export interface TenantConfig {
|
|
29
|
+
/** Opaque tenant tag — e.g. `duabanti`, `duabaconnect`, `duabatrade`. */
|
|
30
|
+
appId: string;
|
|
31
|
+
/** Sellub `storeId` for this tenant (or a tenant-specific id when not on sellub). */
|
|
32
|
+
storeId: string;
|
|
33
|
+
/** Optional: when set, vendure-flow checkouts can be created. */
|
|
34
|
+
channelToken?: string;
|
|
35
|
+
/** Optional default reference prefix passed to dps-server. */
|
|
36
|
+
referencePrefix?: string;
|
|
37
|
+
/** Optional default currency (ISO 4217) — defaults to `GHS`. */
|
|
38
|
+
defaultCurrency?: string;
|
|
39
|
+
/** Optional default Paystack split config applied when caller doesn't override. */
|
|
40
|
+
defaultSplitConfig?: PaystackSplitConfig;
|
|
41
|
+
}
|
|
42
|
+
export interface DonationCheckoutInput {
|
|
43
|
+
amount: number;
|
|
44
|
+
currency?: string;
|
|
45
|
+
customer: CheckoutCustomer;
|
|
46
|
+
campaign?: string;
|
|
47
|
+
callbackUrl?: string;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
export interface SubscriptionCheckoutInput {
|
|
51
|
+
/** Free-form tier id e.g. `starter`, `growth`, `pro`. */
|
|
52
|
+
tier: string;
|
|
53
|
+
/** Paystack subscription plan code (server creates the subscription against this). */
|
|
54
|
+
planCode: string;
|
|
55
|
+
/** First charge amount in major units. */
|
|
56
|
+
amount: number;
|
|
57
|
+
currency?: string;
|
|
58
|
+
customer: CheckoutCustomer;
|
|
59
|
+
callbackUrl?: string;
|
|
60
|
+
metadata?: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
export interface InvoiceCheckoutInput {
|
|
63
|
+
/** Human-facing invoice / quote reference. Stored in `metadata.reference`. */
|
|
64
|
+
reference: string;
|
|
65
|
+
amount: number;
|
|
66
|
+
currency?: string;
|
|
67
|
+
customer: CheckoutCustomer;
|
|
68
|
+
description?: string;
|
|
69
|
+
/** Optional explicit line items (otherwise a single line is synthesized for vendure flow). */
|
|
70
|
+
lineItems?: CheckoutLineItem[];
|
|
71
|
+
/** Force a flow. Defaults to `vendure` when `channelToken` is set, else `direct`. */
|
|
72
|
+
flow?: CheckoutFlow;
|
|
73
|
+
callbackUrl?: string;
|
|
74
|
+
metadata?: Record<string, unknown>;
|
|
75
|
+
}
|
|
76
|
+
export interface ProductCheckoutInput {
|
|
77
|
+
lineItems: CheckoutLineItem[];
|
|
78
|
+
customer: CheckoutCustomer;
|
|
79
|
+
currency?: string;
|
|
80
|
+
/** Optional override; defaults to summing `lineItems`. */
|
|
81
|
+
amount?: number;
|
|
82
|
+
callbackUrl?: string;
|
|
83
|
+
metadata?: Record<string, unknown>;
|
|
84
|
+
splitConfig?: PaystackSplitConfig;
|
|
85
|
+
}
|
|
86
|
+
export interface TenantClient {
|
|
87
|
+
/** Underlying generic DPS client — escape hatch for advanced calls. */
|
|
88
|
+
readonly client: DpsClient;
|
|
89
|
+
readonly config: Readonly<TenantConfig>;
|
|
90
|
+
/** Charge a one-off donation through the `direct` flow. */
|
|
91
|
+
createDonation(input: DonationCheckoutInput): Promise<CheckoutSession>;
|
|
92
|
+
/** Start a Paystack-backed recurring subscription via the `direct` flow + plan code. */
|
|
93
|
+
startSubscription(input: SubscriptionCheckoutInput): Promise<CheckoutSession>;
|
|
94
|
+
/** Pay an invoice / quote (vendure when channelToken is configured, else direct). */
|
|
95
|
+
createInvoice(input: InvoiceCheckoutInput): Promise<CheckoutSession>;
|
|
96
|
+
/** Catalog product checkout — requires line items + a configured channel for vendure. */
|
|
97
|
+
createProductCheckout(input: ProductCheckoutInput): Promise<CheckoutSession>;
|
|
98
|
+
/** Verify a Paystack reference (use on thank-you / callback pages). */
|
|
99
|
+
verifyPayment(reference: string): Promise<PaymentVerification>;
|
|
100
|
+
/** Check whether a customer email has an active subscription with this tenant. */
|
|
101
|
+
hasActiveSubscription(input: {
|
|
102
|
+
email: string;
|
|
103
|
+
}): Promise<SubscriptionStatus>;
|
|
104
|
+
}
|
|
105
|
+
export declare function withTenant(client: DpsClient, config: TenantConfig): TenantClient;
|
package/dist/tenant.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant-bound, domain-specific checkout helpers.
|
|
3
|
+
*
|
|
4
|
+
* These wrap the generic `DpsClient.createCheckout()` with strongly-typed
|
|
5
|
+
* helpers for each checkout type we support across the Duabalabs ecosystem
|
|
6
|
+
* (donations, subscriptions, invoices, …). New checkout types are added
|
|
7
|
+
* here so every consuming app benefits without re-implementing payload
|
|
8
|
+
* boilerplate.
|
|
9
|
+
*
|
|
10
|
+
* Usage (browser):
|
|
11
|
+
*
|
|
12
|
+
* const tenant = withTenant(createDpsClient({ Parse }), {
|
|
13
|
+
* appId: "duabanti",
|
|
14
|
+
* storeId: "duabanti",
|
|
15
|
+
* referencePrefix: "DNTI",
|
|
16
|
+
* });
|
|
17
|
+
* await tenant.createDonation({ amount: 50, customer: { email } });
|
|
18
|
+
*
|
|
19
|
+
* Usage (server):
|
|
20
|
+
*
|
|
21
|
+
* const tenant = withTenant(createDpsServerClient({ ... }), {
|
|
22
|
+
* appId: "duabaconnect",
|
|
23
|
+
* storeId: "duabaconnect",
|
|
24
|
+
* });
|
|
25
|
+
* await tenant.startSubscription({ tier: "starter", planCode, … });
|
|
26
|
+
*/
|
|
27
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Implementation
|
|
29
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
function defaultCurrency(cfg, override) {
|
|
31
|
+
return override || cfg.defaultCurrency || "GHS";
|
|
32
|
+
}
|
|
33
|
+
function baseFields(cfg) {
|
|
34
|
+
return {
|
|
35
|
+
appId: cfg.appId,
|
|
36
|
+
storeId: cfg.storeId,
|
|
37
|
+
referencePrefix: cfg.referencePrefix,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function withTenant(client, config) {
|
|
41
|
+
if (!config.appId)
|
|
42
|
+
throw new Error("withTenant: `appId` is required");
|
|
43
|
+
if (!config.storeId)
|
|
44
|
+
throw new Error("withTenant: `storeId` is required");
|
|
45
|
+
const cfg = { ...config };
|
|
46
|
+
return {
|
|
47
|
+
client,
|
|
48
|
+
config: cfg,
|
|
49
|
+
createDonation(input) {
|
|
50
|
+
const payload = {
|
|
51
|
+
...baseFields(cfg),
|
|
52
|
+
flow: "direct",
|
|
53
|
+
currency: defaultCurrency(cfg, input.currency),
|
|
54
|
+
amount: input.amount,
|
|
55
|
+
customer: input.customer,
|
|
56
|
+
callbackUrl: input.callbackUrl,
|
|
57
|
+
splitConfig: cfg.defaultSplitConfig,
|
|
58
|
+
metadata: {
|
|
59
|
+
type: "donation",
|
|
60
|
+
campaign: input.campaign,
|
|
61
|
+
source: cfg.appId,
|
|
62
|
+
...input.metadata,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
return client.createCheckout(payload);
|
|
66
|
+
},
|
|
67
|
+
startSubscription(input) {
|
|
68
|
+
if (!input.planCode) {
|
|
69
|
+
throw new Error(`startSubscription: planCode is required (no Paystack plan configured for tier "${input.tier}").`);
|
|
70
|
+
}
|
|
71
|
+
const payload = {
|
|
72
|
+
...baseFields(cfg),
|
|
73
|
+
flow: "direct",
|
|
74
|
+
currency: defaultCurrency(cfg, input.currency),
|
|
75
|
+
amount: input.amount,
|
|
76
|
+
customer: input.customer,
|
|
77
|
+
paystackPlanCode: input.planCode,
|
|
78
|
+
callbackUrl: input.callbackUrl,
|
|
79
|
+
splitConfig: cfg.defaultSplitConfig,
|
|
80
|
+
metadata: {
|
|
81
|
+
type: "subscription",
|
|
82
|
+
tier: input.tier,
|
|
83
|
+
source: cfg.appId,
|
|
84
|
+
...input.metadata,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
return client.createCheckout(payload);
|
|
88
|
+
},
|
|
89
|
+
createInvoice(input) {
|
|
90
|
+
const flow = input.flow || (cfg.channelToken ? "vendure" : "direct");
|
|
91
|
+
const lineItems = input.lineItems ||
|
|
92
|
+
(flow === "vendure"
|
|
93
|
+
? [
|
|
94
|
+
{
|
|
95
|
+
name: input.description || `Invoice ${input.reference}`,
|
|
96
|
+
quantity: 1,
|
|
97
|
+
unitPrice: input.amount,
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
: undefined);
|
|
101
|
+
const payload = {
|
|
102
|
+
...baseFields(cfg),
|
|
103
|
+
flow,
|
|
104
|
+
channelToken: flow === "vendure" ? cfg.channelToken : undefined,
|
|
105
|
+
currency: defaultCurrency(cfg, input.currency),
|
|
106
|
+
amount: input.amount,
|
|
107
|
+
lineItems,
|
|
108
|
+
customer: input.customer,
|
|
109
|
+
callbackUrl: input.callbackUrl,
|
|
110
|
+
splitConfig: cfg.defaultSplitConfig,
|
|
111
|
+
metadata: {
|
|
112
|
+
type: "invoice",
|
|
113
|
+
reference: input.reference,
|
|
114
|
+
description: input.description,
|
|
115
|
+
source: cfg.appId,
|
|
116
|
+
...input.metadata,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
return client.createCheckout(payload);
|
|
120
|
+
},
|
|
121
|
+
createProductCheckout(input) {
|
|
122
|
+
const flow = cfg.channelToken ? "vendure" : "direct";
|
|
123
|
+
if (flow === "vendure" && !cfg.channelToken) {
|
|
124
|
+
throw new Error("createProductCheckout: vendure flow requires channelToken on TenantConfig.");
|
|
125
|
+
}
|
|
126
|
+
const payload = {
|
|
127
|
+
...baseFields(cfg),
|
|
128
|
+
flow,
|
|
129
|
+
channelToken: cfg.channelToken,
|
|
130
|
+
currency: defaultCurrency(cfg, input.currency),
|
|
131
|
+
amount: input.amount,
|
|
132
|
+
lineItems: input.lineItems,
|
|
133
|
+
customer: input.customer,
|
|
134
|
+
callbackUrl: input.callbackUrl,
|
|
135
|
+
splitConfig: input.splitConfig || cfg.defaultSplitConfig,
|
|
136
|
+
metadata: {
|
|
137
|
+
type: "product",
|
|
138
|
+
source: cfg.appId,
|
|
139
|
+
...input.metadata,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
return client.createCheckout(payload);
|
|
143
|
+
},
|
|
144
|
+
verifyPayment(reference) {
|
|
145
|
+
return client.verifyPayment(reference);
|
|
146
|
+
},
|
|
147
|
+
hasActiveSubscription(input) {
|
|
148
|
+
return client.checkSubscription({ appId: cfg.appId, email: input.email });
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@duabalabs/dps-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed client for the DPS unified checkout/commerce orchestration cloud functions.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist", "README.md"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"parse": ">=4 <6"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"parse": "^5.3.0",
|
|
29
|
+
"typescript": "^5.4.0"
|
|
30
|
+
}
|
|
31
|
+
}
|