This site is an archive of ama.ne.jp.

文フリ34のふりかえり(無人販売編)

概要

第三十四回文学フリマ東京では、見本誌の展示とタブレットを使った注文システムを1ブースにまとめた無人販売風の展示を行いました。このシステムは、主にFlutterで書かれた注文アプリとPython(Tornado)で書かれた注文サーバーで構成されています。

この記事では、変態美少女ふぃろそふぃ。が推進しているブース無人化計画の流れをベースとして、無人販売注文システムの目的および構成について紹介した上で、第三十四回文学フリマ東京で企画を実施した際の気付きや反省について述べていこうと思います。

ブース無人化計画について

ブース無人化計画は、変態美少女ふぃろそふぃ。が文学フリマ東京を始めとした即売会に取り組む方向性のひとつです。大まかには同人誌即売会での無人販売を実現し、準備完了後から片付け直前まで無人で頒布が成立することを目指しています。より詳細には、田舎の無人野菜直売所から堅牢な自動販売機まで、様々な手法を取り入れてブースの無人化を達成するための中長期的な計画です。

変態美少女ふぃろそふぃ。では、ブースに(少なくとも人間の)売り子を置かずに済むようにすることを中長期的な目標に置いています。ただ作品を手渡したり、金銭の勘定をすること自体はあまり重要ではないからです。極端な例を出すと、ブース設営が終わったら会場を抜け出して大井競馬場に遊びに行くことさえできるようにすべきだと考えています。
あまねけ!ニュースレター #11

この計画はサークル活動を始めてから長年のあいだ構想段階のまま放置されていましたが、COVID‑19の流行によって具体的な企画として始動しました。過去には、中古の100円玉用ドロップセレクター(参考: 旭精工 ドロップメカセレクター AD-81P2)を購入して支払機を制作しようとしたことがありますが、まだ実際の展示には繋がっていません。即売会での展示企画として実施するのは今回の 無人販売注文システム が初めてです。

補助輪のこと

無人販売企画を行う場合、その環境によって利用できる手段や注意すべき点が大きく変わります。特に、来場者がどれほど悪意ある行動1をとる可能性があるか検討することは非常に重要です。これによって、田舎の無人野菜直売所と堅牢な自動販売機のどちらを利用すべきかが決まりますし、企画にかけるべき予算の幅が変化します。

無人販売所は、多くの前提や条件でどのように実現できるかが決まります。このような懸念要素には、大きく分けて「来場者」「支払い方法」「作品の種類」があるでしょう。
あまねけ!ニュースレター #26

現金を使わずに電子版のみ販売するような場合なら、手法はもっと単純化できるでしょう。電子マネーやクレジットカードによる支払いとダウンロード時の検証を組み合わせることで、通貨の選別や商品の排出をこなす物理的な装置がなくともセキュリティを実現できるからです。ただし、このケースは単に通販サイトで商品を購入するのと原理的には変わりません。来場者の体験を向上させるには、物理的な見本誌の設置や即売会先行販売(または限定販売)のようなメリットを用意したり、 お祭り性 に依拠した企画を併用する必要があります。

一方、現金を使ったり、物理的な媒体2で販売する場合はもう少し深い検討が必要です。来場者の善良さに強い仮定を置けば、商品と貯金箱を放置して無人販売所とすることもできますが、仮定に対する根拠がなければ現実的とはいえません。貯金箱が持ち去られないようにワイヤーで繋いでいたとしても、適切な対価を支払って商品を持ち出したことを保証するのは困難です。

そのため、十分な判断材料のない状態で安全な無人販売企画を実現するには、自動販売機のような構造を導入するほかありません。しかし、自動販売機の導入には比較的大きなコスト(数万円~十数万円)がかかるため、最低限のセキュリティから少しずつ導入していきたいのも事実です。

そこで、変態美少女ふぃろそふぃ。では 補助輪付き の無人販売企画を検討しています。ここでいう「補助輪付き」とは、無人販売企画を行っているブースの隣で、来場者からは一見すると関係のないサークルを装って監視に入ることを指しています。つまり、一方のブースを無人販売所のように仕上げつつ、完全な無人では実現の難しい部分をもう一方のブースでカバーするという形態です。文学フリマ東京では申し込み時に連続した2ブースを取得できる3ため、比較的簡単に実現できます。

注文用のタブレット端末が真ん中に置かれた左側の無人販売ブースと、作品紹介フローチャートの無料配布および「ふみきり」作品解説と「機械ネコ」の考察についてのポスター展示を行った右側の有人ブース

もちろんこれは 補助輪付き という言葉の通り、厳密には無人販売を実現できているとはいえません。それでも、本来は来場者への強い信頼が必要な手法を少しずつ安全に試行できます。今回の無人販売注文システムの場合、注文と仮想通貨による支払いまでは無人で行えますが、現金による支払いと商品の受け渡しにはまだ人手が必要です。これが「補助輪」の意味するところであり、あくまで中途の過程としての企画であることを忘れてはいけません。

無人販売注文システムについて

先述の通り、今回の無人販売注文システムは 補助輪付き の無人販売であり、実際には有人の展示です。このシステムは現金計数機能と商品引き渡し機能――つまり、普通の自動販売機にあるべき2つの特徴を備えていません。しかし、注文機能と注文を識別可能な仮想通貨決済機能を備えており、今後より強力な無人販売注文システムに発展できる可能性があります。

注文アプリ(タブレット)のこと

今回の無人販売注文システムで使用した注文アプリ(コードネーム: nightingale)は、僕が覚えたてのFlutterで実装したほとんど書き捨てに近い内容のAndroidアプリです。次回以降も同様の構成で無人販売企画を展開する場合は、まずこのアプリを作り直すところから始まるでしょう。

つかいかたについて

来場者から見た無人販売注文システムは、見本誌に囲まれた台の上に置かれたタブレットで動くアプリそのものです。来場者はタブレットで注文したあと、支払いを済ませて売り子から商品を受け取ります。より具体的には、以下の動画のように注文アプリから注文を行い、隣のブースで商品を受け取るという流れです。

  1. 注文アプリで注文を行う
    1. 注文開始ボタンを押す
    2. 商品を選択してカートに入れる
    3. 支払い方法を選択する
    4. 注文を確定する
  2. 支払いを行う
    • 現金 :arrow_right: 隣のブースで
    • Monero :arrow_right: 任意の場所で
  3. 隣のブースで商品を受け取る

動画内の画像(「光速感情デラックス」表紙・ポストカード宛名面デザイン、「先輩、今日もいいですか」表紙、「花・カフェ・宝くじ」表紙)はそれぞれごまふわラビによってCC BY 4.0でライセンスされています。

Isabella the Monero Girlは何らかのライセンスの下で提供されているものではありません。

Moneroでの決済について

nightingaleの最も大きな特徴は、Monero(XMR)での支払いに対応しているという点でしょう。Moneroとは、本人以外が保有額や取引履歴を追跡できない匿名性の高い暗号資産のひとつです。低い決済手数料で比較的速く送金できるため、あまねけ!や変態美少女ふぃろそふぃ。でも寄付の決済手段として採用しています。

近年、同人誌即売会でSquare ターミナルなどのクレジットカード・電子マネー決済端末が導入されるケースが増えており、非常に便利なキャッシュレス決済の普及を喜ぶ声も多いです。しかし、同人誌のように比較的 センシティブ な購入履歴を、決済サービスや電子マネーの運営企業に引き渡すことに対する危機感を忘れてはなりません。また、キャッシュレス決済の便利さの裏で、決済手数料や出金手数料の名目でサークルの取り分が減っているのも決して無視できないはずです。

Moneroを利用すれば、注文に対する支払いが行われた事実を検証しつつ、誰が支払ったかを秘匿できます。また、送金手数料は支払い側が負担するシステムなのでサークルの取り分が減ることもありません。一見すると購入者に負担を強いる無理な言い分に見えるかもしれませんが、サークルが決済手数料を負担するために キリよく 100円値上げするケースを考えれば、むしろ支払う手数料が明快になって得をするはずです。

nightingaleでは、注文確認時点のXMR-JPYの為替レートをその場で取得して、1円未満の差を四捨五入した支払額を提示します。支払額はこの時点で固定され、その後の変動は反映されません。注文から1時間以内に商品を受け取るルールを設定したので、為替変動幅はそれほど大きくならないという想定です4。実際の支払い時点でレートを確定することもできますが、支払額の算出処理が煩雑になる割に効果は薄いだろうと判断しました。

注文確定について

nightingaleの重要な特徴は、決済手段だけではありません。注文を識別するためのトークンの発行方法として、安全かつ便利な 注文カード方式 を採用しています。

注文のタイミングと商品を引き渡すタイミングが異なる場合は、注文の主体を識別できる状態にする必要があります。通販サイトでは、IDとパスワードの組み合わせでログインしてもらうことで誰が注文したかを識別できますし、ユーザー登録せずに購入できるサイトでも、メールアドレスや電話番号を入力させるケースが多いでしょう。これによって、注文者(支払い者)が意図した住所に商品を配送することができます。

同人誌即売会であれば、注文時に名前やメールアドレス、SNSのアカウントを紐付けると簡易的な認証を実現できます。主に知識要素としての運用が考えられますが、少し手間をかければ所持要素として利用することも可能です。具体的には、商品を受け取るブースで身分証明書、秘密情報が記載されたメール、SNSのプロフィール画面や秘密情報が記載されたチャット画面を提示すれば、特定の注文に紐付いた注文者だと証明できます。

しかし、これらの紐付けは同人誌即売会の開催よりも前、あるいはブースから離れた場所でDMなどを用いて予約(取り置き)する際に行うケースがほとんどですし、全く面識のないサークル-来場者間で利用されることはそう多くありません5。今回のような無人販売注文システムでは、その場で名前やメールアドレスといった情報を入力するのは手間がかかりますし、知らないサークルに個人情報を渡すことに抵抗を感じる人もいるでしょう。

個人情報を使用するのに抵抗があれば、その場でランダムな秘密情報(合言葉)を発行・選択して6受け渡し時に確認するような方式を採用できます。この方式の最大の欠点は、注文時に秘密情報を書き留めておかないと忘れてしまう可能性が高い点でしょう。注文時に秘密情報を渡したければ、今度は筆記用具かレシートプリンタを置く手間が生まれてしまいます。

そこで、今回の注文システムでは、番号が印字された注文カードを注文に紐付けることでこれらの問題を解決します。数桁の番号程度なら入力にも手間がかかりませんし、個人情報を渡さずに済みますし、番号を覚えておく必要もありません。必要なのは、事前に用意した共通デザインの注文カード(名刺印刷サービスを活用できます)と、注文カードに番号を印字するスタンプだけです。

注文カード(左側は注文手順を示す表面で、右側は数字が印字された裏面)

注文者が持っているカードの番号、注文アプリに入力された番号、ブースに持参したカードの番号が全て一致していれば、注文を識別できます。現金決済ならその場で支払いを行ってから、Monero決済なら支払いを確認してから商品を引き渡すことで一連の注文フローは完了です。これは、原理上Bearerトークンのような認証方法といえるでしょう。

なお、この番号は注文アプリに手で直接入力する必要があるため、Dammアルゴリズムによるチェックディジットを採用しています。1桁の誤りや2桁の入れ替えなど、見間違いや押し間違いによる不一致もある程度防げるようにしました。

キャンセルについて

nightingaleは、 冷やかし にも優しいデザインを心がけています。つまり、注文開始直後から注文完了直前まで、いつでも右上の「 :no_entry_sign: やめる」ボタンを押して注文を中止することができます。注文に戻るよう引き留めるしつこいメッセージもなく、一度「はい」を押せば最初の画面に戻る仕組みです。

「おかいものをやめますか?」と中止を確認するキャンセルダイアログ

即売会で無人展示企画を行う場合は、購入するつもりはないものの目新しさから単に触ってみる人も多いため、途中で離脱しやすいデザインにする必要があります。いつでもキャンセルできると提示しておけば安心感を与えられますし、いろいろな人に気軽な気持ちで注文アプリを触ってもらうことができます(突貫工事とはいえせっかく作ったので使ってもらうのが第一です)。

僕にもう少し商売気があれば、一度触ったら離脱しにくいデザインにして(流れで、空気で、罪悪感で)購入させるデザインに仕上げることもできたでしょう。しかし、本当に心から買う気がない人は途中の画面のまま放置してその場を去ってしまいますし、むしろ無人ブースの世話を焼く手間が増えるだけだと判断してやめました。

注文サーバーのこと

注文サーバー(コードネーム: nightperch)は、nightingaleからの注文を受け付けて記録するためのもので、注文アプリとは逆に手癖で書かれたTornado製の何かです。中身は単純にリクエストを検証してSQLiteを読み書きする程度のもので、次があればFastAPIとかでササッとやりたいなと思っています。

機能

nightperchでは以下のエンドポイントを提供しています。注文者が使う機能は簡単なHTMLを返す人間向け(ブラウザ向け)のもので、nightingale用のエンドポイントはJSON形式でやり取りを行うAPIスタイルのインターフェースです。

エンドポイント 機能 利用者
GET / 注文番号を入力するフォームを表示します 注文者
GET /order/:order_id 注文の合計金額と決済手段を表示します 注文者
POST /order/:order_id 注文番号が order_id の注文を作成します nightingale
GET /soldout 売り切れた商品の情報をJSONで返します nightingale

nightperchは主にnightingaleが注文可能な商品を表示し、注文を作成するために用意されたサーバーですが、注文者が自らの注文を確認する機能も兼ねています。これは、Monero決済を選択した際に必須の機能です。

nightperchの注文問い合わせフォーム

Moneroは匿名性の高い暗号資産であり、ウォレットアドレスを知っている他人はもちろん、支払いを受け取った本人でさえもどこから支払いが行われたのかを識別することができません(この点でビットコインなどとは大きく異なります)。しかし、注文に対する支払い手段としてMoneroを採用した場合は、どの注文に対して支払われたのかを識別する必要があります。

このような支払いと注文を紐付けるために、支払い者がトランザクションを暗号化した鍵そのものか、その鍵から生成した署名を提示して支払いを証明できます(payment proof)。この鍵はトランザクションを復号して内容を確認することしかできないので、渡しても問題はありません。しかし、支払いを証明してもらう側から見ると、証明のたびにランダムなデータに対する署名を要求するフローの方が安全です。

しかし、この方式では支払い後に毎回何らかの手段で鍵や署名を送信する必要があり、注文フローが煩雑になりがちです。クレジットカードを利用したときにボールペンで署名するのさえ面倒なのに、支払うたびに署名を生成してショップごとに違うフォームから送信……なんて想像したくもありません。

そのため、Moneroには支払い先のアドレスに8バイトの支払いIDを自由に埋め込むことができるintegrated addressというフォーマットが用意されています。支払いIDは8バイトですから、1つのアドレスで18.4×1018個くらいの注文を受け付けられます。IPv4が4バイト、UUIDが15バイトくらい、IPv6が16バイトなので余裕はそれなりにあるでしょう。nightperchでもintegrated addressを採用しており、注文に紐付いた個別の支払い先アドレスを注文者に提示できるようにしています。

nightperchによる支払い先アドレスの表示

支払い証明は同じウォレットアドレスへの支払いを識別する仕組みでしたが、integrated addressは支払い先のアドレスを分けることによって支払いを識別できるようにする方法です。つまり、個別の注文とintegrated addressを紐付けて、各アドレスに着金したら支払いが完了したとみなせます。

ちなみに、subaddressを使ってもintegrated addressと同様の処理を実現できるものの、アドレスの生成に秘密鍵が必要なので取り回しが悪いです。サーバーで自動的に注文と紐付けるというよりも、個人がPCやスマホで1つずつ発行して支払いを依頼するようなフローに向いています。integrated addressは同じウォレットアドレスのバリエーションなので、複数回の支払いが同じウォレットに送られていることを識別できますが、subaddressはお互いに関連のあるアドレスかどうか識別できないという差もあります(一貫性と匿名性のトレードオフともいえます)。

ネットワーク

nightperchはnightingaleと注文者の両方が利用する設計のため、サーバーの設置場所にかかわらずタブレットが属するネットワーク(ブースネットワーク)からインターネットに出られるようにする必要があります。大きく分けて、ブースネットワークにサーバーを設置してインターネットから参照できるようにするか、外部にサーバーを設置してブースネットワークから参照できるようにするか、という2つの方針が考えられるでしょう。

今回の注文システムでは、ブースに置いたThinkPad X13 Yoga Gen 1でnightperchサーバーを立てて、インターネットから参照できるようにする方式を採用しました。これは、nightingaleとnightperchをネットワーク的に近い位置に保ちたかったのと、モバイル回線などの不調でブースネットワークからインターネットを参照できなくても、注文システムが影響を受けないようにすることをねらった選択です。

nightingale(KC-T302DT) - モバイルホットスポット - Windows - portproxy - nightperch(WSL)

Windows 11のロゴは何らかのライセンスの下で提供されているものではありません。著作権上、あるいは商標権上の制限を受ける可能性があります。

ThinkPadとタブレットは、モバイルホットスポットを通じて接続しました。タブレットからWi-Fiで接続すると、プライベートIPアドレス(多くのケースで 192.168.137.1 のようです)でWindows側にアクセスできます。さらに、以下のコマンドでWindowsとWSLを接続すれば、8000番ポートでnightperchと通信できるようになります。

$wsl2Address = wsl -e hostname -I | ForEach-Object { $_.trim() }
New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort 8000 -Action Allow -Protocol TCP
netsh.exe interface portproxy add v4tov4 listenaddress=* listenport=8000 connectaddress=$wsl2Address connectport=8000

現状確認はこんな感じです:

netsh.exe interface portproxy show v4tov4

ファイアウォールと転送設定の削除はこんな感じです:

netsh.exe interface portproxy delete v4tov4 listenport=8000 listenaddress=*
Remove-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock'

インターネットからnightperchを参照する経路については、モバイル回線からSSHトンネルを掘って通信できるようにしました。この経路を使って https://pay.hentaigirls.net/ というURLからアクセスしてもらえば、注文の検索や支払い先アドレスの表示を行えます(今は使えません)。中継地が多いですが、ブラウザでの表示速度に大きな問題は感じませんでした。ただし、後述の通り一時的な回線断による不調が長引いたので、安定性の面で問題がありそうです。

注文者 - Cloudflare - NGINX - SSH(モバイル回線) - Windows - portproxy - nightperch(WSL)

Cloudflare、NGINX、Windows 11の各ロゴ、Isabella the Monero Girlは何らかのライセンスの下で提供されているものではありません。著作権上、あるいは商標権上の制限を受ける可能性があります。

気付き・反省について

この無人販売注文システムは、暗号資産による決済や分かりやすい注文フローを採用した革新的なシステムとして設計されましたが、まだまだ反省点が残っています。

まず、注文確定時に必要な注文カードを見つけるのが難しかった点が挙げられます。これはnightingaleやnightperchの実装不備ではなく、どちらかといえばブース上の物理的配置の問題です。注文アプリを入力しやすい高さに載せるための台が狭く、注文カードを置くスペースがなかったため、やむなく低い机の上に置いていました。そのため、タブレットに注目している注文者の視線に入りにくかったと考えられます。

白いクロスをかけた長机の上に置かれている、注文用のタブレット端末、10冊以上の見本誌、呼び出し用の非常ボタン、小さな赤い鳥居

nightingale自身の不備としては、注文番号入力後に押す :ballot_box_with_check: がテンキーに埋もれてしまって見にくかった点があります。このあたりはライブラリからそのままはめ込んだ部分で、まぁ次は大きなOKボタンを下に置けばいいでしょう。

注文確定時に注文番号を入力するダイアログ

また、注文完了後にOKボタンを押さずに放置してしまうケースがあったので、数秒後に最初の画面に戻るよう実装すべきだと思いました。どの画面でも一定時間放置したらキャンセル扱いにしてもいいでしょう。

次回以降に実装したいものとして、以下のようなアイデアがあります。もっと面白いアイデアがあったらコメントから教えてください。

  • 最初の画面でつかいかたを紹介するスライドを流す
  • 商品をランダムに選べるようにする
  • 商品を全部まとめて注文できるようにする
  • 2択の質問をいくつか表示して商品をおすすめできるようにする
  • 商品の説明をmarqueeで流す
  • 試し読みできるようにする

nightperchの不備としては、サーバーの実装自体というよりも設置場所に問題がありました。無人販売注文システムがインターネットに出られなくても独立して注文処理を行えるように、ブースに置いたノートPCでサーバーを立ち上げていたのは先述の通りです。しかし、モバイルホットスポット :arrow_right: Windows :arrow_right: WSLで2つのインターフェースを越える必要があり、当日はファイアウォールの設定をトチって手間取ってしまいました。

また、Monero決済を利用する人に向けてnightperchを外部公開するために、モバイル回線を通してSSHトンネルを掘っていたこともトラブルを引き起こしました。ネットワークが不安定になると接続が切れてしまう上に、何度か再接続を繰り返したところSSHサーバが接続を受け付けなくなってしまったのです。

そこからはngrok経由で外部公開していましたが、カスタムドメイン設定は有料プランだったので、注文カードのQRコードが利用できない状況が続きました7。Monero決済を使った人はいなかったので大きな実害はなかったものの、なかなか悔しい戦績です。

そもそも、SSHトンネルとモバイル回線は特性上あんまり相性がよくないので、外部公開したいなら素直に外でサーバーを動かした方がいいですね。次はきっとそうしましょう。

P.S: 来場者の不備としては、無人ブースへのはみ出しが多いという点がありました(もちろん今回に限りません)。無人だからといって誰かが設置した展示の前にはみ出していいわけではないんですが、隣のブースの方が楽しいから仕方ないのかもしれません。監視カメラとか置いてもあんまり意味ないんだな~と毎回思っています。

まとめ

  • 同人誌即売会で無人販売を行うには、当日の環境や商品の形態、決済手段に応じた個別の設計が必要です。
  • 匿名性の高いMonero決済や使いやすい注文カード方式を取り入れた無人販売注文システムを作りました。
  • 注文システムは、Flutterで書かれた注文アプリとPython(Tornado)で書かれた注文サーバーで構成されています。
  • 面白くてためになるお話が収録された「光速感情デラックス」が発行されました。

  1. 不正に商品を持ち去る、過少な支払いを行う、ブースの金品を盗むなど。 

  2. 電子版か物理版かを選択できるような書籍作品はもちろん、キーホルダーや百合フイルムなどの電子化できないグッズ類を含みます。 

  3. もちろん出店料も倍です。 

  4. 注文時点の為替レートで支払額を固定して、一定時間内に支払いを完了することを求める方式は、coinpaymentsなどの仮想通貨決済代行でも採用されています。 

  5. サークル-来場者間での取り置きを仲介するトリオキニなどのサービスは存在します。 

  6. 元気寿司などでは、発券時に記号を選択して呼び出し時に入力するシステムが採用されています。 

  7. 今思えばPage RulesでngrokのURLにリダイレクトすれば当座はよかったのかもしれない。