STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

STORES レジで遭遇した 12 桁バーコード読み取りの謎

こんにちは、 yu です。前回はインターン生としてブログを書きましたが、今回は内定者アルバイトとしてブログを書いていきます。

STORES レジには、会員バーコードやアイテムのバーコードを読み込むスキャン機能があります。 しかし、そのスキャン機能では 12 桁のバーコードが読めないという問題が発生していました。 今回は、なぜ 12 桁のバーコードが読めなかったのかとその解決法について、バーコードの規格とともに紹介します。

発生した問題と原因

問題

STORES レジに登録する商品には、オーナーさまがお好みのバーコード番号を設定できます。 しかし、アイテムに 12 桁のバーコードを設定し、そのバーコードをレジアプリで読み取っても、アイテムが見つからないというエラーが出てしまい検索ができないという問題が発生しました。

修正前

原因

ログを見てみると、 12 桁のバーコードの先頭に 0 が付加され、 13 桁のバーコードとして認識されてしまっていました。

つまり、問題の原因としては、「先頭に 0 が付加されてしまっていた」という大変シンプルなものでした。しかし、これを解決するためには、バーコードの読み取り部分やバーコードの判別方法ついて詳しく調査する必要がありそうです。

バーコードの種類について

諸々の調査へ進む前に、まずは前提知識としてバーコードの種類を紹介します。

  • EAN コード (JAN コード)
    • 日本や世界の流通商品で使われている標準的なバーコード
    • EAN-13 (13 桁) • EAN-8 (8 桁)
  • UPC コード
    • 主にアメリカで使われているバーコード
    • UPC-A (12 桁) • UPC-E (8 桁)
  • CODE128
    • 英数字・記号・制御コードも表現できるバーコード

など、他にもさまざまな種類が存在します。

今回の問題では、 UPC-A (12 桁) のバーコードが EAN-13 (13 桁) のバーコードと誤って認識されてしまっていたので、それぞれの詳細な仕様を調べました。

EAN-13

EAN-13

  • 国コード (3 桁)
    • GS1 というフォーマットによって規定されている
  • 商品コード
    • 自由に決められる
  • チェックデジット
    1. チェックデジット (一番右の桁) を除いた残りの 12 桁の数字をから数えて、奇数番目を 3 倍、偶数番目を 1 倍したものを足し合わせる (チェックサム)。
    2. チェックサムと同じか、チェックサムより大きい最も近い 10 の倍数から、チェックサムを引いたものが、チェックデジットとなる。

参考: International Article Number

UPC-A

UPC-A

  • 商品コード
    • 自由に決められる
  • チェックデジット (1 桁)
    1. 12 桁の数字をから数えて、奇数番目を 3 倍、偶数番目を 1 倍したものを足し合わせる (チェックサム)。
    2. チェックサムを 10 で割ってあまりを出す。
    3. あまりが 0 ならそれがチェックデジットとなり、 0 でなければ 10 からあまりを引いたものが、チェックデジットとなる。

参考: Universal Product Code

調査

バーコードは、 Apple の Vision フレームワークに含まれている VNDetectBarcodesRequest を用いて検出しています。 そこでまずは UPC-A にフレームワークが対応しているかどうか確認してみました。

しかし、Apple 公式の対応リスト には EAN-13 や UPC-E の記載はありますが、肝心の UPC-A の記載がありません。

ここで、「公式ライブラリでは、 UPC-A の先頭に 0 を付加させることで、EAN-13として扱っているのかも?」と仮説を立ててみました。この仮説が正しいか、検証した内容を次に示します。

UPC-A バーコードを用意

まず、 UPC-A のバーコードを用意します。

1 2 3 4 5 6 7 8 9 10 11 12
UPC-A 7 2 5 2 7 2 7 3 0 7 0 6

一応チェックデジットを計算して、正常なバーコードかを確かめてみます。

この UPC-A のバーコードのチェックデジットは、末尾の 6 です。

  1. 末尾のチェックデジットを除いた残りの 11 桁の数字を左から数えて、奇数番目を 3 倍、偶数番目を 1 倍したものを足し合わせる。

    (7 + 5 + 7 + 7 + 0 + 0) * 3 + (2 + 2 + 2 + 3 + 7) = 94

  2. 94 を 10 で割ってあまりを出す。

    94 % 10 = 4

  3. 余りが 0 でなければ 10 からあまりを引いたものが、チェックデジットとなる。

    10 - 4 = 6

末尾の 6 と一致しているので、この UPC-A のバーコードは正常なバーコードといえます。

UPC-A バーコードに 0 を付けてみる

次に、 UPC-A のバーコードに 0 を付加します。 13 桁となるので EAN-13 のバーコードとして計算してみます。

12 11 10 9 8 7 6 5 4 3 2 1
EAN-13 (UPC-A だったもの) 0 7 2 5 2 7 2 7 3 0 7 0 6

この EAN-13 のバーコードのチェックデジットも、同様に末尾の 6 です。

  1. チェックデジット (一番右の桁) を除いた残りの 12 桁の数字を右から数えて、奇数番目を 3 倍、偶数番目を 1 倍したものを足し合わせる。

    (0 + 0 + 7 + 7 + 5 + 7) * 3 + (7 + 3 + 2 + 2 + 2 + 0) = 94

  2. 94 より大きく、かつ最も近い 10 の倍数である 100 から 94 を引いたものが、チェックデジットとなる。

    100 - 94 = 6

こちらも、末尾の 6 と一致しているので、この EAN-13 のバーコードも正常なバーコードといえます。

結果

つまり、 UPC-A の先頭に 0 を付加すると、 EAN-13 のバーコードとしても正常なバーコードとなることが分かります。

Apple の Vision フレームワークは、 UPC-A のバーコードの先頭に意図的に 0 を付加して、 EAN-13 として認識させているようです。

読み取る段階で 12 桁の UPC-A バーコードかどうかを判定できれば最善なのですが、公式ライブラリではそこまで自由が効きません。

検索は 12 桁の数字で行う必要があるため、読み取った後の処理で EAN-13 になってしまった UPC-A バーコードを判別して、 12 桁に戻す必要があります。

対処法

バーコードの判別

アプリ側で見分けたい!と思っても、先頭に 0 が付いて EAN-13 に化けた UPC-A のバーコードは、先述の通りどちらの場合でも正常なバーコードとなるため、アプリ側で見分けることができません。

しかも、 EAN-13 の規格的に先頭に 0 が付かない仕様なら良いのですが、先頭が 0 の場合は北米のバーコードであると GS1 に規定されています。

そこで、読み取ったバーコードが UPC-A であると断定はできないので、 UPC-A の可能性があるかどうかを判定する関数を作りました。

行っていることとしてはシンプルで、この 3 つです。

  1. 先頭の 0 を削除
  2. String を Int の配列に変換
  3. チェックデジットに一致するか計算
func isUPCACode(barcode: String) -> Bool {
    var upcaCode = barcode
    if upcaCode.count == 13 {
        // UPC-A (12 桁) のバーコードの先頭に 0 が付加されて EAN-13 (13 桁) として認識されている可能性がある。
        // 13 桁かつ先頭が 0 の場合のみ、先頭の 0 を削除。
        guard let firstDigit = upcaCode.first, firstDigit == "0" else {
            return false
        }
        upcaCode.removeFirst()
    }

    // String 形式のバーコードを Int の配列に変換
    var digits = [Int]()
    for digitChar in upcaCode {
        guard let digit = Int(String(digitChar)) else { return false }
        digits.append(digit)
    }

    // UPC-A である条件
    // (12 桁目以外の偶数桁の合計 + 奇数桁の合計の 3 倍) の mod を M として、 M が 0 でなければ Μ = 10 - M とすると、 M が 12 桁目のチェックデジットに一致する。
    var oddSum = 0
    var evenSum = 0
    for (index, digit) in digits.dropLast().enumerated() {
        // index が 0 オリジンなので偶奇判定が逆になっている。
        if index % 2 == 0 {
            oddSum += digit * 3
        } else {
            evenSum += digit
        }
    }
    let sum = oddSum + evenSum

    // 条件を確認
    guard let lastDigit = digits.last else { return false }
    let checkDigit = (10 - sum % 10) % 10
    return checkDigit == lastDigit
}

検索フローの修正

ここまでの流れを軽くおさらいしておきます。

  1. Vision フレームワークが 12 桁の UPC-A のバーコードを 13 桁の EAN-13 のものと認識する
  2. EAN-13 になってしまったバーコードは、元々 UPC-A だったかどうかを見分けることができない
  3. EAN-13 のバーコードから、 UPC-A の可能性があるかどうかを見分ける関数を用意した

ここまで辿り着けば、あとは UPC-A のバーコードで検索できるように処理を修正するだけです。

最終的に、下の図のように UPC-A の可能性がある場合には再検索をするという、この解決策を採用することにしました。

修正した検索フロー

QA で確認したところ検索速度には大きな変化は無く、オーナーさまへの影響も限定的と判断したため、このままリリースしました 🙌

修正後

まとめ

今回は 12 桁のバーコードが読めない問題について、バーコードの規格や仕様を調査しながら解決していきました。

私はiOSエンジニアではありますが、Swift のコードがほぼ出てこないというブログになってしまいました。しかし、今まで Swift を触ってきてバーコードに触れる機会は無かったので、 STORES レジならではの良い経験になりました。

(この対応をしてからというもの、バーコードの種類やどの国の製品なのかを見る癖が付いてしまいました 😇)