Khipu for React Native
npm install react-native-khipu| React Native | 0.74 or later |
compileSdk |
34 or later |
| Kotlin Gradle plugin | 2.0.21 or later |
React Native 0.73 and earlier are not supported. On Android, since August 31, 2026 Google
Play requires new apps and updates to target API 36, and a 0.72 project cannot reach that target:
its Gradle tooling does not run on the versions required to get there. 0.73 is excluded for a
different reason: BaseReactPackage and ReactModuleInfo.classIsTurboModule do not exist in
react-android 0.73, and the only alternative available there is deprecated upstream with a
scheduled removal. If you are below 0.74 you need to upgrade React Native.
Add it to the allprojects section of android/build.gradle. While you are there, check that
compileSdk and the Kotlin plugin version meet the requirements above:
buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 21
compileSdkVersion = 34
targetSdkVersion = 34
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
}
}
allprojects {
repositories {
maven { url = 'https://dev.khipu.com/nexus/content/repositories/khenshin' }
}
}Kotlin 2.0.21 is the floor because the Android SDK (com.khipu:khipu-client-android) is compiled
with it and uses org.jetbrains.kotlin.plugin.compose, which only exists from Kotlin 2.0 onwards.
Below compileSdk 34 the build fails at :app:checkDebugAarMetadata.
Check that android/app/build.gradle applies it:
apply plugin: 'kotlin-android'
Required on React Native 0.74, whose template enables jetifier by default even if you
never turned it on. Add this to android/gradle.properties:
android.jetifier.ignorelist = jackson-core
Without it the build fails at :app:desugarDebugFileDependencies with Unsupported class file major version 63.
Khipu opens the user's banking app when a payment needs reinforced authorization (2FA). Android
only allows that if your app declares which packages it may open, so add a <queries> element as
a direct child of <manifest> in android/app/src/main/AndroidManifest.xml.
For Chile, these are the ones:
<queries>
<package android:name="cl.bci.pass" />
<package android:name="cl.bancochile.mi_pass2" />
<package android:name="net.veritran.becl.prod" />
<package android:name="cl.scotiabank.go" />
<package android:name="cl.santander.santanderpasschile" />
<package android:name="com.konylabs.ItauMobileBank" />
<package android:name="cl.bancosecurity.securitypass" />
<package android:name="cl.bice.bicepassmobile2" />
<package android:name="cl.consorcio.tupass" />
</queries>The iOS equivalent is LSApplicationQueriesSchemes, described below.
You do not declare these, and you do not request them: the AAR declares them and the manifest
merger injects them into your app, so they show up in your merged manifest without you adding
anything. Nothing is requested at startup — the permission screen only appears if the flow reaches
a GEOLOCATION_REQUEST, the payer authorizes it explicitly, and denying it does not block the
payment.
The full behaviour is documented at Permissions Declared by the SDK.
Since 4.0.1, which ships Android SDK 2.28.6, the SDK reads QR codes with ZXing instead of ML Kit.
ML Kit's telemetry used to bring ACCESS_NETWORK_STATE into your merged manifest, and it no
longer does. Neither the SDK nor this module uses it, but if your app or one of its libraries
checks the network state without declaring it, that call throws SecurityException. Declare it in
android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />If you use @react-native-community/netinfo there is nothing to do: it declares the permission in
its own manifest.
Since 4.0.2, which ships Android SDK 2.28.7, the SDK carries its own R8 rules and R8 applies them
to your release build. There is nothing to add to proguard-rules.pro.
On 4.0.1 or older, if your release build runs R8 (enableProguardInReleaseBuilds = true in
android/app/build.gradle), add these rules to android/app/proguard-rules.pro. Without them the
APK builds, but R8 strips the SDK's protocol classes and every payment ends in ERROR right after
it starts:
-keep public class com.khipu.client.**{
public protected *;
}
-keep class com.khipu.khenshin.protocol.** { *; }
Since version 2.15.0 the iOS SDK is resolved with Swift Package Manager instead of CocoaPods,
because the CocoaPods trunk becomes read-only on December 2, 2026. Add this at the top of your
ios/Podfile:
require File.join(
File.dirname(`node --print "require.resolve('react-native-khipu/package.json')"`),
"ios/khipu_spm_fix.rb"
)and inside your post_install, after react_native_post_install:
post_install do |installer|
react_native_post_install(installer, config[:reactNativePath])
khipu_fix_spm_modulemaps(installer)
endThe order matters: react_native_post_install is what runs the broken rewrite this works around.
This helper works around a React Native bug (present in 0.87.1) that prevents any pod whose name contains a hyphen from building when it consumes SPM packages. We will drop it once the fix lands upstream.
Apple has required Xcode 26 and the iOS 26 SDK for all App Store Connect uploads since April 28, 2026, so if you ship your app you are already on it.
You do not need use_frameworks!. Static linking, which is React Native's default, works on
Xcode 26.
On Xcode 16 static linking fails with
duplicate symbol ... KhipuClientIOS.o, because React Native's workaround for a separate Xcode 26 issue makes the pod build into the shared products directory and the object ends up in the archive twice. Xcode 16 can no longer be used for App Store uploads, but if you are on it for some other reason, adduse_frameworks! :linkage => :dynamicto yourPodfile. Both linkage modes are verified on both Xcode versions.
Xcode 27 treats any iOS deployment target below 15.0 as a build error, not a warning. React Native sets that floor itself: it is 15.1 since React Native 0.76, and 13.4 in 0.74 and 0.75.
| React Native | Xcode 27 |
|---|---|
| 0.76 and newer | Builds. The SDK comes through Swift Package Manager, and Xcode raises its package to 15.0 on its own. Measured on 0.87.1. |
| 0.74 and 0.75 | Does not build, with or without this plugin: React Native's own pods are at 13.4. Measured on 0.74.7 (the build stops before compiling anything); 0.75 has the same floor. |
Apple requires Xcode 26 for App Store uploads today; Xcode 27 is not required yet. Measured on Xcode 27.0 (27A266a).
Some React Native versions do not build with recent Xcode 26 releases — with or without this
plugin. These are React Native bugs; we list them because you will hit them while integrating
Khipu and the error messages point nowhere useful. Each was verified by uninstalling
react-native-khipu and confirming the build fails identically.
All the results below were measured on Xcode 26.6. We did not test earlier Xcode 26 releases, so treat the "no" rows as "fails on 26.6" rather than as a claim about every Xcode 26.
| React Native | Builds on Xcode 26.6 | If not, why | Workaround |
|---|---|---|---|
| 0.72 | no | Yoga, YGValue.h |
see below |
| 0.74, 0.75 | yes | — | — |
| 0.76.9 through 0.83.4 | no | fmt, format-inl.h |
see below |
| 0.76.8 and earlier, 0.83.5 and later, 0.84, 0.85, 0.87 | yes | — | — |
We built 0.72, 0.73, 0.74.7, 0.75.5, 0.76.9, 0.80.3, 0.82.1, 0.83.1, 0.83.10, 0.84.1, 0.85.0 and
0.87.1. Rows that cover versions we did not build are inferred from the fmt version each release
ships — see below.
Pods/fmt/include/fmt/format-inl.h: error: call to consteval function
'fmt::basic_format_string<...>' is not a constant expression
Xcode's Clang tightened how it validates C++20 consteval, and the fmt version React Native
vendors does not satisfy it — facebook/react-native#55601,
where it is reported against Xcode 26.4 and later. We measured it on 26.6 and did not test
earlier releases, so if you are on Xcode 26.0–26.3 you may not be affected.
Two things have to be true for this to bite you: the fmt your React Native ships must be
11.0.2, and your build must compile it from source. That gives:
| React Native | fmt |
Compiled from source | Affected |
|---|---|---|---|
| 0.76.8 and earlier | 9.1.0 | yes | no — no consteval |
| 0.76.9 – 0.83.4 | 11.0.2 | yes | yes |
| 0.83.5 and later 0.83.x | 12.1.0 | yes | no |
| 0.84.x | 11.0.2 | no — prebuilt binaries | no |
| 0.85 and later | 12.1.0 | no — prebuilt binaries | no |
The bump to fmt 12.1.0 landed upstream on 2026-03-19 and reached 0.85 and the 0.83 patch line,
but not 0.84, whose last release predates it. 0.84 is fine anyway, for a different reason: from
that version React Native ships its iOS third-party dependencies as prebuilt binaries, so your
build never compiles fmt and the version it declares stops mattering.
The boundaries are exact, not rounded: we read spec.version from each release's
third-party-podspecs/fmt.podspec. fmt 9.1.0 holds through 0.76.8, 11.0.2 starts at
0.76.9, and 12.1.0 starts at 0.83.5 — so 0.83.2, 0.83.3 and 0.83.4 are still affected.
We built and confirmed the failure on 0.76.9, 0.80.3, 0.82.1 and 0.83.1, and a clean build on
0.75.5, 0.83.10, 0.84.1, 0.85.0 and 0.87.1. For the releases in between we read the fmt version
rather than building them, so the range is exact but their build outcome follows from the
mechanism above.
Until you can upgrade, compile fmt as C++17 in your post_install:
installer.pods_project.targets.each do |t|
next unless t.name == 'fmt'
t.build_configurations.each do |c|
c.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
end
endSeveral community guides suggest defining
FMT_CONSTEVAL=instead. That does not work — we verified the define reaches thepbxprojand the build still fails. Use the C++17 setting.
ReactCommon/yoga/yoga/YGValue.h: error: identifier '_pt' preceded by whitespace in a
literal operator declaration is deprecated [-Werror,-Wdeprecated-literal-operator]
Xcode's Clang treats that deprecation as an error. Measured on Xcode 26.6. In your post_install:
installer.pods_project.targets.each do |t|
t.build_configurations.each do |c|
f = c.build_settings['OTHER_CPLUSPLUSFLAGS'] || ['$(inherited)']
f = [f] unless f.is_a?(Array)
f << '-Wno-deprecated-literal-operator' unless f.include?('-Wno-deprecated-literal-operator')
c.build_settings['OTHER_CPLUSPLUSFLAGS'] = f
end
endcd ios
pod install
cd ..So that Khipu can open your customer's banking app, your ios/<YourApp>/Info.plist must declare
these URL schemes. This goes in your app — having them in our example is not enough:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>bancochilemipass2</string>
<string>BciPassApp</string>
<string>BICEPassApp</string>
<string>scotiabankgo</string>
<string>SantanderPassApp</string>
<string>tupass</string>
<string>bancoestado</string>
<string>itau.cl</string>
<string>SecurityPass</string>
</array>spm_dependency does not exist before 0.75, so the plugin falls back to CocoaPods automatically
and you do not need the helper from step 1. It resolves the same KhipuClientIOS version the
plugin pins, from the CocoaPods trunk.
That path has an end date. The CocoaPods trunk becomes read-only on December 2, 2026: SDK versions released after that will only be available through Swift Package Manager, so on React Native 0.74 you would stay on the last one published to the trunk. And React Native 0.74 does not build on Xcode 27 (see Xcode 27 needs React Native 0.76 or newer). To keep receiving SDK updates, upgrade React Native.
Send locale explicitly if you need a deterministic language. The two native SDKs disagree on
the default: on iOS it is hardcoded to es_CL, while on Android it follows the device language. The
same payload without locale can therefore render in different languages on each platform. This is
a difference between the native SDKs, not something the plugin decides.
import {
type KhipuColors,
type KhipuOptions,
type KhipuResult,
startOperation,
} from 'react-native-khipu';
// ...
const result: KhipuResult = await startOperation({
operationId: '<paymentId>',
options: {
title: '<Title to display in the payment process>', // Title for the top bar during the payment process.
titleImageUrl: '<Image to display centered in the topbar>', // Url of the image to display in the top bar.
locale: 'es_CL', // Regional settings for the interface language. The standard format combines an ISO 639-1 language code and an ISO 3166 country code. For example, "es_CL" for Spanish (Chile).
theme: 'light', // The theme of the interface, can be 'dark', 'light' or 'system'
showFooter: true, // If true, a message is displayed at the bottom with the Khipu logo.
showMerchantLogo: true, // If true, the merchant's logo is displayed in the top bar.
showPaymentDetails: true, // If true, the payment code and a link to view the details are displayed.
skipExitPage: false, // If true, skips the exit page at the end of the payment process, whether successful or failed.
skipExitSuccessPage: false, // If true, skips the exit page at the end of the payment process only if it was successful.
colors: {
lightTopBarContainer: '<colorHex>', // Optional background color for the top bar in light mode.
lightOnTopBarContainer : '<colorHex>', // Optional color of the elements on the top bar in light mode.
lightPrimary : '<colorHex>', // Optional primary color in light mode.
lightOnPrimary : '<colorHex>', // Optional color of elements on the primary color in light mode.
lightBackground : '<colorHex>', // Optional general background color in light mode.
lightOnBackground : '<colorHex>', // Optional color of elements on the general background in light mode.
darkTopBarContainer : '<colorHex>', // Optional background color for the top bar in dark mode.
darkOnTopBarContainer : '<colorHex>', // Optional color of the elements on the top bar in dark mode.
darkPrimary : '<colorHex>', // Optional primary color in dark mode.
darkOnPrimary : '<colorHex>', // Optional color of elements on the primary color in dark mode.
darkBackground : '<colorHex>', // Optional general background color in dark mode.
darkOnBackground : '<colorHex>', // Optional color of elements on the general background in dark mode.
} as KhipuColors
} as KhipuOptions
})Optional fields are now declared with ?, so you can just leave out the ones you do not use
instead of passing undefined. The as KhipuOptions / as KhipuColors casts above are no longer
necessary either, though they keep working.
result is now a plain string instead of a fixed union of literals, so your build will not break
the day the backend adds a new value. Checks like result === 'OK' behave exactly as before. The
only code worth revisiting is a variable you had annotated with the old literal union: widen it to
string.
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
Made with create-react-native-library