@juspay-tech/react-native-hyperswitch-paypal 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.
Files changed (31) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +33 -0
  3. package/ReactNativeHyperswitchPaypal.podspec +27 -0
  4. package/android/build.gradle +69 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +17 -0
  7. package/android/src/main/java/com/juspaytech/reactnativehyperswitchpaypal/PayPalPendingResult.kt +31 -0
  8. package/android/src/main/java/com/juspaytech/reactnativehyperswitchpaypal/PayPalRedirectActivity.kt +165 -0
  9. package/android/src/main/java/com/juspaytech/reactnativehyperswitchpaypal/PaypalButtonView.kt +56 -0
  10. package/android/src/main/java/com/juspaytech/reactnativehyperswitchpaypal/PaypalButtonViewManager.kt +56 -0
  11. package/android/src/main/java/com/juspaytech/reactnativehyperswitchpaypal/ReactNativeHyperswitchPaypalModule.kt +101 -0
  12. package/android/src/main/java/com/juspaytech/reactnativehyperswitchpaypal/ReactNativeHyperswitchPaypalPackage.kt +17 -0
  13. package/ios/PaypalButtonView.swift +118 -0
  14. package/ios/PaypalButtonViewManager.swift +13 -0
  15. package/ios/ReactNativeHyperswitchPaypal-Bridging-Header.h +2 -0
  16. package/ios/ReactNativeHyperswitchPaypal.mm +17 -0
  17. package/ios/ReactNativeHyperswitchPaypal.swift +92 -0
  18. package/lib/module/PaypalButtonNativeComponent.android.js +5 -0
  19. package/lib/module/PaypalButtonNativeComponent.android.js.map +1 -0
  20. package/lib/module/index.js +18 -0
  21. package/lib/module/index.js.map +1 -0
  22. package/lib/module/package.json +1 -0
  23. package/lib/typescript/package.json +1 -0
  24. package/lib/typescript/src/PaypalButtonNativeComponent.android.d.ts +9 -0
  25. package/lib/typescript/src/PaypalButtonNativeComponent.android.d.ts.map +1 -0
  26. package/lib/typescript/src/index.d.ts +16 -0
  27. package/lib/typescript/src/index.d.ts.map +1 -0
  28. package/package.json +154 -0
  29. package/react-native.config.js +11 -0
  30. package/src/PaypalButtonNativeComponent.android.ts +9 -0
  31. package/src/index.tsx +32 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hyperswitch
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.
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @juspay-tech/react-native-hyperswitch-paypal
2
+
3
+ react native hyperswitch paypal
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @juspay-tech/react-native-hyperswitch-paypal
9
+ ```
10
+
11
+ ## Usage
12
+
13
+
14
+ ```js
15
+ import { multiply } from '@juspay-tech/react-native-hyperswitch-paypal';
16
+
17
+ // ...
18
+
19
+ const result = await multiply(3, 7);
20
+ ```
21
+
22
+
23
+ ## Contributing
24
+
25
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
26
+
27
+ ## License
28
+
29
+ MIT
30
+
31
+ ---
32
+
33
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,27 @@
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 = "ReactNativeHyperswitchPaypal"
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://github.com/juspay/react-native-hyperswitch.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
+
26
+ s.dependency "PayPal", "~> 2.0"
27
+ end
@@ -0,0 +1,69 @@
1
+ buildscript {
2
+ ext.getExtOrDefault = {name ->
3
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeHyperswitchPaypal_' + 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["ReactNativeHyperswitchPaypal_" + name]).toInteger()
25
+ }
26
+
27
+ android {
28
+ namespace "com.juspaytech.reactnativehyperswitchpaypal"
29
+
30
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
31
+
32
+ defaultConfig {
33
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
34
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
35
+ }
36
+
37
+ buildTypes {
38
+ release {
39
+ minifyEnabled false
40
+ }
41
+ }
42
+
43
+ lintOptions {
44
+ disable "GradleCompatible"
45
+ }
46
+
47
+ compileOptions {
48
+ sourceCompatibility JavaVersion.VERSION_17
49
+ targetCompatibility JavaVersion.VERSION_17
50
+ }
51
+
52
+ kotlinOptions {
53
+ jvmTarget = '17'
54
+ }
55
+ }
56
+
57
+ repositories {
58
+ mavenCentral()
59
+ google()
60
+ }
61
+
62
+ def kotlin_version = getExtOrDefault("kotlinVersion")
63
+
64
+ dependencies {
65
+ implementation "com.facebook.react:react-android"
66
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
67
+ implementation "com.paypal.android:paypal-web-payments:2.3.0"
68
+ implementation "com.paypal.android:payment-buttons:2.3.0"
69
+ }
@@ -0,0 +1,5 @@
1
+ ReactNativeHyperswitchPaypal_kotlinVersion=2.0.21
2
+ ReactNativeHyperswitchPaypal_minSdkVersion=24
3
+ ReactNativeHyperswitchPaypal_targetSdkVersion=34
4
+ ReactNativeHyperswitchPaypal_compileSdkVersion=35
5
+ ReactNativeHyperswitchPaypal_ndkVersion=27.1.12297006
@@ -0,0 +1,17 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+
3
+ <application>
4
+ <activity
5
+ android:name=".PayPalRedirectActivity"
6
+ android:exported="true"
7
+ android:launchMode="singleTop"
8
+ android:theme="@android:style/Theme.Translucent.NoTitleBar">
9
+ <intent-filter>
10
+ <action android:name="android.intent.action.VIEW" />
11
+ <category android:name="android.intent.category.DEFAULT" />
12
+ <category android:name="android.intent.category.BROWSABLE" />
13
+ <data android:scheme="${applicationId}.paypal" />
14
+ </intent-filter>
15
+ </activity>
16
+ </application>
17
+ </manifest>
@@ -0,0 +1,31 @@
1
+ package com.juspaytech.reactnativehyperswitchpaypal
2
+
3
+ object PayPalPendingResult {
4
+ @Volatile
5
+ private var resultCallback: PayPalResultCallback? = null
6
+
7
+ interface PayPalResultCallback {
8
+ fun onSuccess(orderId: String, payerId: String)
9
+ fun onCancelled()
10
+ fun onFailure(errorMessage: String, errorCode: String?)
11
+ }
12
+
13
+ fun setCallback(callback: PayPalResultCallback?) {
14
+ this.resultCallback = callback
15
+ }
16
+
17
+ fun notifySuccess(orderId: String, payerId: String) {
18
+ resultCallback?.onSuccess(orderId, payerId)
19
+ resultCallback = null
20
+ }
21
+
22
+ fun notifyCancelled() {
23
+ resultCallback?.onCancelled()
24
+ resultCallback = null
25
+ }
26
+
27
+ fun notifyFailure(errorMessage: String, errorCode: String?) {
28
+ resultCallback?.onFailure(errorMessage, errorCode)
29
+ resultCallback = null
30
+ }
31
+ }
@@ -0,0 +1,165 @@
1
+ package com.juspaytech.reactnativehyperswitchpaypal
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import android.graphics.Color
6
+ import android.os.Bundle
7
+ import android.os.Handler
8
+ import android.os.Looper
9
+ import android.util.Log
10
+ import android.view.WindowManager
11
+ import com.paypal.android.corepayments.CoreConfig
12
+ import com.paypal.android.corepayments.Environment
13
+ import com.paypal.android.paypalwebpayments.PayPalPresentAuthChallengeResult
14
+ import com.paypal.android.paypalwebpayments.PayPalWebCheckoutClient
15
+ import com.paypal.android.paypalwebpayments.PayPalWebCheckoutFinishStartResult
16
+ import com.paypal.android.paypalwebpayments.PayPalWebCheckoutFundingSource
17
+ import com.paypal.android.paypalwebpayments.PayPalWebCheckoutRequest
18
+
19
+ class PayPalRedirectActivity : Activity() {
20
+
21
+ private var payPalClient: PayPalWebCheckoutClient? = null
22
+ private var callbackInvoked = false
23
+ private var browserLaunched = false
24
+
25
+ override fun onCreate(savedInstanceState: Bundle?) {
26
+ // Skip enter animation
27
+ overridePendingTransition(0, 0)
28
+
29
+ // Make window completely invisible
30
+ window.setFlags(
31
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
32
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
33
+ )
34
+ window.statusBarColor = Color.TRANSPARENT
35
+ window.navigationBarColor = Color.TRANSPARENT
36
+
37
+ super.onCreate(savedInstanceState)
38
+
39
+ val params = intent?.extras ?: run {
40
+ Log.e(TAG, "No parameters provided")
41
+ finish()
42
+ return
43
+ }
44
+
45
+ val clientId = params.getString("clientId") ?: run {
46
+ notifyFailure("Missing clientId", null)
47
+ return
48
+ }
49
+ val orderId = params.getString("orderId") ?: run {
50
+ notifyFailure("Missing orderId", null)
51
+ return
52
+ }
53
+ val environmentStr = params.getString("environment", "SANDBOX")
54
+ val returnUrl = params.getString("returnUrl", "${packageName}.paypal")
55
+ val fundingSourceStr = params.getString("fundingSource", "PAYPAL")
56
+
57
+ val environment = if (environmentStr == "PRODUCTION") Environment.LIVE else Environment.SANDBOX
58
+ val fundingSource = when (fundingSourceStr) {
59
+ "PAY_LATER" -> PayPalWebCheckoutFundingSource.PAY_LATER
60
+ "PAYPAL_CREDIT" -> PayPalWebCheckoutFundingSource.PAYPAL_CREDIT
61
+ else -> PayPalWebCheckoutFundingSource.PAYPAL
62
+ }
63
+
64
+ val config = CoreConfig(clientId, environment = environment)
65
+ payPalClient = PayPalWebCheckoutClient(this, config, returnUrl)
66
+
67
+ val request = PayPalWebCheckoutRequest(orderId, fundingSource = fundingSource)
68
+
69
+ payPalClient?.start(this, request) { startResult ->
70
+ when (startResult) {
71
+ is PayPalPresentAuthChallengeResult.Success -> {
72
+ browserLaunched = true
73
+ }
74
+ is PayPalPresentAuthChallengeResult.Failure -> {
75
+ Log.e(TAG, "PayPal start failed: ${startResult.error.errorDescription}")
76
+ notifyFailure(
77
+ startResult.error.errorDescription ?: "Failed to start PayPal",
78
+ startResult.error.code?.toString()
79
+ )
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ override fun finish() {
86
+ super.finish()
87
+ // Skip exit animation
88
+ overridePendingTransition(0, 0)
89
+ }
90
+
91
+ override fun onNewIntent(intent: Intent?) {
92
+ super.onNewIntent(intent)
93
+
94
+ if (callbackInvoked) return
95
+
96
+ intent?.let {
97
+ val result = payPalClient?.finishStart(it)
98
+ handleFinishResult(result)
99
+ }
100
+ }
101
+
102
+ override fun onResume() {
103
+ super.onResume()
104
+
105
+ // If browser was launched but no deep link received (callbackInvoked is false),
106
+ // user cancelled by closing the browser
107
+ if (browserLaunched && !callbackInvoked) {
108
+ // Small delay to ensure onNewIntent didn't just fire
109
+ Handler(Looper.getMainLooper()).postDelayed({
110
+ if (!callbackInvoked) {
111
+ notifyCancelled()
112
+ }
113
+ }, 300)
114
+ }
115
+ }
116
+
117
+ private fun handleFinishResult(result: PayPalWebCheckoutFinishStartResult?) {
118
+ when (result) {
119
+ is PayPalWebCheckoutFinishStartResult.Success -> {
120
+ notifySuccess(result.orderId ?: "", result.payerId ?: "")
121
+ }
122
+ is PayPalWebCheckoutFinishStartResult.Failure -> {
123
+ Log.e(TAG, "PayPal failure - error: ${result.error.errorDescription}")
124
+ notifyFailure(
125
+ result.error.errorDescription ?: result.error.message ?: "Unknown PayPal error",
126
+ result.error.code?.toString()
127
+ )
128
+ }
129
+ is PayPalWebCheckoutFinishStartResult.Canceled -> {
130
+ notifyCancelled()
131
+ }
132
+ PayPalWebCheckoutFinishStartResult.NoResult -> {
133
+ Log.d(TAG, "NoResult from finishStart")
134
+ }
135
+ null -> {
136
+ Log.d(TAG, "finishStart returned null")
137
+ }
138
+ }
139
+ }
140
+
141
+ private fun notifySuccess(orderId: String, payerId: String) {
142
+ if (callbackInvoked) return
143
+ callbackInvoked = true
144
+ PayPalPendingResult.notifySuccess(orderId, payerId)
145
+ finish()
146
+ }
147
+
148
+ private fun notifyCancelled() {
149
+ if (callbackInvoked) return
150
+ callbackInvoked = true
151
+ PayPalPendingResult.notifyCancelled()
152
+ finish()
153
+ }
154
+
155
+ private fun notifyFailure(errorMessage: String, errorCode: String?) {
156
+ if (callbackInvoked) return
157
+ callbackInvoked = true
158
+ PayPalPendingResult.notifyFailure(errorMessage, errorCode)
159
+ finish()
160
+ }
161
+
162
+ companion object {
163
+ private const val TAG = "PayPalRedirect"
164
+ }
165
+ }
@@ -0,0 +1,56 @@
1
+ package com.juspaytech.reactnativehyperswitchpaypal
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.util.Log
5
+ import android.view.View
6
+ import android.widget.FrameLayout
7
+ import com.facebook.react.uimanager.ThemedReactContext
8
+ import com.paypal.android.paymentbuttons.PayPalButton
9
+ import com.paypal.android.paymentbuttons.PayPalButtonColor
10
+ import com.paypal.android.paymentbuttons.PayPalButtonLabel
11
+
12
+ @SuppressLint("ViewConstructor")
13
+ class PaypalButtonView(private val context: ThemedReactContext) : FrameLayout(context) {
14
+
15
+ var buttonColor: PayPalButtonColor = PayPalButtonColor.GOLD
16
+ var buttonLabel: PayPalButtonLabel = PayPalButtonLabel.PAYPAL
17
+ var customCornerRadius: Float = 10.0F
18
+ private var button: PayPalButton? = null
19
+
20
+ fun addButton() {
21
+ if (button != null) {
22
+ removeView(button)
23
+ }
24
+ button = initializePayPalButton()
25
+ addView(button)
26
+ viewTreeObserver.addOnGlobalLayoutListener { requestLayout() }
27
+ }
28
+
29
+ private fun initializePayPalButton(): PayPalButton {
30
+ val payPalButton = PayPalButton(context)
31
+
32
+ payPalButton.color = buttonColor
33
+ payPalButton.label = buttonLabel
34
+ payPalButton.customCornerRadius = customCornerRadius
35
+ payPalButton.setOnClickListener {
36
+ (this.parent as? View)?.performClick() ?: run {
37
+ Log.e("PaypalButtonView", "Unable to find parent of PaypalButtonView.")
38
+ }
39
+ }
40
+
41
+ return payPalButton
42
+ }
43
+
44
+ override fun requestLayout() {
45
+ super.requestLayout()
46
+ post(mLayoutRunnable)
47
+ }
48
+
49
+ private val mLayoutRunnable = Runnable {
50
+ measure(
51
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
52
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
53
+ )
54
+ layout(left, top, right, bottom)
55
+ }
56
+ }
@@ -0,0 +1,56 @@
1
+ package com.juspaytech.reactnativehyperswitchpaypal
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.SimpleViewManager
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+ import com.facebook.react.uimanager.annotations.ReactProp
7
+ import com.paypal.android.paymentbuttons.PayPalButtonColor
8
+ import com.paypal.android.paymentbuttons.PayPalButtonLabel
9
+
10
+ @ReactModule(name = PaypalButtonViewManager.NAME)
11
+ class PaypalButtonViewManager : SimpleViewManager<PaypalButtonView>() {
12
+
13
+ override fun getName(): String {
14
+ return NAME
15
+ }
16
+
17
+ public override fun createViewInstance(context: ThemedReactContext): PaypalButtonView {
18
+ return PaypalButtonView(context)
19
+ }
20
+
21
+ public override fun onAfterUpdateTransaction(view: PaypalButtonView) {
22
+ super.onAfterUpdateTransaction(view)
23
+ view.addButton()
24
+ }
25
+
26
+ @ReactProp(name = "buttonColor")
27
+ fun setButtonColor(view: PaypalButtonView, value: String?) {
28
+ view.buttonColor = when (value) {
29
+ "GOLD" -> PayPalButtonColor.GOLD
30
+ "BLUE" -> PayPalButtonColor.BLUE
31
+ "SILVER" -> PayPalButtonColor.SILVER
32
+ "WHITE" -> PayPalButtonColor.WHITE
33
+ "BLACK" -> PayPalButtonColor.BLACK
34
+ else -> PayPalButtonColor.GOLD
35
+ }
36
+ }
37
+
38
+ @ReactProp(name = "buttonLabel")
39
+ fun setButtonLabel(view: PaypalButtonView, value: String?) {
40
+ view.buttonLabel = when (value) {
41
+ "CHECKOUT" -> PayPalButtonLabel.CHECKOUT
42
+ "BUY_NOW" -> PayPalButtonLabel.BUY_NOW
43
+ "PAY" -> PayPalButtonLabel.PAY
44
+ else -> PayPalButtonLabel.PAYPAL
45
+ }
46
+ }
47
+
48
+ @ReactProp(name = "borderRadius", defaultDouble = 0.0)
49
+ fun setBorderRadius(view: PaypalButtonView, value: Double) {
50
+ view.customCornerRadius = value.toFloat()
51
+ }
52
+
53
+ companion object {
54
+ const val NAME = "PaypalButton"
55
+ }
56
+ }
@@ -0,0 +1,101 @@
1
+ package com.juspaytech.reactnativehyperswitchpaypal
2
+
3
+ import android.content.Intent
4
+ import android.os.Handler
5
+ import android.os.Looper
6
+ import android.util.Log
7
+ import com.facebook.react.bridge.Callback
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
10
+ import com.facebook.react.bridge.ReactMethod
11
+ import com.facebook.react.bridge.WritableNativeMap
12
+ import org.json.JSONObject
13
+
14
+ class ReactNativeHyperswitchPaypalModule(reactContext: ReactApplicationContext) :
15
+ ReactContextBaseJavaModule(reactContext),
16
+ PayPalPendingResult.PayPalResultCallback {
17
+
18
+ private var currentCallback: Callback? = null
19
+ private val mainHandler = Handler(Looper.getMainLooper())
20
+
21
+ override fun getName(): String = NAME
22
+
23
+ @ReactMethod
24
+ fun launchPayPal(requestObj: String, callback: Callback) {
25
+
26
+ try {
27
+ val json = JSONObject(requestObj)
28
+ val clientId = json.getString("clientId")
29
+ val environment = json.optString("environment", "SANDBOX")
30
+ val orderId = json.getString("orderId")
31
+ val returnUrl = json.optString("returnUrl", "").ifEmpty {
32
+ "${reactApplicationContext.packageName}.paypal"
33
+ }
34
+ val fundingSource = json.optString("fundingSource", "PAYPAL")
35
+
36
+ currentCallback = callback
37
+
38
+ // Register for result callback
39
+ PayPalPendingResult.setCallback(this)
40
+
41
+ // Launch PayPalRedirectActivity with parameters
42
+ val intent = Intent(reactApplicationContext, PayPalRedirectActivity::class.java).apply {
43
+ putExtra("clientId", clientId)
44
+ putExtra("environment", environment)
45
+ putExtra("orderId", orderId)
46
+ putExtra("returnUrl", returnUrl)
47
+ putExtra("fundingSource", fundingSource)
48
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
49
+ }
50
+ reactApplicationContext.startActivity(intent)
51
+
52
+ } catch (e: Exception) {
53
+ Log.e(TAG, "Exception in launchPayPal: ${e.message}", e)
54
+ invokeCallbackWithError(e.message ?: "Failed to launch PayPal checkout")
55
+ }
56
+ }
57
+
58
+ // Called from PayPalRedirectActivity on success
59
+ override fun onSuccess(orderId: String, payerId: String) {
60
+ mainHandler.post {
61
+ val map = WritableNativeMap()
62
+ map.putString("status", "success")
63
+ map.putString("orderId", orderId)
64
+ map.putString("payerId", payerId)
65
+ currentCallback?.invoke(map)
66
+ currentCallback = null
67
+ }
68
+ }
69
+
70
+ // Called from PayPalRedirectActivity on cancel
71
+ override fun onCancelled() {
72
+ mainHandler.post {
73
+ val map = WritableNativeMap()
74
+ map.putString("status", "cancelled")
75
+ currentCallback?.invoke(map)
76
+ currentCallback = null
77
+ }
78
+ }
79
+
80
+ // Called from PayPalRedirectActivity on failure
81
+ override fun onFailure(errorMessage: String, errorCode: String?) {
82
+ mainHandler.post {
83
+ Log.e(TAG, "PayPal failure: $errorMessage")
84
+ invokeCallbackWithError(errorMessage)
85
+ }
86
+ }
87
+
88
+ private fun invokeCallbackWithError(errorMessage: String) {
89
+ Log.e(TAG, "PayPal error: $errorMessage")
90
+ val map = WritableNativeMap()
91
+ map.putString("status", "failed")
92
+ map.putString("error_message", errorMessage)
93
+ currentCallback?.invoke(map)
94
+ currentCallback = null
95
+ }
96
+
97
+ companion object {
98
+ const val NAME = "HyperswitchPaypal"
99
+ private const val TAG = "HyperswitchPaypal"
100
+ }
101
+ }
@@ -0,0 +1,17 @@
1
+ package com.juspaytech.reactnativehyperswitchpaypal
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.ReactPackage
5
+ import com.facebook.react.bridge.NativeModule
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.uimanager.ViewManager
8
+
9
+ class ReactNativeHyperswitchPaypalPackage : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return listOf(ReactNativeHyperswitchPaypalModule(reactContext))
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return listOf(PaypalButtonViewManager())
16
+ }
17
+ }
@@ -0,0 +1,118 @@
1
+ import UIKit
2
+ import PayPal
3
+
4
+ class PaypalButtonView: UIView {
5
+
6
+ private var payPalButton: PayPalButton?
7
+ private var containerView: UIView?
8
+
9
+ @objc dynamic var buttonColor: String = "gold" {
10
+ didSet { updateButton() }
11
+ }
12
+
13
+ @objc dynamic var buttonLabel: String = "paypal" {
14
+ didSet { updateButton() }
15
+ }
16
+
17
+ @objc dynamic var borderRadius: Double = 0 {
18
+ didSet { updateButton() }
19
+ }
20
+
21
+ override init(frame: CGRect) {
22
+ super.init(frame: frame)
23
+ setupButton()
24
+ }
25
+
26
+ required init?(coder: NSCoder) {
27
+ super.init(coder: coder)
28
+ setupButton()
29
+ }
30
+
31
+ private func setupButton() {
32
+ if let existingButton = payPalButton {
33
+ existingButton.removeFromSuperview()
34
+ }
35
+
36
+ let color = mapColor(buttonColor)
37
+ let label = mapLabel(buttonLabel)
38
+ let edges = mapEdges(borderRadius)
39
+
40
+ let button = PayPalButton(
41
+ color: color,
42
+ edges: edges,
43
+ size: .collapsed,
44
+ label: label
45
+ )
46
+ button.translatesAutoresizingMaskIntoConstraints = false
47
+ button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
48
+
49
+ payPalButton = button
50
+
51
+ let container = UIView()
52
+ container.translatesAutoresizingMaskIntoConstraints = false
53
+ container.addSubview(button)
54
+
55
+ NSLayoutConstraint.activate([
56
+ button.topAnchor.constraint(equalTo: container.topAnchor),
57
+ button.bottomAnchor.constraint(equalTo: container.bottomAnchor),
58
+ button.leadingAnchor.constraint(equalTo: container.leadingAnchor),
59
+ button.trailingAnchor.constraint(equalTo: container.trailingAnchor),
60
+ ])
61
+
62
+ addSubview(container)
63
+ containerView = container
64
+
65
+ NSLayoutConstraint.activate([
66
+ container.topAnchor.constraint(equalTo: topAnchor),
67
+ container.bottomAnchor.constraint(equalTo: bottomAnchor),
68
+ container.leadingAnchor.constraint(equalTo: leadingAnchor),
69
+ container.trailingAnchor.constraint(equalTo: trailingAnchor),
70
+ ])
71
+ }
72
+
73
+ private func updateButton() {
74
+ setupButton()
75
+ setNeedsLayout()
76
+ }
77
+
78
+ private func mapColor(_ value: String) -> PayPalButton.Color {
79
+ switch value.lowercased() {
80
+ case "blue": return .blue
81
+ case "silver": return .silver
82
+ case "white": return .white
83
+ case "black": return .black
84
+ default: return .gold
85
+ }
86
+ }
87
+
88
+ private func mapLabel(_ value: String) -> PayPalButton.Label? {
89
+ switch value.lowercased() {
90
+ case "checkout": return .checkout
91
+ case "buynow": return .buyNow
92
+ case "pay": return .payWith
93
+ default: return nil
94
+ }
95
+ }
96
+
97
+ private func mapEdges(_ radius: Double) -> PaymentButtonEdges {
98
+ if radius <= 0 {
99
+ return .hardEdges
100
+ } else {
101
+ return .custom(CGFloat(radius))
102
+ }
103
+ }
104
+
105
+ @objc private func buttonTapped() {
106
+ guard let reactView = superview else { return }
107
+ reactView.reactSubviews().first?.perform(#selector(UIView.didMoveToWindow))
108
+ }
109
+
110
+ override var intrinsicContentSize: CGSize {
111
+ return CGSize(width: UIView.noIntrinsicMetric, height: 48)
112
+ }
113
+
114
+ override func layoutSubviews() {
115
+ super.layoutSubviews()
116
+ payPalButton?.layoutSubviews()
117
+ }
118
+ }
@@ -0,0 +1,13 @@
1
+ import PayPal
2
+
3
+ @objc(PaypalButton)
4
+ class PaypalButtonViewManager: RCTViewManager {
5
+
6
+ override func view() -> PaypalButtonView {
7
+ return PaypalButtonView()
8
+ }
9
+
10
+ @objc override static func requiresMainQueueSetup() -> Bool {
11
+ return true
12
+ }
13
+ }
@@ -0,0 +1,2 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTViewManager.h>
@@ -0,0 +1,17 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTViewManager.h>
3
+
4
+ @interface RCT_EXTERN_MODULE(HyperswitchPaypal, NSObject)
5
+
6
+ RCT_EXTERN_METHOD(launchPayPal:(NSString *)requestObj
7
+ callback:(RCTResponseSenderBlock)callback)
8
+
9
+ @end
10
+
11
+ @interface RCT_EXTERN_MODULE(PaypalButton, RCTViewManager)
12
+
13
+ RCT_EXPORT_VIEW_PROPERTY(buttonColor, NSString)
14
+ RCT_EXPORT_VIEW_PROPERTY(buttonLabel, NSString)
15
+ RCT_EXPORT_VIEW_PROPERTY(borderRadius, double)
16
+
17
+ @end
@@ -0,0 +1,92 @@
1
+ import PayPal
2
+
3
+ @objc(HyperswitchPaypal)
4
+ class ReactNativeHyperswitchPaypal: NSObject {
5
+
6
+ private static let TAG = "HyperswitchPaypal"
7
+
8
+ @objc
9
+ static func requiresMainQueueSetup() -> Bool {
10
+ return true
11
+ }
12
+
13
+ @objc(launchPayPal:callback:)
14
+ func launchPayPal(_ requestObj: String, callback: @escaping RCTResponseSenderBlock) {
15
+
16
+ guard let data = requestObj.data(using: .utf8),
17
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
18
+ let error: [String: Any] = [
19
+ "status": "failed",
20
+ "error_message": "Failed to parse request JSON"
21
+ ]
22
+ callback([error])
23
+ return
24
+ }
25
+
26
+ guard let clientId = json["clientId"] as? String else {
27
+ let error: [String: Any] = [
28
+ "status": "failed",
29
+ "error_message": "Missing clientId"
30
+ ]
31
+ callback([error])
32
+ return
33
+ }
34
+
35
+ guard let orderId = json["orderId"] as? String else {
36
+ let error: [String: Any] = [
37
+ "status": "failed",
38
+ "error_message": "Missing orderId"
39
+ ]
40
+ callback([error])
41
+ return
42
+ }
43
+
44
+ let environmentStr = json["environment"] as? String ?? "SANDBOX"
45
+ let fundingSourceStr = json["fundingSource"] as? String ?? "PAYPAL"
46
+
47
+ let environment: Environment = environmentStr == "PRODUCTION" ? .live : .sandbox
48
+
49
+ let fundingSource: PayPalWebCheckoutFundingSource
50
+ switch fundingSourceStr {
51
+ case "PAY_LATER":
52
+ fundingSource = .paylater
53
+ case "PAYPAL_CREDIT":
54
+ fundingSource = .paypalCredit
55
+ default:
56
+ fundingSource = .paypal
57
+ }
58
+
59
+
60
+ let config = CoreConfig(clientID: clientId, environment: environment)
61
+ let payPalClient = PayPalWebCheckoutClient(config: config)
62
+ let request = PayPalWebCheckoutRequest(orderID: orderId, fundingSource: fundingSource)
63
+
64
+ DispatchQueue.main.async {
65
+ payPalClient.start(request: request) { result in
66
+ switch result {
67
+ case .success(let paypalResult):
68
+ let successMap: [String: Any] = [
69
+ "status": "success",
70
+ "orderId": paypalResult.orderID,
71
+ "payerId": paypalResult.payerID
72
+ ]
73
+ callback([successMap])
74
+
75
+ case .failure(let error):
76
+ if PayPalError.isCheckoutCanceled(error) {
77
+ let cancelMap: [String: Any] = [
78
+ "status": "cancelled"
79
+ ]
80
+ callback([cancelMap])
81
+ } else {
82
+ let errorMap: [String: Any] = [
83
+ "status": "failed",
84
+ "error_message": error.localizedDescription
85
+ ]
86
+ callback([errorMap])
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { requireNativeComponent } from 'react-native';
4
+ export default requireNativeComponent('PaypalButton');
5
+ //# sourceMappingURL=PaypalButtonNativeComponent.android.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["requireNativeComponent"],"sourceRoot":"../../src","sources":["PaypalButtonNativeComponent.android.ts"],"mappings":";;AAAA,SAASA,sBAAsB,QAAwB,cAAc;AAQrE,eAAeA,sBAAsB,CAAc,cAAc,CAAC","ignoreList":[]}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ import { NativeModules } from 'react-native';
4
+ const {
5
+ HyperswitchPaypal
6
+ } = NativeModules;
7
+ export const isAvailable = !!HyperswitchPaypal;
8
+ export function launchPayPal(requestObj, callback) {
9
+ if (!HyperswitchPaypal) {
10
+ callback({
11
+ status: 'failed',
12
+ error_message: 'PayPal module not available'
13
+ });
14
+ return;
15
+ }
16
+ return HyperswitchPaypal.launchPayPal(requestObj, callback);
17
+ }
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModules","HyperswitchPaypal","isAvailable","launchPayPal","requestObj","callback","status","error_message"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,QAAQ,cAAc;AAE5C,MAAM;EAAEC;AAAkB,CAAC,GAAGD,aAAa;AAE3C,OAAO,MAAME,WAAW,GAAG,CAAC,CAACD,iBAAiB;AAiB9C,OAAO,SAASE,YAAYA,CAC1BC,UAAkB,EAClBC,QAAwC,EAClC;EAEN,IAAI,CAACJ,iBAAiB,EAAE;IACtBI,QAAQ,CAAC;MAAEC,MAAM,EAAE,QAAQ;MAAEC,aAAa,EAAE;IAA8B,CAAC,CAAC;IAC5E;EACF;EACA,OAAON,iBAAiB,CAACE,YAAY,CAACC,UAAU,EAAEC,QAAQ,CAAC;AAC7D","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,9 @@
1
+ import { type ViewProps } from 'react-native';
2
+ interface NativeProps extends ViewProps {
3
+ buttonColor?: string;
4
+ buttonLabel?: string;
5
+ borderRadius?: number;
6
+ }
7
+ declare const _default: import("react-native").HostComponent<NativeProps>;
8
+ export default _default;
9
+ //# sourceMappingURL=PaypalButtonNativeComponent.android.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaypalButtonNativeComponent.android.d.ts","sourceRoot":"","sources":["../../../src/PaypalButtonNativeComponent.android.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEtE,UAAU,WAAY,SAAQ,SAAS;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;AAED,wBAAmE"}
@@ -0,0 +1,16 @@
1
+ export declare const isAvailable: boolean;
2
+ export type PayPalResult = {
3
+ status: string;
4
+ orderId?: string;
5
+ payerId?: string;
6
+ error_message?: string;
7
+ };
8
+ export type PayPalRequest = {
9
+ clientId: string;
10
+ environment?: 'SANDBOX' | 'PRODUCTION';
11
+ orderId: string;
12
+ returnUrl?: string;
13
+ fundingSource?: 'PAYPAL' | 'PAY_LATER' | 'PAYPAL_CREDIT';
14
+ };
15
+ export declare function launchPayPal(requestObj: string, callback: (result: PayPalResult) => void): void;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,SAAsB,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC;CAC1D,CAAC;AAEF,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GACvC,IAAI,CAON"}
package/package.json ADDED
@@ -0,0 +1,154 @@
1
+ {
2
+ "name": "@juspay-tech/react-native-hyperswitch-paypal",
3
+ "version": "1.0.0",
4
+ "description": "react native hyperswitch paypal",
5
+ "source": "./src/index.tsx",
6
+ "main": "./lib/module/index.js",
7
+ "types": "./lib/typescript/src/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "*.podspec",
22
+ "react-native.config.js",
23
+ "!ios/build",
24
+ "!android/build",
25
+ "!android/gradle",
26
+ "!android/gradlew",
27
+ "!android/gradlew.bat",
28
+ "!android/local.properties",
29
+ "!**/__tests__",
30
+ "!**/__fixtures__",
31
+ "!**/__mocks__",
32
+ "!**/.*"
33
+ ],
34
+ "scripts": {
35
+ "example": "yarn workspace @juspay-tech/react-native-hyperswitch-paypal-example",
36
+ "test": "jest",
37
+ "typecheck": "tsc",
38
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
39
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
40
+ "prepare": "bob build",
41
+ "release": "release-it"
42
+ },
43
+ "keywords": [
44
+ "react-native",
45
+ "ios",
46
+ "android"
47
+ ],
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/juspay/react-native-hyperswitch.git"
51
+ },
52
+ "author": "Hyperswitch <chirag.kv@juspay.in> (https://github.com/ChiragKV-Juspay)",
53
+ "license": "MIT",
54
+ "bugs": {
55
+ "url": "https://github.com/juspay/react-native-hyperswitch/issues"
56
+ },
57
+ "homepage": "https://github.com/juspay/react-native-hyperswitch#readme",
58
+ "publishConfig": {
59
+ "registry": "https://registry.npmjs.org/"
60
+ },
61
+ "devDependencies": {
62
+ "@commitlint/config-conventional": "^19.6.0",
63
+ "@eslint/compat": "^1.2.7",
64
+ "@eslint/eslintrc": "^3.3.0",
65
+ "@eslint/js": "^9.22.0",
66
+ "@evilmartians/lefthook": "^1.5.0",
67
+ "@react-native/eslint-config": "^0.78.0",
68
+ "@release-it/conventional-changelog": "^9.0.2",
69
+ "@types/jest": "^29.5.5",
70
+ "@types/react": "^19.0.0",
71
+ "commitlint": "^19.6.1",
72
+ "del-cli": "^5.1.0",
73
+ "eslint": "^9.22.0",
74
+ "eslint-config-prettier": "^10.1.1",
75
+ "eslint-plugin-prettier": "^5.2.3",
76
+ "jest": "^29.7.0",
77
+ "prettier": "^3.0.3",
78
+ "react": "19.0.0",
79
+ "react-native": "0.78.2",
80
+ "react-native-builder-bob": "^0.40.18",
81
+ "release-it": "^17.10.0",
82
+ "turbo": "^1.10.7",
83
+ "typescript": "^5.2.2"
84
+ },
85
+ "peerDependencies": {
86
+ "react": "*",
87
+ "react-native": "*"
88
+ },
89
+ "workspaces": [
90
+ "example"
91
+ ],
92
+ "packageManager": "yarn@3.6.1",
93
+ "jest": {
94
+ "preset": "react-native",
95
+ "modulePathIgnorePatterns": [
96
+ "<rootDir>/example/node_modules",
97
+ "<rootDir>/lib/"
98
+ ]
99
+ },
100
+ "commitlint": {
101
+ "extends": [
102
+ "@commitlint/config-conventional"
103
+ ]
104
+ },
105
+ "release-it": {
106
+ "git": {
107
+ "commitMessage": "chore: release ${version}",
108
+ "tagName": "v${version}"
109
+ },
110
+ "npm": {
111
+ "publish": true
112
+ },
113
+ "github": {
114
+ "release": true
115
+ },
116
+ "plugins": {
117
+ "@release-it/conventional-changelog": {
118
+ "preset": {
119
+ "name": "angular"
120
+ }
121
+ }
122
+ }
123
+ },
124
+ "prettier": {
125
+ "quoteProps": "consistent",
126
+ "singleQuote": true,
127
+ "tabWidth": 2,
128
+ "trailingComma": "es5",
129
+ "useTabs": false
130
+ },
131
+ "react-native-builder-bob": {
132
+ "source": "src",
133
+ "output": "lib",
134
+ "targets": [
135
+ [
136
+ "module",
137
+ {
138
+ "esm": true
139
+ }
140
+ ],
141
+ [
142
+ "typescript",
143
+ {
144
+ "project": "tsconfig.build.json"
145
+ }
146
+ ]
147
+ ]
148
+ },
149
+ "create-react-native-library": {
150
+ "type": "legacy-module",
151
+ "languages": "kotlin-swift",
152
+ "version": "0.49.8"
153
+ }
154
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ android: {
5
+ sourceDir: './android',
6
+ packageImportPath: 'import com.juspaytech.reactnativehyperswitchpaypal.ReactNativeHyperswitchPaypalPackage;',
7
+ packageInstance: 'new ReactNativeHyperswitchPaypalPackage()',
8
+ },
9
+ },
10
+ },
11
+ };
@@ -0,0 +1,9 @@
1
+ import { requireNativeComponent, type ViewProps } from 'react-native';
2
+
3
+ interface NativeProps extends ViewProps {
4
+ buttonColor?: string;
5
+ buttonLabel?: string;
6
+ borderRadius?: number;
7
+ }
8
+
9
+ export default requireNativeComponent<NativeProps>('PaypalButton');
package/src/index.tsx ADDED
@@ -0,0 +1,32 @@
1
+ import { NativeModules } from 'react-native';
2
+
3
+ const { HyperswitchPaypal } = NativeModules;
4
+
5
+ export const isAvailable = !!HyperswitchPaypal;
6
+
7
+ export type PayPalResult = {
8
+ status: string;
9
+ orderId?: string;
10
+ payerId?: string;
11
+ error_message?: string;
12
+ };
13
+
14
+ export type PayPalRequest = {
15
+ clientId: string;
16
+ environment?: 'SANDBOX' | 'PRODUCTION';
17
+ orderId: string;
18
+ returnUrl?: string;
19
+ fundingSource?: 'PAYPAL' | 'PAY_LATER' | 'PAYPAL_CREDIT';
20
+ };
21
+
22
+ export function launchPayPal(
23
+ requestObj: string,
24
+ callback: (result: PayPalResult) => void
25
+ ): void {
26
+
27
+ if (!HyperswitchPaypal) {
28
+ callback({ status: 'failed', error_message: 'PayPal module not available' });
29
+ return;
30
+ }
31
+ return HyperswitchPaypal.launchPayPal(requestObj, callback);
32
+ }