@almadar/integrations 2.7.0 → 2.8.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.
@@ -135,4 +135,4 @@ declare abstract class BaseIntegration {
135
135
  protected executeWithRetry<T>(fn: () => Promise<T>): Promise<T>;
136
136
  }
137
137
 
138
- export { BaseIntegration as B, type IntegrationParams as I, type ValidationError as V, type IntegrationLogger as a, type IntegrationErrorCode as b, type IntegrationConfig as c, type IntegrationResult as d, IntegrationError as e, type ValidationResult as f, validateParams as v };
138
+ export { BaseIntegration as B, type IntegrationConfig as I, type ValidationError as V, IntegrationError as a, type IntegrationErrorCode as b, type IntegrationLogger as c, type IntegrationParams as d, type IntegrationResult as e, type ValidationResult as f, validateParams as v };
@@ -1,4 +1,4 @@
1
- import { c as IntegrationConfig, B as BaseIntegration, I as IntegrationParams, d as IntegrationResult } from './BaseIntegration-DjXCkytU.js';
1
+ import { I as IntegrationConfig, B as BaseIntegration, d as IntegrationParams, e as IntegrationResult } from './BaseIntegration-d17YX8KE.js';
2
2
 
3
3
  /**
4
4
  * Factory for creating and managing integration instances
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { I as IntegrationParams, a as IntegrationLogger, b as IntegrationErrorCode, c as IntegrationConfig, B as BaseIntegration, d as IntegrationResult } from './BaseIntegration-DjXCkytU.js';
2
- export { e as IntegrationError, V as ValidationError, f as ValidationResult, v as validateParams } from './BaseIntegration-DjXCkytU.js';
1
+ import { d as IntegrationParams, c as IntegrationLogger, b as IntegrationErrorCode, I as IntegrationConfig, B as BaseIntegration, e as IntegrationResult } from './BaseIntegration-d17YX8KE.js';
2
+ export { a as IntegrationError, V as ValidationError, f as ValidationResult, v as validateParams } from './BaseIntegration-d17YX8KE.js';
3
3
  import { ServiceParams, LogMeta } from '@almadar/core';
4
- export { I as IntegrationFactory, g as getIntegrationFactory, r as resetIntegrationFactory } from './factory-BoDqQ89e.js';
4
+ export { I as IntegrationFactory, g as getIntegrationFactory, r as resetIntegrationFactory } from './factory-BcFFy4JA.js';
5
5
  export { GitHubIntegration } from './integrations/github/index.js';
6
6
 
7
7
  /**
@@ -889,15 +889,245 @@ declare function isKnownIntegration(name: string): boolean;
889
889
  declare function getRegisteredIntegrations(): string[];
890
890
 
891
891
  /**
892
- * Stripe integration for payment processing
892
+ * Canonical Almadar shapes for the Stripe integration.
893
+ *
894
+ * Consumers (apps/builder server, future apps) MUST consume these
895
+ * canonical types — never raw `Stripe.*` shapes from the `stripe` SDK.
896
+ * The `apps/builder is an assembler` rule applies inbound webhooks as
897
+ * well as outbound actions: parse, narrow, and re-shape at this layer
898
+ * so the SDK version never appears in apps/builder code.
899
+ *
900
+ * Mirrors the pattern used by the GitHub integration (PR / Issue / Repo
901
+ * canonical shapes in `./github/types.ts`).
902
+ */
903
+ /** Almadar-level tier identifier; mirrors `apps/builder` `TierKey`. */
904
+ type AlmadarTier = 'free' | 'solo' | 'teams';
905
+ /** Subscription status mapped from Stripe's lifecycle. */
906
+ type AlmadarSubscriptionStatus = 'active' | 'past_due' | 'canceled' | 'incomplete' | 'incomplete_expired' | 'trialing' | 'unpaid';
907
+ interface AlmadarCustomer {
908
+ customerId: string;
909
+ email: string | null;
910
+ /** `users/{uid}` reference stored as Stripe metadata for webhook resolution. */
911
+ almadarUid: string | null;
912
+ }
913
+ interface AlmadarSubscription {
914
+ subscriptionId: string;
915
+ customerId: string;
916
+ status: AlmadarSubscriptionStatus;
917
+ /** Stripe Price ID currently attached. */
918
+ priceId: string;
919
+ /** Seats / quantity (Solo=1, Teams=N). */
920
+ quantity: number;
921
+ /** Period boundaries in ISO timestamps. */
922
+ currentPeriodStart: string;
923
+ currentPeriodEnd: string;
924
+ cancelAtPeriodEnd: boolean;
925
+ /** Resolved tier from the price ID. `null` if price doesn't match a known tier. */
926
+ tier: AlmadarTier | null;
927
+ }
928
+ interface AlmadarCheckoutSession {
929
+ /** Hosted Checkout URL. Client redirects here. */
930
+ url: string;
931
+ /** Session id, useful for tracking. */
932
+ sessionId: string;
933
+ customerId: string | null;
934
+ }
935
+ interface AlmadarPortalSession {
936
+ /** Hosted Billing Portal URL. Client redirects here. */
937
+ url: string;
938
+ customerId: string;
939
+ }
940
+ /**
941
+ * Canonical webhook event surface — discriminated union over the five
942
+ * Stripe events apps/builder reacts to. Anything outside this set comes
943
+ * back as `{ error: 'unknown-event-type' }`; signature failures are
944
+ * `{ error: 'bad-signature' }`.
945
+ *
946
+ * IMPORTANT: NEVER expose `Stripe.Event` / `Stripe.Subscription` /
947
+ * `Stripe.Invoice` to consumers — that re-creates the SDK-version
948
+ * coupling we're explicitly breaking.
949
+ */
950
+ interface AlmadarInvoicePayment {
951
+ customerId: string;
952
+ subscriptionId: string | null;
953
+ paidAt: string;
954
+ amountUsd: number;
955
+ }
956
+ interface AlmadarInvoiceFailure {
957
+ customerId: string;
958
+ subscriptionId: string | null;
959
+ failedAt: string;
960
+ reason: string;
961
+ }
962
+ type AlmadarStripeEventOk = {
963
+ type: 'subscription.created';
964
+ eventId: string;
965
+ data: AlmadarSubscription;
966
+ } | {
967
+ type: 'subscription.updated';
968
+ eventId: string;
969
+ data: AlmadarSubscription;
970
+ } | {
971
+ type: 'subscription.deleted';
972
+ eventId: string;
973
+ data: {
974
+ subscriptionId: string;
975
+ customerId: string;
976
+ };
977
+ } | {
978
+ type: 'invoice.payment_succeeded';
979
+ eventId: string;
980
+ data: AlmadarInvoicePayment;
981
+ } | {
982
+ type: 'invoice.payment_failed';
983
+ eventId: string;
984
+ data: AlmadarInvoiceFailure;
985
+ };
986
+ type AlmadarStripeEvent = AlmadarStripeEventOk | {
987
+ error: 'bad-signature';
988
+ } | {
989
+ error: 'unknown-event-type';
990
+ stripeType: string;
991
+ };
992
+ /**
993
+ * Configuration consumed by `createCheckoutSession`. `tier` controls the
994
+ * price selection; `successUrl` / `cancelUrl` are app-supplied so the
995
+ * integration stays app-agnostic.
996
+ */
997
+ interface CreateCheckoutInput {
998
+ customerId: string | null;
999
+ /** Tier the user wants to subscribe to. */
1000
+ tier: 'solo' | 'teams';
1001
+ /** Solo = 1; Teams = 1..3. */
1002
+ quantity: number;
1003
+ successUrl: string;
1004
+ cancelUrl: string;
1005
+ /** Optional metadata written onto the Stripe Subscription on creation. */
1006
+ metadata: Record<string, string>;
1007
+ }
1008
+ interface CreatePortalInput {
1009
+ customerId: string;
1010
+ returnUrl: string;
1011
+ }
1012
+ interface CreateCustomerInput {
1013
+ email: string;
1014
+ almadarUid: string;
1015
+ displayName: string | null;
1016
+ }
1017
+ interface UpdateSubscriptionInput {
1018
+ subscriptionId: string;
1019
+ quantity?: number;
1020
+ cancelAtPeriodEnd?: boolean;
1021
+ }
1022
+ interface CancelSubscriptionInput {
1023
+ subscriptionId: string;
1024
+ /** Default `true`: cancel at period end so the user keeps access. */
1025
+ atPeriodEnd: boolean;
1026
+ }
1027
+ /**
1028
+ * Price → tier mapping table. The integration imports this at construction
1029
+ * time from `IntegrationConfig.env`; consumers pass the IDs they have
1030
+ * provisioned in their Stripe account.
1031
+ *
1032
+ * Solo: monthly $20 fixed. Teams: monthly $20 per seat (quantity drives total).
1033
+ */
1034
+ interface StripePriceMap {
1035
+ solo: string;
1036
+ teams: string;
1037
+ }
1038
+
1039
+ /**
1040
+ * Verify + parse Stripe webhook signatures into canonical Almadar
1041
+ * events. Apps/builder calls this from its `/billing/stripe-webhook`
1042
+ * route and switches on the returned discriminated union — it never
1043
+ * imports the `stripe` SDK or any `Stripe.*` shape.
1044
+ *
1045
+ * The five handled event types mirror docs §5.2 / `Almadar_Studio_Subscriptions.md`:
1046
+ * - customer.subscription.created
1047
+ * - customer.subscription.updated
1048
+ * - customer.subscription.deleted
1049
+ * - invoice.payment_succeeded
1050
+ * - invoice.payment_failed
1051
+ *
1052
+ * Anything else returns `{ error: 'unknown-event-type', stripeType: … }`
1053
+ * so the route can acknowledge with 200 + a log line (Stripe stops
1054
+ * retrying acknowledged events).
1055
+ */
1056
+
1057
+ interface VerifyAndParseInput {
1058
+ rawBody: string | Buffer;
1059
+ signature: string;
1060
+ /** Stripe webhook secret (`whsec_…`). */
1061
+ secret: string;
1062
+ /** Map of provisioned Stripe Price IDs → Almadar tier. */
1063
+ prices: StripePriceMap;
1064
+ }
1065
+ /**
1066
+ * Verify Stripe's signature header against the raw body and shape the
1067
+ * payload into a canonical `AlmadarStripeEvent`. The `stripe` SDK is
1068
+ * only used inside this module.
1069
+ */
1070
+ declare function verifyAndParseStripeEvent(input: VerifyAndParseInput): AlmadarStripeEvent;
1071
+
1072
+ /**
1073
+ * Stripe integration for payment processing and subscription billing.
1074
+ *
1075
+ * Two surfaces:
1076
+ * - Legacy `execute(action, params)` switch for the original three
1077
+ * PaymentIntent actions (kept for back-compat).
1078
+ * - New typed methods (`createCustomer`, `createCheckoutSession`,
1079
+ * `createSubscription`, etc.) that return canonical `Almadar*`
1080
+ * types. Consumers MUST prefer these — `Stripe.*` types never leak.
893
1081
  */
894
1082
  declare class StripeIntegration extends BaseIntegration {
895
1083
  private client;
1084
+ private prices;
896
1085
  constructor(config: IntegrationConfig);
1086
+ /** Provisioned Price IDs the integration was constructed with. */
1087
+ getPrices(): StripePriceMap;
897
1088
  execute(action: string, params: IntegrationParams): Promise<IntegrationResult>;
898
1089
  private createPaymentIntent;
899
1090
  private confirmPayment;
900
1091
  private refund;
1092
+ /** Look up an existing customer by Stripe ID. */
1093
+ getCustomer(customerId: string): Promise<AlmadarCustomer | null>;
1094
+ /**
1095
+ * Create a Stripe Customer for the given Almadar user. `almadarUid` is
1096
+ * stored as Stripe metadata so webhook handlers can resolve back to
1097
+ * the right `users/{uid}` document.
1098
+ */
1099
+ createCustomer(input: CreateCustomerInput): Promise<AlmadarCustomer>;
1100
+ /**
1101
+ * Create a Stripe-hosted Checkout Session for the given tier. Client
1102
+ * redirects the user to the returned `url`; on success Stripe fires
1103
+ * `customer.subscription.created`, which the apps/builder webhook
1104
+ * handler turns into a `users/{uid}.tier` write.
1105
+ */
1106
+ createCheckoutSession(input: CreateCheckoutInput): Promise<AlmadarCheckoutSession>;
1107
+ /** Create a Billing Portal session for self-service plan management. */
1108
+ createBillingPortalSession(input: CreatePortalInput): Promise<AlmadarPortalSession>;
1109
+ /** Fetch a subscription and shape it into the canonical form. */
1110
+ getSubscription(subscriptionId: string): Promise<AlmadarSubscription>;
1111
+ /**
1112
+ * Create a subscription directly (server-side, no Checkout). Used by
1113
+ * P13.3 Solo → Teams upgrade flow.
1114
+ */
1115
+ createSubscription(input: {
1116
+ customerId: string;
1117
+ tier: 'solo' | 'teams';
1118
+ quantity: number;
1119
+ metadata: Record<string, string>;
1120
+ }): Promise<AlmadarSubscription>;
1121
+ /**
1122
+ * Update quantity or cancel-at-period-end. Used for Teams seat resize
1123
+ * and Solo → Teams transition.
1124
+ */
1125
+ updateSubscription(input: UpdateSubscriptionInput): Promise<AlmadarSubscription>;
1126
+ /**
1127
+ * Cancel a subscription. Defaults to `atPeriodEnd: true` so the user
1128
+ * keeps access until the current period ends.
1129
+ */
1130
+ cancelSubscription(input: CancelSubscriptionInput): Promise<AlmadarSubscription>;
901
1131
  }
902
1132
 
903
1133
  /**
@@ -1148,4 +1378,4 @@ declare class DockerIntegration extends BaseIntegration {
1148
1378
  private list;
1149
1379
  }
1150
1380
 
1151
- export { BaseIntegration, type CLIActions, CLIIntegration, ConsoleLogger, type DeepAgentActions, DeepAgentIntegration, type DockerActions, DockerIntegration, type EmailActions, EmailIntegration, type GitHubActions, type IntegrationActionName, IntegrationConfig, type IntegrationConstructor, type IntegrationContracts, IntegrationErrorCode, IntegrationLogger, type IntegrationName, IntegrationResult, LLMIntegration, type LLMIntegrationActions, type OAuthActions, OAuthIntegration, type OtelActions, OtelIntegration, type QueueActions, QueueIntegration, type RedisActions, RedisIntegration, type RetryConfig, type StorageActions, StorageIntegration, type StripeActions, StripeIntegration, type TwilioActions, TwilioIntegration, type YouTubeActions, YouTubeIntegration, getIntegration, getRegisteredIntegrations, isKnownIntegration, registerIntegration, withRetry };
1381
+ export { type AlmadarCheckoutSession, type AlmadarCustomer, type AlmadarInvoiceFailure, type AlmadarInvoicePayment, type AlmadarPortalSession, type AlmadarStripeEvent, type AlmadarStripeEventOk, type AlmadarSubscription, type AlmadarSubscriptionStatus, type AlmadarTier, BaseIntegration, type CLIActions, CLIIntegration, type CancelSubscriptionInput, ConsoleLogger, type CreateCheckoutInput, type CreateCustomerInput, type CreatePortalInput, type DeepAgentActions, DeepAgentIntegration, type DockerActions, DockerIntegration, type EmailActions, EmailIntegration, type GitHubActions, type IntegrationActionName, IntegrationConfig, type IntegrationConstructor, type IntegrationContracts, IntegrationErrorCode, IntegrationLogger, type IntegrationName, IntegrationResult, LLMIntegration, type LLMIntegrationActions, type OAuthActions, OAuthIntegration, type OtelActions, OtelIntegration, type QueueActions, QueueIntegration, type RedisActions, RedisIntegration, type RetryConfig, type StorageActions, StorageIntegration, type StripeActions, StripeIntegration, type StripePriceMap, type TwilioActions, TwilioIntegration, type UpdateSubscriptionInput, type VerifyAndParseInput, type YouTubeActions, YouTubeIntegration, getIntegration, getRegisteredIntegrations, isKnownIntegration, registerIntegration, verifyAndParseStripeEvent, withRetry };
package/dist/index.js CHANGED
@@ -278,6 +278,179 @@ function resetIntegrationFactory() {
278
278
  _factory?.reset();
279
279
  _factory = null;
280
280
  }
281
+ var STRIPE_API_VERSION = "2025-02-24.acacia";
282
+ function priceToTier(priceId, prices) {
283
+ if (priceId === prices.solo) return "solo";
284
+ if (priceId === prices.teams) return "teams";
285
+ return null;
286
+ }
287
+ function mapStatus(status) {
288
+ switch (status) {
289
+ case "active":
290
+ case "past_due":
291
+ case "canceled":
292
+ case "incomplete":
293
+ case "incomplete_expired":
294
+ case "trialing":
295
+ case "unpaid":
296
+ return status;
297
+ case "paused":
298
+ return "unpaid";
299
+ default: {
300
+ return "incomplete";
301
+ }
302
+ }
303
+ }
304
+ function isoFromUnix(seconds) {
305
+ return new Date(seconds * 1e3).toISOString();
306
+ }
307
+ function shapeSubscription(sub, prices) {
308
+ const firstItem = sub.items.data[0];
309
+ const priceId = firstItem?.price.id ?? "";
310
+ const quantity = firstItem?.quantity ?? 1;
311
+ const customerId = typeof sub.customer === "string" ? sub.customer : sub.customer.id;
312
+ return {
313
+ subscriptionId: sub.id,
314
+ customerId,
315
+ status: mapStatus(sub.status),
316
+ priceId,
317
+ quantity,
318
+ currentPeriodStart: isoFromUnix(sub.current_period_start),
319
+ currentPeriodEnd: isoFromUnix(sub.current_period_end),
320
+ cancelAtPeriodEnd: sub.cancel_at_period_end,
321
+ tier: priceToTier(priceId, prices)
322
+ };
323
+ }
324
+ function customerIdOf(customer) {
325
+ if (customer === null) return "";
326
+ return typeof customer === "string" ? customer : customer.id;
327
+ }
328
+ function verifyAndParseStripeEvent(input) {
329
+ const stripe = new Stripe("placeholder-only-for-webhook-utility", {
330
+ apiVersion: STRIPE_API_VERSION
331
+ });
332
+ let event;
333
+ try {
334
+ event = stripe.webhooks.constructEvent(
335
+ input.rawBody,
336
+ input.signature,
337
+ input.secret
338
+ );
339
+ } catch {
340
+ return { error: "bad-signature" };
341
+ }
342
+ switch (event.type) {
343
+ case "customer.subscription.created": {
344
+ const sub = event.data.object;
345
+ return {
346
+ type: "subscription.created",
347
+ eventId: event.id,
348
+ data: shapeSubscription(sub, input.prices)
349
+ };
350
+ }
351
+ case "customer.subscription.updated": {
352
+ const sub = event.data.object;
353
+ return {
354
+ type: "subscription.updated",
355
+ eventId: event.id,
356
+ data: shapeSubscription(sub, input.prices)
357
+ };
358
+ }
359
+ case "customer.subscription.deleted": {
360
+ const sub = event.data.object;
361
+ return {
362
+ type: "subscription.deleted",
363
+ eventId: event.id,
364
+ data: {
365
+ subscriptionId: sub.id,
366
+ customerId: customerIdOf(sub.customer)
367
+ }
368
+ };
369
+ }
370
+ case "invoice.payment_succeeded": {
371
+ const invoice = event.data.object;
372
+ const subRef = invoice.subscription;
373
+ return {
374
+ type: "invoice.payment_succeeded",
375
+ eventId: event.id,
376
+ data: {
377
+ customerId: customerIdOf(invoice.customer),
378
+ subscriptionId: typeof subRef === "string" ? subRef : subRef?.id ?? null,
379
+ paidAt: isoFromUnix(invoice.status_transitions.paid_at ?? invoice.created),
380
+ amountUsd: invoice.amount_paid / 100
381
+ }
382
+ };
383
+ }
384
+ case "invoice.payment_failed": {
385
+ const invoice = event.data.object;
386
+ const subRef = invoice.subscription;
387
+ return {
388
+ type: "invoice.payment_failed",
389
+ eventId: event.id,
390
+ data: {
391
+ customerId: customerIdOf(invoice.customer),
392
+ subscriptionId: typeof subRef === "string" ? subRef : subRef?.id ?? null,
393
+ failedAt: isoFromUnix(invoice.created),
394
+ reason: invoice.last_finalization_error?.message ?? "Payment failed; reason not provided by Stripe."
395
+ }
396
+ };
397
+ }
398
+ default:
399
+ return { error: "unknown-event-type", stripeType: event.type };
400
+ }
401
+ }
402
+
403
+ // src/integrations/stripe/index.ts
404
+ var STRIPE_API_VERSION2 = "2025-02-24.acacia";
405
+ function isoFromUnix2(seconds) {
406
+ return new Date(seconds * 1e3).toISOString();
407
+ }
408
+ function priceToTier2(priceId, prices) {
409
+ if (priceId === prices.solo) return "solo";
410
+ if (priceId === prices.teams) return "teams";
411
+ return null;
412
+ }
413
+ function mapStatus2(status) {
414
+ switch (status) {
415
+ case "active":
416
+ case "past_due":
417
+ case "canceled":
418
+ case "incomplete":
419
+ case "incomplete_expired":
420
+ case "trialing":
421
+ case "unpaid":
422
+ return status;
423
+ case "paused":
424
+ return "unpaid";
425
+ default: {
426
+ return "incomplete";
427
+ }
428
+ }
429
+ }
430
+ function shapeSubscription2(sub, prices) {
431
+ const firstItem = sub.items.data[0];
432
+ const priceId = firstItem?.price.id ?? "";
433
+ const quantity = firstItem?.quantity ?? 1;
434
+ const customerId = typeof sub.customer === "string" ? sub.customer : sub.customer.id;
435
+ return {
436
+ subscriptionId: sub.id,
437
+ customerId,
438
+ status: mapStatus2(sub.status),
439
+ priceId,
440
+ quantity,
441
+ currentPeriodStart: isoFromUnix2(sub.current_period_start),
442
+ currentPeriodEnd: isoFromUnix2(sub.current_period_end),
443
+ cancelAtPeriodEnd: sub.cancel_at_period_end,
444
+ tier: priceToTier2(priceId, prices)
445
+ };
446
+ }
447
+ function shapeCustomer(customer) {
448
+ return {
449
+ customerId: customer.id,
450
+ email: customer.email ?? null,
451
+ almadarUid: customer.metadata?.almadarUid ?? null
452
+ };
453
+ }
281
454
  var StripeIntegration = class extends BaseIntegration {
282
455
  constructor(config) {
283
456
  super(config);
@@ -286,10 +459,18 @@ var StripeIntegration = class extends BaseIntegration {
286
459
  throw new Error("STRIPE_SECRET_KEY not configured");
287
460
  }
288
461
  this.client = new Stripe(apiKey, {
289
- apiVersion: "2025-02-24.acacia"
462
+ apiVersion: STRIPE_API_VERSION2
290
463
  });
464
+ this.prices = {
465
+ solo: config.env.STRIPE_PRICE_SOLO ?? "",
466
+ teams: config.env.STRIPE_PRICE_TEAMS ?? ""
467
+ };
291
468
  this.logger.info("Stripe integration initialized");
292
469
  }
470
+ /** Provisioned Price IDs the integration was constructed with. */
471
+ getPrices() {
472
+ return this.prices;
473
+ }
293
474
  async execute(action, params) {
294
475
  const validation = this.validateParams(action, params);
295
476
  if (!validation.valid) {
@@ -305,7 +486,7 @@ var StripeIntegration = class extends BaseIntegration {
305
486
  };
306
487
  }
307
488
  const startTime = Date.now();
308
- let retries = 0;
489
+ const retries = 0;
309
490
  try {
310
491
  let data;
311
492
  switch (action) {
@@ -334,7 +515,10 @@ var StripeIntegration = class extends BaseIntegration {
334
515
  }
335
516
  async createPaymentIntent(params) {
336
517
  const { amount, currency, metadata } = params;
337
- this.logger.debug("Creating payment intent", { amount: Number(amount), currency: String(currency ?? "") });
518
+ this.logger.debug("Creating payment intent", {
519
+ amount: Number(amount),
520
+ currency: String(currency ?? "")
521
+ });
338
522
  return await this.client.paymentIntents.create({
339
523
  amount,
340
524
  currency,
@@ -343,17 +527,164 @@ var StripeIntegration = class extends BaseIntegration {
343
527
  }
344
528
  async confirmPayment(params) {
345
529
  const { paymentIntentId } = params;
346
- this.logger.debug("Confirming payment", { paymentIntentId: String(paymentIntentId ?? "") });
530
+ this.logger.debug("Confirming payment", {
531
+ paymentIntentId: String(paymentIntentId ?? "")
532
+ });
347
533
  return await this.client.paymentIntents.confirm(paymentIntentId);
348
534
  }
349
535
  async refund(params) {
350
536
  const { paymentIntentId, amount } = params;
351
- this.logger.debug("Creating refund", { paymentIntentId: String(paymentIntentId ?? ""), amount: Number(amount) });
537
+ this.logger.debug("Creating refund", {
538
+ paymentIntentId: String(paymentIntentId ?? ""),
539
+ amount: Number(amount)
540
+ });
352
541
  return await this.client.refunds.create({
353
542
  payment_intent: paymentIntentId,
354
543
  amount
355
544
  });
356
545
  }
546
+ // ───────────────────────────────────────────────────────────────────
547
+ // Typed action surface — canonical Almadar shapes only.
548
+ // ───────────────────────────────────────────────────────────────────
549
+ /** Look up an existing customer by Stripe ID. */
550
+ async getCustomer(customerId) {
551
+ this.logger.debug("Fetching customer", { customerId });
552
+ const customer = await this.client.customers.retrieve(customerId);
553
+ if (customer.deleted === true) return null;
554
+ return shapeCustomer(customer);
555
+ }
556
+ /**
557
+ * Create a Stripe Customer for the given Almadar user. `almadarUid` is
558
+ * stored as Stripe metadata so webhook handlers can resolve back to
559
+ * the right `users/{uid}` document.
560
+ */
561
+ async createCustomer(input) {
562
+ this.logger.debug("Creating customer", { almadarUid: input.almadarUid });
563
+ const customer = await this.client.customers.create({
564
+ email: input.email,
565
+ name: input.displayName ?? void 0,
566
+ metadata: { almadarUid: input.almadarUid }
567
+ });
568
+ return shapeCustomer(customer);
569
+ }
570
+ /**
571
+ * Create a Stripe-hosted Checkout Session for the given tier. Client
572
+ * redirects the user to the returned `url`; on success Stripe fires
573
+ * `customer.subscription.created`, which the apps/builder webhook
574
+ * handler turns into a `users/{uid}.tier` write.
575
+ */
576
+ async createCheckoutSession(input) {
577
+ const price = input.tier === "solo" ? this.prices.solo : this.prices.teams;
578
+ if (!price) {
579
+ throw new Error(`STRIPE_PRICE_${input.tier.toUpperCase()} not configured`);
580
+ }
581
+ this.logger.debug("Creating Checkout session", {
582
+ tier: input.tier,
583
+ quantity: input.quantity,
584
+ hasCustomer: input.customerId !== null
585
+ });
586
+ const session = await this.client.checkout.sessions.create({
587
+ mode: "subscription",
588
+ customer: input.customerId ?? void 0,
589
+ line_items: [{ price, quantity: input.quantity }],
590
+ success_url: input.successUrl,
591
+ cancel_url: input.cancelUrl,
592
+ subscription_data: {
593
+ metadata: input.metadata
594
+ },
595
+ automatic_tax: { enabled: true },
596
+ allow_promotion_codes: true
597
+ });
598
+ return {
599
+ url: session.url ?? "",
600
+ sessionId: session.id,
601
+ customerId: typeof session.customer === "string" ? session.customer : session.customer?.id ?? null
602
+ };
603
+ }
604
+ /** Create a Billing Portal session for self-service plan management. */
605
+ async createBillingPortalSession(input) {
606
+ this.logger.debug("Creating Portal session", { customerId: input.customerId });
607
+ const session = await this.client.billingPortal.sessions.create({
608
+ customer: input.customerId,
609
+ return_url: input.returnUrl
610
+ });
611
+ return {
612
+ url: session.url,
613
+ customerId: input.customerId
614
+ };
615
+ }
616
+ /** Fetch a subscription and shape it into the canonical form. */
617
+ async getSubscription(subscriptionId) {
618
+ this.logger.debug("Fetching subscription", { subscriptionId });
619
+ const sub = await this.client.subscriptions.retrieve(subscriptionId);
620
+ return shapeSubscription2(sub, this.prices);
621
+ }
622
+ /**
623
+ * Create a subscription directly (server-side, no Checkout). Used by
624
+ * P13.3 Solo → Teams upgrade flow.
625
+ */
626
+ async createSubscription(input) {
627
+ const price = input.tier === "solo" ? this.prices.solo : this.prices.teams;
628
+ if (!price) {
629
+ throw new Error(`STRIPE_PRICE_${input.tier.toUpperCase()} not configured`);
630
+ }
631
+ this.logger.debug("Creating subscription", {
632
+ customerId: input.customerId,
633
+ tier: input.tier,
634
+ quantity: input.quantity
635
+ });
636
+ const sub = await this.client.subscriptions.create({
637
+ customer: input.customerId,
638
+ items: [{ price, quantity: input.quantity }],
639
+ metadata: input.metadata,
640
+ proration_behavior: "create_prorations",
641
+ automatic_tax: { enabled: true }
642
+ });
643
+ return shapeSubscription2(sub, this.prices);
644
+ }
645
+ /**
646
+ * Update quantity or cancel-at-period-end. Used for Teams seat resize
647
+ * and Solo → Teams transition.
648
+ */
649
+ async updateSubscription(input) {
650
+ this.logger.debug("Updating subscription", { subscriptionId: input.subscriptionId });
651
+ const update = {
652
+ proration_behavior: "create_prorations"
653
+ };
654
+ if (typeof input.quantity === "number") {
655
+ const existing = await this.client.subscriptions.retrieve(input.subscriptionId);
656
+ const itemId = existing.items.data[0]?.id;
657
+ if (itemId !== void 0) {
658
+ update.items = [{ id: itemId, quantity: input.quantity }];
659
+ }
660
+ }
661
+ if (typeof input.cancelAtPeriodEnd === "boolean") {
662
+ update.cancel_at_period_end = input.cancelAtPeriodEnd;
663
+ }
664
+ const sub = await this.client.subscriptions.update(
665
+ input.subscriptionId,
666
+ update
667
+ );
668
+ return shapeSubscription2(sub, this.prices);
669
+ }
670
+ /**
671
+ * Cancel a subscription. Defaults to `atPeriodEnd: true` so the user
672
+ * keeps access until the current period ends.
673
+ */
674
+ async cancelSubscription(input) {
675
+ this.logger.debug("Canceling subscription", {
676
+ subscriptionId: input.subscriptionId,
677
+ atPeriodEnd: input.atPeriodEnd
678
+ });
679
+ if (input.atPeriodEnd) {
680
+ const sub2 = await this.client.subscriptions.update(input.subscriptionId, {
681
+ cancel_at_period_end: true
682
+ });
683
+ return shapeSubscription2(sub2, this.prices);
684
+ }
685
+ const sub = await this.client.subscriptions.cancel(input.subscriptionId);
686
+ return shapeSubscription2(sub, this.prices);
687
+ }
357
688
  };
358
689
  registerIntegration("stripe", StripeIntegration);
359
690
  var YouTubeIntegration = class extends BaseIntegration {
@@ -2690,6 +3021,6 @@ var DockerIntegration = class extends BaseIntegration {
2690
3021
  };
2691
3022
  registerIntegration("docker", DockerIntegration);
2692
3023
 
2693
- export { BaseIntegration, CLIIntegration, ConsoleLogger, DeepAgentIntegration, DockerIntegration, EmailIntegration, GitHubIntegration, IntegrationFactory, LLMIntegration, OAuthIntegration, OtelIntegration, QueueIntegration, RedisIntegration, StorageIntegration, StripeIntegration, TwilioIntegration, YouTubeIntegration, getIntegration, getIntegrationFactory, getRegisteredIntegrations, isKnownIntegration, registerIntegration, resetIntegrationFactory, validateParams, withRetry };
3024
+ export { BaseIntegration, CLIIntegration, ConsoleLogger, DeepAgentIntegration, DockerIntegration, EmailIntegration, GitHubIntegration, IntegrationFactory, LLMIntegration, OAuthIntegration, OtelIntegration, QueueIntegration, RedisIntegration, StorageIntegration, StripeIntegration, TwilioIntegration, YouTubeIntegration, getIntegration, getIntegrationFactory, getRegisteredIntegrations, isKnownIntegration, registerIntegration, resetIntegrationFactory, validateParams, verifyAndParseStripeEvent, withRetry };
2694
3025
  //# sourceMappingURL=index.js.map
2695
3026
  //# sourceMappingURL=index.js.map