@waffo/pancake-nextjs 0.8.0 → 0.9.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/CHANGELOG.md +15 -0
- package/README.md +56 -8
- package/dist/server.cjs +51 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +108 -1
- package/dist/server.d.ts +108 -1
- package/dist/server.js +50 -0
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to `@waffo/pancake-nextjs` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.9.0] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
Customers signed in to a merchant's app can enter the store's customer portal without email verification.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`createCustomerPortalRedirect(config, { resolveAuthenticatedCustomer, loginUrl?, onError? })`** in `@waffo/pancake-nextjs/server` — returns a GET Route Handler. It asks your `resolveAuthenticatedCustomer(request)` for `{ storeId, buyerIdentity }` from your own authenticated session, calls `client.auth.createCustomerPortalLink()` and answers `302` to the tokenized portal URL. A `null` resolver result answers `302` to `loginUrl`, or `401` when it is not set; a failed link creation goes to `onError`, or a generic `502`. Every response the handler builds sets `Cache-Control: no-store` and `Referrer-Policy: no-referrer`. The SDK reads no identity from the request itself.
|
|
14
|
+
- **Default error logging in `createCustomerPortalRedirect`.** Without `onError`, a failed link creation writes one `console.error` line — `[waffo-pancake] createCustomerPortalRedirect failed: <error message>` — before the generic `502`. Only the message is logged, never the error object; a `#token=` fragment is redacted. With `onError` nothing is logged: that is where your own logging goes.
|
|
15
|
+
- **`AuthenticatedPortalCustomer`, `CustomerPortalRedirectOptions`, `CustomerPortalRouteHandler`** types.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`createCustomerTokenAction` documentation** states that it is a generic token primitive and must not be used as an unauthenticated portal entry. Its behavior is unchanged.
|
|
20
|
+
- **`@waffo/pancake-ts` dependency raised to `^0.26.0`** — the version that adds `auth.createCustomerPortalLink()`. The range also picks up 0.24.0 (webhook amount fields; the six generic amount fields are deprecated, not removed) and 0.25.0 (refund payloads carry what the refunded payment collected); neither is breaking.
|
|
21
|
+
|
|
7
22
|
## [0.8.0] - 2026-09-20
|
|
8
23
|
|
|
9
24
|
Picks up plan change links from `@waffo/pancake-ts` 0.23.0, **and its idempotency change** — see the BREAKING entries under Changed before upgrading.
|
package/README.md
CHANGED
|
@@ -386,6 +386,53 @@ function AccountPage() {
|
|
|
386
386
|
|
|
387
387
|
Action hooks return `{ execute, isLoading, error, data }`. Data hooks return `{ data, isLoading, error, refetch }`.
|
|
388
388
|
|
|
389
|
+
## Customer Portal (SSO)
|
|
390
|
+
|
|
391
|
+
Send a customer who is signed in to **your** app straight into your store's Pancake customer portal — orders, subscriptions and payments — without the portal's email verification step. `createCustomerPortalRedirect` returns a GET Route Handler; link to it from your UI.
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
// app/account/billing-portal/route.ts
|
|
395
|
+
import { createCustomerPortalRedirect } from "@waffo/pancake-nextjs/server";
|
|
396
|
+
import { auth } from "@/lib/auth"; // your auth library
|
|
397
|
+
|
|
398
|
+
export const dynamic = "force-dynamic"; // per-visit session check and redirect, never statically cached
|
|
399
|
+
|
|
400
|
+
export const GET = createCustomerPortalRedirect(
|
|
401
|
+
{ merchantId: process.env.WAFFO_MERCHANT_ID!, privateKey: process.env.WAFFO_PRIVATE_KEY! },
|
|
402
|
+
{
|
|
403
|
+
// Identity comes from YOUR session — never from the request's query, body or headers
|
|
404
|
+
resolveAuthenticatedCustomer: async () => {
|
|
405
|
+
const session = await auth();
|
|
406
|
+
if (!session?.user) return null;
|
|
407
|
+
return { storeId: process.env.WAFFO_STORE_ID!, buyerIdentity: session.user.id };
|
|
408
|
+
},
|
|
409
|
+
loginUrl: "/login?returnTo=/account/billing-portal", // omit to answer 401
|
|
410
|
+
},
|
|
411
|
+
);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
<a href="/account/billing-portal">Manage billing</a>
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
| Situation | Response |
|
|
419
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
420
|
+
| Resolver returns a customer | `302` to the portal URL (carries `#token=...`) |
|
|
421
|
+
| Resolver returns `null` | `302` to `loginUrl`, or `401 { error: "Unauthorized" }` when `loginUrl` is not set |
|
|
422
|
+
| Portal link creation fails | `onError(error, request)` if given, otherwise one `console.error` line and `502 { error }` with a generic message |
|
|
423
|
+
| Resolver throws | The error propagates (Next.js answers 500) |
|
|
424
|
+
|
|
425
|
+
Every response the handler builds (all but a custom `onError` one) carries `Cache-Control: no-store` and `Referrer-Policy: no-referrer`, so the tokenized `Location` is neither cached nor forwarded as a Referer.
|
|
426
|
+
|
|
427
|
+
- **Why a Route Handler, not a Server Action**: the portal entry is a plain link navigation. An HTTP redirect keeps the tokenized portal URL out of client JavaScript and lets the response set `no-store` / `no-referrer`; a Server Action would have to hand the URL to the browser to navigate. It follows the same factory shape as `Webhook()`, but lives in `/server` because it holds the private key.
|
|
428
|
+
- **`buyerIdentity` must be the value you use for authenticated checkout**, so the portal shows the orders placed under it. Whoever controls `buyerIdentity` gets that customer's portal — the resolver is the security boundary.
|
|
429
|
+
- **Environment follows the API Key** in `config`: a test key opens the test portal, a prod key the prod portal. `config.environment` is not used here.
|
|
430
|
+
- **When the portal session expires**, the customer comes back through this route and gets a new link.
|
|
431
|
+
- **Logging is yours to add in `onError`.** Without it, the handler writes one server-side line — `[waffo-pancake] createCustomerPortalRedirect failed: <error message>`, with any `#token=` fragment redacted — and never the error object, the portal URL or the request. Pass `onError` to send failures to your own logger or error tracker, and keep tokens and portal URLs out of what you record.
|
|
432
|
+
- **Do not build `loginUrl` by copying request parameters verbatim.** A string or function that echoes, say, `?next=` from the request into the redirect turns this route into an open redirect. Use constants, or validate against an allow-list of your own paths.
|
|
433
|
+
|
|
434
|
+
> **Do not build a portal entry on `createCustomerTokenAction`.** It is a generic token primitive that signs whatever `buyerIdentity` it is called with, and a Server Action can be invoked from the browser with arbitrary arguments. Exposed as an unauthenticated portal or sign-in entry, it hands out any customer's token. Use `createCustomerPortalRedirect`, which only takes identity from your resolver.
|
|
435
|
+
|
|
389
436
|
## Business-Side Identifiers
|
|
390
437
|
|
|
391
438
|
Attach your own internal references to a checkout or a refund ticket so cross-system reconciliation does not require Waffo IDs. Two flat keys, both optional (max 128 chars):
|
|
@@ -459,14 +506,15 @@ All merchant hooks return `{ data, isLoading, error, refetch }`.
|
|
|
459
506
|
|
|
460
507
|
## Server Actions
|
|
461
508
|
|
|
462
|
-
| Factory
|
|
463
|
-
|
|
|
464
|
-
| `createCheckoutAction(config)`
|
|
465
|
-
| `createCustomerTokenAction(config)`
|
|
466
|
-
| `
|
|
467
|
-
| `
|
|
509
|
+
| Factory | Returns | Description |
|
|
510
|
+
| ----------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
511
|
+
| `createCheckoutAction(config)` | `CheckoutAction` | Checkout session creation (anonymous + authenticated) and plan change links (`planChange` + `authenticatedPlanChange`) |
|
|
512
|
+
| `createCustomerTokenAction(config)` | `CustomerTokenAction` | Customer session token issuance |
|
|
513
|
+
| `createCustomerPortalRedirect(config, options)` | `CustomerPortalRouteHandler` | GET Route Handler: 302 to the customer portal for the customer your session resolver returns — see [Customer Portal (SSO)](#customer-portal-sso) |
|
|
514
|
+
| `createCustomerSessionAction(config)` | `CustomerSessionAction` | Customer self-service operations |
|
|
515
|
+
| `createMerchantQueryAction(config)` | `MerchantQueryAction` | Merchant GraphQL queries |
|
|
468
516
|
|
|
469
|
-
Import from `@waffo/pancake-nextjs/server`. Config requires `merchantId` and `privateKey`.
|
|
517
|
+
Import from `@waffo/pancake-nextjs/server`. Config requires `merchantId` and `privateKey`. `createCustomerTokenAction` signs any `buyerIdentity` it receives: call it only with identity from your own session, never as an unauthenticated portal or sign-in entry.
|
|
470
518
|
|
|
471
519
|
## Idempotency
|
|
472
520
|
|
|
@@ -499,7 +547,7 @@ Uniqueness is yours to guarantee (at most 256 characters of letters, numbers, hy
|
|
|
499
547
|
|
|
500
548
|
### Types
|
|
501
549
|
|
|
502
|
-
Key types: `PriceInfo`, `PriceSnapshot`, `BillingDetail`, `RequestOptions`, `WebhookEvent`, `CheckoutAction`, `CustomerTokenAction`, `CustomerSessionAction`, `MerchantQueryAction`, `CustomerConfig`, `CheckoutButtonProps`, `CheckoutMode`, `CashierLanguage`, `UseCheckoutReturn`, `UseCustomerReturn`, `CustomerActionState<T>`, `QueryState<T>`, `SalesOverview`, `SubscriptionOverview`, `WebhookConfig`.
|
|
550
|
+
Key types: `PriceInfo`, `PriceSnapshot`, `BillingDetail`, `RequestOptions`, `WebhookEvent`, `CheckoutAction`, `CustomerTokenAction`, `CustomerSessionAction`, `MerchantQueryAction`, `CustomerPortalRouteHandler`, `CustomerPortalRedirectOptions`, `AuthenticatedPortalCustomer`, `CustomerConfig`, `CheckoutButtonProps`, `CheckoutMode`, `CashierLanguage`, `UseCheckoutReturn`, `UseCustomerReturn`, `CustomerActionState<T>`, `QueryState<T>`, `SalesOverview`, `SubscriptionOverview`, `WebhookConfig`.
|
|
503
551
|
|
|
504
552
|
## Development
|
|
505
553
|
|
package/dist/server.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(server_exports, {
|
|
|
23
23
|
createBuyerSessionAction: () => createBuyerSessionAction,
|
|
24
24
|
createBuyerTokenAction: () => createBuyerTokenAction,
|
|
25
25
|
createCheckoutAction: () => createCheckoutAction,
|
|
26
|
+
createCustomerPortalRedirect: () => createCustomerPortalRedirect,
|
|
26
27
|
createCustomerSessionAction: () => createCustomerSessionAction,
|
|
27
28
|
createCustomerTokenAction: () => createCustomerTokenAction,
|
|
28
29
|
createMerchantQueryAction: () => createMerchantQueryAction
|
|
@@ -54,6 +55,55 @@ function createCustomerTokenAction(config) {
|
|
|
54
55
|
return client.auth.issueSessionToken(params, options);
|
|
55
56
|
};
|
|
56
57
|
}
|
|
58
|
+
function portalHeaders(extra) {
|
|
59
|
+
const headers = new Headers(extra);
|
|
60
|
+
headers.set("Cache-Control", "no-store");
|
|
61
|
+
headers.set("Referrer-Policy", "no-referrer");
|
|
62
|
+
return headers;
|
|
63
|
+
}
|
|
64
|
+
function safeErrorMessage(error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
return message.replace(/#token=[^\s"'&]*/g, "#token=[redacted]").replace(/\s+/g, " ");
|
|
67
|
+
}
|
|
68
|
+
function createCustomerPortalRedirect(config, options) {
|
|
69
|
+
const client = new import_pancake_ts.WaffoPancake(config);
|
|
70
|
+
return async function GET(request) {
|
|
71
|
+
const customer = await options.resolveAuthenticatedCustomer(request);
|
|
72
|
+
if (!customer) {
|
|
73
|
+
if (options.loginUrl === void 0) {
|
|
74
|
+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
75
|
+
status: 401,
|
|
76
|
+
headers: portalHeaders({ "Content-Type": "application/json" })
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const loginUrl = typeof options.loginUrl === "function" ? options.loginUrl(request) : options.loginUrl;
|
|
80
|
+
return new Response(null, {
|
|
81
|
+
status: 302,
|
|
82
|
+
headers: portalHeaders({ Location: new URL(loginUrl, request.url).toString() })
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
let portalUrl;
|
|
86
|
+
try {
|
|
87
|
+
({ portalUrl } = await client.auth.createCustomerPortalLink({
|
|
88
|
+
storeId: customer.storeId,
|
|
89
|
+
buyerIdentity: customer.buyerIdentity
|
|
90
|
+
}));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (options.onError) {
|
|
93
|
+
return options.onError(error, request);
|
|
94
|
+
}
|
|
95
|
+
console.error("[waffo-pancake] createCustomerPortalRedirect failed:", safeErrorMessage(error));
|
|
96
|
+
return new Response(JSON.stringify({ error: "Failed to create customer portal link" }), {
|
|
97
|
+
status: 502,
|
|
98
|
+
headers: portalHeaders({ "Content-Type": "application/json" })
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return new Response(null, {
|
|
102
|
+
status: 302,
|
|
103
|
+
headers: portalHeaders({ Location: portalUrl })
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
}
|
|
57
107
|
function createCustomerSessionAction(config) {
|
|
58
108
|
const client = new import_pancake_ts.WaffoPancake(config);
|
|
59
109
|
return async (token, actionType, params, options) => {
|
|
@@ -91,6 +141,7 @@ var createBuyerSessionAction = createCustomerSessionAction;
|
|
|
91
141
|
createBuyerSessionAction,
|
|
92
142
|
createBuyerTokenAction,
|
|
93
143
|
createCheckoutAction,
|
|
144
|
+
createCustomerPortalRedirect,
|
|
94
145
|
createCustomerSessionAction,
|
|
95
146
|
createCustomerTokenAction,
|
|
96
147
|
createMerchantQueryAction
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server-side entry point for @waffo/pancake-nextjs.\n *\n * All functions in this module use the private key for RSA signing\n * and must only run on the server (Next.js Server Actions / Route Handlers).\n *\n * @example\n * ```ts\n * // app/lib/waffo.ts\n * \"use server\";\n * import { createCheckoutAction, createCustomerTokenAction, createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * const config = {\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * };\n *\n * export const checkout = createCheckoutAction(config);\n * export const issueCustomerToken = createCustomerTokenAction(config);\n * export const merchantQuery = createMerchantQueryAction(config);\n * ```\n */\nimport { WaffoPancake } from \"@waffo/pancake-ts\";\n\nimport type {\n RequestOptions,\n WaffoPancakeConfig,\n AnonymousCheckoutParams,\n AuthenticatedCheckoutParams,\n AuthenticatedPlanChangeParams,\n CreatePlanChangeSessionParams,\n CheckoutSessionResult,\n AuthenticatedCheckoutResult,\n IssueSessionTokenParams,\n SessionToken,\n GraphQLParams,\n GraphQLResponse,\n} from \"@waffo/pancake-ts\";\n\n// ============================================================\n// Checkout Action\n// ============================================================\n\n/**\n * Parameters for the checkout server action.\n *\n * The `type` field selects the flow: a new purchase (`anonymous` / `authenticated`)\n * or a plan change for an existing subscription (`planChange` /\n * `authenticatedPlanChange`, which carry the required `originOrderId`).\n */\nexport type CheckoutActionParams =\n | ({ type?: \"anonymous\" } & AnonymousCheckoutParams)\n | ({ type: \"authenticated\" } & AuthenticatedCheckoutParams)\n | ({ type: \"planChange\" } & CreatePlanChangeSessionParams)\n | ({ type: \"authenticatedPlanChange\" } & AuthenticatedPlanChangeParams);\n\n/** Result of the checkout server action */\nexport type CheckoutActionResult = CheckoutSessionResult | AuthenticatedCheckoutResult;\n\n/**\n * Server action signature for checkout.\n *\n * The optional `options` carries an `idempotencyKey`; without one no key is sent\n * and a retried call creates a second session.\n */\nexport type CheckoutAction = (params: CheckoutActionParams, options?: RequestOptions) => Promise<CheckoutActionResult>;\n\n/**\n * Create a server action that handles checkout session creation.\n *\n * The private key is captured in the closure and never sent to the client.\n *\n * Handles both new purchases and plan changes; `params.type` selects the flow.\n *\n * @param config - WaffoPancake client configuration (merchantId + privateKey)\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCheckoutAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const checkout = createCheckoutAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n *\n * @example\n * ```ts\n * // Plan change link for an existing subscription — originOrderId is required\n * const session = await checkout({\n * type: \"planChange\",\n * originOrderId: \"ORD_xxx\",\n * productId: \"PROD_target_plan\",\n * currency: \"USD\",\n * });\n * // session.checkoutUrl points at the change confirmation page\n * ```\n */\nexport function createCheckoutAction(config: WaffoPancakeConfig): CheckoutAction {\n const client = new WaffoPancake(config);\n\n return async (params: CheckoutActionParams, options?: RequestOptions): Promise<CheckoutActionResult> => {\n if (params.type === \"authenticated\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.create(sdkParams, options);\n }\n if (params.type === \"planChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.createPlanChangeSession(sdkParams, options);\n }\n if (params.type === \"authenticatedPlanChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.createPlanChange(sdkParams, options);\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.anonymous.create(sdkParams, options);\n };\n}\n\n// ============================================================\n// Customer Token Action\n// ============================================================\n\n/** Server action signature for issuing customer tokens */\nexport type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOptions) => Promise<SessionToken>;\n\n/**\n * Create a server action that issues customer session tokens.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerTokenAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const issueCustomerToken = createCustomerTokenAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createCustomerTokenAction(config: WaffoPancakeConfig): CustomerTokenAction {\n const client = new WaffoPancake(config);\n\n return async (params: IssueSessionTokenParams, options?: RequestOptions): Promise<SessionToken> => {\n return client.auth.issueSessionToken(params, options);\n };\n}\n\n// ============================================================\n// Customer Session Action\n// ============================================================\n\n/** Customer action types */\nexport type CustomerSessionActionType =\n | \"cancelSubscription\"\n | \"cancelOnetimeOrder\"\n | \"reactivateSubscription\"\n | \"createRefundTicket\"\n | \"resubmitRefundTicket\"\n | \"createPlanChangeSession\"\n | \"query\";\n\n/** Server action signature for customer session operations */\nexport type CustomerSessionAction = (\n token: string,\n actionType: CustomerSessionActionType,\n params: unknown,\n options?: RequestOptions,\n) => Promise<unknown>;\n\n/**\n * Create a server action that executes customer self-service operations.\n *\n * `config.environment` is required — session tokens carry no environment, and the\n * gateway rejects a session request without the matching header.\n *\n * `createPlanChangeSession` is the customer-driven half of a plan change: it returns\n * a confirmation-page URL, and the platform allows it only when the subscription\n * belongs to that customer, the target plan is in the same product group, and that\n * group's `selfServicePlanChange` is on — otherwise 403. The merchant-only pricing\n * fields are not part of its params; the platform drops them on this path silently.\n *\n * No call sends an idempotency key unless you pass one (`options.idempotencyKey`,\n * the last argument) — the same rule as every other method in the SDK — so a write\n * retried after a timeout executes twice.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerSessionAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const customerAction = createCustomerSessionAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * environment: \"test\",\n * });\n * ```\n */\nexport function createCustomerSessionAction(config: WaffoPancakeConfig): CustomerSessionAction {\n const client = new WaffoPancake(config);\n\n return async (token: string, actionType: CustomerSessionActionType, params: unknown, options?: RequestOptions): Promise<unknown> => {\n const customer = client.buyer(token);\n switch (actionType) {\n case \"cancelSubscription\":\n return customer.cancelSubscription(params as Parameters<typeof customer.cancelSubscription>[0], options);\n case \"cancelOnetimeOrder\":\n return customer.cancelOnetimeOrder(params as Parameters<typeof customer.cancelOnetimeOrder>[0], options);\n case \"reactivateSubscription\":\n return customer.reactivateSubscription(params as Parameters<typeof customer.reactivateSubscription>[0], options);\n case \"createRefundTicket\":\n return customer.createRefundTicket(params as Parameters<typeof customer.createRefundTicket>[0], options);\n case \"resubmitRefundTicket\":\n return customer.resubmitRefundTicket(params as Parameters<typeof customer.resubmitRefundTicket>[0], options);\n case \"createPlanChangeSession\":\n return customer.createPlanChangeSession(params as Parameters<typeof customer.createPlanChangeSession>[0], options);\n case \"query\":\n // Reads take no key: a cached replay would serve stale data.\n return customer.graphql.query(params as GraphQLParams);\n default:\n throw new Error(`Unknown customer action: ${actionType}`);\n }\n };\n}\n\n// ============================================================\n// Merchant Query Action\n// ============================================================\n\n/** Server action signature for merchant GraphQL queries */\nexport type MerchantQueryAction = (params: GraphQLParams) => Promise<GraphQLResponse>;\n\n/**\n * Create a server action that executes merchant GraphQL queries.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const merchantQuery = createMerchantQueryAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createMerchantQueryAction(config: WaffoPancakeConfig): MerchantQueryAction {\n const client = new WaffoPancake(config);\n\n return async (params: GraphQLParams): Promise<GraphQLResponse> => {\n return client.graphql.query(params);\n };\n}\n\n// ============================================================\n// Deprecated Aliases\n// ============================================================\n\n/** @deprecated Use {@link CustomerTokenAction} instead. */\nexport type BuyerTokenAction = CustomerTokenAction;\n\n/** @deprecated Use {@link createCustomerTokenAction} instead. */\nexport const createBuyerTokenAction = createCustomerTokenAction;\n\n/** @deprecated Use {@link CustomerSessionActionType} instead. */\nexport type BuyerSessionActionType = CustomerSessionActionType;\n\n/** @deprecated Use {@link CustomerSessionAction} instead. */\nexport type BuyerSessionAction = CustomerSessionAction;\n\n/** @deprecated Use {@link createCustomerSessionAction} instead. */\nexport const createBuyerSessionAction = createCustomerSessionAction;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,wBAA6B;AA8EtB,SAAS,qBAAqB,QAA4C;AAC/E,QAAM,SAAS,IAAI,+BAAa,MAAM;AAEtC,SAAO,OAAO,QAA8B,YAA4D;AACtG,QAAI,OAAO,SAAS,iBAAiB;AAEnC,YAAM,EAAE,MAAAA,OAAM,GAAGC,WAAU,IAAI;AAC/B,aAAO,OAAO,SAAS,cAAc,OAAOA,YAAW,OAAO;AAAA,IAChE;AACA,QAAI,OAAO,SAAS,cAAc;AAEhC,YAAM,EAAE,MAAAD,OAAM,GAAGC,WAAU,IAAI;AAC/B,aAAO,OAAO,SAAS,wBAAwBA,YAAW,OAAO;AAAA,IACnE;AACA,QAAI,OAAO,SAAS,2BAA2B;AAE7C,YAAM,EAAE,MAAAD,OAAM,GAAGC,WAAU,IAAI;AAC/B,aAAO,OAAO,SAAS,cAAc,iBAAiBA,YAAW,OAAO;AAAA,IAC1E;AAEA,UAAM,EAAE,MAAM,GAAG,UAAU,IAAI;AAC/B,WAAO,OAAO,SAAS,UAAU,OAAO,WAAW,OAAO;AAAA,EAC5D;AACF;AA0BO,SAAS,0BAA0B,QAAiD;AACzF,QAAM,SAAS,IAAI,+BAAa,MAAM;AAEtC,SAAO,OAAO,QAAiC,YAAoD;AACjG,WAAO,OAAO,KAAK,kBAAkB,QAAQ,OAAO;AAAA,EACtD;AACF;AAuDO,SAAS,4BAA4B,QAAmD;AAC7F,QAAM,SAAS,IAAI,+BAAa,MAAM;AAEtC,SAAO,OAAO,OAAe,YAAuC,QAAiB,YAA+C;AAClI,UAAM,WAAW,OAAO,MAAM,KAAK;AACnC,YAAQ,YAAY;AAAA,MAClB,KAAK;AACH,eAAO,SAAS,mBAAmB,QAA6D,OAAO;AAAA,MACzG,KAAK;AACH,eAAO,SAAS,mBAAmB,QAA6D,OAAO;AAAA,MACzG,KAAK;AACH,eAAO,SAAS,uBAAuB,QAAiE,OAAO;AAAA,MACjH,KAAK;AACH,eAAO,SAAS,mBAAmB,QAA6D,OAAO;AAAA,MACzG,KAAK;AACH,eAAO,SAAS,qBAAqB,QAA+D,OAAO;AAAA,MAC7G,KAAK;AACH,eAAO,SAAS,wBAAwB,QAAkE,OAAO;AAAA,MACnH,KAAK;AAEH,eAAO,SAAS,QAAQ,MAAM,MAAuB;AAAA,MACvD;AACE,cAAM,IAAI,MAAM,4BAA4B,UAAU,EAAE;AAAA,IAC5D;AAAA,EACF;AACF;AA0BO,SAAS,0BAA0B,QAAiD;AACzF,QAAM,SAAS,IAAI,+BAAa,MAAM;AAEtC,SAAO,OAAO,WAAoD;AAChE,WAAO,OAAO,QAAQ,MAAM,MAAM;AAAA,EACpC;AACF;AAUO,IAAM,yBAAyB;AAS/B,IAAM,2BAA2B;","names":["type","sdkParams"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server-side entry point for @waffo/pancake-nextjs.\n *\n * All functions in this module use the private key for RSA signing\n * and must only run on the server (Next.js Server Actions / Route Handlers).\n *\n * @example\n * ```ts\n * // app/lib/waffo.ts\n * \"use server\";\n * import { createCheckoutAction, createCustomerTokenAction, createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * const config = {\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * };\n *\n * export const checkout = createCheckoutAction(config);\n * export const issueCustomerToken = createCustomerTokenAction(config);\n * export const merchantQuery = createMerchantQueryAction(config);\n * ```\n *\n * @example\n * ```ts\n * // app/account/billing-portal/route.ts — customer portal entry from your own login\n * import { createCustomerPortalRedirect } from \"@waffo/pancake-nextjs/server\";\n *\n * export const GET = createCustomerPortalRedirect(config, {\n * resolveAuthenticatedCustomer: async (request) => getSignedInCustomer(request), // your session\n * });\n * ```\n */\nimport { WaffoPancake } from \"@waffo/pancake-ts\";\n\nimport type {\n RequestOptions,\n WaffoPancakeConfig,\n AnonymousCheckoutParams,\n AuthenticatedCheckoutParams,\n AuthenticatedPlanChangeParams,\n CreatePlanChangeSessionParams,\n CheckoutSessionResult,\n AuthenticatedCheckoutResult,\n IssueSessionTokenParams,\n SessionToken,\n GraphQLParams,\n GraphQLResponse,\n} from \"@waffo/pancake-ts\";\n\n// ============================================================\n// Checkout Action\n// ============================================================\n\n/**\n * Parameters for the checkout server action.\n *\n * The `type` field selects the flow: a new purchase (`anonymous` / `authenticated`)\n * or a plan change for an existing subscription (`planChange` /\n * `authenticatedPlanChange`, which carry the required `originOrderId`).\n */\nexport type CheckoutActionParams =\n | ({ type?: \"anonymous\" } & AnonymousCheckoutParams)\n | ({ type: \"authenticated\" } & AuthenticatedCheckoutParams)\n | ({ type: \"planChange\" } & CreatePlanChangeSessionParams)\n | ({ type: \"authenticatedPlanChange\" } & AuthenticatedPlanChangeParams);\n\n/** Result of the checkout server action */\nexport type CheckoutActionResult = CheckoutSessionResult | AuthenticatedCheckoutResult;\n\n/**\n * Server action signature for checkout.\n *\n * The optional `options` carries an `idempotencyKey`; without one no key is sent\n * and a retried call creates a second session.\n */\nexport type CheckoutAction = (params: CheckoutActionParams, options?: RequestOptions) => Promise<CheckoutActionResult>;\n\n/**\n * Create a server action that handles checkout session creation.\n *\n * The private key is captured in the closure and never sent to the client.\n *\n * Handles both new purchases and plan changes; `params.type` selects the flow.\n *\n * @param config - WaffoPancake client configuration (merchantId + privateKey)\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCheckoutAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const checkout = createCheckoutAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n *\n * @example\n * ```ts\n * // Plan change link for an existing subscription — originOrderId is required\n * const session = await checkout({\n * type: \"planChange\",\n * originOrderId: \"ORD_xxx\",\n * productId: \"PROD_target_plan\",\n * currency: \"USD\",\n * });\n * // session.checkoutUrl points at the change confirmation page\n * ```\n */\nexport function createCheckoutAction(config: WaffoPancakeConfig): CheckoutAction {\n const client = new WaffoPancake(config);\n\n return async (params: CheckoutActionParams, options?: RequestOptions): Promise<CheckoutActionResult> => {\n if (params.type === \"authenticated\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.create(sdkParams, options);\n }\n if (params.type === \"planChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.createPlanChangeSession(sdkParams, options);\n }\n if (params.type === \"authenticatedPlanChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.createPlanChange(sdkParams, options);\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.anonymous.create(sdkParams, options);\n };\n}\n\n// ============================================================\n// Customer Token Action\n// ============================================================\n\n/** Server action signature for issuing customer tokens */\nexport type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOptions) => Promise<SessionToken>;\n\n/**\n * Create a server action that issues customer session tokens.\n *\n * This is a generic token primitive: it signs whatever `buyerIdentity` it is\n * handed. Never expose it to the browser as a portal or sign-in entry — a\n * Server Action is callable with arbitrary arguments, so a client-chosen\n * `buyerIdentity` would yield another customer's token. To send a signed-in\n * customer to the customer portal, use {@link createCustomerPortalRedirect},\n * which derives identity from your own session.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerTokenAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const issueCustomerToken = createCustomerTokenAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createCustomerTokenAction(config: WaffoPancakeConfig): CustomerTokenAction {\n const client = new WaffoPancake(config);\n\n return async (params: IssueSessionTokenParams, options?: RequestOptions): Promise<SessionToken> => {\n return client.auth.issueSessionToken(params, options);\n };\n}\n\n// ============================================================\n// Customer Portal Redirect\n// ============================================================\n\n/**\n * Store and customer identity for a customer portal link, resolved by the\n * merchant app from its own authenticated session.\n */\nexport interface AuthenticatedPortalCustomer {\n /** Store ID (Short ID `STO_xxx`) whose portal the customer enters */\n storeId: string;\n /** Customer identity in your system — the same value used for authenticated checkout */\n buyerIdentity: string;\n}\n\n/** Options for {@link createCustomerPortalRedirect}. */\nexport interface CustomerPortalRedirectOptions {\n /**\n * Resolve the signed-in customer from YOUR app's authenticated session\n * (session cookie, auth library, …). Return `null` when nobody is signed in.\n *\n * Must not read `buyerIdentity` or `storeId` from anything the browser\n * controls — query string, body, form fields or custom headers. Whoever\n * controls `buyerIdentity` gets that customer's portal.\n */\n resolveAuthenticatedCustomer: (request: Request) => Promise<AuthenticatedPortalCustomer | null> | AuthenticatedPortalCustomer | null;\n /**\n * Where to send a visitor for whom the resolver returned `null`. A path or\n * absolute URL, resolved against the request URL; a function receives the\n * request (e.g. to add your own `returnTo`). Omit to answer 401 instead.\n *\n * Build it from constants or an allow-list, never by copying request\n * parameters verbatim (e.g. a `?next=` value) — that turns this route into an\n * open redirect.\n */\n loginUrl?: string | ((request: Request) => string);\n /**\n * Build the response when creating the portal link fails (API error, invalid\n * store ID, network error). This is the place for your own logging and error\n * reporting. Defaults to a 502 JSON `{ error }` response with a generic\n * message, after one `console.error` line carrying only the error message;\n * the error itself is not echoed to the browser.\n */\n onError?: (error: unknown, request: Request) => Response | Promise<Response>;\n}\n\n/** GET Route Handler returned by {@link createCustomerPortalRedirect}. */\nexport type CustomerPortalRouteHandler = (request: Request) => Promise<Response>;\n\n/**\n * Build headers for a portal route response. Every response — redirect, 401 or\n * error — is `no-store` and `no-referrer`, so a tokenized `Location` is neither\n * cached nor forwarded as a Referer.\n */\nfunction portalHeaders(extra: Record<string, string>): Headers {\n const headers = new Headers(extra);\n headers.set(\"Cache-Control\", \"no-store\");\n headers.set(\"Referrer-Policy\", \"no-referrer\");\n return headers;\n}\n\n/**\n * One-line, log-safe description of a portal link failure: the error message\n * only (never the error object, which may carry a response body), with any\n * `#token=...` fragment redacted.\n */\nfunction safeErrorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return message.replace(/#token=[^\\s\"'&]*/g, \"#token=[redacted]\").replace(/\\s+/g, \" \");\n}\n\n/**\n * Create a GET Route Handler that sends the signed-in customer to your store's\n * Pancake customer portal, without the portal's email verification step.\n *\n * Behavior:\n * - Calls `resolveAuthenticatedCustomer(request)` — your own session check — to\n * get `{ storeId, buyerIdentity }`. Nothing is read from the request's query\n * string, body or headers by the SDK\n * - Signed in: creates a portal link with `client.auth.createCustomerPortalLink()`\n * and answers `302` to `portalUrl` (which carries `#token=...`)\n * - Not signed in: `302` to `loginUrl` when set, otherwise `401`\n * - Link creation fails: `onError(error, request)`, or by default one server-side\n * `console.error` line with the error message and a generic `502 { error }`\n *\n * Side effects:\n * - One signed API request per signed-in visit; each visit issues a new portal session\n * - Every response it builds (not a custom `onError` one) sets `Cache-Control: no-store` and `Referrer-Policy: no-referrer`\n * so the tokenized `Location` is neither cached nor forwarded as a Referer\n *\n * Boundary conditions:\n * - The portal environment is the environment of the API Key in `config`\n * (test key → test portal, prod key → prod portal)\n * - Errors thrown by `resolveAuthenticatedCustomer` propagate (Next.js answers 500)\n * - The default error log is the message only, with any `#token=` fragment redacted;\n * pass `onError` to log or report failures your own way\n * - `loginUrl` must not echo request parameters verbatim, or the route becomes an open redirect\n *\n * A Route Handler rather than a Server Action: the portal entry is a plain link\n * navigation, and only an HTTP redirect keeps the tokenized URL out of client\n * JavaScript while letting the response carry `no-store` / `no-referrer`.\n *\n * @param config - WaffoPancake client configuration (merchantId + privateKey)\n * @param options - Session resolver, login redirect and error handling\n * @returns A Next.js GET Route Handler\n *\n * @example\n * ```ts\n * // app/account/billing-portal/route.ts\n * import { createCustomerPortalRedirect } from \"@waffo/pancake-nextjs/server\";\n * import { auth } from \"@/lib/auth\"; // your auth library\n *\n * export const dynamic = \"force-dynamic\";\n *\n * export const GET = createCustomerPortalRedirect(\n * { merchantId: process.env.WAFFO_MERCHANT_ID!, privateKey: process.env.WAFFO_PRIVATE_KEY! },\n * {\n * resolveAuthenticatedCustomer: async () => {\n * const session = await auth();\n * if (!session?.user) return null;\n * return { storeId: process.env.WAFFO_STORE_ID!, buyerIdentity: session.user.id };\n * },\n * loginUrl: \"/login?returnTo=/account/billing-portal\",\n * },\n * );\n * // In a page: <a href=\"/account/billing-portal\">Manage billing</a>\n * ```\n */\nexport function createCustomerPortalRedirect(\n config: WaffoPancakeConfig,\n options: CustomerPortalRedirectOptions,\n): CustomerPortalRouteHandler {\n const client = new WaffoPancake(config);\n\n return async function GET(request: Request): Promise<Response> {\n const customer = await options.resolveAuthenticatedCustomer(request);\n\n if (!customer) {\n if (options.loginUrl === undefined) {\n return new Response(JSON.stringify({ error: \"Unauthorized\" }), {\n status: 401,\n headers: portalHeaders({ \"Content-Type\": \"application/json\" }),\n });\n }\n const loginUrl = typeof options.loginUrl === \"function\" ? options.loginUrl(request) : options.loginUrl;\n return new Response(null, {\n status: 302,\n headers: portalHeaders({ Location: new URL(loginUrl, request.url).toString() }),\n });\n }\n\n let portalUrl: string;\n try {\n ({ portalUrl } = await client.auth.createCustomerPortalLink({\n storeId: customer.storeId,\n buyerIdentity: customer.buyerIdentity,\n }));\n } catch (error) {\n if (options.onError) {\n return options.onError(error, request);\n }\n console.error(\"[waffo-pancake] createCustomerPortalRedirect failed:\", safeErrorMessage(error));\n return new Response(JSON.stringify({ error: \"Failed to create customer portal link\" }), {\n status: 502,\n headers: portalHeaders({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n return new Response(null, {\n status: 302,\n headers: portalHeaders({ Location: portalUrl }),\n });\n };\n}\n\n// ============================================================\n// Customer Session Action\n// ============================================================\n\n/** Customer action types */\nexport type CustomerSessionActionType =\n | \"cancelSubscription\"\n | \"cancelOnetimeOrder\"\n | \"reactivateSubscription\"\n | \"createRefundTicket\"\n | \"resubmitRefundTicket\"\n | \"createPlanChangeSession\"\n | \"query\";\n\n/** Server action signature for customer session operations */\nexport type CustomerSessionAction = (\n token: string,\n actionType: CustomerSessionActionType,\n params: unknown,\n options?: RequestOptions,\n) => Promise<unknown>;\n\n/**\n * Create a server action that executes customer self-service operations.\n *\n * `config.environment` is required — session tokens carry no environment, and the\n * gateway rejects a session request without the matching header.\n *\n * `createPlanChangeSession` is the customer-driven half of a plan change: it returns\n * a confirmation-page URL, and the platform allows it only when the subscription\n * belongs to that customer, the target plan is in the same product group, and that\n * group's `selfServicePlanChange` is on — otherwise 403. The merchant-only pricing\n * fields are not part of its params; the platform drops them on this path silently.\n *\n * No call sends an idempotency key unless you pass one (`options.idempotencyKey`,\n * the last argument) — the same rule as every other method in the SDK — so a write\n * retried after a timeout executes twice.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerSessionAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const customerAction = createCustomerSessionAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * environment: \"test\",\n * });\n * ```\n */\nexport function createCustomerSessionAction(config: WaffoPancakeConfig): CustomerSessionAction {\n const client = new WaffoPancake(config);\n\n return async (token: string, actionType: CustomerSessionActionType, params: unknown, options?: RequestOptions): Promise<unknown> => {\n const customer = client.buyer(token);\n switch (actionType) {\n case \"cancelSubscription\":\n return customer.cancelSubscription(params as Parameters<typeof customer.cancelSubscription>[0], options);\n case \"cancelOnetimeOrder\":\n return customer.cancelOnetimeOrder(params as Parameters<typeof customer.cancelOnetimeOrder>[0], options);\n case \"reactivateSubscription\":\n return customer.reactivateSubscription(params as Parameters<typeof customer.reactivateSubscription>[0], options);\n case \"createRefundTicket\":\n return customer.createRefundTicket(params as Parameters<typeof customer.createRefundTicket>[0], options);\n case \"resubmitRefundTicket\":\n return customer.resubmitRefundTicket(params as Parameters<typeof customer.resubmitRefundTicket>[0], options);\n case \"createPlanChangeSession\":\n return customer.createPlanChangeSession(params as Parameters<typeof customer.createPlanChangeSession>[0], options);\n case \"query\":\n // Reads take no key: a cached replay would serve stale data.\n return customer.graphql.query(params as GraphQLParams);\n default:\n throw new Error(`Unknown customer action: ${actionType}`);\n }\n };\n}\n\n// ============================================================\n// Merchant Query Action\n// ============================================================\n\n/** Server action signature for merchant GraphQL queries */\nexport type MerchantQueryAction = (params: GraphQLParams) => Promise<GraphQLResponse>;\n\n/**\n * Create a server action that executes merchant GraphQL queries.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const merchantQuery = createMerchantQueryAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createMerchantQueryAction(config: WaffoPancakeConfig): MerchantQueryAction {\n const client = new WaffoPancake(config);\n\n return async (params: GraphQLParams): Promise<GraphQLResponse> => {\n return client.graphql.query(params);\n };\n}\n\n// ============================================================\n// Deprecated Aliases\n// ============================================================\n\n/** @deprecated Use {@link CustomerTokenAction} instead. */\nexport type BuyerTokenAction = CustomerTokenAction;\n\n/** @deprecated Use {@link createCustomerTokenAction} instead. */\nexport const createBuyerTokenAction = createCustomerTokenAction;\n\n/** @deprecated Use {@link CustomerSessionActionType} instead. */\nexport type BuyerSessionActionType = CustomerSessionActionType;\n\n/** @deprecated Use {@link CustomerSessionAction} instead. */\nexport type BuyerSessionAction = CustomerSessionAction;\n\n/** @deprecated Use {@link createCustomerSessionAction} instead. */\nexport const createBuyerSessionAction = createCustomerSessionAction;\n"],"mappings":"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","names":["type","sdkParams"]}
|
package/dist/server.d.cts
CHANGED
|
@@ -64,6 +64,13 @@ type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOp
|
|
|
64
64
|
/**
|
|
65
65
|
* Create a server action that issues customer session tokens.
|
|
66
66
|
*
|
|
67
|
+
* This is a generic token primitive: it signs whatever `buyerIdentity` it is
|
|
68
|
+
* handed. Never expose it to the browser as a portal or sign-in entry — a
|
|
69
|
+
* Server Action is callable with arbitrary arguments, so a client-chosen
|
|
70
|
+
* `buyerIdentity` would yield another customer's token. To send a signed-in
|
|
71
|
+
* customer to the customer portal, use {@link createCustomerPortalRedirect},
|
|
72
|
+
* which derives identity from your own session.
|
|
73
|
+
*
|
|
67
74
|
* @param config - WaffoPancake client configuration
|
|
68
75
|
* @returns A server action function
|
|
69
76
|
*
|
|
@@ -79,6 +86,106 @@ type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOp
|
|
|
79
86
|
* ```
|
|
80
87
|
*/
|
|
81
88
|
declare function createCustomerTokenAction(config: WaffoPancakeConfig): CustomerTokenAction;
|
|
89
|
+
/**
|
|
90
|
+
* Store and customer identity for a customer portal link, resolved by the
|
|
91
|
+
* merchant app from its own authenticated session.
|
|
92
|
+
*/
|
|
93
|
+
interface AuthenticatedPortalCustomer {
|
|
94
|
+
/** Store ID (Short ID `STO_xxx`) whose portal the customer enters */
|
|
95
|
+
storeId: string;
|
|
96
|
+
/** Customer identity in your system — the same value used for authenticated checkout */
|
|
97
|
+
buyerIdentity: string;
|
|
98
|
+
}
|
|
99
|
+
/** Options for {@link createCustomerPortalRedirect}. */
|
|
100
|
+
interface CustomerPortalRedirectOptions {
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the signed-in customer from YOUR app's authenticated session
|
|
103
|
+
* (session cookie, auth library, …). Return `null` when nobody is signed in.
|
|
104
|
+
*
|
|
105
|
+
* Must not read `buyerIdentity` or `storeId` from anything the browser
|
|
106
|
+
* controls — query string, body, form fields or custom headers. Whoever
|
|
107
|
+
* controls `buyerIdentity` gets that customer's portal.
|
|
108
|
+
*/
|
|
109
|
+
resolveAuthenticatedCustomer: (request: Request) => Promise<AuthenticatedPortalCustomer | null> | AuthenticatedPortalCustomer | null;
|
|
110
|
+
/**
|
|
111
|
+
* Where to send a visitor for whom the resolver returned `null`. A path or
|
|
112
|
+
* absolute URL, resolved against the request URL; a function receives the
|
|
113
|
+
* request (e.g. to add your own `returnTo`). Omit to answer 401 instead.
|
|
114
|
+
*
|
|
115
|
+
* Build it from constants or an allow-list, never by copying request
|
|
116
|
+
* parameters verbatim (e.g. a `?next=` value) — that turns this route into an
|
|
117
|
+
* open redirect.
|
|
118
|
+
*/
|
|
119
|
+
loginUrl?: string | ((request: Request) => string);
|
|
120
|
+
/**
|
|
121
|
+
* Build the response when creating the portal link fails (API error, invalid
|
|
122
|
+
* store ID, network error). This is the place for your own logging and error
|
|
123
|
+
* reporting. Defaults to a 502 JSON `{ error }` response with a generic
|
|
124
|
+
* message, after one `console.error` line carrying only the error message;
|
|
125
|
+
* the error itself is not echoed to the browser.
|
|
126
|
+
*/
|
|
127
|
+
onError?: (error: unknown, request: Request) => Response | Promise<Response>;
|
|
128
|
+
}
|
|
129
|
+
/** GET Route Handler returned by {@link createCustomerPortalRedirect}. */
|
|
130
|
+
type CustomerPortalRouteHandler = (request: Request) => Promise<Response>;
|
|
131
|
+
/**
|
|
132
|
+
* Create a GET Route Handler that sends the signed-in customer to your store's
|
|
133
|
+
* Pancake customer portal, without the portal's email verification step.
|
|
134
|
+
*
|
|
135
|
+
* Behavior:
|
|
136
|
+
* - Calls `resolveAuthenticatedCustomer(request)` — your own session check — to
|
|
137
|
+
* get `{ storeId, buyerIdentity }`. Nothing is read from the request's query
|
|
138
|
+
* string, body or headers by the SDK
|
|
139
|
+
* - Signed in: creates a portal link with `client.auth.createCustomerPortalLink()`
|
|
140
|
+
* and answers `302` to `portalUrl` (which carries `#token=...`)
|
|
141
|
+
* - Not signed in: `302` to `loginUrl` when set, otherwise `401`
|
|
142
|
+
* - Link creation fails: `onError(error, request)`, or by default one server-side
|
|
143
|
+
* `console.error` line with the error message and a generic `502 { error }`
|
|
144
|
+
*
|
|
145
|
+
* Side effects:
|
|
146
|
+
* - One signed API request per signed-in visit; each visit issues a new portal session
|
|
147
|
+
* - Every response it builds (not a custom `onError` one) sets `Cache-Control: no-store` and `Referrer-Policy: no-referrer`
|
|
148
|
+
* so the tokenized `Location` is neither cached nor forwarded as a Referer
|
|
149
|
+
*
|
|
150
|
+
* Boundary conditions:
|
|
151
|
+
* - The portal environment is the environment of the API Key in `config`
|
|
152
|
+
* (test key → test portal, prod key → prod portal)
|
|
153
|
+
* - Errors thrown by `resolveAuthenticatedCustomer` propagate (Next.js answers 500)
|
|
154
|
+
* - The default error log is the message only, with any `#token=` fragment redacted;
|
|
155
|
+
* pass `onError` to log or report failures your own way
|
|
156
|
+
* - `loginUrl` must not echo request parameters verbatim, or the route becomes an open redirect
|
|
157
|
+
*
|
|
158
|
+
* A Route Handler rather than a Server Action: the portal entry is a plain link
|
|
159
|
+
* navigation, and only an HTTP redirect keeps the tokenized URL out of client
|
|
160
|
+
* JavaScript while letting the response carry `no-store` / `no-referrer`.
|
|
161
|
+
*
|
|
162
|
+
* @param config - WaffoPancake client configuration (merchantId + privateKey)
|
|
163
|
+
* @param options - Session resolver, login redirect and error handling
|
|
164
|
+
* @returns A Next.js GET Route Handler
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* // app/account/billing-portal/route.ts
|
|
169
|
+
* import { createCustomerPortalRedirect } from "@waffo/pancake-nextjs/server";
|
|
170
|
+
* import { auth } from "@/lib/auth"; // your auth library
|
|
171
|
+
*
|
|
172
|
+
* export const dynamic = "force-dynamic";
|
|
173
|
+
*
|
|
174
|
+
* export const GET = createCustomerPortalRedirect(
|
|
175
|
+
* { merchantId: process.env.WAFFO_MERCHANT_ID!, privateKey: process.env.WAFFO_PRIVATE_KEY! },
|
|
176
|
+
* {
|
|
177
|
+
* resolveAuthenticatedCustomer: async () => {
|
|
178
|
+
* const session = await auth();
|
|
179
|
+
* if (!session?.user) return null;
|
|
180
|
+
* return { storeId: process.env.WAFFO_STORE_ID!, buyerIdentity: session.user.id };
|
|
181
|
+
* },
|
|
182
|
+
* loginUrl: "/login?returnTo=/account/billing-portal",
|
|
183
|
+
* },
|
|
184
|
+
* );
|
|
185
|
+
* // In a page: <a href="/account/billing-portal">Manage billing</a>
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
declare function createCustomerPortalRedirect(config: WaffoPancakeConfig, options: CustomerPortalRedirectOptions): CustomerPortalRouteHandler;
|
|
82
189
|
/** Customer action types */
|
|
83
190
|
type CustomerSessionActionType = "cancelSubscription" | "cancelOnetimeOrder" | "reactivateSubscription" | "createRefundTicket" | "resubmitRefundTicket" | "createPlanChangeSession" | "query";
|
|
84
191
|
/** Server action signature for customer session operations */
|
|
@@ -146,4 +253,4 @@ type BuyerSessionAction = CustomerSessionAction;
|
|
|
146
253
|
/** @deprecated Use {@link createCustomerSessionAction} instead. */
|
|
147
254
|
declare const createBuyerSessionAction: typeof createCustomerSessionAction;
|
|
148
255
|
|
|
149
|
-
export { type BuyerSessionAction, type BuyerSessionActionType, type BuyerTokenAction, type CheckoutAction, type CheckoutActionParams, type CheckoutActionResult, type CustomerSessionAction, type CustomerSessionActionType, type CustomerTokenAction, type MerchantQueryAction, createBuyerSessionAction, createBuyerTokenAction, createCheckoutAction, createCustomerSessionAction, createCustomerTokenAction, createMerchantQueryAction };
|
|
256
|
+
export { type AuthenticatedPortalCustomer, type BuyerSessionAction, type BuyerSessionActionType, type BuyerTokenAction, type CheckoutAction, type CheckoutActionParams, type CheckoutActionResult, type CustomerPortalRedirectOptions, type CustomerPortalRouteHandler, type CustomerSessionAction, type CustomerSessionActionType, type CustomerTokenAction, type MerchantQueryAction, createBuyerSessionAction, createBuyerTokenAction, createCheckoutAction, createCustomerPortalRedirect, createCustomerSessionAction, createCustomerTokenAction, createMerchantQueryAction };
|
package/dist/server.d.ts
CHANGED
|
@@ -64,6 +64,13 @@ type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOp
|
|
|
64
64
|
/**
|
|
65
65
|
* Create a server action that issues customer session tokens.
|
|
66
66
|
*
|
|
67
|
+
* This is a generic token primitive: it signs whatever `buyerIdentity` it is
|
|
68
|
+
* handed. Never expose it to the browser as a portal or sign-in entry — a
|
|
69
|
+
* Server Action is callable with arbitrary arguments, so a client-chosen
|
|
70
|
+
* `buyerIdentity` would yield another customer's token. To send a signed-in
|
|
71
|
+
* customer to the customer portal, use {@link createCustomerPortalRedirect},
|
|
72
|
+
* which derives identity from your own session.
|
|
73
|
+
*
|
|
67
74
|
* @param config - WaffoPancake client configuration
|
|
68
75
|
* @returns A server action function
|
|
69
76
|
*
|
|
@@ -79,6 +86,106 @@ type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOp
|
|
|
79
86
|
* ```
|
|
80
87
|
*/
|
|
81
88
|
declare function createCustomerTokenAction(config: WaffoPancakeConfig): CustomerTokenAction;
|
|
89
|
+
/**
|
|
90
|
+
* Store and customer identity for a customer portal link, resolved by the
|
|
91
|
+
* merchant app from its own authenticated session.
|
|
92
|
+
*/
|
|
93
|
+
interface AuthenticatedPortalCustomer {
|
|
94
|
+
/** Store ID (Short ID `STO_xxx`) whose portal the customer enters */
|
|
95
|
+
storeId: string;
|
|
96
|
+
/** Customer identity in your system — the same value used for authenticated checkout */
|
|
97
|
+
buyerIdentity: string;
|
|
98
|
+
}
|
|
99
|
+
/** Options for {@link createCustomerPortalRedirect}. */
|
|
100
|
+
interface CustomerPortalRedirectOptions {
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the signed-in customer from YOUR app's authenticated session
|
|
103
|
+
* (session cookie, auth library, …). Return `null` when nobody is signed in.
|
|
104
|
+
*
|
|
105
|
+
* Must not read `buyerIdentity` or `storeId` from anything the browser
|
|
106
|
+
* controls — query string, body, form fields or custom headers. Whoever
|
|
107
|
+
* controls `buyerIdentity` gets that customer's portal.
|
|
108
|
+
*/
|
|
109
|
+
resolveAuthenticatedCustomer: (request: Request) => Promise<AuthenticatedPortalCustomer | null> | AuthenticatedPortalCustomer | null;
|
|
110
|
+
/**
|
|
111
|
+
* Where to send a visitor for whom the resolver returned `null`. A path or
|
|
112
|
+
* absolute URL, resolved against the request URL; a function receives the
|
|
113
|
+
* request (e.g. to add your own `returnTo`). Omit to answer 401 instead.
|
|
114
|
+
*
|
|
115
|
+
* Build it from constants or an allow-list, never by copying request
|
|
116
|
+
* parameters verbatim (e.g. a `?next=` value) — that turns this route into an
|
|
117
|
+
* open redirect.
|
|
118
|
+
*/
|
|
119
|
+
loginUrl?: string | ((request: Request) => string);
|
|
120
|
+
/**
|
|
121
|
+
* Build the response when creating the portal link fails (API error, invalid
|
|
122
|
+
* store ID, network error). This is the place for your own logging and error
|
|
123
|
+
* reporting. Defaults to a 502 JSON `{ error }` response with a generic
|
|
124
|
+
* message, after one `console.error` line carrying only the error message;
|
|
125
|
+
* the error itself is not echoed to the browser.
|
|
126
|
+
*/
|
|
127
|
+
onError?: (error: unknown, request: Request) => Response | Promise<Response>;
|
|
128
|
+
}
|
|
129
|
+
/** GET Route Handler returned by {@link createCustomerPortalRedirect}. */
|
|
130
|
+
type CustomerPortalRouteHandler = (request: Request) => Promise<Response>;
|
|
131
|
+
/**
|
|
132
|
+
* Create a GET Route Handler that sends the signed-in customer to your store's
|
|
133
|
+
* Pancake customer portal, without the portal's email verification step.
|
|
134
|
+
*
|
|
135
|
+
* Behavior:
|
|
136
|
+
* - Calls `resolveAuthenticatedCustomer(request)` — your own session check — to
|
|
137
|
+
* get `{ storeId, buyerIdentity }`. Nothing is read from the request's query
|
|
138
|
+
* string, body or headers by the SDK
|
|
139
|
+
* - Signed in: creates a portal link with `client.auth.createCustomerPortalLink()`
|
|
140
|
+
* and answers `302` to `portalUrl` (which carries `#token=...`)
|
|
141
|
+
* - Not signed in: `302` to `loginUrl` when set, otherwise `401`
|
|
142
|
+
* - Link creation fails: `onError(error, request)`, or by default one server-side
|
|
143
|
+
* `console.error` line with the error message and a generic `502 { error }`
|
|
144
|
+
*
|
|
145
|
+
* Side effects:
|
|
146
|
+
* - One signed API request per signed-in visit; each visit issues a new portal session
|
|
147
|
+
* - Every response it builds (not a custom `onError` one) sets `Cache-Control: no-store` and `Referrer-Policy: no-referrer`
|
|
148
|
+
* so the tokenized `Location` is neither cached nor forwarded as a Referer
|
|
149
|
+
*
|
|
150
|
+
* Boundary conditions:
|
|
151
|
+
* - The portal environment is the environment of the API Key in `config`
|
|
152
|
+
* (test key → test portal, prod key → prod portal)
|
|
153
|
+
* - Errors thrown by `resolveAuthenticatedCustomer` propagate (Next.js answers 500)
|
|
154
|
+
* - The default error log is the message only, with any `#token=` fragment redacted;
|
|
155
|
+
* pass `onError` to log or report failures your own way
|
|
156
|
+
* - `loginUrl` must not echo request parameters verbatim, or the route becomes an open redirect
|
|
157
|
+
*
|
|
158
|
+
* A Route Handler rather than a Server Action: the portal entry is a plain link
|
|
159
|
+
* navigation, and only an HTTP redirect keeps the tokenized URL out of client
|
|
160
|
+
* JavaScript while letting the response carry `no-store` / `no-referrer`.
|
|
161
|
+
*
|
|
162
|
+
* @param config - WaffoPancake client configuration (merchantId + privateKey)
|
|
163
|
+
* @param options - Session resolver, login redirect and error handling
|
|
164
|
+
* @returns A Next.js GET Route Handler
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* // app/account/billing-portal/route.ts
|
|
169
|
+
* import { createCustomerPortalRedirect } from "@waffo/pancake-nextjs/server";
|
|
170
|
+
* import { auth } from "@/lib/auth"; // your auth library
|
|
171
|
+
*
|
|
172
|
+
* export const dynamic = "force-dynamic";
|
|
173
|
+
*
|
|
174
|
+
* export const GET = createCustomerPortalRedirect(
|
|
175
|
+
* { merchantId: process.env.WAFFO_MERCHANT_ID!, privateKey: process.env.WAFFO_PRIVATE_KEY! },
|
|
176
|
+
* {
|
|
177
|
+
* resolveAuthenticatedCustomer: async () => {
|
|
178
|
+
* const session = await auth();
|
|
179
|
+
* if (!session?.user) return null;
|
|
180
|
+
* return { storeId: process.env.WAFFO_STORE_ID!, buyerIdentity: session.user.id };
|
|
181
|
+
* },
|
|
182
|
+
* loginUrl: "/login?returnTo=/account/billing-portal",
|
|
183
|
+
* },
|
|
184
|
+
* );
|
|
185
|
+
* // In a page: <a href="/account/billing-portal">Manage billing</a>
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
declare function createCustomerPortalRedirect(config: WaffoPancakeConfig, options: CustomerPortalRedirectOptions): CustomerPortalRouteHandler;
|
|
82
189
|
/** Customer action types */
|
|
83
190
|
type CustomerSessionActionType = "cancelSubscription" | "cancelOnetimeOrder" | "reactivateSubscription" | "createRefundTicket" | "resubmitRefundTicket" | "createPlanChangeSession" | "query";
|
|
84
191
|
/** Server action signature for customer session operations */
|
|
@@ -146,4 +253,4 @@ type BuyerSessionAction = CustomerSessionAction;
|
|
|
146
253
|
/** @deprecated Use {@link createCustomerSessionAction} instead. */
|
|
147
254
|
declare const createBuyerSessionAction: typeof createCustomerSessionAction;
|
|
148
255
|
|
|
149
|
-
export { type BuyerSessionAction, type BuyerSessionActionType, type BuyerTokenAction, type CheckoutAction, type CheckoutActionParams, type CheckoutActionResult, type CustomerSessionAction, type CustomerSessionActionType, type CustomerTokenAction, type MerchantQueryAction, createBuyerSessionAction, createBuyerTokenAction, createCheckoutAction, createCustomerSessionAction, createCustomerTokenAction, createMerchantQueryAction };
|
|
256
|
+
export { type AuthenticatedPortalCustomer, type BuyerSessionAction, type BuyerSessionActionType, type BuyerTokenAction, type CheckoutAction, type CheckoutActionParams, type CheckoutActionResult, type CustomerPortalRedirectOptions, type CustomerPortalRouteHandler, type CustomerSessionAction, type CustomerSessionActionType, type CustomerTokenAction, type MerchantQueryAction, createBuyerSessionAction, createBuyerTokenAction, createCheckoutAction, createCustomerPortalRedirect, createCustomerSessionAction, createCustomerTokenAction, createMerchantQueryAction };
|
package/dist/server.js
CHANGED
|
@@ -25,6 +25,55 @@ function createCustomerTokenAction(config) {
|
|
|
25
25
|
return client.auth.issueSessionToken(params, options);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
function portalHeaders(extra) {
|
|
29
|
+
const headers = new Headers(extra);
|
|
30
|
+
headers.set("Cache-Control", "no-store");
|
|
31
|
+
headers.set("Referrer-Policy", "no-referrer");
|
|
32
|
+
return headers;
|
|
33
|
+
}
|
|
34
|
+
function safeErrorMessage(error) {
|
|
35
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36
|
+
return message.replace(/#token=[^\s"'&]*/g, "#token=[redacted]").replace(/\s+/g, " ");
|
|
37
|
+
}
|
|
38
|
+
function createCustomerPortalRedirect(config, options) {
|
|
39
|
+
const client = new WaffoPancake(config);
|
|
40
|
+
return async function GET(request) {
|
|
41
|
+
const customer = await options.resolveAuthenticatedCustomer(request);
|
|
42
|
+
if (!customer) {
|
|
43
|
+
if (options.loginUrl === void 0) {
|
|
44
|
+
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
45
|
+
status: 401,
|
|
46
|
+
headers: portalHeaders({ "Content-Type": "application/json" })
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const loginUrl = typeof options.loginUrl === "function" ? options.loginUrl(request) : options.loginUrl;
|
|
50
|
+
return new Response(null, {
|
|
51
|
+
status: 302,
|
|
52
|
+
headers: portalHeaders({ Location: new URL(loginUrl, request.url).toString() })
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let portalUrl;
|
|
56
|
+
try {
|
|
57
|
+
({ portalUrl } = await client.auth.createCustomerPortalLink({
|
|
58
|
+
storeId: customer.storeId,
|
|
59
|
+
buyerIdentity: customer.buyerIdentity
|
|
60
|
+
}));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (options.onError) {
|
|
63
|
+
return options.onError(error, request);
|
|
64
|
+
}
|
|
65
|
+
console.error("[waffo-pancake] createCustomerPortalRedirect failed:", safeErrorMessage(error));
|
|
66
|
+
return new Response(JSON.stringify({ error: "Failed to create customer portal link" }), {
|
|
67
|
+
status: 502,
|
|
68
|
+
headers: portalHeaders({ "Content-Type": "application/json" })
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return new Response(null, {
|
|
72
|
+
status: 302,
|
|
73
|
+
headers: portalHeaders({ Location: portalUrl })
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
}
|
|
28
77
|
function createCustomerSessionAction(config) {
|
|
29
78
|
const client = new WaffoPancake(config);
|
|
30
79
|
return async (token, actionType, params, options) => {
|
|
@@ -61,6 +110,7 @@ export {
|
|
|
61
110
|
createBuyerSessionAction,
|
|
62
111
|
createBuyerTokenAction,
|
|
63
112
|
createCheckoutAction,
|
|
113
|
+
createCustomerPortalRedirect,
|
|
64
114
|
createCustomerSessionAction,
|
|
65
115
|
createCustomerTokenAction,
|
|
66
116
|
createMerchantQueryAction
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server-side entry point for @waffo/pancake-nextjs.\n *\n * All functions in this module use the private key for RSA signing\n * and must only run on the server (Next.js Server Actions / Route Handlers).\n *\n * @example\n * ```ts\n * // app/lib/waffo.ts\n * \"use server\";\n * import { createCheckoutAction, createCustomerTokenAction, createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * const config = {\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * };\n *\n * export const checkout = createCheckoutAction(config);\n * export const issueCustomerToken = createCustomerTokenAction(config);\n * export const merchantQuery = createMerchantQueryAction(config);\n * ```\n */\nimport { WaffoPancake } from \"@waffo/pancake-ts\";\n\nimport type {\n RequestOptions,\n WaffoPancakeConfig,\n AnonymousCheckoutParams,\n AuthenticatedCheckoutParams,\n AuthenticatedPlanChangeParams,\n CreatePlanChangeSessionParams,\n CheckoutSessionResult,\n AuthenticatedCheckoutResult,\n IssueSessionTokenParams,\n SessionToken,\n GraphQLParams,\n GraphQLResponse,\n} from \"@waffo/pancake-ts\";\n\n// ============================================================\n// Checkout Action\n// ============================================================\n\n/**\n * Parameters for the checkout server action.\n *\n * The `type` field selects the flow: a new purchase (`anonymous` / `authenticated`)\n * or a plan change for an existing subscription (`planChange` /\n * `authenticatedPlanChange`, which carry the required `originOrderId`).\n */\nexport type CheckoutActionParams =\n | ({ type?: \"anonymous\" } & AnonymousCheckoutParams)\n | ({ type: \"authenticated\" } & AuthenticatedCheckoutParams)\n | ({ type: \"planChange\" } & CreatePlanChangeSessionParams)\n | ({ type: \"authenticatedPlanChange\" } & AuthenticatedPlanChangeParams);\n\n/** Result of the checkout server action */\nexport type CheckoutActionResult = CheckoutSessionResult | AuthenticatedCheckoutResult;\n\n/**\n * Server action signature for checkout.\n *\n * The optional `options` carries an `idempotencyKey`; without one no key is sent\n * and a retried call creates a second session.\n */\nexport type CheckoutAction = (params: CheckoutActionParams, options?: RequestOptions) => Promise<CheckoutActionResult>;\n\n/**\n * Create a server action that handles checkout session creation.\n *\n * The private key is captured in the closure and never sent to the client.\n *\n * Handles both new purchases and plan changes; `params.type` selects the flow.\n *\n * @param config - WaffoPancake client configuration (merchantId + privateKey)\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCheckoutAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const checkout = createCheckoutAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n *\n * @example\n * ```ts\n * // Plan change link for an existing subscription — originOrderId is required\n * const session = await checkout({\n * type: \"planChange\",\n * originOrderId: \"ORD_xxx\",\n * productId: \"PROD_target_plan\",\n * currency: \"USD\",\n * });\n * // session.checkoutUrl points at the change confirmation page\n * ```\n */\nexport function createCheckoutAction(config: WaffoPancakeConfig): CheckoutAction {\n const client = new WaffoPancake(config);\n\n return async (params: CheckoutActionParams, options?: RequestOptions): Promise<CheckoutActionResult> => {\n if (params.type === \"authenticated\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.create(sdkParams, options);\n }\n if (params.type === \"planChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.createPlanChangeSession(sdkParams, options);\n }\n if (params.type === \"authenticatedPlanChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.createPlanChange(sdkParams, options);\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.anonymous.create(sdkParams, options);\n };\n}\n\n// ============================================================\n// Customer Token Action\n// ============================================================\n\n/** Server action signature for issuing customer tokens */\nexport type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOptions) => Promise<SessionToken>;\n\n/**\n * Create a server action that issues customer session tokens.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerTokenAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const issueCustomerToken = createCustomerTokenAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createCustomerTokenAction(config: WaffoPancakeConfig): CustomerTokenAction {\n const client = new WaffoPancake(config);\n\n return async (params: IssueSessionTokenParams, options?: RequestOptions): Promise<SessionToken> => {\n return client.auth.issueSessionToken(params, options);\n };\n}\n\n// ============================================================\n// Customer Session Action\n// ============================================================\n\n/** Customer action types */\nexport type CustomerSessionActionType =\n | \"cancelSubscription\"\n | \"cancelOnetimeOrder\"\n | \"reactivateSubscription\"\n | \"createRefundTicket\"\n | \"resubmitRefundTicket\"\n | \"createPlanChangeSession\"\n | \"query\";\n\n/** Server action signature for customer session operations */\nexport type CustomerSessionAction = (\n token: string,\n actionType: CustomerSessionActionType,\n params: unknown,\n options?: RequestOptions,\n) => Promise<unknown>;\n\n/**\n * Create a server action that executes customer self-service operations.\n *\n * `config.environment` is required — session tokens carry no environment, and the\n * gateway rejects a session request without the matching header.\n *\n * `createPlanChangeSession` is the customer-driven half of a plan change: it returns\n * a confirmation-page URL, and the platform allows it only when the subscription\n * belongs to that customer, the target plan is in the same product group, and that\n * group's `selfServicePlanChange` is on — otherwise 403. The merchant-only pricing\n * fields are not part of its params; the platform drops them on this path silently.\n *\n * No call sends an idempotency key unless you pass one (`options.idempotencyKey`,\n * the last argument) — the same rule as every other method in the SDK — so a write\n * retried after a timeout executes twice.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerSessionAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const customerAction = createCustomerSessionAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * environment: \"test\",\n * });\n * ```\n */\nexport function createCustomerSessionAction(config: WaffoPancakeConfig): CustomerSessionAction {\n const client = new WaffoPancake(config);\n\n return async (token: string, actionType: CustomerSessionActionType, params: unknown, options?: RequestOptions): Promise<unknown> => {\n const customer = client.buyer(token);\n switch (actionType) {\n case \"cancelSubscription\":\n return customer.cancelSubscription(params as Parameters<typeof customer.cancelSubscription>[0], options);\n case \"cancelOnetimeOrder\":\n return customer.cancelOnetimeOrder(params as Parameters<typeof customer.cancelOnetimeOrder>[0], options);\n case \"reactivateSubscription\":\n return customer.reactivateSubscription(params as Parameters<typeof customer.reactivateSubscription>[0], options);\n case \"createRefundTicket\":\n return customer.createRefundTicket(params as Parameters<typeof customer.createRefundTicket>[0], options);\n case \"resubmitRefundTicket\":\n return customer.resubmitRefundTicket(params as Parameters<typeof customer.resubmitRefundTicket>[0], options);\n case \"createPlanChangeSession\":\n return customer.createPlanChangeSession(params as Parameters<typeof customer.createPlanChangeSession>[0], options);\n case \"query\":\n // Reads take no key: a cached replay would serve stale data.\n return customer.graphql.query(params as GraphQLParams);\n default:\n throw new Error(`Unknown customer action: ${actionType}`);\n }\n };\n}\n\n// ============================================================\n// Merchant Query Action\n// ============================================================\n\n/** Server action signature for merchant GraphQL queries */\nexport type MerchantQueryAction = (params: GraphQLParams) => Promise<GraphQLResponse>;\n\n/**\n * Create a server action that executes merchant GraphQL queries.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const merchantQuery = createMerchantQueryAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createMerchantQueryAction(config: WaffoPancakeConfig): MerchantQueryAction {\n const client = new WaffoPancake(config);\n\n return async (params: GraphQLParams): Promise<GraphQLResponse> => {\n return client.graphql.query(params);\n };\n}\n\n// ============================================================\n// Deprecated Aliases\n// ============================================================\n\n/** @deprecated Use {@link CustomerTokenAction} instead. */\nexport type BuyerTokenAction = CustomerTokenAction;\n\n/** @deprecated Use {@link createCustomerTokenAction} instead. */\nexport const createBuyerTokenAction = createCustomerTokenAction;\n\n/** @deprecated Use {@link CustomerSessionActionType} instead. */\nexport type BuyerSessionActionType = CustomerSessionActionType;\n\n/** @deprecated Use {@link CustomerSessionAction} instead. */\nexport type BuyerSessionAction = CustomerSessionAction;\n\n/** @deprecated Use {@link createCustomerSessionAction} instead. */\nexport const createBuyerSessionAction = createCustomerSessionAction;\n"],"mappings":";AAsBA,SAAS,oBAAoB;AA8EtB,SAAS,qBAAqB,QAA4C;AAC/E,QAAM,SAAS,IAAI,aAAa,MAAM;AAEtC,SAAO,OAAO,QAA8B,YAA4D;AACtG,QAAI,OAAO,SAAS,iBAAiB;AAEnC,YAAM,EAAE,MAAAA,OAAM,GAAGC,WAAU,IAAI;AAC/B,aAAO,OAAO,SAAS,cAAc,OAAOA,YAAW,OAAO;AAAA,IAChE;AACA,QAAI,OAAO,SAAS,cAAc;AAEhC,YAAM,EAAE,MAAAD,OAAM,GAAGC,WAAU,IAAI;AAC/B,aAAO,OAAO,SAAS,wBAAwBA,YAAW,OAAO;AAAA,IACnE;AACA,QAAI,OAAO,SAAS,2BAA2B;AAE7C,YAAM,EAAE,MAAAD,OAAM,GAAGC,WAAU,IAAI;AAC/B,aAAO,OAAO,SAAS,cAAc,iBAAiBA,YAAW,OAAO;AAAA,IAC1E;AAEA,UAAM,EAAE,MAAM,GAAG,UAAU,IAAI;AAC/B,WAAO,OAAO,SAAS,UAAU,OAAO,WAAW,OAAO;AAAA,EAC5D;AACF;AA0BO,SAAS,0BAA0B,QAAiD;AACzF,QAAM,SAAS,IAAI,aAAa,MAAM;AAEtC,SAAO,OAAO,QAAiC,YAAoD;AACjG,WAAO,OAAO,KAAK,kBAAkB,QAAQ,OAAO;AAAA,EACtD;AACF;AAuDO,SAAS,4BAA4B,QAAmD;AAC7F,QAAM,SAAS,IAAI,aAAa,MAAM;AAEtC,SAAO,OAAO,OAAe,YAAuC,QAAiB,YAA+C;AAClI,UAAM,WAAW,OAAO,MAAM,KAAK;AACnC,YAAQ,YAAY;AAAA,MAClB,KAAK;AACH,eAAO,SAAS,mBAAmB,QAA6D,OAAO;AAAA,MACzG,KAAK;AACH,eAAO,SAAS,mBAAmB,QAA6D,OAAO;AAAA,MACzG,KAAK;AACH,eAAO,SAAS,uBAAuB,QAAiE,OAAO;AAAA,MACjH,KAAK;AACH,eAAO,SAAS,mBAAmB,QAA6D,OAAO;AAAA,MACzG,KAAK;AACH,eAAO,SAAS,qBAAqB,QAA+D,OAAO;AAAA,MAC7G,KAAK;AACH,eAAO,SAAS,wBAAwB,QAAkE,OAAO;AAAA,MACnH,KAAK;AAEH,eAAO,SAAS,QAAQ,MAAM,MAAuB;AAAA,MACvD;AACE,cAAM,IAAI,MAAM,4BAA4B,UAAU,EAAE;AAAA,IAC5D;AAAA,EACF;AACF;AA0BO,SAAS,0BAA0B,QAAiD;AACzF,QAAM,SAAS,IAAI,aAAa,MAAM;AAEtC,SAAO,OAAO,WAAoD;AAChE,WAAO,OAAO,QAAQ,MAAM,MAAM;AAAA,EACpC;AACF;AAUO,IAAM,yBAAyB;AAS/B,IAAM,2BAA2B;","names":["type","sdkParams"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server-side entry point for @waffo/pancake-nextjs.\n *\n * All functions in this module use the private key for RSA signing\n * and must only run on the server (Next.js Server Actions / Route Handlers).\n *\n * @example\n * ```ts\n * // app/lib/waffo.ts\n * \"use server\";\n * import { createCheckoutAction, createCustomerTokenAction, createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * const config = {\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * };\n *\n * export const checkout = createCheckoutAction(config);\n * export const issueCustomerToken = createCustomerTokenAction(config);\n * export const merchantQuery = createMerchantQueryAction(config);\n * ```\n *\n * @example\n * ```ts\n * // app/account/billing-portal/route.ts — customer portal entry from your own login\n * import { createCustomerPortalRedirect } from \"@waffo/pancake-nextjs/server\";\n *\n * export const GET = createCustomerPortalRedirect(config, {\n * resolveAuthenticatedCustomer: async (request) => getSignedInCustomer(request), // your session\n * });\n * ```\n */\nimport { WaffoPancake } from \"@waffo/pancake-ts\";\n\nimport type {\n RequestOptions,\n WaffoPancakeConfig,\n AnonymousCheckoutParams,\n AuthenticatedCheckoutParams,\n AuthenticatedPlanChangeParams,\n CreatePlanChangeSessionParams,\n CheckoutSessionResult,\n AuthenticatedCheckoutResult,\n IssueSessionTokenParams,\n SessionToken,\n GraphQLParams,\n GraphQLResponse,\n} from \"@waffo/pancake-ts\";\n\n// ============================================================\n// Checkout Action\n// ============================================================\n\n/**\n * Parameters for the checkout server action.\n *\n * The `type` field selects the flow: a new purchase (`anonymous` / `authenticated`)\n * or a plan change for an existing subscription (`planChange` /\n * `authenticatedPlanChange`, which carry the required `originOrderId`).\n */\nexport type CheckoutActionParams =\n | ({ type?: \"anonymous\" } & AnonymousCheckoutParams)\n | ({ type: \"authenticated\" } & AuthenticatedCheckoutParams)\n | ({ type: \"planChange\" } & CreatePlanChangeSessionParams)\n | ({ type: \"authenticatedPlanChange\" } & AuthenticatedPlanChangeParams);\n\n/** Result of the checkout server action */\nexport type CheckoutActionResult = CheckoutSessionResult | AuthenticatedCheckoutResult;\n\n/**\n * Server action signature for checkout.\n *\n * The optional `options` carries an `idempotencyKey`; without one no key is sent\n * and a retried call creates a second session.\n */\nexport type CheckoutAction = (params: CheckoutActionParams, options?: RequestOptions) => Promise<CheckoutActionResult>;\n\n/**\n * Create a server action that handles checkout session creation.\n *\n * The private key is captured in the closure and never sent to the client.\n *\n * Handles both new purchases and plan changes; `params.type` selects the flow.\n *\n * @param config - WaffoPancake client configuration (merchantId + privateKey)\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCheckoutAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const checkout = createCheckoutAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n *\n * @example\n * ```ts\n * // Plan change link for an existing subscription — originOrderId is required\n * const session = await checkout({\n * type: \"planChange\",\n * originOrderId: \"ORD_xxx\",\n * productId: \"PROD_target_plan\",\n * currency: \"USD\",\n * });\n * // session.checkoutUrl points at the change confirmation page\n * ```\n */\nexport function createCheckoutAction(config: WaffoPancakeConfig): CheckoutAction {\n const client = new WaffoPancake(config);\n\n return async (params: CheckoutActionParams, options?: RequestOptions): Promise<CheckoutActionResult> => {\n if (params.type === \"authenticated\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.create(sdkParams, options);\n }\n if (params.type === \"planChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.createPlanChangeSession(sdkParams, options);\n }\n if (params.type === \"authenticatedPlanChange\") {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.authenticated.createPlanChange(sdkParams, options);\n }\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- remove type field before passing to SDK\n const { type, ...sdkParams } = params;\n return client.checkout.anonymous.create(sdkParams, options);\n };\n}\n\n// ============================================================\n// Customer Token Action\n// ============================================================\n\n/** Server action signature for issuing customer tokens */\nexport type CustomerTokenAction = (params: IssueSessionTokenParams, options?: RequestOptions) => Promise<SessionToken>;\n\n/**\n * Create a server action that issues customer session tokens.\n *\n * This is a generic token primitive: it signs whatever `buyerIdentity` it is\n * handed. Never expose it to the browser as a portal or sign-in entry — a\n * Server Action is callable with arbitrary arguments, so a client-chosen\n * `buyerIdentity` would yield another customer's token. To send a signed-in\n * customer to the customer portal, use {@link createCustomerPortalRedirect},\n * which derives identity from your own session.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerTokenAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const issueCustomerToken = createCustomerTokenAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createCustomerTokenAction(config: WaffoPancakeConfig): CustomerTokenAction {\n const client = new WaffoPancake(config);\n\n return async (params: IssueSessionTokenParams, options?: RequestOptions): Promise<SessionToken> => {\n return client.auth.issueSessionToken(params, options);\n };\n}\n\n// ============================================================\n// Customer Portal Redirect\n// ============================================================\n\n/**\n * Store and customer identity for a customer portal link, resolved by the\n * merchant app from its own authenticated session.\n */\nexport interface AuthenticatedPortalCustomer {\n /** Store ID (Short ID `STO_xxx`) whose portal the customer enters */\n storeId: string;\n /** Customer identity in your system — the same value used for authenticated checkout */\n buyerIdentity: string;\n}\n\n/** Options for {@link createCustomerPortalRedirect}. */\nexport interface CustomerPortalRedirectOptions {\n /**\n * Resolve the signed-in customer from YOUR app's authenticated session\n * (session cookie, auth library, …). Return `null` when nobody is signed in.\n *\n * Must not read `buyerIdentity` or `storeId` from anything the browser\n * controls — query string, body, form fields or custom headers. Whoever\n * controls `buyerIdentity` gets that customer's portal.\n */\n resolveAuthenticatedCustomer: (request: Request) => Promise<AuthenticatedPortalCustomer | null> | AuthenticatedPortalCustomer | null;\n /**\n * Where to send a visitor for whom the resolver returned `null`. A path or\n * absolute URL, resolved against the request URL; a function receives the\n * request (e.g. to add your own `returnTo`). Omit to answer 401 instead.\n *\n * Build it from constants or an allow-list, never by copying request\n * parameters verbatim (e.g. a `?next=` value) — that turns this route into an\n * open redirect.\n */\n loginUrl?: string | ((request: Request) => string);\n /**\n * Build the response when creating the portal link fails (API error, invalid\n * store ID, network error). This is the place for your own logging and error\n * reporting. Defaults to a 502 JSON `{ error }` response with a generic\n * message, after one `console.error` line carrying only the error message;\n * the error itself is not echoed to the browser.\n */\n onError?: (error: unknown, request: Request) => Response | Promise<Response>;\n}\n\n/** GET Route Handler returned by {@link createCustomerPortalRedirect}. */\nexport type CustomerPortalRouteHandler = (request: Request) => Promise<Response>;\n\n/**\n * Build headers for a portal route response. Every response — redirect, 401 or\n * error — is `no-store` and `no-referrer`, so a tokenized `Location` is neither\n * cached nor forwarded as a Referer.\n */\nfunction portalHeaders(extra: Record<string, string>): Headers {\n const headers = new Headers(extra);\n headers.set(\"Cache-Control\", \"no-store\");\n headers.set(\"Referrer-Policy\", \"no-referrer\");\n return headers;\n}\n\n/**\n * One-line, log-safe description of a portal link failure: the error message\n * only (never the error object, which may carry a response body), with any\n * `#token=...` fragment redacted.\n */\nfunction safeErrorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return message.replace(/#token=[^\\s\"'&]*/g, \"#token=[redacted]\").replace(/\\s+/g, \" \");\n}\n\n/**\n * Create a GET Route Handler that sends the signed-in customer to your store's\n * Pancake customer portal, without the portal's email verification step.\n *\n * Behavior:\n * - Calls `resolveAuthenticatedCustomer(request)` — your own session check — to\n * get `{ storeId, buyerIdentity }`. Nothing is read from the request's query\n * string, body or headers by the SDK\n * - Signed in: creates a portal link with `client.auth.createCustomerPortalLink()`\n * and answers `302` to `portalUrl` (which carries `#token=...`)\n * - Not signed in: `302` to `loginUrl` when set, otherwise `401`\n * - Link creation fails: `onError(error, request)`, or by default one server-side\n * `console.error` line with the error message and a generic `502 { error }`\n *\n * Side effects:\n * - One signed API request per signed-in visit; each visit issues a new portal session\n * - Every response it builds (not a custom `onError` one) sets `Cache-Control: no-store` and `Referrer-Policy: no-referrer`\n * so the tokenized `Location` is neither cached nor forwarded as a Referer\n *\n * Boundary conditions:\n * - The portal environment is the environment of the API Key in `config`\n * (test key → test portal, prod key → prod portal)\n * - Errors thrown by `resolveAuthenticatedCustomer` propagate (Next.js answers 500)\n * - The default error log is the message only, with any `#token=` fragment redacted;\n * pass `onError` to log or report failures your own way\n * - `loginUrl` must not echo request parameters verbatim, or the route becomes an open redirect\n *\n * A Route Handler rather than a Server Action: the portal entry is a plain link\n * navigation, and only an HTTP redirect keeps the tokenized URL out of client\n * JavaScript while letting the response carry `no-store` / `no-referrer`.\n *\n * @param config - WaffoPancake client configuration (merchantId + privateKey)\n * @param options - Session resolver, login redirect and error handling\n * @returns A Next.js GET Route Handler\n *\n * @example\n * ```ts\n * // app/account/billing-portal/route.ts\n * import { createCustomerPortalRedirect } from \"@waffo/pancake-nextjs/server\";\n * import { auth } from \"@/lib/auth\"; // your auth library\n *\n * export const dynamic = \"force-dynamic\";\n *\n * export const GET = createCustomerPortalRedirect(\n * { merchantId: process.env.WAFFO_MERCHANT_ID!, privateKey: process.env.WAFFO_PRIVATE_KEY! },\n * {\n * resolveAuthenticatedCustomer: async () => {\n * const session = await auth();\n * if (!session?.user) return null;\n * return { storeId: process.env.WAFFO_STORE_ID!, buyerIdentity: session.user.id };\n * },\n * loginUrl: \"/login?returnTo=/account/billing-portal\",\n * },\n * );\n * // In a page: <a href=\"/account/billing-portal\">Manage billing</a>\n * ```\n */\nexport function createCustomerPortalRedirect(\n config: WaffoPancakeConfig,\n options: CustomerPortalRedirectOptions,\n): CustomerPortalRouteHandler {\n const client = new WaffoPancake(config);\n\n return async function GET(request: Request): Promise<Response> {\n const customer = await options.resolveAuthenticatedCustomer(request);\n\n if (!customer) {\n if (options.loginUrl === undefined) {\n return new Response(JSON.stringify({ error: \"Unauthorized\" }), {\n status: 401,\n headers: portalHeaders({ \"Content-Type\": \"application/json\" }),\n });\n }\n const loginUrl = typeof options.loginUrl === \"function\" ? options.loginUrl(request) : options.loginUrl;\n return new Response(null, {\n status: 302,\n headers: portalHeaders({ Location: new URL(loginUrl, request.url).toString() }),\n });\n }\n\n let portalUrl: string;\n try {\n ({ portalUrl } = await client.auth.createCustomerPortalLink({\n storeId: customer.storeId,\n buyerIdentity: customer.buyerIdentity,\n }));\n } catch (error) {\n if (options.onError) {\n return options.onError(error, request);\n }\n console.error(\"[waffo-pancake] createCustomerPortalRedirect failed:\", safeErrorMessage(error));\n return new Response(JSON.stringify({ error: \"Failed to create customer portal link\" }), {\n status: 502,\n headers: portalHeaders({ \"Content-Type\": \"application/json\" }),\n });\n }\n\n return new Response(null, {\n status: 302,\n headers: portalHeaders({ Location: portalUrl }),\n });\n };\n}\n\n// ============================================================\n// Customer Session Action\n// ============================================================\n\n/** Customer action types */\nexport type CustomerSessionActionType =\n | \"cancelSubscription\"\n | \"cancelOnetimeOrder\"\n | \"reactivateSubscription\"\n | \"createRefundTicket\"\n | \"resubmitRefundTicket\"\n | \"createPlanChangeSession\"\n | \"query\";\n\n/** Server action signature for customer session operations */\nexport type CustomerSessionAction = (\n token: string,\n actionType: CustomerSessionActionType,\n params: unknown,\n options?: RequestOptions,\n) => Promise<unknown>;\n\n/**\n * Create a server action that executes customer self-service operations.\n *\n * `config.environment` is required — session tokens carry no environment, and the\n * gateway rejects a session request without the matching header.\n *\n * `createPlanChangeSession` is the customer-driven half of a plan change: it returns\n * a confirmation-page URL, and the platform allows it only when the subscription\n * belongs to that customer, the target plan is in the same product group, and that\n * group's `selfServicePlanChange` is on — otherwise 403. The merchant-only pricing\n * fields are not part of its params; the platform drops them on this path silently.\n *\n * No call sends an idempotency key unless you pass one (`options.idempotencyKey`,\n * the last argument) — the same rule as every other method in the SDK — so a write\n * retried after a timeout executes twice.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createCustomerSessionAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const customerAction = createCustomerSessionAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * environment: \"test\",\n * });\n * ```\n */\nexport function createCustomerSessionAction(config: WaffoPancakeConfig): CustomerSessionAction {\n const client = new WaffoPancake(config);\n\n return async (token: string, actionType: CustomerSessionActionType, params: unknown, options?: RequestOptions): Promise<unknown> => {\n const customer = client.buyer(token);\n switch (actionType) {\n case \"cancelSubscription\":\n return customer.cancelSubscription(params as Parameters<typeof customer.cancelSubscription>[0], options);\n case \"cancelOnetimeOrder\":\n return customer.cancelOnetimeOrder(params as Parameters<typeof customer.cancelOnetimeOrder>[0], options);\n case \"reactivateSubscription\":\n return customer.reactivateSubscription(params as Parameters<typeof customer.reactivateSubscription>[0], options);\n case \"createRefundTicket\":\n return customer.createRefundTicket(params as Parameters<typeof customer.createRefundTicket>[0], options);\n case \"resubmitRefundTicket\":\n return customer.resubmitRefundTicket(params as Parameters<typeof customer.resubmitRefundTicket>[0], options);\n case \"createPlanChangeSession\":\n return customer.createPlanChangeSession(params as Parameters<typeof customer.createPlanChangeSession>[0], options);\n case \"query\":\n // Reads take no key: a cached replay would serve stale data.\n return customer.graphql.query(params as GraphQLParams);\n default:\n throw new Error(`Unknown customer action: ${actionType}`);\n }\n };\n}\n\n// ============================================================\n// Merchant Query Action\n// ============================================================\n\n/** Server action signature for merchant GraphQL queries */\nexport type MerchantQueryAction = (params: GraphQLParams) => Promise<GraphQLResponse>;\n\n/**\n * Create a server action that executes merchant GraphQL queries.\n *\n * @param config - WaffoPancake client configuration\n * @returns A server action function\n *\n * @example\n * ```ts\n * \"use server\";\n * import { createMerchantQueryAction } from \"@waffo/pancake-nextjs/server\";\n *\n * export const merchantQuery = createMerchantQueryAction({\n * merchantId: process.env.WAFFO_MERCHANT_ID!,\n * privateKey: process.env.WAFFO_PRIVATE_KEY!,\n * });\n * ```\n */\nexport function createMerchantQueryAction(config: WaffoPancakeConfig): MerchantQueryAction {\n const client = new WaffoPancake(config);\n\n return async (params: GraphQLParams): Promise<GraphQLResponse> => {\n return client.graphql.query(params);\n };\n}\n\n// ============================================================\n// Deprecated Aliases\n// ============================================================\n\n/** @deprecated Use {@link CustomerTokenAction} instead. */\nexport type BuyerTokenAction = CustomerTokenAction;\n\n/** @deprecated Use {@link createCustomerTokenAction} instead. */\nexport const createBuyerTokenAction = createCustomerTokenAction;\n\n/** @deprecated Use {@link CustomerSessionActionType} instead. */\nexport type BuyerSessionActionType = CustomerSessionActionType;\n\n/** @deprecated Use {@link CustomerSessionAction} instead. */\nexport type BuyerSessionAction = CustomerSessionAction;\n\n/** @deprecated Use {@link createCustomerSessionAction} instead. */\nexport const createBuyerSessionAction = createCustomerSessionAction;\n"],"mappings":"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","names":["type","sdkParams"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waffo/pancake-nextjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Next.js SDK for Waffo Pancake — checkout components and hooks",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"license": "MIT",
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@waffo/pancake-ts": "^0.
|
|
51
|
+
"@waffo/pancake-ts": "^0.26.0",
|
|
52
52
|
"next": ">=14.0.0",
|
|
53
53
|
"react": ">=18.0.0",
|
|
54
54
|
"react-dom": ">=18.0.0"
|