@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.
- package/dist/{BaseIntegration-DjXCkytU.d.ts → BaseIntegration-d17YX8KE.d.ts} +1 -1
- package/dist/{factory-BoDqQ89e.d.ts → factory-BcFFy4JA.d.ts} +1 -1
- package/dist/index.d.ts +235 -5
- package/dist/index.js +337 -6
- package/dist/index.js.map +1 -1
- package/dist/integrations/github/index.d.ts +1 -1
- package/dist/mocks/index.d.ts +2 -2
- package/dist/runtime/index.d.ts +2 -2
- package/package.json +1 -1
|
@@ -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
|
|
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 {
|
|
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 {
|
|
2
|
-
export {
|
|
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-
|
|
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
|
-
*
|
|
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:
|
|
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
|
-
|
|
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", {
|
|
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", {
|
|
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", {
|
|
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
|