@hayanmind/monetai-react-native 0.2.4
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/LICENSE +20 -0
- package/MonetaiReactNative.podspec +25 -0
- package/README.md +31 -0
- package/android/build.gradle +81 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/hayanmind/monetaireactnative/MonetaiReactNativeModule.kt +110 -0
- package/android/src/main/java/com/hayanmind/monetaireactnative/MonetaiReactNativePackage.kt +17 -0
- package/ios/MonetaiReactNative-Bridging-Header.h +2 -0
- package/ios/MonetaiReactNative.mm +38 -0
- package/ios/MonetaiReactNative.swift +336 -0
- package/lib/commonjs/ApiRequest.js +120 -0
- package/lib/commonjs/ApiRequest.js.map +1 -0
- package/lib/commonjs/MonetaiProvider.js +67 -0
- package/lib/commonjs/MonetaiProvider.js.map +1 -0
- package/lib/commonjs/MonetaiSDK.js +198 -0
- package/lib/commonjs/MonetaiSDK.js.map +1 -0
- package/lib/commonjs/index.js +17 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/lib.js +24 -0
- package/lib/commonjs/lib.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types/global.d.js +2 -0
- package/lib/commonjs/types/global.d.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/ApiRequest.js +110 -0
- package/lib/module/ApiRequest.js.map +1 -0
- package/lib/module/MonetaiProvider.js +61 -0
- package/lib/module/MonetaiProvider.js.map +1 -0
- package/lib/module/MonetaiSDK.js +192 -0
- package/lib/module/MonetaiSDK.js.map +1 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/lib.js +20 -0
- package/lib/module/lib.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types/global.d.js +2 -0
- package/lib/module/types/global.d.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/ApiRequest.d.ts +52 -0
- package/lib/typescript/commonjs/src/ApiRequest.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MonetaiProvider.d.ts +18 -0
- package/lib/typescript/commonjs/src/MonetaiProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MonetaiSDK.d.ts +45 -0
- package/lib/typescript/commonjs/src/MonetaiSDK.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +5 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/lib.d.ts +2 -0
- package/lib/typescript/commonjs/src/lib.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types.d.ts +17 -0
- package/lib/typescript/commonjs/src/types.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/src/ApiRequest.d.ts +52 -0
- package/lib/typescript/module/src/ApiRequest.d.ts.map +1 -0
- package/lib/typescript/module/src/MonetaiProvider.d.ts +18 -0
- package/lib/typescript/module/src/MonetaiProvider.d.ts.map +1 -0
- package/lib/typescript/module/src/MonetaiSDK.d.ts +45 -0
- package/lib/typescript/module/src/MonetaiSDK.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +5 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/lib/typescript/module/src/lib.d.ts +2 -0
- package/lib/typescript/module/src/lib.d.ts.map +1 -0
- package/lib/typescript/module/src/types.d.ts +17 -0
- package/lib/typescript/module/src/types.d.ts.map +1 -0
- package/package.json +176 -0
- package/src/ApiRequest.ts +182 -0
- package/src/MonetaiProvider.tsx +79 -0
- package/src/MonetaiSDK.ts +225 -0
- package/src/index.tsx +5 -0
- package/src/lib.ts +17 -0
- package/src/types/global.d.ts +2 -0
- package/src/types.ts +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Daehoon Kim
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "MonetaiReactNative"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://monetai.io.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
|
+
|
|
18
|
+
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
19
|
+
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
|
20
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
21
|
+
install_modules_dependencies(s)
|
|
22
|
+
else
|
|
23
|
+
s.dependency "React-Core"
|
|
24
|
+
end
|
|
25
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @hayanmind/monetai-react-native
|
|
2
|
+
|
|
3
|
+
.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @hayanmind/monetai-react-native
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { multiply } from '@hayanmind/monetai-react-native';
|
|
15
|
+
|
|
16
|
+
// ...
|
|
17
|
+
|
|
18
|
+
const result = await multiply(3, 7);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Contributing
|
|
22
|
+
|
|
23
|
+
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
MIT
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['MonetaiReactNative_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
apply plugin: "com.android.library"
|
|
20
|
+
apply plugin: "kotlin-android"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def getExtOrIntegerDefault(name) {
|
|
24
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["MonetaiReactNative_" + name]).toInteger()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def supportsNamespace() {
|
|
28
|
+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
29
|
+
def major = parsed[0].toInteger()
|
|
30
|
+
def minor = parsed[1].toInteger()
|
|
31
|
+
|
|
32
|
+
// Namespace support was added in 7.3.0
|
|
33
|
+
return (major == 7 && minor >= 3) || major >= 8
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
android {
|
|
37
|
+
if (supportsNamespace()) {
|
|
38
|
+
namespace "com.hayanmind.monetaireactnative"
|
|
39
|
+
|
|
40
|
+
sourceSets {
|
|
41
|
+
main {
|
|
42
|
+
manifest.srcFile "src/main/AndroidManifestNew.xml"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
48
|
+
|
|
49
|
+
defaultConfig {
|
|
50
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
51
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
buildTypes {
|
|
55
|
+
release {
|
|
56
|
+
minifyEnabled false
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
lintOptions {
|
|
61
|
+
disable "GradleCompatible"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
compileOptions {
|
|
65
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
66
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
repositories {
|
|
71
|
+
mavenCentral()
|
|
72
|
+
google()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
76
|
+
|
|
77
|
+
dependencies {
|
|
78
|
+
implementation "com.facebook.react:react-android"
|
|
79
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
80
|
+
}
|
|
81
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
package com.hayanmind.monetaireactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
8
|
+
import com.facebook.react.bridge.ReactMethod
|
|
9
|
+
import com.facebook.react.bridge.Promise
|
|
10
|
+
|
|
11
|
+
class MonetaiReactNativeModule(reactContext: ReactApplicationContext) :
|
|
12
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
13
|
+
|
|
14
|
+
private val TAG = "MonetaiSDK"
|
|
15
|
+
private val PREFS_NAME = "MonetaiPrefs"
|
|
16
|
+
private val USER_ID_KEY = "MonetaiAppAccountToken"
|
|
17
|
+
private val SDK_KEY = "MonetaiSdkKey"
|
|
18
|
+
|
|
19
|
+
private val sharedPreferences: SharedPreferences by lazy {
|
|
20
|
+
reactApplicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun getName(): String {
|
|
24
|
+
return NAME
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@ReactMethod
|
|
28
|
+
fun setUserId(userId: String, promise: Promise) {
|
|
29
|
+
try {
|
|
30
|
+
sharedPreferences.edit().putString(USER_ID_KEY, userId).apply()
|
|
31
|
+
Log.d(TAG, "userId 저장 완료: $userId")
|
|
32
|
+
promise.resolve(null)
|
|
33
|
+
} catch (e: Exception) {
|
|
34
|
+
promise.reject("ERROR_SETTING_USER_ID", "userId 저장 실패", e)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@ReactMethod
|
|
39
|
+
fun getUserId(promise: Promise) {
|
|
40
|
+
try {
|
|
41
|
+
val userId = sharedPreferences.getString(USER_ID_KEY, null)
|
|
42
|
+
if (userId != null) {
|
|
43
|
+
Log.d(TAG, "getUserId: $userId")
|
|
44
|
+
promise.resolve(userId)
|
|
45
|
+
} else {
|
|
46
|
+
Log.d(TAG, "getUserId: 값이 없음, null 반환")
|
|
47
|
+
promise.resolve(null)
|
|
48
|
+
}
|
|
49
|
+
} catch (e: Exception) {
|
|
50
|
+
promise.reject("ERROR_GETTING_USER_ID", "userId 조회 실패", e)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@ReactMethod
|
|
55
|
+
fun setSdkKey(sdkKey: String, promise: Promise) {
|
|
56
|
+
try {
|
|
57
|
+
sharedPreferences.edit().putString(SDK_KEY, sdkKey).apply()
|
|
58
|
+
Log.d(TAG, "sdkKey 저장 완료")
|
|
59
|
+
promise.resolve(null)
|
|
60
|
+
} catch (e: Exception) {
|
|
61
|
+
promise.reject("ERROR_SETTING_SDK_KEY", "sdkKey 저장 실패", e)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@ReactMethod
|
|
66
|
+
fun getSdkKey(promise: Promise) {
|
|
67
|
+
try {
|
|
68
|
+
val sdkKey = sharedPreferences.getString(SDK_KEY, null)
|
|
69
|
+
if (sdkKey != null) {
|
|
70
|
+
Log.d(TAG, "getSdkKey 성공")
|
|
71
|
+
promise.resolve(sdkKey)
|
|
72
|
+
} else {
|
|
73
|
+
Log.d(TAG, "getSdkKey: 값이 없음, null 반환")
|
|
74
|
+
promise.resolve(null)
|
|
75
|
+
}
|
|
76
|
+
} catch (e: Exception) {
|
|
77
|
+
promise.reject("ERROR_GETTING_SDK_KEY", "sdkKey 조회 실패", e)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@ReactMethod
|
|
82
|
+
fun startObserving(useStoreKit2: Boolean, promise: Promise) {
|
|
83
|
+
Log.w(TAG, "startObserving: Android 플랫폼에서는 지원하지 않는 기능입니다")
|
|
84
|
+
promise.resolve(null)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@ReactMethod
|
|
88
|
+
fun sendReceipt(promise: Promise) {
|
|
89
|
+
Log.w(TAG, "sendReceipt: Android 플랫폼에서는 지원하지 않는 기능입니다")
|
|
90
|
+
promise.resolve(null)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@ReactMethod
|
|
94
|
+
fun reset(promise: Promise) {
|
|
95
|
+
try {
|
|
96
|
+
sharedPreferences.edit()
|
|
97
|
+
.remove(USER_ID_KEY)
|
|
98
|
+
.remove(SDK_KEY)
|
|
99
|
+
.apply()
|
|
100
|
+
Log.d(TAG, "userId와 sdkKey가 초기화되었습니다")
|
|
101
|
+
promise.resolve(null)
|
|
102
|
+
} catch (e: Exception) {
|
|
103
|
+
promise.reject("ERROR_RESETTING", "데이터 초기화 실패", e)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
companion object {
|
|
108
|
+
const val NAME = "MonetaiReactNative"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.hayanmind.monetaireactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MonetaiReactNativePackage : ReactPackage {
|
|
10
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
11
|
+
return listOf(MonetaiReactNativeModule(reactContext))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
15
|
+
return emptyList()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(MonetaiReactNative, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(multiply:(float)a withB:(float)b
|
|
6
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
7
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
8
|
+
|
|
9
|
+
RCT_EXTERN_METHOD(startObserving:(BOOL)useStoreKit2
|
|
10
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
11
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
|
|
13
|
+
RCT_EXTERN_METHOD(setUserId:(NSString *)userId
|
|
14
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
15
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
16
|
+
|
|
17
|
+
RCT_EXTERN_METHOD(getUserId:(RCTPromiseResolveBlock)resolve
|
|
18
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
19
|
+
|
|
20
|
+
RCT_EXTERN_METHOD(setSdkKey:(NSString *)sdkKey
|
|
21
|
+
withResolver:(RCTPromiseResolveBlock)resolve
|
|
22
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
23
|
+
|
|
24
|
+
RCT_EXTERN_METHOD(getSdkKey:(RCTPromiseResolveBlock)resolve
|
|
25
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
26
|
+
|
|
27
|
+
RCT_EXTERN_METHOD(sendReceipt:(RCTPromiseResolveBlock)resolve
|
|
28
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
29
|
+
|
|
30
|
+
RCT_EXTERN_METHOD(reset:(RCTPromiseResolveBlock)resolve
|
|
31
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
32
|
+
|
|
33
|
+
+ (BOOL)requiresMainQueueSetup
|
|
34
|
+
{
|
|
35
|
+
return NO;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@end
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import StoreKit
|
|
3
|
+
|
|
4
|
+
// Hardcoded endpoint URL for mapping
|
|
5
|
+
private let mappingURL = URL(string: "https://monetai-api-414410537412.us-central1.run.app/sdk/transaction-id-to-user-id/ios")!
|
|
6
|
+
// 영수증 검증용 엔드포인트
|
|
7
|
+
private let receiptValidationURL = URL(string: "https://monetai-api-414410537412.us-central1.run.app/sdk/transaction-id-to-user-id/ios/receipt")!
|
|
8
|
+
|
|
9
|
+
// MARK: - 영수증 검증 및 전송 로직
|
|
10
|
+
class ReceiptValidator {
|
|
11
|
+
static let shared = ReceiptValidator()
|
|
12
|
+
|
|
13
|
+
// 영수증 전송만 담당하는 단일 메소드
|
|
14
|
+
func sendReceipt() {
|
|
15
|
+
guard let receiptURL = Bundle.main.appStoreReceiptURL,
|
|
16
|
+
let receiptData = try? Data(contentsOf: receiptURL),
|
|
17
|
+
let userId = KeychainManager.shared.getToken(),
|
|
18
|
+
let bundleId = Bundle.main.bundleIdentifier,
|
|
19
|
+
let sdkKey = KeychainManager.shared.getSdkKey() else {
|
|
20
|
+
print("[Monetai][Error] 필요한 정보가 누락되었습니다")
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let base64Receipt = receiptData.base64EncodedString()
|
|
25
|
+
|
|
26
|
+
let payload: [String: Any] = [
|
|
27
|
+
"receiptData": base64Receipt,
|
|
28
|
+
"bundleId": bundleId,
|
|
29
|
+
"userId": userId,
|
|
30
|
+
"sdkKey": sdkKey
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
var request = URLRequest(url: receiptValidationURL)
|
|
34
|
+
request.httpMethod = "POST"
|
|
35
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
36
|
+
request.httpBody = try? JSONSerialization.data(withJSONObject: payload)
|
|
37
|
+
|
|
38
|
+
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
39
|
+
if let error = error {
|
|
40
|
+
print("[Monetai][Error] 영수증 전송 실패: \(error)")
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if let httpResponse = response as? HTTPURLResponse {
|
|
45
|
+
print("[Monetai][Debug] 영수증 전송 응답: \(httpResponse.statusCode)")
|
|
46
|
+
}
|
|
47
|
+
}.resume()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// MARK: - Keychain Helper
|
|
52
|
+
class KeychainManager {
|
|
53
|
+
static let shared = KeychainManager()
|
|
54
|
+
private let tokenKey = "MonetaiAppAccountToken"
|
|
55
|
+
private let sdkKey = "MonetaiSdkKey"
|
|
56
|
+
|
|
57
|
+
func setToken(_ token: String) {
|
|
58
|
+
guard let data = token.data(using: .utf8) else { return }
|
|
59
|
+
let query: [String: Any] = [
|
|
60
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
61
|
+
kSecAttrAccount as String: tokenKey,
|
|
62
|
+
kSecValueData as String: data
|
|
63
|
+
]
|
|
64
|
+
SecItemDelete(query as CFDictionary)
|
|
65
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
66
|
+
print("[Monetai][Debug] setUserId status: \(status)")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func getToken() -> String? {
|
|
70
|
+
let query: [String: Any] = [
|
|
71
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
72
|
+
kSecAttrAccount as String: tokenKey,
|
|
73
|
+
kSecReturnData as String: kCFBooleanTrue!,
|
|
74
|
+
kSecMatchLimit as String: kSecMatchLimitOne
|
|
75
|
+
]
|
|
76
|
+
var item: AnyObject?
|
|
77
|
+
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
78
|
+
guard status == errSecSuccess,
|
|
79
|
+
let data = item as? Data,
|
|
80
|
+
let token = String(data: data, encoding: .utf8) else {
|
|
81
|
+
print("[Monetai][Error] getUserId status: \(status)")
|
|
82
|
+
return nil
|
|
83
|
+
}
|
|
84
|
+
print("[Monetai][Debug] getUserId: \(token)")
|
|
85
|
+
return token
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func setSdkKey(_ key: String) {
|
|
89
|
+
guard let data = key.data(using: .utf8) else { return }
|
|
90
|
+
let query: [String: Any] = [
|
|
91
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
92
|
+
kSecAttrAccount as String: sdkKey,
|
|
93
|
+
kSecValueData as String: data
|
|
94
|
+
]
|
|
95
|
+
SecItemDelete(query as CFDictionary)
|
|
96
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
97
|
+
print("[Monetai][Debug] setSdkKey status: \(status)")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func getSdkKey() -> String? {
|
|
101
|
+
let query: [String: Any] = [
|
|
102
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
103
|
+
kSecAttrAccount as String: sdkKey,
|
|
104
|
+
kSecReturnData as String: kCFBooleanTrue!,
|
|
105
|
+
kSecMatchLimit as String: kSecMatchLimitOne
|
|
106
|
+
]
|
|
107
|
+
var item: AnyObject?
|
|
108
|
+
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
109
|
+
guard status == errSecSuccess,
|
|
110
|
+
let data = item as? Data,
|
|
111
|
+
let key = String(data: data, encoding: .utf8) else {
|
|
112
|
+
print("[Monetai][Error] getSdkKey status: \(status)")
|
|
113
|
+
return nil
|
|
114
|
+
}
|
|
115
|
+
print("[Monetai][Debug] getSdkKey: \(key)")
|
|
116
|
+
return key
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// MARK: - StoreKit1 Observer
|
|
121
|
+
class PurchaseObserver: NSObject, SKPaymentTransactionObserver {
|
|
122
|
+
static let shared = PurchaseObserver()
|
|
123
|
+
|
|
124
|
+
private override init() {
|
|
125
|
+
super.init()
|
|
126
|
+
print("[Monetai][Debug] PurchaseObserver initialized")
|
|
127
|
+
SKPaymentQueue.default().add(self)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
deinit {
|
|
131
|
+
SKPaymentQueue.default().remove(self)
|
|
132
|
+
print("[Monetai][Debug] PurchaseObserver deinitialized")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@objc static func start() {
|
|
136
|
+
print("[Monetai][Debug] PurchaseObserver.start() called")
|
|
137
|
+
_ = PurchaseObserver.shared
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
141
|
+
print("[Monetai][Debug] paymentQueue count: \(transactions.count)")
|
|
142
|
+
for tx in transactions {
|
|
143
|
+
let stateDesc: String = {
|
|
144
|
+
switch tx.transactionState {
|
|
145
|
+
case .purchasing: return "purchasing"
|
|
146
|
+
case .purchased: return "purchased"
|
|
147
|
+
case .failed: return "failed"
|
|
148
|
+
case .restored: return "restored"
|
|
149
|
+
case .deferred: return "deferred"
|
|
150
|
+
@unknown default: return "unknown"
|
|
151
|
+
}
|
|
152
|
+
}()
|
|
153
|
+
print("[Monetai][Debug] State: \(stateDesc), id: \(tx.transactionIdentifier ?? "nil")")
|
|
154
|
+
guard let origId = tx.original?.transactionIdentifier ?? tx.transactionIdentifier else {
|
|
155
|
+
print("[Monetai][Error] No transactionIdentifier")
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
handleTransaction(origId)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private func handleTransaction(_ originalTxId: String) {
|
|
163
|
+
print("[Monetai][Debug] handleTransaction: \(originalTxId)")
|
|
164
|
+
guard let userId = KeychainManager.shared.getToken() else {
|
|
165
|
+
print("[Monetai][Error] no userId set")
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
guard let bundleId = Bundle.main.bundleIdentifier else {
|
|
169
|
+
print("[Monetai][Error] Bundle identifier is nil")
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
guard let sdkKey = KeychainManager.shared.getSdkKey() else {
|
|
173
|
+
print("[Monetai][Error] no sdkKey set")
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
let payload: [String: Any] = [
|
|
177
|
+
"transactionId": originalTxId,
|
|
178
|
+
"bundleId": bundleId,
|
|
179
|
+
"userId": userId,
|
|
180
|
+
"sdkKey": sdkKey
|
|
181
|
+
]
|
|
182
|
+
print("[Monetai][Debug] POST to \(mappingURL): \(payload)")
|
|
183
|
+
sendMapping(payload)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private func sendMapping(_ payload: [String: Any]) {
|
|
187
|
+
var req = URLRequest(url: mappingURL)
|
|
188
|
+
req.httpMethod = "POST"
|
|
189
|
+
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
190
|
+
req.httpBody = try? JSONSerialization.data(withJSONObject: payload)
|
|
191
|
+
URLSession.shared.dataTask(with: req) { _, res, err in
|
|
192
|
+
if let err = err {
|
|
193
|
+
print("[Monetai][Error] Mapping POST failed: \(err)")
|
|
194
|
+
} else {
|
|
195
|
+
print("[Monetai][Debug] Mapping POST succeeded: \(String(describing: res))")
|
|
196
|
+
}
|
|
197
|
+
}.resume()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// MARK: - StoreKit2 Observer (iOS 15+)
|
|
202
|
+
@available(iOS 15.0, *)
|
|
203
|
+
class StoreKit2Observer {
|
|
204
|
+
static let shared = StoreKit2Observer()
|
|
205
|
+
|
|
206
|
+
private init() {
|
|
207
|
+
print("[Monetai][Debug] StoreKit2Observer initialized")
|
|
208
|
+
Task {
|
|
209
|
+
for await update in Transaction.updates {
|
|
210
|
+
print("[Monetai][Debug] SK2 update: \(update)")
|
|
211
|
+
if case .verified(let txn) = update {
|
|
212
|
+
let origId = String(txn.originalID)
|
|
213
|
+
print("[Monetai][Debug] SK2 origTxId: \(origId)")
|
|
214
|
+
handleTransaction(origId)
|
|
215
|
+
} else {
|
|
216
|
+
print("[Monetai][Error] SK2 unverified: \(update)")
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private func handleTransaction(_ originalTxId: String) {
|
|
223
|
+
print("[Monetai][Debug] SK2 handle: \(originalTxId)")
|
|
224
|
+
guard let userId = KeychainManager.shared.getToken() else {
|
|
225
|
+
print("[Monetai][Error] SK2 no userId")
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
guard let bundleId = Bundle.main.bundleIdentifier else {
|
|
229
|
+
print("[Monetai][Error] Bundle identifier is nil")
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
guard let sdkKey = KeychainManager.shared.getSdkKey() else {
|
|
233
|
+
print("[Monetai][Error] no sdkKey set")
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
let payload: [String: Any] = [
|
|
237
|
+
"transactionId": originalTxId,
|
|
238
|
+
"bundleId": bundleId,
|
|
239
|
+
"userId": userId,
|
|
240
|
+
"sdkKey": sdkKey
|
|
241
|
+
]
|
|
242
|
+
print("[Monetai][Debug] SK2 POST to \(mappingURL): \(payload)")
|
|
243
|
+
var req = URLRequest(url: mappingURL)
|
|
244
|
+
req.httpMethod = "POST"
|
|
245
|
+
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
246
|
+
req.httpBody = try? JSONSerialization.data(withJSONObject: payload)
|
|
247
|
+
URLSession.shared.dataTask(with: req) { _, res, err in
|
|
248
|
+
if let err = err {
|
|
249
|
+
print("[Monetai][Error] SK2 POST failed: \(err)")
|
|
250
|
+
} else {
|
|
251
|
+
print("[Monetai][Debug] SK2 POST succeeded: \(String(describing: res))")
|
|
252
|
+
}
|
|
253
|
+
}.resume()
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// MARK: - React Native Module
|
|
258
|
+
@objc(MonetaiReactNative)
|
|
259
|
+
class MonetaiReactNative: NSObject {
|
|
260
|
+
|
|
261
|
+
@objc(setUserId:withResolver:withRejecter:)
|
|
262
|
+
func setUserId(_ userId: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
263
|
+
KeychainManager.shared.setToken(userId)
|
|
264
|
+
resolve(nil)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@objc(getUserId:withRejecter:)
|
|
268
|
+
func getUserId(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
269
|
+
if let userId = KeychainManager.shared.getToken() {
|
|
270
|
+
resolve(userId)
|
|
271
|
+
} else {
|
|
272
|
+
resolve(nil)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@objc(setSdkKey:withResolver:withRejecter:)
|
|
277
|
+
func setSdkKey(_ sdkKey: String, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
278
|
+
KeychainManager.shared.setSdkKey(sdkKey)
|
|
279
|
+
resolve(nil)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@objc(getSdkKey:withRejecter:)
|
|
283
|
+
func getSdkKey(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
284
|
+
if let sdkKey = KeychainManager.shared.getSdkKey() {
|
|
285
|
+
resolve(sdkKey)
|
|
286
|
+
} else {
|
|
287
|
+
resolve(nil)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@objc(startObserving:withResolver:withRejecter:)
|
|
292
|
+
func startObserving(useStoreKit2: Bool, resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
293
|
+
if useStoreKit2 {
|
|
294
|
+
if #available(iOS 15.0, *) {
|
|
295
|
+
_ = StoreKit2Observer.shared
|
|
296
|
+
resolve(nil)
|
|
297
|
+
} else {
|
|
298
|
+
reject("SK2_UNAVAILABLE", "StoreKit2 requires iOS 15.0 or newer", nil)
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
PurchaseObserver.start()
|
|
302
|
+
resolve(nil)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@objc(sendReceipt:withRejecter:)
|
|
307
|
+
func sendReceipt(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
308
|
+
ReceiptValidator.shared.sendReceipt()
|
|
309
|
+
resolve(nil)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@objc(reset:withRejecter:)
|
|
313
|
+
func reset(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
314
|
+
// userId와 sdkKey 모두 제거
|
|
315
|
+
let tokenQuery: [String: Any] = [
|
|
316
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
317
|
+
kSecAttrAccount as String: "MonetaiAppAccountToken"
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
let sdkKeyQuery: [String: Any] = [
|
|
321
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
322
|
+
kSecAttrAccount as String: "MonetaiSdkKey"
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
SecItemDelete(tokenQuery as CFDictionary)
|
|
326
|
+
SecItemDelete(sdkKeyQuery as CFDictionary)
|
|
327
|
+
|
|
328
|
+
print("[Monetai][Debug] userId와 sdkKey가 초기화되었습니다")
|
|
329
|
+
resolve(nil)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@objc
|
|
333
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
334
|
+
return false
|
|
335
|
+
}
|
|
336
|
+
}
|