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

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

MishiPay 让买家可以使用智能手机扫描商品并付款,而无需浪费时间排队结账。借助 MishiPay 的扫码即走技术,买家可以使用自己的手机扫描商品条形码并付款,然后直接离开商店。研究表明,每年因在实体店内排队而造成的损失约为 2,000 亿美元。

我们的技术依赖于设备硬件功能(例如 GPS 传感器和摄像头),可让用户查找支持 MishiPay 的商店、扫描实体店内的商品条形码,然后使用他们选择的数字付款方式付款。扫码即走技术的初始版本是平台专用 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 的商店数量在每个地区不断增加,用户需要滚动列表并找到正确的商店。
  • 用户有时会不小心选择错误的商店,导致购买交易记录有误。

为解决这些问题,我们在每家商店的店内显示屏上嵌入了具有地理定位信息的唯一二维码。这为用户提供了更快捷的新手入门体验。用户只需扫描商店内营销材料上印刷的地理定位二维码,即可访问“扫码即走”网站应用。这样,他们就不必输入网址 mishipay.shop 即可访问该服务。

使用 PWA 的店内扫描体验。

扫描商品

MishiPay 应用的一项核心功能是条形码扫描,因为这让用户能够在到达收银机之前扫描自己的购买交易并查看总金额。

为了在 Web 上构建扫描体验,我们确定了三个核心层。

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

视频串流

借助 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 密集型任务分流到 Web Worker。在支持硬件图形加速的设备上,WebGL API 及其 WebGL2RenderingContext 可以优化对 CPU 密集型预处理任务的提升效果。

解码器层

最后一个层是解码器层,负责从处理层捕获的帧解码条形码。借助 Shape Detection API(尚未在所有浏览器上提供),浏览器本身可以从 ImageBitmapSource 解码条形码,ImageBitmapSource 可以是 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 企业 1D 和 2D
Scandit 企业 1D 和 2D
开源和商业条形码扫描库的比较

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

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

扫描的未来

一旦所有主要浏览器都完全支持 Shape Detection API,我们可能会推出一个新的 HTML 元素 <scanner>,该元素具有条形码扫描器所需的功能。MishiPay 的工程团队认为,由于越来越多的开源库和许可库支持“扫描并付款”等体验,因此将条形码扫描功能作为新的 HTML 元素具有坚实的用例。

总结

应用疲劳是开发者在其产品进入市场时面临的一个问题。用户通常希望在下载应用之前了解该应用为他们带来的价值。在商店中,MishiPay 可以为买家节省时间并改善他们的体验,因此让买家在下载应用后才能使用应用,这违背了常识。这正是 PWA 的用武之地。通过消除进入障碍,我们的交易量增加了 10 倍,用户也因此节省了 2.5 年的时间。

致谢

本文由 Joe Medley 审核。