了解改用 PWA 如何帮助 MishiPay 拓展业务。
借助 MishiPay,买家可以使用智能手机扫描商品并付款,而无需在结账处排队浪费时间。借助 MishiPay 的扫码即购技术,购物者可以使用自己的手机扫描商品上的条形码并付款,然后离开商店。研究显示,实体店排队每年给全球零售业造成 2, 000 亿美元的损失。
我们的技术依赖于设备硬件功能,例如 GPS 传感器和摄像头,这些功能可让用户找到支持 MishiPay 的商店、扫描实体店内的商品条形码,然后使用自己选择的数字支付方式付款。我们 Scan & Go 技术的初始版本是特定于 iOS 和 Android 平台的应用,早期采用者非常喜欢这项技术。请继续阅读,了解改用 PWA 后,交易次数增加了 10 倍,排队时间节省了 2.5 年!
10×
交易量增加
2.5 年
已保存排队信息
挑战
用户在排队或结账时发现我们的技术非常有用,因为他们可以跳过队列,获得顺畅的实体店体验。但下载 Android 或 iOS 应用的麻烦让用户即使知道我们的技术很有价值,也不会选择使用。对于 MishiPay 而言,这是一个日益严峻的挑战,我们需要降低入门门槛,以提高用户采用率。
解决方案
我们努力构建并发布 PWA,帮助用户摆脱安装烦恼,并鼓励新用户在实体店内试用我们的技术,无需排队即可享受顺畅的购物体验。自发布以来,与平台专用应用相比,我们的 PWA 在用户采用率方面出现了大幅增长。
技术层面的深入探讨
查找已启用 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 即可访问该服务。
扫描商品
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 API 的 drawImage() 方法将 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 对象。

/**
* 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));
});

后备解决方案
目前有多种开源和企业扫描库可供选择,这些库可以轻松集成到任何 Web 应用中以实现扫描。以下是 MishiPay 推荐的一些库。
所有这些库都是成熟的 SDK,它们构成了我们讨论的所有层。它们还公开了接口,以支持各种扫描操作。根据业务需求所需的条形码格式和检测速度,可以选择 Wasm 解决方案或非 Wasm 解决方案。尽管需要额外的资源 (Wasm) 来解码条形码,但 Wasm 解决方案在准确性方面优于非 Wasm 解决方案。
Scandit 是我们的首选。它支持我们业务用例所需的所有条形码格式;在扫描速度方面,它优于所有可用的开源库。
扫描的未来
一旦所有主流浏览器都完全支持 Shape Detection API,我们可能会获得一个具有条形码扫描器所需功能的新 HTML 元素 <scanner>。MishiPay 的工程团队认为,随着越来越多的开源和许可库支持“扫码即走”等体验,条形码扫描功能成为新的 HTML 元素具有充分的用例。
总结
当开发者的产品进入市场时,他们会面临应用疲劳问题。用户通常希望在下载应用之前了解其价值。在商店中,MishiPay 可以节省买家的时间并改善他们的体验,但如果买家必须等待下载完成后才能使用应用,这与 MishiPay 的初衷背道而驰。而我们的 PWA 正好可以解决这些问题。
通过消除准入障碍,我们的交易量增加了 10 倍,并让用户节省了 2.5 年的排队时间。
致谢
本文由 Joe Medley 审核。