MishiPay 的 PWA 将交易次数提高了 10 倍,并节省了 2.5 年的排队时间

了解改用 PWA 对 MishiPay 的业务有何帮助。

MishiPay 让买家可以使用智能手机扫描购物内容并付款,而不必浪费时间在结账时排队等候。借助 MishiPay 的 Scan & Go 技术,买家可以使用自己的手机扫描商品上的条形码并付款,然后只需离开商店即可。研究表明,店内排队每年会给全球零售业带来约 2,000 亿美元的成本。

我们的技术依赖于设备硬件功能,例如 GPS 传感器和摄像头,可让用户定位支持 MishiPay 的商店,扫描实体店内的商品条形码,然后使用他们选择的数字付款方式付款。我们最初版本的 Scan & Go 技术是针对特定平台的 iOS 和 Android 应用,尝鲜者喜欢该技术。请继续阅读下文,了解改用 PWA 如何将交易量增加 10 倍并节省 2.5 年的排队时间!

    10×

    交易量增幅

    2.5 年

    已保存队列

挑战

用户在排队或排队时发现我们的技术非常有用,因为它可以跳过排队,获得顺畅的店内体验。但是,下载 Android 或 iOS 应用非常麻烦,因此用户不选择我们的技术(无论其价值所在)。这对 MishiPay 来说是一个日益严峻的挑战,我们需要通过降低进入门槛来提高用户的采用率。

解决方案

我们在构建和发布 PWA 方面付出了诸多努力,帮助我们消除了安装的麻烦,并鼓励新用户在实体店内试用我们的技术,跳过排队,获得无缝的购物体验。自发布以来,相较于针对具体平台的应用,PWA 的用户采用率大幅上升。

并排比较直接启动 PWA(左侧,速度更快)与安装和启动 Android 应用(右侧,较慢)。
按平台划分的交易。¡OS:16397 (3.98%)。Android:13769 (3.34%)。网站:382184 (92.68%)。
大多数交易都是在网上发生的。

技术深入探究

查找支持 MishiPay 的商店

为启用此功能,我们依赖 getCurrentPosition() API 以及基于 IP 的回退解决方案。

const geoOptions = {
  timeout: 10 * 1000,
  enableHighAccuracy: true,
  maximumAge: 0,
};

window.navigator.geolocation.getCurrentPosition(
  (position) => {
    const cords = position.coords;
    console.log(`Latitude :  ${cords.latitude}`);
    console.log(`Longitude :  ${cords.longitude}`);
  },
  (error) => {
    console.debug(`Error: ${error.code}:${error.message}`);
    /**
     * Invoke the IP based location services
     * to fetch the latitude and longitude of the user.
     */
  },
  geoOptions,
);

这种方法在应用的早期版本中非常有效,但后来被证明是 MishiPay 用户的巨大痛点,原因如下:

  • 基于 IP 的后备解决方案中的位置信息不准确。
  • 每个地区支持 MishiPay 的商店在不断增加,这要求用户滚动列表并确定正确的商店。
  • 用户偶尔会不小心选择了错误的商店,导致系统错误地记录购买交易。

为了解决这些问题,我们在每家商店的店内显示屏上嵌入了具有唯一性的地理定位二维码。这为更快的新手入门体验铺平了道路。用户只需扫描商店内提供的营销材料上印有的地理定位二维码,即可访问“扫描和前往”Web 应用。这样一来,用户就无需输入网址 mishipay.shop 即可访问该服务。

使用 PWA 的店内扫描体验。

正在扫描商品

MishiPay 应用的核心功能是条形码扫描,因为这让我们的用户能够扫描自己的购买交易,甚至可以在他们原本需要进行现金提取之前查看累计总额。

为了打造网络扫描体验,我们确定了三个核心层。

示意图,显示了三个主线程层:视频流、处理层和解码器层。

视频流

借助 getUserMedia() 方法,我们可以根据下列限制条件访问用户的后视摄像头。调用此方法会自动触发提示,提示用户接受或拒绝访问其摄像头。获得对视频流的访问权限后,我们便可以将其中继到某个视频元素,如下所示:

/**
 * Video Stream Layer
 * https://developer.mozilla.org/docs/Web/API/MediaDevices/getUserMedia
 */
const canvasEle = document.getElementById('canvas');
const videoEle = document.getElementById('videoElement');
const canvasCtx = canvasEle.getContext('2d');
fetchVideoStream();
function fetchVideoStream() {
  let constraints = { video: { facingMode: 'environment' } };
  if (navigator.mediaDevices !== undefined) {
    navigator.mediaDevices
      .getUserMedia(constraints)
      .then((stream) => {
        videoEle.srcObject = stream;
        videoStream = stream;
        videoEle.play();
        // Initiate frame capture - Processing Layer.
      })
      .catch((error) => {
        console.debug(error);
        console.warn(`Failed to access the stream:${error.name}`);
      });
  } else {
    console.warn(`getUserMedia API not supported!!`);
  }
}

处理层

为了检测给定视频流中的条形码,我们需要定期捕获帧并将其传输到解码器层。如需捕获帧,我们只需使用 Canvas APIdrawImage() 方法将数据流从 VideoElement 绘制到 HTMLCanvasElement 上。

/**
 * Processing Layer - Frame Capture
 * https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Manipulating_video_using_canvas
 */
async function captureFrames() {
  if (videoEle.readyState === videoEle.HAVE_ENOUGH_DATA) {
    const canvasHeight = (canvasEle.height = videoEle.videoHeight);
    const canvasWidth = (canvasEle.width = videoEle.videoWidth);
    canvasCtx.drawImage(videoEle, 0, 0, canvasWidth, canvasHeight);
    // Transfer the `canvasEle` to the decoder for barcode detection.
    const result = await decodeBarcode(canvasEle);
  } else {
    console.log('Video feed not available yet');
  }
}

对于高级用例,该层还会执行一些预处理任务,例如剪裁、旋转或转换为灰度模式。这些任务可能占用大量 CPU 资源,并且由于条形码扫描是一项长时间运行的操作,因此会导致应用无响应。借助 OffscreenCanvas API,我们可将 CPU 密集型任务分流给网页工作器。在支持硬件图形加速的设备上,WebGL API 及其 WebGL2RenderingContext 可以优化 CPU 密集型预处理任务的增益。

解码器层

最后一层是解码器层,负责对处理层捕获的帧中的条形码进行解码。得益于 Shape Detection API(尚未在所有浏览器上推出),浏览器本身就可以从 ImageBitmapSource 解码条形码,该 API 可以是 img 元素、SVG image 元素、video 元素、canvas 元素、Blob 对象、ImageData 对象或 ImageBitmap 对象。

显示三个主线程层的示意图:视频流、处理层和 Shape Detection API。

/**
 * Barcode Decoder with Shape Detection API
 * https://web.dev/shape-detection/
 */
async function decodeBarcode(canvas) {
  const formats = [
    'aztec',
    'code_128',
    'code_39',
    'code_93',
    'codabar',
    'data_matrix',
    'ean_13',
    'ean_8',
    'itf',
    'pdf417',
    'qr_code',
    'upc_a',
    'upc_e',
  ];
  const barcodeDetector = new window.BarcodeDetector({
    formats,
  });
  try {
    const barcodes = await barcodeDetector.detect(canvas);
    console.log(barcodes);
    return barcodes.length > 0 ? barcodes[0]['rawValue'] : undefined;
  } catch (e) {
    throw e;
  }
}

对于尚不支持 Shape Detection API 的设备,我们需要一种回退解决方案来解码条形码。Shape Detection API 提供了 getSupportedFormats() 方法,可帮助在 Shape Detection API 和回退解决方案之间切换。

// Feature detection.
if (!('BarceodeDetector' in window)) {
  return;
}
// Check supported barcode formats.
BarcodeDetector.getSupportedFormats()
.then((supportedFormats) => {
  supportedFormats.forEach((format) => console.log(format));
});

展示如何使用 Shape Detection API 或后备解决方案(具体取决于条形码检测器支持和支持的条形码格式)的流程图。

后备解决方案

我们提供了多种开源扫描库和企业扫描库,这些库可以轻松与任何 Web 应用集成以实现扫描。以下是 MishiPay 推荐的一些库。

库名称 类型 Wasm 解决方案 条形码格式
QuaggaJs 开源 不支持 1 日
ZxingJs 开源 不支持 1D 和 2D(受限)
CodeCorp Enterprise 1D 和 2D
Scandit Enterprise 1D 和 2D
开源和商业条形码扫描库的比较

上述所有库都是构成了上述所有层的完整 SDK。它们还公开了用于支持各种扫描操作的接口。根据业务案例所需的条形码格式和检测速度,您可以决定在 Wasm 解决方案还是非 Wasm 解决方案之间做出决定。尽管需要额外的资源 (Wasm) 来解码条形码会产生开销,但 Wasm 解决方案在准确性方面优于非 Wasm 解决方案。

Scandit 是我们的主要选择。它支持我们的业务用例所需的所有条形码格式;在扫描速度方面,它优于所有可用的开源库。

扫描的未来

一旦所有主流浏览器均全面支持 Shape Detection API,我们就可能会添加一个新的 HTML 元素 <scanner>,它具有条形码扫描器所需的功能。MishiPay 的工程部认为,由于支持扫描和开始等体验的开源库和许可库的数量不断增加,条形码扫描功能成为一种新的 HTML 元素具有一个可靠的用例。

总结

应用疲劳是开发者在产品进入市场时面临的一个问题。用户通常希望在下载应用之前,先了解应用给他们的价值。在商店里,MishiPay 节省了买家的时间并改善了他们的体验,因此等待他们下载才能使用应用是违背常理的。这正是 PWA 大显身手的地方。我们消除了进入门槛,将交易量增加了 10 倍,并让用户能够节省 2 年半的排队等待时间。

致谢

本文由 Joe Medley 审核。