@sayren/store-sdk 0.23.0 → 0.28.1
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 +636 -482
- package/dist/index.mjs +370 -347
- package/dist/server.d.mts +19 -28
- package/dist/server.mjs +29 -42
- package/package.json +1 -1
- package/src/client.ts +55 -153
- package/src/http.ts +1 -1
- package/src/index.ts +3 -2
- package/src/member-delegation.ts +51 -77
- package/src/oauth.ts +28 -33
- package/src/schemas/api-log.ts +7 -5
- package/src/schemas/audit.ts +5 -5
- package/src/schemas/auth.ts +0 -64
- package/src/schemas/claim.ts +4 -4
- package/src/schemas/coupon.ts +2 -2
- package/src/schemas/customer-delegation.ts +48 -0
- package/src/schemas/customer.ts +3 -3
- package/src/schemas/fulfillment.ts +9 -11
- package/src/schemas/order.ts +1 -1
- package/src/schemas/payment.ts +7 -5
- package/src/schemas/product.ts +12 -5
- package/src/schemas/promotion.ts +4 -4
- package/src/schemas/refund-policy.ts +12 -12
- package/src/schemas/saved-option-set.ts +1 -1
- package/src/schemas/scope.ts +3 -4
- package/src/schemas/shipping-policy.ts +18 -18
- package/src/schemas/store.ts +9 -9
- package/src/schemas/storefront-hosting.ts +62 -38
- package/src/schemas/subscription.ts +222 -0
- package/src/schemas/telemetry.ts +2 -2
- package/src/schemas/upload.ts +1 -1
- package/src/schemas/webhook-event.ts +1 -1
- package/src/server.ts +1 -2
- package/src/session.ts +46 -29
- package/src/schemas/api-client.ts +0 -121
package/dist/server.d.mts
CHANGED
|
@@ -1,47 +1,38 @@
|
|
|
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
2
|
//#region src/member-delegation.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* 구매자 위임 — 자체 회원 시스템을 가진 셀러 서버가 로그인한 사용자를 sayren 구매자 토큰으로
|
|
5
|
-
* (
|
|
4
|
+
* 구매자 위임 — 자체 회원 시스템을 가진 셀러 서버가 로그인한 사용자를 sayren 구매자 토큰으로 바꾼다.
|
|
5
|
+
* 계정 토큰(개인 액세스 토큰 등)을 쓰므로 **서버에서만** 부른다.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* refresh는 없다 — 만료되면 같은 호출로 다시 받는다.
|
|
7
|
+
* 응답은 구매자 액세스 토큰(30분)이고 refresh는 없다 — 만료되면 같은 호출로 다시 받는다.
|
|
9
8
|
*/
|
|
10
9
|
interface DelegatedMemberToken {
|
|
11
10
|
accessToken: string;
|
|
12
11
|
expiresIn: number;
|
|
13
12
|
memberId: string;
|
|
14
13
|
}
|
|
15
|
-
|
|
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` 등) */
|
|
14
|
+
/** 위임 실패 — `error`는 api 오류 코드(엔벨로프 `error.code`, 없으면 `http_{status}`) */
|
|
39
15
|
export declare class MemberDelegationError extends Error {
|
|
40
16
|
readonly error: string;
|
|
41
17
|
readonly description: string;
|
|
42
18
|
readonly status: number;
|
|
43
19
|
constructor(error: string, description: string, status: number);
|
|
44
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과 상점 id를 쓴다.
|
|
23
|
+
* `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
|
|
24
|
+
*/
|
|
25
|
+
export declare function delegateMember(options: {
|
|
26
|
+
/** api 주소(예: `https://api.sayren.app`) */
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
accessToken: string;
|
|
29
|
+
storeId: string;
|
|
30
|
+
uid?: string;
|
|
31
|
+
email?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
authenticatedAt?: Date;
|
|
34
|
+
fetch?: typeof globalThis.fetch;
|
|
35
|
+
}): Promise<DelegatedMemberToken>;
|
|
45
36
|
//#endregion
|
|
46
37
|
//#region src/webhook-signature.d.ts
|
|
47
38
|
interface SignWebhookPayloadOptions {
|
package/dist/server.mjs
CHANGED
|
@@ -1,46 +1,6 @@
|
|
|
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
2
|
//#region src/member-delegation.ts
|
|
3
|
-
/**
|
|
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` 등) */
|
|
3
|
+
/** 위임 실패 — `error`는 api 오류 코드(엔벨로프 `error.code`, 없으면 `http_{status}`) */
|
|
44
4
|
var MemberDelegationError = class extends Error {
|
|
45
5
|
error;
|
|
46
6
|
description;
|
|
@@ -53,6 +13,33 @@ var MemberDelegationError = class extends Error {
|
|
|
53
13
|
this.name = "MemberDelegationError";
|
|
54
14
|
}
|
|
55
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* 계정 토큰으로 구매자 위임(`POST /v1/customers/delegations`) — 계정 토큰(개인 액세스 토큰 등)과 상점 id를 쓴다.
|
|
18
|
+
* `customer:rw`가 필요하다. `uid`(셀러 자체 인증의 사용자 id) 또는 `email`로 지목하고, 처음 보는 사용자면 구매자를 만든다.
|
|
19
|
+
*/
|
|
20
|
+
async function delegateMember(options) {
|
|
21
|
+
const res = await (options.fetch ?? globalThis.fetch)(`${options.baseUrl.replace(/\/+$/, "")}/v1/customers/delegations`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
"content-type": "application/json",
|
|
25
|
+
authorization: `Bearer ${options.accessToken}`,
|
|
26
|
+
"x-store-id": options.storeId
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
...options.uid ? { uid: options.uid } : {},
|
|
30
|
+
...options.email ? { email: options.email } : {},
|
|
31
|
+
...options.name ? { name: options.name } : {},
|
|
32
|
+
...options.authenticatedAt ? { authenticatedAt: options.authenticatedAt.toISOString() } : {}
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
const body = await res.json().catch(() => ({}));
|
|
36
|
+
if (!res.ok || typeof body.data?.accessToken !== "string") throw new MemberDelegationError(typeof body.error?.code === "string" ? body.error.code : `http_${res.status}`, typeof body.error?.message === "string" ? body.error.message : "", res.status);
|
|
37
|
+
return {
|
|
38
|
+
accessToken: body.data.accessToken,
|
|
39
|
+
expiresIn: Number(body.data.expiresIn),
|
|
40
|
+
memberId: String(body.data.memberId)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
56
43
|
//#endregion
|
|
57
44
|
//#region src/webhook-signature.ts
|
|
58
45
|
/**
|
|
@@ -177,4 +164,4 @@ async function verifyWebhookSignature(options) {
|
|
|
177
164
|
};
|
|
178
165
|
}
|
|
179
166
|
//#endregion
|
|
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,
|
|
167
|
+
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, delegateMember, parseWebhookSignatureHeader, signWebhookPayload, verifyWebhookSignature };
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
ApiError,
|
|
5
|
-
createHttp,
|
|
6
|
-
path,
|
|
7
|
-
type Query,
|
|
8
|
-
resolveToken,
|
|
9
|
-
type TokenSource,
|
|
10
|
-
unwrapData,
|
|
11
|
-
} from "./http";
|
|
2
|
+
import { ApiError, createHttp, path, type Query, resolveToken, type TokenSource } from "./http";
|
|
12
3
|
import {
|
|
13
4
|
type AnalyticsPagesParams,
|
|
14
5
|
type AnalyticsProductsParams,
|
|
@@ -29,14 +20,6 @@ import {
|
|
|
29
20
|
analyticsSourcesQuerySchema,
|
|
30
21
|
analyticsSourcesSchema,
|
|
31
22
|
} from "./schemas/analytics";
|
|
32
|
-
import {
|
|
33
|
-
type ApiClientListParams,
|
|
34
|
-
apiClientSchema,
|
|
35
|
-
apiClientSecretRotatedSchema,
|
|
36
|
-
type OAuthClientRegistrationRequest,
|
|
37
|
-
oauthClientRegistrationRequestSchema,
|
|
38
|
-
oauthClientRegistrationResponseSchema,
|
|
39
|
-
} from "./schemas/api-client";
|
|
40
23
|
import { type ApiLogListOptions, apiLogPageSchema, apiLogSummarySchema } from "./schemas/api-log";
|
|
41
24
|
import {
|
|
42
25
|
type ApprovalDecisionRequest,
|
|
@@ -47,15 +30,7 @@ import {
|
|
|
47
30
|
approvalRequestSchema,
|
|
48
31
|
auditLogPageSchema,
|
|
49
32
|
} from "./schemas/audit";
|
|
50
|
-
import {
|
|
51
|
-
type ApiTokenCreateRequest,
|
|
52
|
-
apiTokenCreatedSchema,
|
|
53
|
-
apiTokenCreateRequestSchema,
|
|
54
|
-
apiTokenSchema,
|
|
55
|
-
clientCredentialsTokenResponseSchema,
|
|
56
|
-
myAccountSchema,
|
|
57
|
-
myStoreSchema,
|
|
58
|
-
} from "./schemas/auth";
|
|
33
|
+
import { myAccountSchema, myStoreSchema } from "./schemas/auth";
|
|
59
34
|
import {
|
|
60
35
|
type CategoryCreateRequest,
|
|
61
36
|
type CategoryDeleteStrategy,
|
|
@@ -292,6 +267,7 @@ import {
|
|
|
292
267
|
type WriteStorefrontFilesRequest,
|
|
293
268
|
writeStorefrontFilesRequestSchema,
|
|
294
269
|
} from "./schemas/storefront-hosting";
|
|
270
|
+
import { publicPlansSchema, storeSubscriptionSchema } from "./schemas/subscription";
|
|
295
271
|
import {
|
|
296
272
|
type UploadPurpose,
|
|
297
273
|
type UploadRequest,
|
|
@@ -305,20 +281,16 @@ import type { AuthorizeOptions, StoreSession } from "./session";
|
|
|
305
281
|
export interface StoreClientOptions {
|
|
306
282
|
/** 예: https://api.example.com/v1 */
|
|
307
283
|
baseUrl: string;
|
|
308
|
-
/** 유저 토큰 (1단계). 인증/스토어 목록 API에 사용 */
|
|
309
|
-
userToken?: TokenSource;
|
|
310
|
-
/** 스토어 토큰 (3단계). 테넌트 리소스 API에 사용 */
|
|
311
|
-
storeToken?: TokenSource;
|
|
312
|
-
/** 테넌트 storeId — storeHttp(테넌트 리소스) 요청에 X-Store-Id로 실린다(userToken 직접 인가). */
|
|
313
|
-
storeId?: TokenSource;
|
|
314
284
|
/**
|
|
315
|
-
* 토큰
|
|
316
|
-
*
|
|
285
|
+
* 계정 토큰(accounts가 발급한 JWT — 상점 플랫폼 로그인, 개인 액세스 토큰, 연결된 앱의 토큰).
|
|
286
|
+
* 계정 API(`/me/*`)와 상점 API 모두에 Bearer로 싣는다. `session`을 주면 생략해도 세션 토큰을 쓴다.
|
|
317
287
|
*/
|
|
318
|
-
|
|
288
|
+
accessToken?: TokenSource;
|
|
289
|
+
/** 대상 상점 id — 상점 API 요청에 `X-Store-Id`로 싣는다. 계정 API(`/me/*`)에는 싣지 않는다 */
|
|
290
|
+
storeId?: TokenSource;
|
|
319
291
|
/**
|
|
320
|
-
* SDK 세션 매니저 — 전달하면
|
|
321
|
-
* 이 세션에서 자동 배선한다. 개별 옵션(
|
|
292
|
+
* SDK 세션 매니저 — 전달하면 Bearer 토큰·401 refresh·복구 불가 시 clear를
|
|
293
|
+
* 이 세션에서 자동 배선한다. 개별 옵션(accessToken/refresh/onUnauthorized)을 명시하면 그쪽이 우선한다.
|
|
322
294
|
*/
|
|
323
295
|
session?: StoreSession;
|
|
324
296
|
/** 401 시 세션 갱신 시도 — true면 요청을 1회 재시도한다(refresh 토큰 회전) */
|
|
@@ -328,44 +300,37 @@ export interface StoreClientOptions {
|
|
|
328
300
|
fetch?: typeof globalThis.fetch;
|
|
329
301
|
}
|
|
330
302
|
|
|
331
|
-
function formBody(fields: Record<string, string>): string {
|
|
332
|
-
return new URLSearchParams(fields).toString();
|
|
333
|
-
}
|
|
334
|
-
|
|
335
303
|
export function createStoreClient(options: StoreClientOptions) {
|
|
336
304
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
337
305
|
const { session } = options;
|
|
338
306
|
|
|
339
|
-
// session이 있으면
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
options.userToken ?? (session ? () => session.getToken() : undefined);
|
|
307
|
+
// session이 있으면 토큰/refresh/onUnauthorized의 기본값을 세션에서 배선한다. 개별 옵션을 명시하면 그쪽이 우선한다.
|
|
308
|
+
const tokenSource: TokenSource =
|
|
309
|
+
options.accessToken ?? (session ? () => session.getToken() : undefined);
|
|
343
310
|
const refresh = options.refresh ?? (session ? () => session.refresh() : undefined);
|
|
344
311
|
const onUnauthorized = options.onUnauthorized ?? (session ? () => session.clear() : undefined);
|
|
345
|
-
// storeToken(명시 옵션)이 auth.apiToken(PAT/storeToken급 주입)보다 우선한다.
|
|
346
|
-
const storeTokenSource: TokenSource = options.storeToken ?? options.auth?.apiToken;
|
|
347
312
|
|
|
313
|
+
function bearer(): Record<string, string> {
|
|
314
|
+
const token = resolveToken(tokenSource);
|
|
315
|
+
return token ? { authorization: `Bearer ${token}` } : {};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** 계정 API(`/me/*`) — 상점을 고르지 않는다 */
|
|
348
319
|
const userHttp = createHttp({
|
|
349
320
|
baseUrl: options.baseUrl,
|
|
350
321
|
fetch: fetchFn,
|
|
351
|
-
headers:
|
|
352
|
-
const token = resolveToken(userTokenSource);
|
|
353
|
-
return token ? { authorization: `Bearer ${token}` } : ({} as Record<string, string>);
|
|
354
|
-
},
|
|
322
|
+
headers: bearer,
|
|
355
323
|
refresh,
|
|
356
324
|
onUnauthorized,
|
|
357
325
|
});
|
|
358
326
|
|
|
327
|
+
/** 상점 API — 같은 계정 토큰 + `X-Store-Id` */
|
|
359
328
|
const storeHttp = createHttp({
|
|
360
329
|
baseUrl: options.baseUrl,
|
|
361
330
|
fetch: fetchFn,
|
|
362
331
|
headers: () => {
|
|
363
|
-
const token = resolveToken(storeTokenSource);
|
|
364
332
|
const storeId = resolveToken(options.storeId);
|
|
365
|
-
return {
|
|
366
|
-
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
367
|
-
...(storeId ? { "x-store-id": storeId } : {}),
|
|
368
|
-
};
|
|
333
|
+
return { ...bearer(), ...(storeId ? { "x-store-id": storeId } : {}) };
|
|
369
334
|
},
|
|
370
335
|
refresh,
|
|
371
336
|
onUnauthorized,
|
|
@@ -391,7 +356,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
391
356
|
}
|
|
392
357
|
|
|
393
358
|
return {
|
|
394
|
-
/** session의 현재
|
|
359
|
+
/** session의 현재 액세스 토큰 (session 미배선 시 null — 던지지 않음) */
|
|
395
360
|
get accessToken(): string | null {
|
|
396
361
|
return session ? session.accessToken : null;
|
|
397
362
|
},
|
|
@@ -405,90 +370,11 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
405
370
|
userinfo: () => requireSession().userinfo(),
|
|
406
371
|
|
|
407
372
|
auth: {
|
|
408
|
-
/**
|
|
409
|
-
* 서버 간 연동(M2M) — client_credentials grant (client_secret_basic).
|
|
410
|
-
* 클라이언트가 스토어에 바인딩되어 있어 **storeToken이 곧바로 발급**된다.
|
|
411
|
-
* ⚠ client_secret은 서버 환경에서만 사용할 것 (브라우저 반입 금지)
|
|
412
|
-
*/
|
|
413
|
-
async issueStoreTokenByClientCredentials(
|
|
414
|
-
clientId: string,
|
|
415
|
-
clientSecret: string,
|
|
416
|
-
scope?: string,
|
|
417
|
-
) {
|
|
418
|
-
// RFC 7617 client_secret_basic — UTF-8 바이트를 base64로 인코딩한다.
|
|
419
|
-
const credentials = base64Utf8(`${clientId}:${clientSecret}`);
|
|
420
|
-
const response = await fetchFn(`${options.baseUrl}/oauth2/token`, {
|
|
421
|
-
method: "POST",
|
|
422
|
-
headers: {
|
|
423
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
424
|
-
authorization: `Basic ${credentials}`,
|
|
425
|
-
},
|
|
426
|
-
body: formBody({ grant_type: "client_credentials", ...(scope ? { scope } : {}) }),
|
|
427
|
-
});
|
|
428
|
-
if (!response.ok) {
|
|
429
|
-
const body = (await response.json().catch(() => null)) as {
|
|
430
|
-
error_description?: string;
|
|
431
|
-
} | null;
|
|
432
|
-
throw new Error(body?.error_description ?? `토큰 발급 실패 (HTTP ${response.status})`);
|
|
433
|
-
}
|
|
434
|
-
return clientCredentialsTokenResponseSchema.parse(unwrapData(await response.json()));
|
|
435
|
-
},
|
|
436
|
-
/** 2단계 — 소속 스토어 목록 */
|
|
373
|
+
/** 소속 상점 목록 */
|
|
437
374
|
listMyStores: () => userHttp.request("GET", "/me/stores", z.array(myStoreSchema)),
|
|
438
|
-
/** RFC 7009 — token을 폐기한다 (public client는 client_id만 동반) */
|
|
439
|
-
async revoke(token: string, clientId: string) {
|
|
440
|
-
await fetchFn(`${options.baseUrl}/oauth2/revoke`, {
|
|
441
|
-
method: "POST",
|
|
442
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
443
|
-
body: formBody({ token, client_id: clientId }),
|
|
444
|
-
});
|
|
445
|
-
},
|
|
446
375
|
me: () => userHttp.request("GET", "/me", myAccountSchema),
|
|
447
376
|
},
|
|
448
377
|
|
|
449
|
-
/** API 토큰(PAT) 관리 — OWNER/ADMIN 전용. 발급된 토큰은 storeToken처럼 Bearer로 사용 */
|
|
450
|
-
apiTokens: {
|
|
451
|
-
create: (body: ApiTokenCreateRequest) =>
|
|
452
|
-
storeHttp.request("POST", "/store/api-tokens", apiTokenCreatedSchema, {
|
|
453
|
-
body: apiTokenCreateRequestSchema.parse(body),
|
|
454
|
-
}),
|
|
455
|
-
list: () => storeHttp.request("GET", "/store/api-tokens", z.array(apiTokenSchema)),
|
|
456
|
-
revoke: (tokenId: string) =>
|
|
457
|
-
storeHttp.requestVoid("DELETE", path`/store/api-tokens/${tokenId}`),
|
|
458
|
-
},
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* 앱(OAuth 클라이언트) 관리 — OWNER/ADMIN storeToken 전용(PAT·M2M 거부). auth-spec §12, 부록 B-13.
|
|
462
|
-
* 시크릿 평문은 register·rotateSecret 응답에서만 한 번 노출된다(apiTokens.create와 같은 성격).
|
|
463
|
-
*/
|
|
464
|
-
apiClients: {
|
|
465
|
-
/**
|
|
466
|
-
* DCR 등록 (RFC 7591) — `POST /v1/oauth2/register`. OAuth 프로토콜 표면이라 응답이 평면 JSON이다.
|
|
467
|
-
* 등록 오류(`invalid_client_metadata`·`invalid_redirect_uri`)는 ApiError(code=error,
|
|
468
|
-
* message=error_description)로, Bearer 인증 오류는 엔벨로프 그대로 ApiError로 던진다.
|
|
469
|
-
*/
|
|
470
|
-
register: (body: OAuthClientRegistrationRequest) =>
|
|
471
|
-
storeHttp.request("POST", "/oauth2/register", oauthClientRegistrationResponseSchema, {
|
|
472
|
-
body: oauthClientRegistrationRequestSchema.parse(body),
|
|
473
|
-
}),
|
|
474
|
-
list: (params?: ApiClientListParams) =>
|
|
475
|
-
storeHttp.request("GET", "/store/api-clients", z.array(apiClientSchema), {
|
|
476
|
-
query: params as Query,
|
|
477
|
-
}),
|
|
478
|
-
get: (clientId: string) =>
|
|
479
|
-
storeHttp.request("GET", path`/store/api-clients/${clientId}`, apiClientSchema),
|
|
480
|
-
/** confidential 전용. 기존 시크릿은 즉시 무효(유예 없음) */
|
|
481
|
-
rotateSecret: (clientId: string) =>
|
|
482
|
-
storeHttp.request(
|
|
483
|
-
"POST",
|
|
484
|
-
path`/store/api-clients/${clientId}/rotate-secret`,
|
|
485
|
-
apiClientSecretRotatedSchema,
|
|
486
|
-
),
|
|
487
|
-
/** REVOKED로 전환(되돌릴 수 없음). 이미 비활성이면 현재 상태를 그대로 돌려준다 */
|
|
488
|
-
deactivate: (clientId: string) =>
|
|
489
|
-
storeHttp.request("POST", path`/store/api-clients/${clientId}/deactivate`, apiClientSchema),
|
|
490
|
-
},
|
|
491
|
-
|
|
492
378
|
/**
|
|
493
379
|
* 미디어 업로드 — 자리를 받아(`POST /uploads`) 파일 바이트는 호출자가
|
|
494
380
|
* `uploadUrl`로 직접 PUT한다. 업로드가 끝나면 `url`이 공개 주소가 된다.
|
|
@@ -605,7 +491,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
605
491
|
}),
|
|
606
492
|
get: (optionSetId: string) =>
|
|
607
493
|
storeHttp.request("GET", path`/saved-option-sets/${optionSetId}`, savedOptionSetSchema),
|
|
608
|
-
/** 이름이 겹치면 409 `OPTION_SET_NAME_TAKEN`,
|
|
494
|
+
/** 이름이 겹치면 409 `OPTION_SET_NAME_TAKEN`, 상점당 200개를 넘으면 409 `OPTION_SET_LIMIT_EXCEEDED` */
|
|
609
495
|
create: (body: SavedOptionSetRequestInput) =>
|
|
610
496
|
storeHttp.request("POST", "/saved-option-sets", savedOptionSetSchema, {
|
|
611
497
|
body: savedOptionSetRequestSchema.parse(body),
|
|
@@ -675,7 +561,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
675
561
|
get: (orderId: string) => storeHttp.request("GET", path`/orders/${orderId}`, orderSchema),
|
|
676
562
|
/**
|
|
677
563
|
* `POST /v1/orders/{orderId}/cash-receipt/reissue` — 실패했거나 재시도 대기 중인 현금영수증을 지금 다시 발급·취소한다.
|
|
678
|
-
*
|
|
564
|
+
* 상점 플랫폼 OWNER·ADMIN 전용(PAT·M2M 403 `INSUFFICIENT_ROLE`). 404 `CASH_RECEIPT_NOT_FOUND`, 409 `CASH_RECEIPT_NOT_REISSUABLE`
|
|
679
565
|
*/
|
|
680
566
|
reissueCashReceipt: (orderId: string) =>
|
|
681
567
|
storeHttp.request("POST", path`/orders/${orderId}/cash-receipt/reissue`, cashReceiptSchema),
|
|
@@ -738,7 +624,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
738
624
|
}),
|
|
739
625
|
/**
|
|
740
626
|
* 배송완료 처리 — `DISPATCHED`·`DELIVERING`만 넘어간다. 응답의 `autoDecisionDate`가 자동 구매확정 예정일이다
|
|
741
|
-
* (
|
|
627
|
+
* (상점 설정 `autoConfirmDays`가 0이면 null).
|
|
742
628
|
*/
|
|
743
629
|
markDelivered: (orderItemId: string, idempotencyKey?: string) =>
|
|
744
630
|
storeHttp.request("POST", path`/order-items/${orderItemId}/delivered`, orderItemSchema, {
|
|
@@ -827,7 +713,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
827
713
|
},
|
|
828
714
|
|
|
829
715
|
/**
|
|
830
|
-
* 구매자 로그인 설정 — 이메일·비밀번호 허용, 소셜 공급자 연결. 비밀 값을 다루므로
|
|
716
|
+
* 구매자 로그인 설정 — 이메일·비밀번호 허용, 소셜 공급자 연결. 비밀 값을 다루므로 상점 플랫폼에 로그인한 OWNER만 호출할 수 있다
|
|
831
717
|
*/
|
|
832
718
|
loginSettings: {
|
|
833
719
|
get: () => storeHttp.request("GET", "/store/login-settings", loginSettingsSchema),
|
|
@@ -855,7 +741,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
855
741
|
storeHttp.request("DELETE", path`/store/auth-providers/${provider}`, loginSettingsSchema),
|
|
856
742
|
},
|
|
857
743
|
|
|
858
|
-
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(
|
|
744
|
+
/** 구매자 관리 — 조회 `customer:r`, 탈퇴 처리 `customer:rw`(상점 플랫폼 OWNER·ADMIN) */
|
|
859
745
|
customers: {
|
|
860
746
|
list: (
|
|
861
747
|
params?: PageParams & Omit<z.input<typeof customerListQuerySchema>, "page" | "size">,
|
|
@@ -1069,9 +955,9 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1069
955
|
},
|
|
1070
956
|
|
|
1071
957
|
/**
|
|
1072
|
-
* 결제(PG) 설정 —
|
|
1073
|
-
* 403 `INSUFFICIENT_ROLE`이다(조회 포함). OWNER 여부는 요청 시점의
|
|
1074
|
-
* 양도·강등
|
|
958
|
+
* 결제(PG) 설정 — **상점 플랫폼 OWNER 전용**. ADMIN·STAFF·PAT·M2M·3rd-party 앱 토큰은 스코프가 있어도
|
|
959
|
+
* 403 `INSUFFICIENT_ROLE`이다(조회 포함). OWNER 여부는 요청 시점의 상점 멤버십으로 다시 확인하므로
|
|
960
|
+
* 양도·강등 직후에도 바로 403이다. 비밀 값(시크릿 키·API Secret·웹훅 시크릿)은 요청으로만 보내고
|
|
1075
961
|
* 응답에는 `secrets[].masked`(앞 4자리 마스킹)만 나온다.
|
|
1076
962
|
*/
|
|
1077
963
|
paymentSettings: {
|
|
@@ -1241,7 +1127,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1241
1127
|
}),
|
|
1242
1128
|
},
|
|
1243
1129
|
|
|
1244
|
-
/** API 로그 (보관 7일,
|
|
1130
|
+
/** API 로그 (보관 7일, 상점 플랫폼 OWNER/ADMIN) */
|
|
1245
1131
|
apiLogs: {
|
|
1246
1132
|
/** 최신순 커서 페이지네이션. 다음 페이지는 응답의 `nextCursor`를 `cursor`로 넘긴다 */
|
|
1247
1133
|
list: (params?: ApiLogListOptions) =>
|
|
@@ -1257,7 +1143,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1257
1143
|
summary: () => storeHttp.request("GET", "/api-logs/summary", apiLogSummarySchema),
|
|
1258
1144
|
},
|
|
1259
1145
|
|
|
1260
|
-
/** 감사 로그 (관리 API 쓰기의 행위자·대상·결과,
|
|
1146
|
+
/** 감사 로그 (관리 API 쓰기의 행위자·대상·결과, 상점 플랫폼 OWNER/ADMIN, `audit:r`) */
|
|
1261
1147
|
auditLogs: {
|
|
1262
1148
|
/** 최신순 커서 페이지네이션. 다음 페이지는 응답의 `nextCursor`를 `cursor`로 넘긴다 */
|
|
1263
1149
|
list: (params?: AuditLogListOptions) =>
|
|
@@ -1272,7 +1158,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1272
1158
|
},
|
|
1273
1159
|
|
|
1274
1160
|
/**
|
|
1275
|
-
* 승인 요청 — 에이전트의 고위험 쓰기가 202로 보류된 건. 목록·승인·거절은
|
|
1161
|
+
* 승인 요청 — 에이전트의 고위험 쓰기가 202로 보류된 건. 목록·승인·거절은 상점 플랫폼 OWNER/ADMIN,
|
|
1276
1162
|
* 단건 조회는 요청한 토큰도 할 수 있다(상태 확인용).
|
|
1277
1163
|
*/
|
|
1278
1164
|
approvalRequests: {
|
|
@@ -1361,7 +1247,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1361
1247
|
),
|
|
1362
1248
|
/**
|
|
1363
1249
|
* `POST /v1/storefront/site` (201) — 템플릿으로 사이트를 만들고 v1을 발행한다. 공개 상태는 오픈 준비 중이다.
|
|
1364
|
-
* 사이트를 만드는 유일한 경로다(
|
|
1250
|
+
* 사이트를 만드는 유일한 경로다(상점을 만들어도 사이트는 생기지 않는다). 템플릿은 여기서만 고른다
|
|
1365
1251
|
*/
|
|
1366
1252
|
create: (body: CreateStorefrontSiteRequest) =>
|
|
1367
1253
|
storeHttp.request("POST", "/storefront/site", storefrontSiteSchema, {
|
|
@@ -1412,7 +1298,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1412
1298
|
}),
|
|
1413
1299
|
/**
|
|
1414
1300
|
* `POST /v1/storefront/site/preview-link` — 오픈 준비 중인 사이트를 구성원이 볼 수 있는 5분짜리 링크.
|
|
1415
|
-
*
|
|
1301
|
+
* 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다
|
|
1416
1302
|
*/
|
|
1417
1303
|
createPreviewLink: (body: CreateStorefrontPreviewLinkRequest = {}) =>
|
|
1418
1304
|
storeHttp.request("POST", "/storefront/site/preview-link", storefrontPreviewLinkSchema, {
|
|
@@ -1470,7 +1356,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1470
1356
|
storeHttp.request("DELETE", "/storefront/site/editing", storefrontEditingSchema),
|
|
1471
1357
|
/**
|
|
1472
1358
|
* `POST /v1/storefront/site/editing/preview-link` — 편집 미리보기(dev 서버) 5분짜리 링크.
|
|
1473
|
-
*
|
|
1359
|
+
* 상점 플랫폼에서 로그인한 OWNER·ADMIN만 받는다(에이전트 불가)
|
|
1474
1360
|
*/
|
|
1475
1361
|
createEditPreviewLink: (body: CreateStorefrontEditPreviewLinkRequest = {}) =>
|
|
1476
1362
|
storeHttp.request(
|
|
@@ -1583,6 +1469,22 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1583
1469
|
}),
|
|
1584
1470
|
},
|
|
1585
1471
|
|
|
1472
|
+
subscription: {
|
|
1473
|
+
/**
|
|
1474
|
+
* `GET /v1/store/subscription` — 스코프 `store:r`. 현재 요금제·상태·사용량 기간·한도·이번 기간 사용량.
|
|
1475
|
+
* 결제 수단·청구 금액은 싣지 않는다. 요금제 변경은 상점 플랫폼에서만 한다
|
|
1476
|
+
*/
|
|
1477
|
+
get: () => storeHttp.request("GET", "/store/subscription", storeSubscriptionSchema),
|
|
1478
|
+
},
|
|
1479
|
+
|
|
1480
|
+
plans: {
|
|
1481
|
+
/**
|
|
1482
|
+
* `GET /v1/public/plans` — 인증 없음. 공개 요금제의 지금 버전(가격·기능 키 값). 값은 서버가 관리하고 언제든 바뀔 수 있어
|
|
1483
|
+
* 코드에 요금제 수치를 두지 말고 이 값을 읽는다
|
|
1484
|
+
*/
|
|
1485
|
+
listPublic: () => storeHttp.request("GET", "/public/plans", publicPlansSchema),
|
|
1486
|
+
},
|
|
1487
|
+
|
|
1586
1488
|
store: {
|
|
1587
1489
|
get: () => storeHttp.request("GET", "/store", storeSchema),
|
|
1588
1490
|
/**
|
|
@@ -1608,7 +1510,7 @@ export function createStoreClient(options: StoreClientOptions) {
|
|
|
1608
1510
|
}),
|
|
1609
1511
|
/**
|
|
1610
1512
|
* `POST /v1/store/invitations` (201) — 스코프 `member:rw`. 초대 메일을 보낸다. 발송 결과는 응답의
|
|
1611
|
-
* `deliveryStatus`다(발송이 실패해도 초대는 만들어진다).
|
|
1513
|
+
* `deliveryStatus`다(발송이 실패해도 초대는 만들어진다). 상점·초대한 사람·받는 주소별 발송 한도가 있다(429, `details.limit`).
|
|
1612
1514
|
*/
|
|
1613
1515
|
invite: (body: InviteMemberRequest) =>
|
|
1614
1516
|
storeHttp.request("POST", "/store/invitations", invitationSchema, {
|
package/src/http.ts
CHANGED
|
@@ -74,7 +74,7 @@ export const APPROVAL_ALREADY_DECIDED_CODE = "APPROVAL_ALREADY_DECIDED";
|
|
|
74
74
|
* 성공 값으로 돌려주지 않는다 — 돌려주면 호출부가 승인 요청을 상품·주문으로 읽는다.
|
|
75
75
|
*
|
|
76
76
|
* - `code = APPROVAL_REQUIRED`(202): 에이전트의 고위험 쓰기가 보류됐다. 아직 아무것도 바뀌지 않았다.
|
|
77
|
-
* 셀러가
|
|
77
|
+
* 셀러가 상점 플랫폼(`approval.approvalUrl`)에서 승인하면 서버가 원 요청을 실행한다. 같은 요청을 다시 보내지 말고
|
|
78
78
|
* `approvalRequests.get(approvalId)`로 결과를 확인한다.
|
|
79
79
|
* - `code = APPROVAL_ALREADY_DECIDED`(200): 같은 멱등키로 보낸 요청의 승인이 이미 끝났다. `status`가
|
|
80
80
|
* `SUCCEEDED`·`FAILED`·`UNKNOWN`이면 실행 결과(`approval.result`)를 보고, `REJECTED`·`EXPIRED`면 새 멱등키로 다시 요청한다.
|
package/src/index.ts
CHANGED
|
@@ -12,13 +12,11 @@ export {
|
|
|
12
12
|
buildAuthorizeUrl,
|
|
13
13
|
createPkcePair,
|
|
14
14
|
exchangeAuthorizationCode,
|
|
15
|
-
exchangeStoreTokenViaOAuth,
|
|
16
15
|
type OAuthTokenResponse,
|
|
17
16
|
oauthTokenResponseSchema,
|
|
18
17
|
refreshUserToken,
|
|
19
18
|
} from "./oauth";
|
|
20
19
|
export * from "./schemas/analytics";
|
|
21
|
-
export * from "./schemas/api-client";
|
|
22
20
|
export * from "./schemas/api-log";
|
|
23
21
|
export * from "./schemas/audit";
|
|
24
22
|
export * from "./schemas/auth";
|
|
@@ -27,6 +25,7 @@ export * from "./schemas/claim";
|
|
|
27
25
|
export * from "./schemas/common";
|
|
28
26
|
export * from "./schemas/coupon";
|
|
29
27
|
export * from "./schemas/customer";
|
|
28
|
+
export * from "./schemas/customer-delegation";
|
|
30
29
|
export * from "./schemas/fulfillment";
|
|
31
30
|
export * from "./schemas/login";
|
|
32
31
|
export * from "./schemas/misc";
|
|
@@ -40,6 +39,7 @@ export * from "./schemas/scope";
|
|
|
40
39
|
export * from "./schemas/shipping-policy";
|
|
41
40
|
export * from "./schemas/store";
|
|
42
41
|
export * from "./schemas/storefront-hosting";
|
|
42
|
+
export * from "./schemas/subscription";
|
|
43
43
|
export * from "./schemas/telemetry";
|
|
44
44
|
export * from "./schemas/upload";
|
|
45
45
|
export * from "./schemas/webhook-event";
|
|
@@ -48,6 +48,7 @@ export {
|
|
|
48
48
|
createStoreSession,
|
|
49
49
|
type SessionStorage,
|
|
50
50
|
STORE_SESSION_KEYS,
|
|
51
|
+
type StoreAuthServer,
|
|
51
52
|
type StoreSession,
|
|
52
53
|
type StoreSessionOptions,
|
|
53
54
|
} from "./session";
|