@stardeck-customer-apps/testing 0.28.0 → 0.29.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/SKILL.md CHANGED
@@ -233,6 +233,21 @@ app.payments.setStripePaymentMethods(["card"]); // merchant turned PromptPay of
233
233
  await expect(payments.createStripePaymentIntent({ ...req, method: "promptpay" }))
234
234
  .rejects.toMatchObject({ statusCode: 422, code: "method_not_allowed" });
235
235
 
236
+ // Provider refunds (payments-sdk >= 4.13.0): same routes, summaries and error codes as the
237
+ // platform. A refund never pays out on its own; you move it, then deliver the event, then the
238
+ // app re-reads the refund (as it must in production).
239
+ const refund = await payments.refundStripePaymentIntent(pi.id, {
240
+ idempotencyKey: row.id, amount: 5000, refundRowId: row.id,
241
+ }); // status "pending"; app.payments.getStripePaymentIntent(pi.id).amountRefunded === 5000
242
+ app.payments.settleRefund(refund.id, "failed"); // or "succeeded" / "needs_customer"
243
+ await app.payments.deliverStripeEvent(POST, { refundId: refund.id }); // refund.failed
244
+ // amountRefunded drops back to 0 and the payment can be refunded again.
245
+ // Staff refunded in Stripe's dashboard: already succeeded, no refundRowId. It returns the new
246
+ // refund's id; deliver it with deliverStripeEvent(POST, { refundId: dashId }) the same way.
247
+ const dashId = app.payments.dashboardRefund(pi.id, 3000);
248
+ // Beam: refundBeamCharge(chargeId, { idempotencyKey, amount? }) -> PENDING; settleRefund(id,
249
+ // "succeeded" | "failed"); deliverBeamEvent(POST, { refundId }) -> refund.succeeded / refund.failed.
250
+
236
251
  // File upload
237
252
  const storage = new StorageClient();
238
253
  await storage.upload(new File(["hi"], "note.txt", { type: "text/plain" }));
@@ -467,6 +482,31 @@ externalId })` helper models a trusted platform/channel link, and
467
482
  `already_succeeded` on cancel after payment), and another project's payment id → 404
468
483
  `not_found`. The app's `POST /api/payments/payment-intents` route refuses card (403
469
484
  `CARD_CHARGE_SERVER_ONLY`); create card payments from server code.
485
+ Provider refunds (payments-sdk >= 4.13.0): `refundStripePaymentIntent`, `getStripeRefund`,
486
+ `findStripeCheckoutSessionByPaymentIntent`, `refundBeamCharge` and `getBeamRefund` answer
487
+ like the platform (400 `invalid_request`; 404 `payment_not_owned` / `refund_not_found`; 409
488
+ `payment_not_refundable`, `refund_exceeds_remaining`, `refund_partial_not_allowed`,
489
+ `refund_in_progress`, `idempotency_conflict`; a repeated `idempotencyKey` returns the same
490
+ refund). Refunds start Stripe `pending` / Beam `PENDING` and never pay out by themselves:
491
+ `.settleRefund(refundId, "succeeded" | "failed" | "needs_customer", { failureReason? })`
492
+ moves one (`needs_customer` is Stripe PromptPay's `requires_action` with
493
+ `nextActionExpiresAt`, and throws on a card refund or on Beam; a succeeded Stripe refund can
494
+ still be settled `failed`, as a card refund can fail days later; Beam statuses are final).
495
+ `.dashboardRefund(paymentRef, amount?)` makes a refund the app never requested (a Stripe
496
+ PaymentIntent id or a Beam direct-charge id): already succeeded, no `refundRowId`; returns the new refund's id.
497
+ Neither delivers a webhook: follow with `.deliverStripeEvent(handler, { refundId })`
498
+ (`refund.created` for a new refund, `refund.updated` after a settle, `refund.failed` once
499
+ failed; pass `type` to force one, e.g. to test events arriving out of order) or
500
+ `.deliverBeamEvent(handler, { refundId })` (`refund.succeeded` / `refund.failed`; a PENDING
501
+ refund has no event). The event carries the refund's id, so handlers re-read it with
502
+ `getStripeRefund` / `getBeamRefund`. Failed refunds stop counting: the intent's
503
+ `amountRefunded` (succeeded + pending Stripe refunds) drops back and the amount can be
504
+ refunded again. Beam follows its rules: partial only on `CARD` / `CARD_TOKEN` charges, one
505
+ PENDING refund per charge. Inspect with `.stripeRefunds`, `.getStripeRefund(id)`,
506
+ `.beamRefunds`, `.getBeamRefund(id)`. A paid one-time Stripe Checkout session (`markPaid`)
507
+ gets a succeeded PaymentIntent: `getCheckoutSessionStatus` returns its `paymentIntentId`
508
+ (null before payment) and it can be refunded. Refund calls are server-only; the app's
509
+ payments route does not expose them.
470
510
  Bolt+ controls: `.approveBoltIntent(id)`, `.declineBoltIntent(id)`,
471
511
  `.expireBoltIntent(id)`, `.listBoltIntents()`, `.getBoltIntent(id)`,
472
512
  `.count`, `.clear()`. Approve marks PAID and records a charge; deliver the
@@ -572,7 +612,7 @@ true })` remains source-compatible but the simulator (like the control plane)
572
612
  the returned canonical set. Page directory UIs through `search({ query,
573
613
  cursor, limit })`; `list()` intentionally keeps its previous array shape.
574
614
  - `PaymentsServerClient` (Stripe checkout + Stripe inline payments + Beam payment links + Beam direct
575
- charges and card tokenization + Bolt+ connections/intents/charges, product list) — captured in `app.payments`;
615
+ charges and card tokenization + Bolt+ connections/intents/charges, product list, provider refunds) — captured in `app.payments`;
576
616
  fulfill checkout via `markPaid` (poll) or `deliverStripeEvent` /
577
617
  `deliverBeamEvent` (webhook push). Drive a terminal without hardware via
578
618
  `approveBoltIntent` / `declineBoltIntent` / `expireBoltIntent`. Beam payment-link
@@ -588,8 +628,9 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
588
628
  `stripe.createCheckout`, `stripe.getCheckoutSessionStatus`,
589
629
  `stripe.createPaymentIntent`, `stripe.getPaymentIntent`, `stripe.findPaymentIntents`,
590
630
  `stripe.cancelPaymentIntent`, `stripe.getElementsConfig`, `stripe.getReadiness`,
631
+ `stripe.refundPaymentIntent`, `stripe.getRefund`, `stripe.findCheckoutSessionByIntent`,
591
632
  `beam.createPaymentLink`, `beam.getPaymentLinkStatus`, `beam.createDirectCharge`,
592
- and `beam.getCharge`. HTTP failures support
633
+ `beam.getCharge`, `beam.refundCharge`, and `beam.getRefund`. HTTP failures support
593
634
  statuses 400, 402, 409, 429, 500, and 503 plus optional `code`, `details`, and
594
635
  `retryAfterSeconds`; `{ kind: "network", message }` rejects fetch instead.
595
636
  `app.reset()` clears pending failures.
package/dist/index.d.mts CHANGED
@@ -217,7 +217,7 @@ interface CapturedCheckout {
217
217
  metadata?: Record<string, string>;
218
218
  createdAt: Date;
219
219
  }
220
- type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus" | "beam.createDirectCharge" | "beam.getCharge" | "stripe.createPaymentIntent" | "stripe.getPaymentIntent" | "stripe.findPaymentIntents" | "stripe.cancelPaymentIntent" | "stripe.getElementsConfig" | "stripe.getReadiness";
220
+ type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus" | "beam.createDirectCharge" | "beam.getCharge" | "stripe.createPaymentIntent" | "stripe.getPaymentIntent" | "stripe.findPaymentIntents" | "stripe.cancelPaymentIntent" | "stripe.getElementsConfig" | "stripe.getReadiness" | "stripe.refundPaymentIntent" | "stripe.getRefund" | "stripe.findCheckoutSessionByIntent" | "beam.refundCharge" | "beam.getRefund";
221
221
  type PaymentSimulatorFailure = {
222
222
  kind?: "http";
223
223
  status: 400 | 402 | 404 | 409 | 422 | 429 | 500 | 502 | 503;
@@ -339,6 +339,46 @@ interface SimStripePaymentIntent {
339
339
  /** Unix seconds. */
340
340
  created: number;
341
341
  }
342
+ /** A Stripe refund as the simulator holds it. */
343
+ interface SimStripeRefund {
344
+ id: string;
345
+ paymentIntentId: string;
346
+ /** The project that owns the payment; other projects get 404. */
347
+ projectId: string;
348
+ amount: number;
349
+ /** Lowercase. */
350
+ currency: string;
351
+ status: _stardeck_customer_apps_payments_sdk.StripeRefundStatus;
352
+ failureReason: string | null;
353
+ pendingReason: string | null;
354
+ /** Unix seconds; set while a PromptPay refund waits for the customer's bank details. */
355
+ nextActionExpiresAt: number | null;
356
+ /** The app's local refund row id; null for a refund made in the Stripe dashboard. */
357
+ refundRowId: string | null;
358
+ /** The app's metadata, with Stardeck's reserved keys removed. */
359
+ appMetadata: Record<string, string>;
360
+ reason: string | null;
361
+ /** "app" when requested through the SDK, "dashboard" when made by `dashboardRefund`. */
362
+ origin: "app" | "dashboard";
363
+ /** How many times `settleRefund` has moved it; picks `refund.created` vs `refund.updated`. */
364
+ revision: number;
365
+ /** Unix seconds. */
366
+ created: number;
367
+ }
368
+ /** A Beam refund as the simulator holds it. */
369
+ interface SimBeamRefund {
370
+ refundId: string;
371
+ chargeId: string;
372
+ amount: number;
373
+ /** As Beam returns it, e.g. "THB". */
374
+ currency: string;
375
+ status: _stardeck_customer_apps_payments_sdk.BeamRefundStatus;
376
+ failureCode: string | null;
377
+ reason: string | null;
378
+ origin: "app" | "dashboard";
379
+ /** ISO 8601. */
380
+ createdAt: string;
381
+ }
342
382
  interface TestPayments {
343
383
  get checkouts(): CapturedCheckout[];
344
384
  latest(): CapturedCheckout | undefined;
@@ -388,17 +428,62 @@ interface TestPayments {
388
428
  accountId?: string;
389
429
  } | {
390
430
  paymentIntentId: string;
431
+ } | {
432
+ refundId: string;
433
+ type?: "refund.created" | "refund.updated" | "refund.failed";
391
434
  }, options?: {
392
435
  path?: string;
393
436
  deploymentSecret?: string;
394
437
  }): Promise<Response>;
438
+ /**
439
+ * Deliver a Beam webhook. Pass `{ refundId }` to send the `refund.succeeded`
440
+ * or `refund.failed` event for a settled Beam refund, built from the stored
441
+ * refund. A refund still PENDING has no event: call `settleRefund` first.
442
+ */
395
443
  deliverBeamEvent(handler: PaymentsWebhookHandler, event: {
396
444
  type: string;
397
445
  payload: Record<string, unknown>;
446
+ } | {
447
+ refundId: string;
398
448
  }, options?: {
399
449
  path?: string;
400
450
  deploymentSecret?: string;
401
451
  }): Promise<Response>;
452
+ /** Every Stripe refund so far, oldest first, as stored. */
453
+ get stripeRefunds(): SimStripeRefund[];
454
+ getStripeRefund(refundId: string): SimStripeRefund | undefined;
455
+ /** Every Beam refund so far, oldest first, as stored. */
456
+ get beamRefunds(): SimBeamRefund[];
457
+ getBeamRefund(refundId: string): SimBeamRefund | undefined;
458
+ /**
459
+ * Move a refund the app requested to its next state, as the provider would
460
+ * later. Refunds start `pending` (Stripe PromptPay: `requires_action`) and
461
+ * never pay out on their own.
462
+ *
463
+ * - `"succeeded"`: Stripe `succeeded` / Beam `SUCCEEDED`.
464
+ * - `"failed"`: Stripe `failed` (also from `succeeded`, like a card refund
465
+ * that fails days later) / Beam `FAILED`. A failed refund stops counting
466
+ * toward `amountRefunded` and toward what the payment can still refund.
467
+ * - `"needs_customer"`: Stripe PromptPay only (`requires_action`: the
468
+ * customer must send bank details). Beam has no such state and throws.
469
+ *
470
+ * `failureReason` is Stripe's `failure_reason` or Beam's `failureCode`.
471
+ * Delivers no webhook: follow with `deliverStripeEvent(handler, { refundId })`
472
+ * or `deliverBeamEvent(handler, { refundId })`. Read the result back with
473
+ * `getStripeRefund` / `getBeamRefund` or the SDK.
474
+ */
475
+ settleRefund(refundId: string, outcome: "succeeded" | "failed" | "needs_customer", options?: {
476
+ failureReason?: string;
477
+ }): void;
478
+ /**
479
+ * A refund made in the provider's dashboard, which the app never requested:
480
+ * no `refundRowId`, already succeeded. `paymentRef` is a Stripe PaymentIntent
481
+ * id or a Beam charge id; omit `amount` to refund what remains. The provider's
482
+ * own rules apply (Beam: partial on CARD charges only). Delivers no webhook;
483
+ * follow with `deliverStripeEvent` / `deliverBeamEvent` and `{ refundId }`.
484
+ * Returns the new refund's id.
485
+ */
486
+ dashboardRefund(paymentRef: string, amount?: number): string;
402
487
  /**
403
488
  * Mark a PENDING bolt intent PAID and record a SUCCEEDED charge. Does not
404
489
  * deliver a webhook — call `deliverBeamEvent` with `charge.succeeded` (reuse
package/dist/index.d.ts CHANGED
@@ -217,7 +217,7 @@ interface CapturedCheckout {
217
217
  metadata?: Record<string, string>;
218
218
  createdAt: Date;
219
219
  }
220
- type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus" | "beam.createDirectCharge" | "beam.getCharge" | "stripe.createPaymentIntent" | "stripe.getPaymentIntent" | "stripe.findPaymentIntents" | "stripe.cancelPaymentIntent" | "stripe.getElementsConfig" | "stripe.getReadiness";
220
+ type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus" | "beam.createDirectCharge" | "beam.getCharge" | "stripe.createPaymentIntent" | "stripe.getPaymentIntent" | "stripe.findPaymentIntents" | "stripe.cancelPaymentIntent" | "stripe.getElementsConfig" | "stripe.getReadiness" | "stripe.refundPaymentIntent" | "stripe.getRefund" | "stripe.findCheckoutSessionByIntent" | "beam.refundCharge" | "beam.getRefund";
221
221
  type PaymentSimulatorFailure = {
222
222
  kind?: "http";
223
223
  status: 400 | 402 | 404 | 409 | 422 | 429 | 500 | 502 | 503;
@@ -339,6 +339,46 @@ interface SimStripePaymentIntent {
339
339
  /** Unix seconds. */
340
340
  created: number;
341
341
  }
342
+ /** A Stripe refund as the simulator holds it. */
343
+ interface SimStripeRefund {
344
+ id: string;
345
+ paymentIntentId: string;
346
+ /** The project that owns the payment; other projects get 404. */
347
+ projectId: string;
348
+ amount: number;
349
+ /** Lowercase. */
350
+ currency: string;
351
+ status: _stardeck_customer_apps_payments_sdk.StripeRefundStatus;
352
+ failureReason: string | null;
353
+ pendingReason: string | null;
354
+ /** Unix seconds; set while a PromptPay refund waits for the customer's bank details. */
355
+ nextActionExpiresAt: number | null;
356
+ /** The app's local refund row id; null for a refund made in the Stripe dashboard. */
357
+ refundRowId: string | null;
358
+ /** The app's metadata, with Stardeck's reserved keys removed. */
359
+ appMetadata: Record<string, string>;
360
+ reason: string | null;
361
+ /** "app" when requested through the SDK, "dashboard" when made by `dashboardRefund`. */
362
+ origin: "app" | "dashboard";
363
+ /** How many times `settleRefund` has moved it; picks `refund.created` vs `refund.updated`. */
364
+ revision: number;
365
+ /** Unix seconds. */
366
+ created: number;
367
+ }
368
+ /** A Beam refund as the simulator holds it. */
369
+ interface SimBeamRefund {
370
+ refundId: string;
371
+ chargeId: string;
372
+ amount: number;
373
+ /** As Beam returns it, e.g. "THB". */
374
+ currency: string;
375
+ status: _stardeck_customer_apps_payments_sdk.BeamRefundStatus;
376
+ failureCode: string | null;
377
+ reason: string | null;
378
+ origin: "app" | "dashboard";
379
+ /** ISO 8601. */
380
+ createdAt: string;
381
+ }
342
382
  interface TestPayments {
343
383
  get checkouts(): CapturedCheckout[];
344
384
  latest(): CapturedCheckout | undefined;
@@ -388,17 +428,62 @@ interface TestPayments {
388
428
  accountId?: string;
389
429
  } | {
390
430
  paymentIntentId: string;
431
+ } | {
432
+ refundId: string;
433
+ type?: "refund.created" | "refund.updated" | "refund.failed";
391
434
  }, options?: {
392
435
  path?: string;
393
436
  deploymentSecret?: string;
394
437
  }): Promise<Response>;
438
+ /**
439
+ * Deliver a Beam webhook. Pass `{ refundId }` to send the `refund.succeeded`
440
+ * or `refund.failed` event for a settled Beam refund, built from the stored
441
+ * refund. A refund still PENDING has no event: call `settleRefund` first.
442
+ */
395
443
  deliverBeamEvent(handler: PaymentsWebhookHandler, event: {
396
444
  type: string;
397
445
  payload: Record<string, unknown>;
446
+ } | {
447
+ refundId: string;
398
448
  }, options?: {
399
449
  path?: string;
400
450
  deploymentSecret?: string;
401
451
  }): Promise<Response>;
452
+ /** Every Stripe refund so far, oldest first, as stored. */
453
+ get stripeRefunds(): SimStripeRefund[];
454
+ getStripeRefund(refundId: string): SimStripeRefund | undefined;
455
+ /** Every Beam refund so far, oldest first, as stored. */
456
+ get beamRefunds(): SimBeamRefund[];
457
+ getBeamRefund(refundId: string): SimBeamRefund | undefined;
458
+ /**
459
+ * Move a refund the app requested to its next state, as the provider would
460
+ * later. Refunds start `pending` (Stripe PromptPay: `requires_action`) and
461
+ * never pay out on their own.
462
+ *
463
+ * - `"succeeded"`: Stripe `succeeded` / Beam `SUCCEEDED`.
464
+ * - `"failed"`: Stripe `failed` (also from `succeeded`, like a card refund
465
+ * that fails days later) / Beam `FAILED`. A failed refund stops counting
466
+ * toward `amountRefunded` and toward what the payment can still refund.
467
+ * - `"needs_customer"`: Stripe PromptPay only (`requires_action`: the
468
+ * customer must send bank details). Beam has no such state and throws.
469
+ *
470
+ * `failureReason` is Stripe's `failure_reason` or Beam's `failureCode`.
471
+ * Delivers no webhook: follow with `deliverStripeEvent(handler, { refundId })`
472
+ * or `deliverBeamEvent(handler, { refundId })`. Read the result back with
473
+ * `getStripeRefund` / `getBeamRefund` or the SDK.
474
+ */
475
+ settleRefund(refundId: string, outcome: "succeeded" | "failed" | "needs_customer", options?: {
476
+ failureReason?: string;
477
+ }): void;
478
+ /**
479
+ * A refund made in the provider's dashboard, which the app never requested:
480
+ * no `refundRowId`, already succeeded. `paymentRef` is a Stripe PaymentIntent
481
+ * id or a Beam charge id; omit `amount` to refund what remains. The provider's
482
+ * own rules apply (Beam: partial on CARD charges only). Delivers no webhook;
483
+ * follow with `deliverStripeEvent` / `deliverBeamEvent` and `{ refundId }`.
484
+ * Returns the new refund's id.
485
+ */
486
+ dashboardRefund(paymentRef: string, amount?: number): string;
402
487
  /**
403
488
  * Mark a PENDING bolt intent PAID and record a SUCCEEDED charge. Does not
404
489
  * deliver a webhook — call `deliverBeamEvent` with `charge.succeeded` (reuse