framepayments-react-native 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -0
- package/README.md +135 -0
- package/android/build.gradle +46 -0
- package/android/src/main/AndroidManifest.xml +12 -0
- package/android/src/main/java/com/framepayments/reactnativeframe/FrameCheckoutActivity.kt +40 -0
- package/android/src/main/java/com/framepayments/reactnativeframe/FrameFlowActivity.kt +84 -0
- package/android/src/main/java/com/framepayments/reactnativeframe/FrameSDKModule.kt +175 -0
- package/android/src/main/java/com/framepayments/reactnativeframe/FrameSDKPackage.kt +16 -0
- package/ios/FrameReactNative.podspec +26 -0
- package/ios/FrameSDKBridge.m +26 -0
- package/ios/FrameSDKBridge.swift +161 -0
- package/lib/errors.d.ts +25 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +40 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +9 -0
- package/lib/native.d.ts +18 -0
- package/lib/native.d.ts.map +1 -0
- package/lib/native.js +49 -0
- package/lib/types.d.ts +36 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +5 -0
- package/package.json +64 -0
- package/src/__tests__/native.test.ts +116 -0
- package/src/errors.ts +53 -0
- package/src/index.ts +12 -0
- package/src/native.ts +78 -0
- package/src/types.ts +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2024 Frame Payments
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Frame React Native SDK
|
|
2
|
+
|
|
3
|
+
React Native SDK for [Frame Payments](https://framepayments.com). It bridges the native Frame iOS and Android SDKs to provide payment UI (checkout and cart modals) and initialization. For API operations (customers, charge intents, refunds, etc.), use the [framepayments](https://www.npmjs.com/package/framepayments) (frame-node) package from JavaScript.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- React Native >= 0.72
|
|
8
|
+
- iOS 17+ / Android 8.0+ (API 26+)
|
|
9
|
+
- A [Frame](https://framepayments.com) account and API key
|
|
10
|
+
|
|
11
|
+
**Tested with:** Frame iOS SDK (SPM) and Frame Android SDK 1.2.x. Other compatible versions may work but are not officially tested.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @framepayments/react-native-frame
|
|
17
|
+
# or
|
|
18
|
+
yarn add @framepayments/react-native-frame
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### iOS
|
|
22
|
+
|
|
23
|
+
1. **Add the Frame iOS SDK via Swift Package Manager** (required): In Xcode, open your project, then go to **File → Add Package Dependencies**. Enter:
|
|
24
|
+
```
|
|
25
|
+
https://github.com/Frame-Payments/frame-ios
|
|
26
|
+
```
|
|
27
|
+
Add the **Frame-iOS** package and choose the version or branch you need. The React Native SDK’s native code depends on it; CocoaPods cannot pull it in automatically.
|
|
28
|
+
|
|
29
|
+
2. **Install pods**:
|
|
30
|
+
```bash
|
|
31
|
+
cd ios && pod install && cd ..
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Android
|
|
35
|
+
|
|
36
|
+
No extra steps; autolinking includes the native module.
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### 1. Initialize
|
|
41
|
+
|
|
42
|
+
Call once at app startup (e.g. in your root component or App.js).
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import Frame from '@framepayments/react-native-frame';
|
|
46
|
+
|
|
47
|
+
Frame.initialize({
|
|
48
|
+
apiKey: 'YOUR_FRAME_SECRET_KEY',
|
|
49
|
+
debugMode: false, // set true for development
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Present Checkout
|
|
54
|
+
|
|
55
|
+
Opens the native checkout modal. Resolves with the created charge intent on success.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const chargeIntent = await Frame.presentCheckout({
|
|
59
|
+
customerId: 'cus_xxx', // optional
|
|
60
|
+
amount: 10000, // cents
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Present Cart
|
|
65
|
+
|
|
66
|
+
Opens the cart flow (cart screen then checkout). Resolves with the charge intent when the user completes payment.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const chargeIntent = await Frame.presentCart({
|
|
70
|
+
customerId: 'cus_xxx', // optional
|
|
71
|
+
items: [
|
|
72
|
+
{ id: '1', title: 'Product A', amountInCents: 10000, imageUrl: 'https://...' },
|
|
73
|
+
],
|
|
74
|
+
shippingAmountInCents: 500,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Error handling
|
|
79
|
+
|
|
80
|
+
The SDK rejects with an error object that includes `code` and `message`. Common codes:
|
|
81
|
+
|
|
82
|
+
- `NOT_INITIALIZED` – You called `presentCheckout` or `presentCart` before `Frame.initialize()`.
|
|
83
|
+
- `USER_CANCELED` – The user dismissed the modal or closed checkout without completing payment.
|
|
84
|
+
- `NO_ACTIVITY` / `NO_ROOT_VC` – No host activity or view controller available (e.g. app not ready).
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
try {
|
|
88
|
+
const intent = await Frame.presentCheckout({ amount: 10000 });
|
|
89
|
+
} catch (e: any) {
|
|
90
|
+
if (e.code === 'USER_CANCELED') return;
|
|
91
|
+
console.error(e.code, e.message);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### API calls (customers, charge intents, refunds)
|
|
96
|
+
|
|
97
|
+
Install the Frame Node SDK (optional peer dependency) and use it from your React Native app:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm install framepayments
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { FrameSDK } from 'framepayments';
|
|
105
|
+
|
|
106
|
+
const frame = new FrameSDK({ apiKey: 'YOUR_SECRET_KEY' });
|
|
107
|
+
const customers = await frame.customers.list();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Security
|
|
111
|
+
|
|
112
|
+
- **API key**: Use your Frame **secret** key only in a secure context. Do not commit it to source control; use environment variables or a secure config.
|
|
113
|
+
- **Production**: Disable `debugMode` in production to avoid logging sensitive data.
|
|
114
|
+
- Payment card data is handled by the native Frame SDKs (Evervault, etc.) and never touches your JS bundle.
|
|
115
|
+
|
|
116
|
+
## Running the example
|
|
117
|
+
|
|
118
|
+
The [example](./example) folder contains a sample app (App.tsx, package.json) that uses the SDK for init, presentCheckout, presentCart, and frame-node for listing customers.
|
|
119
|
+
|
|
120
|
+
1. **From this repo (local SDK):** Create a new React Native app (e.g. `npx react-native init FrameExample`), then copy `example/App.tsx` into it and add the dependency: `"@framepayments/react-native-frame": "file:/path/to/frame-react-native"`. Run `npm install`. On iOS: add Frame-iOS via SPM in Xcode (see [Installation – iOS](#ios) above), then `cd ios && pod install`. See [example/README.md](./example/README.md) for details.
|
|
121
|
+
2. **Using the published package:** Install `@framepayments/react-native-frame` and `framepayments` in your app and use the same patterns as in `example/App.tsx`.
|
|
122
|
+
|
|
123
|
+
## Release checklist
|
|
124
|
+
|
|
125
|
+
For maintainers publishing a new version:
|
|
126
|
+
|
|
127
|
+
1. Bump version in `package.json`
|
|
128
|
+
2. Update `CHANGELOG.md` with the new version and date
|
|
129
|
+
3. Commit and tag: `git tag v1.0.0`
|
|
130
|
+
4. Push and create a GitHub release
|
|
131
|
+
5. Publish: `npm publish --access public` (for scoped packages)
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
Apache-2.0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
dependencies {
|
|
7
|
+
classpath 'com.android.tools.build:gradle:8.1.0'
|
|
8
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
apply plugin: 'com.android.library'
|
|
13
|
+
apply plugin: 'kotlin-android'
|
|
14
|
+
|
|
15
|
+
def safeExtGet(prop, fallback) {
|
|
16
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
android {
|
|
20
|
+
namespace "com.framepayments.reactnativeframe"
|
|
21
|
+
compileSdk safeExtGet('compileSdkVersion', 34)
|
|
22
|
+
defaultConfig {
|
|
23
|
+
minSdk safeExtGet('minSdkVersion', 26)
|
|
24
|
+
targetSdk safeExtGet('targetSdkVersion', 34)
|
|
25
|
+
}
|
|
26
|
+
compileOptions {
|
|
27
|
+
sourceCompatibility JavaVersion.VERSION_11
|
|
28
|
+
targetCompatibility JavaVersion.VERSION_11
|
|
29
|
+
}
|
|
30
|
+
kotlinOptions {
|
|
31
|
+
jvmTarget = '11'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
repositories {
|
|
36
|
+
google()
|
|
37
|
+
mavenCentral()
|
|
38
|
+
maven { url "https://jitpack.io" }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dependencies {
|
|
42
|
+
implementation 'com.facebook.react:react-native:+'
|
|
43
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0"
|
|
44
|
+
implementation 'com.framepayments:framesdk:1.2.0'
|
|
45
|
+
implementation 'com.framepayments:framesdk_ui:1.2.0'
|
|
46
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<application>
|
|
3
|
+
<activity
|
|
4
|
+
android:name="com.framepayments.reactnativeframe.FrameCheckoutActivity"
|
|
5
|
+
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
|
6
|
+
android:exported="false" />
|
|
7
|
+
<activity
|
|
8
|
+
android:name="com.framepayments.reactnativeframe.FrameFlowActivity"
|
|
9
|
+
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
|
10
|
+
android:exported="false" />
|
|
11
|
+
</application>
|
|
12
|
+
</manifest>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package com.framepayments.reactnativeframe
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
7
|
+
import com.framepayments.framesdk.FrameNetworking
|
|
8
|
+
import com.framepayments.framesdk.chargeintents.ChargeIntent
|
|
9
|
+
import com.framepayments.framesdk_ui.FrameCheckoutView
|
|
10
|
+
import com.google.gson.Gson
|
|
11
|
+
|
|
12
|
+
class FrameCheckoutActivity : AppCompatActivity() {
|
|
13
|
+
|
|
14
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
15
|
+
super.onCreate(savedInstanceState)
|
|
16
|
+
val container = FrameLayout(this).apply {
|
|
17
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
18
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
19
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
setContentView(container)
|
|
23
|
+
val customerId = intent.getStringExtra(EXTRA_CUSTOMER_ID)
|
|
24
|
+
val amount = intent.getIntExtra(EXTRA_AMOUNT, 0)
|
|
25
|
+
val checkoutView = FrameCheckoutView(this)
|
|
26
|
+
checkoutView.configure(customerId, amount) { chargeIntent ->
|
|
27
|
+
val json = Gson().toJson(chargeIntent)
|
|
28
|
+
setResult(RESULT_OK, Intent().putExtra(EXTRA_CHARGE_INTENT_JSON, json))
|
|
29
|
+
finish()
|
|
30
|
+
}
|
|
31
|
+
container.addView(checkoutView)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
const val EXTRA_CUSTOMER_ID = "customer_id"
|
|
36
|
+
const val EXTRA_AMOUNT = "amount"
|
|
37
|
+
const val EXTRA_CHARGE_INTENT_JSON = "charge_intent_json"
|
|
38
|
+
const val REQUEST_CODE = 9001
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package com.framepayments.reactnativeframe
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
7
|
+
import com.framepayments.framesdk_ui.FrameCartItem
|
|
8
|
+
import com.framepayments.framesdk_ui.FrameCartView
|
|
9
|
+
import com.framepayments.framesdk_ui.FrameCheckoutView
|
|
10
|
+
import com.google.gson.Gson
|
|
11
|
+
import com.google.gson.reflect.TypeToken
|
|
12
|
+
|
|
13
|
+
class FrameFlowActivity : AppCompatActivity() {
|
|
14
|
+
|
|
15
|
+
private var cartView: FrameCartView? = null
|
|
16
|
+
private var checkoutView: FrameCheckoutView? = null
|
|
17
|
+
private val container by lazy {
|
|
18
|
+
FrameLayout(this).apply {
|
|
19
|
+
layoutParams = FrameLayout.LayoutParams(
|
|
20
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
21
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
22
|
+
)
|
|
23
|
+
id = android.R.id.content
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
28
|
+
super.onCreate(savedInstanceState)
|
|
29
|
+
setContentView(container)
|
|
30
|
+
val customerId = intent.getStringExtra(EXTRA_CUSTOMER_ID)
|
|
31
|
+
val itemsJson = intent.getStringExtra(EXTRA_ITEMS_JSON)
|
|
32
|
+
val shippingCents = intent.getIntExtra(EXTRA_SHIPPING_CENTS, 0)
|
|
33
|
+
val items: List<FrameCartItem> = parseCartItems(itemsJson) ?: emptyList()
|
|
34
|
+
showCart(customerId, items, shippingCents)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private fun parseCartItems(json: String?): List<FrameCartItem>? {
|
|
38
|
+
if (json.isNullOrBlank()) return emptyList()
|
|
39
|
+
return try {
|
|
40
|
+
val type = object : TypeToken<List<CartItemDto>>() {}.type
|
|
41
|
+
val list: List<CartItemDto> = Gson().fromJson(json, type)
|
|
42
|
+
list.map { FrameCartItem(it.id, it.title, it.amountInCents, it.imageUrl) }
|
|
43
|
+
} catch (e: Exception) {
|
|
44
|
+
null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private data class CartItemDto(
|
|
49
|
+
val id: String,
|
|
50
|
+
val title: String,
|
|
51
|
+
val amountInCents: Int,
|
|
52
|
+
val imageUrl: String
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
private fun showCart(customerId: String?, items: List<FrameCartItem>, shippingCents: Int) {
|
|
56
|
+
container.removeAllViews()
|
|
57
|
+
cartView = FrameCartView(this).apply {
|
|
58
|
+
configure(customerId, items, shippingCents) { totalCents ->
|
|
59
|
+
showCheckout(customerId, totalCents)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
container.addView(cartView)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun showCheckout(customerId: String?, amount: Int) {
|
|
66
|
+
container.removeAllViews()
|
|
67
|
+
checkoutView = FrameCheckoutView(this).apply {
|
|
68
|
+
configure(customerId, amount) { chargeIntent ->
|
|
69
|
+
val json = Gson().toJson(chargeIntent)
|
|
70
|
+
setResult(RESULT_OK, Intent().putExtra(EXTRA_CHARGE_INTENT_JSON, json))
|
|
71
|
+
finish()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
container.addView(checkoutView)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
companion object {
|
|
78
|
+
const val EXTRA_CUSTOMER_ID = "customer_id"
|
|
79
|
+
const val EXTRA_ITEMS_JSON = "items_json"
|
|
80
|
+
const val EXTRA_SHIPPING_CENTS = "shipping_cents"
|
|
81
|
+
const val EXTRA_CHARGE_INTENT_JSON = "charge_intent_json"
|
|
82
|
+
const val REQUEST_CODE = 9002
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
package com.framepayments.reactnativeframe
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Intent
|
|
5
|
+
import com.facebook.react.bridge.ActivityEventListener
|
|
6
|
+
import com.facebook.react.bridge.Promise
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
9
|
+
import com.facebook.react.bridge.ReactMethod
|
|
10
|
+
import com.facebook.react.bridge.WritableNativeArray
|
|
11
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
12
|
+
import org.json.JSONArray
|
|
13
|
+
import com.framepayments.framesdk.FrameNetworking
|
|
14
|
+
import org.json.JSONObject
|
|
15
|
+
|
|
16
|
+
class FrameSDKModule(reactContext: ReactApplicationContext) :
|
|
17
|
+
ReactContextBaseJavaModule(reactContext), ActivityEventListener {
|
|
18
|
+
|
|
19
|
+
init {
|
|
20
|
+
reactContext.addActivityEventListener(this)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private var checkoutPromise: Promise? = null
|
|
24
|
+
private var cartPromise: Promise? = null
|
|
25
|
+
|
|
26
|
+
override fun getName(): String = "FrameSDK"
|
|
27
|
+
|
|
28
|
+
@ReactMethod
|
|
29
|
+
fun initialize(apiKey: String, debugMode: Boolean, promise: Promise) {
|
|
30
|
+
try {
|
|
31
|
+
val ctx = reactApplicationContext.applicationContext
|
|
32
|
+
FrameNetworking.initializeWithAPIKey(ctx, apiKey, debugMode)
|
|
33
|
+
promise.resolve(null)
|
|
34
|
+
} catch (e: Exception) {
|
|
35
|
+
promise.reject("INIT_FAILED", e.message, e)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@ReactMethod
|
|
40
|
+
fun presentCheckout(customerId: String?, amount: Double, promise: Promise) {
|
|
41
|
+
val activity = currentActivity ?: run {
|
|
42
|
+
promise.reject("NO_ACTIVITY", "No current activity", null)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
checkoutPromise = promise
|
|
46
|
+
val intent = Intent(activity, FrameCheckoutActivity::class.java).apply {
|
|
47
|
+
putExtra(FrameCheckoutActivity.EXTRA_CUSTOMER_ID, customerId)
|
|
48
|
+
putExtra(FrameCheckoutActivity.EXTRA_AMOUNT, amount.toInt())
|
|
49
|
+
}
|
|
50
|
+
activity.startActivityForResult(intent, FrameCheckoutActivity.REQUEST_CODE)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@ReactMethod
|
|
54
|
+
fun presentCart(
|
|
55
|
+
customerId: String?,
|
|
56
|
+
items: com.facebook.react.bridge.ReadableArray,
|
|
57
|
+
shippingAmountInCents: Double,
|
|
58
|
+
promise: Promise
|
|
59
|
+
) {
|
|
60
|
+
val activity = currentActivity ?: run {
|
|
61
|
+
promise.reject("NO_ACTIVITY", "No current activity", null)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
val itemsJson = readableArrayToJson(items) ?: run {
|
|
65
|
+
promise.reject("INVALID_ITEMS", "Invalid cart items", null)
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
cartPromise = promise
|
|
69
|
+
val intent = Intent(activity, FrameFlowActivity::class.java).apply {
|
|
70
|
+
putExtra(FrameFlowActivity.EXTRA_CUSTOMER_ID, customerId)
|
|
71
|
+
putExtra(FrameFlowActivity.EXTRA_ITEMS_JSON, itemsJson)
|
|
72
|
+
putExtra(FrameFlowActivity.EXTRA_SHIPPING_CENTS, shippingAmountInCents.toInt())
|
|
73
|
+
}
|
|
74
|
+
activity.startActivityForResult(intent, FrameFlowActivity.REQUEST_CODE)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private fun readableArrayToJson(items: com.facebook.react.bridge.ReadableArray): String? {
|
|
78
|
+
val arr = org.json.JSONArray()
|
|
79
|
+
for (i in 0 until items.size()) {
|
|
80
|
+
val item = items.getMap(i) ?: return null
|
|
81
|
+
val obj = org.json.JSONObject()
|
|
82
|
+
obj.put("id", item.getString("id"))
|
|
83
|
+
obj.put("title", item.getString("title"))
|
|
84
|
+
obj.put("amountInCents", item.getDouble("amountInCents").toInt())
|
|
85
|
+
obj.put("imageUrl", item.getString("imageUrl"))
|
|
86
|
+
arr.put(obj)
|
|
87
|
+
}
|
|
88
|
+
return arr.toString()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override fun onActivityResult(activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?) {
|
|
92
|
+
when (requestCode) {
|
|
93
|
+
FrameCheckoutActivity.REQUEST_CODE -> handleCheckoutResult(resultCode, data)
|
|
94
|
+
FrameFlowActivity.REQUEST_CODE -> handleCartResult(resultCode, data)
|
|
95
|
+
else -> return
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private fun handleCheckoutResult(resultCode: Int, data: Intent?) {
|
|
100
|
+
val promise = checkoutPromise ?: return
|
|
101
|
+
checkoutPromise = null
|
|
102
|
+
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
103
|
+
val json = data.getStringExtra(FrameCheckoutActivity.EXTRA_CHARGE_INTENT_JSON)
|
|
104
|
+
if (json != null) {
|
|
105
|
+
try {
|
|
106
|
+
val obj = JSONObject(json)
|
|
107
|
+
val map = jsonObjectToWritableMap(obj)
|
|
108
|
+
promise.resolve(map)
|
|
109
|
+
} catch (e: Exception) {
|
|
110
|
+
promise.reject("PARSE_ERROR", e.message, e)
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
promise.reject("NO_RESULT", "No charge intent in result", null)
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
promise.reject("USER_CANCELED", "User cancelled checkout", null)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private fun handleCartResult(resultCode: Int, data: Intent?) {
|
|
121
|
+
val promise = cartPromise ?: return
|
|
122
|
+
cartPromise = null
|
|
123
|
+
if (resultCode == Activity.RESULT_OK && data != null) {
|
|
124
|
+
val json = data.getStringExtra(FrameFlowActivity.EXTRA_CHARGE_INTENT_JSON)
|
|
125
|
+
if (json != null) {
|
|
126
|
+
try {
|
|
127
|
+
val obj = JSONObject(json)
|
|
128
|
+
val map = jsonObjectToWritableMap(obj)
|
|
129
|
+
promise.resolve(map)
|
|
130
|
+
} catch (e: Exception) {
|
|
131
|
+
promise.reject("PARSE_ERROR", e.message, e)
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
promise.reject("NO_RESULT", "No charge intent in result", null)
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
promise.reject("USER_CANCELED", "User cancelled", null)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private fun jsonObjectToWritableMap(obj: JSONObject): WritableNativeMap {
|
|
142
|
+
val map = WritableNativeMap()
|
|
143
|
+
for (key in obj.keys()) {
|
|
144
|
+
if (obj.isNull(key)) continue
|
|
145
|
+
when (val v = obj.get(key)) {
|
|
146
|
+
is String -> map.putString(key, v)
|
|
147
|
+
is Int -> map.putInt(key, v)
|
|
148
|
+
is Long -> map.putDouble(key, v.toDouble())
|
|
149
|
+
is Double -> map.putDouble(key, v)
|
|
150
|
+
is Boolean -> map.putBoolean(key, v)
|
|
151
|
+
is JSONObject -> map.putMap(key, jsonObjectToWritableMap(v))
|
|
152
|
+
is JSONArray -> map.putArray(key, jsonArrayToWritableArray(v))
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return map
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private fun jsonArrayToWritableArray(arr: JSONArray): WritableNativeArray {
|
|
159
|
+
val list = WritableNativeArray()
|
|
160
|
+
for (i in 0 until arr.length()) {
|
|
161
|
+
when (val v = arr.get(i)) {
|
|
162
|
+
is String -> list.pushString(v)
|
|
163
|
+
is Int -> list.pushInt(v)
|
|
164
|
+
is Long -> list.pushDouble(v.toDouble())
|
|
165
|
+
is Double -> list.pushDouble(v)
|
|
166
|
+
is Boolean -> list.pushBoolean(v)
|
|
167
|
+
is JSONObject -> list.pushMap(jsonObjectToWritableMap(v))
|
|
168
|
+
is JSONArray -> list.pushArray(jsonArrayToWritableArray(v))
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return list
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
override fun onNewIntent(intent: Intent?) {}
|
|
175
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.framepayments.reactnativeframe
|
|
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
|
+
class FrameSDKPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(FrameSDKModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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 = 'FrameReactNative'
|
|
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
|
+
s.platforms = { :ios => '17.0' }
|
|
13
|
+
s.source = { :git => package['repository']['url'], :tag => "#{s.version}" }
|
|
14
|
+
s.source_files = '**/*.{h,m,mm,swift}'
|
|
15
|
+
s.requires_arc = true
|
|
16
|
+
|
|
17
|
+
s.dependency 'React-Core'
|
|
18
|
+
|
|
19
|
+
# Frame-iOS SDK – must be available to the app. Add via Swift Package Manager in Xcode:
|
|
20
|
+
# File → Add Package Dependencies → https://github.com/Frame-Payments/frame-ios
|
|
21
|
+
# Our native code imports Frame; ensure the app target links Frame-iOS (SPM).
|
|
22
|
+
# If Frame-iOS is provided as a pod in the future, uncomment:
|
|
23
|
+
# s.dependency 'Frame-iOS'
|
|
24
|
+
|
|
25
|
+
install_modules_dependencies(s) if respond_to?(:install_modules_dependencies)
|
|
26
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FrameSDKBridge.m
|
|
3
|
+
// FrameReactNative
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
#import <React/RCTBridgeModule.h>
|
|
7
|
+
|
|
8
|
+
@interface RCT_EXTERN_MODULE(FrameSDK, FrameSDKBridge)
|
|
9
|
+
|
|
10
|
+
RCT_EXTERN_METHOD(initialize:(NSString *)apiKey
|
|
11
|
+
debugMode:(BOOL)debugMode
|
|
12
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
13
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
14
|
+
|
|
15
|
+
RCT_EXTERN_METHOD(presentCheckout:(id)customerId
|
|
16
|
+
amount:(nonnull NSNumber *)amount
|
|
17
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
18
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
19
|
+
|
|
20
|
+
RCT_EXTERN_METHOD(presentCart:(id)customerId
|
|
21
|
+
items:(NSArray *)items
|
|
22
|
+
shippingAmountInCents:(nonnull NSNumber *)shippingAmountInCents
|
|
23
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
24
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
25
|
+
|
|
26
|
+
@end
|