@apps-in-toss/web-bridge 1.1.2 → 1.2.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/built/index.cjs CHANGED
@@ -117,17 +117,112 @@ var Storage = {
117
117
  };
118
118
 
119
119
  // src/iap.ts
120
+ var import_bridge_core3 = require("@apps-in-toss/bridge-core");
121
+
122
+ // src/isMinVersionSupported.ts
120
123
  var import_bridge_core2 = require("@apps-in-toss/bridge-core");
124
+
125
+ // src/utils/compareVersion.ts
126
+ var SEMVER_REGEX = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\\-]+(?:\.[\da-z\\-]+)*))?(?:\+[\da-z\\-]+(?:\.[\da-z\\-]+)*)?)?)?$/i;
127
+ var isWildcard = (val) => ["*", "x", "X"].includes(val);
128
+ var tryParse = (val) => {
129
+ const num = parseInt(val, 10);
130
+ return isNaN(num) ? val : num;
131
+ };
132
+ var coerceTypes = (a, b) => {
133
+ return typeof a === typeof b ? [a, b] : [String(a), String(b)];
134
+ };
135
+ var compareValues = (a, b) => {
136
+ if (isWildcard(a) || isWildcard(b)) {
137
+ return 0;
138
+ }
139
+ const [aVal, bVal] = coerceTypes(tryParse(a), tryParse(b));
140
+ if (aVal > bVal) {
141
+ return 1;
142
+ }
143
+ if (aVal < bVal) {
144
+ return -1;
145
+ }
146
+ return 0;
147
+ };
148
+ var parseVersion = (version) => {
149
+ if (typeof version !== "string") {
150
+ throw new TypeError("Invalid argument: expected a string");
151
+ }
152
+ const match = version.match(SEMVER_REGEX);
153
+ if (!match) {
154
+ throw new Error(`Invalid semver: '${version}'`);
155
+ }
156
+ const [, major, minor, patch, build, preRelease] = match;
157
+ return [major, minor, patch, build, preRelease];
158
+ };
159
+ var compareSegments = (a, b) => {
160
+ const maxLength = Math.max(a.length, b.length);
161
+ for (let i = 0; i < maxLength; i++) {
162
+ const segA = a[i] ?? "0";
163
+ const segB = b[i] ?? "0";
164
+ const result = compareValues(segA, segB);
165
+ if (result !== 0) {
166
+ return result;
167
+ }
168
+ }
169
+ return 0;
170
+ };
171
+ var compareVersions = (v1, v2) => {
172
+ const seg1 = parseVersion(v1);
173
+ const seg2 = parseVersion(v2);
174
+ const preRelease1 = seg1.pop();
175
+ const preRelease2 = seg2.pop();
176
+ const mainCompare = compareSegments(seg1, seg2);
177
+ if (mainCompare !== 0) {
178
+ return mainCompare;
179
+ }
180
+ if (preRelease1 && preRelease2) {
181
+ return compareSegments(preRelease1.split("."), preRelease2.split("."));
182
+ }
183
+ if (preRelease1) {
184
+ return -1;
185
+ }
186
+ if (preRelease2) {
187
+ return 1;
188
+ }
189
+ return 0;
190
+ };
191
+
192
+ // src/isMinVersionSupported.ts
193
+ function isMinVersionSupported(minVersions) {
194
+ const operationalEnvironment = (0, import_bridge_core2.createConstantBridge)("getOperationalEnvironment")();
195
+ if (operationalEnvironment === "sandbox") {
196
+ return true;
197
+ }
198
+ const currentVersion = (0, import_bridge_core2.createConstantBridge)("getTossAppVersion")();
199
+ const isIOS = (0, import_bridge_core2.createConstantBridge)("getPlatformOS")() === "ios";
200
+ const minVersion = isIOS ? minVersions.ios : minVersions.android;
201
+ if (minVersion === void 0) {
202
+ return false;
203
+ }
204
+ if (minVersion === "always") {
205
+ return true;
206
+ }
207
+ if (minVersion === "never") {
208
+ return false;
209
+ }
210
+ return compareVersions(currentVersion, minVersion) >= 0;
211
+ }
212
+
213
+ // src/iap.ts
214
+ function processProductGrant(params) {
215
+ return (0, import_bridge_core3.createAsyncBridge)("processProductGrant")(params);
216
+ }
121
217
  var IAP = {
122
218
  /**
123
219
  * @public
124
220
  * @category 인앱결제
125
- * @name iapCreateOneTimePurchaseOrder
221
+ * @name createOneTimePurchaseOrder
126
222
  * @description
127
223
  * 특정 인앱결제 주문서 페이지로 이동해요. 사용자가 상품 구매 버튼을 누르는 상황 등에 사용할 수 있어요. 사용자의 결제는 이동한 페이지에서 진행돼요. 만약 결제 중에 에러가 발생하면 에러 유형에 따라 에러 페이지로 이동해요.
128
224
  * @param {IapCreateOneTimePurchaseOrderOptions} params - 인앱결제를 생성할 때 필요한 정보예요.
129
- * @param {string} params.productId - 주문할 상품의 ID예요.
130
- * @returns {Promise<IapCreateOneTimePurchaseOrderResult | undefined>} 결제에 성공하면 결제 결과 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 인앱결제를 실행할 수 없어서 `undefined`를 반환해요.
225
+ * @returns {() => void} 앱브릿지 cleanup 함수를 반환해요. 인앱결제 기능이 끝나면 반드시 이 함수를 호출해서 리소스를 해제해야 해요.
131
226
  *
132
227
  * @throw {code: "INVALID_PRODUCT_ID"} - 유효하지 않은 상품 ID이거나, 해당 상품이 존재하지 않을 때 발생해요.
133
228
  * @throw {code: "PAYMENT_PENDING"} - 사용자가 요청한 결제가 아직 승인을 기다리고 있을 때 발생해요.
@@ -139,30 +234,127 @@ var IAP = {
139
234
  * @throw {code: "INTERNAL_ERROR"} - 서버 내부 문제로 요청을 처리할 수 없을 때 발생해요.
140
235
  * @throw {code: "KOREAN_ACCOUNT_ONLY"} - iOS 환경에서 사용자의 계정이 한국 계정이 아닐 때 발생해요.
141
236
  * @throw {code: "USER_CANCELED"} - 사용자가 결제를 완료하지 않고 주문서 페이지를 이탈했을 때 발생해요.
237
+ * @throw {code: "PRODUCT_NOT_GRANTED_BY_PARTNER"} - 파트너사의 상품 지급이 실패했을 때 발생해요.
142
238
  */
143
- createOneTimePurchaseOrder: (0, import_bridge_core2.createAsyncBridge)(
144
- "iapCreateOneTimePurchaseOrder"
145
- ),
239
+ createOneTimePurchaseOrder: (params) => {
240
+ const isIAPSupported = isMinVersionSupported({
241
+ android: "5.219.0",
242
+ ios: "5.219.0"
243
+ });
244
+ const noop = () => {
245
+ };
246
+ if (!isIAPSupported) {
247
+ return noop;
248
+ }
249
+ const isProcessProductGrantSupported = isMinVersionSupported({
250
+ android: "5.231.1",
251
+ ios: "5.230.0"
252
+ });
253
+ const { options, onEvent, onError } = params;
254
+ const sku = options.sku ?? options.productId;
255
+ if (!isProcessProductGrantSupported) {
256
+ const v1 = () => {
257
+ (0, import_bridge_core3.createAsyncBridge)(
258
+ "iapCreateOneTimePurchaseOrder"
259
+ )({
260
+ productId: sku
261
+ }).then((response) => {
262
+ Promise.resolve(options.processProductGrant({ orderId: response.orderId })).then(() => {
263
+ onEvent({ type: "success", data: response });
264
+ }).catch((error) => {
265
+ onError(error);
266
+ });
267
+ }).catch((error) => {
268
+ onError(error);
269
+ });
270
+ return noop;
271
+ };
272
+ return v1();
273
+ }
274
+ const unregisterCallbacks = (0, import_bridge_core3.createEventBridge)("requestOneTimePurchase")({
275
+ options: { sku },
276
+ onEvent: async (event) => {
277
+ if (event.type === "purchased") {
278
+ const isProductGranted = await options.processProductGrant({ orderId: event.data.orderId });
279
+ await processProductGrant({
280
+ orderId: event.data.orderId,
281
+ isProductGranted
282
+ }).catch(onError);
283
+ } else {
284
+ onEvent(event);
285
+ }
286
+ },
287
+ onError: (error) => {
288
+ onError(error);
289
+ }
290
+ });
291
+ return unregisterCallbacks;
292
+ },
146
293
  /**
147
294
  * @public
148
295
  * @category 인앱결제
149
- * @name iapGetProductItemList
296
+ * @name getProductItemList
150
297
  * @description 인앱결제로 구매할 수 있는 상품 목록을 가져와요. 상품 목록 화면에 진입할 때 호출해요.
151
298
  * @returns {Promise<{ products: IapProductListItem[] } | undefined>} 상품 목록을 포함한 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 `undefined`를 반환해요.
152
299
  */
153
- getProductItemList: (0, import_bridge_core2.createAsyncBridge)("iapGetProductItemList")
300
+ getProductItemList: (0, import_bridge_core3.createAsyncBridge)("iapGetProductItemList"),
301
+ /**
302
+ * @public
303
+ * @category 인앱결제
304
+ * @name getPendingOrders
305
+ * @description 대기 중인 주문 목록을 가져와요. 이 함수를 사용하면 결제가 아직 완료되지 않은 주문 정보를 확인할 수 있어요.
306
+ * @returns {Promise<{orderIds: string[]}}>} 대기 중인 주문ID 배열을 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
307
+ *
308
+ * @example
309
+ * ### 대기 중인 주문 목록 가져오기
310
+ * ```typescript
311
+ * import { IAP } from '@apps-in-toss/web-framework';
312
+ *
313
+ * async function fetchOrders() {
314
+ * try {
315
+ * const pendingOrders = await IAP.getPendingOrders();
316
+ * return pendingOrders;
317
+ * } catch (error) {
318
+ * console.error(error);
319
+ * }
320
+ * }
321
+ * ```
322
+ */
323
+ getPendingOrders: (0, import_bridge_core3.createAsyncBridge)("getPendingOrders"),
324
+ /**
325
+ * @public
326
+ * @category 인앱결제
327
+ * @name getCompletedOrRefundedOrders
328
+ * @description 인앱결제로 구매하거나 환불한 주문 목록을 가져와요.
329
+ * @returns {Promise<CompletedOrRefundedOrdersResult>} 페이지네이션을 포함한 주문 목록 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
330
+ *
331
+ * @example
332
+ * ```typescript
333
+ * import { IAP } from "@apps-in-toss/web-framework";
334
+ *
335
+ * async function fetchOrders() {
336
+ * try {
337
+ * const response = await IAP.getCompletedOrRefundedOrders();
338
+ * return response;
339
+ * } catch (error) {
340
+ * console.error(error);
341
+ * }
342
+ * }
343
+ * ```
344
+ */
345
+ getCompletedOrRefundedOrders: (0, import_bridge_core3.createAsyncBridge)("getCompletedOrRefundedOrders")
154
346
  };
155
347
 
156
348
  // src/getSafeAreaInsets.ts
157
- var import_bridge_core3 = require("@apps-in-toss/bridge-core");
158
- var getSafeAreaBottom = (0, import_bridge_core3.createConstantBridge)("getSafeAreaBottom");
159
- var getSafeAreaTop = (0, import_bridge_core3.createConstantBridge)("getSafeAreaTop");
349
+ var import_bridge_core4 = require("@apps-in-toss/bridge-core");
350
+ var getSafeAreaBottom = (0, import_bridge_core4.createConstantBridge)("getSafeAreaBottom");
351
+ var getSafeAreaTop = (0, import_bridge_core4.createConstantBridge)("getSafeAreaTop");
160
352
  function getSafeAreaInsets() {
161
353
  return { top: getSafeAreaTop(), bottom: getSafeAreaBottom() };
162
354
  }
163
355
 
164
356
  // src/googleAdMob.ts
165
- var import_bridge_core4 = require("@apps-in-toss/bridge-core");
357
+ var import_bridge_core5 = require("@apps-in-toss/bridge-core");
166
358
  var GoogleAdMob = {
167
359
  /**
168
360
  * @public
@@ -213,9 +405,9 @@ var GoogleAdMob = {
213
405
  * ```
214
406
  */
215
407
  loadAdMobInterstitialAd: Object.assign(
216
- (0, import_bridge_core4.createEventBridge)("loadAdMobInterstitialAd"),
408
+ (0, import_bridge_core5.createEventBridge)("loadAdMobInterstitialAd"),
217
409
  {
218
- isSupported: (0, import_bridge_core4.createConstantBridge)("loadAdMobInterstitialAd_isSupported")
410
+ isSupported: (0, import_bridge_core5.createConstantBridge)("loadAdMobInterstitialAd_isSupported")
219
411
  }
220
412
  ),
221
413
  /**
@@ -288,9 +480,9 @@ var GoogleAdMob = {
288
480
  * ```
289
481
  */
290
482
  showAdMobInterstitialAd: Object.assign(
291
- (0, import_bridge_core4.createEventBridge)("showAdMobInterstitialAd"),
483
+ (0, import_bridge_core5.createEventBridge)("showAdMobInterstitialAd"),
292
484
  {
293
- isSupported: (0, import_bridge_core4.createConstantBridge)("showAdMobInterstitialAd_isSupported")
485
+ isSupported: (0, import_bridge_core5.createConstantBridge)("showAdMobInterstitialAd_isSupported")
294
486
  }
295
487
  ),
296
488
  /**
@@ -342,9 +534,9 @@ var GoogleAdMob = {
342
534
  * ```
343
535
  */
344
536
  loadAdMobRewardedAd: Object.assign(
345
- (0, import_bridge_core4.createEventBridge)("loadAdMobRewardedAd"),
537
+ (0, import_bridge_core5.createEventBridge)("loadAdMobRewardedAd"),
346
538
  {
347
- isSupported: (0, import_bridge_core4.createConstantBridge)("loadAdMobRewardedAd_isSupported")
539
+ isSupported: (0, import_bridge_core5.createConstantBridge)("loadAdMobRewardedAd_isSupported")
348
540
  }
349
541
  ),
350
542
  /**
@@ -417,9 +609,9 @@ var GoogleAdMob = {
417
609
  * ```
418
610
  */
419
611
  showAdMobRewardedAd: Object.assign(
420
- (0, import_bridge_core4.createEventBridge)("showAdMobRewardedAd"),
612
+ (0, import_bridge_core5.createEventBridge)("showAdMobRewardedAd"),
421
613
  {
422
- isSupported: (0, import_bridge_core4.createConstantBridge)("showAdMobRewardedAd_isSupported")
614
+ isSupported: (0, import_bridge_core5.createConstantBridge)("showAdMobRewardedAd_isSupported")
423
615
  }
424
616
  ),
425
617
  /**
@@ -476,8 +668,8 @@ var GoogleAdMob = {
476
668
  * }
477
669
  * ```
478
670
  */
479
- loadAppsInTossAdMob: Object.assign((0, import_bridge_core4.createEventBridge)("loadAppsInTossAdMob"), {
480
- isSupported: (0, import_bridge_core4.createConstantBridge)("loadAppsInTossAdMob_isSupported")
671
+ loadAppsInTossAdMob: Object.assign((0, import_bridge_core5.createEventBridge)("loadAppsInTossAdMob"), {
672
+ isSupported: (0, import_bridge_core5.createConstantBridge)("loadAppsInTossAdMob_isSupported")
481
673
  }),
482
674
  /**
483
675
  * @public
@@ -554,19 +746,19 @@ var GoogleAdMob = {
554
746
  * }
555
747
  * ```
556
748
  */
557
- showAppsInTossAdMob: Object.assign((0, import_bridge_core4.createEventBridge)("showAppsInTossAdMob"), {
558
- isSupported: (0, import_bridge_core4.createConstantBridge)("showAppsInTossAdMob_isSupported")
749
+ showAppsInTossAdMob: Object.assign((0, import_bridge_core5.createEventBridge)("showAppsInTossAdMob"), {
750
+ isSupported: (0, import_bridge_core5.createConstantBridge)("showAppsInTossAdMob_isSupported")
559
751
  })
560
752
  };
561
753
 
562
754
  // src/graniteEvent.ts
563
- var import_bridge_core5 = require("@apps-in-toss/bridge-core");
755
+ var import_bridge_core6 = require("@apps-in-toss/bridge-core");
564
756
  var graniteEvent = {
565
757
  addEventListener: (event, {
566
758
  onEvent,
567
759
  onError,
568
760
  options
569
- }) => (0, import_bridge_core5.createEventBridge)(event)({
761
+ }) => (0, import_bridge_core6.createEventBridge)(event)({
570
762
  onEvent,
571
763
  onError: onError ?? (() => {
572
764
  }),
@@ -574,97 +766,6 @@ var graniteEvent = {
574
766
  })
575
767
  };
576
768
 
577
- // src/isMinVersionSupported.ts
578
- var import_bridge_core6 = require("@apps-in-toss/bridge-core");
579
-
580
- // src/utils/compareVersion.ts
581
- var SEMVER_REGEX = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\\-]+(?:\.[\da-z\\-]+)*))?(?:\+[\da-z\\-]+(?:\.[\da-z\\-]+)*)?)?)?$/i;
582
- var isWildcard = (val) => ["*", "x", "X"].includes(val);
583
- var tryParse = (val) => {
584
- const num = parseInt(val, 10);
585
- return isNaN(num) ? val : num;
586
- };
587
- var coerceTypes = (a, b) => {
588
- return typeof a === typeof b ? [a, b] : [String(a), String(b)];
589
- };
590
- var compareValues = (a, b) => {
591
- if (isWildcard(a) || isWildcard(b)) {
592
- return 0;
593
- }
594
- const [aVal, bVal] = coerceTypes(tryParse(a), tryParse(b));
595
- if (aVal > bVal) {
596
- return 1;
597
- }
598
- if (aVal < bVal) {
599
- return -1;
600
- }
601
- return 0;
602
- };
603
- var parseVersion = (version) => {
604
- if (typeof version !== "string") {
605
- throw new TypeError("Invalid argument: expected a string");
606
- }
607
- const match = version.match(SEMVER_REGEX);
608
- if (!match) {
609
- throw new Error(`Invalid semver: '${version}'`);
610
- }
611
- const [, major, minor, patch, build, preRelease] = match;
612
- return [major, minor, patch, build, preRelease];
613
- };
614
- var compareSegments = (a, b) => {
615
- const maxLength = Math.max(a.length, b.length);
616
- for (let i = 0; i < maxLength; i++) {
617
- const segA = a[i] ?? "0";
618
- const segB = b[i] ?? "0";
619
- const result = compareValues(segA, segB);
620
- if (result !== 0) {
621
- return result;
622
- }
623
- }
624
- return 0;
625
- };
626
- var compareVersions = (v1, v2) => {
627
- const seg1 = parseVersion(v1);
628
- const seg2 = parseVersion(v2);
629
- const preRelease1 = seg1.pop();
630
- const preRelease2 = seg2.pop();
631
- const mainCompare = compareSegments(seg1, seg2);
632
- if (mainCompare !== 0) {
633
- return mainCompare;
634
- }
635
- if (preRelease1 && preRelease2) {
636
- return compareSegments(preRelease1.split("."), preRelease2.split("."));
637
- }
638
- if (preRelease1) {
639
- return -1;
640
- }
641
- if (preRelease2) {
642
- return 1;
643
- }
644
- return 0;
645
- };
646
-
647
- // src/isMinVersionSupported.ts
648
- function isMinVersionSupported(minVersions) {
649
- const operationalEnvironment = (0, import_bridge_core6.createConstantBridge)("getOperationalEnvironment")();
650
- if (operationalEnvironment === "sandbox") {
651
- return true;
652
- }
653
- const currentVersion = (0, import_bridge_core6.createConstantBridge)("getTossAppVersion")();
654
- const isIOS = (0, import_bridge_core6.createConstantBridge)("getPlatformOS")() === "ios";
655
- const minVersion = isIOS ? minVersions.ios : minVersions.android;
656
- if (minVersion === void 0) {
657
- return false;
658
- }
659
- if (minVersion === "always") {
660
- return true;
661
- }
662
- if (minVersion === "never") {
663
- return false;
664
- }
665
- return compareVersions(currentVersion, minVersion) >= 0;
666
- }
667
-
668
769
  // src/appsInTossEvent.ts
669
770
  var import_bridge_core7 = require("@apps-in-toss/bridge-core");
670
771
  var appsInTossEvent = {
package/built/index.d.cts CHANGED
@@ -85,6 +85,16 @@ declare const Storage: {
85
85
  clearItems: (args_0: void) => Promise<void>;
86
86
  };
87
87
 
88
+ type Sku = {
89
+ /**
90
+ * @deprecated `productId`는 더 이상 사용하지 않아요. 대신 `sku`를 사용해요.
91
+ */
92
+ productId: string;
93
+ sku?: string;
94
+ } | {
95
+ productId?: never;
96
+ sku: string;
97
+ };
88
98
  /**
89
99
  * @public
90
100
  * @category 인앱결제
@@ -107,6 +117,29 @@ interface IapCreateOneTimePurchaseOrderResult {
107
117
  fraction: number;
108
118
  miniAppIconUrl: string | null;
109
119
  }
120
+ interface SuccessEvent {
121
+ type: 'success';
122
+ data: IapCreateOneTimePurchaseOrderResult;
123
+ }
124
+ /**
125
+ * @public
126
+ * @category 인앱결제
127
+ * @name IapCreateOneTimePurchaseOrderOptions
128
+ * @property {Sku} options - 결제할 상품의 정보예요.
129
+ * @property {string} options.sku - 주문할 상품의 고유 ID예요.
130
+ * @property {(params: { orderId: string }) => boolean | Promise<boolean>} processProductGrant - 주문이 만들어진 뒤 실제로 상품을 지급할 때 호출해요. `orderId`를 받아서 지급 성공 여부를 `true` 또는 `Promise<true>`로 반환해요. 지급에 실패하면 `false`를 반환해요.
131
+ * @property {(event: SuccessEvent) => void | Promise<void>} onEvent - 결제가 성공했을 때 호출해요. `event.type`이 `'success'`이고, `event.data`에 `IapCreateOneTimePurchaseOrderResult`가 들어 있어요.
132
+ * @property {(error: unknown) => void | Promise<void>} onError - 결제 과정에서 에러가 발생했을 때 호출해요. 에러 객체를 받아서 로깅하거나 복구 절차를 실행할 수 있어요.
133
+ */
134
+ interface IapCreateOneTimePurchaseOrderOptions {
135
+ options: Sku & {
136
+ processProductGrant: (params: {
137
+ orderId: string;
138
+ }) => boolean | Promise<boolean>;
139
+ };
140
+ onEvent: (event: SuccessEvent) => void | Promise<void>;
141
+ onError: (error: unknown) => void | Promise<void>;
142
+ }
110
143
  /**
111
144
  * @public
112
145
  * @category 인앱결제
@@ -125,6 +158,29 @@ interface IapProductListItem {
125
158
  iconUrl: string;
126
159
  description: string;
127
160
  }
161
+ /**
162
+ * @public
163
+ * @category 인앱결제
164
+ * @name CompletedOrRefundedOrdersResult
165
+ * @description 인앱결제로 구매하거나 환불한 주문 목록을 나타내는 객체예요. 페이지네이션 정보를 포함해요.
166
+ * @property {boolean} hasNext 다음 페이지가 있는지 여부예요. `true`면 더 많은 주문이 남아 있어요.
167
+ * @property {string | null} [nextKey] 다음 주문 목록을 조회할 때 사용할 키예요. 마지막 페이지라면 `null`이거나 생략될 수 있어요.
168
+ * @property {Array} orders 주문 정보를 담은 배열이에요. 각 요소는 하나의 주문을 나타내요.
169
+ * @property {string} orders[].orderId 주문의 고유 ID예요.
170
+ * @property {string} orders[].sku 주문 상품의 고유 ID예요.
171
+ * @property {'COMPLETED' | 'REFUNDED'} orders[].status 주문의 상태예요. 'COMPLETED'는 주문이 완료된 상태, 'REFUNDED'는 환불된 상태를 의미해요.
172
+ * @property {string} orders[].date 주문의 날짜 정보예요. ISO 8601 형식(YYYY-MM-DDTHH:mm:ss)을 사용해요. 예를 들어 "2025-09-22T00:00:00" 형식으로 제공돼요. 주문 상태가 `COMPLETED`라면 주문한 날짜를, `REFUNDED`라면 환불한 날짜를 나타내요.
173
+ */
174
+ interface CompletedOrRefundedOrdersResult {
175
+ hasNext: boolean;
176
+ nextKey?: string | null;
177
+ orders: {
178
+ orderId: string;
179
+ sku: string;
180
+ status: 'COMPLETED' | 'REFUNDED';
181
+ date: string;
182
+ }[];
183
+ }
128
184
  /**
129
185
  * @public
130
186
  * @category 인앱결제
@@ -137,12 +193,11 @@ declare const IAP: {
137
193
  /**
138
194
  * @public
139
195
  * @category 인앱결제
140
- * @name iapCreateOneTimePurchaseOrder
196
+ * @name createOneTimePurchaseOrder
141
197
  * @description
142
198
  * 특정 인앱결제 주문서 페이지로 이동해요. 사용자가 상품 구매 버튼을 누르는 상황 등에 사용할 수 있어요. 사용자의 결제는 이동한 페이지에서 진행돼요. 만약 결제 중에 에러가 발생하면 에러 유형에 따라 에러 페이지로 이동해요.
143
199
  * @param {IapCreateOneTimePurchaseOrderOptions} params - 인앱결제를 생성할 때 필요한 정보예요.
144
- * @param {string} params.productId - 주문할 상품의 ID예요.
145
- * @returns {Promise<IapCreateOneTimePurchaseOrderResult | undefined>} 결제에 성공하면 결제 결과 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 인앱결제를 실행할 수 없어서 `undefined`를 반환해요.
200
+ * @returns {() => void} 앱브릿지 cleanup 함수를 반환해요. 인앱결제 기능이 끝나면 반드시 이 함수를 호출해서 리소스를 해제해야 해요.
146
201
  *
147
202
  * @throw {code: "INVALID_PRODUCT_ID"} - 유효하지 않은 상품 ID이거나, 해당 상품이 존재하지 않을 때 발생해요.
148
203
  * @throw {code: "PAYMENT_PENDING"} - 사용자가 요청한 결제가 아직 승인을 기다리고 있을 때 발생해요.
@@ -154,20 +209,66 @@ declare const IAP: {
154
209
  * @throw {code: "INTERNAL_ERROR"} - 서버 내부 문제로 요청을 처리할 수 없을 때 발생해요.
155
210
  * @throw {code: "KOREAN_ACCOUNT_ONLY"} - iOS 환경에서 사용자의 계정이 한국 계정이 아닐 때 발생해요.
156
211
  * @throw {code: "USER_CANCELED"} - 사용자가 결제를 완료하지 않고 주문서 페이지를 이탈했을 때 발생해요.
212
+ * @throw {code: "PRODUCT_NOT_GRANTED_BY_PARTNER"} - 파트너사의 상품 지급이 실패했을 때 발생해요.
157
213
  */
158
- createOneTimePurchaseOrder: (args_0: {
159
- productId: string;
160
- }) => Promise<IapCreateOneTimePurchaseOrderResult>;
214
+ createOneTimePurchaseOrder: (params: IapCreateOneTimePurchaseOrderOptions) => () => void;
161
215
  /**
162
216
  * @public
163
217
  * @category 인앱결제
164
- * @name iapGetProductItemList
218
+ * @name getProductItemList
165
219
  * @description 인앱결제로 구매할 수 있는 상품 목록을 가져와요. 상품 목록 화면에 진입할 때 호출해요.
166
220
  * @returns {Promise<{ products: IapProductListItem[] } | undefined>} 상품 목록을 포함한 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 `undefined`를 반환해요.
167
221
  */
168
222
  getProductItemList: () => Promise<{
169
223
  products: IapProductListItem[];
170
224
  }>;
225
+ /**
226
+ * @public
227
+ * @category 인앱결제
228
+ * @name getPendingOrders
229
+ * @description 대기 중인 주문 목록을 가져와요. 이 함수를 사용하면 결제가 아직 완료되지 않은 주문 정보를 확인할 수 있어요.
230
+ * @returns {Promise<{orderIds: string[]}}>} 대기 중인 주문ID 배열을 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
231
+ *
232
+ * @example
233
+ * ### 대기 중인 주문 목록 가져오기
234
+ * ```typescript
235
+ * import { IAP } from '@apps-in-toss/web-framework';
236
+ *
237
+ * async function fetchOrders() {
238
+ * try {
239
+ * const pendingOrders = await IAP.getPendingOrders();
240
+ * return pendingOrders;
241
+ * } catch (error) {
242
+ * console.error(error);
243
+ * }
244
+ * }
245
+ * ```
246
+ */
247
+ getPendingOrders: () => Promise<{
248
+ orderIds: string[];
249
+ }>;
250
+ /**
251
+ * @public
252
+ * @category 인앱결제
253
+ * @name getCompletedOrRefundedOrders
254
+ * @description 인앱결제로 구매하거나 환불한 주문 목록을 가져와요.
255
+ * @returns {Promise<CompletedOrRefundedOrdersResult>} 페이지네이션을 포함한 주문 목록 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * import { IAP } from "@apps-in-toss/web-framework";
260
+ *
261
+ * async function fetchOrders() {
262
+ * try {
263
+ * const response = await IAP.getCompletedOrRefundedOrders();
264
+ * return response;
265
+ * } catch (error) {
266
+ * console.error(error);
267
+ * }
268
+ * }
269
+ * ```
270
+ */
271
+ getCompletedOrRefundedOrders: () => Promise<CompletedOrRefundedOrdersResult>;
171
272
  };
172
273
 
173
274
  declare function getSafeAreaInsets(): {
@@ -787,4 +888,4 @@ declare const startUpdateLocation: {
787
888
  openPermissionDialog(): Promise<"denied" | "allowed">;
788
889
  };
789
890
 
790
- export { type AddAccessoryButtonOptions, type AppsInTossEvent, type AppsInTossGlobals, GoogleAdMob, type GraniteEvent, IAP, type IapCreateOneTimePurchaseOrderResult, type IapProductListItem, Storage, type TdsEvent, appsInTossEvent, env, fetchAlbumPhotos, fetchContacts, getAppsInTossGlobals, getClipboardText, getCurrentLocation, getSafeAreaInsets, graniteEvent, isMinVersionSupported, openCamera, partner, setClipboardText, startUpdateLocation, tdsEvent };
891
+ export { type AddAccessoryButtonOptions, type AppsInTossEvent, type AppsInTossGlobals, type CompletedOrRefundedOrdersResult, GoogleAdMob, type GraniteEvent, IAP, type IapCreateOneTimePurchaseOrderOptions, type IapProductListItem, Storage, type TdsEvent, appsInTossEvent, env, fetchAlbumPhotos, fetchContacts, getAppsInTossGlobals, getClipboardText, getCurrentLocation, getSafeAreaInsets, graniteEvent, isMinVersionSupported, openCamera, partner, setClipboardText, startUpdateLocation, tdsEvent };
package/built/index.d.ts CHANGED
@@ -85,6 +85,16 @@ declare const Storage: {
85
85
  clearItems: (args_0: void) => Promise<void>;
86
86
  };
87
87
 
88
+ type Sku = {
89
+ /**
90
+ * @deprecated `productId`는 더 이상 사용하지 않아요. 대신 `sku`를 사용해요.
91
+ */
92
+ productId: string;
93
+ sku?: string;
94
+ } | {
95
+ productId?: never;
96
+ sku: string;
97
+ };
88
98
  /**
89
99
  * @public
90
100
  * @category 인앱결제
@@ -107,6 +117,29 @@ interface IapCreateOneTimePurchaseOrderResult {
107
117
  fraction: number;
108
118
  miniAppIconUrl: string | null;
109
119
  }
120
+ interface SuccessEvent {
121
+ type: 'success';
122
+ data: IapCreateOneTimePurchaseOrderResult;
123
+ }
124
+ /**
125
+ * @public
126
+ * @category 인앱결제
127
+ * @name IapCreateOneTimePurchaseOrderOptions
128
+ * @property {Sku} options - 결제할 상품의 정보예요.
129
+ * @property {string} options.sku - 주문할 상품의 고유 ID예요.
130
+ * @property {(params: { orderId: string }) => boolean | Promise<boolean>} processProductGrant - 주문이 만들어진 뒤 실제로 상품을 지급할 때 호출해요. `orderId`를 받아서 지급 성공 여부를 `true` 또는 `Promise<true>`로 반환해요. 지급에 실패하면 `false`를 반환해요.
131
+ * @property {(event: SuccessEvent) => void | Promise<void>} onEvent - 결제가 성공했을 때 호출해요. `event.type`이 `'success'`이고, `event.data`에 `IapCreateOneTimePurchaseOrderResult`가 들어 있어요.
132
+ * @property {(error: unknown) => void | Promise<void>} onError - 결제 과정에서 에러가 발생했을 때 호출해요. 에러 객체를 받아서 로깅하거나 복구 절차를 실행할 수 있어요.
133
+ */
134
+ interface IapCreateOneTimePurchaseOrderOptions {
135
+ options: Sku & {
136
+ processProductGrant: (params: {
137
+ orderId: string;
138
+ }) => boolean | Promise<boolean>;
139
+ };
140
+ onEvent: (event: SuccessEvent) => void | Promise<void>;
141
+ onError: (error: unknown) => void | Promise<void>;
142
+ }
110
143
  /**
111
144
  * @public
112
145
  * @category 인앱결제
@@ -125,6 +158,29 @@ interface IapProductListItem {
125
158
  iconUrl: string;
126
159
  description: string;
127
160
  }
161
+ /**
162
+ * @public
163
+ * @category 인앱결제
164
+ * @name CompletedOrRefundedOrdersResult
165
+ * @description 인앱결제로 구매하거나 환불한 주문 목록을 나타내는 객체예요. 페이지네이션 정보를 포함해요.
166
+ * @property {boolean} hasNext 다음 페이지가 있는지 여부예요. `true`면 더 많은 주문이 남아 있어요.
167
+ * @property {string | null} [nextKey] 다음 주문 목록을 조회할 때 사용할 키예요. 마지막 페이지라면 `null`이거나 생략될 수 있어요.
168
+ * @property {Array} orders 주문 정보를 담은 배열이에요. 각 요소는 하나의 주문을 나타내요.
169
+ * @property {string} orders[].orderId 주문의 고유 ID예요.
170
+ * @property {string} orders[].sku 주문 상품의 고유 ID예요.
171
+ * @property {'COMPLETED' | 'REFUNDED'} orders[].status 주문의 상태예요. 'COMPLETED'는 주문이 완료된 상태, 'REFUNDED'는 환불된 상태를 의미해요.
172
+ * @property {string} orders[].date 주문의 날짜 정보예요. ISO 8601 형식(YYYY-MM-DDTHH:mm:ss)을 사용해요. 예를 들어 "2025-09-22T00:00:00" 형식으로 제공돼요. 주문 상태가 `COMPLETED`라면 주문한 날짜를, `REFUNDED`라면 환불한 날짜를 나타내요.
173
+ */
174
+ interface CompletedOrRefundedOrdersResult {
175
+ hasNext: boolean;
176
+ nextKey?: string | null;
177
+ orders: {
178
+ orderId: string;
179
+ sku: string;
180
+ status: 'COMPLETED' | 'REFUNDED';
181
+ date: string;
182
+ }[];
183
+ }
128
184
  /**
129
185
  * @public
130
186
  * @category 인앱결제
@@ -137,12 +193,11 @@ declare const IAP: {
137
193
  /**
138
194
  * @public
139
195
  * @category 인앱결제
140
- * @name iapCreateOneTimePurchaseOrder
196
+ * @name createOneTimePurchaseOrder
141
197
  * @description
142
198
  * 특정 인앱결제 주문서 페이지로 이동해요. 사용자가 상품 구매 버튼을 누르는 상황 등에 사용할 수 있어요. 사용자의 결제는 이동한 페이지에서 진행돼요. 만약 결제 중에 에러가 발생하면 에러 유형에 따라 에러 페이지로 이동해요.
143
199
  * @param {IapCreateOneTimePurchaseOrderOptions} params - 인앱결제를 생성할 때 필요한 정보예요.
144
- * @param {string} params.productId - 주문할 상품의 ID예요.
145
- * @returns {Promise<IapCreateOneTimePurchaseOrderResult | undefined>} 결제에 성공하면 결제 결과 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 인앱결제를 실행할 수 없어서 `undefined`를 반환해요.
200
+ * @returns {() => void} 앱브릿지 cleanup 함수를 반환해요. 인앱결제 기능이 끝나면 반드시 이 함수를 호출해서 리소스를 해제해야 해요.
146
201
  *
147
202
  * @throw {code: "INVALID_PRODUCT_ID"} - 유효하지 않은 상품 ID이거나, 해당 상품이 존재하지 않을 때 발생해요.
148
203
  * @throw {code: "PAYMENT_PENDING"} - 사용자가 요청한 결제가 아직 승인을 기다리고 있을 때 발생해요.
@@ -154,20 +209,66 @@ declare const IAP: {
154
209
  * @throw {code: "INTERNAL_ERROR"} - 서버 내부 문제로 요청을 처리할 수 없을 때 발생해요.
155
210
  * @throw {code: "KOREAN_ACCOUNT_ONLY"} - iOS 환경에서 사용자의 계정이 한국 계정이 아닐 때 발생해요.
156
211
  * @throw {code: "USER_CANCELED"} - 사용자가 결제를 완료하지 않고 주문서 페이지를 이탈했을 때 발생해요.
212
+ * @throw {code: "PRODUCT_NOT_GRANTED_BY_PARTNER"} - 파트너사의 상품 지급이 실패했을 때 발생해요.
157
213
  */
158
- createOneTimePurchaseOrder: (args_0: {
159
- productId: string;
160
- }) => Promise<IapCreateOneTimePurchaseOrderResult>;
214
+ createOneTimePurchaseOrder: (params: IapCreateOneTimePurchaseOrderOptions) => () => void;
161
215
  /**
162
216
  * @public
163
217
  * @category 인앱결제
164
- * @name iapGetProductItemList
218
+ * @name getProductItemList
165
219
  * @description 인앱결제로 구매할 수 있는 상품 목록을 가져와요. 상품 목록 화면에 진입할 때 호출해요.
166
220
  * @returns {Promise<{ products: IapProductListItem[] } | undefined>} 상품 목록을 포함한 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 `undefined`를 반환해요.
167
221
  */
168
222
  getProductItemList: () => Promise<{
169
223
  products: IapProductListItem[];
170
224
  }>;
225
+ /**
226
+ * @public
227
+ * @category 인앱결제
228
+ * @name getPendingOrders
229
+ * @description 대기 중인 주문 목록을 가져와요. 이 함수를 사용하면 결제가 아직 완료되지 않은 주문 정보를 확인할 수 있어요.
230
+ * @returns {Promise<{orderIds: string[]}}>} 대기 중인 주문ID 배열을 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
231
+ *
232
+ * @example
233
+ * ### 대기 중인 주문 목록 가져오기
234
+ * ```typescript
235
+ * import { IAP } from '@apps-in-toss/web-framework';
236
+ *
237
+ * async function fetchOrders() {
238
+ * try {
239
+ * const pendingOrders = await IAP.getPendingOrders();
240
+ * return pendingOrders;
241
+ * } catch (error) {
242
+ * console.error(error);
243
+ * }
244
+ * }
245
+ * ```
246
+ */
247
+ getPendingOrders: () => Promise<{
248
+ orderIds: string[];
249
+ }>;
250
+ /**
251
+ * @public
252
+ * @category 인앱결제
253
+ * @name getCompletedOrRefundedOrders
254
+ * @description 인앱결제로 구매하거나 환불한 주문 목록을 가져와요.
255
+ * @returns {Promise<CompletedOrRefundedOrdersResult>} 페이지네이션을 포함한 주문 목록 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
256
+ *
257
+ * @example
258
+ * ```typescript
259
+ * import { IAP } from "@apps-in-toss/web-framework";
260
+ *
261
+ * async function fetchOrders() {
262
+ * try {
263
+ * const response = await IAP.getCompletedOrRefundedOrders();
264
+ * return response;
265
+ * } catch (error) {
266
+ * console.error(error);
267
+ * }
268
+ * }
269
+ * ```
270
+ */
271
+ getCompletedOrRefundedOrders: () => Promise<CompletedOrRefundedOrdersResult>;
171
272
  };
172
273
 
173
274
  declare function getSafeAreaInsets(): {
@@ -787,4 +888,4 @@ declare const startUpdateLocation: {
787
888
  openPermissionDialog(): Promise<"denied" | "allowed">;
788
889
  };
789
890
 
790
- export { type AddAccessoryButtonOptions, type AppsInTossEvent, type AppsInTossGlobals, GoogleAdMob, type GraniteEvent, IAP, type IapCreateOneTimePurchaseOrderResult, type IapProductListItem, Storage, type TdsEvent, appsInTossEvent, env, fetchAlbumPhotos, fetchContacts, getAppsInTossGlobals, getClipboardText, getCurrentLocation, getSafeAreaInsets, graniteEvent, isMinVersionSupported, openCamera, partner, setClipboardText, startUpdateLocation, tdsEvent };
891
+ export { type AddAccessoryButtonOptions, type AppsInTossEvent, type AppsInTossGlobals, type CompletedOrRefundedOrdersResult, GoogleAdMob, type GraniteEvent, IAP, type IapCreateOneTimePurchaseOrderOptions, type IapProductListItem, Storage, type TdsEvent, appsInTossEvent, env, fetchAlbumPhotos, fetchContacts, getAppsInTossGlobals, getClipboardText, getCurrentLocation, getSafeAreaInsets, graniteEvent, isMinVersionSupported, openCamera, partner, setClipboardText, startUpdateLocation, tdsEvent };
package/built/index.js CHANGED
@@ -76,17 +76,112 @@ var Storage = {
76
76
  };
77
77
 
78
78
  // src/iap.ts
79
- import { createAsyncBridge as createAsyncBridge2 } from "@apps-in-toss/bridge-core";
79
+ import { createAsyncBridge as createAsyncBridge2, createEventBridge } from "@apps-in-toss/bridge-core";
80
+
81
+ // src/isMinVersionSupported.ts
82
+ import { createConstantBridge } from "@apps-in-toss/bridge-core";
83
+
84
+ // src/utils/compareVersion.ts
85
+ var SEMVER_REGEX = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\\-]+(?:\.[\da-z\\-]+)*))?(?:\+[\da-z\\-]+(?:\.[\da-z\\-]+)*)?)?)?$/i;
86
+ var isWildcard = (val) => ["*", "x", "X"].includes(val);
87
+ var tryParse = (val) => {
88
+ const num = parseInt(val, 10);
89
+ return isNaN(num) ? val : num;
90
+ };
91
+ var coerceTypes = (a, b) => {
92
+ return typeof a === typeof b ? [a, b] : [String(a), String(b)];
93
+ };
94
+ var compareValues = (a, b) => {
95
+ if (isWildcard(a) || isWildcard(b)) {
96
+ return 0;
97
+ }
98
+ const [aVal, bVal] = coerceTypes(tryParse(a), tryParse(b));
99
+ if (aVal > bVal) {
100
+ return 1;
101
+ }
102
+ if (aVal < bVal) {
103
+ return -1;
104
+ }
105
+ return 0;
106
+ };
107
+ var parseVersion = (version) => {
108
+ if (typeof version !== "string") {
109
+ throw new TypeError("Invalid argument: expected a string");
110
+ }
111
+ const match = version.match(SEMVER_REGEX);
112
+ if (!match) {
113
+ throw new Error(`Invalid semver: '${version}'`);
114
+ }
115
+ const [, major, minor, patch, build, preRelease] = match;
116
+ return [major, minor, patch, build, preRelease];
117
+ };
118
+ var compareSegments = (a, b) => {
119
+ const maxLength = Math.max(a.length, b.length);
120
+ for (let i = 0; i < maxLength; i++) {
121
+ const segA = a[i] ?? "0";
122
+ const segB = b[i] ?? "0";
123
+ const result = compareValues(segA, segB);
124
+ if (result !== 0) {
125
+ return result;
126
+ }
127
+ }
128
+ return 0;
129
+ };
130
+ var compareVersions = (v1, v2) => {
131
+ const seg1 = parseVersion(v1);
132
+ const seg2 = parseVersion(v2);
133
+ const preRelease1 = seg1.pop();
134
+ const preRelease2 = seg2.pop();
135
+ const mainCompare = compareSegments(seg1, seg2);
136
+ if (mainCompare !== 0) {
137
+ return mainCompare;
138
+ }
139
+ if (preRelease1 && preRelease2) {
140
+ return compareSegments(preRelease1.split("."), preRelease2.split("."));
141
+ }
142
+ if (preRelease1) {
143
+ return -1;
144
+ }
145
+ if (preRelease2) {
146
+ return 1;
147
+ }
148
+ return 0;
149
+ };
150
+
151
+ // src/isMinVersionSupported.ts
152
+ function isMinVersionSupported(minVersions) {
153
+ const operationalEnvironment = createConstantBridge("getOperationalEnvironment")();
154
+ if (operationalEnvironment === "sandbox") {
155
+ return true;
156
+ }
157
+ const currentVersion = createConstantBridge("getTossAppVersion")();
158
+ const isIOS = createConstantBridge("getPlatformOS")() === "ios";
159
+ const minVersion = isIOS ? minVersions.ios : minVersions.android;
160
+ if (minVersion === void 0) {
161
+ return false;
162
+ }
163
+ if (minVersion === "always") {
164
+ return true;
165
+ }
166
+ if (minVersion === "never") {
167
+ return false;
168
+ }
169
+ return compareVersions(currentVersion, minVersion) >= 0;
170
+ }
171
+
172
+ // src/iap.ts
173
+ function processProductGrant(params) {
174
+ return createAsyncBridge2("processProductGrant")(params);
175
+ }
80
176
  var IAP = {
81
177
  /**
82
178
  * @public
83
179
  * @category 인앱결제
84
- * @name iapCreateOneTimePurchaseOrder
180
+ * @name createOneTimePurchaseOrder
85
181
  * @description
86
182
  * 특정 인앱결제 주문서 페이지로 이동해요. 사용자가 상품 구매 버튼을 누르는 상황 등에 사용할 수 있어요. 사용자의 결제는 이동한 페이지에서 진행돼요. 만약 결제 중에 에러가 발생하면 에러 유형에 따라 에러 페이지로 이동해요.
87
183
  * @param {IapCreateOneTimePurchaseOrderOptions} params - 인앱결제를 생성할 때 필요한 정보예요.
88
- * @param {string} params.productId - 주문할 상품의 ID예요.
89
- * @returns {Promise<IapCreateOneTimePurchaseOrderResult | undefined>} 결제에 성공하면 결제 결과 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 인앱결제를 실행할 수 없어서 `undefined`를 반환해요.
184
+ * @returns {() => void} 앱브릿지 cleanup 함수를 반환해요. 인앱결제 기능이 끝나면 반드시 이 함수를 호출해서 리소스를 해제해야 해요.
90
185
  *
91
186
  * @throw {code: "INVALID_PRODUCT_ID"} - 유효하지 않은 상품 ID이거나, 해당 상품이 존재하지 않을 때 발생해요.
92
187
  * @throw {code: "PAYMENT_PENDING"} - 사용자가 요청한 결제가 아직 승인을 기다리고 있을 때 발생해요.
@@ -98,30 +193,127 @@ var IAP = {
98
193
  * @throw {code: "INTERNAL_ERROR"} - 서버 내부 문제로 요청을 처리할 수 없을 때 발생해요.
99
194
  * @throw {code: "KOREAN_ACCOUNT_ONLY"} - iOS 환경에서 사용자의 계정이 한국 계정이 아닐 때 발생해요.
100
195
  * @throw {code: "USER_CANCELED"} - 사용자가 결제를 완료하지 않고 주문서 페이지를 이탈했을 때 발생해요.
196
+ * @throw {code: "PRODUCT_NOT_GRANTED_BY_PARTNER"} - 파트너사의 상품 지급이 실패했을 때 발생해요.
101
197
  */
102
- createOneTimePurchaseOrder: createAsyncBridge2(
103
- "iapCreateOneTimePurchaseOrder"
104
- ),
198
+ createOneTimePurchaseOrder: (params) => {
199
+ const isIAPSupported = isMinVersionSupported({
200
+ android: "5.219.0",
201
+ ios: "5.219.0"
202
+ });
203
+ const noop = () => {
204
+ };
205
+ if (!isIAPSupported) {
206
+ return noop;
207
+ }
208
+ const isProcessProductGrantSupported = isMinVersionSupported({
209
+ android: "5.231.1",
210
+ ios: "5.230.0"
211
+ });
212
+ const { options, onEvent, onError } = params;
213
+ const sku = options.sku ?? options.productId;
214
+ if (!isProcessProductGrantSupported) {
215
+ const v1 = () => {
216
+ createAsyncBridge2(
217
+ "iapCreateOneTimePurchaseOrder"
218
+ )({
219
+ productId: sku
220
+ }).then((response) => {
221
+ Promise.resolve(options.processProductGrant({ orderId: response.orderId })).then(() => {
222
+ onEvent({ type: "success", data: response });
223
+ }).catch((error) => {
224
+ onError(error);
225
+ });
226
+ }).catch((error) => {
227
+ onError(error);
228
+ });
229
+ return noop;
230
+ };
231
+ return v1();
232
+ }
233
+ const unregisterCallbacks = createEventBridge("requestOneTimePurchase")({
234
+ options: { sku },
235
+ onEvent: async (event) => {
236
+ if (event.type === "purchased") {
237
+ const isProductGranted = await options.processProductGrant({ orderId: event.data.orderId });
238
+ await processProductGrant({
239
+ orderId: event.data.orderId,
240
+ isProductGranted
241
+ }).catch(onError);
242
+ } else {
243
+ onEvent(event);
244
+ }
245
+ },
246
+ onError: (error) => {
247
+ onError(error);
248
+ }
249
+ });
250
+ return unregisterCallbacks;
251
+ },
105
252
  /**
106
253
  * @public
107
254
  * @category 인앱결제
108
- * @name iapGetProductItemList
255
+ * @name getProductItemList
109
256
  * @description 인앱결제로 구매할 수 있는 상품 목록을 가져와요. 상품 목록 화면에 진입할 때 호출해요.
110
257
  * @returns {Promise<{ products: IapProductListItem[] } | undefined>} 상품 목록을 포함한 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.219.0, iOS 5.219.0)보다 낮으면 `undefined`를 반환해요.
111
258
  */
112
- getProductItemList: createAsyncBridge2("iapGetProductItemList")
259
+ getProductItemList: createAsyncBridge2("iapGetProductItemList"),
260
+ /**
261
+ * @public
262
+ * @category 인앱결제
263
+ * @name getPendingOrders
264
+ * @description 대기 중인 주문 목록을 가져와요. 이 함수를 사용하면 결제가 아직 완료되지 않은 주문 정보를 확인할 수 있어요.
265
+ * @returns {Promise<{orderIds: string[]}}>} 대기 중인 주문ID 배열을 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
266
+ *
267
+ * @example
268
+ * ### 대기 중인 주문 목록 가져오기
269
+ * ```typescript
270
+ * import { IAP } from '@apps-in-toss/web-framework';
271
+ *
272
+ * async function fetchOrders() {
273
+ * try {
274
+ * const pendingOrders = await IAP.getPendingOrders();
275
+ * return pendingOrders;
276
+ * } catch (error) {
277
+ * console.error(error);
278
+ * }
279
+ * }
280
+ * ```
281
+ */
282
+ getPendingOrders: createAsyncBridge2("getPendingOrders"),
283
+ /**
284
+ * @public
285
+ * @category 인앱결제
286
+ * @name getCompletedOrRefundedOrders
287
+ * @description 인앱결제로 구매하거나 환불한 주문 목록을 가져와요.
288
+ * @returns {Promise<CompletedOrRefundedOrdersResult>} 페이지네이션을 포함한 주문 목록 객체를 반환해요. 앱 버전이 최소 지원 버전(안드로이드 5.231.0, iOS 5.231.0)보다 낮으면 `undefined`를 반환해요.
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * import { IAP } from "@apps-in-toss/web-framework";
293
+ *
294
+ * async function fetchOrders() {
295
+ * try {
296
+ * const response = await IAP.getCompletedOrRefundedOrders();
297
+ * return response;
298
+ * } catch (error) {
299
+ * console.error(error);
300
+ * }
301
+ * }
302
+ * ```
303
+ */
304
+ getCompletedOrRefundedOrders: createAsyncBridge2("getCompletedOrRefundedOrders")
113
305
  };
114
306
 
115
307
  // src/getSafeAreaInsets.ts
116
- import { createConstantBridge } from "@apps-in-toss/bridge-core";
117
- var getSafeAreaBottom = createConstantBridge("getSafeAreaBottom");
118
- var getSafeAreaTop = createConstantBridge("getSafeAreaTop");
308
+ import { createConstantBridge as createConstantBridge2 } from "@apps-in-toss/bridge-core";
309
+ var getSafeAreaBottom = createConstantBridge2("getSafeAreaBottom");
310
+ var getSafeAreaTop = createConstantBridge2("getSafeAreaTop");
119
311
  function getSafeAreaInsets() {
120
312
  return { top: getSafeAreaTop(), bottom: getSafeAreaBottom() };
121
313
  }
122
314
 
123
315
  // src/googleAdMob.ts
124
- import { createConstantBridge as createConstantBridge2, createEventBridge } from "@apps-in-toss/bridge-core";
316
+ import { createConstantBridge as createConstantBridge3, createEventBridge as createEventBridge2 } from "@apps-in-toss/bridge-core";
125
317
  var GoogleAdMob = {
126
318
  /**
127
319
  * @public
@@ -172,9 +364,9 @@ var GoogleAdMob = {
172
364
  * ```
173
365
  */
174
366
  loadAdMobInterstitialAd: Object.assign(
175
- createEventBridge("loadAdMobInterstitialAd"),
367
+ createEventBridge2("loadAdMobInterstitialAd"),
176
368
  {
177
- isSupported: createConstantBridge2("loadAdMobInterstitialAd_isSupported")
369
+ isSupported: createConstantBridge3("loadAdMobInterstitialAd_isSupported")
178
370
  }
179
371
  ),
180
372
  /**
@@ -247,9 +439,9 @@ var GoogleAdMob = {
247
439
  * ```
248
440
  */
249
441
  showAdMobInterstitialAd: Object.assign(
250
- createEventBridge("showAdMobInterstitialAd"),
442
+ createEventBridge2("showAdMobInterstitialAd"),
251
443
  {
252
- isSupported: createConstantBridge2("showAdMobInterstitialAd_isSupported")
444
+ isSupported: createConstantBridge3("showAdMobInterstitialAd_isSupported")
253
445
  }
254
446
  ),
255
447
  /**
@@ -301,9 +493,9 @@ var GoogleAdMob = {
301
493
  * ```
302
494
  */
303
495
  loadAdMobRewardedAd: Object.assign(
304
- createEventBridge("loadAdMobRewardedAd"),
496
+ createEventBridge2("loadAdMobRewardedAd"),
305
497
  {
306
- isSupported: createConstantBridge2("loadAdMobRewardedAd_isSupported")
498
+ isSupported: createConstantBridge3("loadAdMobRewardedAd_isSupported")
307
499
  }
308
500
  ),
309
501
  /**
@@ -376,9 +568,9 @@ var GoogleAdMob = {
376
568
  * ```
377
569
  */
378
570
  showAdMobRewardedAd: Object.assign(
379
- createEventBridge("showAdMobRewardedAd"),
571
+ createEventBridge2("showAdMobRewardedAd"),
380
572
  {
381
- isSupported: createConstantBridge2("showAdMobRewardedAd_isSupported")
573
+ isSupported: createConstantBridge3("showAdMobRewardedAd_isSupported")
382
574
  }
383
575
  ),
384
576
  /**
@@ -435,8 +627,8 @@ var GoogleAdMob = {
435
627
  * }
436
628
  * ```
437
629
  */
438
- loadAppsInTossAdMob: Object.assign(createEventBridge("loadAppsInTossAdMob"), {
439
- isSupported: createConstantBridge2("loadAppsInTossAdMob_isSupported")
630
+ loadAppsInTossAdMob: Object.assign(createEventBridge2("loadAppsInTossAdMob"), {
631
+ isSupported: createConstantBridge3("loadAppsInTossAdMob_isSupported")
440
632
  }),
441
633
  /**
442
634
  * @public
@@ -513,19 +705,19 @@ var GoogleAdMob = {
513
705
  * }
514
706
  * ```
515
707
  */
516
- showAppsInTossAdMob: Object.assign(createEventBridge("showAppsInTossAdMob"), {
517
- isSupported: createConstantBridge2("showAppsInTossAdMob_isSupported")
708
+ showAppsInTossAdMob: Object.assign(createEventBridge2("showAppsInTossAdMob"), {
709
+ isSupported: createConstantBridge3("showAppsInTossAdMob_isSupported")
518
710
  })
519
711
  };
520
712
 
521
713
  // src/graniteEvent.ts
522
- import { createEventBridge as createEventBridge2 } from "@apps-in-toss/bridge-core";
714
+ import { createEventBridge as createEventBridge3 } from "@apps-in-toss/bridge-core";
523
715
  var graniteEvent = {
524
716
  addEventListener: (event, {
525
717
  onEvent,
526
718
  onError,
527
719
  options
528
- }) => createEventBridge2(event)({
720
+ }) => createEventBridge3(event)({
529
721
  onEvent,
530
722
  onError: onError ?? (() => {
531
723
  }),
@@ -533,105 +725,14 @@ var graniteEvent = {
533
725
  })
534
726
  };
535
727
 
536
- // src/isMinVersionSupported.ts
537
- import { createConstantBridge as createConstantBridge3 } from "@apps-in-toss/bridge-core";
538
-
539
- // src/utils/compareVersion.ts
540
- var SEMVER_REGEX = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\\-]+(?:\.[\da-z\\-]+)*))?(?:\+[\da-z\\-]+(?:\.[\da-z\\-]+)*)?)?)?$/i;
541
- var isWildcard = (val) => ["*", "x", "X"].includes(val);
542
- var tryParse = (val) => {
543
- const num = parseInt(val, 10);
544
- return isNaN(num) ? val : num;
545
- };
546
- var coerceTypes = (a, b) => {
547
- return typeof a === typeof b ? [a, b] : [String(a), String(b)];
548
- };
549
- var compareValues = (a, b) => {
550
- if (isWildcard(a) || isWildcard(b)) {
551
- return 0;
552
- }
553
- const [aVal, bVal] = coerceTypes(tryParse(a), tryParse(b));
554
- if (aVal > bVal) {
555
- return 1;
556
- }
557
- if (aVal < bVal) {
558
- return -1;
559
- }
560
- return 0;
561
- };
562
- var parseVersion = (version) => {
563
- if (typeof version !== "string") {
564
- throw new TypeError("Invalid argument: expected a string");
565
- }
566
- const match = version.match(SEMVER_REGEX);
567
- if (!match) {
568
- throw new Error(`Invalid semver: '${version}'`);
569
- }
570
- const [, major, minor, patch, build, preRelease] = match;
571
- return [major, minor, patch, build, preRelease];
572
- };
573
- var compareSegments = (a, b) => {
574
- const maxLength = Math.max(a.length, b.length);
575
- for (let i = 0; i < maxLength; i++) {
576
- const segA = a[i] ?? "0";
577
- const segB = b[i] ?? "0";
578
- const result = compareValues(segA, segB);
579
- if (result !== 0) {
580
- return result;
581
- }
582
- }
583
- return 0;
584
- };
585
- var compareVersions = (v1, v2) => {
586
- const seg1 = parseVersion(v1);
587
- const seg2 = parseVersion(v2);
588
- const preRelease1 = seg1.pop();
589
- const preRelease2 = seg2.pop();
590
- const mainCompare = compareSegments(seg1, seg2);
591
- if (mainCompare !== 0) {
592
- return mainCompare;
593
- }
594
- if (preRelease1 && preRelease2) {
595
- return compareSegments(preRelease1.split("."), preRelease2.split("."));
596
- }
597
- if (preRelease1) {
598
- return -1;
599
- }
600
- if (preRelease2) {
601
- return 1;
602
- }
603
- return 0;
604
- };
605
-
606
- // src/isMinVersionSupported.ts
607
- function isMinVersionSupported(minVersions) {
608
- const operationalEnvironment = createConstantBridge3("getOperationalEnvironment")();
609
- if (operationalEnvironment === "sandbox") {
610
- return true;
611
- }
612
- const currentVersion = createConstantBridge3("getTossAppVersion")();
613
- const isIOS = createConstantBridge3("getPlatformOS")() === "ios";
614
- const minVersion = isIOS ? minVersions.ios : minVersions.android;
615
- if (minVersion === void 0) {
616
- return false;
617
- }
618
- if (minVersion === "always") {
619
- return true;
620
- }
621
- if (minVersion === "never") {
622
- return false;
623
- }
624
- return compareVersions(currentVersion, minVersion) >= 0;
625
- }
626
-
627
728
  // src/appsInTossEvent.ts
628
- import { createEventBridge as createEventBridge3 } from "@apps-in-toss/bridge-core";
729
+ import { createEventBridge as createEventBridge4 } from "@apps-in-toss/bridge-core";
629
730
  var appsInTossEvent = {
630
731
  addEventListener: (event, {
631
732
  onEvent,
632
733
  onError,
633
734
  options
634
- }) => createEventBridge3(event)({
735
+ }) => createEventBridge4(event)({
635
736
  onEvent,
636
737
  onError: onError ?? (() => {
637
738
  }),
@@ -663,7 +764,7 @@ var getAppsInTossGlobals = () => {
663
764
  };
664
765
 
665
766
  // src/tdsEvent.ts
666
- import { createEventBridge as createEventBridge4 } from "@apps-in-toss/bridge-core";
767
+ import { createEventBridge as createEventBridge5 } from "@apps-in-toss/bridge-core";
667
768
  var tdsEvent = {
668
769
  /**
669
770
  * @public
@@ -693,7 +794,7 @@ var tdsEvent = {
693
794
  onEvent,
694
795
  onError,
695
796
  options
696
- }) => createEventBridge4(event)({
797
+ }) => createEventBridge5(event)({
697
798
  onEvent,
698
799
  onError: onError ?? (() => {
699
800
  }),
@@ -858,14 +959,14 @@ var getClipboardText = createPermissionFunction({
858
959
  });
859
960
 
860
961
  // src/permissions/startUpdateLocation.ts
861
- import { createAsyncBridge as createAsyncBridge11, createEventBridge as createEventBridge5 } from "@apps-in-toss/bridge-core";
962
+ import { createAsyncBridge as createAsyncBridge11, createEventBridge as createEventBridge6 } from "@apps-in-toss/bridge-core";
862
963
  import {
863
964
  StartUpdateLocationPermissionError
864
965
  } from "@apps-in-toss/types";
865
966
  var getPermission2 = createAsyncBridge11("getPermission");
866
967
  var openPermissionDialog2 = createAsyncBridge11("openPermissionDialog");
867
968
  var startUpdateLocation = (params) => {
868
- return createEventBridge5("updateLocationEvent")({
969
+ return createEventBridge6("updateLocationEvent")({
869
970
  ...params,
870
971
  onError: (error) => {
871
972
  const locationError = new StartUpdateLocationPermissionError();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@apps-in-toss/web-bridge",
3
3
  "type": "module",
4
- "version": "1.1.2",
4
+ "version": "1.2.0",
5
5
  "description": "Web Bridge for Apps In Toss",
6
6
  "scripts": {
7
7
  "prepack": "yarn build",
@@ -28,11 +28,11 @@
28
28
  "built"
29
29
  ],
30
30
  "dependencies": {
31
- "@apps-in-toss/types": "1.1.2"
31
+ "@apps-in-toss/types": "1.2.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@apps-in-toss/bridge-core": "1.1.2",
35
- "@apps-in-toss/framework": "1.1.2",
34
+ "@apps-in-toss/bridge-core": "1.2.0",
35
+ "@apps-in-toss/framework": "1.2.0",
36
36
  "@swc/core": "^1.12.7",
37
37
  "picocolors": "^1.1.1",
38
38
  "ts-morph": "^26.0.0",
@@ -46,5 +46,5 @@
46
46
  "publishConfig": {
47
47
  "access": "public"
48
48
  },
49
- "gitHead": "7b3c5e53a3ed0ffef8af2690a5ab92e8dfb0213b"
49
+ "gitHead": "f567520ba5a18de1fcddb329fb70d432cf10af85"
50
50
  }