Skip to content

Commit

Permalink
Merge pull request #670 from plaid/add-financekit-sync-api
Browse files Browse the repository at this point in the history
  • Loading branch information
dtroupe-plaid authored May 25, 2024
2 parents 5a30e53 + f36c70b commit ef7e1af
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 8 deletions.
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
# RELEASES

## LinkKit V12.0.0-beta.2 — 2024-05-24

### React Native

#### Requirements

This SDK now works with any supported version of React Native.

### Android

Android SDK [4.4.0](https://github.com/plaid/plaid-link-android/releases/tag/v4.4.0)

#### Changes
- Support Autofill for SMS OTP in Link Sessions using Google play-services-auth-api-phone library version 18.0.2.
- Change LinkActivity to `exported=false`.

#### Requirements

| Name | Version |
|------|---------|
| Android Studio | 4.0+ |
| Kotlin | 1.8+ |

### iOS

iOS SDK [6.0.0-beta2](https://github.com/plaid/plaid-link-ios/releases/tag/6.0.0-beta2)

#### Changes

- Add Objective-C FinanceKit APIs for React Native.
- Add support for FinanceKit and Apple card.

#### Requirements

| Name | Version |
|------|---------|
| Xcode | >= 15.3.0 |
| iOS | >= 14.0 |

## LinkKit V12.0.0-beta.1 — 2024-05-22

### React Native
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ While these older versions are expected to continue to work without disruption,

| Plaid SDK Version | Min React Native Version | Android SDK | Android Min Version | Android Compile Version| iOS SDK | iOS Min Version | Status |
|-------------------|--------------------------|-------------|---------------------|------------------------|---------|-----------------|-------------------------------|
| 12.0.0-beta.1 | * | [4.4.0+] | 21 | 34 | >=6.0.0 | 14.0 | Active, supports Xcode 15.3.0 |
| 12.0.0-beta.2 | * | [4.4.0+] | 21 | 34 | >=6.0.0 | 14.0 | Active, supports Xcode 15.3.0 |
| 12.0.0-beta.1 | * | [4.4.0+] | 21 | 34 | >=6.0.0 | 14.0 | **Deprecated** |
| 11.10.0 | * | [4.4.0+] | 21 | 34 | >=5.5.0 | 14.0 | Active, supports Xcode 15.0.1 |
| 11.9.0 | * | [4.3.1+] | 21 | 34 | >=5.5.0 | 14.0 | Active, supports Xcode 15.0.1 |
| 11.8.2 | * | [4.3.1+] | 21 | 34 | >=5.4.2 | 14.0 | Active, supports Xcode 15.0.1 |
Expand Down
46 changes: 43 additions & 3 deletions example/src/Screens/PlaidLinkScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {TextInput, Text, TouchableOpacity} from 'react-native';
import {Platform, TextInput, Text, TouchableOpacity} from 'react-native';
import {styles} from '../Styles';

import {
Expand All @@ -12,10 +12,12 @@ import {
usePlaidEmitter,
LinkIOSPresentationStyle,
LinkTokenConfiguration,
FinanceKitError,
create,
open,
syncFinanceKit,
} from 'react-native-plaid-link-sdk';

import {create, open} from 'react-native-plaid-link-sdk/dist/PlaidLink';

function isValidString(str: string): boolean {
if (str && str.trim() !== '') {
return true;
Expand Down Expand Up @@ -67,6 +69,29 @@ export function PlaidLinkScreen() {
const [text, onChangeText] = React.useState('');
const [disabled, setDisabled] = React.useState(true);

const iOSVersionParts = String(Platform.Version).split('.');
const [majorVersion, minorVersion, patchVersion] =
iOSVersionParts.length === 3 ? iOSVersionParts : [null, null, null];

const financeKitText = () => {
if (majorVersion && minorVersion) {
const majorInt = parseInt(majorVersion, 10);
const minorInt = parseInt(minorVersion, 10);

if (majorInt > 17) {
return <Text style={styles.button}>Sync FinanceKit</Text>;
} else if (majorInt === 17 && minorInt >= 4) {
return <Text style={styles.button}>Sync FinanceKit</Text>;
} else {
return (
<Text style={styles.button}>
FinanceKit not supported on this version of iOS
</Text>
);
}
}
};

return (
<>
<TextInput
Expand Down Expand Up @@ -97,6 +122,21 @@ export function PlaidLinkScreen() {
}}>
<Text style={styles.button}>Open Link</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => {
const completionHandler = (error?: FinanceKitError) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Sync completed successfully');
}
};
const requestAuthorizationIfNeeded = true;
syncFinanceKit(text, requestAuthorizationIfNeeded, completionHandler);
}}>
{financeKitText()}
</TouchableOpacity>
</>
);
}
24 changes: 24 additions & 0 deletions ios/RNLinksdk.mm
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,28 @@ - (void)stopObserving {
self.presentingViewController = nil;
}

RCT_EXPORT_METHOD(syncFinanceKit:(NSString *)token
requestAuthorizationIfNeeded:(BOOL)requestAuthorizationIfNeeded
onSuccess:(RCTResponseSenderBlock)onSuccess
onError:(RCTResponseSenderBlock)onError) {

[RNPlaidHelper syncFinanceKit:token
requestAuthorizationIfNeeded:requestAuthorizationIfNeeded
onSuccess:^{
onSuccess(@[]);
}
onError:^(NSError *error) {

NSDictionary *financeKitError = @{
@"type": [NSNumber numberWithInteger: error.code],
@"message": error.localizedDescription
};

onError(@[financeKitError]);
}
];
}

#pragma mark - Bridging

+ (PLKEnvironment)environmentFromString:(NSString *)string {
Expand Down Expand Up @@ -618,6 +640,8 @@ + (NSString *)stringForViewName:(PLKViewName *)viewName {
return @"SELECT_SAVED_INSTITUTION";
case PLKViewNameValueSelectSavedAccount:
return @"SELECT_SAVED_ACCOUNT";
case PLKViewNameValueProfileDataReview:
return @"PROFILE_DATA_REVIEW";
}

return @"unknown";
Expand Down
4 changes: 4 additions & 0 deletions ios/RNPlaidHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@

+ (id <PLKHandler> _Nullable)createWithLinkTokenConfiguration:(PLKLinkTokenConfiguration * _Nonnull)linkTokenConfiguration error:(NSError * _Nullable * _Nullable)error;

+ (void) syncFinanceKit:(NSString *_Nonnull)token
requestAuthorizationIfNeeded:(BOOL)requestAuthorizationIfNeeded
onSuccess:(void (^_Nonnull)(void))onSuccess
onError:(void (^_Nonnull)(NSError * _Nonnull error))onError;
@end
11 changes: 11 additions & 0 deletions ios/RNPlaidHelper.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@ @implementation RNPlaidHelper
return [PLKPlaid createWithLinkTokenConfiguration:linkTokenConfiguration error:error];
}

+ (void)syncFinanceKit:(NSString *)token requestAuthorizationIfNeeded:(BOOL)requestAuthorizationIfNeeded onSuccess:(void (^)(void))onSuccess onError:(void (^)(NSError * _Nonnull))onError {
if (@available(iOS 17.4, *)) {
[PLKPlaid syncFinanceKitWithToken:token requestAuthorizationIfNeeded:requestAuthorizationIfNeeded onSuccess:onSuccess onError:onError];
} else {
NSError *error = [NSError errorWithDomain:@"com.plaid.financeKit"
code:1001
userInfo:@{ NSLocalizedDescriptionKey: @"FinanceKit Requires iOS >= 17.4" }];
onError(error);
}
}

@end
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-plaid-link-sdk",
"version": "12.0.0-beta.1",
"version": "12.0.0-beta.2",
"description": "React Native Plaid Link SDK",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion react-native-plaid-link-sdk.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ Pod::Spec.new do |s|
end

s.dependency 'React-Core'
s.dependency 'Plaid', '~> 6.0.0-beta1'
s.dependency 'Plaid', '~> 6.0.0-beta3'
end
43 changes: 43 additions & 0 deletions src/PlaidLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
LinkTokenConfiguration,
PlaidLinkComponentProps,
PlaidLinkProps,
FinanceKitErrorType,
FinanceKitError,
} from './Types';
import RNLinksdkAndroid from './fabric/NativePlaidLinkModuleAndroid';
import RNLinksdkiOS from './fabric/NativePlaidLinkModuleiOS';
Expand Down Expand Up @@ -100,6 +102,47 @@ export const dismissLink = () => {
}
};

/**
* Function to sync the user's transactions from their Apple card.
*
* @param {string} token - The `LinkToken` your server retrieved from the /link/token/create endpoint from the Plaid API.
* This token must be associated with an accessToken.
* @param {boolean} requestAuthorizationIfNeeded - Indicates if the user should be prompted to authorize the sync if
* they have not already done so.
* @param {function} completion - A callback function that is called when the sync has completed.
*
* @warning This method only works on iOS >= 17.4.
* @warning This method is not supported on Android or MacCatalyst.
* @warning This method can only be used once the user has granted access to their Apple card via a standard Link Session.
* @warning This method requires that your app has been granted FinanceKit access from Apple.
*/
export const syncFinanceKit = (
token: string,
requestAuthorizationIfNeeded: boolean,
completion: (error?: FinanceKitError) => void
): void => {
if (Platform.OS === 'android') {
completion({
type: FinanceKitErrorType.Unknown,
message: "FinanceKit is unavailable on Android!",
})
} else {
RNLinksdkiOS?.syncFinanceKit(
token,
requestAuthorizationIfNeeded,
() => {
completion()
},
(error: FinanceKitError) => {
completion({
type: error.type,
message: error.message,
})
}
)
}
};

/**
* @deprecated This component is deprecated. Create your own component and use the create & open methods.
*/
Expand Down
40 changes: 40 additions & 0 deletions src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,43 @@ export interface LinkOpenProps {
export type PlaidLinkComponentProps = (PlaidLinkProps & {
children: React.ReactNode
});

export enum FinanceKitErrorType {
InvalidToken = 0,
PermissionError = 1,
LinkApiError = 2,
PermissionAccessError = 3,
Unknown = 4
}

interface InvalidTokenError {
type: FinanceKitErrorType.InvalidToken;
message: string;
}

interface PermissionError {
type: FinanceKitErrorType.PermissionError;
message: string;
}

interface LinkApiError {
type: FinanceKitErrorType.LinkApiError;
message: string;
}

interface PermissionAccessError {
type: FinanceKitErrorType.PermissionAccessError;
message: string;
}

interface UnknownError {
type: FinanceKitErrorType.Unknown;
message: string;
}

export type FinanceKitError =
| InvalidTokenError
| PermissionError
| LinkApiError
| PermissionAccessError
| UnknownError;
8 changes: 7 additions & 1 deletion src/fabric/NativePlaidLinkModuleiOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import {TurboModuleRegistry, TurboModule} from 'react-native';
import {Int32} from 'react-native/Libraries/Types/CodegenTypes';
import {UnsafeObject} from './fabricUtils';
import {LinkSuccess, LinkExit, LinkError} from '../Types';
import {LinkSuccess, LinkExit, LinkError, FinanceKitError} from '../Types';

export interface Spec extends TurboModule {
create(token: string, noLoadingState: boolean): void;
Expand All @@ -16,6 +16,12 @@ export interface Spec extends TurboModule {
// those two are here for event emitter methods
addListener(eventName: string): void;
removeListeners(count: Int32): void;
syncFinanceKit(
token: string,
requestAuthorizationIfNeeded: boolean,
onSuccess: (success: void) => void,
onError: (error: FinanceKitError) => void
): void
}

export default TurboModuleRegistry.get<Spec>('RNLinksdk');
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
openLink,
PlaidLink,
create,
open,
dismissLink,
usePlaidEmitter,
PlaidLink,
syncFinanceKit,
} from './PlaidLink';

export * from './Types';
Expand All @@ -18,6 +19,7 @@ export {
open,
dismissLink,
usePlaidEmitter,
syncFinanceKit
};

// Components
Expand Down

0 comments on commit ef7e1af

Please sign in to comment.