用React Native为应用程序添加信用卡扫描功能的教程

329 阅读8分钟

在移动应用程序中,用户经常通过输入他们的信用卡信息来进行购买。我们可能都有过在智能手机上手动输入这16位数字的令人沮丧的经历。

许多应用程序正在增加自动化,以简化这一过程。因此,为了输入支付细节,用户可以给他们的信用卡拍照,或者从他们设备的照片库中上传一张照片。很酷,对吗?

在这篇文章中,我们将学习如何在React Native应用中使用文本识别API实现类似的功能,这是一个基于ML套件的API,可以识别任何基于拉丁文的字符集。我们将使用设备上的文本识别API并涵盖以下内容:

  • 创建一个新的React Native项目
  • 什么是react-native-cardscan?
  • 集成 react-native-text-recognition
  • 编写卡号识别逻辑

你可以在这个GitHub资源库中找到本教程的完整代码。我们最终的用户界面将看起来像下面的gif图。

Final Credit Card Scan UI

Credit Card Scan UI Upload

创建一个新的React Native项目

首先,我们将创建一个新的React Native项目。如果你想在你现有的项目中加入信用卡扫描功能,请随意跳过这一部分。

在你喜欢的文件夹目录下,在终端运行下面的命令,创建一个新的React Native项目。

npx react-native init <project-name> --template react-native-template-typescript

在你喜欢的IDE中打开该项目,用下面的代码替换App.tsx 文件中的代码。

import React from 'react';
import {SafeAreaView, Text} from 'react-native';

const App: React.FC = () => {
  return (
    <SafeAreaView>
      <Text>Credit Card Scanner</Text>
    </SafeAreaView>
  );
};

export default App;

现在,让我们来运行该应用程序。对于iOS,我们需要在构建项目之前安装pods。

cd ios && pod install && cd ..

然后,我们就可以构建iOS项目了。

yarn ios

对于Android,我们可以直接构建项目。

yarn android

上面的步骤将启动Metro服务器以及iOS和Android模拟器,然后在它们上面运行应用程序。目前,用上面的代码在App.tsx ,我们会看到只有一个空白的屏幕,上面的文字是Credit Card Scanner

什么是 react-native-cardscan?

react-native-cardscan是一个围绕CardScan的包装库,一个用于扫描借记卡和信用卡的简约库。 react-native-cardscan为React Native应用中的信用卡扫描提供了简单的即插即用的用法。然而,在写这篇文章的时候,react-native-cardscan已经不再被维护,并被废弃使用了。Stripe正在将react-native-cardscan整合到自己的移动应用的支付解决方案中。你可以在GitHub上查看新的仓库,然而,在写这篇文章的时候,它仍在开发中。

由于这个库已被废弃,我们将用react-native-text-recognition 创建我们自己的自定义信用卡扫描逻辑。

集成 react-native-text-recognition

react-native-text-recognition是一个围绕iOS上的Vision框架和Android上的Firebase ML构建的包装库。如果你要在生产应用中实现卡片扫描,我建议你为文本识别创建自己的本地模块。然而,为了本教程的简单性,我将使用这个库。

让我们写代码来扫描信用卡。在我们集成文本识别之前,让我们添加我们需要的其他辅助库:react-native-vision-camerareact-native-image-crop-picker。我们将使用这些库分别从我们设备的摄像头捕捉照片和从手机图库中挑选图片。

yarn add react-native-image-crop-picker react-native-vision-camera
// Install pods for iOS
cd ios && pod install && cd ..

如果你使用的是React Native ≥v0.69,由于较新架构的变化,react-native-vision-camera将无法构建。请按照本PR中的变化来解决。

现在我们的辅助依赖已经安装完毕,让我们来安装 react-native-text-recognition。

yarn add react-native-text-recognition
pod install

有了我们的依赖,让我们开始写我们的代码。将下面的代码添加到App.tsx ,以实现图像提取功能。

....
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow Access to Photo Library</string>
....

我们还需要权限来访问iOS上用户的照片库。为此,将下面的代码添加到你的iOS项目的info.plist 文件中。

import React from 'react';
import {SafeAreaView, Text, StatusBar, Pressable} from 'react-native';
import ImagePicker, {ImageOrVideo} from 'react-native-image-crop-picker';

const App: React.FC = () => {

  const pickAndRecognize: () => void = useCallback(async () => {
    ImagePicker.openPicker({
      cropping: false,
    })
      .then(async (res: ImageOrVideo) => {
        console.log('res:', res);
      })
      .catch(err => {
        console.log('err:', err);
      });
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle={'dark-content'} />
      <Text style={styles.title}>Credit Card Scanner</Text>
      <Pressable style={styles.galleryBtn} onPress={pickAndRecognize}>
        <Text style={styles.btnText}>Pick from Gallery</Text>
      </Pressable>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 20,
    fontWeight: '700',
    color: '#111',
    letterSpacing: 0.6,
    marginTop: 18,
  },
  galleryBtn: {
    paddingVertical: 14,
    paddingHorizontal: 24,
    backgroundColor: '#000',
    borderRadius: 40,
    marginTop: 18,
  },
  btnText: {
    fontSize: 16,
    color: '#fff',
    fontWeight: '400',
    letterSpacing: 0.4,
  },
});

export default App;

在上面的代码中,我们已经为我们的视图添加了一些文本和样式,但主要部分是我们声明pickAndRecognize 功能的地方。请记住,我们在这个函数中没有做任何与识别有关的事情;我们这样命名是因为我们以后会在这个函数中添加Text Recognition 逻辑。

现在,上述代码的输出将看起来像下面这样:

Implement Image Picking Functionality

现在,我们已经能够从用户的照片库中挑选图片了。让我们也来添加从相机捕捉图像的功能,并在用户界面上查看相机预览。

将下面的代码添加到你的App.tsx 返回语句中。

// Export the asset from a file like this or directly use it.
import {Capture} from './assets/icons'; 
....
<SafeAreaView style={styles.container}>
      ....
      {device && hasPermissions ? (
        <View>
          <Camera
            photo
            enableHighQualityPhotos
            ref={camera}
            style={styles.camera}
            isActive={true}
            device={device}
          />
          <Pressable
            style={styles.captureBtnContainer}
            // We will define this method later
            onPress={captureAndRecognize}>
            <Image source={Capture} />
          </Pressable>
        </View>
      ) : (
        <Text>No Camera Found</Text>
      )}
</SafeAreaView>

添加相关的样式:

const styles = StyleSheet.create({
....
  camera: {
    marginVertical: 24,
    height: 240,
    width: 360,
    borderRadius: 20,
    borderWidth: 2,
    borderColor: '#000',
  },
  captureBtnContainer: {
    position: 'absolute',
    bottom: 28,
    right: 10,
  },
....
});

我们还需要创建一些状态变量和引用:

const App: React.FC = () => {
  const camera = useRef<Camera>(null);
  const devices = useCameraDevices();
  let device: any = devices.back;
  const [hasPermissions, setHasPermissions] = useState<boolean>(false);
  ....
}

我们在相机视图上显示一个Image 来选择图片。你可以继续下载该资产

在我们预览相机之前,我们需要添加权限来访问我们设备的相机。要做到这一点,将下面的字符串添加到你的iOS项目的info.plist 文件中。

....
<key>NSCameraUsageDescription</key>
<string>Allow Access to Camera</string>
....

对于Android,将下面的代码添加到你的AndroidManifest.xml 文件中。

....
    <uses-permission android:name="android.permission.CAMERA"/>
....

当我们的应用程序被加载时,我们需要向用户请求权限。让我们编写代码来做这件事。将下面的代码添加到你的App.tsx 文件中:

....
  useEffect(() => {
    (async () => {
      const cameraPermission: CameraPermissionRequestResult =
        await Camera.requestCameraPermission();
      const microPhonePermission: CameraPermissionRequestResult =
        await Camera.requestMicrophonePermission();
      if (cameraPermission === 'denied' || microPhonePermission === 'denied') {
        Alert.alert(
          'Allow Permissions',
          'Please allow camera and microphone permission to access camera features',
          [
            {
              text: 'Go to Settings',
              onPress: () => Linking.openSettings(),
            },
            {
              text: 'Cancel',
            },
          ],
        );
        setHasPermissions(false);
      } else {
        setHasPermissions(true);
      }
    })();
  }, []);
....

现在,我们的应用程序用户界面上有一个工作的相机视图。

Working Camera View App UI

现在,我们的相机视图已经工作了,让我们添加代码来从相机中捕捉图像。

还记得我们想从我们的捕获按钮中触发的captureAndRecognize 方法吗?现在让我们来定义它。在App.tsx 中添加下面的方法声明。

....
  const captureAndRecognize = useCallback(async () => {
    try {
      const image = await camera.current?.takePhoto({
        qualityPrioritization: 'quality',
        enableAutoStabilization: true,
        flash: 'on',
        skipMetadata: true,
      });
      console.log('image:', image);
    } catch (err) {
      console.log('err:', err);
    }
  }, []);
....

pickAndRecognize 方法类似,我们还没有在这个方法中添加信用卡识别逻辑。我们将在下一步中这样做。

编写卡号识别逻辑

我们现在能够从我们设备的照片库和相机中获取图片。现在,我们需要编写逻辑,它将做以下工作:

  • 将一张图片作为输入,并为该图片中识别的所有文本返回一个字符串数组
  • 遍历数组中返回的字符串,检查每一项是否是潜在的信用卡号码
  • 如果我们找到一个信用卡号码,我们将返回该字符串和显示内容
  • 如果我们没有找到任何匹配的字符串,那么我们就显示一个错误,显示No Valid Credit Card Found

这些步骤非常简单明了。让我们来写这个方法:

const findCardNumberInArray: (arr: string[]) => string = arr => {
  let creditCardNumber = '';
  arr.forEach(e => {
    let numericValues = e.replace(/\D/g, '');
    const creditCardRegex =
      /^(?:4\[0-9]{12}(?:[0-9]{3})?|[25\][1-7]\[0-9]{14}|6(?:011|5[0-9\][0-9])\[0-9]{12}|3[47\][0-9]{13}|3(?:0\[0-5]|[68\][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/;
    if (creditCardRegex.test(numericValues)) {
      creditCardNumber = numericValues;
      return;
    }
  });
  return creditCardNumber;
};

const validateCard: (result: string[]) => void = result => {
    const cardNumber = findCardNumberInArray(result);
    if (cardNumber?.length) {
      setProcessedText(cardNumber);
      setCardIsFound(true);
    } else {
      setProcessedText('No valid Credit Card found, please try again!!');
      setCardIsFound(false);
    }
};

在上面的代码中,我们写了两个方法,validateCardfindCardNumberInArrayvalidateCard 方法接受一个参数,即string[] 或一个字符串数组。然后,它将该数组传递给findCardNumberInArray 方法。如果在数组中找到了信用卡号码字符串,该方法将其返回。如果没有,它将返回一个空字符串。

然后,我们检查cardNumber 变量中是否有一个字符串。如果有,我们就设置一些状态变量,否则,我们就设置状态变量以显示错误。

让我们看看findCardNumberInArray 方法是如何工作的。这个方法也需要一个string[] 的参数。然后,它循环浏览数组中的each 元素,并将所有非数字值从字符串中剥离。最后,它用一个regex ,检查字符串是否是一个有效的信用卡号码。

如果字符串与regex ,那么我们将该字符串作为信用卡号码从该方法中返回。如果没有字符串与regex 匹配,那么我们就返回一个空字符串。

你还会注意到,我们还没有在代码中声明这些新的状态变量。现在让我们来做这件事。将下面的代码添加到你的App.tsx 文件中。

....
  const [processedText, setProcessedText] = React.useState<string>(
    'Scan a Card to see\nCard Number here',
  );
  const [isProcessingText, setIsProcessingText] = useState<boolean>(false);
  const [cardIsFound, setCardIsFound] = useState<boolean>(false);
....

现在,我们只需要将validateCard 方法插入我们的代码中。用下面的代码编辑你的pickAndRecognizecaptureAndRecognize 方法。

....
  const pickAndRecognize: () => void = useCallback(async () => {
    ....
      .then(async (res: ImageOrVideo) => {
        setIsProcessingText(true);
        const result: string[] = await TextRecognition.recognize(res?.path);
        setIsProcessingText(false);
        validateCard(result);
      })
      .catch(err => {
        console.log('err:', err);
        setIsProcessingText(false);
      });
  }, []);

  const captureAndRecognize = useCallback(async () => {
    ....
      setIsProcessingText(true);
      const result: string[] = await TextRecognition.recognize(
        image?.path as string,
      );
      setIsProcessingText(false);
      validateCard(result);
    } catch (err) {
      console.log('err:', err);
      setIsProcessingText(false);
    }
  }, []);
....

有了这个,我们就完成了!我们只需要在我们的用户界面中显示输出。要做到这一点,在你的App.tsx 返回语句中添加下面的代码。

....
      {isProcessingText ? (
        <ActivityIndicator
          size={'large'}
          style={styles.activityIndicator}
          color={'blue'}
        />
      ) : cardIsFound ? (
        <Text style={styles.creditCardNo}>
          {getFormattedCreditCardNumber(processedText)}
        </Text>
      ) : (
        <Text style={styles.errorText}>{processedText}</Text>
      )}
....

如果有一些文本处理正在进行,上面的代码会显示一个ActivityIndicator 或加载器。如果我们找到了一张信用卡,那么它就显示为一个文本。你还会注意到,我们正在使用一个getFormattedCreditCardNumber 方法来渲染文本。我们接下来会写它。如果所有的条件都是false ,那么意味着我们有一个错误,所以我们显示带有错误样式的文本。

现在我们来声明getFormattedCreditCardNumber 方法。将下面的代码添加到你的App.tsx 文件中。

....

const getFormattedCreditCardNumber: (cardNo: string) => string = cardNo => {
  let formattedCardNo = '';
  for (let i = 0; i < cardNo?.length; i++) {
    if (i % 4 === 0 && i !== 0) {
      formattedCardNo += ` • ${cardNo?.[i]}`;
      continue;
    }
    formattedCardNo += cardNo?.[i];
  }
  return formattedCardNo;
};
....

上面的方法需要一个参数,即cardNo ,这是一个string 。然后,它遍历cardNo ,并在每四个字母后插入一个 。这只是一个用来格式化信用卡号码字符串的实用函数。

我们最终的输出用户界面将看起来像下面这样:

Final UI Output Credit Card Scanner

结语

在这篇文章中,我们学习了如何通过添加信用卡扫描功能来改进我们的移动应用程序。使用react-native-text-recognition库,我们设置了我们的应用程序,从设备的摄像头捕捉照片,并从手机库中挑选图片,识别出16位的信用卡号码。

文本识别并不仅仅限于信用卡扫描。你可以用它来解决许多其他的商业问题,比如为特定的任务自动输入数据,如收据、名片,等等谢谢你的阅读。我希望你喜欢这篇文章,如果你有任何问题,请务必留下评论。