React Native with VGS Collect
Setting up DyScan
Prerequisite Credentials
The following credentials from Dyneti are necessary to install and use DyScan for React Native with VGS Collect. Please contact us for any missing credentials.
- An NPM token.
- A GitHub token.
- Nexus username and password for Dyneti's Maven repository.
- An API key from Dyneti.
NPM
After getting the access token for Dyneti's NPM repo access, go to your RN project root directory and set it up with the following terminal commands.
If you are using ~/.zshrc:
echo "export DYSCAN_NPM_TOKEN=accessTokenHere" >> ~/.zshrc
source ~/.zshrc
echo "@dyneti:registry=https://registry.npmjs.org/" >> .npmrc
echo "//registry.npmjs.org/:_authToken=\${DYSCAN_NPM_TOKEN}" >> .npmrc
If you are using ~/.bash_profile:
echo "export DYSCAN_NPM_TOKEN=accessTokenHere" >> ~/.bash_profile
source ~/.bash_profile
echo "@dyneti:registry=https://registry.npmjs.org/" >> .npmrc
echo "//registry.npmjs.org/:_authToken=\${DYSCAN_NPM_TOKEN}" >> .npmrc
Verify whether the terminal can see the value of DYSCAN_NPM_TOKEN
echo $DYSCAN_NPM_TOKEN
Check that the .npmrc file has these two lines
@dyneti:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=${DYSCAN_NPM_TOKEN}
Install DyScan and the VGS integration with the following terminal commands
npm install @dyneti/react-native-dyscan @dyneti/react-native-dyscan-vgs @vgs/collect-react-native
Yarn
Configure Yarn to use the token from Dyneti.
yarn config set npmScopes.dyneti.npmRegistryServer "https://registry.npmjs.org/"
yarn config set npmScopes.dyneti.npmAlwaysAuth true
yarn config set npmScopes.dyneti.npmAuthToken $DYSCAN_NPM_TOKEN
The commands above assume the token is set as a DYSCAN_NPM_TOKEN
environment variable. If you prefer to use an environment variable in .yarnrc.yml, use ${DYSCAN_NPM_TOKEN}
.
# .yarnrc.yml
npmScopes:
dyneti:
npmAlwaysAuth: true
npmAuthToken: ${DYSCAN_NPM_TOKEN}
npmRegistryServer: "https://registry.npmjs.org/"
Install DyScan and the VGS integration with Yarn using
yarn add @dyneti/react-native-dyscan @dyneti/react-native-dyscan-vgs @vgs/collect-react-native
Linking for Android
Modify android/build.gradle to include Dyneti's Maven repository.
buildscript {
...
}
allprojects {
repositories {
// Other repositories are here
maven {
credentials {
username = "nexusUsername"
password = "nexusPassword"
}
url "https://nexus.dyneti.com/repository/maven-releases/"
authentication {
basic(BasicAuthentication)
}
}
}
}
If you run into any build issues with Android, please see our React Native guide for troubleshooting steps, or contact us.
Linking for iOS
Add the following line to ios/Podfile under your app's target.
pod 'DyScan', :podspec => '../node_modules/@dyneti/react-native-dyscan/specs/DyScan.podspec'
Then install the pods. Inside the ios/ directory,
pod install # or bundle install && bundle exec pod install
If you have not previously asked for camera permissions in the app, you will need to add the “NSCameraUsageDescription” (Privacy - Camera Usage Description) to your Info.plist file. To do this in Xcode, navigate to Info.plist
. When you hover over any of the fields in the file, a small plus icon should show up next to the field. Click on the plus sign and type in “NSCameraUsageDescription” into the new field. You should set the value to be the string a user sees when they are prompted for the camera permission. For example,
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Used for card scans</string>
<!-- other keys -->
</dict>
</plist>
If you run into any build issues with iOS, please see our React Native guide for troubleshooting steps, or contact us.
Interfacing DyScan
To use DyScan to collect credit card numbers and expiration dates, first create a DyScan client and a VGS collector.
import { createDyScanFormClient } from "@dyneti/react-native-dyscan-vgs";
import { VGSCollect } from "@vgs/collect-react-native";
const client = createDyScanFormClient(
"YOUR_API_KEY_HERE",
// All options are optional
// See below for complete documentation of options
{bgColor: "#0C2341",
bgOpacity: 0.7}
);
const collector = new VGSCollect("YOUR_VGS_VAULT_ID", "sandbox"); // or "production"
Then register the DyScan client and VGS collector with a form component. Dyneti provides DynetiVGSCardInput
and DynetiVGSTextInput
. For example, the following registers the client and the VGS collector with a card number collection component.
<DynetiVGSCardInput
collector={collector}
fieldName="card_number"
dyScanClient={client}
placeholder="4111 1111 1111 1111"
onStateChange={(state: any) => {
console.log("card_number state: ", state);
}}
iconPosition="right"
showCardBrandIcon={true}
showCameraButton={true}
cameraIconHeight={32}
/>
See below for complete documentation of props.
Supported Input Fields
Dyneti's library supports collecting credit card numbers and expiration dates. For credit card numbers, use DynetiVGSCardInput
, and for expiration dates, use DynetiVGSTextInput
and pass "expDate"
to the type
prop.
Camera Icons on Card Input
A pressble camera icon can be shown in the card input text field. Dyneti's default icons follow the user's light or dark theme. You can modify the apperance with the cameraIconWidth
, cameraIconHeight
, and cameraIconStyle
props. A custom icon can also be provided through the customCameraIcon
prop. For example,
const customCameraIcon = require("myCustomCameraIcon.png");
<DynetiVGSCardInput
collector={collector}
fieldName="card_number"
dyScanClient={client}
placeholder="4111 1111 1111 1111"
iconPosition="right"
showCameraButton={true}
customCameraIcon={customCameraIcon}
cameraIconHeight={32}
/>
See below for full prop documentation.
Expiration Date Formats
DyScan's integration with VGS Collect supports expiration dates either in MM/YYYY
or in MM/YY
format. To choose between them, change the mask
prop to DynetiVGSTextInput
. We also recommend updating the placeholder text to avoid confusing end-users. For example, the following component works well for MM/YY
format.
<DynetiVGSTextInput
collector={collector}
fieldName="expiration_date"
dyScanClient={client}
type="expDate"
mask="##/##"
placeholder="MM/YY"
divider="/"
/>
Adapt the below example component for MM/YYYY
format.
<DynetiVGSTextInput
collector={collector}
fieldName="expiration_date"
dyScanClient={client}
type="expDate"
mask="##/####"
placeholder="MM/YYYY"
divider="/"
/>
Example Component
Here's an example adapted from VGS's example but replacing the card number and expiration date with Dyneti's to allow card scanning. It then sends it to the default inbound VGS route. Note that Dyneti's components can be mixed with VGS's.
import {
DynetiVGSCardInput,
DynetiVGSTextInput,
createDyScanFormClient,
} from "@dyneti/react-native-dyscan-vgs";
import {
VGSCVCInput,
VGSCollect,
VGSError,
VGSErrorCode,
VGSTextInput,
VGSTokenizationConfiguration,
} from "@vgs/collect-react-native";
import {
Pressable,
SafeAreaView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from "react-native";
const FormExample = () => {
const collector = new VGSCollect("YOUR_VGS_VAULT_ID", "sandbox");
const handleSubmit = async () => {
try {
const { status, response } = await collector.submit("/post", "POST");
if (response.ok) {
try {
const responseBody = await response.json();
const json = JSON.stringify(responseBody, null, 2);
console.log("Success:", json);
} catch (error) {
console.warn(
"Error parsing response body. Body can be empty or your <vaultId> is wrong!",
error,
);
}
} else {
console.warn(`Server responded with error: ${status}\n${response}`);
}
} catch (error) {
if (error instanceof VGSError) {
switch (error.code) {
case VGSErrorCode.InputDataIsNotValid:
for (const fieldName in error.details) {
console.error(
`Not valid fieldName: ${fieldName}: ${error.details[
fieldName
].join(", ")}`,
);
}
break;
default:
console.error("VGSError:", error.code, error.message);
}
} else {
console.error("Network or unexpected error:", error);
}
}
};
const client = createDyScanFormClient("YOUR_API_KEY_HERE", {
bgColor: "#0C2341",
bgOpacity: 0.7,
});
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
<Text style={styles.title}>DyScan+VGS Collect Example</Text>
<VGSTextInput
collector={collector}
fieldName="card_holder"
type="cardHolderName"
placeholder="Name"
onStateChange={(state: any) => {
console.log("card_holder state: ", state);
}}
containerStyle={styles.inputContainer} // Container-specific styles
textStyle={styles.inputText} // text-specific styles
/>
<DynetiVGSTextInput
collector={collector}
fieldName="expiration_date"
dyScanClient={client}
type="expDate"
mask="##/##"
placeholder="MM/YY"
divider="/"
onStateChange={(state: any) => {
console.log("expiration_date state: ", state);
}}
containerStyle={styles.inputContainer}
textStyle={styles.inputText}
/>
<DynetiVGSCardInput
collector={collector}
fieldName="card_number"
dyScanClient={client}
placeholder="4111 1111 1111 1111"
onStateChange={(state: any) => {
console.log("card_number state: ", state);
}}
iconPosition="right"
showCardBrandIcon={true}
showCameraButton={true}
containerStyle={styles.inputContainer}
textStyle={styles.inputText}
cameraIconHeight={32}
/>
<VGSCVCInput
collector={collector}
fieldName="card_cvc"
placeholder="CVC/CVV"
onStateChange={(state: any) => {
console.log("card_cvc state: ", state);
}}
containerStyle={styles.inputContainer}
textStyle={styles.inputText}
/>
<Pressable
style={[styles.button, styles.buttonOpen]}
onPress={() => client.scanCard()}
>
<Text style={styles.textStyle}>Scan Card</Text>
</Pressable>
<TouchableOpacity
style={[styles.button, { backgroundColor: "blue" }]}
onPress={handleSubmit}
>
<Text style={{ color: "white" }}>Submit</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "white",
},
container: {
flex: 1,
paddingHorizontal: 20,
paddingTop: 40,
},
title: {
fontSize: 20,
marginBottom: 10,
},
inputContainer: {
height: 50,
borderWidth: 2,
borderRadius: 8,
paddingHorizontal: 12,
backgroundColor: "white",
marginBottom: 20,
},
inputText: {
fontSize: 16,
color: "black",
},
button: {
backgroundColor: "blue",
padding: 15,
borderRadius: 8,
alignItems: "center",
margin: 5,
},
buttonOpen: {
backgroundColor: "#F194FF",
},
buttonClose: {
backgroundColor: "#2196F3",
},
textStyle: {
color: "white",
fontWeight: "bold",
textAlign: "center",
},
modalText: {
marginBottom: 15,
textAlign: "center",
},
});
export default FormExample;
API Reference
Also see VGS's documentation.
createFormClient(apiKey, options?)
Use createFormClient
to create a DyScan client of type DyScanFormClient
.
const client = createFormClient("YOUR_API_KEY", {showDynetiLogo: true});
Function parameters
Parameter | Description | Type |
---|---|---|
apiKey | API key provided by Dyneti. | string |
options | Object containing options for configuring the scan | object |
Options reference
Parameter | Description | Type |
---|---|---|
showCorners | Whether to show the scan area outline. | boolean |
cornerThickness | How thick to make the lines outlining the scan area, indicating to the user where to place their credit card. | number |
cornerInactiveColor | The color to use in drawing the outline of the scan region when nothing of interest is detected. | color* |
cornerCompletedColor | The color to use in drawing the outline of the scan region when we have successfully scanned a card. | color* |
bgColor | The color to use to obscure everything outside the scan region in order to draw the user’s attention to the scan region. | color* |
bgOpacity | The opacity to use when obscuring everything outside the scan region, as an integer ranging from 0.0 (transparent) to 1.0 (opaque). | number |
lightTorchWhenDark | Whether the phone will turn on the flashlight if multiple frames have appeared to be too dark. If set to false, torch toggle button will appear. | boolean |
vibrateOnCompletion | Whether the device will vibrate once it has successfully extracted the credit card data from the images provided by the camera. | boolean |
showDynetiLogo | Whether to show the Dyneti logo above the scan region | boolean |
showCardOverlay | Whether to show the overlay displaying a sample card number and expiration date hinting at the user to place their card in the scan region. | boolean |
enableSidewaysScanning | If set to true, vertical cards will be able to be scanned when positioned in the horizontal scan window. Note that enableSidewaysScanning and showRotateButton should not simultaneously be set to true . | boolean |
showHelperText | Whether to show the helper text on screen. | boolean |
helperTextString | What to text to display to the user to guide them in scanning their card. | string |
helperTextColor | The color to use to display the helper text. | color* |
helperTextSize | The font size of the helper text. | number |
helperTextFontFamily | The font family name of the helper text. The font needs to be placed in assets first. | string |
showRotateButton | Whether to show the button which allows the user to rotate the scan region by 90 degrees, facilitating the scanning of vertical cards. | boolean |
helperTextPosition | The preset position of the helper text. Available values: top, center, bottom. Default: bottom | string |
Color Representation
Colors are inputted as hex-encoded strings representing the RGB values with a '#' at the front, the so-called 'HTML Color Codes.' For example, #ff0000
is red. The representation must be exactly 7 characters long; otherwise we will ignore it when trying to convert the strings to the native representations.
Client methods
scanCard()
This can be used to manually start the card scan. For example as part of a button outside the form.
DynetiVGSTextInput
This is the base component for integrating with DyScan. We recommend using it for collecting expiration dates by passing type={"expDate"}
. We recommend using DynetiVGSCardInput
for collecting credit card numbers and VGS's components for all other fields such as CVV or SSN.
Required props
Prop | Description | Type |
---|---|---|
collector | A VGS Collect instance. | VGSCollect |
fieldName | The name of the field. It will be used by the collector when making requests to your VGS vault. | string |
dyScanClient | A DyScan client created by createFormClient . | DyScanFormClient |
Optional props
Prop | Description | Type |
---|---|---|
type | The type of the data to be collected. Both "card" and "expDate" are supported, but we recommend using DynetiVGSCardInput for credit card input. | "card" | "expDate" |
mask | The mask pattern for the input field. We recommend "##/##" for MM/YY expiration dates and "##/####" for MM/YYYY format. | string |
divider | The divider to use when replacing non-mask characters on submission. For example passing mask as "##/##" and divider as "-" will send expiration dates to VGS in MM-YY format. | string |
serializers | A VGSSerializer array for the input field. | VGSSerializer[] |
validationRules | A ValidationRule array for the input field. A default one is inferred from the type prop. | ValidationRule[] |
onStateChange | A callback that is fired when the input field changes. | (state: VGSTextInputState) => void |
placeholder | Placeholder text for the input field. | string |
keyboardType | The keyboard type for the field. By default expiration date fields will receive the numeric keyboard. | "default" | "email-address" | "numeric" | "phone-pad" |
secureTextEntry | Whether the input field should have secure text entry. Defaults to false . | boolean |
autoCorrect | Whether autocorrect should be enabled. Defaults to false . | boolean |
containerStyle | An object style for the container view. | object |
textStyle | An object style for the text input. | object |
tokenization | Tokenization configuration for the field. | false | VGSTokenizationConfiguration |
testId | The test ID for testing purposes. | string |
DynetiVGSCardInput
All props from DynetiVGSTextInput
except for type
can be supplied to DynetiVGSCardInput
which also supports the following additional props.
Required props
Prop | Description | Type |
---|---|---|
collector | A VGS Collect instance | VGSCollect |
dyScanClient | A DyScan client created by createFormClient | DyScanFormClient |
Addtional optional props
Prop | Description | Type |
---|---|---|
iconWidth | The width of the card brand icon. Defaults to 42 . | number |
iconHeight | The height of the card brand icon. Defaults to 24 . | number |
iconPadding | Padding around the card brand icon. Defaults to 8 . | number |
iconPosition | The position of the card brand and camera icon. Defaults to "right" . | "right" | "left" | "none" |
containerHeight | The height of the container view. Defaults to 50 . | number |
iconStyle | Style object for the card brand icon. | object |
showCameraButton | Whether to show a camera button inside the card input text field. | boolean |
customCameraIcon | A custom icon for the camera button. | ImageSourcePropType |
iconGap | The size of the gap between the card brand icon and the camera icon. Defaults to 4 . | number |
cameraIconWidth | The width of the camera button. Defaults to match iconWidth which defaults to 42 . | number |
cameraIconHeight | The height of the camera button. Defaults to match iconHeight which defaults to 24 . | number |
cameraIconStyle | Style object for the card brand icon. | object |