@sayren/store-sdk 0.6.0 → 0.7.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/index.d.mts CHANGED
@@ -100,6 +100,7 @@ export declare const analyticsOverviewSchema: z.ZodObject<{
100
100
  conversionRate: z.ZodNullable<z.ZodNumber>;
101
101
  date: z.ZodString;
102
102
  }, z.core.$strip>>;
103
+ excludedBotSessions: z.ZodNumber;
103
104
  from: z.ZodString;
104
105
  to: z.ZodString;
105
106
  timezone: z.ZodLiteral<"Asia/Seoul">;
@@ -144,6 +145,7 @@ export declare const analyticsProductRowSchema: z.ZodObject<{
144
145
  quantity: z.ZodNumber;
145
146
  revenue: z.ZodNumber;
146
147
  purchaseRate: z.ZodNullable<z.ZodNumber>;
148
+ clickThroughRate: z.ZodNullable<z.ZodNumber>;
147
149
  }, z.core.$strip>;
148
150
  export type AnalyticsProductRow = z.infer<typeof analyticsProductRowSchema>;
149
151
  export declare const analyticsProductsSchema: z.ZodObject<{
@@ -165,6 +167,7 @@ export declare const analyticsProductsSchema: z.ZodObject<{
165
167
  quantity: z.ZodNumber;
166
168
  revenue: z.ZodNumber;
167
169
  purchaseRate: z.ZodNullable<z.ZodNumber>;
170
+ clickThroughRate: z.ZodNullable<z.ZodNumber>;
168
171
  }, z.core.$strip>>;
169
172
  from: z.ZodString;
170
173
  to: z.ZodString;
@@ -279,6 +282,159 @@ export declare const analyticsSourcesSchema: z.ZodObject<{
279
282
  lastComputedAt: z.ZodNullable<z.ZodString>;
280
283
  }, z.core.$strip>;
281
284
  export type AnalyticsSources = z.infer<typeof analyticsSourcesSchema>;
285
+ export declare const analyticsRealtimeSchema: z.ZodObject<{
286
+ asOf: z.ZodString;
287
+ windowMinutes: z.ZodLiteral<5>;
288
+ visitors: z.ZodNumber;
289
+ sessions: z.ZodNumber;
290
+ pageViews: z.ZodNumber;
291
+ pages: z.ZodArray<z.ZodObject<{
292
+ path: z.ZodString;
293
+ visitors: z.ZodNumber;
294
+ pageViews: z.ZodNumber;
295
+ }, z.core.$strip>>;
296
+ }, z.core.$strip>;
297
+ export type AnalyticsRealtime = z.infer<typeof analyticsRealtimeSchema>;
298
+ export declare const analyticsDeviceRowSchema: z.ZodObject<{
299
+ device: z.ZodEnum<{
300
+ unknown: "unknown";
301
+ desktop: "desktop";
302
+ mobile: "mobile";
303
+ tablet: "tablet";
304
+ }>;
305
+ sessions: z.ZodNumber;
306
+ productViewSessions: z.ZodNumber;
307
+ addToCartSessions: z.ZodNumber;
308
+ beginCheckoutSessions: z.ZodNumber;
309
+ purchaseSessions: z.ZodNumber;
310
+ conversionRate: z.ZodNullable<z.ZodNumber>;
311
+ }, z.core.$strip>;
312
+ export type AnalyticsDeviceRow = z.infer<typeof analyticsDeviceRowSchema>;
313
+ export declare const analyticsDevicesSchema: z.ZodObject<{
314
+ rows: z.ZodArray<z.ZodObject<{
315
+ device: z.ZodEnum<{
316
+ unknown: "unknown";
317
+ desktop: "desktop";
318
+ mobile: "mobile";
319
+ tablet: "tablet";
320
+ }>;
321
+ sessions: z.ZodNumber;
322
+ productViewSessions: z.ZodNumber;
323
+ addToCartSessions: z.ZodNumber;
324
+ beginCheckoutSessions: z.ZodNumber;
325
+ purchaseSessions: z.ZodNumber;
326
+ conversionRate: z.ZodNullable<z.ZodNumber>;
327
+ }, z.core.$strip>>;
328
+ from: z.ZodString;
329
+ to: z.ZodString;
330
+ timezone: z.ZodLiteral<"Asia/Seoul">;
331
+ currency: z.ZodLiteral<"KRW">;
332
+ includeTest: z.ZodBoolean;
333
+ lastComputedAt: z.ZodNullable<z.ZodString>;
334
+ }, z.core.$strip>;
335
+ export type AnalyticsDevices = z.infer<typeof analyticsDevicesSchema>;
336
+ export declare const analyticsPagesQuerySchema: z.ZodObject<{
337
+ from: z.ZodString;
338
+ to: z.ZodString;
339
+ includeTest: z.ZodOptional<z.ZodEnum<{
340
+ true: "true";
341
+ false: "false";
342
+ }>>;
343
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
344
+ }, z.core.$strip>;
345
+ export type AnalyticsPagesParams = z.infer<typeof analyticsPagesQuerySchema>;
346
+ export declare const analyticsPagesSchema: z.ZodObject<{
347
+ landings: z.ZodArray<z.ZodObject<{
348
+ path: z.ZodString;
349
+ entrances: z.ZodNumber;
350
+ bounces: z.ZodNumber;
351
+ bounceRate: z.ZodNullable<z.ZodNumber>;
352
+ }, z.core.$strip>>;
353
+ exits: z.ZodArray<z.ZodObject<{
354
+ path: z.ZodString;
355
+ pageViews: z.ZodNumber;
356
+ exits: z.ZodNumber;
357
+ exitRate: z.ZodNullable<z.ZodNumber>;
358
+ }, z.core.$strip>>;
359
+ from: z.ZodString;
360
+ to: z.ZodString;
361
+ timezone: z.ZodLiteral<"Asia/Seoul">;
362
+ currency: z.ZodLiteral<"KRW">;
363
+ includeTest: z.ZodBoolean;
364
+ lastComputedAt: z.ZodNullable<z.ZodString>;
365
+ }, z.core.$strip>;
366
+ export type AnalyticsPages = z.infer<typeof analyticsPagesSchema>;
367
+ export declare const analyticsSearchQuerySchema: z.ZodObject<{
368
+ from: z.ZodString;
369
+ to: z.ZodString;
370
+ includeTest: z.ZodOptional<z.ZodEnum<{
371
+ true: "true";
372
+ false: "false";
373
+ }>>;
374
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
375
+ }, z.core.$strip>;
376
+ export type AnalyticsSearchParams = z.infer<typeof analyticsSearchQuerySchema>;
377
+ export declare const analyticsSearchSchema: z.ZodObject<{
378
+ summary: z.ZodObject<{
379
+ searches: z.ZodNumber;
380
+ zeroResultSearches: z.ZodNumber;
381
+ searchSessions: z.ZodNumber;
382
+ purchaseSessions: z.ZodNumber;
383
+ conversionRate: z.ZodNullable<z.ZodNumber>;
384
+ }, z.core.$strip>;
385
+ terms: z.ZodArray<z.ZodObject<{
386
+ term: z.ZodString;
387
+ searches: z.ZodNumber;
388
+ zeroResultSearches: z.ZodNumber;
389
+ searchSessions: z.ZodNumber;
390
+ purchaseSessions: z.ZodNumber;
391
+ conversionRate: z.ZodNullable<z.ZodNumber>;
392
+ }, z.core.$strip>>;
393
+ zeroResultTerms: z.ZodArray<z.ZodObject<{
394
+ term: z.ZodString;
395
+ searches: z.ZodNumber;
396
+ zeroResultSearches: z.ZodNumber;
397
+ }, z.core.$strip>>;
398
+ from: z.ZodString;
399
+ to: z.ZodString;
400
+ timezone: z.ZodLiteral<"Asia/Seoul">;
401
+ currency: z.ZodLiteral<"KRW">;
402
+ includeTest: z.ZodBoolean;
403
+ lastComputedAt: z.ZodNullable<z.ZodString>;
404
+ }, z.core.$strip>;
405
+ export type AnalyticsSearch = z.infer<typeof analyticsSearchSchema>;
406
+ export declare const analyticsCheckoutOutcomeSchema: z.ZodEnum<{
407
+ OTHER: "OTHER";
408
+ COMPLETED: "COMPLETED";
409
+ NOT_OPENED: "NOT_OPENED";
410
+ USER_CANCELED: "USER_CANCELED";
411
+ DECLINED: "DECLINED";
412
+ PREPARE_FAILURE: "PREPARE_FAILURE";
413
+ IN_PROGRESS: "IN_PROGRESS";
414
+ }>;
415
+ export type AnalyticsCheckoutOutcome = z.infer<typeof analyticsCheckoutOutcomeSchema>;
416
+ export declare const analyticsCheckoutSchema: z.ZodObject<{
417
+ sessions: z.ZodNumber;
418
+ outcomes: z.ZodArray<z.ZodObject<{
419
+ outcome: z.ZodEnum<{
420
+ OTHER: "OTHER";
421
+ COMPLETED: "COMPLETED";
422
+ NOT_OPENED: "NOT_OPENED";
423
+ USER_CANCELED: "USER_CANCELED";
424
+ DECLINED: "DECLINED";
425
+ PREPARE_FAILURE: "PREPARE_FAILURE";
426
+ IN_PROGRESS: "IN_PROGRESS";
427
+ }>;
428
+ sessions: z.ZodNumber;
429
+ }, z.core.$strip>>;
430
+ from: z.ZodString;
431
+ to: z.ZodString;
432
+ timezone: z.ZodLiteral<"Asia/Seoul">;
433
+ currency: z.ZodLiteral<"KRW">;
434
+ includeTest: z.ZodBoolean;
435
+ lastComputedAt: z.ZodNullable<z.ZodString>;
436
+ }, z.core.$strip>;
437
+ export type AnalyticsCheckout = z.infer<typeof analyticsCheckoutSchema>;
282
438
  //#endregion
283
439
  //#region src/schemas/api-client.d.ts
284
440
  /**
@@ -5065,6 +5221,7 @@ export declare function createStoreClient(options: StoreClientOptions): {
5065
5221
  conversionRate: number | null;
5066
5222
  date: string;
5067
5223
  }[];
5224
+ excludedBotSessions: number;
5068
5225
  from: string;
5069
5226
  to: string;
5070
5227
  timezone: "Asia/Seoul";
@@ -5086,6 +5243,7 @@ export declare function createStoreClient(options: StoreClientOptions): {
5086
5243
  quantity: number;
5087
5244
  revenue: number;
5088
5245
  purchaseRate: number | null;
5246
+ clickThroughRate: number | null;
5089
5247
  }[];
5090
5248
  from: string;
5091
5249
  to: string;
@@ -5118,6 +5276,101 @@ export declare function createStoreClient(options: StoreClientOptions): {
5118
5276
  includeTest: boolean;
5119
5277
  lastComputedAt: string | null;
5120
5278
  }>;
5279
+ /** `GET /v1/analytics/realtime` — 최근 5분 방문자·세션·페이지(원본 이벤트 기준, 봇 제외) */
5280
+ realtime: () => Promise<{
5281
+ asOf: string;
5282
+ windowMinutes: 5;
5283
+ visitors: number;
5284
+ sessions: number;
5285
+ pageViews: number;
5286
+ pages: {
5287
+ path: string;
5288
+ visitors: number;
5289
+ pageViews: number;
5290
+ }[];
5291
+ }>;
5292
+ /** `GET /v1/analytics/devices` — 기기별 세션 퍼널 */
5293
+ devices: (params: AnalyticsRangeParams) => Promise<{
5294
+ rows: {
5295
+ device: "unknown" | "desktop" | "mobile" | "tablet";
5296
+ sessions: number;
5297
+ productViewSessions: number;
5298
+ addToCartSessions: number;
5299
+ beginCheckoutSessions: number;
5300
+ purchaseSessions: number;
5301
+ conversionRate: number | null;
5302
+ }[];
5303
+ from: string;
5304
+ to: string;
5305
+ timezone: "Asia/Seoul";
5306
+ currency: "KRW";
5307
+ includeTest: boolean;
5308
+ lastComputedAt: string | null;
5309
+ }>;
5310
+ /** `GET /v1/analytics/pages` — 첫 페이지(진입·반송)와 이탈 페이지 */
5311
+ pages: (params: AnalyticsPagesParams) => Promise<{
5312
+ landings: {
5313
+ path: string;
5314
+ entrances: number;
5315
+ bounces: number;
5316
+ bounceRate: number | null;
5317
+ }[];
5318
+ exits: {
5319
+ path: string;
5320
+ pageViews: number;
5321
+ exits: number;
5322
+ exitRate: number | null;
5323
+ }[];
5324
+ from: string;
5325
+ to: string;
5326
+ timezone: "Asia/Seoul";
5327
+ currency: "KRW";
5328
+ includeTest: boolean;
5329
+ lastComputedAt: string | null;
5330
+ }>;
5331
+ /** `GET /v1/analytics/search` — 검색어별 검색·결과 0개·검색 후 구매 */
5332
+ search: (params: AnalyticsSearchParams) => Promise<{
5333
+ summary: {
5334
+ searches: number;
5335
+ zeroResultSearches: number;
5336
+ searchSessions: number;
5337
+ purchaseSessions: number;
5338
+ conversionRate: number | null;
5339
+ };
5340
+ terms: {
5341
+ term: string;
5342
+ searches: number;
5343
+ zeroResultSearches: number;
5344
+ searchSessions: number;
5345
+ purchaseSessions: number;
5346
+ conversionRate: number | null;
5347
+ }[];
5348
+ zeroResultTerms: {
5349
+ term: string;
5350
+ searches: number;
5351
+ zeroResultSearches: number;
5352
+ }[];
5353
+ from: string;
5354
+ to: string;
5355
+ timezone: "Asia/Seoul";
5356
+ currency: "KRW";
5357
+ includeTest: boolean;
5358
+ lastComputedAt: string | null;
5359
+ }>;
5360
+ /** `GET /v1/analytics/checkout` — 결제 세션의 결과별 수(결제 단계 이탈 이유) */
5361
+ checkout: (params: AnalyticsRangeParams) => Promise<{
5362
+ sessions: number;
5363
+ outcomes: {
5364
+ outcome: "OTHER" | "COMPLETED" | "NOT_OPENED" | "USER_CANCELED" | "DECLINED" | "PREPARE_FAILURE" | "IN_PROGRESS";
5365
+ sessions: number;
5366
+ }[];
5367
+ from: string;
5368
+ to: string;
5369
+ timezone: "Asia/Seoul";
5370
+ currency: "KRW";
5371
+ includeTest: boolean;
5372
+ lastComputedAt: string | null;
5373
+ }>;
5121
5374
  };
5122
5375
  /**
5123
5376
  * 결제 내역 — 스코프 `settlement:r`. 결제 건(세션) 단위이고 주문 목록과 다르다. 주문이 만들어지지 않은
package/dist/index.mjs CHANGED
@@ -204,7 +204,8 @@ const analyticsOverviewSchema = z.object({
204
204
  ...rangeMeta,
205
205
  summary: analyticsTotalsSchema.describe("기간 합계"),
206
206
  funnel: z.array(analyticsFunnelStepSchema).describe("세션 기준 구매 퍼널. 각 단계는 그 행동을 한 세션 수이고, 앞 단계를 거쳤는지는 따지지 않는다"),
207
- rows: z.array(analyticsTotalsSchema.extend({ date: z.string().describe("일자 (YYYY-MM-DD)") })).describe("날짜별 값. 세션이 없는 날은 생략된다")
207
+ rows: z.array(analyticsTotalsSchema.extend({ date: z.string().describe("일자 (YYYY-MM-DD)") })).describe("날짜별 값. 세션이 없는 날은 생략된다"),
208
+ excludedBotSessions: z.number().int().describe("집계에서 뺀 봇 세션 수 — 봇 표시 이벤트가 하나라도 있는 세션은 모든 값에서 빠진다")
208
209
  });
209
210
  const analyticsProductSortSchema = z.enum([
210
211
  "VIEWS",
@@ -227,7 +228,8 @@ const analyticsProductRowSchema = z.object({
227
228
  purchases: z.number().int().describe("이 상품이 포함된 구매(주문) 수"),
228
229
  quantity: z.number().int().describe("구매 수량"),
229
230
  revenue: z.number().int().describe("이 상품의 구매 금액 — 주문 상품 금액(할인 반영, 배송비 제외) 합계. 이후 취소·환불은 빼지 않는다"),
230
- purchaseRate: rate("구매율 = 구매 / 조회 세션. 조회 세션이 없으면 null")
231
+ purchaseRate: rate("구매율 = 구매 / 조회 세션. 조회 세션이 없으면 null"),
232
+ clickThroughRate: rate("목록 → 상세 전환율 = 상세 조회 / 목록 노출. 노출이 없으면 null(검색·외부 링크로 바로 들어온 조회는 노출 없이 잡힌다)")
231
233
  });
232
234
  const analyticsProductsSchema = z.object({
233
235
  ...rangeMeta,
@@ -275,6 +277,94 @@ const analyticsSourcesSchema = z.object({
275
277
  groupBy: analyticsSourceGroupBySchema,
276
278
  rows: z.array(analyticsSourceRowSchema).describe("세션 수 내림차순")
277
279
  });
280
+ const analyticsRealtimeSchema = z.object({
281
+ asOf: z.string().describe("기준 시각(ISO 8601)"),
282
+ windowMinutes: z.literal(5).describe("최근 몇 분 동안 활동한 방문을 세는가"),
283
+ visitors: z.number().int().describe("최근 5분 안에 이벤트가 있는 방문자 수(봇 제외)"),
284
+ sessions: z.number().int(),
285
+ pageViews: z.number().int(),
286
+ pages: z.array(z.object({
287
+ path: z.string(),
288
+ visitors: z.number().int(),
289
+ pageViews: z.number().int()
290
+ })).describe("최근 5분 페이지뷰가 있는 경로. 방문자 수 내림차순 최대 10개")
291
+ });
292
+ const analyticsDeviceRowSchema = z.object({
293
+ device: z.enum([
294
+ "desktop",
295
+ "mobile",
296
+ "tablet",
297
+ "unknown"
298
+ ]),
299
+ sessions: z.number().int(),
300
+ productViewSessions: z.number().int(),
301
+ addToCartSessions: z.number().int(),
302
+ beginCheckoutSessions: z.number().int(),
303
+ purchaseSessions: z.number().int(),
304
+ conversionRate: rate("전환율 = 구매 세션 / 세션")
305
+ });
306
+ const analyticsDevicesSchema = z.object({
307
+ ...rangeMeta,
308
+ rows: z.array(analyticsDeviceRowSchema).describe("기기별 세션 기준 퍼널. 세션 수 내림차순")
309
+ });
310
+ const limitParam = z.coerce.number().int().min(1).max(100).optional().describe("최대 행 수. 기본 20, 최대 100");
311
+ const analyticsPagesQuerySchema = analyticsRangeQuerySchema.extend({ limit: limitParam });
312
+ const analyticsPagesSchema = z.object({
313
+ ...rangeMeta,
314
+ landings: z.array(z.object({
315
+ path: z.string(),
316
+ entrances: z.number().int().describe("이 페이지로 시작한 세션 수"),
317
+ bounces: z.number().int().describe("이 페이지로 시작해 참여하지 않고 끝난 세션 수"),
318
+ bounceRate: rate("반송률 = 반송 / 진입")
319
+ })).describe("첫 페이지. 진입 수 내림차순"),
320
+ exits: z.array(z.object({
321
+ path: z.string(),
322
+ pageViews: z.number().int(),
323
+ exits: z.number().int().describe("이 페이지가 세션의 마지막 페이지뷰였던 수"),
324
+ exitRate: rate("이탈률 = 이탈 / 페이지뷰")
325
+ })).describe("이탈 페이지. 이탈 수 내림차순")
326
+ });
327
+ const analyticsSearchQuerySchema = analyticsRangeQuerySchema.extend({ limit: limitParam });
328
+ const analyticsSearchSchema = z.object({
329
+ ...rangeMeta,
330
+ summary: z.object({
331
+ searches: z.number().int(),
332
+ zeroResultSearches: z.number().int().describe("결과가 0개였던 검색 수"),
333
+ searchSessions: z.number().int().describe("검색한 세션 수(세션당 한 번)"),
334
+ purchaseSessions: z.number().int().describe("검색한 뒤 같은 날 구매한 세션 수(세션당 한 번). 검색 전에 끝난 구매는 세지 않는다"),
335
+ conversionRate: rate("검색 후 구매율 = 구매 세션 / 검색 세션")
336
+ }),
337
+ terms: z.array(z.object({
338
+ term: z.string().describe("검색어 — 소문자, 공백 정리"),
339
+ searches: z.number().int(),
340
+ zeroResultSearches: z.number().int(),
341
+ searchSessions: z.number().int().describe("이 검색어로 검색한 세션 수"),
342
+ purchaseSessions: z.number().int().describe("이 검색어로 검색한 뒤 같은 날 구매한 세션 수"),
343
+ conversionRate: rate("검색 후 구매율 = 구매 세션 / 검색 세션")
344
+ })).describe("검색 수 내림차순. 세션 수는 검색어마다 세므로 여러 검색어를 쓴 세션은 검색어마다 잡힌다(더하면 요약보다 크다)"),
345
+ zeroResultTerms: z.array(z.object({
346
+ term: z.string(),
347
+ searches: z.number().int(),
348
+ zeroResultSearches: z.number().int()
349
+ })).describe("결과가 0개였던 적이 있는 검색어. 결과 없음 수 내림차순 — 검색 수 상위(`terms`)에 들지 않는 드문 검색어도 여기 잡힌다")
350
+ });
351
+ const analyticsCheckoutOutcomeSchema = z.enum([
352
+ "COMPLETED",
353
+ "NOT_OPENED",
354
+ "USER_CANCELED",
355
+ "DECLINED",
356
+ "PREPARE_FAILURE",
357
+ "OTHER",
358
+ "IN_PROGRESS"
359
+ ]).describe("결제 세션의 최종 결과 — `COMPLETED`(결제 완료), `NOT_OPENED`(결제창을 열지 않고 끝남), `USER_CANCELED`(결제창에서 취소), `DECLINED`(카드 거절 등 승인 거절), `PREPARE_FAILURE`(결제창을 띄우기 전 장애), `OTHER`(그 밖의 이탈·만료), `IN_PROGRESS`(아직 진행 중 — 만료 전 대기·승인 중). 실패 분류는 세션의 마지막 결제 시도 기준이다");
360
+ const analyticsCheckoutSchema = z.object({
361
+ ...rangeMeta,
362
+ sessions: z.number().int().describe("기간 안에 시작한 결제 세션 수(결제 요청 단위)"),
363
+ outcomes: z.array(z.object({
364
+ outcome: analyticsCheckoutOutcomeSchema,
365
+ sessions: z.number().int()
366
+ })).describe("결과별 결제 세션 수. 결과마다 한 행")
367
+ });
278
368
  //#endregion
279
369
  //#region src/schemas/api-client.ts
280
370
  /**
@@ -2277,7 +2367,17 @@ get: (params) => storeHttp.request("GET", "/sales-report", salesReportSchema, {
2277
2367
  /** `GET /v1/analytics/products` — 상품별 노출·조회·장바구니·구매 */
2278
2368
  products: (params) => storeHttp.request("GET", "/analytics/products", analyticsProductsSchema, { query: analyticsProductsQuerySchema.parse(params) }),
2279
2369
  /** `GET /v1/analytics/sources` — 채널·소스·캠페인·기기별 세션과 구매 */
2280
- sources: (params) => storeHttp.request("GET", "/analytics/sources", analyticsSourcesSchema, { query: analyticsSourcesQuerySchema.parse(params) })
2370
+ sources: (params) => storeHttp.request("GET", "/analytics/sources", analyticsSourcesSchema, { query: analyticsSourcesQuerySchema.parse(params) }),
2371
+ /** `GET /v1/analytics/realtime` — 최근 5분 방문자·세션·페이지(원본 이벤트 기준, 봇 제외) */
2372
+ realtime: () => storeHttp.request("GET", "/analytics/realtime", analyticsRealtimeSchema),
2373
+ /** `GET /v1/analytics/devices` — 기기별 세션 퍼널 */
2374
+ devices: (params) => storeHttp.request("GET", "/analytics/devices", analyticsDevicesSchema, { query: analyticsRangeQuerySchema.parse(params) }),
2375
+ /** `GET /v1/analytics/pages` — 첫 페이지(진입·반송)와 이탈 페이지 */
2376
+ pages: (params) => storeHttp.request("GET", "/analytics/pages", analyticsPagesSchema, { query: analyticsPagesQuerySchema.parse(params) }),
2377
+ /** `GET /v1/analytics/search` — 검색어별 검색·결과 0개·검색 후 구매 */
2378
+ search: (params) => storeHttp.request("GET", "/analytics/search", analyticsSearchSchema, { query: analyticsSearchQuerySchema.parse(params) }),
2379
+ /** `GET /v1/analytics/checkout` — 결제 세션의 결과별 수(결제 단계 이탈 이유) */
2380
+ checkout: (params) => storeHttp.request("GET", "/analytics/checkout", analyticsCheckoutSchema, { query: analyticsRangeQuerySchema.parse(params) })
2281
2381
  },
2282
2382
  /**
2283
2383
  * 결제 내역 — 스코프 `settlement:r`. 결제 건(세션) 단위이고 주문 목록과 다르다. 주문이 만들어지지 않은
@@ -2812,4 +2912,4 @@ function createStoreSession(options) {
2812
2912
  };
2813
2913
  }
2814
2914
  //#endregion
2815
- export { ApiError, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_OPTION_GROUPS, MAX_VALUES_PER_GROUP, MAX_VARIANTS, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, RESERVED_STORE_CODES, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addressChangeRequestListSchema, addressTypeSchema, analyticsChannelSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approveClaimResultSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, createPkcePair, createStoreClient, createStoreSession, customerAddressSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, effectivePaymentRoutingModeSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationListQuerySchema, invitationSchema, isReservedStoreCode, isStoreScope, isWebOrigin, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, myAccountSchema, myStoreSchema, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentRoutingModeSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productImageUploadResultSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, refreshUserToken, rejectClaimRequestSchema, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, scopeAccessSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, tossPaymentsCredentialsInputSchema, trackingUpdateResultSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookRedeliverFailedResultSchema, webhookRequestSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema };
2915
+ export { ApiError, MAX_ANALYTICS_ALLOWED_ORIGINS, MAX_OPTION_GROUPS, MAX_VALUES_PER_GROUP, MAX_VARIANTS, PAYMENT_ROUTING_EXCLUSION_CODES, PORTONE_CHANNEL_NAME_MAX_LENGTH, PORTONE_MAX_CHANNELS, PORTONE_MIN_CHANNELS, RESERVED_STORE_CODES, STORE_CODE_PATTERN, STORE_ROLE_SCOPES, STORE_SCOPE_RESOURCES, STORE_SESSION_KEYS, TOSS_WIDGET_CLIENT_KEY_PATTERN, TOSS_WIDGET_KEY_MESSAGE, TOSS_WIDGET_SECRET_KEY_PATTERN, VARIANT_STOCK_EXCLUSIVE_MESSAGE, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, addressChangeRequestListSchema, addressTypeSchema, analyticsChannelSchema, analyticsCheckoutOutcomeSchema, analyticsCheckoutSchema, analyticsDeviceRowSchema, analyticsDevicesSchema, analyticsFunnelStepSchema, analyticsOriginSchema, analyticsOverviewSchema, analyticsPagesQuerySchema, analyticsPagesSchema, analyticsProductRowSchema, analyticsProductSortSchema, analyticsProductsQuerySchema, analyticsProductsSchema, analyticsRangeQuerySchema, analyticsRealtimeSchema, analyticsSearchQuerySchema, analyticsSearchSchema, analyticsSourceGroupBySchema, analyticsSourceRowSchema, analyticsSourcesQuerySchema, analyticsSourcesSchema, analyticsTotalsSchema, apiClientListQuerySchema, apiClientSchema, apiClientSecretRotatedSchema, apiClientStatusSchema, apiClientTypeSchema, apiLogErrorSchema, apiLogListQuerySchema, apiLogPageSchema, apiLogPrincipalSchema, apiLogPrincipalTypeSchema, apiLogSchema, apiLogStatusFilterSchema, apiLogSummarySchema, apiTokenCreateRequestSchema, apiTokenCreatedSchema, apiTokenSchema, approveClaimResultSchema, buildAuthorizeUrl, buildQuery, bulkResultSchema, cancelBySellerRequestSchema, cancelBySellerResultSchema, carrierSchema, categoryAttributeSchema, categoryCreateRequestSchema, categoryDeleteQuerySchema, categoryDeleteStrategySchema, categoryListQuerySchema, categoryPatchRequestSchema, categoryProductSortSchema, categorySchema, categorySearchResultSchema, categoryTreeNodeSchema, categoryWithPathSchema, changeTypeSchema, claimDetailSchema, claimHoldResultSchema, claimListQuerySchema, claimReasonSchema, claimSchema, claimStatusSchema, claimTypeSchema, claimWebhookEventDataSchema, claimWebhookEventSchema, clientCredentialsTokenResponseSchema, createPkcePair, createStoreClient, createStoreSession, customerAddressSchema, customerDetailSchema, customerInquiryCategorySchema, customerInquiryListQuerySchema, customerListQuerySchema, customerOrderSchema, customerSortSchema, customerStatsSchema, customerSummarySchema, customerWithdrawalBlockedDetailsSchema, deliveryInfoSchema, deliveryMethodSchema, deliveryStatusSchema, deliveryTypeSchema, discountRequestSchema, discountSchema, dispatchRequestSchema, effectivePaymentRoutingModeSchema, eventTypeSchema, exchangeAuthorizationCode, exchangeStoreTokenViaOAuth, holdOrderItemRequestSchema, inquiryListQuerySchema, inquirySchema, inquiryWebhookEventDataSchema, inquiryWebhookEventSchema, invitationListQuerySchema, invitationSchema, isReservedStoreCode, isStoreScope, isWebOrigin, liveGateConditionCodeSchema, liveGateConditionSchema, liveGateErrorDetailsSchema, liveGateSchema, loginProviderSchema, loginProviderStatusSchema, loginProviderTestSchema, loginSettingsSchema, maskedSecretSchema, myAccountSchema, myStoreSchema, oauthClientRegistrationRequestSchema, oauthClientRegistrationResponseSchema, oauthTokenResponseSchema, optionGroupRequestSchema, optionGroupSchema, optionModelRequestSchema, optionModelSchema, optionValueRequestSchema, optionValueSchema, orderItemListQuerySchema, orderItemMemoResultSchema, orderItemPageSchema, orderItemSchema, orderItemStatusSchema, orderListQuerySchema, orderSchema, orderSummaryPageSchema, orderSummarySchema, orderWebhookEventDataSchema, orderWebhookEventSchema, pageInfoSchema, pageQueryShape, pageSchema, paymentAttemptRecordSchema, paymentAttemptStatusSchema, paymentCredentialSetSchema, paymentListQuerySchema, paymentProviderSchema, paymentProviderVerificationSchema, paymentRecordDetailSchema, paymentRecordPageSchema, paymentRecordSchema, paymentRecordStatusSchema, paymentRefundRecordSchema, paymentRoutingModeSchema, paymentSettingsSchema, pgEnvironmentSchema, pgPaymentMethodSchema, pgProviderSchema, pgVerificationResultSchema, portOneChannelSchema, portOneCredentialsInputSchema, productCreateRequestSchema, productImageUploadResultSchema, productListQuerySchema, productOutOfStockWebhookEventDataSchema, productOutOfStockWebhookEventSchema, productPatchRequestSchema, productSchema, productSortSchema, productStatusSchema, productSummarySchema, productVariantRequestSchema, productVariantSchema, purchaseDecisionExtendResultSchema, purchaseDecisionRequestResultSchema, refreshUserToken, rejectClaimRequestSchema, reviewListQuerySchema, reviewReportResultSchema, reviewSchema, reviewSortSchema, reviewWebhookEventDataSchema, reviewWebhookEventSchema, salesReportGroupBySchema, salesReportQuerySchema, salesReportRowSchema, salesReportSchema, salesTotalsSchema, saveLoginProviderRequestSchema, scopeAccessSchema, sellerAddressListQuerySchema, sellerAddressRequestSchema, sellerAddressSchema, sellerCustomerInquirySchema, settlementDetailQuerySchema, settlementDetailSchema, settlementListQuerySchema, settlementStatusSchema, settlementSummarySchema, settlementWithDetailsSchema, shippingAddressSchema, storeCodeSchema, storeMemberRoleUpdatedSchema, storeMemberSchema, storeRoleSchema, storeSchema, storeScopeResourceSchema, storeTokenResponseSchema, tossPaymentsCredentialsInputSchema, trackingUpdateResultSchema, updateLoginProviderRequestSchema, updateLoginSettingsRequestSchema, updatePaymentProviderRequestSchema, updatePaymentSettingsRequestSchema, updateStoreRequestSchema, uploadRequestSchema, uploadTicketSchema, upsertPortOneProviderRequestSchema, upsertTossPaymentsProviderRequestSchema, userTokenResponseSchema, variantStockRequestSchema, variantStockSchema, verifyPaymentProviderRequestSchema, webhookCreatedSchema, webhookDeliveryErrorCodeSchema, webhookDeliveryListQuerySchema, webhookDeliverySchema, webhookDeliveryStatusSchema, webhookEventSchema, webhookRedeliverFailedResultSchema, webhookRequestSchema, webhookSchema, webhookSecretRotatedSchema, webhookStatusSchema, webhookTestRequestSchema, webhookTestResultSchema, withdrawCustomerRequestSchema };
package/dist/server.d.mts CHANGED
@@ -1,4 +1,48 @@
1
1
  import { a as WEBHOOK_HEADERS, i as WEBHOOK_EVENT_TYPE_HEADER, n as WEBHOOK_DELIVERY_ID_HEADER, o as WEBHOOK_SIGNATURE_HEADER, r as WEBHOOK_EVENT_ID_HEADER, s as WEBHOOK_USER_AGENT, t as WEBHOOK_DEFAULT_TOLERANCE_SEC } from "./webhook-headers-D6QmPdEJ.mjs";
2
+ //#region src/member-delegation.d.ts
3
+ /**
4
+ * 구매자 위임 — 자체 회원 시스템을 가진 셀러 서버가 로그인한 사용자를 sayren 구매자 토큰으로 바꾼다
5
+ * (token-exchange의 `member-uid`·`member-ref` 확장, auth-spec §12-10). 클라이언트 시크릿을 쓰므로 **서버에서만** 부른다.
6
+ *
7
+ * 스토어에 바인딩된 confidential 클라이언트 + `member:delegate` 스코프가 필요하다. 응답은 구매자 액세스 토큰(30분)이고
8
+ * refresh는 없다 — 만료되면 같은 호출로 다시 받는다.
9
+ */
10
+ interface DelegatedMemberToken {
11
+ accessToken: string;
12
+ expiresIn: number;
13
+ memberId: string;
14
+ }
15
+ interface DelegationOptions {
16
+ /** api 주소(예: `https://api.sayren.app`) — `/v1/oauth2/token`을 붙인다 */
17
+ baseUrl: string;
18
+ clientId: string;
19
+ clientSecret: string;
20
+ fetch?: typeof globalThis.fetch;
21
+ }
22
+ /** 셀러 자체 인증의 사용자 id로 지목한다. 처음 보는 id면 구매자를 만든다(이메일·이름은 그때만 쓴다) */
23
+ export declare function delegateMemberByUid(options: DelegationOptions & {
24
+ uid: string;
25
+ email?: string;
26
+ name?: string;
27
+ /**
28
+ * 사용자가 내 서비스에서 실제로 로그인한 시각. 주면 구매자 토큰의 인증 시각이 되고(탈퇴처럼 최근 인증이 필요한 동작에
29
+ * 쓴다), 주지 않으면 인증 시각이 없는 토큰이 나온다
30
+ */
31
+ authenticatedAt?: Date;
32
+ }): Promise<DelegatedMemberToken>;
33
+ /** 이메일로 지목한다. 처음 보는 이메일이면 구매자를 만든다 */
34
+ export declare function delegateMemberByEmail(options: DelegationOptions & {
35
+ email: string;
36
+ name?: string;
37
+ }): Promise<DelegatedMemberToken>;
38
+ /** 위임 실패 — `error`는 OAuth 오류 코드(`invalid_client`·`invalid_scope`·`invalid_request` 등) */
39
+ export declare class MemberDelegationError extends Error {
40
+ readonly error: string;
41
+ readonly description: string;
42
+ readonly status: number;
43
+ constructor(error: string, description: string, status: number);
44
+ }
45
+ //#endregion
2
46
  //#region src/webhook-signature.d.ts
3
47
  interface SignWebhookPayloadOptions {
4
48
  /** 요청 본문 원문. 전송하는 바이트와 정확히 같아야 한다 */
@@ -56,4 +100,4 @@ interface VerifyWebhookSignatureOptions {
56
100
  */
57
101
  export declare function verifyWebhookSignature(options: VerifyWebhookSignatureOptions): Promise<WebhookSignatureVerification>;
58
102
  //#endregion
59
- export { type ParsedWebhookSignature, type SignWebhookPayloadOptions, type VerifyWebhookSignatureOptions, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, type WebhookSignatureFailureReason, type WebhookSignatureVerification };
103
+ export { type DelegatedMemberToken, type ParsedWebhookSignature, type SignWebhookPayloadOptions, type VerifyWebhookSignatureOptions, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, type WebhookSignatureFailureReason, type WebhookSignatureVerification };
package/dist/server.mjs CHANGED
@@ -1,4 +1,59 @@
1
1
  import { a as WEBHOOK_HEADERS, i as WEBHOOK_EVENT_TYPE_HEADER, n as WEBHOOK_DELIVERY_ID_HEADER, o as WEBHOOK_SIGNATURE_HEADER, r as WEBHOOK_EVENT_ID_HEADER, s as WEBHOOK_USER_AGENT, t as WEBHOOK_DEFAULT_TOLERANCE_SEC } from "./webhook-headers-ap_exsb9.mjs";
2
+ //#region src/member-delegation.ts
3
+ /** 셀러 자체 인증의 사용자 id로 지목한다. 처음 보는 id면 구매자를 만든다(이메일·이름은 그때만 쓴다) */
4
+ function delegateMemberByUid(options) {
5
+ return delegate(options, {
6
+ subject_token_type: "urn:sayren:params:oauth:token-type:member-uid",
7
+ subject_token: options.uid,
8
+ ...options.authenticatedAt ? { subject_auth_time: String(Math.floor(options.authenticatedAt.getTime() / 1e3)) } : {},
9
+ ...options.email ? { subject_email: options.email } : {},
10
+ ...options.name ? { subject_name: options.name } : {}
11
+ });
12
+ }
13
+ /** 이메일로 지목한다. 처음 보는 이메일이면 구매자를 만든다 */
14
+ function delegateMemberByEmail(options) {
15
+ return delegate(options, {
16
+ subject_token_type: "urn:sayren:params:oauth:token-type:member-ref",
17
+ subject_token: options.email,
18
+ ...options.name ? { subject_name: options.name } : {}
19
+ });
20
+ }
21
+ async function delegate(options, subject) {
22
+ const doFetch = options.fetch ?? globalThis.fetch;
23
+ const basic = btoa(`${options.clientId}:${options.clientSecret}`);
24
+ const res = await doFetch(`${options.baseUrl.replace(/\/+$/, "")}/v1/oauth2/token`, {
25
+ method: "POST",
26
+ headers: {
27
+ "content-type": "application/x-www-form-urlencoded",
28
+ authorization: `Basic ${basic}`
29
+ },
30
+ body: new URLSearchParams({
31
+ grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
32
+ ...subject
33
+ }).toString()
34
+ });
35
+ const body = await res.json().catch(() => ({}));
36
+ if (!res.ok || typeof body.access_token !== "string") throw new MemberDelegationError(typeof body.error === "string" ? body.error : `http_${res.status}`, typeof body.error_description === "string" ? body.error_description : "", res.status);
37
+ return {
38
+ accessToken: body.access_token,
39
+ expiresIn: Number(body.expires_in),
40
+ memberId: String(body.memberId)
41
+ };
42
+ }
43
+ /** 위임 실패 — `error`는 OAuth 오류 코드(`invalid_client`·`invalid_scope`·`invalid_request` 등) */
44
+ var MemberDelegationError = class extends Error {
45
+ error;
46
+ description;
47
+ status;
48
+ constructor(error, description, status) {
49
+ super(description ? `${error}: ${description}` : error);
50
+ this.error = error;
51
+ this.description = description;
52
+ this.status = status;
53
+ this.name = "MemberDelegationError";
54
+ }
55
+ };
56
+ //#endregion
2
57
  //#region src/webhook-signature.ts
3
58
  /**
4
59
  * 웹훅 서명 (`Sayren-Signature`) 생성·파싱·검증.
@@ -122,4 +177,4 @@ async function verifyWebhookSignature(options) {
122
177
  };
123
178
  }
124
179
  //#endregion
125
- export { WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, parseWebhookSignatureHeader, signWebhookPayload, verifyWebhookSignature };
180
+ export { MemberDelegationError, WEBHOOK_DEFAULT_TOLERANCE_SEC, WEBHOOK_DELIVERY_ID_HEADER, WEBHOOK_EVENT_ID_HEADER, WEBHOOK_EVENT_TYPE_HEADER, WEBHOOK_HEADERS, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_USER_AGENT, delegateMemberByEmail, delegateMemberByUid, parseWebhookSignatureHeader, signWebhookPayload, verifyWebhookSignature };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayren/store-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "exports": {
package/src/client.ts CHANGED
@@ -2,13 +2,22 @@ import { z } from "zod";
2
2
  import { base64Utf8 } from "./base64";
3
3
  import { createHttp, path, type Query, resolveToken, type TokenSource, unwrapData } from "./http";
4
4
  import {
5
+ type AnalyticsPagesParams,
5
6
  type AnalyticsProductsParams,
6
7
  type AnalyticsRangeParams,
8
+ type AnalyticsSearchParams,
7
9
  type AnalyticsSourcesParams,
10
+ analyticsCheckoutSchema,
11
+ analyticsDevicesSchema,
8
12
  analyticsOverviewSchema,
13
+ analyticsPagesQuerySchema,
14
+ analyticsPagesSchema,
9
15
  analyticsProductsQuerySchema,
10
16
  analyticsProductsSchema,
11
17
  analyticsRangeQuerySchema,
18
+ analyticsRealtimeSchema,
19
+ analyticsSearchQuerySchema,
20
+ analyticsSearchSchema,
12
21
  analyticsSourcesQuerySchema,
13
22
  analyticsSourcesSchema,
14
23
  } from "./schemas/analytics";
@@ -719,6 +728,28 @@ export function createStoreClient(options: StoreClientOptions) {
719
728
  storeHttp.request("GET", "/analytics/sources", analyticsSourcesSchema, {
720
729
  query: analyticsSourcesQuerySchema.parse(params),
721
730
  }),
731
+ /** `GET /v1/analytics/realtime` — 최근 5분 방문자·세션·페이지(원본 이벤트 기준, 봇 제외) */
732
+ realtime: () => storeHttp.request("GET", "/analytics/realtime", analyticsRealtimeSchema),
733
+ /** `GET /v1/analytics/devices` — 기기별 세션 퍼널 */
734
+ devices: (params: AnalyticsRangeParams) =>
735
+ storeHttp.request("GET", "/analytics/devices", analyticsDevicesSchema, {
736
+ query: analyticsRangeQuerySchema.parse(params),
737
+ }),
738
+ /** `GET /v1/analytics/pages` — 첫 페이지(진입·반송)와 이탈 페이지 */
739
+ pages: (params: AnalyticsPagesParams) =>
740
+ storeHttp.request("GET", "/analytics/pages", analyticsPagesSchema, {
741
+ query: analyticsPagesQuerySchema.parse(params) as unknown as Query,
742
+ }),
743
+ /** `GET /v1/analytics/search` — 검색어별 검색·결과 0개·검색 후 구매 */
744
+ search: (params: AnalyticsSearchParams) =>
745
+ storeHttp.request("GET", "/analytics/search", analyticsSearchSchema, {
746
+ query: analyticsSearchQuerySchema.parse(params) as unknown as Query,
747
+ }),
748
+ /** `GET /v1/analytics/checkout` — 결제 세션의 결과별 수(결제 단계 이탈 이유) */
749
+ checkout: (params: AnalyticsRangeParams) =>
750
+ storeHttp.request("GET", "/analytics/checkout", analyticsCheckoutSchema, {
751
+ query: analyticsRangeQuerySchema.parse(params),
752
+ }),
722
753
  },
723
754
 
724
755
  /**
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 구매자 위임 — 자체 회원 시스템을 가진 셀러 서버가 로그인한 사용자를 sayren 구매자 토큰으로 바꾼다
3
+ * (token-exchange의 `member-uid`·`member-ref` 확장, auth-spec §12-10). 클라이언트 시크릿을 쓰므로 **서버에서만** 부른다.
4
+ *
5
+ * 스토어에 바인딩된 confidential 클라이언트 + `member:delegate` 스코프가 필요하다. 응답은 구매자 액세스 토큰(30분)이고
6
+ * refresh는 없다 — 만료되면 같은 호출로 다시 받는다.
7
+ */
8
+
9
+ export interface DelegatedMemberToken {
10
+ accessToken: string;
11
+ expiresIn: number;
12
+ memberId: string;
13
+ }
14
+
15
+ interface DelegationOptions {
16
+ /** api 주소(예: `https://api.sayren.app`) — `/v1/oauth2/token`을 붙인다 */
17
+ baseUrl: string;
18
+ clientId: string;
19
+ clientSecret: string;
20
+ fetch?: typeof globalThis.fetch;
21
+ }
22
+
23
+ /** 셀러 자체 인증의 사용자 id로 지목한다. 처음 보는 id면 구매자를 만든다(이메일·이름은 그때만 쓴다) */
24
+ export function delegateMemberByUid(
25
+ options: DelegationOptions & {
26
+ uid: string;
27
+ email?: string;
28
+ name?: string;
29
+ /**
30
+ * 사용자가 내 서비스에서 실제로 로그인한 시각. 주면 구매자 토큰의 인증 시각이 되고(탈퇴처럼 최근 인증이 필요한 동작에
31
+ * 쓴다), 주지 않으면 인증 시각이 없는 토큰이 나온다
32
+ */
33
+ authenticatedAt?: Date;
34
+ },
35
+ ): Promise<DelegatedMemberToken> {
36
+ return delegate(options, {
37
+ subject_token_type: "urn:sayren:params:oauth:token-type:member-uid",
38
+ subject_token: options.uid,
39
+ ...(options.authenticatedAt
40
+ ? { subject_auth_time: String(Math.floor(options.authenticatedAt.getTime() / 1000)) }
41
+ : {}),
42
+ ...(options.email ? { subject_email: options.email } : {}),
43
+ ...(options.name ? { subject_name: options.name } : {}),
44
+ });
45
+ }
46
+
47
+ /** 이메일로 지목한다. 처음 보는 이메일이면 구매자를 만든다 */
48
+ export function delegateMemberByEmail(
49
+ options: DelegationOptions & { email: string; name?: string },
50
+ ): Promise<DelegatedMemberToken> {
51
+ return delegate(options, {
52
+ subject_token_type: "urn:sayren:params:oauth:token-type:member-ref",
53
+ subject_token: options.email,
54
+ ...(options.name ? { subject_name: options.name } : {}),
55
+ });
56
+ }
57
+
58
+ async function delegate(
59
+ options: DelegationOptions,
60
+ subject: Record<string, string>,
61
+ ): Promise<DelegatedMemberToken> {
62
+ const doFetch = options.fetch ?? globalThis.fetch;
63
+ const basic = btoa(`${options.clientId}:${options.clientSecret}`);
64
+ const res = await doFetch(`${options.baseUrl.replace(/\/+$/, "")}/v1/oauth2/token`, {
65
+ method: "POST",
66
+ headers: {
67
+ "content-type": "application/x-www-form-urlencoded",
68
+ authorization: `Basic ${basic}`,
69
+ },
70
+ body: new URLSearchParams({
71
+ grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
72
+ ...subject,
73
+ }).toString(),
74
+ });
75
+ const body = (await res.json().catch(() => ({}))) as Record<string, unknown>;
76
+ if (!res.ok || typeof body.access_token !== "string") {
77
+ const error = typeof body.error === "string" ? body.error : `http_${res.status}`;
78
+ const description = typeof body.error_description === "string" ? body.error_description : "";
79
+ throw new MemberDelegationError(error, description, res.status);
80
+ }
81
+ return {
82
+ accessToken: body.access_token,
83
+ expiresIn: Number(body.expires_in),
84
+ memberId: String(body.memberId),
85
+ };
86
+ }
87
+
88
+ /** 위임 실패 — `error`는 OAuth 오류 코드(`invalid_client`·`invalid_scope`·`invalid_request` 등) */
89
+ export class MemberDelegationError extends Error {
90
+ constructor(
91
+ readonly error: string,
92
+ readonly description: string,
93
+ readonly status: number,
94
+ ) {
95
+ super(description ? `${error}: ${description}` : error);
96
+ this.name = "MemberDelegationError";
97
+ }
98
+ }
@@ -96,6 +96,10 @@ export const analyticsOverviewSchema = z.object({
96
96
  rows: z
97
97
  .array(analyticsTotalsSchema.extend({ date: z.string().describe("일자 (YYYY-MM-DD)") }))
98
98
  .describe("날짜별 값. 세션이 없는 날은 생략된다"),
99
+ excludedBotSessions: z
100
+ .number()
101
+ .int()
102
+ .describe("집계에서 뺀 봇 세션 수 — 봇 표시 이벤트가 하나라도 있는 세션은 모든 값에서 빠진다"),
99
103
  });
100
104
  export type AnalyticsOverview = z.infer<typeof analyticsOverviewSchema>;
101
105
 
@@ -132,6 +136,9 @@ export const analyticsProductRowSchema = z.object({
132
136
  "이 상품의 구매 금액 — 주문 상품 금액(할인 반영, 배송비 제외) 합계. 이후 취소·환불은 빼지 않는다",
133
137
  ),
134
138
  purchaseRate: rate("구매율 = 구매 / 조회 세션. 조회 세션이 없으면 null"),
139
+ clickThroughRate: rate(
140
+ "목록 → 상세 전환율 = 상세 조회 / 목록 노출. 노출이 없으면 null(검색·외부 링크로 바로 들어온 조회는 노출 없이 잡힌다)",
141
+ ),
135
142
  });
136
143
  export type AnalyticsProductRow = z.infer<typeof analyticsProductRowSchema>;
137
144
 
@@ -198,3 +205,141 @@ export const analyticsSourcesSchema = z.object({
198
205
  rows: z.array(analyticsSourceRowSchema).describe("세션 수 내림차순"),
199
206
  });
200
207
  export type AnalyticsSources = z.infer<typeof analyticsSourcesSchema>;
208
+
209
+ // ─── 2차(#6): 실시간·기기별 퍼널·첫/이탈 페이지·검색어·결제 단계 이탈 ─────────────────────
210
+
211
+ export const analyticsRealtimeSchema = z.object({
212
+ asOf: z.string().describe("기준 시각(ISO 8601)"),
213
+ windowMinutes: z.literal(5).describe("최근 몇 분 동안 활동한 방문을 세는가"),
214
+ visitors: z.number().int().describe("최근 5분 안에 이벤트가 있는 방문자 수(봇 제외)"),
215
+ sessions: z.number().int(),
216
+ pageViews: z.number().int(),
217
+ pages: z
218
+ .array(z.object({ path: z.string(), visitors: z.number().int(), pageViews: z.number().int() }))
219
+ .describe("최근 5분 페이지뷰가 있는 경로. 방문자 수 내림차순 최대 10개"),
220
+ });
221
+ export type AnalyticsRealtime = z.infer<typeof analyticsRealtimeSchema>;
222
+
223
+ export const analyticsDeviceRowSchema = z.object({
224
+ device: z.enum(["desktop", "mobile", "tablet", "unknown"]),
225
+ sessions: z.number().int(),
226
+ productViewSessions: z.number().int(),
227
+ addToCartSessions: z.number().int(),
228
+ beginCheckoutSessions: z.number().int(),
229
+ purchaseSessions: z.number().int(),
230
+ conversionRate: rate("전환율 = 구매 세션 / 세션"),
231
+ });
232
+ export type AnalyticsDeviceRow = z.infer<typeof analyticsDeviceRowSchema>;
233
+
234
+ export const analyticsDevicesSchema = z.object({
235
+ ...rangeMeta,
236
+ rows: z.array(analyticsDeviceRowSchema).describe("기기별 세션 기준 퍼널. 세션 수 내림차순"),
237
+ });
238
+ export type AnalyticsDevices = z.infer<typeof analyticsDevicesSchema>;
239
+
240
+ const limitParam = z.coerce
241
+ .number()
242
+ .int()
243
+ .min(1)
244
+ .max(100)
245
+ .optional()
246
+ .describe("최대 행 수. 기본 20, 최대 100");
247
+
248
+ export const analyticsPagesQuerySchema = analyticsRangeQuerySchema.extend({ limit: limitParam });
249
+ export type AnalyticsPagesParams = z.infer<typeof analyticsPagesQuerySchema>;
250
+
251
+ export const analyticsPagesSchema = z.object({
252
+ ...rangeMeta,
253
+ landings: z
254
+ .array(
255
+ z.object({
256
+ path: z.string(),
257
+ entrances: z.number().int().describe("이 페이지로 시작한 세션 수"),
258
+ bounces: z.number().int().describe("이 페이지로 시작해 참여하지 않고 끝난 세션 수"),
259
+ bounceRate: rate("반송률 = 반송 / 진입"),
260
+ }),
261
+ )
262
+ .describe("첫 페이지. 진입 수 내림차순"),
263
+ exits: z
264
+ .array(
265
+ z.object({
266
+ path: z.string(),
267
+ pageViews: z.number().int(),
268
+ exits: z.number().int().describe("이 페이지가 세션의 마지막 페이지뷰였던 수"),
269
+ exitRate: rate("이탈률 = 이탈 / 페이지뷰"),
270
+ }),
271
+ )
272
+ .describe("이탈 페이지. 이탈 수 내림차순"),
273
+ });
274
+ export type AnalyticsPages = z.infer<typeof analyticsPagesSchema>;
275
+
276
+ export const analyticsSearchQuerySchema = analyticsRangeQuerySchema.extend({ limit: limitParam });
277
+ export type AnalyticsSearchParams = z.infer<typeof analyticsSearchQuerySchema>;
278
+
279
+ export const analyticsSearchSchema = z.object({
280
+ ...rangeMeta,
281
+ summary: z.object({
282
+ searches: z.number().int(),
283
+ zeroResultSearches: z.number().int().describe("결과가 0개였던 검색 수"),
284
+ searchSessions: z.number().int().describe("검색한 세션 수(세션당 한 번)"),
285
+ purchaseSessions: z
286
+ .number()
287
+ .int()
288
+ .describe(
289
+ "검색한 뒤 같은 날 구매한 세션 수(세션당 한 번). 검색 전에 끝난 구매는 세지 않는다",
290
+ ),
291
+ conversionRate: rate("검색 후 구매율 = 구매 세션 / 검색 세션"),
292
+ }),
293
+ terms: z
294
+ .array(
295
+ z.object({
296
+ term: z.string().describe("검색어 — 소문자, 공백 정리"),
297
+ searches: z.number().int(),
298
+ zeroResultSearches: z.number().int(),
299
+ searchSessions: z.number().int().describe("이 검색어로 검색한 세션 수"),
300
+ purchaseSessions: z.number().int().describe("이 검색어로 검색한 뒤 같은 날 구매한 세션 수"),
301
+ conversionRate: rate("검색 후 구매율 = 구매 세션 / 검색 세션"),
302
+ }),
303
+ )
304
+ .describe(
305
+ "검색 수 내림차순. 세션 수는 검색어마다 세므로 여러 검색어를 쓴 세션은 검색어마다 잡힌다(더하면 요약보다 크다)",
306
+ ),
307
+ zeroResultTerms: z
308
+ .array(
309
+ z.object({
310
+ term: z.string(),
311
+ searches: z.number().int(),
312
+ zeroResultSearches: z.number().int(),
313
+ }),
314
+ )
315
+ .describe(
316
+ "결과가 0개였던 적이 있는 검색어. 결과 없음 수 내림차순 — 검색 수 상위(`terms`)에 들지 않는 드문 검색어도 여기 잡힌다",
317
+ ),
318
+ });
319
+ export type AnalyticsSearch = z.infer<typeof analyticsSearchSchema>;
320
+
321
+ export const analyticsCheckoutOutcomeSchema = z
322
+ .enum([
323
+ "COMPLETED",
324
+ "NOT_OPENED",
325
+ "USER_CANCELED",
326
+ "DECLINED",
327
+ "PREPARE_FAILURE",
328
+ "OTHER",
329
+ "IN_PROGRESS",
330
+ ])
331
+ .describe(
332
+ "결제 세션의 최종 결과 — `COMPLETED`(결제 완료), `NOT_OPENED`(결제창을 열지 않고 끝남), `USER_CANCELED`(결제창에서 취소), " +
333
+ "`DECLINED`(카드 거절 등 승인 거절), `PREPARE_FAILURE`(결제창을 띄우기 전 장애), `OTHER`(그 밖의 이탈·만료), " +
334
+ "`IN_PROGRESS`(아직 진행 중 — 만료 전 대기·승인 중). 실패 분류는 세션의 마지막 결제 시도 기준이다",
335
+ );
336
+ export type AnalyticsCheckoutOutcome = z.infer<typeof analyticsCheckoutOutcomeSchema>;
337
+
338
+ export const analyticsCheckoutSchema = z.object({
339
+ ...rangeMeta,
340
+ sessions: z.number().int().describe("기간 안에 시작한 결제 세션 수(결제 요청 단위)"),
341
+ outcomes: z
342
+ .array(z.object({ outcome: analyticsCheckoutOutcomeSchema, sessions: z.number().int() }))
343
+ .describe("결과별 결제 세션 수. 결과마다 한 행"),
344
+ });
345
+ export type AnalyticsCheckout = z.infer<typeof analyticsCheckoutSchema>;
package/src/server.ts CHANGED
@@ -3,6 +3,12 @@
3
3
  * 브라우저 번들에서 import하지 않는다.
4
4
  */
5
5
 
6
+ export {
7
+ type DelegatedMemberToken,
8
+ delegateMemberByEmail,
9
+ delegateMemberByUid,
10
+ MemberDelegationError,
11
+ } from "./member-delegation";
6
12
  export {
7
13
  WEBHOOK_DEFAULT_TOLERANCE_SEC,
8
14
  WEBHOOK_DELIVERY_ID_HEADER,