@phpsandbox/sdk 0.5.0 → 0.6.0-canary.1

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
@@ -80,8 +80,30 @@ const checkout = await platform.payments.checkout({
80
80
 
81
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
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
+
83
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.
84
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
+
85
107
  ## Server invitations
86
108
 
87
109
  Server administrators can invite someone by email and inspect the shared invitation state:
@@ -14527,6 +14527,39 @@ function date4(params) {
14527
14527
  config(en_default());
14528
14528
 
14529
14529
  // src/customers.ts
14530
+ var eventName = external_exports.string().trim().min(1).max(100);
14531
+ var customerConfigurationSchema = external_exports.object({
14532
+ funnel: external_exports.object({
14533
+ title: external_exports.string().trim().min(1).max(100),
14534
+ stages: external_exports.array(
14535
+ external_exports.discriminatedUnion("measure", [
14536
+ external_exports.object({
14537
+ id: external_exports.string().regex(/^[a-zA-Z0-9_-]+$/).max(50),
14538
+ label: external_exports.string().trim().min(1).max(100),
14539
+ measure: external_exports.literal("event"),
14540
+ event: eventName
14541
+ }),
14542
+ external_exports.object({
14543
+ id: external_exports.string().regex(/^[a-zA-Z0-9_-]+$/).max(50),
14544
+ label: external_exports.string().trim().min(1).max(100),
14545
+ measure: external_exports.literal("first_payment")
14546
+ }),
14547
+ external_exports.object({
14548
+ id: external_exports.string().regex(/^[a-zA-Z0-9_-]+$/).max(50),
14549
+ label: external_exports.string().trim().min(1).max(100),
14550
+ measure: external_exports.literal("event_after_payment"),
14551
+ event: eventName
14552
+ })
14553
+ ])
14554
+ ).max(12)
14555
+ }).nullable(),
14556
+ followUp: external_exports.object({
14557
+ label: external_exports.string().trim().min(1).max(100),
14558
+ triggerEvent: eventName,
14559
+ prerequisiteEvent: eventName.nullable(),
14560
+ stopOnPayment: external_exports.boolean()
14561
+ }).nullable()
14562
+ });
14530
14563
  var customerModeSchema = external_exports.enum(["test", "live"]);
14531
14564
  var customerSchema = external_exports.object({
14532
14565
  id: external_exports.string(),
@@ -14535,6 +14568,12 @@ var customerSchema = external_exports.object({
14535
14568
  name: external_exports.string().nullable(),
14536
14569
  source: external_exports.string().nullable(),
14537
14570
  marketingSubscribed: external_exports.boolean(),
14571
+ communicationBasis: external_exports.enum(["unknown", "legacy_account", "explicit_opt_in", "unsubscribed"]).optional(),
14572
+ legacyPolicy: external_exports.string().nullable().optional(),
14573
+ joinedAt: external_exports.string().nullable().optional(),
14574
+ importedAt: external_exports.string().nullable().optional(),
14575
+ lastActiveAt: external_exports.string().nullable().optional(),
14576
+ billingStatus: external_exports.enum(["unknown", "free", "paid"]).optional(),
14538
14577
  suppressionReason: external_exports.string().nullable(),
14539
14578
  activatedAt: external_exports.string().nullable(),
14540
14579
  createdAt: external_exports.string().nullable()
@@ -14548,6 +14587,8 @@ var spaceSchema = external_exports.object({
14548
14587
  replyTo: external_exports.string().nullable(),
14549
14588
  senderVerified: external_exports.boolean(),
14550
14589
  domainRecords: external_exports.string().nullable(),
14590
+ configuration: customerConfigurationSchema.optional(),
14591
+ emailTemplate: external_exports.string().nullable().optional(),
14551
14592
  provisionedAt: external_exports.string().nullable().optional(),
14552
14593
  lastRequestAt: external_exports.string().nullable().optional(),
14553
14594
  processedAt: external_exports.string().nullable().optional()
@@ -14560,7 +14601,7 @@ var journeyInputSchema = external_exports.object({
14560
14601
  activationEvent: external_exports.string()
14561
14602
  });
14562
14603
  var journeySchema = journeyInputSchema.extend({
14563
- kind: external_exports.enum(["welcome", "setup", "purchase"]),
14604
+ kind: external_exports.enum(["welcome", "setup", "purchase", "credit_limit"]),
14564
14605
  version: external_exports.number().int().optional(),
14565
14606
  startsAt: external_exports.string().nullable().optional(),
14566
14607
  draft: journeyInputSchema.nullable().optional()
@@ -14576,16 +14617,18 @@ var messageSchema = external_exports.object({
14576
14617
  dueAt: external_exports.string().nullable().optional(),
14577
14618
  attemptedAt: external_exports.string().nullable().optional(),
14578
14619
  receivedAt: external_exports.string().nullable().optional(),
14579
- journeyKind: external_exports.enum(["welcome", "setup", "purchase"]).nullable().optional(),
14620
+ journeyKind: external_exports.enum(["welcome", "setup", "purchase", "credit_limit"]).nullable().optional(),
14580
14621
  journeyVersion: external_exports.number().int().nullable().optional(),
14581
14622
  category: external_exports.enum(["marketing", "service"]).optional(),
14582
14623
  from: external_exports.string().nullable().optional(),
14583
14624
  replyTo: external_exports.string().nullable().optional(),
14584
14625
  renderedText: external_exports.string().nullable().optional(),
14626
+ renderedHtml: external_exports.string().nullable().optional(),
14585
14627
  unsubscribeUrl: external_exports.string().nullable().optional(),
14586
14628
  createdAt: external_exports.string().nullable()
14587
14629
  });
14588
14630
  var overviewSchema = external_exports.object({
14631
+ funnel: external_exports.object({ title: external_exports.string(), stages: external_exports.array(external_exports.object({ id: external_exports.string(), label: external_exports.string(), count: external_exports.number() })) }).nullable().optional(),
14589
14632
  space: spaceSchema.nullable(),
14590
14633
  customers: external_exports.array(customerSchema),
14591
14634
  journeys: external_exports.array(journeySchema),
@@ -14594,10 +14637,58 @@ var overviewSchema = external_exports.object({
14594
14637
  messageTotal: external_exports.number().optional(),
14595
14638
  events: external_exports.array(external_exports.string()).optional()
14596
14639
  });
14640
+ var customerOrganizationSchema = external_exports.object({
14641
+ id: external_exports.string(),
14642
+ externalId: external_exports.string(),
14643
+ name: external_exports.string(),
14644
+ ownerCustomerId: external_exports.string(),
14645
+ firstPaidAt: external_exports.string().nullable(),
14646
+ activatedAt: external_exports.string().nullable()
14647
+ });
14597
14648
  var CustomerRuntime = class {
14598
14649
  constructor(client) {
14599
14650
  this.client = client;
14600
14651
  }
14652
+ /** Configure this application's event funnel and follow-up rules. Does not activate messaging. */
14653
+ async configure(input) {
14654
+ const configuration = customerConfigurationSchema.parse(input);
14655
+ return customerConfigurationSchema.parse(
14656
+ (await this.client.put("/platform/customers/runtime/configuration", {
14657
+ ...configuration,
14658
+ ...input.emailTemplate !== void 0 ? { emailTemplate: input.emailTemplate } : {}
14659
+ })).data
14660
+ );
14661
+ }
14662
+ /** Inspect the workspace and environment selected by this app credential. */
14663
+ async context() {
14664
+ return external_exports.object({ id: external_exports.string(), notebookId: external_exports.string(), mode: customerModeSchema }).parse((await this.client.get("/platform/customers/runtime/context")).data);
14665
+ }
14666
+ /** Pull due conversion follow-ups, then recheck authoritative billing, credit and access. */
14667
+ async pendingFollowUps() {
14668
+ return external_exports.array(external_exports.object({ messageId: external_exports.string(), organizationExternalId: external_exports.string(), actorExternalId: external_exports.string() })).parse((await this.client.get("/platform/customers/runtime/follow-ups")).data);
14669
+ }
14670
+ /** Eligible deliveries run immediately; expired checks are never reused by a later worker. */
14671
+ async resolveFollowUp(messageId, resolution) {
14672
+ return messageSchema.parse(
14673
+ (await this.client.post(`/platform/customers/runtime/follow-ups/${encodeURIComponent(messageId)}/resolve`, {
14674
+ resolution
14675
+ })).data
14676
+ );
14677
+ }
14678
+ async organization(input) {
14679
+ return customerOrganizationSchema.parse(
14680
+ (await this.client.put("/platform/customers/runtime/organizations", input)).data
14681
+ );
14682
+ }
14683
+ /** Report only server-confirmed positive payments from your existing billing integration. */
14684
+ async confirmPayment(organizationExternalId, input) {
14685
+ return customerOrganizationSchema.parse(
14686
+ (await this.client.post(
14687
+ `/platform/customers/runtime/organizations/${encodeURIComponent(organizationExternalId)}/payments`,
14688
+ input
14689
+ )).data
14690
+ );
14691
+ }
14601
14692
  async identify(input) {
14602
14693
  return customerSchema.parse((await this.client.post("/platform/customers/runtime/identify", input)).data);
14603
14694
  }
@@ -15242,14 +15333,14 @@ async function* readPublicationStream(input, options = {}) {
15242
15333
  const reader = stream.getReader();
15243
15334
  const decoder = new TextDecoder();
15244
15335
  let buffer = "";
15245
- let eventName = "message";
15336
+ let eventName2 = "message";
15246
15337
  let data = [];
15247
15338
  let ended = false;
15248
15339
  let receivedResult = false;
15249
15340
  function dispatch() {
15250
- const name = eventName;
15341
+ const name = eventName2;
15251
15342
  const lines = data;
15252
- eventName = "message";
15343
+ eventName2 = "message";
15253
15344
  data = [];
15254
15345
  if (!lines.length || !PublishStreamSseEventNameSchema.safeParse(name).success) {
15255
15346
  return null;
@@ -15281,7 +15372,7 @@ async function* readPublicationStream(input, options = {}) {
15281
15372
  content = content.slice(1);
15282
15373
  }
15283
15374
  if (field === "event") {
15284
- eventName = content;
15375
+ eventName2 = content;
15285
15376
  }
15286
15377
  if (field === "data") {
15287
15378
  data.push(content);