@apps-in-toss/native-modules 1.11.2 → 1.12.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/dist/index.cjs CHANGED
@@ -663,7 +663,8 @@ async function getTossShareLinkV1(path) {
663
663
  }
664
664
 
665
665
  // src/AppsInTossModule/native-modules/iap.ts
666
- var import_es_toolkit2 = require("es-toolkit");
666
+ var noop2 = () => {
667
+ };
667
668
  function iapCreateOneTimePurchaseOrder(params) {
668
669
  const sku = params.sku ?? params.productId;
669
670
  return AppsInTossModule.iapCreateOneTimePurchaseOrder({ productId: sku });
@@ -697,7 +698,7 @@ function createOneTimePurchaseOrder(params) {
697
698
  ios: "5.219.0"
698
699
  });
699
700
  if (!isIAPSupported) {
700
- return import_es_toolkit2.noop;
701
+ return noop2;
701
702
  }
702
703
  const isProcessProductGrantSupported = isMinVersionSupported({
703
704
  android: "5.231.1",
@@ -716,7 +717,7 @@ function createOneTimePurchaseOrder(params) {
716
717
  }).catch((error) => {
717
718
  onError(error);
718
719
  });
719
- return import_es_toolkit2.noop;
720
+ return noop2;
720
721
  };
721
722
  return v1();
722
723
  }
@@ -778,8 +779,37 @@ async function completeProductGrant(params) {
778
779
  }
779
780
  return AppsInTossModule.completeProductGrant(params);
780
781
  }
782
+ function createSubscriptionPurchaseOrder(params) {
783
+ const isSupported = isMinVersionSupported({
784
+ android: "5.248.0",
785
+ ios: "5.249.0"
786
+ });
787
+ if (!isSupported) {
788
+ return noop2;
789
+ }
790
+ const { options, onEvent, onError } = params;
791
+ const { sku, offerId, processProductGrant: processProductGrant2 } = options;
792
+ const unregisterCallbacks = INTERNAL__appBridgeHandler.invokeAppBridgeMethod(
793
+ "requestSubscriptionPurchase",
794
+ { sku, offerId: offerId ?? null },
795
+ {
796
+ onPurchased: async (purchasedParams) => {
797
+ const isProductGranted = await processProductGrant2(purchasedParams);
798
+ await AppsInTossModule.processProductGrant({ orderId: purchasedParams.orderId, isProductGranted });
799
+ },
800
+ onSuccess: (result) => {
801
+ onEvent({ type: "success", data: result });
802
+ },
803
+ onError: (error) => {
804
+ onError(error);
805
+ }
806
+ }
807
+ );
808
+ return unregisterCallbacks;
809
+ }
781
810
  var IAP = {
782
811
  createOneTimePurchaseOrder,
812
+ createSubscriptionPurchaseOrder,
783
813
  getProductItemList,
784
814
  getPendingOrders,
785
815
  getCompletedOrRefundedOrders,
package/dist/index.d.cts CHANGED
@@ -976,7 +976,7 @@ interface IapCreateOneTimePurchaseOrderResult {
976
976
  fraction: number;
977
977
  miniAppIconUrl: string | null;
978
978
  }
979
- interface SuccessEvent {
979
+ interface OneTimePurchaseSuccessEvent {
980
980
  type: 'success';
981
981
  data: IapCreateOneTimePurchaseOrderResult;
982
982
  }
@@ -1002,12 +1002,12 @@ interface IapCreateOneTimePurchaseOrderOptions {
1002
1002
  orderId: string;
1003
1003
  }) => boolean | Promise<boolean>;
1004
1004
  };
1005
- onEvent: (event: SuccessEvent) => void | Promise<void>;
1005
+ onEvent: (event: OneTimePurchaseSuccessEvent) => void | Promise<void>;
1006
1006
  onError: (error: unknown) => void | Promise<void>;
1007
1007
  }
1008
1008
  interface IapRequestOneTimePurchaseOptions {
1009
1009
  options: Sku;
1010
- onEvent: (event: PurchasedEvent | SuccessEvent) => void | Promise<void>;
1010
+ onEvent: (event: PurchasedEvent | OneTimePurchaseSuccessEvent) => void | Promise<void>;
1011
1011
  onError: (error: unknown) => void | Promise<void>;
1012
1012
  }
1013
1013
  declare function iapCreateOneTimePurchaseOrder(params: Sku): Promise<IapCreateOneTimePurchaseOrderResult>;
@@ -1076,24 +1076,88 @@ declare function requestOneTimePurchase(params: IapRequestOneTimePurchaseOptions
1076
1076
  * ```
1077
1077
  */
1078
1078
  declare function createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions): () => void;
1079
+ interface BasicProductListItem {
1080
+ sku: string;
1081
+ displayAmount: string;
1082
+ displayName: string;
1083
+ iconUrl: string;
1084
+ description: string;
1085
+ }
1086
+ type Offer = {
1087
+ type: 'FREE_TRIAL';
1088
+ offerId: string;
1089
+ period: string;
1090
+ } | {
1091
+ type: 'NEW_SUBSCRIPTION';
1092
+ offerId: string;
1093
+ period: string;
1094
+ displayAmount: string;
1095
+ } | {
1096
+ type: 'RETURNING';
1097
+ offerId: string;
1098
+ period: string;
1099
+ displayAmount: string;
1100
+ };
1079
1101
  /**
1080
1102
  * @public
1081
1103
  * @category 인앱결제
1082
- * @name IapProductListItem
1083
- * @description 인앱결제로 구매할 수 있는 상품 하나의 정보를 담은 객체예요. 상품 목록을 화면에 표시할 때 사용해요.
1104
+ * @name ConsumableProductListItem
1105
+ * @description 소모품 상품 정보를 담은 객체예요.
1084
1106
  * @property {string} sku - 상품의 고유 ID예요.
1107
+ * @property {string} type - 상품의 유형이에요. `CONSUMABLE`을 나타내요.
1085
1108
  * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
1086
1109
  * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1087
1110
  * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
1088
1111
  * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
1089
1112
  */
1090
- interface IapProductListItem {
1091
- sku: string;
1092
- displayAmount: string;
1093
- displayName: string;
1094
- iconUrl: string;
1095
- description: string;
1113
+ interface ConsumableProductListItem extends BasicProductListItem {
1114
+ type: 'CONSUMABLE';
1115
+ }
1116
+ /**
1117
+ * @public
1118
+ * @category 인앱결제
1119
+ * @name NonConsumableProductListItem
1120
+ * @description 비소모품 상품 정보를 담은 객체예요.
1121
+ * @property {string} sku - 상품의 고유 ID예요.
1122
+ * @property {string} type - 상품의 유형이에요. `NON_CONSUMABLE`을 나타내요.
1123
+ * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
1124
+ * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1125
+ * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
1126
+ * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
1127
+ */
1128
+ interface NonConsumableProductListItem extends BasicProductListItem {
1129
+ type: 'NON_CONSUMABLE';
1096
1130
  }
1131
+ /**
1132
+ * @public
1133
+ * @category 인앱결제
1134
+ * @name SubscriptionProductListItem
1135
+ * @description 자동 갱신 구독 상품 정보를 담은 객체예요.
1136
+ * @property {string} sku - 상품의 고유 ID예요.
1137
+ * @property {string} type - 상품의 유형이에요. `SUBSCRIPTION`을 나타내요.
1138
+ * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
1139
+ * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1140
+ * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
1141
+ * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
1142
+ * @property {string} renewalCycle - 구독 갱신 주기이에요. `WEEKLY`, `MONTHLY`, `YEARLY` 중 하나를 나타내요.
1143
+ * @property {Offer[]} offers - 구독 혜택 옵션 목록이에요. 각 옵션은 하나의 구독 혜택을 나타내요.
1144
+ * @property {string} offers[].type - 구독 혜택 옵션 유형이에요. `FREE_TRIAL`, `NEW_SUBSCRIPTION`, `RETURNING` 중 하나를 나타내요.
1145
+ * @property {string} offers[].offerId - 구독 혜택 옵션의 고유 ID예요.
1146
+ * @property {string} offers[].period - 구독 혜택 옵션의 적용 기간이에요.
1147
+ * @property {string} offers[].displayAmount - 통화 단위가 포함된 구독 혜택 옵션의 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1148
+ */
1149
+ interface SubscriptionProductListItem extends BasicProductListItem {
1150
+ type: 'SUBSCRIPTION';
1151
+ renewalCycle: 'WEEKLY' | 'MONTHLY' | 'YEARLY';
1152
+ offers?: Offer[];
1153
+ }
1154
+ /**
1155
+ * @public
1156
+ * @category 인앱결제
1157
+ * @name IapProductListItem
1158
+ * @description 인앱결제로 구매할 수 있는 상품 하나의 정보를 담은 객체예요. 상품 목록을 화면에 표시할 때 사용해요.
1159
+ */
1160
+ type IapProductListItem = ConsumableProductListItem | NonConsumableProductListItem | SubscriptionProductListItem;
1097
1161
  /**
1098
1162
  * @public
1099
1163
  * @category 인앱결제
@@ -1272,6 +1336,90 @@ declare function completeProductGrant(params: {
1272
1336
  orderId: string;
1273
1337
  };
1274
1338
  }): Promise<boolean | undefined>;
1339
+ /**
1340
+ * @public
1341
+ * @category 인앱결제
1342
+ * @name CreateSubscriptionPurchaseOrderOptions
1343
+ * @description 구독 인앱결제를 생성할 때 필요한 옵션이에요.
1344
+ * @property {object} options - 결제할 구독 상품의 정보예요.
1345
+ * @property {string} options.sku - 주문할 구독 상품의 고유 ID예요.
1346
+ * @property {string | null} [options.offerId] - 적용할 offer ID예요. 없으면 기본 가격이 적용돼요.
1347
+ * @property {(params: { orderId: string, subscriptionId?: string }) => boolean | Promise<boolean>} options.processProductGrant - 주문이 만들어진 뒤 실제로 상품을 지급할 때 호출해요.
1348
+ * @property {(event: SubscriptionSuccessEvent) => void | Promise<void>} onEvent - 결제가 성공했을 때 호출해요.
1349
+ * @property {(error: unknown) => void | Promise<void>} onError - 결제 과정에서 에러가 발생했을 때 호출해요.
1350
+ */
1351
+ interface CreateSubscriptionPurchaseOrderOptions {
1352
+ options: {
1353
+ sku: string;
1354
+ offerId?: string | null;
1355
+ processProductGrant: (params: {
1356
+ orderId: string;
1357
+ subscriptionId?: string;
1358
+ }) => boolean | Promise<boolean>;
1359
+ };
1360
+ onEvent: (event: SubscriptionSuccessEvent) => void | Promise<void>;
1361
+ onError: (error: unknown) => void | Promise<void>;
1362
+ }
1363
+ /**
1364
+ * @public
1365
+ * @category 인앱결제
1366
+ * @name IapCreateSubscriptionPurchaseOrderResult
1367
+ * @description 구독 인앱결제가 완료되면 결제 세부 정보와 상품 정보를 담아 반환해요. `IapCreateOneTimePurchaseOrderResult`와 동일한 구조예요.
1368
+ */
1369
+ type IapCreateSubscriptionPurchaseOrderResult = IapCreateOneTimePurchaseOrderResult;
1370
+ interface SubscriptionSuccessEvent {
1371
+ type: 'success';
1372
+ data: IapCreateSubscriptionPurchaseOrderResult;
1373
+ }
1374
+ /**
1375
+ * @public
1376
+ * @category 인앱결제
1377
+ * @name createSubscriptionPurchaseOrder
1378
+ * @description
1379
+ * 구독 인앱결제 주문서 페이지로 이동해요. 사용자가 구독 상품 구매 버튼을 누르는 상황 등에 사용할 수 있어요.
1380
+ * @param {CreateSubscriptionPurchaseOrderOptions} params - 구독 인앱결제를 생성할 때 필요한 정보예요.
1381
+ * @returns {() => void} 앱브릿지 cleanup 함수를 반환해요. 인앱결제 기능이 끝나면 반드시 이 함수를 호출해서 리소스를 해제해야 해요.
1382
+ *
1383
+ * @example
1384
+ * ### 구독 인앱결제 주문서 페이지로 이동하기
1385
+ *
1386
+ * ```tsx
1387
+ * import { IAP } from "@apps-in-toss/web-framework";
1388
+ * import { Button } from "@toss/tds-react-native";
1389
+ * import { useCallback } from "react";
1390
+ *
1391
+ * interface Props {
1392
+ * sku: string;
1393
+ * offerId?: string;
1394
+ * }
1395
+ *
1396
+ * function SubscriptionPurchaseButton({ sku, offerId }: Props) {
1397
+ * const handleClick = useCallback(async () => {
1398
+ * const cleanup = IAP.createSubscriptionPurchaseOrder({
1399
+ * options: {
1400
+ * sku,
1401
+ * offerId,
1402
+ * processProductGrant: ({ orderId, subscriptionId }) => {
1403
+ * // 상품 지급 로직 작성
1404
+ * return true; // 상품 지급 여부
1405
+ * },
1406
+ * },
1407
+ * onEvent: (event) => {
1408
+ * console.log(event);
1409
+ * },
1410
+ * onError: (error) => {
1411
+ * console.error(error);
1412
+ * },
1413
+ * });
1414
+ *
1415
+ * return cleanup;
1416
+ * }, [sku, offerId]);
1417
+ *
1418
+ * return <Button onClick={handleClick}>구독하기</Button>;
1419
+ * }
1420
+ * ```
1421
+ */
1422
+ declare function createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void;
1275
1423
  /**
1276
1424
  * @public
1277
1425
  * @category 인앱결제
@@ -1285,6 +1433,7 @@ declare function completeProductGrant(params: {
1285
1433
  */
1286
1434
  declare const IAP: {
1287
1435
  createOneTimePurchaseOrder: typeof createOneTimePurchaseOrder;
1436
+ createSubscriptionPurchaseOrder: typeof createSubscriptionPurchaseOrder;
1288
1437
  getProductItemList: typeof getProductItemList;
1289
1438
  getPendingOrders: typeof getPendingOrders;
1290
1439
  getCompletedOrRefundedOrders: typeof getCompletedOrRefundedOrders;
@@ -1469,6 +1618,15 @@ interface Spec extends TurboModule {
1469
1618
  orderId: string;
1470
1619
  }) => void;
1471
1620
  }) => () => void;
1621
+ requestSubscriptionPurchase: (params: {
1622
+ sku: string;
1623
+ offerId: string | null;
1624
+ }, fallbacks: {
1625
+ onPurchased: (params: {
1626
+ orderId: string;
1627
+ subscriptionId?: string;
1628
+ }) => void;
1629
+ }) => () => void;
1472
1630
  processProductGrant: (params: {
1473
1631
  orderId: string;
1474
1632
  isProductGranted: boolean;
@@ -3004,4 +3162,4 @@ declare const INTERNAL__module: {
3004
3162
  tossCoreEventLog: typeof tossCoreEventLog;
3005
3163
  };
3006
3164
 
3007
- export { AppsInTossModule, type AppsInTossSignTossCertParams, BedrockCoreModule, BedrockModule, type CheckoutPaymentOptions, type CheckoutPaymentResult, type CompletedOrRefundedOrdersResult, type ContactsViralParams, type EventLogParams, type GameCenterGameProfileResponse, type GetUserKeyForGameErrorResponse, type GetUserKeyForGameResponse, type GetUserKeyForGameSuccessResponse, GoogleAdMob, type GrantPromotionRewardForGameErrorResponse, type GrantPromotionRewardForGameErrorResult, type GrantPromotionRewardForGameResponse, type GrantPromotionRewardForGameSuccessResponse, type HapticFeedbackType, IAP, AppsInTossModuleInstance as INTERNAL__AppsInTossModule, INTERNAL__appBridgeHandler, INTERNAL__module, type IapCreateOneTimePurchaseOrderOptions, type IapCreateOneTimePurchaseOrderResult, type IapProductListItem, type NetworkStatus, type Primitive, type SaveBase64DataParams, Storage, type SubmitGameCenterLeaderBoardScoreResponse, TossPay, type UpdateLocationEventEmitter, appLogin, appsInTossEvent, appsInTossSignTossCert, closeView, contactsViral, eventLog, fetchAlbumPhotos, fetchContacts, generateHapticFeedback, getClipboardText, getCurrentLocation, getDeviceId, getGameCenterGameProfile, getIsTossLoginIntegratedService, getLocale, getNetworkStatus, getOperationalEnvironment, getPlatformOS, getSchemeUri, getServerTime, getTossAppVersion, getTossShareLink, getUserKeyForGame, grantPromotionRewardForGame, iapCreateOneTimePurchaseOrder, isMinVersionSupported, onVisibilityChangedByTransparentServiceWeb, openCamera, openGameCenterLeaderboard, openURL, processProductGrant, requestOneTimePurchase, saveBase64Data, setClipboardText, setDeviceOrientation, setIosSwipeGestureEnabled, setScreenAwakeMode, setSecureScreen, share, startUpdateLocation, submitGameCenterLeaderBoardScore };
3165
+ export { AppsInTossModule, type AppsInTossSignTossCertParams, BedrockCoreModule, BedrockModule, type CheckoutPaymentOptions, type CheckoutPaymentResult, type CompletedOrRefundedOrdersResult, type ConsumableProductListItem, type ContactsViralParams, type CreateSubscriptionPurchaseOrderOptions, type EventLogParams, type GameCenterGameProfileResponse, type GetUserKeyForGameErrorResponse, type GetUserKeyForGameResponse, type GetUserKeyForGameSuccessResponse, GoogleAdMob, type GrantPromotionRewardForGameErrorResponse, type GrantPromotionRewardForGameErrorResult, type GrantPromotionRewardForGameResponse, type GrantPromotionRewardForGameSuccessResponse, type HapticFeedbackType, IAP, AppsInTossModuleInstance as INTERNAL__AppsInTossModule, INTERNAL__appBridgeHandler, INTERNAL__module, type IapCreateOneTimePurchaseOrderOptions, type IapCreateOneTimePurchaseOrderResult, type IapCreateSubscriptionPurchaseOrderResult, type IapProductListItem, type NetworkStatus, type NonConsumableProductListItem, type Primitive, type SaveBase64DataParams, Storage, type SubmitGameCenterLeaderBoardScoreResponse, type SubscriptionProductListItem, TossPay, type UpdateLocationEventEmitter, appLogin, appsInTossEvent, appsInTossSignTossCert, closeView, contactsViral, eventLog, fetchAlbumPhotos, fetchContacts, generateHapticFeedback, getClipboardText, getCurrentLocation, getDeviceId, getGameCenterGameProfile, getIsTossLoginIntegratedService, getLocale, getNetworkStatus, getOperationalEnvironment, getPlatformOS, getSchemeUri, getServerTime, getTossAppVersion, getTossShareLink, getUserKeyForGame, grantPromotionRewardForGame, iapCreateOneTimePurchaseOrder, isMinVersionSupported, onVisibilityChangedByTransparentServiceWeb, openCamera, openGameCenterLeaderboard, openURL, processProductGrant, requestOneTimePurchase, saveBase64Data, setClipboardText, setDeviceOrientation, setIosSwipeGestureEnabled, setScreenAwakeMode, setSecureScreen, share, startUpdateLocation, submitGameCenterLeaderBoardScore };
package/dist/index.d.ts CHANGED
@@ -976,7 +976,7 @@ interface IapCreateOneTimePurchaseOrderResult {
976
976
  fraction: number;
977
977
  miniAppIconUrl: string | null;
978
978
  }
979
- interface SuccessEvent {
979
+ interface OneTimePurchaseSuccessEvent {
980
980
  type: 'success';
981
981
  data: IapCreateOneTimePurchaseOrderResult;
982
982
  }
@@ -1002,12 +1002,12 @@ interface IapCreateOneTimePurchaseOrderOptions {
1002
1002
  orderId: string;
1003
1003
  }) => boolean | Promise<boolean>;
1004
1004
  };
1005
- onEvent: (event: SuccessEvent) => void | Promise<void>;
1005
+ onEvent: (event: OneTimePurchaseSuccessEvent) => void | Promise<void>;
1006
1006
  onError: (error: unknown) => void | Promise<void>;
1007
1007
  }
1008
1008
  interface IapRequestOneTimePurchaseOptions {
1009
1009
  options: Sku;
1010
- onEvent: (event: PurchasedEvent | SuccessEvent) => void | Promise<void>;
1010
+ onEvent: (event: PurchasedEvent | OneTimePurchaseSuccessEvent) => void | Promise<void>;
1011
1011
  onError: (error: unknown) => void | Promise<void>;
1012
1012
  }
1013
1013
  declare function iapCreateOneTimePurchaseOrder(params: Sku): Promise<IapCreateOneTimePurchaseOrderResult>;
@@ -1076,24 +1076,88 @@ declare function requestOneTimePurchase(params: IapRequestOneTimePurchaseOptions
1076
1076
  * ```
1077
1077
  */
1078
1078
  declare function createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions): () => void;
1079
+ interface BasicProductListItem {
1080
+ sku: string;
1081
+ displayAmount: string;
1082
+ displayName: string;
1083
+ iconUrl: string;
1084
+ description: string;
1085
+ }
1086
+ type Offer = {
1087
+ type: 'FREE_TRIAL';
1088
+ offerId: string;
1089
+ period: string;
1090
+ } | {
1091
+ type: 'NEW_SUBSCRIPTION';
1092
+ offerId: string;
1093
+ period: string;
1094
+ displayAmount: string;
1095
+ } | {
1096
+ type: 'RETURNING';
1097
+ offerId: string;
1098
+ period: string;
1099
+ displayAmount: string;
1100
+ };
1079
1101
  /**
1080
1102
  * @public
1081
1103
  * @category 인앱결제
1082
- * @name IapProductListItem
1083
- * @description 인앱결제로 구매할 수 있는 상품 하나의 정보를 담은 객체예요. 상품 목록을 화면에 표시할 때 사용해요.
1104
+ * @name ConsumableProductListItem
1105
+ * @description 소모품 상품 정보를 담은 객체예요.
1084
1106
  * @property {string} sku - 상품의 고유 ID예요.
1107
+ * @property {string} type - 상품의 유형이에요. `CONSUMABLE`을 나타내요.
1085
1108
  * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
1086
1109
  * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1087
1110
  * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
1088
1111
  * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
1089
1112
  */
1090
- interface IapProductListItem {
1091
- sku: string;
1092
- displayAmount: string;
1093
- displayName: string;
1094
- iconUrl: string;
1095
- description: string;
1113
+ interface ConsumableProductListItem extends BasicProductListItem {
1114
+ type: 'CONSUMABLE';
1115
+ }
1116
+ /**
1117
+ * @public
1118
+ * @category 인앱결제
1119
+ * @name NonConsumableProductListItem
1120
+ * @description 비소모품 상품 정보를 담은 객체예요.
1121
+ * @property {string} sku - 상품의 고유 ID예요.
1122
+ * @property {string} type - 상품의 유형이에요. `NON_CONSUMABLE`을 나타내요.
1123
+ * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
1124
+ * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1125
+ * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
1126
+ * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
1127
+ */
1128
+ interface NonConsumableProductListItem extends BasicProductListItem {
1129
+ type: 'NON_CONSUMABLE';
1096
1130
  }
1131
+ /**
1132
+ * @public
1133
+ * @category 인앱결제
1134
+ * @name SubscriptionProductListItem
1135
+ * @description 자동 갱신 구독 상품 정보를 담은 객체예요.
1136
+ * @property {string} sku - 상품의 고유 ID예요.
1137
+ * @property {string} type - 상품의 유형이에요. `SUBSCRIPTION`을 나타내요.
1138
+ * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
1139
+ * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1140
+ * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
1141
+ * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
1142
+ * @property {string} renewalCycle - 구독 갱신 주기이에요. `WEEKLY`, `MONTHLY`, `YEARLY` 중 하나를 나타내요.
1143
+ * @property {Offer[]} offers - 구독 혜택 옵션 목록이에요. 각 옵션은 하나의 구독 혜택을 나타내요.
1144
+ * @property {string} offers[].type - 구독 혜택 옵션 유형이에요. `FREE_TRIAL`, `NEW_SUBSCRIPTION`, `RETURNING` 중 하나를 나타내요.
1145
+ * @property {string} offers[].offerId - 구독 혜택 옵션의 고유 ID예요.
1146
+ * @property {string} offers[].period - 구독 혜택 옵션의 적용 기간이에요.
1147
+ * @property {string} offers[].displayAmount - 통화 단위가 포함된 구독 혜택 옵션의 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
1148
+ */
1149
+ interface SubscriptionProductListItem extends BasicProductListItem {
1150
+ type: 'SUBSCRIPTION';
1151
+ renewalCycle: 'WEEKLY' | 'MONTHLY' | 'YEARLY';
1152
+ offers?: Offer[];
1153
+ }
1154
+ /**
1155
+ * @public
1156
+ * @category 인앱결제
1157
+ * @name IapProductListItem
1158
+ * @description 인앱결제로 구매할 수 있는 상품 하나의 정보를 담은 객체예요. 상품 목록을 화면에 표시할 때 사용해요.
1159
+ */
1160
+ type IapProductListItem = ConsumableProductListItem | NonConsumableProductListItem | SubscriptionProductListItem;
1097
1161
  /**
1098
1162
  * @public
1099
1163
  * @category 인앱결제
@@ -1272,6 +1336,90 @@ declare function completeProductGrant(params: {
1272
1336
  orderId: string;
1273
1337
  };
1274
1338
  }): Promise<boolean | undefined>;
1339
+ /**
1340
+ * @public
1341
+ * @category 인앱결제
1342
+ * @name CreateSubscriptionPurchaseOrderOptions
1343
+ * @description 구독 인앱결제를 생성할 때 필요한 옵션이에요.
1344
+ * @property {object} options - 결제할 구독 상품의 정보예요.
1345
+ * @property {string} options.sku - 주문할 구독 상품의 고유 ID예요.
1346
+ * @property {string | null} [options.offerId] - 적용할 offer ID예요. 없으면 기본 가격이 적용돼요.
1347
+ * @property {(params: { orderId: string, subscriptionId?: string }) => boolean | Promise<boolean>} options.processProductGrant - 주문이 만들어진 뒤 실제로 상품을 지급할 때 호출해요.
1348
+ * @property {(event: SubscriptionSuccessEvent) => void | Promise<void>} onEvent - 결제가 성공했을 때 호출해요.
1349
+ * @property {(error: unknown) => void | Promise<void>} onError - 결제 과정에서 에러가 발생했을 때 호출해요.
1350
+ */
1351
+ interface CreateSubscriptionPurchaseOrderOptions {
1352
+ options: {
1353
+ sku: string;
1354
+ offerId?: string | null;
1355
+ processProductGrant: (params: {
1356
+ orderId: string;
1357
+ subscriptionId?: string;
1358
+ }) => boolean | Promise<boolean>;
1359
+ };
1360
+ onEvent: (event: SubscriptionSuccessEvent) => void | Promise<void>;
1361
+ onError: (error: unknown) => void | Promise<void>;
1362
+ }
1363
+ /**
1364
+ * @public
1365
+ * @category 인앱결제
1366
+ * @name IapCreateSubscriptionPurchaseOrderResult
1367
+ * @description 구독 인앱결제가 완료되면 결제 세부 정보와 상품 정보를 담아 반환해요. `IapCreateOneTimePurchaseOrderResult`와 동일한 구조예요.
1368
+ */
1369
+ type IapCreateSubscriptionPurchaseOrderResult = IapCreateOneTimePurchaseOrderResult;
1370
+ interface SubscriptionSuccessEvent {
1371
+ type: 'success';
1372
+ data: IapCreateSubscriptionPurchaseOrderResult;
1373
+ }
1374
+ /**
1375
+ * @public
1376
+ * @category 인앱결제
1377
+ * @name createSubscriptionPurchaseOrder
1378
+ * @description
1379
+ * 구독 인앱결제 주문서 페이지로 이동해요. 사용자가 구독 상품 구매 버튼을 누르는 상황 등에 사용할 수 있어요.
1380
+ * @param {CreateSubscriptionPurchaseOrderOptions} params - 구독 인앱결제를 생성할 때 필요한 정보예요.
1381
+ * @returns {() => void} 앱브릿지 cleanup 함수를 반환해요. 인앱결제 기능이 끝나면 반드시 이 함수를 호출해서 리소스를 해제해야 해요.
1382
+ *
1383
+ * @example
1384
+ * ### 구독 인앱결제 주문서 페이지로 이동하기
1385
+ *
1386
+ * ```tsx
1387
+ * import { IAP } from "@apps-in-toss/web-framework";
1388
+ * import { Button } from "@toss/tds-react-native";
1389
+ * import { useCallback } from "react";
1390
+ *
1391
+ * interface Props {
1392
+ * sku: string;
1393
+ * offerId?: string;
1394
+ * }
1395
+ *
1396
+ * function SubscriptionPurchaseButton({ sku, offerId }: Props) {
1397
+ * const handleClick = useCallback(async () => {
1398
+ * const cleanup = IAP.createSubscriptionPurchaseOrder({
1399
+ * options: {
1400
+ * sku,
1401
+ * offerId,
1402
+ * processProductGrant: ({ orderId, subscriptionId }) => {
1403
+ * // 상품 지급 로직 작성
1404
+ * return true; // 상품 지급 여부
1405
+ * },
1406
+ * },
1407
+ * onEvent: (event) => {
1408
+ * console.log(event);
1409
+ * },
1410
+ * onError: (error) => {
1411
+ * console.error(error);
1412
+ * },
1413
+ * });
1414
+ *
1415
+ * return cleanup;
1416
+ * }, [sku, offerId]);
1417
+ *
1418
+ * return <Button onClick={handleClick}>구독하기</Button>;
1419
+ * }
1420
+ * ```
1421
+ */
1422
+ declare function createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void;
1275
1423
  /**
1276
1424
  * @public
1277
1425
  * @category 인앱결제
@@ -1285,6 +1433,7 @@ declare function completeProductGrant(params: {
1285
1433
  */
1286
1434
  declare const IAP: {
1287
1435
  createOneTimePurchaseOrder: typeof createOneTimePurchaseOrder;
1436
+ createSubscriptionPurchaseOrder: typeof createSubscriptionPurchaseOrder;
1288
1437
  getProductItemList: typeof getProductItemList;
1289
1438
  getPendingOrders: typeof getPendingOrders;
1290
1439
  getCompletedOrRefundedOrders: typeof getCompletedOrRefundedOrders;
@@ -1469,6 +1618,15 @@ interface Spec extends TurboModule {
1469
1618
  orderId: string;
1470
1619
  }) => void;
1471
1620
  }) => () => void;
1621
+ requestSubscriptionPurchase: (params: {
1622
+ sku: string;
1623
+ offerId: string | null;
1624
+ }, fallbacks: {
1625
+ onPurchased: (params: {
1626
+ orderId: string;
1627
+ subscriptionId?: string;
1628
+ }) => void;
1629
+ }) => () => void;
1472
1630
  processProductGrant: (params: {
1473
1631
  orderId: string;
1474
1632
  isProductGranted: boolean;
@@ -3004,4 +3162,4 @@ declare const INTERNAL__module: {
3004
3162
  tossCoreEventLog: typeof tossCoreEventLog;
3005
3163
  };
3006
3164
 
3007
- export { AppsInTossModule, type AppsInTossSignTossCertParams, BedrockCoreModule, BedrockModule, type CheckoutPaymentOptions, type CheckoutPaymentResult, type CompletedOrRefundedOrdersResult, type ContactsViralParams, type EventLogParams, type GameCenterGameProfileResponse, type GetUserKeyForGameErrorResponse, type GetUserKeyForGameResponse, type GetUserKeyForGameSuccessResponse, GoogleAdMob, type GrantPromotionRewardForGameErrorResponse, type GrantPromotionRewardForGameErrorResult, type GrantPromotionRewardForGameResponse, type GrantPromotionRewardForGameSuccessResponse, type HapticFeedbackType, IAP, AppsInTossModuleInstance as INTERNAL__AppsInTossModule, INTERNAL__appBridgeHandler, INTERNAL__module, type IapCreateOneTimePurchaseOrderOptions, type IapCreateOneTimePurchaseOrderResult, type IapProductListItem, type NetworkStatus, type Primitive, type SaveBase64DataParams, Storage, type SubmitGameCenterLeaderBoardScoreResponse, TossPay, type UpdateLocationEventEmitter, appLogin, appsInTossEvent, appsInTossSignTossCert, closeView, contactsViral, eventLog, fetchAlbumPhotos, fetchContacts, generateHapticFeedback, getClipboardText, getCurrentLocation, getDeviceId, getGameCenterGameProfile, getIsTossLoginIntegratedService, getLocale, getNetworkStatus, getOperationalEnvironment, getPlatformOS, getSchemeUri, getServerTime, getTossAppVersion, getTossShareLink, getUserKeyForGame, grantPromotionRewardForGame, iapCreateOneTimePurchaseOrder, isMinVersionSupported, onVisibilityChangedByTransparentServiceWeb, openCamera, openGameCenterLeaderboard, openURL, processProductGrant, requestOneTimePurchase, saveBase64Data, setClipboardText, setDeviceOrientation, setIosSwipeGestureEnabled, setScreenAwakeMode, setSecureScreen, share, startUpdateLocation, submitGameCenterLeaderBoardScore };
3165
+ export { AppsInTossModule, type AppsInTossSignTossCertParams, BedrockCoreModule, BedrockModule, type CheckoutPaymentOptions, type CheckoutPaymentResult, type CompletedOrRefundedOrdersResult, type ConsumableProductListItem, type ContactsViralParams, type CreateSubscriptionPurchaseOrderOptions, type EventLogParams, type GameCenterGameProfileResponse, type GetUserKeyForGameErrorResponse, type GetUserKeyForGameResponse, type GetUserKeyForGameSuccessResponse, GoogleAdMob, type GrantPromotionRewardForGameErrorResponse, type GrantPromotionRewardForGameErrorResult, type GrantPromotionRewardForGameResponse, type GrantPromotionRewardForGameSuccessResponse, type HapticFeedbackType, IAP, AppsInTossModuleInstance as INTERNAL__AppsInTossModule, INTERNAL__appBridgeHandler, INTERNAL__module, type IapCreateOneTimePurchaseOrderOptions, type IapCreateOneTimePurchaseOrderResult, type IapCreateSubscriptionPurchaseOrderResult, type IapProductListItem, type NetworkStatus, type NonConsumableProductListItem, type Primitive, type SaveBase64DataParams, Storage, type SubmitGameCenterLeaderBoardScoreResponse, type SubscriptionProductListItem, TossPay, type UpdateLocationEventEmitter, appLogin, appsInTossEvent, appsInTossSignTossCert, closeView, contactsViral, eventLog, fetchAlbumPhotos, fetchContacts, generateHapticFeedback, getClipboardText, getCurrentLocation, getDeviceId, getGameCenterGameProfile, getIsTossLoginIntegratedService, getLocale, getNetworkStatus, getOperationalEnvironment, getPlatformOS, getSchemeUri, getServerTime, getTossAppVersion, getTossShareLink, getUserKeyForGame, grantPromotionRewardForGame, iapCreateOneTimePurchaseOrder, isMinVersionSupported, onVisibilityChangedByTransparentServiceWeb, openCamera, openGameCenterLeaderboard, openURL, processProductGrant, requestOneTimePurchase, saveBase64Data, setClipboardText, setDeviceOrientation, setIosSwipeGestureEnabled, setScreenAwakeMode, setSecureScreen, share, startUpdateLocation, submitGameCenterLeaderBoardScore };
package/dist/index.js CHANGED
@@ -586,7 +586,8 @@ async function getTossShareLinkV1(path) {
586
586
  }
587
587
 
588
588
  // src/AppsInTossModule/native-modules/iap.ts
589
- import { noop as noop2 } from "es-toolkit";
589
+ var noop2 = () => {
590
+ };
590
591
  function iapCreateOneTimePurchaseOrder(params) {
591
592
  const sku = params.sku ?? params.productId;
592
593
  return AppsInTossModule.iapCreateOneTimePurchaseOrder({ productId: sku });
@@ -701,8 +702,37 @@ async function completeProductGrant(params) {
701
702
  }
702
703
  return AppsInTossModule.completeProductGrant(params);
703
704
  }
705
+ function createSubscriptionPurchaseOrder(params) {
706
+ const isSupported = isMinVersionSupported({
707
+ android: "5.248.0",
708
+ ios: "5.249.0"
709
+ });
710
+ if (!isSupported) {
711
+ return noop2;
712
+ }
713
+ const { options, onEvent, onError } = params;
714
+ const { sku, offerId, processProductGrant: processProductGrant2 } = options;
715
+ const unregisterCallbacks = INTERNAL__appBridgeHandler.invokeAppBridgeMethod(
716
+ "requestSubscriptionPurchase",
717
+ { sku, offerId: offerId ?? null },
718
+ {
719
+ onPurchased: async (purchasedParams) => {
720
+ const isProductGranted = await processProductGrant2(purchasedParams);
721
+ await AppsInTossModule.processProductGrant({ orderId: purchasedParams.orderId, isProductGranted });
722
+ },
723
+ onSuccess: (result) => {
724
+ onEvent({ type: "success", data: result });
725
+ },
726
+ onError: (error) => {
727
+ onError(error);
728
+ }
729
+ }
730
+ );
731
+ return unregisterCallbacks;
732
+ }
704
733
  var IAP = {
705
734
  createOneTimePurchaseOrder,
735
+ createSubscriptionPurchaseOrder,
706
736
  getProductItemList,
707
737
  getPendingOrders,
708
738
  getCompletedOrRefundedOrders,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@apps-in-toss/native-modules",
3
3
  "type": "module",
4
- "version": "1.11.2",
4
+ "version": "1.12.0",
5
5
  "description": "Native Modules for Apps In Toss",
6
6
  "scripts": {
7
7
  "typecheck": "tsc --noEmit",
@@ -42,7 +42,7 @@
42
42
  "vitest": "^3.2.4"
43
43
  },
44
44
  "dependencies": {
45
- "@apps-in-toss/types": "^1.11.2",
45
+ "@apps-in-toss/types": "^1.12.0",
46
46
  "es-toolkit": "^1.39.3"
47
47
  },
48
48
  "peerDependencies": {
@@ -74,6 +74,10 @@ interface Spec extends __TurboModule {
74
74
  params: { sku: string },
75
75
  fallbacks: { onPurchased: (params: { orderId: string }) => void }
76
76
  ) => () => void;
77
+ requestSubscriptionPurchase: (
78
+ params: { sku: string; offerId: string | null },
79
+ fallbacks: { onPurchased: (params: { orderId: string; subscriptionId?: string }) => void }
80
+ ) => () => void;
77
81
  processProductGrant: (params: { orderId: string; isProductGranted: boolean }) => Promise<void>;
78
82
  getPendingOrders: (
79
83
  params: CompatiblePlaceholderArgument
@@ -1,8 +1,9 @@
1
- import { noop } from 'es-toolkit';
2
1
  import { AppsInTossModule } from './AppsInTossModule';
3
2
  import { isMinVersionSupported } from './isMinVersionSupported';
4
3
  import { INTERNAL__appBridgeHandler } from '../native-event-emitter/internal/appBridge';
5
4
 
5
+ const noop = () => {};
6
+
6
7
  type Sku =
7
8
  | {
8
9
  /**
@@ -38,8 +39,7 @@ export interface IapCreateOneTimePurchaseOrderResult {
38
39
  fraction: number;
39
40
  miniAppIconUrl: string | null;
40
41
  }
41
-
42
- interface SuccessEvent {
42
+ interface OneTimePurchaseSuccessEvent {
43
43
  type: 'success';
44
44
  data: IapCreateOneTimePurchaseOrderResult;
45
45
  }
@@ -61,13 +61,13 @@ interface PurchasedEvent {
61
61
  */
62
62
  export interface IapCreateOneTimePurchaseOrderOptions {
63
63
  options: Sku & { processProductGrant: (params: { orderId: string }) => boolean | Promise<boolean> };
64
- onEvent: (event: SuccessEvent) => void | Promise<void>;
64
+ onEvent: (event: OneTimePurchaseSuccessEvent) => void | Promise<void>;
65
65
  onError: (error: unknown) => void | Promise<void>;
66
66
  }
67
67
 
68
68
  interface IapRequestOneTimePurchaseOptions {
69
69
  options: Sku;
70
- onEvent: (event: PurchasedEvent | SuccessEvent) => void | Promise<void>;
70
+ onEvent: (event: PurchasedEvent | OneTimePurchaseSuccessEvent) => void | Promise<void>;
71
71
  onError: (error: unknown) => void | Promise<void>;
72
72
  }
73
73
 
@@ -222,25 +222,97 @@ function createOneTimePurchaseOrder(params: IapCreateOneTimePurchaseOrderOptions
222
222
  return unregisterCallbacks;
223
223
  }
224
224
 
225
+ interface BasicProductListItem {
226
+ sku: string;
227
+ displayAmount: string;
228
+ displayName: string;
229
+ iconUrl: string;
230
+ description: string;
231
+ }
232
+
233
+ type Offer =
234
+ | {
235
+ type: 'FREE_TRIAL';
236
+ offerId: string;
237
+ period: string;
238
+ }
239
+ | {
240
+ type: 'NEW_SUBSCRIPTION';
241
+ offerId: string;
242
+ period: string;
243
+ displayAmount: string;
244
+ }
245
+ | {
246
+ type: 'RETURNING';
247
+ offerId: string;
248
+ period: string;
249
+ displayAmount: string;
250
+ };
251
+
225
252
  /**
226
253
  * @public
227
254
  * @category 인앱결제
228
- * @name IapProductListItem
229
- * @description 인앱결제로 구매할 수 있는 상품 하나의 정보를 담은 객체예요. 상품 목록을 화면에 표시할 때 사용해요.
255
+ * @name ConsumableProductListItem
256
+ * @description 소모품 상품 정보를 담은 객체예요.
230
257
  * @property {string} sku - 상품의 고유 ID예요.
258
+ * @property {string} type - 상품의 유형이에요. `CONSUMABLE`을 나타내요.
231
259
  * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
232
260
  * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
233
261
  * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
234
262
  * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
235
263
  */
236
- export interface IapProductListItem {
237
- sku: string;
238
- displayAmount: string;
239
- displayName: string;
240
- iconUrl: string;
241
- description: string;
264
+ export interface ConsumableProductListItem extends BasicProductListItem {
265
+ type: 'CONSUMABLE';
266
+ }
267
+
268
+ /**
269
+ * @public
270
+ * @category 인앱결제
271
+ * @name NonConsumableProductListItem
272
+ * @description 비소모품 상품 정보를 담은 객체예요.
273
+ * @property {string} sku - 상품의 고유 ID예요.
274
+ * @property {string} type - 상품의 유형이에요. `NON_CONSUMABLE`을 나타내요.
275
+ * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
276
+ * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
277
+ * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
278
+ * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
279
+ */
280
+ export interface NonConsumableProductListItem extends BasicProductListItem {
281
+ type: 'NON_CONSUMABLE';
282
+ }
283
+
284
+ /**
285
+ * @public
286
+ * @category 인앱결제
287
+ * @name SubscriptionProductListItem
288
+ * @description 자동 갱신 구독 상품 정보를 담은 객체예요.
289
+ * @property {string} sku - 상품의 고유 ID예요.
290
+ * @property {string} type - 상품의 유형이에요. `SUBSCRIPTION`을 나타내요.
291
+ * @property {string} displayName - 화면에 표시할 상품 이름이에요. 상품 이름은 앱인토스 콘솔에서 설정한 값이에요.
292
+ * @property {string} displayAmount - 통화 단위가 포함된 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
293
+ * @property {string} iconUrl - 상품 아이콘 이미지의 URL이에요. 아이콘은 앱인토스 콘솔에서 설정한 이미지예요.
294
+ * @property {string} description - 상품에 대한 설명이에요. 설명은 앱인토스 콘솔에서 설정한 값이에요.
295
+ * @property {string} renewalCycle - 구독 갱신 주기이에요. `WEEKLY`, `MONTHLY`, `YEARLY` 중 하나를 나타내요.
296
+ * @property {Offer[]} offers - 구독 혜택 옵션 목록이에요. 각 옵션은 하나의 구독 혜택을 나타내요.
297
+ * @property {string} offers[].type - 구독 혜택 옵션 유형이에요. `FREE_TRIAL`, `NEW_SUBSCRIPTION`, `RETURNING` 중 하나를 나타내요.
298
+ * @property {string} offers[].offerId - 구독 혜택 옵션의 고유 ID예요.
299
+ * @property {string} offers[].period - 구독 혜택 옵션의 적용 기간이에요.
300
+ * @property {string} offers[].displayAmount - 통화 단위가 포함된 구독 혜택 옵션의 가격 정보예요. 예를 들어 `1,000원`으로 가격과 통화가 함께 표시돼요.
301
+ */
302
+ export interface SubscriptionProductListItem extends BasicProductListItem {
303
+ type: 'SUBSCRIPTION';
304
+ renewalCycle: 'WEEKLY' | 'MONTHLY' | 'YEARLY';
305
+ offers?: Offer[];
242
306
  }
243
307
 
308
+ /**
309
+ * @public
310
+ * @category 인앱결제
311
+ * @name IapProductListItem
312
+ * @description 인앱결제로 구매할 수 있는 상품 하나의 정보를 담은 객체예요. 상품 목록을 화면에 표시할 때 사용해요.
313
+ */
314
+ export type IapProductListItem = ConsumableProductListItem | NonConsumableProductListItem | SubscriptionProductListItem;
315
+
244
316
  /**
245
317
  * @public
246
318
  * @category 인앱결제
@@ -449,6 +521,122 @@ async function completeProductGrant(params: { params: { orderId: string } }) {
449
521
  return AppsInTossModule.completeProductGrant(params);
450
522
  }
451
523
 
524
+ /**
525
+ * @public
526
+ * @category 인앱결제
527
+ * @name CreateSubscriptionPurchaseOrderOptions
528
+ * @description 구독 인앱결제를 생성할 때 필요한 옵션이에요.
529
+ * @property {object} options - 결제할 구독 상품의 정보예요.
530
+ * @property {string} options.sku - 주문할 구독 상품의 고유 ID예요.
531
+ * @property {string | null} [options.offerId] - 적용할 offer ID예요. 없으면 기본 가격이 적용돼요.
532
+ * @property {(params: { orderId: string, subscriptionId?: string }) => boolean | Promise<boolean>} options.processProductGrant - 주문이 만들어진 뒤 실제로 상품을 지급할 때 호출해요.
533
+ * @property {(event: SubscriptionSuccessEvent) => void | Promise<void>} onEvent - 결제가 성공했을 때 호출해요.
534
+ * @property {(error: unknown) => void | Promise<void>} onError - 결제 과정에서 에러가 발생했을 때 호출해요.
535
+ */
536
+ export interface CreateSubscriptionPurchaseOrderOptions {
537
+ options: {
538
+ sku: string;
539
+ offerId?: string | null;
540
+ processProductGrant: (params: { orderId: string; subscriptionId?: string }) => boolean | Promise<boolean>;
541
+ };
542
+ onEvent: (event: SubscriptionSuccessEvent) => void | Promise<void>;
543
+ onError: (error: unknown) => void | Promise<void>;
544
+ }
545
+
546
+ /**
547
+ * @public
548
+ * @category 인앱결제
549
+ * @name IapCreateSubscriptionPurchaseOrderResult
550
+ * @description 구독 인앱결제가 완료되면 결제 세부 정보와 상품 정보를 담아 반환해요. `IapCreateOneTimePurchaseOrderResult`와 동일한 구조예요.
551
+ */
552
+ export type IapCreateSubscriptionPurchaseOrderResult = IapCreateOneTimePurchaseOrderResult;
553
+
554
+ interface SubscriptionSuccessEvent {
555
+ type: 'success';
556
+ data: IapCreateSubscriptionPurchaseOrderResult;
557
+ }
558
+
559
+ /**
560
+ * @public
561
+ * @category 인앱결제
562
+ * @name createSubscriptionPurchaseOrder
563
+ * @description
564
+ * 구독 인앱결제 주문서 페이지로 이동해요. 사용자가 구독 상품 구매 버튼을 누르는 상황 등에 사용할 수 있어요.
565
+ * @param {CreateSubscriptionPurchaseOrderOptions} params - 구독 인앱결제를 생성할 때 필요한 정보예요.
566
+ * @returns {() => void} 앱브릿지 cleanup 함수를 반환해요. 인앱결제 기능이 끝나면 반드시 이 함수를 호출해서 리소스를 해제해야 해요.
567
+ *
568
+ * @example
569
+ * ### 구독 인앱결제 주문서 페이지로 이동하기
570
+ *
571
+ * ```tsx
572
+ * import { IAP } from "@apps-in-toss/web-framework";
573
+ * import { Button } from "@toss/tds-react-native";
574
+ * import { useCallback } from "react";
575
+ *
576
+ * interface Props {
577
+ * sku: string;
578
+ * offerId?: string;
579
+ * }
580
+ *
581
+ * function SubscriptionPurchaseButton({ sku, offerId }: Props) {
582
+ * const handleClick = useCallback(async () => {
583
+ * const cleanup = IAP.createSubscriptionPurchaseOrder({
584
+ * options: {
585
+ * sku,
586
+ * offerId,
587
+ * processProductGrant: ({ orderId, subscriptionId }) => {
588
+ * // 상품 지급 로직 작성
589
+ * return true; // 상품 지급 여부
590
+ * },
591
+ * },
592
+ * onEvent: (event) => {
593
+ * console.log(event);
594
+ * },
595
+ * onError: (error) => {
596
+ * console.error(error);
597
+ * },
598
+ * });
599
+ *
600
+ * return cleanup;
601
+ * }, [sku, offerId]);
602
+ *
603
+ * return <Button onClick={handleClick}>구독하기</Button>;
604
+ * }
605
+ * ```
606
+ */
607
+ function createSubscriptionPurchaseOrder(params: CreateSubscriptionPurchaseOrderOptions): () => void {
608
+ const isSupported = isMinVersionSupported({
609
+ android: '5.248.0',
610
+ ios: '5.249.0',
611
+ });
612
+
613
+ if (!isSupported) {
614
+ return noop;
615
+ }
616
+
617
+ const { options, onEvent, onError } = params;
618
+ const { sku, offerId, processProductGrant } = options;
619
+
620
+ const unregisterCallbacks = INTERNAL__appBridgeHandler.invokeAppBridgeMethod(
621
+ 'requestSubscriptionPurchase',
622
+ { sku, offerId: offerId ?? null },
623
+ {
624
+ onPurchased: async (purchasedParams: { orderId: string; subscriptionId?: string }) => {
625
+ const isProductGranted = await processProductGrant(purchasedParams);
626
+ await AppsInTossModule.processProductGrant({ orderId: purchasedParams.orderId, isProductGranted });
627
+ },
628
+ onSuccess: (result: IapCreateSubscriptionPurchaseOrderResult) => {
629
+ onEvent({ type: 'success', data: result });
630
+ },
631
+ onError: (error: unknown) => {
632
+ onError(error);
633
+ },
634
+ }
635
+ );
636
+
637
+ return unregisterCallbacks;
638
+ }
639
+
452
640
  /**
453
641
  * @public
454
642
  * @category 인앱결제
@@ -462,6 +650,7 @@ async function completeProductGrant(params: { params: { orderId: string } }) {
462
650
  */
463
651
  export const IAP = {
464
652
  createOneTimePurchaseOrder,
653
+ createSubscriptionPurchaseOrder,
465
654
  getProductItemList,
466
655
  getPendingOrders,
467
656
  getCompletedOrRefundedOrders,