メタバース内で決済を使いたい
もっと自由に、メタバースの中で自分のビジネスを展開したい。
そう思っている人は多いのではないでしょうか。自分もその一人です。
実際の商品とそっくりな3Dモデルを見ることができて、現実の店舗に行かなくてもその場で購入できたり、
3Dアセットも一度メタバース内で表示を確認してから購入したり、
メタバースの技術を使えば、色々な店舗の形態をとることができるはずです。
Inflatonの紹介
しかし、現状はどうでしょうか。
既存のメタバースプラットフォームは販売するものに対しての規約や技術的制限でがんじがらめです。
もっとメタバースを自由に楽しみたい。そういった思いから誰でもメタバースをホストできる仕組みを開発しています。
Inflaton(インフラトン)という名前です。
まだ鋭意開発中なのでリリースはされていません。
詳細についてはnoteの方に書いていますので、もしご興味をお持ちでしたら是非ご覧ください。
応援していただけるとありがたいです。
メタバースにPAY.JPが組み込めるのか検証
さて、今回の記事の本題ですが、このInflatonで誰でも簡単に決済機能が組み込めたら素敵だな、ということで、今回はPAY.JPのAPIを検証してみました。
検証環境
Inflatonは以下の環境で動く予定です。
サーバーサイド: Nuxt (v4.2+)
クライアントサイド: Unity(6+) + WebXR Exporter
動作環境: Chrome、Quest
PAY.JPは以下のライブラリを使用しました。
サーバーサイド: Node.jsライブラリ
クライアントサイド: payjp.js
WebXR ExporterのサンプルシーンでPAY.JSの組み込みを検証します。

検証
前提となるシナリオ
以下のシナリオについてを考えてみます。
あなたは自分のメタバースワールド内で使える服などのアイテムを販売しています。
ユーザーはアイテムを購入する前に、手に取ってみたり、試着したりして、気に入ったら購入できます。
購入する場合は、何らかの手段で自分のクレジットカード情報を入力し、
購入ボタンを押すことで売買が成立します。
ユースケース1 ブラウザモード
メタバースといえばVRを思い浮かべる人も多いかもしれませんが、VRである必要はありません。
まずはVRではなく、普通にブラウザで操作をする場合を実装してみます。
技術的な課題として以下が挙げられます。
- Unity内でクレジットカード情報を入力させることができるのか?
- Unityからブラウザのpayjp.jsと連携できるのか?
検証1 Unity内のみで決済を完結できるか
シナリオ
まず、手始めにユーザーにとって一番理想的なUXが実現できるのか検証します。
ユーザーがメタバース内でその場でクレジットカード情報を入力し、購入完了まで完結できる。
実装方法
UnityはC#からJSを呼び出せる機能があります。
ただし、直接呼び出せるJS関数は、.jslibという拡張子でUnityのAssets内に定義しなければなりません。
よって、ブラウザで読み込んでいる他の要素と連携するにはwindowに設定したイベント経由で連携します。
詳細はUnityのドキュメントを確認してください。
https://docs.unity3d.com/6000.2/Documentation/Manual/webgl-interactingwithbrowserscripting.html
実装の方針としては以下の通りです。
- Unity内で何らかのUIによりクレジットカード情報を入力させる。
- 入力されたクレジットカード情報をブラウザ側へ渡す。
- payjp.jsが作成したinput要素へクレジットカード情報を書き込み。
- payjp.jsによりトークン生成を行う。
Unity側(.jslib)
mergeInto(LibraryManager.library, {
SubmitPayjp: function (cardNumber, cardExpiry, cardCvc) {
const cn = window.document.querySelector('#cardNumber');
const ce = window.document.querySelector('#cardExpiry');
const cc = window.document.querySelector('#cardCvc');
cn.value = cardNumber;
ce.value = cardExpiry;
cc.value = cardCvc;
window.dispatchEvent(new CustomEvent('submitPayjp'));
}
});
querySelectorで取得している要素はpayjp.jsがマウントしたクレジットカードのinput要素です。
Unity側(C#)
using System.Runtime.InteropServices;
using UnityEngine;
public class JSCommunicator : MonoBehaviour
{
[DllImport("__Internal")]
private static extern void SubmitPayjp(string cardNumber, string cardExpiry, string cardCvc);
public void SayHello()
{
SubmitPayjp("4242424242424242", "12 / 30", "123");
}
}
今回はクレジットカード情報を入力させるUIをつくる部分は省き、値はハードコーディングにしています。
結果
この方法は実現できません。
payjp.jsが生成するクレジットカード情報のinput要素はpay.jpドメイン内のiframe内で定義されています。
よってブラウザのquerySelectorから取得することができません。
これはPCI DSS準拠のセキュアな実装のためです。
payjp.jsのAPIにもクレジットカード番号などの値をJSからセットするAPIはありません。
payjp.jsのAPIで要素にフォーカスするAPIはありますが、別オリジンのiframe内のため、document.activeElementでも取得できません。
検証2-1 ブラウザ+Unityで決済を完結できるか(ウィンドウ表示)
シナリオ
検証1でクレジットカード情報を入力するinputはJSからの入力を受け付けないことが分かりました。
よって、payjp.jsが生成するクレジットカード情報入力UIはブラウザ側で表示し、Unityでのメタバース表示と同じページに配置して使用するケースを考えます。
ユーザーがメタバース空間の表示とブラウザのUIを両方操作してクレジットカード情報を入力し、
購入完了まで完結できる。
ユーザーはブラウザをウィンドウ表示で使用しているとする。
実装方法
ブラウザの同一画面でUnityでの描画とブラウザのUIを両方表示します。
今回はUIが適当ですが、実際はメニューに隠したり決済の時だけ表示したり等の工夫が必要です。
結果
決済の完了が可能です。
当たり前といえば当たり前ですが、無事に決済が完了できます。
途中、3Dセキュアの別ウィンドウでUnityの描画は隠れる場合がありますが、問題なく決済を実装することができます。
決済完了後はUnity側のC#からサーバーサイドに購入情報を問い合わせてもいいですし、MyGameInstance.SendMessageでUnity側に情報を送ることもできそうです。
検証2-1 ブラウザ+Unityで決済を完結できるか(フルスクリーン表示)
シナリオ
検証2-1と同様にブラウザ+Unity表示ですが、メタバースで遊んでいるユーザーはフルスクリーンで表示することが多いと想定されます。
フルスクリーン表示でのUXを確認します。
ユーザーがメタバース空間の表示とブラウザのUIを両方操作してクレジットカード情報を入力し、
購入完了まで完結できる。
ユーザーはブラウザをフルスクリーン表示で使用しているとする。
実装方法
UnityのWebビルドをフルスクリーンで表示すると、デフォルトの挙動ではcavasがTopLayerに設定されてしまい、その他のUIが見えなくなってしまいます。
canvasがTopLayerに設定されるので、その他のUIのz-indexを調整しても前面に表示することはできません。
そこで、Unityのビルドで作成されるunityInstance.SetFullscreen(1)を使わずに、フルスクリーン関数を自前で作成します。
#unity-canvas-ontainer内に、決済UIを入れます。
(TailwindCSSを使っています。)
<div id="unity-container">
<div id="unity-canvas-container" class="relative">
<canvas id="unity-canvas" style="width: 100%; height: 100%;"></canvas>
<div class="absolute bottom-10 left-10 z-10 w-full">
<div id="v2-demo"></div><!-- payjpがクレジットカード情報UIをマウントする -->
<button class="btn" @click="onPayjpSubmit">トークン作成</button>
<p v-text="payjpToken"></p><!--payjpトークン表示-->
<button class="btn" @click="charge">支払う</button>
<p v-text="alertMsg"></p><!--決済成功/失敗表示-->
</div>
</div>
<div id="unity-loading-bar">
...(省略)
そして、フルスクリーンにする関数でUnityデフォルトの関数ではなく、#unity-canvas-containerをフルスクリーンにするようにします。
const setFullScreen = () => {
// unityInstance?.SetFullscreen(1); // これは使わない
const unityContainer = document.querySelector('#unity-canvas-container');
if (unityContainer && unityContainer.requestFullscreen) {
unityContainer.requestFullscreen();
}
}
これでフルスクリーン表示でも決済UIがUnity画面上に表示される様になりました。
この状態でトークン作成を行ってみます。
しかし、ここで3Dセキュアのダイアログが表示され、フルスクリーン表示が強制的に解除されてしまいます。

これはブラウザの仕様であり、どうしようもありません。
回避策として以下を実装してみました。
- あらかじめ画面いっぱいになるようにUnity画面(決済UIを含む)を表示。
- フルスクリーンAPIではなくブラウザ自体をフルスクリーンにする。
#unity-canvas-containerを横いっぱい、縦はh-screenで画面いっぱいにします。
<div id="unity-container">
<div id="unity-canvas-container" class="relative w-full h-screen">
<canvas id="unity-canvas" style="width: 100%; height: 100%;"></canvas>
<div class="absolute bottom-10 left-10 z-10 w-128">
<div id="v2-demo"></div><!-- payjpがクレジットカード情報UIをマウントする -->
<button class="btn" @click="onPayjpSubmit">トークン作成</button>
<p v-text="payjpToken"></p><!--payjpトークン表示-->
<button class="btn" @click="charge">支払う</button>
<p v-text="alertMsg"></p><!--決済成功/失敗表示-->
</div>
</div>
<div id="unity-loading-bar">
<div id="unity-logo"></div>
<div id="unity-progress-bar-empty">
<div id="unity-progress-bar-full"></div>
</div>
</div>
</div>
F11キーを押してブラウザをフルスクリーン表示します。
これでフルスクリーンAPIと同じ状態になりました。
そしてトークン作成を行ってみます。
無事にフルスクリーンが解除されることなく3Dセキュアの別ウィンドウが表示されました!
決済時の3Dセキュアも同様です。
無事に支払い完了されました。
結果
決済の完了が可能です。
ただし、フルスクリーンにする方法は工夫が必要です。
JavaScriptでのフルスクリーンAPIは使用できず、CSSによるUnity表示部分のサイズ調整が必要になります。
ユースケース2 VRモード
シナリオ
VRモードでメタバースにいるユーザーがその場で購入完了まで完結できる。
結果
VRモードでは決済は組み込めません。
ブラウザモードで検証した通り、payjp.jsでの実装はブラウザでしか正常に機能しません。
WebXRのVRモードではブラウザの要素をVR世界に持ち込むことができませんので、payjp.jsを動かすことはできません。
一度VRモードを終了し、ブラウザ表示にしてからクレジットカード情報を入力する必要があります。
回避策として、スマートフォンなどの他のデバイスで決済を進め、完了ステータスを同期する方法は可能ですが、クレジットカード情報の入力のために一旦VRゴーグルを外したり、パススルーモードにしなければならないため、結局一旦はVRモードを終了しなければなりません。
課題
今回の検証で以下の課題が分かりました。
Unity(メタバース)内だけでは決済を完結できない
ブラウザであれば、ブラウザの機能でクレジットカード情報の自動入力が使えますが、Unity内では使えません。
Unity用のSDKが期待されます。
VRモードでのUXが悪い
上記と同じ理由に起因するものですが、特にVRモードでは一度VRモードを終了する必要があり、没入感が損なわれてしまいます。
まとめ
まだまだメタバースは伸びしろのある技術です。決済の方法ももっと便利になっていくと思われます。
特にVRモードでの決済UXは改善されることが期待されます。
今後の技術トレンドをウォッチしながら日進月歩でより良い方法を模索していきたいと思います。
おまけ
サーバーサイドNuxt側で使用したコードを記載します。
(検証用なのでかなり適当なものですが...。)
server/api/payjp.ts。
公式のドキュメントの方にはTypeScriptでの使い方は、
import * as Payjp from 'payjp';
で読み込むとありますが、自分のセットアップの場合は、
import Payjp from 'payjp';
で読み込む必要がありました。(2025/12/05時点)
import Payjp from 'payjp';
import z from 'zod';
const bodySchema = z.object({
token: z.string()
});
export default defineEventHandler(async (event) => {
setHeader(event, 'Content-Type', 'application/json');
const { token } = await readValidatedBody(event, bodySchema.parse);
const payjp = Payjp('sk_test_f134adb6690aba7f4ae1811f');
try {
const charge = await payjp.charges.create({
amount: 1000,
currency: 'jpy',
card: token,
three_d_secure: true
});
return {
'result': 'ok',
'id': charge.id,
'paid': charge.paid
}
} catch (e: any) {
console.error(e);
}
return {
'result': 'ng'
}
});








