spoclip-kit 2.7.0 → 4.0.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 +17 -9
- package/dist/index.cjs +20 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +20 -97
- package/dist/index.js.map +1 -1
- package/dist/libs.cjs +20 -101
- package/dist/libs.cjs.map +1 -1
- package/dist/libs.d.cts +15 -80
- package/dist/libs.d.ts +15 -80
- package/dist/libs.js +20 -97
- package/dist/libs.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +0 -2
- package/dist/types.d.ts +0 -2
- package/package.json +1 -1
- package/dist/membership-C_ziSHS0.d.cts +0 -3
- package/dist/membership-C_ziSHS0.d.ts +0 -3
package/README.md
CHANGED
|
@@ -13,15 +13,23 @@ npm install spoclip-kit
|
|
|
13
13
|
### Import specific modules
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
import {
|
|
17
|
+
calculateOriginalDownloadCostWon,
|
|
18
|
+
isDownloadDurationAllowed,
|
|
19
|
+
} from 'spoclip-kit/libs';
|
|
20
|
+
|
|
21
|
+
// 10초 미만은 다운로드 불가
|
|
22
|
+
isDownloadDurationAllowed(8); // false
|
|
23
|
+
isDownloadDurationAllowed(60); // true
|
|
24
|
+
|
|
25
|
+
// 30초 무료 + 초과분 100원/분 (멤버십 구분 없음)
|
|
26
|
+
calculateOriginalDownloadCostWon(60); // 50 (30초 초과분 30초 과금)
|
|
27
|
+
|
|
28
|
+
// 전체 영상 10% 할인
|
|
29
|
+
calculateOriginalDownloadCostWon(60, { isFullVideo: true }); // 45
|
|
30
|
+
|
|
31
|
+
// 중복 화각 동시 다운로드 (단일 화각 비용 × 화각 수)
|
|
32
|
+
calculateOriginalDownloadCostWon(60, { angleCount: 3 }); // 150
|
|
25
33
|
```
|
|
26
34
|
|
|
27
35
|
## Development
|
package/dist/index.cjs
CHANGED
|
@@ -21,13 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
GYM_GOLD_CHARGE_POLICY: () => GYM_GOLD_CHARGE_POLICY,
|
|
24
|
-
GYM_PARTNER_REGISTRATION_GOLD_POLICY: () => GYM_PARTNER_REGISTRATION_GOLD_POLICY,
|
|
25
24
|
MIN_DOWNLOAD_SECONDS: () => MIN_DOWNLOAD_SECONDS,
|
|
26
25
|
ORIGINAL_DOWNLOAD_COST_POLICY: () => ORIGINAL_DOWNLOAD_COST_POLICY,
|
|
27
26
|
assertValidGymGoldChargeKrw: () => assertValidGymGoldChargeKrw,
|
|
28
|
-
calculateGymGoldCharge: () => calculateGymGoldCharge,
|
|
29
|
-
calculateGymPartnerRegistrationGold: () => calculateGymPartnerRegistrationGold,
|
|
30
|
-
calculateGymPartnerRegistrationGoldTotal: () => calculateGymPartnerRegistrationGoldTotal,
|
|
31
27
|
calculateOriginalDownloadCostWon: () => calculateOriginalDownloadCostWon,
|
|
32
28
|
color: () => color,
|
|
33
29
|
colorV2: () => colorV2,
|
|
@@ -40,30 +36,20 @@ module.exports = __toCommonJS(src_exports);
|
|
|
40
36
|
|
|
41
37
|
// src/libs/cost.ts
|
|
42
38
|
var ORIGINAL_DOWNLOAD_COST_POLICY = {
|
|
43
|
-
baseDurationSeconds: 600,
|
|
44
|
-
baseCostWon: 2e3,
|
|
45
|
-
fullVideoDiscountRate: 0.1,
|
|
46
39
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
40
|
+
* 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.
|
|
41
|
+
* 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,
|
|
42
|
+
* 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).
|
|
50
43
|
*/
|
|
51
|
-
|
|
52
|
-
FREE: 100,
|
|
53
|
-
PLUS: 50,
|
|
54
|
-
PRO: 25
|
|
55
|
-
},
|
|
44
|
+
freeSeconds: 30,
|
|
56
45
|
/**
|
|
57
|
-
* 멤버십
|
|
58
|
-
* 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,
|
|
59
|
-
* 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).
|
|
60
|
-
* plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.
|
|
46
|
+
* 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.
|
|
61
47
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
krwPerMinute: 100,
|
|
49
|
+
/**
|
|
50
|
+
* 전체 영상 다운로드 시 할인율(10%).
|
|
51
|
+
*/
|
|
52
|
+
fullVideoDiscountRate: 0.1
|
|
67
53
|
};
|
|
68
54
|
var MIN_DOWNLOAD_SECONDS = 10;
|
|
69
55
|
function isDownloadDurationAllowed(durationSeconds) {
|
|
@@ -71,14 +57,18 @@ function isDownloadDurationAllowed(durationSeconds) {
|
|
|
71
57
|
}
|
|
72
58
|
function calculateOriginalDownloadCostWon(durationSeconds, options = {}) {
|
|
73
59
|
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;
|
|
74
|
-
const {
|
|
75
|
-
const
|
|
76
|
-
const billableSeconds = Math.max(
|
|
60
|
+
const { isFullVideo, angleCount } = options;
|
|
61
|
+
const normalizedAngleCount = Number.isFinite(angleCount) ? Math.max(1, Math.floor(angleCount)) : 1;
|
|
62
|
+
const billableSeconds = Math.max(
|
|
63
|
+
0,
|
|
64
|
+
durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds
|
|
65
|
+
);
|
|
77
66
|
if (billableSeconds <= 0) return 0;
|
|
78
|
-
const unitPricePerSecond =
|
|
67
|
+
const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;
|
|
79
68
|
const discountMultiplier = isFullVideo ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate : 1;
|
|
80
|
-
const
|
|
81
|
-
|
|
69
|
+
const rawCostPerAngle = billableSeconds * unitPricePerSecond * discountMultiplier;
|
|
70
|
+
const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));
|
|
71
|
+
return costPerAngle * normalizedAngleCount;
|
|
82
72
|
}
|
|
83
73
|
|
|
84
74
|
// src/libs/gym-gold-charge.ts
|
|
@@ -88,17 +78,6 @@ var GYM_GOLD_CHARGE_POLICY = {
|
|
|
88
78
|
bonusRate: 0.2,
|
|
89
79
|
bonusRateBps: 2e3
|
|
90
80
|
};
|
|
91
|
-
function calculateGymGoldCharge(input) {
|
|
92
|
-
assertValidGymGoldChargeKrw(input.chargeKrw);
|
|
93
|
-
const baseGold = input.chargeKrw;
|
|
94
|
-
const bonusGold = calculateBonusGold(input.chargeKrw);
|
|
95
|
-
return {
|
|
96
|
-
chargeKrw: input.chargeKrw,
|
|
97
|
-
baseGold,
|
|
98
|
-
bonusGold,
|
|
99
|
-
totalGold: baseGold + bonusGold
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
81
|
function isGymGoldChargeKrwAllowed(chargeKrw) {
|
|
103
82
|
return Number.isFinite(chargeKrw) && Number.isInteger(chargeKrw) && chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw && chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0;
|
|
104
83
|
}
|
|
@@ -120,62 +99,6 @@ function assertValidGymGoldChargeKrw(chargeKrw) {
|
|
|
120
99
|
);
|
|
121
100
|
}
|
|
122
101
|
}
|
|
123
|
-
function calculateBonusGold(chargeKrw) {
|
|
124
|
-
return Math.floor(chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps / 1e4);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// src/libs/gym-registration-gold.ts
|
|
128
|
-
var GYM_PARTNER_REGISTRATION_GOLD_POLICY = {
|
|
129
|
-
goldPerDay: 350,
|
|
130
|
-
timeZone: "Asia/Seoul"
|
|
131
|
-
};
|
|
132
|
-
var DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
133
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
134
|
-
function calculateGymPartnerRegistrationGold(input) {
|
|
135
|
-
const startDate = parseKstDateString(input.startDate, "startDate");
|
|
136
|
-
const endDate = parseKstDateString(input.endDate, "endDate");
|
|
137
|
-
const startDateKey = toUtcDateKey(startDate);
|
|
138
|
-
const endDateKey = toUtcDateKey(endDate);
|
|
139
|
-
if (endDateKey < startDateKey) {
|
|
140
|
-
throw new RangeError("endDate must be on or after startDate");
|
|
141
|
-
}
|
|
142
|
-
const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;
|
|
143
|
-
return {
|
|
144
|
-
days,
|
|
145
|
-
goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
function calculateGymPartnerRegistrationGoldTotal(periods) {
|
|
149
|
-
const items = periods.map((period) => {
|
|
150
|
-
const result = calculateGymPartnerRegistrationGold(period);
|
|
151
|
-
return {
|
|
152
|
-
...period,
|
|
153
|
-
...result
|
|
154
|
-
};
|
|
155
|
-
});
|
|
156
|
-
return {
|
|
157
|
-
totalDays: items.reduce((sum, item) => sum + item.days, 0),
|
|
158
|
-
totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),
|
|
159
|
-
items
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
function parseKstDateString(input, fieldName) {
|
|
163
|
-
const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);
|
|
164
|
-
if (dateOnlyMatch === null) {
|
|
165
|
-
throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);
|
|
166
|
-
}
|
|
167
|
-
const year = Number(dateOnlyMatch[1]);
|
|
168
|
-
const month = Number(dateOnlyMatch[2]);
|
|
169
|
-
const day = Number(dateOnlyMatch[3]);
|
|
170
|
-
const utcDate = new Date(Date.UTC(year, month - 1, day));
|
|
171
|
-
if (utcDate.getUTCFullYear() !== year || utcDate.getUTCMonth() + 1 !== month || utcDate.getUTCDate() !== day) {
|
|
172
|
-
throw new RangeError(`${fieldName} must be a valid date`);
|
|
173
|
-
}
|
|
174
|
-
return { year, month, day };
|
|
175
|
-
}
|
|
176
|
-
function toUtcDateKey(date) {
|
|
177
|
-
return Date.UTC(date.year, date.month - 1, date.day);
|
|
178
|
-
}
|
|
179
102
|
|
|
180
103
|
// src/styles/color.ts
|
|
181
104
|
var color = {
|
|
@@ -423,13 +346,9 @@ var mobileTypo = {
|
|
|
423
346
|
// Annotate the CommonJS export names for ESM import in node:
|
|
424
347
|
0 && (module.exports = {
|
|
425
348
|
GYM_GOLD_CHARGE_POLICY,
|
|
426
|
-
GYM_PARTNER_REGISTRATION_GOLD_POLICY,
|
|
427
349
|
MIN_DOWNLOAD_SECONDS,
|
|
428
350
|
ORIGINAL_DOWNLOAD_COST_POLICY,
|
|
429
351
|
assertValidGymGoldChargeKrw,
|
|
430
|
-
calculateGymGoldCharge,
|
|
431
|
-
calculateGymPartnerRegistrationGold,
|
|
432
|
-
calculateGymPartnerRegistrationGoldTotal,
|
|
433
352
|
calculateOriginalDownloadCostWon,
|
|
434
353
|
color,
|
|
435
354
|
colorV2,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/libs/cost.ts","../src/libs/gym-gold-charge.ts","../src/libs/gym-registration-gold.ts","../src/styles/color.ts","../src/styles/typo.ts"],"sourcesContent":["// Main entry point for spoclip-kit\nexport * from './libs';\nexport * from './types';\nexport * from './styles';\n","import type { TicketCode } from '../types/membership';\n\nexport const ORIGINAL_DOWNLOAD_COST_POLICY = {\n baseDurationSeconds: 600,\n baseCostWon: 2000,\n fullVideoDiscountRate: 0.1,\n /**\n * v14 신정책: 멤버십 plan별 분당 KRW 요율.\n * `calculateOriginalDownloadCostWon(duration, { plan })`로 호출 시 적용된다.\n * plan 미지정 시 `baseCostWon/baseDurationSeconds` 기반 단일 단가(레거시 200원/분)로 폴백 — 기존 호출자 호환.\n */\n perPlanKrwPerMinute: {\n FREE: 100,\n PLUS: 50,\n PRO: 25,\n },\n /**\n * 멤버십 plan별 무료 다운로드 구간(초).\n * 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).\n * plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.\n */\n freeSecondsByPlan: {\n FREE: 10,\n PLUS: 20,\n PRO: 30,\n },\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 멤버십 plan. 지정 시 `perPlanKrwPerMinute[plan]`을 단가로,\n * `freeSecondsByPlan[plan]`을 무료 구간으로 적용한다.\n * 미지정(undefined) 시 레거시 단일 단가(`baseCostWon/baseDurationSeconds`) + 무료 구간 없음.\n */\n plan?: TicketCode;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { plan, isFullVideo } = options;\n\n // plan 지정 시 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n // 레거시 경로(plan 미지정)는 무료 구간 0으로 기존 동작을 유지한다.\n const freeSeconds =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.freeSecondsByPlan[plan]\n : 0;\n const billableSeconds = Math.max(0, durationSeconds - freeSeconds);\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.perPlanKrwPerMinute[plan] / 60\n : ORIGINAL_DOWNLOAD_COST_POLICY.baseCostWon /\n ORIGINAL_DOWNLOAD_COST_POLICY.baseDurationSeconds;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다.\n const rawCost = billableSeconds * unitPricePerSecond * discountMultiplier;\n return Math.ceil(Number(rawCost.toFixed(6)));\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport interface GymGoldChargeInput {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n}\n\nexport interface GymGoldChargeResult {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n /**\n * 1:1 base Gold granted for paid KRW.\n */\n baseGold: number;\n /**\n * Additional bonus Gold granted by charge policy.\n */\n bonusGold: number;\n /**\n * Final Gold amount credited to the gym balance.\n */\n totalGold: number;\n}\n\nexport function calculateGymGoldCharge(\n input: GymGoldChargeInput,\n): GymGoldChargeResult {\n assertValidGymGoldChargeKrw(input.chargeKrw);\n\n const baseGold = input.chargeKrw;\n const bonusGold = calculateBonusGold(input.chargeKrw);\n\n return {\n chargeKrw: input.chargeKrw,\n baseGold,\n bonusGold,\n totalGold: baseGold + bonusGold,\n };\n}\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n\nfunction calculateBonusGold(chargeKrw: number): number {\n return Math.floor((chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps) / 10_000);\n}\n","export const GYM_PARTNER_REGISTRATION_GOLD_POLICY = {\n goldPerDay: 350,\n timeZone: 'Asia/Seoul',\n} as const;\n\nexport interface GymPartnerRegistrationGoldInput {\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n startDate: string;\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n endDate: string;\n}\n\nexport interface GymPartnerRegistrationGoldResult {\n days: number;\n goldAmount: number;\n}\n\nexport interface GymPartnerRegistrationGoldTotalItem\n extends GymPartnerRegistrationGoldInput,\n GymPartnerRegistrationGoldResult {}\n\nexport interface GymPartnerRegistrationGoldTotalResult {\n totalDays: number;\n totalGoldAmount: number;\n items: GymPartnerRegistrationGoldTotalItem[];\n}\n\ninterface KstDateParts {\n year: number;\n month: number;\n day: number;\n}\n\nconst DATE_ONLY_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\nexport function calculateGymPartnerRegistrationGold(\n input: GymPartnerRegistrationGoldInput,\n): GymPartnerRegistrationGoldResult {\n const startDate = parseKstDateString(input.startDate, 'startDate');\n const endDate = parseKstDateString(input.endDate, 'endDate');\n\n const startDateKey = toUtcDateKey(startDate);\n const endDateKey = toUtcDateKey(endDate);\n\n if (endDateKey < startDateKey) {\n throw new RangeError('endDate must be on or after startDate');\n }\n\n const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;\n\n return {\n days,\n goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay,\n };\n}\n\nexport function calculateGymPartnerRegistrationGoldTotal(\n periods: GymPartnerRegistrationGoldInput[],\n): GymPartnerRegistrationGoldTotalResult {\n const items = periods.map((period) => {\n const result = calculateGymPartnerRegistrationGold(period);\n\n return {\n ...period,\n ...result,\n };\n });\n\n return {\n totalDays: items.reduce((sum, item) => sum + item.days, 0),\n totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),\n items,\n };\n}\n\nfunction parseKstDateString(\n input: string,\n fieldName: 'startDate' | 'endDate',\n): KstDateParts {\n const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);\n if (dateOnlyMatch === null) {\n throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);\n }\n\n const year = Number(dateOnlyMatch[1]);\n const month = Number(dateOnlyMatch[2]);\n const day = Number(dateOnlyMatch[3]);\n const utcDate = new Date(Date.UTC(year, month - 1, day));\n\n if (\n utcDate.getUTCFullYear() !== year ||\n utcDate.getUTCMonth() + 1 !== month ||\n utcDate.getUTCDate() !== day\n ) {\n throw new RangeError(`${fieldName} must be a valid date`);\n }\n\n return { year, month, day };\n}\n\nfunction toUtcDateKey(date: KstDateParts): number {\n return Date.UTC(date.year, date.month - 1, date.day);\n}\n","const color = {\n primary: '#242535',\n primaryB: '#515265',\n primary2: '#9335FB',\n primary2B: '#6323AA',\n secondary: '#FADD32',\n bgColor: '#F3F4F6',\n error: '#EA2E2E',\n\n white: '#FFFFFF',\n black: '#111111',\n gray1: '#424242',\n gray2: '#616161',\n gray3: '#8E8E8E',\n gray4: '#BDBDBD',\n gray5: '#E0E0E0',\n gray6: '#EDEDED',\n\n sub: '#A3A4BD',\n sub3: '#5561E8',\n sub3b: '#F1F7FF',\n sub4: '#1D1A1A',\n\n point: '#FADD32',\n} as const;\n\n/**\n * @deprecated use color instead (작명 실수 ㅠ)\n */\n\nconst colorV2 = color;\n\nexport { colorV2, color };\n","type TypographyKey =\n | 'h1-24b'\n | 'h1-24m'\n | 'h1-24'\n | 'h2-20b'\n | 'h2-20m'\n | 'h2-20'\n | 'h3-18b'\n | 'h3-18m'\n | 'h3-18'\n | 'b1-16b'\n | 'b1-16m'\n | 'b1-16'\n | 'b2-14b'\n | 'b2-14m'\n | 'b2-14'\n | 'b3-12b'\n | 'b3-12m'\n | 'b3-12'\n | 's1-10b'\n | 's1-10m'\n | 's1-10';\n\nconst typo = {\n 'h1-24b': {\n fontSize: 24,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24m': {\n fontSize: 24,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24': {\n fontSize: 24,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'h2-20b': {\n fontSize: 20,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20m': {\n fontSize: 20,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20': {\n fontSize: 20,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18b': {\n fontSize: 18,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18m': {\n fontSize: 18,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18': {\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16b': {\n fontSize: 16,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16m': {\n fontSize: 16,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16': {\n fontSize: 16,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b2-14b': {\n fontSize: 14,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14m': {\n fontSize: 14,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14': {\n fontSize: 14,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b3-12b': {\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12m': {\n fontSize: 12,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12': {\n fontSize: 12,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 's1-10b': {\n fontSize: 10,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10m': {\n fontSize: 10,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10': {\n fontSize: 10,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n} as const;\n\nconst transformLineheight = (key: TypographyKey) => {\n return typo[key].lineHeight * typo[key].fontSize;\n};\n\nconst mobileTypo = {\n 'h1-24b': {\n ...typo['h1-24b'],\n lineHeight: transformLineheight('h1-24b'),\n },\n\n 'h1-24m': {\n ...typo['h1-24m'],\n lineHeight: transformLineheight('h1-24m'),\n },\n\n 'h1-24': {\n ...typo['h1-24'],\n lineHeight: transformLineheight('h1-24'),\n },\n\n 'h2-20b': {\n ...typo['h2-20b'],\n lineHeight: transformLineheight('h2-20b'),\n },\n\n 'h2-20m': {\n ...typo['h2-20m'],\n lineHeight: transformLineheight('h2-20m'),\n },\n\n 'h2-20': {\n ...typo['h2-20'],\n lineHeight: transformLineheight('h2-20'),\n },\n\n 'h3-18b': {\n ...typo['h3-18b'],\n lineHeight: transformLineheight('h3-18b'),\n },\n\n 'h3-18m': {\n ...typo['h3-18m'],\n lineHeight: transformLineheight('h3-18m'),\n },\n\n 'h3-18': {\n ...typo['h3-18'],\n lineHeight: transformLineheight('h3-18'),\n },\n\n 'b1-16b': {\n ...typo['b1-16b'],\n lineHeight: transformLineheight('b1-16b'),\n },\n\n 'b1-16m': {\n ...typo['b1-16m'],\n lineHeight: transformLineheight('b1-16m'),\n },\n\n 'b1-16': {\n ...typo['b1-16'],\n lineHeight: transformLineheight('b1-16'),\n },\n\n 'b2-14b': {\n ...typo['b2-14b'],\n lineHeight: transformLineheight('b2-14b'),\n },\n\n 'b2-14m': {\n ...typo['b2-14m'],\n lineHeight: transformLineheight('b2-14m'),\n },\n\n 'b2-14': {\n ...typo['b2-14'],\n lineHeight: transformLineheight('b2-14'),\n },\n\n 'b3-12b': {\n ...typo['b3-12b'],\n lineHeight: transformLineheight('b3-12b'),\n },\n\n 'b3-12m': {\n ...typo['b3-12m'],\n lineHeight: transformLineheight('b3-12m'),\n },\n\n 'b3-12': {\n ...typo['b3-12'],\n lineHeight: transformLineheight('b3-12'),\n },\n\n 's1-10b': {\n ...typo['s1-10b'],\n lineHeight: transformLineheight('s1-10b'),\n },\n\n 's1-10m': {\n ...typo['s1-10m'],\n lineHeight: transformLineheight('s1-10m'),\n },\n\n 's1-10': {\n ...typo['s1-10'],\n lineHeight: transformLineheight('s1-10'),\n },\n} as const;\n\nexport { typo, mobileTypo };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/libs/cost.ts","../src/libs/gym-gold-charge.ts","../src/styles/color.ts","../src/styles/typo.ts"],"sourcesContent":["// Main entry point for spoclip-kit\nexport * from './libs';\nexport * from './types';\nexport * from './styles';\n","export const ORIGINAL_DOWNLOAD_COST_POLICY = {\n /**\n * 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.\n * 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).\n */\n freeSeconds: 30,\n /**\n * 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.\n */\n krwPerMinute: 100,\n /**\n * 전체 영상 다운로드 시 할인율(10%).\n */\n fullVideoDiscountRate: 0.1,\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 동시에 다운로드할 화각(camera angle) 수. 최소 1, 기본 1.\n * 모든 화각은 동일한 길이로 함께 받으므로 단일 화각 비용 × 화각 수로 과금한다.\n * 1 미만이거나 유한하지 않은 값은 1로 보정한다.\n */\n angleCount?: number;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { isFullVideo, angleCount } = options;\n\n // 화각 수는 최소 1로 보정한다. 정수가 아닌 값은 내림 처리.\n const normalizedAngleCount = Number.isFinite(angleCount)\n ? Math.max(1, Math.floor(angleCount as number))\n : 1;\n\n // 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n const billableSeconds = Math.max(\n 0,\n durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds,\n );\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다. 단일 화각 비용을 원 단위로 확정한 뒤\n // 화각 수만큼 곱한다 (각 화각을 개별 다운로드 건으로 과금).\n const rawCostPerAngle =\n billableSeconds * unitPricePerSecond * discountMultiplier;\n const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));\n\n return costPerAngle * normalizedAngleCount;\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n","const color = {\n primary: '#242535',\n primaryB: '#515265',\n primary2: '#9335FB',\n primary2B: '#6323AA',\n secondary: '#FADD32',\n bgColor: '#F3F4F6',\n error: '#EA2E2E',\n\n white: '#FFFFFF',\n black: '#111111',\n gray1: '#424242',\n gray2: '#616161',\n gray3: '#8E8E8E',\n gray4: '#BDBDBD',\n gray5: '#E0E0E0',\n gray6: '#EDEDED',\n\n sub: '#A3A4BD',\n sub3: '#5561E8',\n sub3b: '#F1F7FF',\n sub4: '#1D1A1A',\n\n point: '#FADD32',\n} as const;\n\n/**\n * @deprecated use color instead (작명 실수 ㅠ)\n */\n\nconst colorV2 = color;\n\nexport { colorV2, color };\n","type TypographyKey =\n | 'h1-24b'\n | 'h1-24m'\n | 'h1-24'\n | 'h2-20b'\n | 'h2-20m'\n | 'h2-20'\n | 'h3-18b'\n | 'h3-18m'\n | 'h3-18'\n | 'b1-16b'\n | 'b1-16m'\n | 'b1-16'\n | 'b2-14b'\n | 'b2-14m'\n | 'b2-14'\n | 'b3-12b'\n | 'b3-12m'\n | 'b3-12'\n | 's1-10b'\n | 's1-10m'\n | 's1-10';\n\nconst typo = {\n 'h1-24b': {\n fontSize: 24,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24m': {\n fontSize: 24,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24': {\n fontSize: 24,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'h2-20b': {\n fontSize: 20,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20m': {\n fontSize: 20,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20': {\n fontSize: 20,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18b': {\n fontSize: 18,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18m': {\n fontSize: 18,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18': {\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16b': {\n fontSize: 16,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16m': {\n fontSize: 16,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16': {\n fontSize: 16,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b2-14b': {\n fontSize: 14,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14m': {\n fontSize: 14,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14': {\n fontSize: 14,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b3-12b': {\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12m': {\n fontSize: 12,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12': {\n fontSize: 12,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 's1-10b': {\n fontSize: 10,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10m': {\n fontSize: 10,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10': {\n fontSize: 10,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n} as const;\n\nconst transformLineheight = (key: TypographyKey) => {\n return typo[key].lineHeight * typo[key].fontSize;\n};\n\nconst mobileTypo = {\n 'h1-24b': {\n ...typo['h1-24b'],\n lineHeight: transformLineheight('h1-24b'),\n },\n\n 'h1-24m': {\n ...typo['h1-24m'],\n lineHeight: transformLineheight('h1-24m'),\n },\n\n 'h1-24': {\n ...typo['h1-24'],\n lineHeight: transformLineheight('h1-24'),\n },\n\n 'h2-20b': {\n ...typo['h2-20b'],\n lineHeight: transformLineheight('h2-20b'),\n },\n\n 'h2-20m': {\n ...typo['h2-20m'],\n lineHeight: transformLineheight('h2-20m'),\n },\n\n 'h2-20': {\n ...typo['h2-20'],\n lineHeight: transformLineheight('h2-20'),\n },\n\n 'h3-18b': {\n ...typo['h3-18b'],\n lineHeight: transformLineheight('h3-18b'),\n },\n\n 'h3-18m': {\n ...typo['h3-18m'],\n lineHeight: transformLineheight('h3-18m'),\n },\n\n 'h3-18': {\n ...typo['h3-18'],\n lineHeight: transformLineheight('h3-18'),\n },\n\n 'b1-16b': {\n ...typo['b1-16b'],\n lineHeight: transformLineheight('b1-16b'),\n },\n\n 'b1-16m': {\n ...typo['b1-16m'],\n lineHeight: transformLineheight('b1-16m'),\n },\n\n 'b1-16': {\n ...typo['b1-16'],\n lineHeight: transformLineheight('b1-16'),\n },\n\n 'b2-14b': {\n ...typo['b2-14b'],\n lineHeight: transformLineheight('b2-14b'),\n },\n\n 'b2-14m': {\n ...typo['b2-14m'],\n lineHeight: transformLineheight('b2-14m'),\n },\n\n 'b2-14': {\n ...typo['b2-14'],\n lineHeight: transformLineheight('b2-14'),\n },\n\n 'b3-12b': {\n ...typo['b3-12b'],\n lineHeight: transformLineheight('b3-12b'),\n },\n\n 'b3-12m': {\n ...typo['b3-12m'],\n lineHeight: transformLineheight('b3-12m'),\n },\n\n 'b3-12': {\n ...typo['b3-12'],\n lineHeight: transformLineheight('b3-12'),\n },\n\n 's1-10b': {\n ...typo['s1-10b'],\n lineHeight: transformLineheight('s1-10b'),\n },\n\n 's1-10m': {\n ...typo['s1-10m'],\n lineHeight: transformLineheight('s1-10m'),\n },\n\n 's1-10': {\n ...typo['s1-10'],\n lineHeight: transformLineheight('s1-10'),\n },\n} as const;\n\nexport { typo, mobileTypo };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,gCAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM3C,aAAa;AAAA;AAAA;AAAA;AAAA,EAIb,cAAc;AAAA;AAAA;AAAA;AAAA,EAId,uBAAuB;AACzB;AAMO,IAAM,uBAAuB;AAM7B,SAAS,0BAA0B,iBAAkC;AAC1E,SACE,OAAO,SAAS,eAAe,KAAK,mBAAmB;AAE3D;AAYO,SAAS,iCACd,iBACA,UAAuC,CAAC,GAChC;AACR,MAAI,CAAC,OAAO,SAAS,eAAe,KAAK,mBAAmB,EAAG,QAAO;AAEtE,QAAM,EAAE,aAAa,WAAW,IAAI;AAGpC,QAAM,uBAAuB,OAAO,SAAS,UAAU,IACnD,KAAK,IAAI,GAAG,KAAK,MAAM,UAAoB,CAAC,IAC5C;AAGJ,QAAM,kBAAkB,KAAK;AAAA,IAC3B;AAAA,IACA,kBAAkB,8BAA8B;AAAA,EAClD;AACA,MAAI,mBAAmB,EAAG,QAAO;AAEjC,QAAM,qBAAqB,8BAA8B,eAAe;AAExE,QAAM,qBAAqB,cACvB,IAAI,8BAA8B,wBAClC;AAKJ,QAAM,kBACJ,kBAAkB,qBAAqB;AACzC,QAAM,eAAe,KAAK,KAAK,OAAO,gBAAgB,QAAQ,CAAC,CAAC,CAAC;AAEjE,SAAO,eAAe;AACxB;;;AC7EO,IAAM,yBAAyB;AAAA,EACpC,cAAc;AAAA,EACd,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc;AAChB;AAEO,SAAS,0BAA0B,WAA4B;AACpE,SACE,OAAO,SAAS,SAAS,KACzB,OAAO,UAAU,SAAS,KAC1B,aAAa,uBAAuB,gBACpC,YAAY,uBAAuB,kBAAkB;AAEzD;AAEO,SAAS,4BAA4B,WAAyB;AACnE,MAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC/B,UAAM,IAAI,WAAW,mCAAmC;AAAA,EAC1D;AAEA,MAAI,CAAC,OAAO,UAAU,SAAS,GAAG;AAChC,UAAM,IAAI,WAAW,8BAA8B;AAAA,EACrD;AAEA,MAAI,YAAY,uBAAuB,cAAc;AACnD,UAAM,IAAI;AAAA,MACR,8BAA8B,uBAAuB,YAAY;AAAA,IACnE;AAAA,EACF;AAEA,MAAI,YAAY,uBAAuB,kBAAkB,GAAG;AAC1D,UAAM,IAAI;AAAA,MACR,mCAAmC,uBAAuB,aAAa;AAAA,IACzE;AAAA,EACF;AACF;;;ACpCA,IAAM,QAAQ;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,EACT,OAAO;AAAA,EAEP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EAEP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EAEN,OAAO;AACT;AAMA,IAAM,UAAU;;;ACPhB,IAAM,OAAO;AAAA,EACX,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EAEA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EAEA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EAEA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EAEA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,SAAS;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AACF;AAEA,IAAM,sBAAsB,CAAC,QAAuB;AAClD,SAAO,KAAK,GAAG,EAAE,aAAa,KAAK,GAAG,EAAE;AAC1C;AAEA,IAAM,aAAa;AAAA,EACjB,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,SAAS;AAAA,IACP,GAAG,KAAK,OAAO;AAAA,IACf,YAAY,oBAAoB,OAAO;AAAA,EACzC;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,SAAS;AAAA,IACP,GAAG,KAAK,OAAO;AAAA,IACf,YAAY,oBAAoB,OAAO;AAAA,EACzC;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,SAAS;AAAA,IACP,GAAG,KAAK,OAAO;AAAA,IACf,YAAY,oBAAoB,OAAO;AAAA,EACzC;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,SAAS;AAAA,IACP,GAAG,KAAK,OAAO;AAAA,IACf,YAAY,oBAAoB,OAAO;AAAA,EACzC;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,SAAS;AAAA,IACP,GAAG,KAAK,OAAO;AAAA,IACf,YAAY,oBAAoB,OAAO;AAAA,EACzC;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,SAAS;AAAA,IACP,GAAG,KAAK,OAAO;AAAA,IACf,YAAY,oBAAoB,OAAO;AAAA,EACzC;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,UAAU;AAAA,IACR,GAAG,KAAK,QAAQ;AAAA,IAChB,YAAY,oBAAoB,QAAQ;AAAA,EAC1C;AAAA,EAEA,SAAS;AAAA,IACP,GAAG,KAAK,OAAO;AAAA,IACf,YAAY,oBAAoB,OAAO;AAAA,EACzC;AACF;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { GYM_GOLD_CHARGE_POLICY,
|
|
2
|
-
export { T as TicketCode } from './membership-C_ziSHS0.cjs';
|
|
1
|
+
export { GYM_GOLD_CHARGE_POLICY, MIN_DOWNLOAD_SECONDS, ORIGINAL_DOWNLOAD_COST_POLICY, OriginalDownloadCostOptions, assertValidGymGoldChargeKrw, calculateOriginalDownloadCostWon, isDownloadDurationAllowed, isGymGoldChargeKrwAllowed } from './libs.cjs';
|
|
3
2
|
export { APIErrorResponse, APIResponse, APIResponseBase } from './types.cjs';
|
|
4
3
|
export { color, colorV2, mobileTypo, typo } from './styles.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { GYM_GOLD_CHARGE_POLICY,
|
|
2
|
-
export { T as TicketCode } from './membership-C_ziSHS0.js';
|
|
1
|
+
export { GYM_GOLD_CHARGE_POLICY, MIN_DOWNLOAD_SECONDS, ORIGINAL_DOWNLOAD_COST_POLICY, OriginalDownloadCostOptions, assertValidGymGoldChargeKrw, calculateOriginalDownloadCostWon, isDownloadDurationAllowed, isGymGoldChargeKrwAllowed } from './libs.js';
|
|
3
2
|
export { APIErrorResponse, APIResponse, APIResponseBase } from './types.js';
|
|
4
3
|
export { color, colorV2, mobileTypo, typo } from './styles.js';
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
// src/libs/cost.ts
|
|
2
2
|
var ORIGINAL_DOWNLOAD_COST_POLICY = {
|
|
3
|
-
baseDurationSeconds: 600,
|
|
4
|
-
baseCostWon: 2e3,
|
|
5
|
-
fullVideoDiscountRate: 0.1,
|
|
6
3
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.
|
|
5
|
+
* 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,
|
|
6
|
+
* 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).
|
|
10
7
|
*/
|
|
11
|
-
|
|
12
|
-
FREE: 100,
|
|
13
|
-
PLUS: 50,
|
|
14
|
-
PRO: 25
|
|
15
|
-
},
|
|
8
|
+
freeSeconds: 30,
|
|
16
9
|
/**
|
|
17
|
-
* 멤버십
|
|
18
|
-
* 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,
|
|
19
|
-
* 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).
|
|
20
|
-
* plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.
|
|
10
|
+
* 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.
|
|
21
11
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
krwPerMinute: 100,
|
|
13
|
+
/**
|
|
14
|
+
* 전체 영상 다운로드 시 할인율(10%).
|
|
15
|
+
*/
|
|
16
|
+
fullVideoDiscountRate: 0.1
|
|
27
17
|
};
|
|
28
18
|
var MIN_DOWNLOAD_SECONDS = 10;
|
|
29
19
|
function isDownloadDurationAllowed(durationSeconds) {
|
|
@@ -31,14 +21,18 @@ function isDownloadDurationAllowed(durationSeconds) {
|
|
|
31
21
|
}
|
|
32
22
|
function calculateOriginalDownloadCostWon(durationSeconds, options = {}) {
|
|
33
23
|
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;
|
|
34
|
-
const {
|
|
35
|
-
const
|
|
36
|
-
const billableSeconds = Math.max(
|
|
24
|
+
const { isFullVideo, angleCount } = options;
|
|
25
|
+
const normalizedAngleCount = Number.isFinite(angleCount) ? Math.max(1, Math.floor(angleCount)) : 1;
|
|
26
|
+
const billableSeconds = Math.max(
|
|
27
|
+
0,
|
|
28
|
+
durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds
|
|
29
|
+
);
|
|
37
30
|
if (billableSeconds <= 0) return 0;
|
|
38
|
-
const unitPricePerSecond =
|
|
31
|
+
const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;
|
|
39
32
|
const discountMultiplier = isFullVideo ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate : 1;
|
|
40
|
-
const
|
|
41
|
-
|
|
33
|
+
const rawCostPerAngle = billableSeconds * unitPricePerSecond * discountMultiplier;
|
|
34
|
+
const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));
|
|
35
|
+
return costPerAngle * normalizedAngleCount;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
// src/libs/gym-gold-charge.ts
|
|
@@ -48,17 +42,6 @@ var GYM_GOLD_CHARGE_POLICY = {
|
|
|
48
42
|
bonusRate: 0.2,
|
|
49
43
|
bonusRateBps: 2e3
|
|
50
44
|
};
|
|
51
|
-
function calculateGymGoldCharge(input) {
|
|
52
|
-
assertValidGymGoldChargeKrw(input.chargeKrw);
|
|
53
|
-
const baseGold = input.chargeKrw;
|
|
54
|
-
const bonusGold = calculateBonusGold(input.chargeKrw);
|
|
55
|
-
return {
|
|
56
|
-
chargeKrw: input.chargeKrw,
|
|
57
|
-
baseGold,
|
|
58
|
-
bonusGold,
|
|
59
|
-
totalGold: baseGold + bonusGold
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
45
|
function isGymGoldChargeKrwAllowed(chargeKrw) {
|
|
63
46
|
return Number.isFinite(chargeKrw) && Number.isInteger(chargeKrw) && chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw && chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0;
|
|
64
47
|
}
|
|
@@ -80,62 +63,6 @@ function assertValidGymGoldChargeKrw(chargeKrw) {
|
|
|
80
63
|
);
|
|
81
64
|
}
|
|
82
65
|
}
|
|
83
|
-
function calculateBonusGold(chargeKrw) {
|
|
84
|
-
return Math.floor(chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps / 1e4);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/libs/gym-registration-gold.ts
|
|
88
|
-
var GYM_PARTNER_REGISTRATION_GOLD_POLICY = {
|
|
89
|
-
goldPerDay: 350,
|
|
90
|
-
timeZone: "Asia/Seoul"
|
|
91
|
-
};
|
|
92
|
-
var DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
93
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
94
|
-
function calculateGymPartnerRegistrationGold(input) {
|
|
95
|
-
const startDate = parseKstDateString(input.startDate, "startDate");
|
|
96
|
-
const endDate = parseKstDateString(input.endDate, "endDate");
|
|
97
|
-
const startDateKey = toUtcDateKey(startDate);
|
|
98
|
-
const endDateKey = toUtcDateKey(endDate);
|
|
99
|
-
if (endDateKey < startDateKey) {
|
|
100
|
-
throw new RangeError("endDate must be on or after startDate");
|
|
101
|
-
}
|
|
102
|
-
const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;
|
|
103
|
-
return {
|
|
104
|
-
days,
|
|
105
|
-
goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function calculateGymPartnerRegistrationGoldTotal(periods) {
|
|
109
|
-
const items = periods.map((period) => {
|
|
110
|
-
const result = calculateGymPartnerRegistrationGold(period);
|
|
111
|
-
return {
|
|
112
|
-
...period,
|
|
113
|
-
...result
|
|
114
|
-
};
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
totalDays: items.reduce((sum, item) => sum + item.days, 0),
|
|
118
|
-
totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),
|
|
119
|
-
items
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
function parseKstDateString(input, fieldName) {
|
|
123
|
-
const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);
|
|
124
|
-
if (dateOnlyMatch === null) {
|
|
125
|
-
throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);
|
|
126
|
-
}
|
|
127
|
-
const year = Number(dateOnlyMatch[1]);
|
|
128
|
-
const month = Number(dateOnlyMatch[2]);
|
|
129
|
-
const day = Number(dateOnlyMatch[3]);
|
|
130
|
-
const utcDate = new Date(Date.UTC(year, month - 1, day));
|
|
131
|
-
if (utcDate.getUTCFullYear() !== year || utcDate.getUTCMonth() + 1 !== month || utcDate.getUTCDate() !== day) {
|
|
132
|
-
throw new RangeError(`${fieldName} must be a valid date`);
|
|
133
|
-
}
|
|
134
|
-
return { year, month, day };
|
|
135
|
-
}
|
|
136
|
-
function toUtcDateKey(date) {
|
|
137
|
-
return Date.UTC(date.year, date.month - 1, date.day);
|
|
138
|
-
}
|
|
139
66
|
|
|
140
67
|
// src/styles/color.ts
|
|
141
68
|
var color = {
|
|
@@ -382,13 +309,9 @@ var mobileTypo = {
|
|
|
382
309
|
};
|
|
383
310
|
export {
|
|
384
311
|
GYM_GOLD_CHARGE_POLICY,
|
|
385
|
-
GYM_PARTNER_REGISTRATION_GOLD_POLICY,
|
|
386
312
|
MIN_DOWNLOAD_SECONDS,
|
|
387
313
|
ORIGINAL_DOWNLOAD_COST_POLICY,
|
|
388
314
|
assertValidGymGoldChargeKrw,
|
|
389
|
-
calculateGymGoldCharge,
|
|
390
|
-
calculateGymPartnerRegistrationGold,
|
|
391
|
-
calculateGymPartnerRegistrationGoldTotal,
|
|
392
315
|
calculateOriginalDownloadCostWon,
|
|
393
316
|
color,
|
|
394
317
|
colorV2,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/libs/cost.ts","../src/libs/gym-gold-charge.ts","../src/libs/gym-registration-gold.ts","../src/styles/color.ts","../src/styles/typo.ts"],"sourcesContent":["import type { TicketCode } from '../types/membership';\n\nexport const ORIGINAL_DOWNLOAD_COST_POLICY = {\n baseDurationSeconds: 600,\n baseCostWon: 2000,\n fullVideoDiscountRate: 0.1,\n /**\n * v14 신정책: 멤버십 plan별 분당 KRW 요율.\n * `calculateOriginalDownloadCostWon(duration, { plan })`로 호출 시 적용된다.\n * plan 미지정 시 `baseCostWon/baseDurationSeconds` 기반 단일 단가(레거시 200원/분)로 폴백 — 기존 호출자 호환.\n */\n perPlanKrwPerMinute: {\n FREE: 100,\n PLUS: 50,\n PRO: 25,\n },\n /**\n * 멤버십 plan별 무료 다운로드 구간(초).\n * 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).\n * plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.\n */\n freeSecondsByPlan: {\n FREE: 10,\n PLUS: 20,\n PRO: 30,\n },\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 멤버십 plan. 지정 시 `perPlanKrwPerMinute[plan]`을 단가로,\n * `freeSecondsByPlan[plan]`을 무료 구간으로 적용한다.\n * 미지정(undefined) 시 레거시 단일 단가(`baseCostWon/baseDurationSeconds`) + 무료 구간 없음.\n */\n plan?: TicketCode;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { plan, isFullVideo } = options;\n\n // plan 지정 시 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n // 레거시 경로(plan 미지정)는 무료 구간 0으로 기존 동작을 유지한다.\n const freeSeconds =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.freeSecondsByPlan[plan]\n : 0;\n const billableSeconds = Math.max(0, durationSeconds - freeSeconds);\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.perPlanKrwPerMinute[plan] / 60\n : ORIGINAL_DOWNLOAD_COST_POLICY.baseCostWon /\n ORIGINAL_DOWNLOAD_COST_POLICY.baseDurationSeconds;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다.\n const rawCost = billableSeconds * unitPricePerSecond * discountMultiplier;\n return Math.ceil(Number(rawCost.toFixed(6)));\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport interface GymGoldChargeInput {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n}\n\nexport interface GymGoldChargeResult {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n /**\n * 1:1 base Gold granted for paid KRW.\n */\n baseGold: number;\n /**\n * Additional bonus Gold granted by charge policy.\n */\n bonusGold: number;\n /**\n * Final Gold amount credited to the gym balance.\n */\n totalGold: number;\n}\n\nexport function calculateGymGoldCharge(\n input: GymGoldChargeInput,\n): GymGoldChargeResult {\n assertValidGymGoldChargeKrw(input.chargeKrw);\n\n const baseGold = input.chargeKrw;\n const bonusGold = calculateBonusGold(input.chargeKrw);\n\n return {\n chargeKrw: input.chargeKrw,\n baseGold,\n bonusGold,\n totalGold: baseGold + bonusGold,\n };\n}\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n\nfunction calculateBonusGold(chargeKrw: number): number {\n return Math.floor((chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps) / 10_000);\n}\n","export const GYM_PARTNER_REGISTRATION_GOLD_POLICY = {\n goldPerDay: 350,\n timeZone: 'Asia/Seoul',\n} as const;\n\nexport interface GymPartnerRegistrationGoldInput {\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n startDate: string;\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n endDate: string;\n}\n\nexport interface GymPartnerRegistrationGoldResult {\n days: number;\n goldAmount: number;\n}\n\nexport interface GymPartnerRegistrationGoldTotalItem\n extends GymPartnerRegistrationGoldInput,\n GymPartnerRegistrationGoldResult {}\n\nexport interface GymPartnerRegistrationGoldTotalResult {\n totalDays: number;\n totalGoldAmount: number;\n items: GymPartnerRegistrationGoldTotalItem[];\n}\n\ninterface KstDateParts {\n year: number;\n month: number;\n day: number;\n}\n\nconst DATE_ONLY_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\nexport function calculateGymPartnerRegistrationGold(\n input: GymPartnerRegistrationGoldInput,\n): GymPartnerRegistrationGoldResult {\n const startDate = parseKstDateString(input.startDate, 'startDate');\n const endDate = parseKstDateString(input.endDate, 'endDate');\n\n const startDateKey = toUtcDateKey(startDate);\n const endDateKey = toUtcDateKey(endDate);\n\n if (endDateKey < startDateKey) {\n throw new RangeError('endDate must be on or after startDate');\n }\n\n const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;\n\n return {\n days,\n goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay,\n };\n}\n\nexport function calculateGymPartnerRegistrationGoldTotal(\n periods: GymPartnerRegistrationGoldInput[],\n): GymPartnerRegistrationGoldTotalResult {\n const items = periods.map((period) => {\n const result = calculateGymPartnerRegistrationGold(period);\n\n return {\n ...period,\n ...result,\n };\n });\n\n return {\n totalDays: items.reduce((sum, item) => sum + item.days, 0),\n totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),\n items,\n };\n}\n\nfunction parseKstDateString(\n input: string,\n fieldName: 'startDate' | 'endDate',\n): KstDateParts {\n const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);\n if (dateOnlyMatch === null) {\n throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);\n }\n\n const year = Number(dateOnlyMatch[1]);\n const month = Number(dateOnlyMatch[2]);\n const day = Number(dateOnlyMatch[3]);\n const utcDate = new Date(Date.UTC(year, month - 1, day));\n\n if (\n utcDate.getUTCFullYear() !== year ||\n utcDate.getUTCMonth() + 1 !== month ||\n utcDate.getUTCDate() !== day\n ) {\n throw new RangeError(`${fieldName} must be a valid date`);\n }\n\n return { year, month, day };\n}\n\nfunction toUtcDateKey(date: KstDateParts): number {\n return Date.UTC(date.year, date.month - 1, date.day);\n}\n","const color = {\n primary: '#242535',\n primaryB: '#515265',\n primary2: '#9335FB',\n primary2B: '#6323AA',\n secondary: '#FADD32',\n bgColor: '#F3F4F6',\n error: '#EA2E2E',\n\n white: '#FFFFFF',\n black: '#111111',\n gray1: '#424242',\n gray2: '#616161',\n gray3: '#8E8E8E',\n gray4: '#BDBDBD',\n gray5: '#E0E0E0',\n gray6: '#EDEDED',\n\n sub: '#A3A4BD',\n sub3: '#5561E8',\n sub3b: '#F1F7FF',\n sub4: '#1D1A1A',\n\n point: '#FADD32',\n} as const;\n\n/**\n * @deprecated use color instead (작명 실수 ㅠ)\n */\n\nconst colorV2 = color;\n\nexport { colorV2, color };\n","type TypographyKey =\n | 'h1-24b'\n | 'h1-24m'\n | 'h1-24'\n | 'h2-20b'\n | 'h2-20m'\n | 'h2-20'\n | 'h3-18b'\n | 'h3-18m'\n | 'h3-18'\n | 'b1-16b'\n | 'b1-16m'\n | 'b1-16'\n | 'b2-14b'\n | 'b2-14m'\n | 'b2-14'\n | 'b3-12b'\n | 'b3-12m'\n | 'b3-12'\n | 's1-10b'\n | 's1-10m'\n | 's1-10';\n\nconst typo = {\n 'h1-24b': {\n fontSize: 24,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24m': {\n fontSize: 24,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24': {\n fontSize: 24,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'h2-20b': {\n fontSize: 20,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20m': {\n fontSize: 20,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20': {\n fontSize: 20,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18b': {\n fontSize: 18,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18m': {\n fontSize: 18,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18': {\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16b': {\n fontSize: 16,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16m': {\n fontSize: 16,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16': {\n fontSize: 16,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b2-14b': {\n fontSize: 14,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14m': {\n fontSize: 14,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14': {\n fontSize: 14,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b3-12b': {\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12m': {\n fontSize: 12,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12': {\n fontSize: 12,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 's1-10b': {\n fontSize: 10,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10m': {\n fontSize: 10,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10': {\n fontSize: 10,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n} as const;\n\nconst transformLineheight = (key: TypographyKey) => {\n return typo[key].lineHeight * typo[key].fontSize;\n};\n\nconst mobileTypo = {\n 'h1-24b': {\n ...typo['h1-24b'],\n lineHeight: transformLineheight('h1-24b'),\n },\n\n 'h1-24m': {\n ...typo['h1-24m'],\n lineHeight: transformLineheight('h1-24m'),\n },\n\n 'h1-24': {\n ...typo['h1-24'],\n lineHeight: transformLineheight('h1-24'),\n },\n\n 'h2-20b': {\n ...typo['h2-20b'],\n lineHeight: transformLineheight('h2-20b'),\n },\n\n 'h2-20m': {\n ...typo['h2-20m'],\n lineHeight: transformLineheight('h2-20m'),\n },\n\n 'h2-20': {\n ...typo['h2-20'],\n lineHeight: transformLineheight('h2-20'),\n },\n\n 'h3-18b': {\n ...typo['h3-18b'],\n lineHeight: transformLineheight('h3-18b'),\n },\n\n 'h3-18m': {\n ...typo['h3-18m'],\n lineHeight: transformLineheight('h3-18m'),\n },\n\n 'h3-18': {\n ...typo['h3-18'],\n lineHeight: transformLineheight('h3-18'),\n },\n\n 'b1-16b': {\n ...typo['b1-16b'],\n lineHeight: transformLineheight('b1-16b'),\n },\n\n 'b1-16m': {\n ...typo['b1-16m'],\n lineHeight: transformLineheight('b1-16m'),\n },\n\n 'b1-16': {\n ...typo['b1-16'],\n lineHeight: transformLineheight('b1-16'),\n },\n\n 'b2-14b': {\n ...typo['b2-14b'],\n lineHeight: transformLineheight('b2-14b'),\n },\n\n 'b2-14m': {\n ...typo['b2-14m'],\n lineHeight: transformLineheight('b2-14m'),\n },\n\n 'b2-14': {\n ...typo['b2-14'],\n lineHeight: transformLineheight('b2-14'),\n },\n\n 'b3-12b': {\n ...typo['b3-12b'],\n lineHeight: transformLineheight('b3-12b'),\n },\n\n 'b3-12m': {\n ...typo['b3-12m'],\n lineHeight: transformLineheight('b3-12m'),\n },\n\n 'b3-12': {\n ...typo['b3-12'],\n lineHeight: transformLineheight('b3-12'),\n },\n\n 's1-10b': {\n ...typo['s1-10b'],\n lineHeight: transformLineheight('s1-10b'),\n },\n\n 's1-10m': {\n ...typo['s1-10m'],\n lineHeight: transformLineheight('s1-10m'),\n },\n\n 's1-10': {\n ...typo['s1-10'],\n lineHeight: transformLineheight('s1-10'),\n },\n} as const;\n\nexport { typo, mobileTypo };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/libs/cost.ts","../src/libs/gym-gold-charge.ts","../src/styles/color.ts","../src/styles/typo.ts"],"sourcesContent":["export const ORIGINAL_DOWNLOAD_COST_POLICY = {\n /**\n * 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.\n * 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).\n */\n freeSeconds: 30,\n /**\n * 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.\n */\n krwPerMinute: 100,\n /**\n * 전체 영상 다운로드 시 할인율(10%).\n */\n fullVideoDiscountRate: 0.1,\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 동시에 다운로드할 화각(camera angle) 수. 최소 1, 기본 1.\n * 모든 화각은 동일한 길이로 함께 받으므로 단일 화각 비용 × 화각 수로 과금한다.\n * 1 미만이거나 유한하지 않은 값은 1로 보정한다.\n */\n angleCount?: number;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { isFullVideo, angleCount } = options;\n\n // 화각 수는 최소 1로 보정한다. 정수가 아닌 값은 내림 처리.\n const normalizedAngleCount = Number.isFinite(angleCount)\n ? Math.max(1, Math.floor(angleCount as number))\n : 1;\n\n // 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n const billableSeconds = Math.max(\n 0,\n durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds,\n );\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다. 단일 화각 비용을 원 단위로 확정한 뒤\n // 화각 수만큼 곱한다 (각 화각을 개별 다운로드 건으로 과금).\n const rawCostPerAngle =\n billableSeconds * unitPricePerSecond * discountMultiplier;\n const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));\n\n return costPerAngle * normalizedAngleCount;\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n","const color = {\n primary: '#242535',\n primaryB: '#515265',\n primary2: '#9335FB',\n primary2B: '#6323AA',\n secondary: '#FADD32',\n bgColor: '#F3F4F6',\n error: '#EA2E2E',\n\n white: '#FFFFFF',\n black: '#111111',\n gray1: '#424242',\n gray2: '#616161',\n gray3: '#8E8E8E',\n gray4: '#BDBDBD',\n gray5: '#E0E0E0',\n gray6: '#EDEDED',\n\n sub: '#A3A4BD',\n sub3: '#5561E8',\n sub3b: '#F1F7FF',\n sub4: '#1D1A1A',\n\n point: '#FADD32',\n} as const;\n\n/**\n * @deprecated use color instead (작명 실수 ㅠ)\n */\n\nconst colorV2 = color;\n\nexport { colorV2, color };\n","type TypographyKey =\n | 'h1-24b'\n | 'h1-24m'\n | 'h1-24'\n | 'h2-20b'\n | 'h2-20m'\n | 'h2-20'\n | 'h3-18b'\n | 'h3-18m'\n | 'h3-18'\n | 'b1-16b'\n | 'b1-16m'\n | 'b1-16'\n | 'b2-14b'\n | 'b2-14m'\n | 'b2-14'\n | 'b3-12b'\n | 'b3-12m'\n | 'b3-12'\n | 's1-10b'\n | 's1-10m'\n | 's1-10';\n\nconst typo = {\n 'h1-24b': {\n fontSize: 24,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24m': {\n fontSize: 24,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h1-24': {\n fontSize: 24,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'h2-20b': {\n fontSize: 20,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20m': {\n fontSize: 20,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h2-20': {\n fontSize: 20,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18b': {\n fontSize: 18,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18m': {\n fontSize: 18,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'h3-18': {\n fontSize: 18,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16b': {\n fontSize: 16,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16m': {\n fontSize: 16,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b1-16': {\n fontSize: 16,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b2-14b': {\n fontSize: 14,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14m': {\n fontSize: 14,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b2-14': {\n fontSize: 14,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 'b3-12b': {\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12m': {\n fontSize: 12,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 'b3-12': {\n fontSize: 12,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n\n 's1-10b': {\n fontSize: 10,\n fontWeight: 700,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10m': {\n fontSize: 10,\n fontWeight: 500,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n 's1-10': {\n fontSize: 10,\n fontWeight: 400,\n lineHeight: 1.5,\n letterSpacing: 0,\n },\n} as const;\n\nconst transformLineheight = (key: TypographyKey) => {\n return typo[key].lineHeight * typo[key].fontSize;\n};\n\nconst mobileTypo = {\n 'h1-24b': {\n ...typo['h1-24b'],\n lineHeight: transformLineheight('h1-24b'),\n },\n\n 'h1-24m': {\n ...typo['h1-24m'],\n lineHeight: transformLineheight('h1-24m'),\n },\n\n 'h1-24': {\n ...typo['h1-24'],\n lineHeight: transformLineheight('h1-24'),\n },\n\n 'h2-20b': {\n ...typo['h2-20b'],\n lineHeight: transformLineheight('h2-20b'),\n },\n\n 'h2-20m': {\n ...typo['h2-20m'],\n lineHeight: transformLineheight('h2-20m'),\n },\n\n 'h2-20': {\n ...typo['h2-20'],\n lineHeight: transformLineheight('h2-20'),\n },\n\n 'h3-18b': {\n ...typo['h3-18b'],\n lineHeight: transformLineheight('h3-18b'),\n },\n\n 'h3-18m': {\n ...typo['h3-18m'],\n lineHeight: transformLineheight('h3-18m'),\n },\n\n 'h3-18': {\n ...typo['h3-18'],\n lineHeight: transformLineheight('h3-18'),\n },\n\n 'b1-16b': {\n ...typo['b1-16b'],\n lineHeight: transformLineheight('b1-16b'),\n },\n\n 'b1-16m': {\n ...typo['b1-16m'],\n lineHeight: transformLineheight('b1-16m'),\n },\n\n 'b1-16': {\n ...typo['b1-16'],\n lineHeight: transformLineheight('b1-16'),\n },\n\n 'b2-14b': {\n ...typo['b2-14b'],\n lineHeight: transformLineheight('b2-14b'),\n },\n\n 'b2-14m': {\n ...typo['b2-14m'],\n lineHeight: transformLineheight('b2-14m'),\n },\n\n 'b2-14': {\n ...typo['b2-14'],\n lineHeight: transformLineheight('b2-14'),\n },\n\n 'b3-12b': {\n ...typo['b3-12b'],\n lineHeight: transformLineheight('b3-12b'),\n },\n\n 'b3-12m': {\n ...typo['b3-12m'],\n lineHeight: transformLineheight('b3-12m'),\n },\n\n 'b3-12': {\n ...typo['b3-12'],\n lineHeight: transformLineheight('b3-12'),\n },\n\n 's1-10b': {\n ...typo['s1-10b'],\n lineHeight: transformLineheight('s1-10b'),\n },\n\n 's1-10m': {\n ...typo['s1-10m'],\n lineHeight: transformLineheight('s1-10m'),\n },\n\n 's1-10': {\n ...typo['s1-10'],\n lineHeight: transformLineheight('s1-10'),\n },\n} as const;\n\nexport { typo, mobileTypo };\n"],"mappings":"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","names":[]}
|
package/dist/libs.cjs
CHANGED
|
@@ -21,13 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var libs_exports = {};
|
|
22
22
|
__export(libs_exports, {
|
|
23
23
|
GYM_GOLD_CHARGE_POLICY: () => GYM_GOLD_CHARGE_POLICY,
|
|
24
|
-
GYM_PARTNER_REGISTRATION_GOLD_POLICY: () => GYM_PARTNER_REGISTRATION_GOLD_POLICY,
|
|
25
24
|
MIN_DOWNLOAD_SECONDS: () => MIN_DOWNLOAD_SECONDS,
|
|
26
25
|
ORIGINAL_DOWNLOAD_COST_POLICY: () => ORIGINAL_DOWNLOAD_COST_POLICY,
|
|
27
26
|
assertValidGymGoldChargeKrw: () => assertValidGymGoldChargeKrw,
|
|
28
|
-
calculateGymGoldCharge: () => calculateGymGoldCharge,
|
|
29
|
-
calculateGymPartnerRegistrationGold: () => calculateGymPartnerRegistrationGold,
|
|
30
|
-
calculateGymPartnerRegistrationGoldTotal: () => calculateGymPartnerRegistrationGoldTotal,
|
|
31
27
|
calculateOriginalDownloadCostWon: () => calculateOriginalDownloadCostWon,
|
|
32
28
|
isDownloadDurationAllowed: () => isDownloadDurationAllowed,
|
|
33
29
|
isGymGoldChargeKrwAllowed: () => isGymGoldChargeKrwAllowed
|
|
@@ -36,30 +32,20 @@ module.exports = __toCommonJS(libs_exports);
|
|
|
36
32
|
|
|
37
33
|
// src/libs/cost.ts
|
|
38
34
|
var ORIGINAL_DOWNLOAD_COST_POLICY = {
|
|
39
|
-
baseDurationSeconds: 600,
|
|
40
|
-
baseCostWon: 2e3,
|
|
41
|
-
fullVideoDiscountRate: 0.1,
|
|
42
35
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
36
|
+
* 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.
|
|
37
|
+
* 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,
|
|
38
|
+
* 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).
|
|
46
39
|
*/
|
|
47
|
-
|
|
48
|
-
FREE: 100,
|
|
49
|
-
PLUS: 50,
|
|
50
|
-
PRO: 25
|
|
51
|
-
},
|
|
40
|
+
freeSeconds: 30,
|
|
52
41
|
/**
|
|
53
|
-
* 멤버십
|
|
54
|
-
* 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,
|
|
55
|
-
* 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).
|
|
56
|
-
* plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.
|
|
42
|
+
* 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.
|
|
57
43
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
44
|
+
krwPerMinute: 100,
|
|
45
|
+
/**
|
|
46
|
+
* 전체 영상 다운로드 시 할인율(10%).
|
|
47
|
+
*/
|
|
48
|
+
fullVideoDiscountRate: 0.1
|
|
63
49
|
};
|
|
64
50
|
var MIN_DOWNLOAD_SECONDS = 10;
|
|
65
51
|
function isDownloadDurationAllowed(durationSeconds) {
|
|
@@ -67,14 +53,18 @@ function isDownloadDurationAllowed(durationSeconds) {
|
|
|
67
53
|
}
|
|
68
54
|
function calculateOriginalDownloadCostWon(durationSeconds, options = {}) {
|
|
69
55
|
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;
|
|
70
|
-
const {
|
|
71
|
-
const
|
|
72
|
-
const billableSeconds = Math.max(
|
|
56
|
+
const { isFullVideo, angleCount } = options;
|
|
57
|
+
const normalizedAngleCount = Number.isFinite(angleCount) ? Math.max(1, Math.floor(angleCount)) : 1;
|
|
58
|
+
const billableSeconds = Math.max(
|
|
59
|
+
0,
|
|
60
|
+
durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds
|
|
61
|
+
);
|
|
73
62
|
if (billableSeconds <= 0) return 0;
|
|
74
|
-
const unitPricePerSecond =
|
|
63
|
+
const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;
|
|
75
64
|
const discountMultiplier = isFullVideo ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate : 1;
|
|
76
|
-
const
|
|
77
|
-
|
|
65
|
+
const rawCostPerAngle = billableSeconds * unitPricePerSecond * discountMultiplier;
|
|
66
|
+
const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));
|
|
67
|
+
return costPerAngle * normalizedAngleCount;
|
|
78
68
|
}
|
|
79
69
|
|
|
80
70
|
// src/libs/gym-gold-charge.ts
|
|
@@ -84,17 +74,6 @@ var GYM_GOLD_CHARGE_POLICY = {
|
|
|
84
74
|
bonusRate: 0.2,
|
|
85
75
|
bonusRateBps: 2e3
|
|
86
76
|
};
|
|
87
|
-
function calculateGymGoldCharge(input) {
|
|
88
|
-
assertValidGymGoldChargeKrw(input.chargeKrw);
|
|
89
|
-
const baseGold = input.chargeKrw;
|
|
90
|
-
const bonusGold = calculateBonusGold(input.chargeKrw);
|
|
91
|
-
return {
|
|
92
|
-
chargeKrw: input.chargeKrw,
|
|
93
|
-
baseGold,
|
|
94
|
-
bonusGold,
|
|
95
|
-
totalGold: baseGold + bonusGold
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
77
|
function isGymGoldChargeKrwAllowed(chargeKrw) {
|
|
99
78
|
return Number.isFinite(chargeKrw) && Number.isInteger(chargeKrw) && chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw && chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0;
|
|
100
79
|
}
|
|
@@ -116,72 +95,12 @@ function assertValidGymGoldChargeKrw(chargeKrw) {
|
|
|
116
95
|
);
|
|
117
96
|
}
|
|
118
97
|
}
|
|
119
|
-
function calculateBonusGold(chargeKrw) {
|
|
120
|
-
return Math.floor(chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps / 1e4);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// src/libs/gym-registration-gold.ts
|
|
124
|
-
var GYM_PARTNER_REGISTRATION_GOLD_POLICY = {
|
|
125
|
-
goldPerDay: 350,
|
|
126
|
-
timeZone: "Asia/Seoul"
|
|
127
|
-
};
|
|
128
|
-
var DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
129
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
130
|
-
function calculateGymPartnerRegistrationGold(input) {
|
|
131
|
-
const startDate = parseKstDateString(input.startDate, "startDate");
|
|
132
|
-
const endDate = parseKstDateString(input.endDate, "endDate");
|
|
133
|
-
const startDateKey = toUtcDateKey(startDate);
|
|
134
|
-
const endDateKey = toUtcDateKey(endDate);
|
|
135
|
-
if (endDateKey < startDateKey) {
|
|
136
|
-
throw new RangeError("endDate must be on or after startDate");
|
|
137
|
-
}
|
|
138
|
-
const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;
|
|
139
|
-
return {
|
|
140
|
-
days,
|
|
141
|
-
goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
function calculateGymPartnerRegistrationGoldTotal(periods) {
|
|
145
|
-
const items = periods.map((period) => {
|
|
146
|
-
const result = calculateGymPartnerRegistrationGold(period);
|
|
147
|
-
return {
|
|
148
|
-
...period,
|
|
149
|
-
...result
|
|
150
|
-
};
|
|
151
|
-
});
|
|
152
|
-
return {
|
|
153
|
-
totalDays: items.reduce((sum, item) => sum + item.days, 0),
|
|
154
|
-
totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),
|
|
155
|
-
items
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function parseKstDateString(input, fieldName) {
|
|
159
|
-
const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);
|
|
160
|
-
if (dateOnlyMatch === null) {
|
|
161
|
-
throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);
|
|
162
|
-
}
|
|
163
|
-
const year = Number(dateOnlyMatch[1]);
|
|
164
|
-
const month = Number(dateOnlyMatch[2]);
|
|
165
|
-
const day = Number(dateOnlyMatch[3]);
|
|
166
|
-
const utcDate = new Date(Date.UTC(year, month - 1, day));
|
|
167
|
-
if (utcDate.getUTCFullYear() !== year || utcDate.getUTCMonth() + 1 !== month || utcDate.getUTCDate() !== day) {
|
|
168
|
-
throw new RangeError(`${fieldName} must be a valid date`);
|
|
169
|
-
}
|
|
170
|
-
return { year, month, day };
|
|
171
|
-
}
|
|
172
|
-
function toUtcDateKey(date) {
|
|
173
|
-
return Date.UTC(date.year, date.month - 1, date.day);
|
|
174
|
-
}
|
|
175
98
|
// Annotate the CommonJS export names for ESM import in node:
|
|
176
99
|
0 && (module.exports = {
|
|
177
100
|
GYM_GOLD_CHARGE_POLICY,
|
|
178
|
-
GYM_PARTNER_REGISTRATION_GOLD_POLICY,
|
|
179
101
|
MIN_DOWNLOAD_SECONDS,
|
|
180
102
|
ORIGINAL_DOWNLOAD_COST_POLICY,
|
|
181
103
|
assertValidGymGoldChargeKrw,
|
|
182
|
-
calculateGymGoldCharge,
|
|
183
|
-
calculateGymPartnerRegistrationGold,
|
|
184
|
-
calculateGymPartnerRegistrationGoldTotal,
|
|
185
104
|
calculateOriginalDownloadCostWon,
|
|
186
105
|
isDownloadDurationAllowed,
|
|
187
106
|
isGymGoldChargeKrwAllowed
|
package/dist/libs.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/libs/index.ts","../src/libs/cost.ts","../src/libs/gym-gold-charge.ts","../src/libs/gym-registration-gold.ts"],"sourcesContent":["export * from './cost';\nexport * from './gym-gold-charge';\nexport * from './gym-registration-gold';\n","import type { TicketCode } from '../types/membership';\n\nexport const ORIGINAL_DOWNLOAD_COST_POLICY = {\n baseDurationSeconds: 600,\n baseCostWon: 2000,\n fullVideoDiscountRate: 0.1,\n /**\n * v14 신정책: 멤버십 plan별 분당 KRW 요율.\n * `calculateOriginalDownloadCostWon(duration, { plan })`로 호출 시 적용된다.\n * plan 미지정 시 `baseCostWon/baseDurationSeconds` 기반 단일 단가(레거시 200원/분)로 폴백 — 기존 호출자 호환.\n */\n perPlanKrwPerMinute: {\n FREE: 100,\n PLUS: 50,\n PRO: 25,\n },\n /**\n * 멤버십 plan별 무료 다운로드 구간(초).\n * 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).\n * plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.\n */\n freeSecondsByPlan: {\n FREE: 10,\n PLUS: 20,\n PRO: 30,\n },\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 멤버십 plan. 지정 시 `perPlanKrwPerMinute[plan]`을 단가로,\n * `freeSecondsByPlan[plan]`을 무료 구간으로 적용한다.\n * 미지정(undefined) 시 레거시 단일 단가(`baseCostWon/baseDurationSeconds`) + 무료 구간 없음.\n */\n plan?: TicketCode;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { plan, isFullVideo } = options;\n\n // plan 지정 시 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n // 레거시 경로(plan 미지정)는 무료 구간 0으로 기존 동작을 유지한다.\n const freeSeconds =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.freeSecondsByPlan[plan]\n : 0;\n const billableSeconds = Math.max(0, durationSeconds - freeSeconds);\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.perPlanKrwPerMinute[plan] / 60\n : ORIGINAL_DOWNLOAD_COST_POLICY.baseCostWon /\n ORIGINAL_DOWNLOAD_COST_POLICY.baseDurationSeconds;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다.\n const rawCost = billableSeconds * unitPricePerSecond * discountMultiplier;\n return Math.ceil(Number(rawCost.toFixed(6)));\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport interface GymGoldChargeInput {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n}\n\nexport interface GymGoldChargeResult {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n /**\n * 1:1 base Gold granted for paid KRW.\n */\n baseGold: number;\n /**\n * Additional bonus Gold granted by charge policy.\n */\n bonusGold: number;\n /**\n * Final Gold amount credited to the gym balance.\n */\n totalGold: number;\n}\n\nexport function calculateGymGoldCharge(\n input: GymGoldChargeInput,\n): GymGoldChargeResult {\n assertValidGymGoldChargeKrw(input.chargeKrw);\n\n const baseGold = input.chargeKrw;\n const bonusGold = calculateBonusGold(input.chargeKrw);\n\n return {\n chargeKrw: input.chargeKrw,\n baseGold,\n bonusGold,\n totalGold: baseGold + bonusGold,\n };\n}\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n\nfunction calculateBonusGold(chargeKrw: number): number {\n return Math.floor((chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps) / 10_000);\n}\n","export const GYM_PARTNER_REGISTRATION_GOLD_POLICY = {\n goldPerDay: 350,\n timeZone: 'Asia/Seoul',\n} as const;\n\nexport interface GymPartnerRegistrationGoldInput {\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n startDate: string;\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n endDate: string;\n}\n\nexport interface GymPartnerRegistrationGoldResult {\n days: number;\n goldAmount: number;\n}\n\nexport interface GymPartnerRegistrationGoldTotalItem\n extends GymPartnerRegistrationGoldInput,\n GymPartnerRegistrationGoldResult {}\n\nexport interface GymPartnerRegistrationGoldTotalResult {\n totalDays: number;\n totalGoldAmount: number;\n items: GymPartnerRegistrationGoldTotalItem[];\n}\n\ninterface KstDateParts {\n year: number;\n month: number;\n day: number;\n}\n\nconst DATE_ONLY_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\nexport function calculateGymPartnerRegistrationGold(\n input: GymPartnerRegistrationGoldInput,\n): GymPartnerRegistrationGoldResult {\n const startDate = parseKstDateString(input.startDate, 'startDate');\n const endDate = parseKstDateString(input.endDate, 'endDate');\n\n const startDateKey = toUtcDateKey(startDate);\n const endDateKey = toUtcDateKey(endDate);\n\n if (endDateKey < startDateKey) {\n throw new RangeError('endDate must be on or after startDate');\n }\n\n const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;\n\n return {\n days,\n goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay,\n };\n}\n\nexport function calculateGymPartnerRegistrationGoldTotal(\n periods: GymPartnerRegistrationGoldInput[],\n): GymPartnerRegistrationGoldTotalResult {\n const items = periods.map((period) => {\n const result = calculateGymPartnerRegistrationGold(period);\n\n return {\n ...period,\n ...result,\n };\n });\n\n return {\n totalDays: items.reduce((sum, item) => sum + item.days, 0),\n totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),\n items,\n };\n}\n\nfunction parseKstDateString(\n input: string,\n fieldName: 'startDate' | 'endDate',\n): KstDateParts {\n const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);\n if (dateOnlyMatch === null) {\n throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);\n }\n\n const year = Number(dateOnlyMatch[1]);\n const month = Number(dateOnlyMatch[2]);\n const day = Number(dateOnlyMatch[3]);\n const utcDate = new Date(Date.UTC(year, month - 1, day));\n\n if (\n utcDate.getUTCFullYear() !== year ||\n utcDate.getUTCMonth() + 1 !== month ||\n utcDate.getUTCDate() !== day\n ) {\n throw new RangeError(`${fieldName} must be a valid date`);\n }\n\n return { year, month, day };\n}\n\nfunction toUtcDateKey(date: KstDateParts): number {\n return Date.UTC(date.year, date.month - 1, date.day);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/libs/index.ts","../src/libs/cost.ts","../src/libs/gym-gold-charge.ts"],"sourcesContent":["export * from './cost';\nexport * from './gym-gold-charge';\n","export const ORIGINAL_DOWNLOAD_COST_POLICY = {\n /**\n * 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.\n * 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).\n */\n freeSeconds: 30,\n /**\n * 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.\n */\n krwPerMinute: 100,\n /**\n * 전체 영상 다운로드 시 할인율(10%).\n */\n fullVideoDiscountRate: 0.1,\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 동시에 다운로드할 화각(camera angle) 수. 최소 1, 기본 1.\n * 모든 화각은 동일한 길이로 함께 받으므로 단일 화각 비용 × 화각 수로 과금한다.\n * 1 미만이거나 유한하지 않은 값은 1로 보정한다.\n */\n angleCount?: number;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { isFullVideo, angleCount } = options;\n\n // 화각 수는 최소 1로 보정한다. 정수가 아닌 값은 내림 처리.\n const normalizedAngleCount = Number.isFinite(angleCount)\n ? Math.max(1, Math.floor(angleCount as number))\n : 1;\n\n // 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n const billableSeconds = Math.max(\n 0,\n durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds,\n );\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다. 단일 화각 비용을 원 단위로 확정한 뒤\n // 화각 수만큼 곱한다 (각 화각을 개별 다운로드 건으로 과금).\n const rawCostPerAngle =\n billableSeconds * unitPricePerSecond * discountMultiplier;\n const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));\n\n return costPerAngle * normalizedAngleCount;\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,gCAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM3C,aAAa;AAAA;AAAA;AAAA;AAAA,EAIb,cAAc;AAAA;AAAA;AAAA;AAAA,EAId,uBAAuB;AACzB;AAMO,IAAM,uBAAuB;AAM7B,SAAS,0BAA0B,iBAAkC;AAC1E,SACE,OAAO,SAAS,eAAe,KAAK,mBAAmB;AAE3D;AAYO,SAAS,iCACd,iBACA,UAAuC,CAAC,GAChC;AACR,MAAI,CAAC,OAAO,SAAS,eAAe,KAAK,mBAAmB,EAAG,QAAO;AAEtE,QAAM,EAAE,aAAa,WAAW,IAAI;AAGpC,QAAM,uBAAuB,OAAO,SAAS,UAAU,IACnD,KAAK,IAAI,GAAG,KAAK,MAAM,UAAoB,CAAC,IAC5C;AAGJ,QAAM,kBAAkB,KAAK;AAAA,IAC3B;AAAA,IACA,kBAAkB,8BAA8B;AAAA,EAClD;AACA,MAAI,mBAAmB,EAAG,QAAO;AAEjC,QAAM,qBAAqB,8BAA8B,eAAe;AAExE,QAAM,qBAAqB,cACvB,IAAI,8BAA8B,wBAClC;AAKJ,QAAM,kBACJ,kBAAkB,qBAAqB;AACzC,QAAM,eAAe,KAAK,KAAK,OAAO,gBAAgB,QAAQ,CAAC,CAAC,CAAC;AAEjE,SAAO,eAAe;AACxB;;;AC7EO,IAAM,yBAAyB;AAAA,EACpC,cAAc;AAAA,EACd,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc;AAChB;AAEO,SAAS,0BAA0B,WAA4B;AACpE,SACE,OAAO,SAAS,SAAS,KACzB,OAAO,UAAU,SAAS,KAC1B,aAAa,uBAAuB,gBACpC,YAAY,uBAAuB,kBAAkB;AAEzD;AAEO,SAAS,4BAA4B,WAAyB;AACnE,MAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC/B,UAAM,IAAI,WAAW,mCAAmC;AAAA,EAC1D;AAEA,MAAI,CAAC,OAAO,UAAU,SAAS,GAAG;AAChC,UAAM,IAAI,WAAW,8BAA8B;AAAA,EACrD;AAEA,MAAI,YAAY,uBAAuB,cAAc;AACnD,UAAM,IAAI;AAAA,MACR,8BAA8B,uBAAuB,YAAY;AAAA,IACnE;AAAA,EACF;AAEA,MAAI,YAAY,uBAAuB,kBAAkB,GAAG;AAC1D,UAAM,IAAI;AAAA,MACR,mCAAmC,uBAAuB,aAAa;AAAA,IACzE;AAAA,EACF;AACF;","names":[]}
|
package/dist/libs.d.cts
CHANGED
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
import { T as TicketCode } from './membership-C_ziSHS0.cjs';
|
|
2
|
-
|
|
3
1
|
declare const ORIGINAL_DOWNLOAD_COST_POLICY: {
|
|
4
|
-
readonly baseDurationSeconds: 600;
|
|
5
|
-
readonly baseCostWon: 2000;
|
|
6
|
-
readonly fullVideoDiscountRate: 0.1;
|
|
7
2
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
3
|
+
* 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.
|
|
4
|
+
* 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,
|
|
5
|
+
* 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).
|
|
6
|
+
*/
|
|
7
|
+
readonly freeSeconds: 30;
|
|
8
|
+
/**
|
|
9
|
+
* 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.
|
|
11
10
|
*/
|
|
12
|
-
readonly
|
|
13
|
-
readonly FREE: 100;
|
|
14
|
-
readonly PLUS: 50;
|
|
15
|
-
readonly PRO: 25;
|
|
16
|
-
};
|
|
11
|
+
readonly krwPerMinute: 100;
|
|
17
12
|
/**
|
|
18
|
-
*
|
|
19
|
-
* 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,
|
|
20
|
-
* 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).
|
|
21
|
-
* plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.
|
|
13
|
+
* 전체 영상 다운로드 시 할인율(10%).
|
|
22
14
|
*/
|
|
23
|
-
readonly
|
|
24
|
-
readonly FREE: 10;
|
|
25
|
-
readonly PLUS: 20;
|
|
26
|
-
readonly PRO: 30;
|
|
27
|
-
};
|
|
15
|
+
readonly fullVideoDiscountRate: 0.1;
|
|
28
16
|
};
|
|
29
17
|
/**
|
|
30
18
|
* 다운로드 가능한 최소 길이(초).
|
|
@@ -39,11 +27,11 @@ declare function isDownloadDurationAllowed(durationSeconds: number): boolean;
|
|
|
39
27
|
interface OriginalDownloadCostOptions {
|
|
40
28
|
isFullVideo?: boolean;
|
|
41
29
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
30
|
+
* 동시에 다운로드할 화각(camera angle) 수. 최소 1, 기본 1.
|
|
31
|
+
* 모든 화각은 동일한 길이로 함께 받으므로 단일 화각 비용 × 화각 수로 과금한다.
|
|
32
|
+
* 1 미만이거나 유한하지 않은 값은 1로 보정한다.
|
|
45
33
|
*/
|
|
46
|
-
|
|
34
|
+
angleCount?: number;
|
|
47
35
|
}
|
|
48
36
|
declare function calculateOriginalDownloadCostWon(durationSeconds: number, options?: OriginalDownloadCostOptions): number;
|
|
49
37
|
|
|
@@ -53,60 +41,7 @@ declare const GYM_GOLD_CHARGE_POLICY: {
|
|
|
53
41
|
readonly bonusRate: 0.2;
|
|
54
42
|
readonly bonusRateBps: 2000;
|
|
55
43
|
};
|
|
56
|
-
interface GymGoldChargeInput {
|
|
57
|
-
/**
|
|
58
|
-
* Actual KRW amount paid through PortOne.
|
|
59
|
-
*/
|
|
60
|
-
chargeKrw: number;
|
|
61
|
-
}
|
|
62
|
-
interface GymGoldChargeResult {
|
|
63
|
-
/**
|
|
64
|
-
* Actual KRW amount paid through PortOne.
|
|
65
|
-
*/
|
|
66
|
-
chargeKrw: number;
|
|
67
|
-
/**
|
|
68
|
-
* 1:1 base Gold granted for paid KRW.
|
|
69
|
-
*/
|
|
70
|
-
baseGold: number;
|
|
71
|
-
/**
|
|
72
|
-
* Additional bonus Gold granted by charge policy.
|
|
73
|
-
*/
|
|
74
|
-
bonusGold: number;
|
|
75
|
-
/**
|
|
76
|
-
* Final Gold amount credited to the gym balance.
|
|
77
|
-
*/
|
|
78
|
-
totalGold: number;
|
|
79
|
-
}
|
|
80
|
-
declare function calculateGymGoldCharge(input: GymGoldChargeInput): GymGoldChargeResult;
|
|
81
44
|
declare function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean;
|
|
82
45
|
declare function assertValidGymGoldChargeKrw(chargeKrw: number): void;
|
|
83
46
|
|
|
84
|
-
|
|
85
|
-
readonly goldPerDay: 350;
|
|
86
|
-
readonly timeZone: "Asia/Seoul";
|
|
87
|
-
};
|
|
88
|
-
interface GymPartnerRegistrationGoldInput {
|
|
89
|
-
/**
|
|
90
|
-
* KST calendar date in YYYY-MM-DD format.
|
|
91
|
-
*/
|
|
92
|
-
startDate: string;
|
|
93
|
-
/**
|
|
94
|
-
* KST calendar date in YYYY-MM-DD format.
|
|
95
|
-
*/
|
|
96
|
-
endDate: string;
|
|
97
|
-
}
|
|
98
|
-
interface GymPartnerRegistrationGoldResult {
|
|
99
|
-
days: number;
|
|
100
|
-
goldAmount: number;
|
|
101
|
-
}
|
|
102
|
-
interface GymPartnerRegistrationGoldTotalItem extends GymPartnerRegistrationGoldInput, GymPartnerRegistrationGoldResult {
|
|
103
|
-
}
|
|
104
|
-
interface GymPartnerRegistrationGoldTotalResult {
|
|
105
|
-
totalDays: number;
|
|
106
|
-
totalGoldAmount: number;
|
|
107
|
-
items: GymPartnerRegistrationGoldTotalItem[];
|
|
108
|
-
}
|
|
109
|
-
declare function calculateGymPartnerRegistrationGold(input: GymPartnerRegistrationGoldInput): GymPartnerRegistrationGoldResult;
|
|
110
|
-
declare function calculateGymPartnerRegistrationGoldTotal(periods: GymPartnerRegistrationGoldInput[]): GymPartnerRegistrationGoldTotalResult;
|
|
111
|
-
|
|
112
|
-
export { GYM_GOLD_CHARGE_POLICY, GYM_PARTNER_REGISTRATION_GOLD_POLICY, type GymGoldChargeInput, type GymGoldChargeResult, type GymPartnerRegistrationGoldInput, type GymPartnerRegistrationGoldResult, type GymPartnerRegistrationGoldTotalItem, type GymPartnerRegistrationGoldTotalResult, MIN_DOWNLOAD_SECONDS, ORIGINAL_DOWNLOAD_COST_POLICY, type OriginalDownloadCostOptions, assertValidGymGoldChargeKrw, calculateGymGoldCharge, calculateGymPartnerRegistrationGold, calculateGymPartnerRegistrationGoldTotal, calculateOriginalDownloadCostWon, isDownloadDurationAllowed, isGymGoldChargeKrwAllowed };
|
|
47
|
+
export { GYM_GOLD_CHARGE_POLICY, MIN_DOWNLOAD_SECONDS, ORIGINAL_DOWNLOAD_COST_POLICY, type OriginalDownloadCostOptions, assertValidGymGoldChargeKrw, calculateOriginalDownloadCostWon, isDownloadDurationAllowed, isGymGoldChargeKrwAllowed };
|
package/dist/libs.d.ts
CHANGED
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
import { T as TicketCode } from './membership-C_ziSHS0.js';
|
|
2
|
-
|
|
3
1
|
declare const ORIGINAL_DOWNLOAD_COST_POLICY: {
|
|
4
|
-
readonly baseDurationSeconds: 600;
|
|
5
|
-
readonly baseCostWon: 2000;
|
|
6
|
-
readonly fullVideoDiscountRate: 0.1;
|
|
7
2
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
3
|
+
* 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.
|
|
4
|
+
* 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,
|
|
5
|
+
* 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).
|
|
6
|
+
*/
|
|
7
|
+
readonly freeSeconds: 30;
|
|
8
|
+
/**
|
|
9
|
+
* 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.
|
|
11
10
|
*/
|
|
12
|
-
readonly
|
|
13
|
-
readonly FREE: 100;
|
|
14
|
-
readonly PLUS: 50;
|
|
15
|
-
readonly PRO: 25;
|
|
16
|
-
};
|
|
11
|
+
readonly krwPerMinute: 100;
|
|
17
12
|
/**
|
|
18
|
-
*
|
|
19
|
-
* 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,
|
|
20
|
-
* 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).
|
|
21
|
-
* plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.
|
|
13
|
+
* 전체 영상 다운로드 시 할인율(10%).
|
|
22
14
|
*/
|
|
23
|
-
readonly
|
|
24
|
-
readonly FREE: 10;
|
|
25
|
-
readonly PLUS: 20;
|
|
26
|
-
readonly PRO: 30;
|
|
27
|
-
};
|
|
15
|
+
readonly fullVideoDiscountRate: 0.1;
|
|
28
16
|
};
|
|
29
17
|
/**
|
|
30
18
|
* 다운로드 가능한 최소 길이(초).
|
|
@@ -39,11 +27,11 @@ declare function isDownloadDurationAllowed(durationSeconds: number): boolean;
|
|
|
39
27
|
interface OriginalDownloadCostOptions {
|
|
40
28
|
isFullVideo?: boolean;
|
|
41
29
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
30
|
+
* 동시에 다운로드할 화각(camera angle) 수. 최소 1, 기본 1.
|
|
31
|
+
* 모든 화각은 동일한 길이로 함께 받으므로 단일 화각 비용 × 화각 수로 과금한다.
|
|
32
|
+
* 1 미만이거나 유한하지 않은 값은 1로 보정한다.
|
|
45
33
|
*/
|
|
46
|
-
|
|
34
|
+
angleCount?: number;
|
|
47
35
|
}
|
|
48
36
|
declare function calculateOriginalDownloadCostWon(durationSeconds: number, options?: OriginalDownloadCostOptions): number;
|
|
49
37
|
|
|
@@ -53,60 +41,7 @@ declare const GYM_GOLD_CHARGE_POLICY: {
|
|
|
53
41
|
readonly bonusRate: 0.2;
|
|
54
42
|
readonly bonusRateBps: 2000;
|
|
55
43
|
};
|
|
56
|
-
interface GymGoldChargeInput {
|
|
57
|
-
/**
|
|
58
|
-
* Actual KRW amount paid through PortOne.
|
|
59
|
-
*/
|
|
60
|
-
chargeKrw: number;
|
|
61
|
-
}
|
|
62
|
-
interface GymGoldChargeResult {
|
|
63
|
-
/**
|
|
64
|
-
* Actual KRW amount paid through PortOne.
|
|
65
|
-
*/
|
|
66
|
-
chargeKrw: number;
|
|
67
|
-
/**
|
|
68
|
-
* 1:1 base Gold granted for paid KRW.
|
|
69
|
-
*/
|
|
70
|
-
baseGold: number;
|
|
71
|
-
/**
|
|
72
|
-
* Additional bonus Gold granted by charge policy.
|
|
73
|
-
*/
|
|
74
|
-
bonusGold: number;
|
|
75
|
-
/**
|
|
76
|
-
* Final Gold amount credited to the gym balance.
|
|
77
|
-
*/
|
|
78
|
-
totalGold: number;
|
|
79
|
-
}
|
|
80
|
-
declare function calculateGymGoldCharge(input: GymGoldChargeInput): GymGoldChargeResult;
|
|
81
44
|
declare function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean;
|
|
82
45
|
declare function assertValidGymGoldChargeKrw(chargeKrw: number): void;
|
|
83
46
|
|
|
84
|
-
|
|
85
|
-
readonly goldPerDay: 350;
|
|
86
|
-
readonly timeZone: "Asia/Seoul";
|
|
87
|
-
};
|
|
88
|
-
interface GymPartnerRegistrationGoldInput {
|
|
89
|
-
/**
|
|
90
|
-
* KST calendar date in YYYY-MM-DD format.
|
|
91
|
-
*/
|
|
92
|
-
startDate: string;
|
|
93
|
-
/**
|
|
94
|
-
* KST calendar date in YYYY-MM-DD format.
|
|
95
|
-
*/
|
|
96
|
-
endDate: string;
|
|
97
|
-
}
|
|
98
|
-
interface GymPartnerRegistrationGoldResult {
|
|
99
|
-
days: number;
|
|
100
|
-
goldAmount: number;
|
|
101
|
-
}
|
|
102
|
-
interface GymPartnerRegistrationGoldTotalItem extends GymPartnerRegistrationGoldInput, GymPartnerRegistrationGoldResult {
|
|
103
|
-
}
|
|
104
|
-
interface GymPartnerRegistrationGoldTotalResult {
|
|
105
|
-
totalDays: number;
|
|
106
|
-
totalGoldAmount: number;
|
|
107
|
-
items: GymPartnerRegistrationGoldTotalItem[];
|
|
108
|
-
}
|
|
109
|
-
declare function calculateGymPartnerRegistrationGold(input: GymPartnerRegistrationGoldInput): GymPartnerRegistrationGoldResult;
|
|
110
|
-
declare function calculateGymPartnerRegistrationGoldTotal(periods: GymPartnerRegistrationGoldInput[]): GymPartnerRegistrationGoldTotalResult;
|
|
111
|
-
|
|
112
|
-
export { GYM_GOLD_CHARGE_POLICY, GYM_PARTNER_REGISTRATION_GOLD_POLICY, type GymGoldChargeInput, type GymGoldChargeResult, type GymPartnerRegistrationGoldInput, type GymPartnerRegistrationGoldResult, type GymPartnerRegistrationGoldTotalItem, type GymPartnerRegistrationGoldTotalResult, MIN_DOWNLOAD_SECONDS, ORIGINAL_DOWNLOAD_COST_POLICY, type OriginalDownloadCostOptions, assertValidGymGoldChargeKrw, calculateGymGoldCharge, calculateGymPartnerRegistrationGold, calculateGymPartnerRegistrationGoldTotal, calculateOriginalDownloadCostWon, isDownloadDurationAllowed, isGymGoldChargeKrwAllowed };
|
|
47
|
+
export { GYM_GOLD_CHARGE_POLICY, MIN_DOWNLOAD_SECONDS, ORIGINAL_DOWNLOAD_COST_POLICY, type OriginalDownloadCostOptions, assertValidGymGoldChargeKrw, calculateOriginalDownloadCostWon, isDownloadDurationAllowed, isGymGoldChargeKrwAllowed };
|
package/dist/libs.js
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
1
|
// src/libs/cost.ts
|
|
2
2
|
var ORIGINAL_DOWNLOAD_COST_POLICY = {
|
|
3
|
-
baseDurationSeconds: 600,
|
|
4
|
-
baseCostWon: 2e3,
|
|
5
|
-
fullVideoDiscountRate: 0.1,
|
|
6
3
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.
|
|
5
|
+
* 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,
|
|
6
|
+
* 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).
|
|
10
7
|
*/
|
|
11
|
-
|
|
12
|
-
FREE: 100,
|
|
13
|
-
PLUS: 50,
|
|
14
|
-
PRO: 25
|
|
15
|
-
},
|
|
8
|
+
freeSeconds: 30,
|
|
16
9
|
/**
|
|
17
|
-
* 멤버십
|
|
18
|
-
* 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,
|
|
19
|
-
* 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).
|
|
20
|
-
* plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.
|
|
10
|
+
* 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.
|
|
21
11
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
krwPerMinute: 100,
|
|
13
|
+
/**
|
|
14
|
+
* 전체 영상 다운로드 시 할인율(10%).
|
|
15
|
+
*/
|
|
16
|
+
fullVideoDiscountRate: 0.1
|
|
27
17
|
};
|
|
28
18
|
var MIN_DOWNLOAD_SECONDS = 10;
|
|
29
19
|
function isDownloadDurationAllowed(durationSeconds) {
|
|
@@ -31,14 +21,18 @@ function isDownloadDurationAllowed(durationSeconds) {
|
|
|
31
21
|
}
|
|
32
22
|
function calculateOriginalDownloadCostWon(durationSeconds, options = {}) {
|
|
33
23
|
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;
|
|
34
|
-
const {
|
|
35
|
-
const
|
|
36
|
-
const billableSeconds = Math.max(
|
|
24
|
+
const { isFullVideo, angleCount } = options;
|
|
25
|
+
const normalizedAngleCount = Number.isFinite(angleCount) ? Math.max(1, Math.floor(angleCount)) : 1;
|
|
26
|
+
const billableSeconds = Math.max(
|
|
27
|
+
0,
|
|
28
|
+
durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds
|
|
29
|
+
);
|
|
37
30
|
if (billableSeconds <= 0) return 0;
|
|
38
|
-
const unitPricePerSecond =
|
|
31
|
+
const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;
|
|
39
32
|
const discountMultiplier = isFullVideo ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate : 1;
|
|
40
|
-
const
|
|
41
|
-
|
|
33
|
+
const rawCostPerAngle = billableSeconds * unitPricePerSecond * discountMultiplier;
|
|
34
|
+
const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));
|
|
35
|
+
return costPerAngle * normalizedAngleCount;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
// src/libs/gym-gold-charge.ts
|
|
@@ -48,17 +42,6 @@ var GYM_GOLD_CHARGE_POLICY = {
|
|
|
48
42
|
bonusRate: 0.2,
|
|
49
43
|
bonusRateBps: 2e3
|
|
50
44
|
};
|
|
51
|
-
function calculateGymGoldCharge(input) {
|
|
52
|
-
assertValidGymGoldChargeKrw(input.chargeKrw);
|
|
53
|
-
const baseGold = input.chargeKrw;
|
|
54
|
-
const bonusGold = calculateBonusGold(input.chargeKrw);
|
|
55
|
-
return {
|
|
56
|
-
chargeKrw: input.chargeKrw,
|
|
57
|
-
baseGold,
|
|
58
|
-
bonusGold,
|
|
59
|
-
totalGold: baseGold + bonusGold
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
45
|
function isGymGoldChargeKrwAllowed(chargeKrw) {
|
|
63
46
|
return Number.isFinite(chargeKrw) && Number.isInteger(chargeKrw) && chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw && chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0;
|
|
64
47
|
}
|
|
@@ -80,71 +63,11 @@ function assertValidGymGoldChargeKrw(chargeKrw) {
|
|
|
80
63
|
);
|
|
81
64
|
}
|
|
82
65
|
}
|
|
83
|
-
function calculateBonusGold(chargeKrw) {
|
|
84
|
-
return Math.floor(chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps / 1e4);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/libs/gym-registration-gold.ts
|
|
88
|
-
var GYM_PARTNER_REGISTRATION_GOLD_POLICY = {
|
|
89
|
-
goldPerDay: 350,
|
|
90
|
-
timeZone: "Asia/Seoul"
|
|
91
|
-
};
|
|
92
|
-
var DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
93
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
94
|
-
function calculateGymPartnerRegistrationGold(input) {
|
|
95
|
-
const startDate = parseKstDateString(input.startDate, "startDate");
|
|
96
|
-
const endDate = parseKstDateString(input.endDate, "endDate");
|
|
97
|
-
const startDateKey = toUtcDateKey(startDate);
|
|
98
|
-
const endDateKey = toUtcDateKey(endDate);
|
|
99
|
-
if (endDateKey < startDateKey) {
|
|
100
|
-
throw new RangeError("endDate must be on or after startDate");
|
|
101
|
-
}
|
|
102
|
-
const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;
|
|
103
|
-
return {
|
|
104
|
-
days,
|
|
105
|
-
goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function calculateGymPartnerRegistrationGoldTotal(periods) {
|
|
109
|
-
const items = periods.map((period) => {
|
|
110
|
-
const result = calculateGymPartnerRegistrationGold(period);
|
|
111
|
-
return {
|
|
112
|
-
...period,
|
|
113
|
-
...result
|
|
114
|
-
};
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
totalDays: items.reduce((sum, item) => sum + item.days, 0),
|
|
118
|
-
totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),
|
|
119
|
-
items
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
function parseKstDateString(input, fieldName) {
|
|
123
|
-
const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);
|
|
124
|
-
if (dateOnlyMatch === null) {
|
|
125
|
-
throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);
|
|
126
|
-
}
|
|
127
|
-
const year = Number(dateOnlyMatch[1]);
|
|
128
|
-
const month = Number(dateOnlyMatch[2]);
|
|
129
|
-
const day = Number(dateOnlyMatch[3]);
|
|
130
|
-
const utcDate = new Date(Date.UTC(year, month - 1, day));
|
|
131
|
-
if (utcDate.getUTCFullYear() !== year || utcDate.getUTCMonth() + 1 !== month || utcDate.getUTCDate() !== day) {
|
|
132
|
-
throw new RangeError(`${fieldName} must be a valid date`);
|
|
133
|
-
}
|
|
134
|
-
return { year, month, day };
|
|
135
|
-
}
|
|
136
|
-
function toUtcDateKey(date) {
|
|
137
|
-
return Date.UTC(date.year, date.month - 1, date.day);
|
|
138
|
-
}
|
|
139
66
|
export {
|
|
140
67
|
GYM_GOLD_CHARGE_POLICY,
|
|
141
|
-
GYM_PARTNER_REGISTRATION_GOLD_POLICY,
|
|
142
68
|
MIN_DOWNLOAD_SECONDS,
|
|
143
69
|
ORIGINAL_DOWNLOAD_COST_POLICY,
|
|
144
70
|
assertValidGymGoldChargeKrw,
|
|
145
|
-
calculateGymGoldCharge,
|
|
146
|
-
calculateGymPartnerRegistrationGold,
|
|
147
|
-
calculateGymPartnerRegistrationGoldTotal,
|
|
148
71
|
calculateOriginalDownloadCostWon,
|
|
149
72
|
isDownloadDurationAllowed,
|
|
150
73
|
isGymGoldChargeKrwAllowed
|
package/dist/libs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/libs/cost.ts","../src/libs/gym-gold-charge.ts","../src/libs/gym-registration-gold.ts"],"sourcesContent":["import type { TicketCode } from '../types/membership';\n\nexport const ORIGINAL_DOWNLOAD_COST_POLICY = {\n baseDurationSeconds: 600,\n baseCostWon: 2000,\n fullVideoDiscountRate: 0.1,\n /**\n * v14 신정책: 멤버십 plan별 분당 KRW 요율.\n * `calculateOriginalDownloadCostWon(duration, { plan })`로 호출 시 적용된다.\n * plan 미지정 시 `baseCostWon/baseDurationSeconds` 기반 단일 단가(레거시 200원/분)로 폴백 — 기존 호출자 호환.\n */\n perPlanKrwPerMinute: {\n FREE: 100,\n PLUS: 50,\n PRO: 25,\n },\n /**\n * 멤버십 plan별 무료 다운로드 구간(초).\n * 다운로드 길이 중 앞 `freeSecondsByPlan[plan]`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `perPlanKrwPerMinute[plan]` 요율을 적용한다 (초과분 과금).\n * plan 미지정(레거시 단일 단가) 경로에는 무료 구간을 적용하지 않는다.\n */\n freeSecondsByPlan: {\n FREE: 10,\n PLUS: 20,\n PRO: 30,\n },\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 멤버십 plan. 지정 시 `perPlanKrwPerMinute[plan]`을 단가로,\n * `freeSecondsByPlan[plan]`을 무료 구간으로 적용한다.\n * 미지정(undefined) 시 레거시 단일 단가(`baseCostWon/baseDurationSeconds`) + 무료 구간 없음.\n */\n plan?: TicketCode;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { plan, isFullVideo } = options;\n\n // plan 지정 시 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n // 레거시 경로(plan 미지정)는 무료 구간 0으로 기존 동작을 유지한다.\n const freeSeconds =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.freeSecondsByPlan[plan]\n : 0;\n const billableSeconds = Math.max(0, durationSeconds - freeSeconds);\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond =\n plan !== undefined\n ? ORIGINAL_DOWNLOAD_COST_POLICY.perPlanKrwPerMinute[plan] / 60\n : ORIGINAL_DOWNLOAD_COST_POLICY.baseCostWon /\n ORIGINAL_DOWNLOAD_COST_POLICY.baseDurationSeconds;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다.\n const rawCost = billableSeconds * unitPricePerSecond * discountMultiplier;\n return Math.ceil(Number(rawCost.toFixed(6)));\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport interface GymGoldChargeInput {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n}\n\nexport interface GymGoldChargeResult {\n /**\n * Actual KRW amount paid through PortOne.\n */\n chargeKrw: number;\n /**\n * 1:1 base Gold granted for paid KRW.\n */\n baseGold: number;\n /**\n * Additional bonus Gold granted by charge policy.\n */\n bonusGold: number;\n /**\n * Final Gold amount credited to the gym balance.\n */\n totalGold: number;\n}\n\nexport function calculateGymGoldCharge(\n input: GymGoldChargeInput,\n): GymGoldChargeResult {\n assertValidGymGoldChargeKrw(input.chargeKrw);\n\n const baseGold = input.chargeKrw;\n const bonusGold = calculateBonusGold(input.chargeKrw);\n\n return {\n chargeKrw: input.chargeKrw,\n baseGold,\n bonusGold,\n totalGold: baseGold + bonusGold,\n };\n}\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n\nfunction calculateBonusGold(chargeKrw: number): number {\n return Math.floor((chargeKrw * GYM_GOLD_CHARGE_POLICY.bonusRateBps) / 10_000);\n}\n","export const GYM_PARTNER_REGISTRATION_GOLD_POLICY = {\n goldPerDay: 350,\n timeZone: 'Asia/Seoul',\n} as const;\n\nexport interface GymPartnerRegistrationGoldInput {\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n startDate: string;\n /**\n * KST calendar date in YYYY-MM-DD format.\n */\n endDate: string;\n}\n\nexport interface GymPartnerRegistrationGoldResult {\n days: number;\n goldAmount: number;\n}\n\nexport interface GymPartnerRegistrationGoldTotalItem\n extends GymPartnerRegistrationGoldInput,\n GymPartnerRegistrationGoldResult {}\n\nexport interface GymPartnerRegistrationGoldTotalResult {\n totalDays: number;\n totalGoldAmount: number;\n items: GymPartnerRegistrationGoldTotalItem[];\n}\n\ninterface KstDateParts {\n year: number;\n month: number;\n day: number;\n}\n\nconst DATE_ONLY_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\n\nexport function calculateGymPartnerRegistrationGold(\n input: GymPartnerRegistrationGoldInput,\n): GymPartnerRegistrationGoldResult {\n const startDate = parseKstDateString(input.startDate, 'startDate');\n const endDate = parseKstDateString(input.endDate, 'endDate');\n\n const startDateKey = toUtcDateKey(startDate);\n const endDateKey = toUtcDateKey(endDate);\n\n if (endDateKey < startDateKey) {\n throw new RangeError('endDate must be on or after startDate');\n }\n\n const days = Math.floor((endDateKey - startDateKey) / MS_PER_DAY) + 1;\n\n return {\n days,\n goldAmount: days * GYM_PARTNER_REGISTRATION_GOLD_POLICY.goldPerDay,\n };\n}\n\nexport function calculateGymPartnerRegistrationGoldTotal(\n periods: GymPartnerRegistrationGoldInput[],\n): GymPartnerRegistrationGoldTotalResult {\n const items = periods.map((period) => {\n const result = calculateGymPartnerRegistrationGold(period);\n\n return {\n ...period,\n ...result,\n };\n });\n\n return {\n totalDays: items.reduce((sum, item) => sum + item.days, 0),\n totalGoldAmount: items.reduce((sum, item) => sum + item.goldAmount, 0),\n items,\n };\n}\n\nfunction parseKstDateString(\n input: string,\n fieldName: 'startDate' | 'endDate',\n): KstDateParts {\n const dateOnlyMatch = DATE_ONLY_PATTERN.exec(input);\n if (dateOnlyMatch === null) {\n throw new RangeError(`${fieldName} must be a YYYY-MM-DD date`);\n }\n\n const year = Number(dateOnlyMatch[1]);\n const month = Number(dateOnlyMatch[2]);\n const day = Number(dateOnlyMatch[3]);\n const utcDate = new Date(Date.UTC(year, month - 1, day));\n\n if (\n utcDate.getUTCFullYear() !== year ||\n utcDate.getUTCMonth() + 1 !== month ||\n utcDate.getUTCDate() !== day\n ) {\n throw new RangeError(`${fieldName} must be a valid date`);\n }\n\n return { year, month, day };\n}\n\nfunction toUtcDateKey(date: KstDateParts): number {\n return Date.UTC(date.year, date.month - 1, date.day);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/libs/cost.ts","../src/libs/gym-gold-charge.ts"],"sourcesContent":["export const ORIGINAL_DOWNLOAD_COST_POLICY = {\n /**\n * 무료 다운로드 구간(초). 멤버십 구분 없이 모든 사용자에게 동일하게 적용한다.\n * 다운로드 길이 중 앞 `freeSeconds`초는 과금에서 제외하고,\n * 이를 초과한 구간에만 `krwPerMinute` 요율을 적용한다 (초과분 과금).\n */\n freeSeconds: 30,\n /**\n * 무료 구간 초과분에 적용하는 분당 KRW 요율. 멤버십 구분 없는 단일 요율.\n */\n krwPerMinute: 100,\n /**\n * 전체 영상 다운로드 시 할인율(10%).\n */\n fullVideoDiscountRate: 0.1,\n} as const;\n\n/**\n * 다운로드 가능한 최소 길이(초).\n * 이보다 짧은 구간은 다운로드 요청 자체를 허용하지 않는다 (server·FE 공용 제약).\n */\nexport const MIN_DOWNLOAD_SECONDS = 10;\n\n/**\n * 다운로드 요청 길이가 허용되는지 검사한다.\n * `durationSeconds >= MIN_DOWNLOAD_SECONDS`일 때만 true.\n */\nexport function isDownloadDurationAllowed(durationSeconds: number): boolean {\n return (\n Number.isFinite(durationSeconds) && durationSeconds >= MIN_DOWNLOAD_SECONDS\n );\n}\n\nexport interface OriginalDownloadCostOptions {\n isFullVideo?: boolean;\n /**\n * 동시에 다운로드할 화각(camera angle) 수. 최소 1, 기본 1.\n * 모든 화각은 동일한 길이로 함께 받으므로 단일 화각 비용 × 화각 수로 과금한다.\n * 1 미만이거나 유한하지 않은 값은 1로 보정한다.\n */\n angleCount?: number;\n}\n\nexport function calculateOriginalDownloadCostWon(\n durationSeconds: number,\n options: OriginalDownloadCostOptions = {},\n): number {\n if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) return 0;\n\n const { isFullVideo, angleCount } = options;\n\n // 화각 수는 최소 1로 보정한다. 정수가 아닌 값은 내림 처리.\n const normalizedAngleCount = Number.isFinite(angleCount)\n ? Math.max(1, Math.floor(angleCount as number))\n : 1;\n\n // 앞 freeSeconds 구간은 과금에서 제외하고 초과분만 과금한다.\n const billableSeconds = Math.max(\n 0,\n durationSeconds - ORIGINAL_DOWNLOAD_COST_POLICY.freeSeconds,\n );\n if (billableSeconds <= 0) return 0;\n\n const unitPricePerSecond = ORIGINAL_DOWNLOAD_COST_POLICY.krwPerMinute / 60;\n\n const discountMultiplier = isFullVideo\n ? 1 - ORIGINAL_DOWNLOAD_COST_POLICY.fullVideoDiscountRate\n : 1;\n\n // 부동소수점 drift(예: 30.000000000000004) 때문에 1원 과다 청구되는 것을 막기 위해\n // 원 단위 미만 노이즈를 보정한 뒤 올림한다. 단일 화각 비용을 원 단위로 확정한 뒤\n // 화각 수만큼 곱한다 (각 화각을 개별 다운로드 건으로 과금).\n const rawCostPerAngle =\n billableSeconds * unitPricePerSecond * discountMultiplier;\n const costPerAngle = Math.ceil(Number(rawCostPerAngle.toFixed(6)));\n\n return costPerAngle * normalizedAngleCount;\n}\n","export const GYM_GOLD_CHARGE_POLICY = {\n minChargeKrw: 10_000,\n chargeUnitKrw: 10_000,\n bonusRate: 0.2,\n bonusRateBps: 2_000,\n} as const;\n\nexport function isGymGoldChargeKrwAllowed(chargeKrw: number): boolean {\n return (\n Number.isFinite(chargeKrw) &&\n Number.isInteger(chargeKrw) &&\n chargeKrw >= GYM_GOLD_CHARGE_POLICY.minChargeKrw &&\n chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw === 0\n );\n}\n\nexport function assertValidGymGoldChargeKrw(chargeKrw: number): void {\n if (!Number.isFinite(chargeKrw)) {\n throw new RangeError('chargeKrw must be a finite number');\n }\n\n if (!Number.isInteger(chargeKrw)) {\n throw new RangeError('chargeKrw must be an integer');\n }\n\n if (chargeKrw < GYM_GOLD_CHARGE_POLICY.minChargeKrw) {\n throw new RangeError(\n `chargeKrw must be at least ${GYM_GOLD_CHARGE_POLICY.minChargeKrw}`,\n );\n }\n\n if (chargeKrw % GYM_GOLD_CHARGE_POLICY.chargeUnitKrw !== 0) {\n throw new RangeError(\n `chargeKrw must be a multiple of ${GYM_GOLD_CHARGE_POLICY.chargeUnitKrw}`,\n );\n }\n}\n"],"mappings":";AAAO,IAAM,gCAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM3C,aAAa;AAAA;AAAA;AAAA;AAAA,EAIb,cAAc;AAAA;AAAA;AAAA;AAAA,EAId,uBAAuB;AACzB;AAMO,IAAM,uBAAuB;AAM7B,SAAS,0BAA0B,iBAAkC;AAC1E,SACE,OAAO,SAAS,eAAe,KAAK,mBAAmB;AAE3D;AAYO,SAAS,iCACd,iBACA,UAAuC,CAAC,GAChC;AACR,MAAI,CAAC,OAAO,SAAS,eAAe,KAAK,mBAAmB,EAAG,QAAO;AAEtE,QAAM,EAAE,aAAa,WAAW,IAAI;AAGpC,QAAM,uBAAuB,OAAO,SAAS,UAAU,IACnD,KAAK,IAAI,GAAG,KAAK,MAAM,UAAoB,CAAC,IAC5C;AAGJ,QAAM,kBAAkB,KAAK;AAAA,IAC3B;AAAA,IACA,kBAAkB,8BAA8B;AAAA,EAClD;AACA,MAAI,mBAAmB,EAAG,QAAO;AAEjC,QAAM,qBAAqB,8BAA8B,eAAe;AAExE,QAAM,qBAAqB,cACvB,IAAI,8BAA8B,wBAClC;AAKJ,QAAM,kBACJ,kBAAkB,qBAAqB;AACzC,QAAM,eAAe,KAAK,KAAK,OAAO,gBAAgB,QAAQ,CAAC,CAAC,CAAC;AAEjE,SAAO,eAAe;AACxB;;;AC7EO,IAAM,yBAAyB;AAAA,EACpC,cAAc;AAAA,EACd,eAAe;AAAA,EACf,WAAW;AAAA,EACX,cAAc;AAChB;AAEO,SAAS,0BAA0B,WAA4B;AACpE,SACE,OAAO,SAAS,SAAS,KACzB,OAAO,UAAU,SAAS,KAC1B,aAAa,uBAAuB,gBACpC,YAAY,uBAAuB,kBAAkB;AAEzD;AAEO,SAAS,4BAA4B,WAAyB;AACnE,MAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC/B,UAAM,IAAI,WAAW,mCAAmC;AAAA,EAC1D;AAEA,MAAI,CAAC,OAAO,UAAU,SAAS,GAAG;AAChC,UAAM,IAAI,WAAW,8BAA8B;AAAA,EACrD;AAEA,MAAI,YAAY,uBAAuB,cAAc;AACnD,UAAM,IAAI;AAAA,MACR,8BAA8B,uBAAuB,YAAY;AAAA,IACnE;AAAA,EACF;AAEA,MAAI,YAAY,uBAAuB,kBAAkB,GAAG;AAC1D,UAAM,IAAI;AAAA,MACR,mCAAmC,uBAAuB,aAAa;AAAA,IACzE;AAAA,EACF;AACF;","names":[]}
|
package/dist/types.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/index.ts"],"sourcesContent":["export * from './
|
|
1
|
+
{"version":3,"sources":["../src/types/index.ts"],"sourcesContent":["export * from './api';\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/types.d.cts
CHANGED
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED