@phpsandbox/sdk 0.4.0 → 0.6.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,6 +55,55 @@ PHPSandbox.rest('token', 'https://api.phpsandbox.io/v1', {
55
55
  });
56
56
  ```
57
57
 
58
+ ## App server integration
59
+
60
+ Apps hosted on PHPSandbox can use one environment-scoped identity for Customers and Payments:
61
+
62
+ ```ts
63
+ import { PHPSandbox } from '@phpsandbox/sdk';
64
+
65
+ const platform = PHPSandbox.app();
66
+ const customer = await platform.customers.identify({
67
+ externalId: account.customerExternalId,
68
+ email: account.email,
69
+ billingStatus: account.hasSubscription ? 'paid' : 'free',
70
+ });
71
+ const checkout = await platform.payments.checkout({
72
+ customerId: customer.id,
73
+ requestId: order.requestId,
74
+ orderId: order.id,
75
+ priceId: order.priceId,
76
+ successPath: '/thanks',
77
+ cancelPath: '/account',
78
+ });
79
+ ```
80
+
81
+ Persist an opaque UUID on each app account for its external customer identity; separate databases must not reuse sequential IDs. Initialize this client only on the app server. It reads the provisioned app configuration and exposes only app runtime capabilities, without notebook or organization administration. Production uses its own identity and never falls back to development. Select `{ mode: 'test' }` once for an intentional sandbox run. Runtimes without `process.env` can supply server bindings through `{ env }`.
82
+
83
+ Applications can configure their own organization conversion stages and one event follow-up. Configuration never activates a journey:
84
+
85
+ ```ts
86
+ await app.customers.configure({
87
+ funnel: {
88
+ title: 'Learning outcomes',
89
+ stages: [{ id: 'lesson', label: 'Completed a lesson', measure: 'event', event: 'lesson.completed' }],
90
+ },
91
+ followUp: { label: 'Help learners', triggerEvent: 'lesson.stalled', prerequisiteEvent: null, stopOnPayment: false },
92
+ emailTemplate: '<html><body><h1>{{subject}}</h1><main>{{content}}</main></body></html>',
93
+ });
94
+ ```
95
+
96
+ Stages support `event`, `first_payment`, and `event_after_payment` measures. Historical imports never count as new conversions or trigger event follow-ups. The existing `credit_limit` journey identifier is retained for compatibility; its trigger, prerequisite and payment rule now come from application configuration, while its delay and reviewed content remain journey settings. Pause the journey before changing its rules, then review and activate it separately. Due follow-ups require a fresh application eligibility resolution through `pendingFollowUps()` and `resolveFollowUp()`.
97
+
98
+ HTML templates support escaped `subject`, `content`, `name`, `email` and `unsubscribeUrl` placeholders; `content` is required. Plain text remains available, and marketing messages always receive an unsubscribe footer. Set `emailTemplate: null` to use plain text. Queued messages and reviewed campaigns preserve their selected design. Production currently uses a fixed platform From address; display name and Reply-To are configurable. Custom sending domains are not supported.
99
+
100
+
101
+ Verify incoming payment callbacks with `platform.payments.verifyEvent(rawBody, signature)`, passing the `phpsandbox-signature` header. The client verifies the original bytes, timestamp and environment before returning the event; the app must still validate order ownership and apply revisions idempotently.
102
+
103
+ Application Customers also supports historical identification (`historical`, `joinedAt`, `lastActiveAt`), separate legacy-account eligibility (`legacyPolicy`), known unsubscribes and suppressions, organization membership snapshots, and confirmed positive payments from existing billing. Use `customers.organization()` to synchronize relationships and `customers.confirmPayment()` only after backend provider verification. Historical payments never create first-payment conversions.
104
+
105
+ For approved credit-limit journeys, an application worker pulls `customers.pendingFollowUps()`, rechecks authoritative payment, access and actual credits, then immediately calls `customers.resolveFollowUp()`. Delivery rechecks contact eligibility and requires a fresh application decision. Keep original preference timestamps with `customers.preferences(..., { subscribed, source, occurredAt })`; a legacy policy does not fabricate consent.
106
+
58
107
  ## Server invitations
59
108
 
60
109
  Server administrators can invite someone by email and inspect the shared invitation state:
@@ -309,6 +358,16 @@ if (!state.enabled) {
309
358
  await notebook.mail.enable();
310
359
  }
311
360
 
361
+ // Capture a development email in the notebook inbox without external delivery.
362
+ await notebook.mail.capture({
363
+ requestId: crypto.randomUUID(),
364
+ from: 'hello@example.com',
365
+ fromName: 'Example app',
366
+ to: 'alex@example.com',
367
+ subject: 'Welcome',
368
+ text: 'Hello Alex',
369
+ });
370
+
312
371
  const mails = await notebook.mail.list();
313
372
  const mail = await notebook.mail.get(mails.data[0].hash);
314
373
 
package/dist/app.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type { Client } from './index.js';
2
+ import { CustomerRuntime } from './customers.js';
3
+ import { PaymentRuntime, type PaymentMode } from './payments.js';
4
+ export interface AppRuntimeOptions {
5
+ /** Defaults to live in production and test otherwise. There is no fallback between modes. */
6
+ mode?: PaymentMode;
7
+ /** Supply server bindings explicitly on runtimes without process.env. */
8
+ env?: Record<string, string | undefined>;
9
+ }
10
+ export declare function resolveAppConfiguration(options: AppRuntimeOptions): {
11
+ mode: "test" | "live";
12
+ token: string;
13
+ url: string;
14
+ };
15
+ /** Scoped app-server APIs. Organization and workspace management are deliberately excluded. */
16
+ export declare class AppRuntime {
17
+ readonly mode: PaymentMode;
18
+ readonly customers: CustomerRuntime;
19
+ readonly payments: PaymentRuntime;
20
+ constructor(client: Client, token: string, mode: PaymentMode);
21
+ }
package/dist/app.js ADDED
@@ -0,0 +1,23 @@
1
+ import { CustomerRuntime } from './customers.js';
2
+ import { PaymentRuntime } from './payments.js';
3
+ export function resolveAppConfiguration(options) {
4
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
5
+ throw new Error('Application credentials must only be used on a trusted server.');
6
+ }
7
+ const environment = options.env ?? (typeof process === 'undefined' ? {} : process.env);
8
+ const mode = options.mode ?? (environment.NODE_ENV === 'production' ? 'live' : 'test');
9
+ const token = environment[`PHPSANDBOX_APP_${mode.toUpperCase()}_TOKEN`];
10
+ const url = environment.PHPSANDBOX_APP_API_URL;
11
+ if (!token || !/^happ_[a-f0-9]{64}$/.test(token) || !url) {
12
+ throw new Error(`Connect your app's ${mode === 'live' ? 'production' : 'development'} platform environment before using Customers or Payments.`);
13
+ }
14
+ return { mode, token, url };
15
+ }
16
+ /** Scoped app-server APIs. Organization and workspace management are deliberately excluded. */
17
+ export class AppRuntime {
18
+ constructor(client, token, mode) {
19
+ this.mode = mode;
20
+ this.customers = new CustomerRuntime(client);
21
+ this.payments = new PaymentRuntime(client, { token, mode });
22
+ }
23
+ }