@wopr-network/platform-core 1.42.1 → 1.42.3

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.
@@ -39,6 +39,8 @@ export declare class Instance {
39
39
  /** Simple per-instance mutex to serialize start/stop/restart/remove. */
40
40
  private lockPromise;
41
41
  constructor(deps: InstanceDeps);
42
+ /** Serialize to a plain object safe for JSON.stringify / tRPC responses. */
43
+ toJSON(): Record<string, unknown>;
42
44
  /**
43
45
  * Remote instances have containerId like "remote:node-3".
44
46
  * Local Docker operations are not supported — callers (e.g. wopr-platform)
@@ -25,6 +25,21 @@ export class Instance {
25
25
  this.eventEmitter = deps.eventEmitter;
26
26
  this.botMetricsTracker = deps.botMetricsTracker;
27
27
  }
28
+ /** Serialize to a plain object safe for JSON.stringify / tRPC responses. */
29
+ toJSON() {
30
+ return {
31
+ id: this.id,
32
+ containerId: this.containerId,
33
+ containerName: this.containerName,
34
+ url: this.url,
35
+ name: this.profile.name,
36
+ image: this.profile.image,
37
+ tenantId: this.profile.tenantId,
38
+ env: this.profile.env,
39
+ restartPolicy: this.profile.restartPolicy,
40
+ nodeId: this.profile.nodeId,
41
+ };
42
+ }
28
43
  /**
29
44
  * Remote instances have containerId like "remote:node-3".
30
45
  * Local Docker operations are not supported — callers (e.g. wopr-platform)
@@ -61,16 +61,23 @@ export async function handleWebhookEvent(deps, event) {
61
61
  case "checkout.session.completed": {
62
62
  const session = event.data.object;
63
63
  const tenant = session.client_reference_id ?? session.metadata?.wopr_tenant;
64
- if (!tenant || !session.customer) {
64
+ if (!tenant) {
65
65
  result = { handled: false, event_type: event.type };
66
66
  break;
67
67
  }
68
- const customerId = typeof session.customer === "string" ? session.customer : session.customer.id;
69
- // Upsert tenant-to-customer mapping (no subscription).
70
- await deps.tenantRepo.upsert({
71
- tenant,
72
- processorCustomerId: customerId,
73
- });
68
+ const customerId = session.customer
69
+ ? typeof session.customer === "string"
70
+ ? session.customer
71
+ : session.customer.id
72
+ : null;
73
+ // Upsert tenant-to-customer mapping only when a customer ID is present
74
+ // (guest checkouts have no Stripe customer).
75
+ if (customerId) {
76
+ await deps.tenantRepo.upsert({
77
+ tenant,
78
+ processorCustomerId: customerId,
79
+ });
80
+ }
74
81
  // Determine credit amount from price metadata or payment amount.
75
82
  const amountPaid = session.amount_total; // in cents
76
83
  if (amountPaid == null || amountPaid <= 0) {
@@ -126,12 +126,17 @@ describe("handleWebhookEvent (credit model)", () => {
126
126
  event_type: "checkout.session.completed",
127
127
  });
128
128
  });
129
- it("returns handled:false when customer is missing", async () => {
129
+ it("handles guest checkout when customer is null (tenant known via client_reference_id)", async () => {
130
130
  const event = createCheckoutEvent({
131
131
  customer: null,
132
132
  });
133
133
  const result = await handleWebhookEvent(deps, event);
134
- expect(result.handled).toBe(false);
134
+ // Guest checkout: no Stripe customer, but tenant is known — should still credit.
135
+ expect(result.handled).toBe(true);
136
+ expect(result.tenant).toBeDefined();
137
+ // No customer mapping should be stored for a guest checkout.
138
+ const mapping = await tenantRepo.getByTenant(result.tenant ?? "");
139
+ expect(mapping).toBeNull();
135
140
  });
136
141
  it("returns creditedCents:0 when amount_total is 0", async () => {
137
142
  const event = createCheckoutEvent({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.42.1",
3
+ "version": "1.42.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -59,6 +59,22 @@ export class Instance {
59
59
  this.botMetricsTracker = deps.botMetricsTracker;
60
60
  }
61
61
 
62
+ /** Serialize to a plain object safe for JSON.stringify / tRPC responses. */
63
+ toJSON(): Record<string, unknown> {
64
+ return {
65
+ id: this.id,
66
+ containerId: this.containerId,
67
+ containerName: this.containerName,
68
+ url: this.url,
69
+ name: this.profile.name,
70
+ image: this.profile.image,
71
+ tenantId: this.profile.tenantId,
72
+ env: this.profile.env,
73
+ restartPolicy: this.profile.restartPolicy,
74
+ nodeId: this.profile.nodeId,
75
+ };
76
+ }
77
+
62
78
  /**
63
79
  * Remote instances have containerId like "remote:node-3".
64
80
  * Local Docker operations are not supported — callers (e.g. wopr-platform)
@@ -171,13 +171,18 @@ describe("handleWebhookEvent (credit model)", () => {
171
171
  });
172
172
  });
173
173
 
174
- it("returns handled:false when customer is missing", async () => {
174
+ it("handles guest checkout when customer is null (tenant known via client_reference_id)", async () => {
175
175
  const event = createCheckoutEvent({
176
176
  customer: null,
177
177
  });
178
178
  const result = await handleWebhookEvent(deps, event);
179
179
 
180
- expect(result.handled).toBe(false);
180
+ // Guest checkout: no Stripe customer, but tenant is known — should still credit.
181
+ expect(result.handled).toBe(true);
182
+ expect(result.tenant).toBeDefined();
183
+ // No customer mapping should be stored for a guest checkout.
184
+ const mapping = await tenantRepo.getByTenant(result.tenant ?? "");
185
+ expect(mapping).toBeNull();
181
186
  });
182
187
 
183
188
  it("returns creditedCents:0 when amount_total is 0", async () => {
@@ -127,18 +127,25 @@ export async function handleWebhookEvent(deps: WebhookDeps, event: Stripe.Event)
127
127
  const session = event.data.object as Stripe.Checkout.Session;
128
128
  const tenant = session.client_reference_id ?? session.metadata?.wopr_tenant;
129
129
 
130
- if (!tenant || !session.customer) {
130
+ if (!tenant) {
131
131
  result = { handled: false, event_type: event.type };
132
132
  break;
133
133
  }
134
134
 
135
- const customerId = typeof session.customer === "string" ? session.customer : session.customer.id;
135
+ const customerId = session.customer
136
+ ? typeof session.customer === "string"
137
+ ? session.customer
138
+ : session.customer.id
139
+ : null;
136
140
 
137
- // Upsert tenant-to-customer mapping (no subscription).
138
- await deps.tenantRepo.upsert({
139
- tenant,
140
- processorCustomerId: customerId,
141
- });
141
+ // Upsert tenant-to-customer mapping only when a customer ID is present
142
+ // (guest checkouts have no Stripe customer).
143
+ if (customerId) {
144
+ await deps.tenantRepo.upsert({
145
+ tenant,
146
+ processorCustomerId: customerId,
147
+ });
148
+ }
142
149
 
143
150
  // Determine credit amount from price metadata or payment amount.
144
151
  const amountPaid = session.amount_total; // in cents