feeef 0.8.11 → 0.9.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/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  `feeefjs` is a TypeScript library for managing feeef e-commerce platforms for self-hosted stores. It provides a wrapper for feeef rest api such like send order..., also have frontend srvices like the `CartService` class for managing cart items, shipping methods, and calculating totals. The library also includes a `NotifiableService` base class for handling listeners that react to changes in the service state.
4
4
 
5
+ ## Delivery parcel types
6
+
7
+ Canonical shipment shapes mirror the API domain in `feeefapps/backend/services/delivery/domain/parcel.ts`. In this package they live under `src/delivery/parcel.ts` and are exported from the main entry (`ParcelCreate`, `ParcelUpdate`, `DeliveryCarrierClient`, …). Keep them in sync when the backend parcel model changes; published npm `feeef` should re-export the same definitions after each release.
8
+
5
9
  ## Developer OAuth (third-party apps)
6
10
 
7
11
  Use `OAuthRepository.buildAuthorizeUrl` (point `baseUrl` at the **accounts** host, e.g. `https://accounts.feeef.org`) and `FeeeF` client `oauth.exchangeAuthorizationCode` / `revokeToken` / `introspectToken` against the **API** base (`.../v1`). Full flow, scopes, and troubleshooting: Feeef backend **`docs/OAUTH2_DEVELOPER.md`** (in the Adonis API repo).
package/build/index.js CHANGED
@@ -167,6 +167,7 @@ var OrderRepository = class extends ModelRepository {
167
167
  if (options.shippingState) params.shippingState = options.shippingState;
168
168
  if (options.shippingCity) params.shippingCity = options.shippingCity;
169
169
  if (options.deliveryService) params.deliveryService = options.deliveryService;
170
+ if (options.references !== void 0) params.references = options.references;
170
171
  }
171
172
  return super.list({ params });
172
173
  }
@@ -321,6 +322,15 @@ var ProductRepository = class extends ModelRepository {
321
322
  const res = await this.client.get(`/stores/${storeId}/${this.resource}/${productId}/report`);
322
323
  return res.data;
323
324
  }
325
+ /**
326
+ * Lite orders report for a product in a store.
327
+ */
328
+ async liteOrdersReport(storeId, productId) {
329
+ const res = await this.client.get(
330
+ `/stores/${storeId}/${this.resource}/${productId}/analytics/lor`
331
+ );
332
+ return res.data;
333
+ }
324
334
  };
325
335
 
326
336
  // src/feeef/repositories/store_invites_repository.ts
@@ -329,6 +339,8 @@ var StoreInvitesRepository = class {
329
339
  this.client = client;
330
340
  this.resource = resource;
331
341
  }
342
+ client;
343
+ resource;
332
344
  /**
333
345
  * Lists invites for a store.
334
346
  * @param storeId - The store ID.
@@ -434,6 +446,13 @@ var StoreRepository = class extends ModelRepository {
434
446
  }
435
447
  return chartData;
436
448
  }
449
+ /**
450
+ * Lite orders report for the store (8 UTC days + total).
451
+ */
452
+ async liteOrdersReport(storeId) {
453
+ const res = await this.client.get(`/${this.resource}/${storeId}/analytics/lor`);
454
+ return res.data;
455
+ }
437
456
  /**
438
457
  * Adds a member to the store.
439
458
  * @param storeId - The store ID.
@@ -1160,6 +1179,7 @@ var PromoRepository = class {
1160
1179
  constructor(client) {
1161
1180
  this.client = client;
1162
1181
  }
1182
+ client;
1163
1183
  /**
1164
1184
  * Lists promos with optional pagination and validNow filter.
1165
1185
  */
@@ -1815,6 +1835,15 @@ var ProductLandingPagesRepository = class extends ModelRepository {
1815
1835
  const params = { ...options?.params };
1816
1836
  return super.list({ params });
1817
1837
  }
1838
+ /**
1839
+ * Lite orders report for a product landing page in a store.
1840
+ */
1841
+ async liteOrdersReport(storeId, landingPageId) {
1842
+ const res = await this.client.get(
1843
+ `/stores/${storeId}/product_landing_pages/${landingPageId}/analytics/lor`
1844
+ );
1845
+ return res.data;
1846
+ }
1818
1847
  };
1819
1848
 
1820
1849
  // src/core/entities/attachment.ts
@@ -2252,10 +2281,14 @@ var ShippingPriceStatus = /* @__PURE__ */ ((ShippingPriceStatus2) => {
2252
2281
  return ShippingPriceStatus2;
2253
2282
  })(ShippingPriceStatus || {});
2254
2283
  function getShippingPrice(prices, countryCode, stateCode, type) {
2284
+ console.log("0 [getShippingPrice]", { prices, countryCode, stateCode, type });
2255
2285
  const countryRates = prices[countryCode];
2286
+ console.log("1 [getShippingPrice] countryRates", countryRates);
2256
2287
  if (!countryRates) return null;
2257
2288
  const stateRates = countryRates[stateCode];
2289
+ console.log("2 [getShippingPrice] stateRates", stateRates);
2258
2290
  if (!stateRates) return null;
2291
+ console.log("3 [getShippingPrice] stateRates[type]", stateRates[type]);
2259
2292
  return stateRates[type] ?? null;
2260
2293
  }
2261
2294
  function isShippingAvailable(prices, countryCode, stateCode) {
@@ -4085,6 +4118,14 @@ var ProductType = /* @__PURE__ */ ((ProductType2) => {
4085
4118
  ProductType2["service"] = "service";
4086
4119
  return ProductType2;
4087
4120
  })(ProductType || {});
4121
+ function formatProductOrderReference(productId) {
4122
+ return `product:${productId}`;
4123
+ }
4124
+
4125
+ // src/core/entities/product_landing_page.ts
4126
+ function formatProductLandingPageOrderReference(landingPageId) {
4127
+ return `product_landing_page:${landingPageId}`;
4128
+ }
4088
4129
 
4089
4130
  // src/core/entities/feedback.ts
4090
4131
  var FeedbackStatus = /* @__PURE__ */ ((FeedbackStatus2) => {
@@ -4144,6 +4185,8 @@ var FeeefTransmitHttpClient = class {
4144
4185
  this.options = options;
4145
4186
  this.getAuthorizationHeader = getAuthorizationHeader;
4146
4187
  }
4188
+ options;
4189
+ getAuthorizationHeader;
4147
4190
  send(request) {
4148
4191
  return fetch(request);
4149
4192
  }
@@ -4192,6 +4235,414 @@ function createFeeefTransmitFromAxios(client, options) {
4192
4235
  });
4193
4236
  }
4194
4237
 
4238
+ // src/ai/ai_calculator.ts
4239
+ var FALLBACK_AI_EXCHANGE_RATE = 260;
4240
+ var DEFAULT_TTS = {
4241
+ whenScriptEmptyTokens: 200,
4242
+ whenAttachmentsOnlyTokens: 400,
4243
+ promptBaseTokens: 400,
4244
+ promptPerAttachmentTokens: 300,
4245
+ outputMinimumTokens: 300,
4246
+ outputToTextTokenRatio: 2.5,
4247
+ maxTotalTokens: 32e3
4248
+ };
4249
+ var DEFAULT_RESOLVED = {
4250
+ retailMarkup: { multiplier: 2.5 },
4251
+ referenceAttachmentSurcharge: {
4252
+ perFileUsd: 0.1,
4253
+ highResolutionExtraPerFileUsd: 0.05,
4254
+ lowResolutionDiscountPerFileUsd: 0.05
4255
+ },
4256
+ imageGeneration: { fallbackProviderCostPerImageUsd: 0.131 },
4257
+ textGeneration: {
4258
+ freeTierMaxPromptTokens: 1e3,
4259
+ estimatedPromptTokensDefault: 2e3,
4260
+ estimatedOutputTokensDefault: 1e3
4261
+ },
4262
+ voiceGeneration: {
4263
+ minimumChargeUsd: 50 / FALLBACK_AI_EXCHANGE_RATE,
4264
+ scriptEnhancementAddonUsd: 25 / FALLBACK_AI_EXCHANGE_RATE,
4265
+ ttsTokenEstimate: { ...DEFAULT_TTS }
4266
+ },
4267
+ landingPageImage: {
4268
+ fixedChargeUsd: 100 / FALLBACK_AI_EXCHANGE_RATE
4269
+ }
4270
+ };
4271
+ function mergeTts(base, partial) {
4272
+ if (!partial) return { ...base };
4273
+ return {
4274
+ whenScriptEmptyTokens: partial.whenScriptEmptyTokens ?? base.whenScriptEmptyTokens,
4275
+ whenAttachmentsOnlyTokens: partial.whenAttachmentsOnlyTokens ?? base.whenAttachmentsOnlyTokens,
4276
+ promptBaseTokens: partial.promptBaseTokens ?? base.promptBaseTokens,
4277
+ promptPerAttachmentTokens: partial.promptPerAttachmentTokens ?? base.promptPerAttachmentTokens,
4278
+ outputMinimumTokens: partial.outputMinimumTokens ?? base.outputMinimumTokens,
4279
+ outputToTextTokenRatio: partial.outputToTextTokenRatio ?? base.outputToTextTokenRatio,
4280
+ maxTotalTokens: partial.maxTotalTokens ?? base.maxTotalTokens
4281
+ };
4282
+ }
4283
+ function mergeAiModelsBilling(partial) {
4284
+ const d = DEFAULT_RESOLVED;
4285
+ if (!partial) return structuredClone(d);
4286
+ const v = partial.voiceGeneration;
4287
+ const tts = mergeTts(d.voiceGeneration.ttsTokenEstimate, v?.ttsTokenEstimate);
4288
+ return {
4289
+ retailMarkup: {
4290
+ multiplier: partial.retailMarkup?.multiplier ?? d.retailMarkup.multiplier
4291
+ },
4292
+ referenceAttachmentSurcharge: {
4293
+ perFileUsd: partial.referenceAttachmentSurcharge?.perFileUsd ?? d.referenceAttachmentSurcharge.perFileUsd,
4294
+ highResolutionExtraPerFileUsd: partial.referenceAttachmentSurcharge?.highResolutionExtraPerFileUsd ?? d.referenceAttachmentSurcharge.highResolutionExtraPerFileUsd,
4295
+ lowResolutionDiscountPerFileUsd: partial.referenceAttachmentSurcharge?.lowResolutionDiscountPerFileUsd ?? d.referenceAttachmentSurcharge.lowResolutionDiscountPerFileUsd
4296
+ },
4297
+ imageGeneration: {
4298
+ fallbackProviderCostPerImageUsd: partial.imageGeneration?.fallbackProviderCostPerImageUsd ?? d.imageGeneration.fallbackProviderCostPerImageUsd
4299
+ },
4300
+ textGeneration: {
4301
+ freeTierMaxPromptTokens: partial.textGeneration?.freeTierMaxPromptTokens ?? d.textGeneration.freeTierMaxPromptTokens,
4302
+ estimatedPromptTokensDefault: partial.textGeneration?.estimatedPromptTokensDefault ?? d.textGeneration.estimatedPromptTokensDefault,
4303
+ estimatedOutputTokensDefault: partial.textGeneration?.estimatedOutputTokensDefault ?? d.textGeneration.estimatedOutputTokensDefault
4304
+ },
4305
+ voiceGeneration: {
4306
+ minimumChargeUsd: v?.minimumChargeUsd ?? d.voiceGeneration.minimumChargeUsd,
4307
+ scriptEnhancementAddonUsd: v?.scriptEnhancementAddonUsd ?? d.voiceGeneration.scriptEnhancementAddonUsd,
4308
+ ttsTokenEstimate: tts
4309
+ },
4310
+ landingPageImage: {
4311
+ fixedChargeUsd: partial.landingPageImage?.fixedChargeUsd ?? d.landingPageImage.fixedChargeUsd
4312
+ }
4313
+ };
4314
+ }
4315
+ function roundMoney(amount, precision = 3) {
4316
+ const factor = 10 ** precision;
4317
+ return Math.round((amount + Number.EPSILON) * factor) / factor;
4318
+ }
4319
+ function getLegacyAiBillingFlat(exchangeRate, resolved = mergeAiModelsBilling(null)) {
4320
+ const t = resolved.voiceGeneration.ttsTokenEstimate;
4321
+ return {
4322
+ MULTIPLIER: resolved.retailMarkup.multiplier,
4323
+ FREE_TEXT_TOKENS_THRESHOLD: resolved.textGeneration.freeTierMaxPromptTokens,
4324
+ DEFAULT_EXCHANGE_RATE: FALLBACK_AI_EXCHANGE_RATE,
4325
+ DEFAULT_GOOGLE_IMAGE_COST_USD: resolved.imageGeneration.fallbackProviderCostPerImageUsd,
4326
+ DEFAULT_ATTACHMENT_COST_USD: resolved.referenceAttachmentSurcharge.perFileUsd,
4327
+ ATTACHMENT_HIGH_RES_EXTRA_USD: resolved.referenceAttachmentSurcharge.highResolutionExtraPerFileUsd,
4328
+ ATTACHMENT_LOW_RES_DISCOUNT_USD: resolved.referenceAttachmentSurcharge.lowResolutionDiscountPerFileUsd,
4329
+ VOICEOVER_FIXED_COST_DZD: roundMoney(
4330
+ resolved.voiceGeneration.minimumChargeUsd * exchangeRate,
4331
+ 3
4332
+ ),
4333
+ VOICEOVER_ENHANCE_ADDON_DZD: roundMoney(
4334
+ resolved.voiceGeneration.scriptEnhancementAddonUsd * exchangeRate,
4335
+ 3
4336
+ ),
4337
+ IMAGE_LANDING_PAGE_FIXED_COST_DZD: roundMoney(
4338
+ resolved.landingPageImage.fixedChargeUsd * exchangeRate,
4339
+ 3
4340
+ ),
4341
+ DEFAULT_TEXT_PROMPT_TOKENS: resolved.textGeneration.estimatedPromptTokensDefault,
4342
+ DEFAULT_TEXT_OUTPUT_TOKENS: resolved.textGeneration.estimatedOutputTokensDefault,
4343
+ VOICE_TTS_EMPTY_SCRIPT_TEXT_TOKENS: t.whenScriptEmptyTokens,
4344
+ VOICE_TTS_ATTACHMENT_ONLY_TEXT_TOKENS: t.whenAttachmentsOnlyTokens,
4345
+ VOICE_TTS_PROMPT_BASE: t.promptBaseTokens,
4346
+ VOICE_TTS_PROMPT_PER_ATTACHMENT: t.promptPerAttachmentTokens,
4347
+ VOICE_TTS_OUTPUT_MIN: t.outputMinimumTokens,
4348
+ VOICE_TTS_OUTPUT_TEXT_FACTOR: t.outputToTextTokenRatio,
4349
+ VOICE_TTS_TOKEN_CAP: t.maxTotalTokens
4350
+ };
4351
+ }
4352
+ var AI_BILLING = getLegacyAiBillingFlat(FALLBACK_AI_EXCHANGE_RATE);
4353
+ function findModel(models, modelId, fallbackId) {
4354
+ return models.find((m) => m.id === modelId) || models.find((m) => m.id === fallbackId) || models[0];
4355
+ }
4356
+ function modelHasVoiceCapability(model) {
4357
+ const caps = model?.capabilities;
4358
+ if (!Array.isArray(caps)) return false;
4359
+ return caps.some((c) => c === "voice" || c === "audio");
4360
+ }
4361
+ function findVoiceModel(models, modelId, fallbackId) {
4362
+ return models.find((m) => m.id === modelId) || models.find((m) => m.id === fallbackId) || models.find((m) => modelHasVoiceCapability(m));
4363
+ }
4364
+ function pickTtsProviderOutputUsd(model) {
4365
+ if (!model?.pricing?.length) return null;
4366
+ const voiceLike = modelHasVoiceCapability(model);
4367
+ for (const unit of ["audio", "voice"]) {
4368
+ const row = model.pricing.find((p) => p.unit === unit);
4369
+ if (row?.output == null) continue;
4370
+ const usd = row.output;
4371
+ if (usd > 0) return usd;
4372
+ }
4373
+ if (voiceLike) {
4374
+ const row = model.pricing.find((p) => p.unit === "image");
4375
+ if (row?.output == null) return null;
4376
+ const usd = row.output;
4377
+ if (usd > 0) return usd;
4378
+ }
4379
+ return null;
4380
+ }
4381
+ function ttsTokenEstimatesFromResolved(b, scriptCharLength, attachmentCount) {
4382
+ const t = b.voiceGeneration.ttsTokenEstimate;
4383
+ let textTok = Math.round(scriptCharLength / 4);
4384
+ if (textTok <= 0) {
4385
+ textTok = attachmentCount > 0 ? t.whenAttachmentsOnlyTokens : t.whenScriptEmptyTokens;
4386
+ }
4387
+ const rawPrompt = t.promptBaseTokens + textTok + attachmentCount * t.promptPerAttachmentTokens;
4388
+ const promptTokens = Math.min(t.maxTotalTokens, Math.max(0, rawPrompt));
4389
+ const rawOutput = Math.max(t.outputMinimumTokens, Math.round(textTok * t.outputToTextTokenRatio));
4390
+ const outputTokens = Math.min(t.maxTotalTokens, rawOutput);
4391
+ return { promptTokens, outputTokens };
4392
+ }
4393
+ function defaultVoiceTtsTokenEstimates(scriptCharLength, attachmentCount) {
4394
+ return ttsTokenEstimatesFromResolved(
4395
+ mergeAiModelsBilling(null),
4396
+ scriptCharLength,
4397
+ attachmentCount
4398
+ );
4399
+ }
4400
+ function pickVoiceTokenPricing(model, totalTokens) {
4401
+ const tokenPricings = (model?.pricing ?? []).filter((p) => p.unit === "tokens");
4402
+ if (!tokenPricings.length) return null;
4403
+ const isLargeContext = totalTokens > 2e5;
4404
+ const preferred = tokenPricings.find(
4405
+ (p) => isLargeContext ? String(p.contextThreshold ?? "").includes(">") : String(p.contextThreshold ?? "").includes("<=")
4406
+ ) || tokenPricings[0];
4407
+ const input = preferred.input ?? 0;
4408
+ const output = preferred.output ?? 0;
4409
+ if (input <= 0 && output <= 0) return null;
4410
+ return { input, output };
4411
+ }
4412
+ var AiCalculator = class {
4413
+ config;
4414
+ constructor(config = {}) {
4415
+ const exchangeRate = config.exchangeRate ?? FALLBACK_AI_EXCHANGE_RATE;
4416
+ const billing = mergeAiModelsBilling(config.billing ?? null);
4417
+ const fallbackDzd = billing.imageGeneration.fallbackProviderCostPerImageUsd * exchangeRate;
4418
+ this.config = {
4419
+ exchangeRate,
4420
+ defaultImageCost: config.defaultImageCost ?? fallbackDzd,
4421
+ referenceImageCost: config.referenceImageCost ?? 5,
4422
+ resolutionCosts: config.resolutionCosts ?? {
4423
+ MEDIA_RESOLUTION_LOW: 0,
4424
+ MEDIA_RESOLUTION_MEDIUM: 5,
4425
+ MEDIA_RESOLUTION_HIGH: 10
4426
+ },
4427
+ models: config.models ?? [],
4428
+ billing
4429
+ };
4430
+ }
4431
+ /** TTS base DZD: localCost → flat USD row → tokens (per 1M) × estimates × rate × multiplier → floor. */
4432
+ _voiceoverBaseUserCostDzd(modelId, promptTokens, outputTokens) {
4433
+ const { exchangeRate, billing, models } = this.config;
4434
+ const b = billing;
4435
+ const floorDzd = roundMoney(b.voiceGeneration.minimumChargeUsd * exchangeRate);
4436
+ const model = findVoiceModel(models, modelId, "gemini-2.5-pro-preview-tts");
4437
+ if (model?.localCost !== void 0 && model.localCost !== null) {
4438
+ return { baseDzd: roundMoney(model.localCost), usedLocalCost: true };
4439
+ }
4440
+ const providerUsd = pickTtsProviderOutputUsd(model);
4441
+ if (providerUsd != null) {
4442
+ const providerDzd = providerUsd * exchangeRate;
4443
+ return {
4444
+ baseDzd: roundMoney(providerDzd * b.retailMarkup.multiplier),
4445
+ usedLocalCost: false
4446
+ };
4447
+ }
4448
+ const tokenPricing = pickVoiceTokenPricing(model, promptTokens + outputTokens);
4449
+ if (tokenPricing) {
4450
+ const providerCostUsd = promptTokens / 1e6 * tokenPricing.input + outputTokens / 1e6 * tokenPricing.output;
4451
+ const providerDzd = providerCostUsd * exchangeRate;
4452
+ return {
4453
+ baseDzd: roundMoney(providerDzd * b.retailMarkup.multiplier),
4454
+ usedLocalCost: false
4455
+ };
4456
+ }
4457
+ return { baseDzd: floorDzd, usedLocalCost: false };
4458
+ }
4459
+ _attachmentExtraUserDzd(attachmentCount, attachmentResolution) {
4460
+ if (attachmentCount <= 0) return 0;
4461
+ const { exchangeRate, billing } = this.config;
4462
+ const ref = billing.referenceAttachmentSurcharge;
4463
+ const m = billing.retailMarkup.multiplier;
4464
+ let attachCostUsd = attachmentCount * ref.perFileUsd;
4465
+ if (attachmentResolution === "high") {
4466
+ attachCostUsd += attachmentCount * ref.highResolutionExtraPerFileUsd;
4467
+ } else if (attachmentResolution === "low") {
4468
+ attachCostUsd -= attachmentCount * ref.lowResolutionDiscountPerFileUsd;
4469
+ }
4470
+ return roundMoney(attachCostUsd * exchangeRate * m);
4471
+ }
4472
+ /**
4473
+ * Estimate the cost of an image generation action.
4474
+ * Covers: image gen, logo gen, editOrGenerateSimpleImage.
4475
+ */
4476
+ estimateImageGeneration(options = {}) {
4477
+ const {
4478
+ modelId = "gemini-3.1-flash-image-preview",
4479
+ attachmentCount = 0,
4480
+ attachmentResolution = "medium",
4481
+ resolution,
4482
+ imageSize,
4483
+ iterations = 1,
4484
+ referenceImageCount = 0
4485
+ } = options;
4486
+ const model = findModel(this.config.models, modelId, "gemini-3.1-flash-image-preview");
4487
+ const { exchangeRate, defaultImageCost, billing } = this.config;
4488
+ const mult = billing.retailMarkup.multiplier;
4489
+ const localCost = model?.localCost;
4490
+ const imagePricing = model?.pricing?.find((p) => p.unit === "image");
4491
+ const providerCostUsd = imagePricing?.output ? imagePricing.output : defaultImageCost / exchangeRate;
4492
+ const providerCostDzd = imagePricing?.output ? providerCostUsd * exchangeRate : defaultImageCost;
4493
+ const computedUserCostDzd = providerCostDzd * mult;
4494
+ const usedLocalCost = localCost !== void 0 && localCost !== null;
4495
+ const basePerIteration = usedLocalCost ? localCost : roundMoney(computedUserCostDzd);
4496
+ const baseCostDzd = roundMoney(basePerIteration * iterations);
4497
+ const refExtraDzd = roundMoney(referenceImageCount * this.config.referenceImageCost);
4498
+ const attachExtraDzd = this._attachmentExtraUserDzd(attachmentCount, attachmentResolution);
4499
+ const supportsImageSize = modelId === "gemini-3.1-flash-image-preview";
4500
+ let outputResKey = "MEDIA_RESOLUTION_HIGH";
4501
+ if (supportsImageSize && imageSize) {
4502
+ outputResKey = imageSize === "4K" ? "MEDIA_RESOLUTION_HIGH" : imageSize === "2K" ? "MEDIA_RESOLUTION_MEDIUM" : "MEDIA_RESOLUTION_LOW";
4503
+ } else if (resolution) {
4504
+ outputResKey = resolution;
4505
+ }
4506
+ const outputResExtraDzd = supportsImageSize ? roundMoney(this.config.resolutionCosts[outputResKey] ?? 0) : 0;
4507
+ let referenceResolutionExtraDzd = 0;
4508
+ if (referenceImageCount > 0 && resolution) {
4509
+ const low = this.config.resolutionCosts.MEDIA_RESOLUTION_LOW ?? 0;
4510
+ const tier = this.config.resolutionCosts[resolution] ?? 0;
4511
+ referenceResolutionExtraDzd = roundMoney(Math.max(0, tier - low));
4512
+ }
4513
+ const resExtraDzd = roundMoney(outputResExtraDzd + referenceResolutionExtraDzd);
4514
+ const userCostDzd = roundMoney(baseCostDzd + refExtraDzd + attachExtraDzd + resExtraDzd);
4515
+ return {
4516
+ providerCostUsd: providerCostUsd * iterations,
4517
+ providerCostDzd: providerCostDzd * iterations,
4518
+ userCostDzd,
4519
+ exchangeRate,
4520
+ multiplier: mult,
4521
+ usedLocalCost,
4522
+ breakdown: {
4523
+ baseCostDzd,
4524
+ referenceImageExtraDzd: refExtraDzd,
4525
+ attachmentExtraDzd: attachExtraDzd,
4526
+ outputResolutionExtraDzd: outputResExtraDzd,
4527
+ referenceResolutionExtraDzd,
4528
+ resolutionExtraDzd: resExtraDzd,
4529
+ iterations,
4530
+ referenceImageCount,
4531
+ attachmentCount
4532
+ }
4533
+ };
4534
+ }
4535
+ /**
4536
+ * Estimate the cost of a text generation action.
4537
+ * Covers: updateProductUsingAi, generateSimpleCode, generateCustomComponentCode.
4538
+ * Uses estimated tokens (exact cost billed post-generation).
4539
+ */
4540
+ estimateTextGeneration(options = {}) {
4541
+ const tg = this.config.billing.textGeneration;
4542
+ const mult = this.config.billing.retailMarkup.multiplier;
4543
+ const {
4544
+ modelId = "gemini-3-flash-preview",
4545
+ estimatedPromptTokens = tg.estimatedPromptTokensDefault,
4546
+ estimatedOutputTokens = tg.estimatedOutputTokensDefault
4547
+ } = options;
4548
+ const totalTokens = estimatedPromptTokens + estimatedOutputTokens;
4549
+ const { exchangeRate } = this.config;
4550
+ const model = findModel(this.config.models, modelId, "gemini-3-flash-preview");
4551
+ const tokenPricing = model?.pricing?.find((p) => p.unit === "tokens");
4552
+ if (!tokenPricing || totalTokens < tg.freeTierMaxPromptTokens) {
4553
+ return {
4554
+ providerCostUsd: 0,
4555
+ providerCostDzd: 0,
4556
+ userCostDzd: 0,
4557
+ exchangeRate,
4558
+ multiplier: mult,
4559
+ usedLocalCost: false,
4560
+ breakdown: {
4561
+ estimatedPromptTokens,
4562
+ estimatedOutputTokens,
4563
+ isFree: true
4564
+ }
4565
+ };
4566
+ }
4567
+ const inputPrice = tokenPricing.input ?? 0;
4568
+ const outputPrice = tokenPricing.output ?? 0;
4569
+ const providerCostUsd = estimatedPromptTokens / 1e6 * inputPrice + estimatedOutputTokens / 1e6 * outputPrice;
4570
+ const providerCostDzd = providerCostUsd * exchangeRate;
4571
+ const userCostDzd = roundMoney(providerCostDzd * mult);
4572
+ return {
4573
+ providerCostUsd,
4574
+ providerCostDzd,
4575
+ userCostDzd,
4576
+ exchangeRate,
4577
+ multiplier: mult,
4578
+ usedLocalCost: false,
4579
+ breakdown: {
4580
+ estimatedPromptTokens,
4581
+ estimatedOutputTokens,
4582
+ isFree: false
4583
+ }
4584
+ };
4585
+ }
4586
+ /**
4587
+ * Voiceover: base + attachment surcharge + optional script-enhancement add-on.
4588
+ */
4589
+ estimateVoiceover(options = {}) {
4590
+ const modelId = options.modelId ?? "gemini-2.5-pro-preview-tts";
4591
+ const attachmentCount = options.attachmentCount ?? 0;
4592
+ const attachmentResolution = options.attachmentResolution ?? "medium";
4593
+ const enhanceScript = options.enhanceScript !== false;
4594
+ const charLen = options.scriptCharLength ?? 0;
4595
+ const b = this.config.billing;
4596
+ const cap = b.voiceGeneration.ttsTokenEstimate.maxTotalTokens;
4597
+ const tokenEst = (
4598
+ // eslint-disable-next-line eqeqeq
4599
+ options.estimatedPromptTokens != null && options.estimatedOutputTokens != null ? {
4600
+ promptTokens: Math.max(0, Math.min(cap, options.estimatedPromptTokens)),
4601
+ outputTokens: Math.max(0, Math.min(cap, options.estimatedOutputTokens))
4602
+ } : ttsTokenEstimatesFromResolved(b, charLen, attachmentCount)
4603
+ );
4604
+ const { exchangeRate } = this.config;
4605
+ const { baseDzd, usedLocalCost } = this._voiceoverBaseUserCostDzd(
4606
+ modelId,
4607
+ tokenEst.promptTokens,
4608
+ tokenEst.outputTokens
4609
+ );
4610
+ const attachExtra = this._attachmentExtraUserDzd(attachmentCount, attachmentResolution);
4611
+ const enhanceExtra = enhanceScript ? roundMoney(b.voiceGeneration.scriptEnhancementAddonUsd * exchangeRate) : 0;
4612
+ const userCostDzd = roundMoney(baseDzd + attachExtra + enhanceExtra);
4613
+ return {
4614
+ providerCostUsd: userCostDzd / exchangeRate,
4615
+ providerCostDzd: userCostDzd,
4616
+ userCostDzd,
4617
+ exchangeRate,
4618
+ multiplier: 1,
4619
+ usedLocalCost,
4620
+ breakdown: {
4621
+ fixedCostDzd: baseDzd,
4622
+ attachmentExtraDzd: attachExtra,
4623
+ enhanceAddonDzd: enhanceExtra,
4624
+ attachmentCount,
4625
+ estimatedPromptTokens: tokenEst.promptTokens,
4626
+ estimatedOutputTokens: tokenEst.outputTokens
4627
+ }
4628
+ };
4629
+ }
4630
+ /** Get the fixed cost for image landing page generation. */
4631
+ estimateImageLandingPage() {
4632
+ const { exchangeRate, billing } = this.config;
4633
+ const costDzd = roundMoney(billing.landingPageImage.fixedChargeUsd * exchangeRate);
4634
+ return {
4635
+ providerCostUsd: costDzd / exchangeRate,
4636
+ providerCostDzd: costDzd,
4637
+ userCostDzd: costDzd,
4638
+ exchangeRate,
4639
+ multiplier: 1,
4640
+ usedLocalCost: false,
4641
+ breakdown: { fixedCostDzd: costDzd }
4642
+ };
4643
+ }
4644
+ };
4645
+
4195
4646
  // src/utils.ts
4196
4647
  var convertDartColorToCssNumber = (dartColor) => {
4197
4648
  const alpha = dartColor >> 24 & 255;
@@ -4276,8 +4727,10 @@ function validatePhoneNumber(phone) {
4276
4727
  return null;
4277
4728
  }
4278
4729
  export {
4730
+ AI_BILLING,
4279
4731
  ATTACHMENT_TYPES,
4280
4732
  ActionsService,
4733
+ AiCalculator,
4281
4734
  AppRepository,
4282
4735
  CartService,
4283
4736
  CategoryRepository,
@@ -4290,6 +4743,7 @@ export {
4290
4743
  EcomanagerDeliveryIntegrationApi,
4291
4744
  EcotrackDeliveryIntegrationApi,
4292
4745
  EmbaddedContactType,
4746
+ FALLBACK_AI_EXCHANGE_RATE,
4293
4747
  FeedbackPriority,
4294
4748
  FeedbackRepository,
4295
4749
  FeedbackStatus,
@@ -4346,6 +4800,9 @@ export {
4346
4800
  createImageGenerationFormData,
4347
4801
  cssColorToHslString,
4348
4802
  dartColorToCssColor,
4803
+ defaultVoiceTtsTokenEstimates,
4804
+ formatProductLandingPageOrderReference,
4805
+ formatProductOrderReference,
4349
4806
  generatePublicIntegrationsData,
4350
4807
  generatePublicIntegrationsDataGoogleAnalytics,
4351
4808
  generatePublicIntegrationsDataGoogleSheets,
@@ -4366,9 +4823,11 @@ export {
4366
4823
  generatePublicStoreIntegrationWebhooks,
4367
4824
  generatePublicStoreIntegrations,
4368
4825
  getAvailableShippingTypes,
4826
+ getLegacyAiBillingFlat,
4369
4827
  getShippingPrice,
4370
4828
  isAttachmentType,
4371
4829
  isShippingAvailable,
4830
+ mergeAiModelsBilling,
4372
4831
  normalizeAttachmentPayload,
4373
4832
  normalizeAttachmentPayloads,
4374
4833
  normalizeImageGeneration,