# 決済完了の通知

> 自分のサーバーで決済が終わったらサーバーREST API（v2）でチャットルームに知らせ、ページスクリプトが受け取ってチャット画面に表示するサンプルです。

自分のサーバーで決済が終わった瞬間に、チャットルームにいる全員へ「購入完了」の通知を表示します。サーバーは[サーバーREST API](/ja/doc/3.6)で `payment` チャンネルにデータを送り、ページスクリプトはサーバーが送ったものかを確認してからチャット画面に表示します。

## 流れ {#flow}

1. 決済代行会社が自分のサーバーに決済完了を通知します(自分のサーバーの既存の処理)。
2. 自分のサーバーが `POST https://api.uchat.io/v2/rooms/{id}/events` で `payment` チャンネルに送ります。
3. いまチャットルームに接続している全員のページに、`payment` チャンネルでデータが届きます。
4. ページスクリプトが送信者の権限が `4`(サーバー)であることを確認し、`room.print` で表示します。

## サーバー: Node.js {#server-node}

Node.js 18以上の組み込み `fetch` を使います。APIキーとAPIチャットルームID（`room_…`）は管理ページの **API** メニューで発行・確認し、キーはサーバーの環境変数にだけ置きます。キーの権限には **イベント** が必要です。

```js
// notify-payment.mjs
const ROOM = 'room_xxxxxxxxxxxxxxxx'; // APIチャットルームID

export async function notifyPayment(productName) {
	const res = await fetch(`https://api.uchat.io/v2/rooms/${ROOM}/events`, {
		method: 'POST',
		headers: {
			Authorization: `Bearer ${process.env.UCHAT_API_KEY}`,
			'Content-Type': 'application/json',
		},
		body: JSON.stringify({ plugin: 'payment', data: ['paid', productName] }),
	});
	const result = await res.json();
	if (!res.ok) {
		// 429 chat_rate_limited: 無料チャットルームの送信上限超過など
		console.warn('uchat notify failed', res.status, result.error);
	}
	return result;
}

// 決済完了の処理の中で
// await notifyPayment('プレミアム利用券');
```

## サーバー: PHP {#server-php}

```php
<?php
function notify_payment($product_name) {
	$ch = curl_init('https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events');
	curl_setopt_array($ch, [
		CURLOPT_POST => true,
		CURLOPT_RETURNTRANSFER => true,
		CURLOPT_HTTPHEADER => [
			'Authorization: Bearer ' . getenv('UCHAT_API_KEY'),
			'Content-Type: application/json',
		],
		CURLOPT_POSTFIELDS => json_encode(['plugin' => 'payment', 'data' => ['paid', $product_name]], JSON_UNESCAPED_UNICODE),
		CURLOPT_TIMEOUT => 5,
	]);
	$body = curl_exec($ch);
	$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
	curl_close($ch);
	$result = json_decode($body, true);
	if ($status >= 400) {
		error_log('uchat notify failed: ' . $status . ' ' . ($result['error']['code'] ?? 'no response'));
	}
	return $result;
}

// 決済完了の処理の中で
// notify_payment('プレミアム利用券');
```

成功するとHTTP 202と `{"data":{"sent":true}}` が返ります。チャットルームに誰もいなくても成功です(受け取る人がいないだけです)。失敗するとHTTPステータスと `{"error":{"code":…,"message":…}}` が返ります。エラーコードの一覧は[サーバーREST API](/ja/doc/3.6#errors)をご覧ください。

## ページ: 受け取って表示する {#page-script}

```html
<script>U=window.U=window.U||{},U.events=U.events||[],U.chat=function(n){return{on:function(e,t){U.events.push([n,e,t])},off:function(e){for(var t=U.events.length;t>0;)U.events[--t][0]==n&&U.events[t][1]==e&&U.events.splice(t,1)}}};</script>
<script async src="https://client.uchat.io/uchat.js"></script>
<u-chat room="my-room" style="display:inline-block;width:100%;max-width:400px;height:500px"></u-chat>

<script>
U.chat('my-room').on('after.create#payment', function (room) {
	function esc(value) {
		return String(value).replace(/[&<>"']/g, function (c) {
			return '&#' + c.charCodeAt(0) + ';';
		});
	}

	room.plugin.add('payment').onReceived(function (room, data) {
		// サーバー API が送ったものだけを信頼する。ユーザーも同じチャンネルに送れる
		if (!data.user || data.user.auth !== 4) return;
		if (data.type !== 0) return; // 入室時に届く保存値(type 1)はスキップする
		var msg = data.data;
		if (msg[0] !== 'paid' || typeof msg[1] !== 'string') return;
		room.print('🎉 誰かが<b>' + esc(msg[1].slice(0, 50)) + '</b>を購入しました!');
	});
});
</script>
```

## 注意点 {#notes}

- `room.print` はHTMLとして表示するため、受け取った値は上のコードのように必ずエスケープします。
- 権限を確認せずに表示すると、どのユーザーでも同じチャンネルで偽の決済通知を表示できてしまいます。`data.user.auth === 4` は、サーバーAPIで送ったデータか、モデレーター以上が保存した値にだけ付きます。保存値は入室時に `type` 1で届くため、上のコードは `type` 0(サーバーAPIで全員に送ったもの)だけを受け取ります。ニックネームを指定して送ったサーバーAPIのデータも `type` 1なので、保存値と区別できません。
- RESTリクエストの本文の先頭部分とリクエスト元IPは、管理ページの **API** メニューの呼び出し履歴に30日間残ります。購入者の名前・連絡先・カード情報のような個人情報は送らないでください。
- そのまま送ったデータは、いま接続している人にだけ届きます。チャットルームに誰もいなくても成功を返し、失敗した場合でも決済処理そのものを失敗扱いにせず、記録だけ残します。決済通知は過ぎた出来事なので、`save` で保存しないのが適切です。
- 1人にだけ送るには、本文に `"to":"ニックネーム"` を入れます(日本語のニックネームも使えます)。この方法で送ったデータは `type` 1で届くので、上のコードの `type` の確認を外す必要があります。
- 無料チャットルームでは、直近60秒間に受け取る人数の合計が上限を超えるとHTTP 429 `chat_rate_limited` になります([チャンネルAPIと上限](/ja/doc/3.5))。
- コードを書かずにチャットルームに表示するだけなら、**イベント通知ウィンドウ**プラグインをオンにして、`plugin` を `alertBox`、`data` を `["custom", "購入完了", 商品名]` にして送ることもできます。
- サーバーから通知を送る一般的な流れは[サーバーからチャットルームに通知を送る](/ja/doc/2.5)にあります。
