feeef 0.8.3 → 0.8.5

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/README.md CHANGED
@@ -213,3 +213,76 @@ interface ProductOffer {
213
213
  ## Contributing
214
214
 
215
215
  Feel free to fork this repository and contribute improvements, bug fixes, or additional features. Open a pull request for any major changes!
216
+
217
+ ## Developer OAuth (Authorization Code)
218
+
219
+ Use this flow when a third-party app (created in `apps`) needs user authorization.
220
+
221
+ ### Endpoints
222
+
223
+ - Authorize: `GET /v1/oauth/authorize`
224
+ - Token: `POST /v1/oauth/token`
225
+ - Revoke: `POST /v1/oauth/revoke`
226
+ - Introspect: `POST /v1/oauth/introspect`
227
+
228
+ ### Step-by-step (Google-like)
229
+
230
+ 1. Build authorize URL:
231
+
232
+ ```ts
233
+ import { FeeeF, OAuthRepository } from 'feeef'
234
+
235
+ const feeef = new FeeeF({
236
+ apiKey: '<your_api_key>',
237
+ baseURL: 'https://api.feeef.org/v1',
238
+ })
239
+
240
+ const authorizeUrl = OAuthRepository.buildAuthorizeUrl({
241
+ baseUrl: 'https://api.feeef.org/v1',
242
+ clientId: '<client_id>',
243
+ redirectUri: 'https://your-app.com/oauth/callback',
244
+ scope: ['*'], // or explicit scopes
245
+ state: crypto.randomUUID(),
246
+ })
247
+ ```
248
+
249
+ 2. Open `authorizeUrl` in browser.
250
+ 3. If user is not signed in, API may return `401` with:
251
+ - `error: "login_required"`
252
+ - `login_url` (accounts sign-in URL with `next`)
253
+ 4. Redirect user to `login_url`.
254
+ 5. After sign-in + authorization, user is redirected to your callback with `code` (and `state`).
255
+ 6. Exchange `code` for token:
256
+
257
+ ```ts
258
+ const tokenResponse = await feeef.oauth.exchangeAuthorizationCode({
259
+ code: '<code_from_callback>',
260
+ redirectUri: 'https://your-app.com/oauth/callback',
261
+ clientId: '<client_id>',
262
+ clientSecret: '<client_secret>',
263
+ })
264
+
265
+ await feeef.oauth.revokeToken(tokenResponse.access_token)
266
+
267
+ const introspection = await feeef.oauth.introspectToken(tokenResponse.access_token)
268
+ console.log(introspection.active)
269
+ ```
270
+
271
+ Alternative static helper:
272
+
273
+ ```ts
274
+ import { OAuthRepository } from 'feeef'
275
+
276
+ const authorizeUrl = OAuthRepository.buildAuthorizeUrl({
277
+ baseUrl: 'https://api.feeef.org/v1',
278
+ clientId: '<client_id>',
279
+ redirectUri: 'https://your-app.com/oauth/callback',
280
+ })
281
+ ```
282
+
283
+ ### Important
284
+
285
+ - `redirect_uri` must exactly match one of the app's registered redirect URIs.
286
+ - Keep `client_secret` server-side only.
287
+ - Always validate `state` on callback.
288
+ - Prefer PKCE (`code_challenge`, `code_verifier`) for public clients.
package/build/index.js CHANGED
@@ -141,7 +141,12 @@ var OrderRepository = class extends ModelRepository {
141
141
  if (options.page) params.page = options.page;
142
142
  if (options.offset) params.offset = options.offset;
143
143
  if (options.limit) params.limit = options.limit;
144
- if (options.storeId) params.store_id = options.storeId;
144
+ const useMultiStore = options.storeIds != null && options.storeIds.length > 0;
145
+ if (useMultiStore) {
146
+ params.store_ids = options.storeIds;
147
+ } else if (options.storeId) {
148
+ params.store_id = options.storeId;
149
+ }
145
150
  if (options.status) {
146
151
  params.status = Array.isArray(options.status) ? options.status : [options.status];
147
152
  }
@@ -318,6 +323,65 @@ var ProductRepository = class extends ModelRepository {
318
323
  }
319
324
  };
320
325
 
326
+ // src/feeef/repositories/store_invites_repository.ts
327
+ var StoreInvitesRepository = class {
328
+ constructor(client, resource) {
329
+ this.client = client;
330
+ this.resource = resource;
331
+ }
332
+ /**
333
+ * Lists invites for a store.
334
+ * @param storeId - The store ID.
335
+ * @param params - Optional filters (e.g. status).
336
+ * @returns A Promise that resolves to the list of invites.
337
+ */
338
+ async list(storeId, params) {
339
+ const res = await this.client.get(`/${this.resource}/${storeId}/invites`, { params });
340
+ return res.data;
341
+ }
342
+ /**
343
+ * Creates a store invite (sends email to invitee).
344
+ * @param storeId - The store ID.
345
+ * @param data - The invite data.
346
+ * @returns A Promise that resolves to the created invite.
347
+ */
348
+ async create(storeId, data) {
349
+ const res = await this.client.post(`/${this.resource}/${storeId}/invites`, data);
350
+ return res.data;
351
+ }
352
+ /**
353
+ * Gets invite details (public or full if authorized).
354
+ * @param storeId - The store ID.
355
+ * @param inviteId - The invite ID.
356
+ * @returns A Promise that resolves to the invite.
357
+ */
358
+ async get(storeId, inviteId) {
359
+ const res = await this.client.get(`/${this.resource}/${storeId}/invites/${inviteId}`);
360
+ return res.data;
361
+ }
362
+ /**
363
+ * Revokes a pending invite.
364
+ * @param storeId - The store ID.
365
+ * @param inviteId - The invite ID.
366
+ */
367
+ async revoke(storeId, inviteId) {
368
+ await this.client.delete(`/${this.resource}/${storeId}/invites/${inviteId}`);
369
+ }
370
+ /**
371
+ * Accepts an invite (authenticated user's email must match invite email).
372
+ * @param storeId - The store ID.
373
+ * @param inviteId - The invite ID.
374
+ * @param token - The invite token from the email link.
375
+ * @returns A Promise that resolves to the created store member.
376
+ */
377
+ async accept(storeId, inviteId, token) {
378
+ const res = await this.client.post(`/${this.resource}/${storeId}/invites/${inviteId}/accept`, {
379
+ token
380
+ });
381
+ return res.data;
382
+ }
383
+ };
384
+
321
385
  // src/feeef/repositories/stores.ts
322
386
  var StoreRepository = class extends ModelRepository {
323
387
  /**
@@ -410,67 +474,24 @@ var StoreRepository = class extends ModelRepository {
410
474
  await this.client.delete(`/${this.resource}/${storeId}/members/${memberId}`);
411
475
  }
412
476
  /**
413
- * Creates a store invite (sends email to invitee).
414
- * @param storeId - The store ID.
415
- * @param data - The invite data.
416
- * @returns A Promise that resolves to the created invite.
417
- */
418
- async createInvite(storeId, data) {
419
- const res = await this.client.post(`/${this.resource}/${storeId}/invites`, data);
420
- return res.data;
421
- }
422
- /**
423
- * Lists invites for a store.
424
- * @param storeId - The store ID.
425
- * @param params - Optional filters (e.g. status).
426
- * @returns A Promise that resolves to the list of invites.
427
- */
428
- async listInvites(storeId, params) {
429
- const res = await this.client.get(`/${this.resource}/${storeId}/invites`, { params });
430
- return res.data;
431
- }
432
- /**
433
- * Revokes a pending invite.
434
- * @param storeId - The store ID.
435
- * @param inviteId - The invite ID.
436
- */
437
- async revokeInvite(storeId, inviteId) {
438
- await this.client.delete(`/${this.resource}/${storeId}/invites/${inviteId}`);
439
- }
440
- /**
441
- * Gets invite details (public or full if authorized).
442
- * @param storeId - The store ID.
443
- * @param inviteId - The invite ID.
444
- * @returns A Promise that resolves to the invite.
477
+ * Repository for store invites. Use e.g. `ff.stores.invites.list(storeId)`, `ff.stores.invites.create(storeId, data)`.
445
478
  */
446
- async getInvite(storeId, inviteId) {
447
- const res = await this.client.get(`/${this.resource}/${storeId}/invites/${inviteId}`);
448
- return res.data;
449
- }
450
- /**
451
- * Accepts an invite (authenticated user's email must match invite email).
452
- * @param storeId - The store ID.
453
- * @param inviteId - The invite ID.
454
- * @param token - The invite token from the email link.
455
- * @returns A Promise that resolves to the created store member.
456
- */
457
- async acceptInvite(storeId, inviteId, token) {
458
- const res = await this.client.post(`/${this.resource}/${storeId}/invites/${inviteId}/accept`, {
459
- token
460
- });
461
- return res.data;
479
+ get invites() {
480
+ return new StoreInvitesRepository(this.client, this.resource);
462
481
  }
463
482
  /**
464
483
  * Upgrades or renews a store's subscription plan.
465
484
  * @param id - The store ID.
466
485
  * @param plan - The plan type to upgrade to.
467
486
  * @param months - The number of months (1-12).
468
- * @returns A Promise that resolves when the upgrade is complete.
487
+ * @param code - Optional promo code.
469
488
  */
470
- async upgrade(id, plan, months) {
489
+ async upgrade(id, plan, months, options) {
471
490
  await this.client.post(`/${this.resource}/${id}/subscription/upgrade`, {
472
491
  plan,
473
- months
492
+ months,
493
+ // eslint-disable-next-line eqeqeq
494
+ ...options?.code != null && options.code !== "" && { code: options.code }
474
495
  });
475
496
  }
476
497
  /**
@@ -796,6 +817,22 @@ var AppRepository = class extends ModelRepository {
796
817
  constructor(client) {
797
818
  super("apps", client);
798
819
  }
820
+ /**
821
+ * Lists apps with optional pagination and filterator.
822
+ * @param options - Page, limit, filterator, q, and extra params forwarded to the API.
823
+ */
824
+ async list(options) {
825
+ const params = { ...options?.params };
826
+ if (options) {
827
+ if (options.page !== void 0) params.page = options.page;
828
+ if (options.limit !== void 0) params.limit = options.limit;
829
+ if (options.q !== void 0) params.q = options.q;
830
+ if (options.filterator !== void 0) params.filterator = options.filterator;
831
+ if (options.userId !== void 0) params.userId = options.userId;
832
+ if (options.active !== void 0) params.active = options.active;
833
+ }
834
+ return super.list({ page: options?.page, limit: options?.limit, params });
835
+ }
799
836
  /**
800
837
  * Regenerates the client secret for the app. Returns the app with
801
838
  * clientSecret set once; store it securely.
@@ -809,6 +846,14 @@ var AppRepository = class extends ModelRepository {
809
846
  }
810
847
  /**
811
848
  * Builds the OAuth authorize URL to which the user should be redirected.
849
+ * This is the first step of the authorization-code flow (similar UX to Google OAuth).
850
+ *
851
+ * If the user is not logged in yet, API `GET /oauth/authorize` returns:
852
+ * - `401 login_required`
853
+ * - `login_url` (accounts sign-in URL with `next=...`)
854
+ *
855
+ * The client should navigate to `login_url`, let the user sign in, and then
856
+ * continue by opening the original authorize URL again (or rely on `next`).
812
857
  *
813
858
  * @param params - Parameters for the authorize URL.
814
859
  * @param params.baseUrl - API base URL (e.g. https://api.feeef.org/api/v1).
@@ -837,6 +882,79 @@ var AppRepository = class extends ModelRepository {
837
882
  }
838
883
  };
839
884
 
885
+ // src/feeef/repositories/oauth.ts
886
+ var OAuthRepository = class {
887
+ client;
888
+ constructor(client) {
889
+ this.client = client;
890
+ }
891
+ /**
892
+ * Builds the authorize URL for browser redirect.
893
+ */
894
+ static buildAuthorizeUrl(params) {
895
+ const base = params.baseUrl.endsWith("/") ? params.baseUrl : `${params.baseUrl}/`;
896
+ const url = new URL("oauth/authorize", base);
897
+ url.searchParams.set("client_id", params.clientId);
898
+ url.searchParams.set("redirect_uri", params.redirectUri);
899
+ url.searchParams.set("response_type", "code");
900
+ if (params.scope?.length) url.searchParams.set("scope", params.scope.join(" "));
901
+ if (params.state) url.searchParams.set("state", params.state);
902
+ if (params.codeChallenge) url.searchParams.set("code_challenge", params.codeChallenge);
903
+ if (params.codeChallengeMethod) {
904
+ url.searchParams.set("code_challenge_method", params.codeChallengeMethod);
905
+ }
906
+ return url.toString();
907
+ }
908
+ /**
909
+ * Exchanges an authorization code for an access token.
910
+ */
911
+ async exchangeAuthorizationCode(params) {
912
+ const body = new URLSearchParams({
913
+ grant_type: "authorization_code",
914
+ code: params.code,
915
+ redirect_uri: params.redirectUri,
916
+ client_id: params.clientId,
917
+ client_secret: params.clientSecret
918
+ });
919
+ if (params.codeVerifier) {
920
+ body.set("code_verifier", params.codeVerifier);
921
+ }
922
+ const response = await this.client.post("/oauth/token", body.toString(), {
923
+ headers: {
924
+ "Content-Type": "application/x-www-form-urlencoded"
925
+ }
926
+ });
927
+ return response.data;
928
+ }
929
+ /**
930
+ * Revokes an OAuth token.
931
+ */
932
+ async revokeToken(token, tokenTypeHint) {
933
+ const body = new URLSearchParams({ token });
934
+ if (tokenTypeHint) {
935
+ body.set("token_type_hint", tokenTypeHint);
936
+ }
937
+ const response = await this.client.post("/oauth/revoke", body.toString(), {
938
+ headers: {
939
+ "Content-Type": "application/x-www-form-urlencoded"
940
+ }
941
+ });
942
+ return response.data;
943
+ }
944
+ /**
945
+ * Introspects an OAuth token.
946
+ */
947
+ async introspectToken(token) {
948
+ const body = new URLSearchParams({ token });
949
+ const response = await this.client.post("/oauth/introspect", body.toString(), {
950
+ headers: {
951
+ "Content-Type": "application/x-www-form-urlencoded"
952
+ }
953
+ });
954
+ return response.data;
955
+ }
956
+ };
957
+
840
958
  // src/feeef/repositories/deposits.ts
841
959
  var DepositRepository = class extends ModelRepository {
842
960
  /**
@@ -972,6 +1090,49 @@ var DepositRepository = class extends ModelRepository {
972
1090
  }
973
1091
  };
974
1092
 
1093
+ // src/feeef/repositories/promos.ts
1094
+ var PromoRepository = class {
1095
+ constructor(client) {
1096
+ this.client = client;
1097
+ }
1098
+ /**
1099
+ * Lists promos with optional pagination and validNow filter.
1100
+ */
1101
+ async list(params) {
1102
+ const query = {};
1103
+ if (params?.page != null) query.page = params.page;
1104
+ if (params?.limit != null) query.limit = params.limit;
1105
+ if (params?.validNow === true) query.validNow = "1";
1106
+ if (params?.filterator) query.filterator = params.filterator;
1107
+ const res = await this.client.get("/promos", { params: query });
1108
+ return res.data;
1109
+ }
1110
+ /**
1111
+ * Fetches a single promo by id.
1112
+ */
1113
+ async find(params) {
1114
+ const res = await this.client.get(`/promos/${params.id}`);
1115
+ return res.data;
1116
+ }
1117
+ /**
1118
+ * Validates a promo code. Returns validation result with discount info or reason.
1119
+ */
1120
+ async validate(params) {
1121
+ const res = await this.client.post("/promos/validate", {
1122
+ code: params.code,
1123
+ storeId: params.storeId
1124
+ });
1125
+ return res.data;
1126
+ }
1127
+ /**
1128
+ * Creates a promo (admin). Returns the created promo.
1129
+ */
1130
+ async create(data) {
1131
+ const res = await this.client.post("/promos", data);
1132
+ return res.data;
1133
+ }
1134
+ };
1135
+
975
1136
  // src/feeef/repositories/transfers.ts
976
1137
  var TransferRepository = class extends ModelRepository {
977
1138
  /**
@@ -3446,6 +3607,10 @@ var FeeeF = class {
3446
3607
  * The repository for managing developer-registered apps (OAuth clients).
3447
3608
  */
3448
3609
  apps;
3610
+ /**
3611
+ * The repository for OAuth2 authorize/token/revoke/introspect operations.
3612
+ */
3613
+ oauth;
3449
3614
  /**
3450
3615
  * The repository for managing orders.
3451
3616
  */
@@ -3458,6 +3623,10 @@ var FeeeF = class {
3458
3623
  * The repository for managing transfers.
3459
3624
  */
3460
3625
  transfers;
3626
+ /**
3627
+ * The repository for managing promo codes (list, validate, create).
3628
+ */
3629
+ promos;
3461
3630
  /**
3462
3631
  * The repository for managing categories.
3463
3632
  */
@@ -3533,9 +3702,11 @@ var FeeeF = class {
3533
3702
  this.imageGenerations = new ImageGenerationsRepository(this.client);
3534
3703
  this.users = new UserRepository(this.client);
3535
3704
  this.apps = new AppRepository(this.client);
3705
+ this.oauth = new OAuthRepository(this.client);
3536
3706
  this.orders = new OrderRepository(this.client);
3537
3707
  this.deposits = new DepositRepository(this.client);
3538
3708
  this.transfers = new TransferRepository(this.client);
3709
+ this.promos = new PromoRepository(this.client);
3539
3710
  this.categories = new CategoryRepository(this.client);
3540
3711
  this.countries = new CountryRepository(this.client);
3541
3712
  this.states = new StateRepository(this.client);
@@ -3995,6 +4166,7 @@ export {
3995
4166
  ModelRepository,
3996
4167
  NoestDeliveryIntegrationApi,
3997
4168
  NotificationsService,
4169
+ OAuthRepository,
3998
4170
  OrderRepository,
3999
4171
  OrderStatus,
4000
4172
  PaymentStatus,
@@ -4004,6 +4176,7 @@ export {
4004
4176
  ProductStatus,
4005
4177
  ProductType,
4006
4178
  ProductVariantView,
4179
+ PromoRepository,
4007
4180
  ShippingMethodPolicy,
4008
4181
  ShippingMethodRepository,
4009
4182
  ShippingMethodStatus,
@@ -4014,6 +4187,7 @@ export {
4014
4187
  StorageService,
4015
4188
  StoreActionType,
4016
4189
  StoreInviteStatus,
4190
+ StoreInvitesRepository,
4017
4191
  StoreMemberRole,
4018
4192
  StoreRepository,
4019
4193
  StoreSubscriptionStatus,