1- import type Stripe from 'stripe'
21import type { OpenApiSchemaObject , OpenApiSpec } from './types'
32import { OPENAPI_RESOURCE_TABLE_ALIASES } from './runtimeMappings'
43
54const SCHEMA_REF_PREFIX = '#/components/schemas/'
65
7- type ListFn = (
8- params : Stripe . PaginationParams & { created ?: Stripe . RangeQueryParam }
9- ) => Promise < { data : unknown [ ] ; has_more : boolean ; pageCursor ?: string } >
6+ export type ListParams = {
7+ limit ?: number
8+ starting_after ?: string
9+ ending_before ?: string
10+ created ?: { gt ?: number ; gte ?: number ; lt ?: number ; lte ?: number }
11+ }
12+
13+ export type ListResult = { data : unknown [ ] ; has_more : boolean ; pageCursor ?: string }
14+
15+ export type ListFn = ( params : ListParams ) => Promise < ListResult >
16+
17+ export type RetrieveFn = ( id : string ) => Promise < unknown >
1018
1119export type ListEndpoint = {
1220 tableName : string
@@ -25,10 +33,6 @@ export type NestedEndpoint = {
2533 supportsPagination : boolean
2634}
2735
28- function snakeToCamel ( s : string ) : string {
29- return s . replace ( / _ ( [ a - z ] ) / g, ( _ , c ) => c . toUpperCase ( ) )
30- }
31-
3236function resolveTableName ( resourceId : string , aliases : Record < string , string > ) : string {
3337 const alias = aliases [ resourceId ]
3438 if ( alias ) return alias
@@ -197,161 +201,86 @@ export function isV2Path(apiPath: string): boolean {
197201 return apiPath . startsWith ( '/v2/' )
198202}
199203
200- function pathToSdkSegments ( apiPath : string ) : string [ ] {
201- if ( isV2Path ( apiPath ) ) {
202- return [
203- 'v2' ,
204- ...apiPath
205- . replace ( / ^ \/ v 2 \/ / , '' )
206- . split ( '/' )
207- . filter ( ( s ) => ! s . startsWith ( '{' ) )
208- . map ( snakeToCamel ) ,
209- ]
210- }
211- return apiPath
212- . replace ( / ^ \/ v [ 1 2 ] \/ / , '' )
213- . split ( '/' )
214- . filter ( ( s ) => ! s . startsWith ( '{' ) )
215- . map ( snakeToCamel )
216- }
204+ // ---------------------------------------------------------------------------
205+ // HTTP-based list / retrieve builders (no Stripe SDK dependency)
206+ // ---------------------------------------------------------------------------
217207
218- // eslint-disable-next-line @typescript-eslint/no-explicit-any
219- function resolveStripeResource ( stripe : Stripe , segments : string [ ] , apiPath : string ) : any {
220- // eslint-disable-next-line @typescript-eslint/no-explicit-any
221- let resource : any = stripe
222- for ( const segment of segments ) {
223- resource = resource ?. [ segment ]
224- if ( ! resource ) {
225- throw new Error ( `Stripe SDK has no property "${ segment } " when resolving path "${ apiPath } "` )
226- }
227- }
228- return resource
208+ const STRIPE_API_BASE = 'https://api.stripe.com'
209+ const V2_STRIPE_VERSION = '2026-02-25.clover'
210+
211+ function authHeaders ( apiKey : string ) : Record < string , string > {
212+ return { Authorization : `Bearer ${ apiKey } ` }
229213}
230214
231215/**
232- * Check whether an API path can be resolved to a Stripe SDK resource.
233- * v1 requires both `.list()` and `.retrieve()`.
234- * v2 only requires `.list()` (retrieve may not be available on all v2 resources).
216+ * Build a callable list function that hits the Stripe HTTP API directly.
217+ * Supports both v1 (has_more pagination) and v2 (next_page_url pagination).
235218 */
236- export function canResolveSdkResource ( stripe : Stripe , apiPath : string ) : boolean {
237- try {
238- const segments = pathToSdkSegments ( apiPath )
239- const resource = resolveStripeResource ( stripe , segments , apiPath )
240- if ( isV2Path ( apiPath ) ) {
241- return typeof resource . list === 'function'
219+ export function buildListFn ( apiKey : string , apiPath : string ) : ListFn {
220+ if ( isV2Path ( apiPath ) ) {
221+ return async ( params ) => {
222+ const qs = new URLSearchParams ( )
223+ qs . set ( 'limit' , String ( Math . min ( params . limit ?? 20 , 20 ) ) )
224+ if ( params . starting_after ) qs . set ( 'page' , params . starting_after )
225+
226+ const response = await fetch ( `${ STRIPE_API_BASE } ${ apiPath } ?${ qs } ` , {
227+ headers : { ...authHeaders ( apiKey ) , 'Stripe-Version' : V2_STRIPE_VERSION } ,
228+ } )
229+ const body = ( await response . json ( ) ) as {
230+ data : unknown [ ]
231+ next_page_url ?: string | null
232+ }
233+ const pageCursor = extractPageToken ( body . next_page_url )
234+ return { data : body . data ?? [ ] , has_more : ! ! body . next_page_url , pageCursor }
242235 }
243- return typeof resource . list === 'function' && typeof resource . retrieve === 'function'
244- } catch {
245- return false
246236 }
247- }
248237
249- /**
250- * Build a callable list function by navigating the Stripe SDK object using
251- * the API path segments converted from snake_case to camelCase.
252- * Path parameters (e.g. `{customer}`) are stripped automatically.
253- */
254- export function buildListFn ( stripe : Stripe , apiPath : string , apiKey : string = '' ) : ListFn {
255- const v2 = isV2Path ( apiPath )
256- if ( v2 ) {
257- return buildV2ListFn ( apiKey , apiPath )
258- }
259- const segments = pathToSdkSegments ( apiPath )
260- return ( params ) => {
261- const resource = resolveStripeResource ( stripe , segments , apiPath )
262- if ( typeof resource . list !== 'function' ) {
263- throw new Error ( `Stripe SDK resource at "${ apiPath } " has no list() method` )
238+ return async ( params ) => {
239+ const qs = new URLSearchParams ( )
240+ if ( params . limit != null ) qs . set ( 'limit' , String ( params . limit ) )
241+ if ( params . starting_after ) qs . set ( 'starting_after' , params . starting_after )
242+ if ( params . ending_before ) qs . set ( 'ending_before' , params . ending_before )
243+ if ( params . created ) {
244+ for ( const [ op , val ] of Object . entries ( params . created ) ) {
245+ if ( val != null ) qs . set ( `created[${ op } ]` , String ( val ) )
246+ }
264247 }
265- return resource . list ( params )
248+
249+ const response = await fetch ( `${ STRIPE_API_BASE } ${ apiPath } ?${ qs } ` , {
250+ headers : authHeaders ( apiKey ) ,
251+ } )
252+ const body = ( await response . json ( ) ) as { data : unknown [ ] ; has_more : boolean }
253+ return { data : body . data ?? [ ] , has_more : body . has_more }
266254 }
267255}
268256
269- type RetrieveFn = ( id : string ) => Promise < Stripe . Response < unknown > >
270-
271257/**
272- * Build a callable retrieve function by navigating the Stripe SDK object using
273- * the API path segments converted from snake_case to camelCase.
274- * Path parameters (e.g. `{customer}`) are stripped automatically.
258+ * Build a callable retrieve function that hits the Stripe HTTP API directly.
275259 */
276- export function buildRetrieveFn ( stripe : Stripe , apiPath : string , apiKey : string ) : RetrieveFn {
277- const v2 = isV2Path ( apiPath )
278- if ( v2 ) {
279- return buildV2RetrieveFn ( apiKey , apiPath )
280- }
281- const segments = pathToSdkSegments ( apiPath )
282- return ( id : string ) => {
283- const resource = resolveStripeResource ( stripe , segments , apiPath )
284- if ( typeof resource . retrieve !== 'function' ) {
285- throw new Error ( `Stripe SDK resource at "${ apiPath } " has no retrieve() method` )
260+ export function buildRetrieveFn ( apiKey : string , apiPath : string ) : RetrieveFn {
261+ if ( isV2Path ( apiPath ) ) {
262+ return async ( id ) => {
263+ const response = await fetch ( `${ STRIPE_API_BASE } ${ apiPath } /${ id } ` , {
264+ headers : { ...authHeaders ( apiKey ) , 'Stripe-Version' : V2_STRIPE_VERSION } ,
265+ } )
266+ return await response . json ( )
286267 }
287- return resource . retrieve ( id )
288268 }
289- }
290269
291- /**
292- * Build a list function that calls Stripe rawRequest directly for a fixed endpoint.
293- * Useful when the Stripe SDK does not expose a matching namespace.
294- */
295- export function buildRawRequestListFn ( stripe : Stripe , apiPath : string ) : ListFn {
296- return ( params ) =>
297- stripe . rawRequest ( 'GET' , apiPath , { ...params } ) as unknown as Promise < {
298- data : unknown [ ]
299- has_more : boolean
300- } >
270+ return async ( id ) => {
271+ const response = await fetch ( `${ STRIPE_API_BASE } ${ apiPath } /${ id } ` , {
272+ headers : authHeaders ( apiKey ) ,
273+ } )
274+ return await response . json ( )
275+ }
301276}
302277
303278function extractPageToken ( nextPageUrl : string | null | undefined ) : string | undefined {
304279 if ( ! nextPageUrl ) return undefined
305280 try {
306- const url = new URL ( nextPageUrl , 'https://api.stripe.com' )
281+ const url = new URL ( nextPageUrl , STRIPE_API_BASE )
307282 return url . searchParams . get ( 'page' ) ?? undefined
308283 } catch {
309284 return undefined
310285 }
311286}
312-
313- /**
314- * Build a list function for v2 API endpoints.
315- * V2 uses `page` token pagination and returns `next_page_url` instead of `has_more`.
316- * The response is normalized to the v1 shape so the sync worker can process it uniformly.
317- */
318- export function buildV2ListFn ( apiKey : string , apiPath : string ) : ListFn {
319- return async ( params ) => {
320- const qs = new URLSearchParams ( )
321- qs . set ( 'limit' , String ( Math . min ( params . limit ?? 20 , 20 ) ) )
322- if ( params . starting_after ) qs . set ( 'page' , params . starting_after )
323- const url = `https://api.stripe.com${ apiPath } ?${ qs . toString ( ) } `
324-
325- const response = await fetch ( url , {
326- headers : {
327- Authorization : `Bearer ${ apiKey } ` ,
328- 'Stripe-Version' : '2026-02-25.clover' ,
329- } ,
330- } )
331-
332- const raw = await response . text ( )
333-
334- const body = JSON . parse ( raw ) as {
335- data : unknown [ ]
336- next_page_url ?: string | null
337- }
338- const nextToken = extractPageToken ( body . next_page_url )
339- return {
340- data : body . data ?? [ ] ,
341- has_more : ! ! body . next_page_url ,
342- pageCursor : nextToken ,
343- }
344- }
345- }
346-
347- export function buildV2RetrieveFn ( apiKey : string , apiPath : string ) : RetrieveFn {
348- return async ( id : string ) => {
349- const response = await fetch ( `https://api.stripe.com${ apiPath } /${ id } ` , {
350- headers : {
351- Authorization : `Bearer ${ apiKey } ` ,
352- 'Stripe-Version' : '2026-02-25.clover' ,
353- } ,
354- } )
355- return ( await response . json ( ) ) as Stripe . Response < unknown >
356- }
357- }
0 commit comments