# Payment completion notice

> An example where your server notifies the chat room through the server REST API (v2) when a payment completes, and a page script receives it and shows it in the chat window.

The moment a payment completes on your server, show a "purchase complete" notice to everyone in the chat room. Your server sends data to the `payment` channel with the [Server REST API](/en/doc/3.6), and the page script checks that it came from the server before showing it in the chat window.

## Flow {#flow}

1. The payment provider tells your server the payment is complete (your server's existing handling).
2. Your server sends to the `payment` channel with `POST https://api.uchat.io/v2/rooms/{id}/events`.
3. The data arrives on the `payment` channel in the page of everyone currently in the chat room.
4. The page script checks that the sender's permission is `4` (server) and shows it with `room.print`.

## Server: Node.js {#server-node}

This uses the built-in `fetch` in Node.js 18 or later. Issue the API key and find the API chat room id (`room_…`) in the **API** menu of the admin page, and keep the key only in your server's environment variables. The key needs the **Events** permission.

```js
// notify-payment.mjs
const ROOM = 'room_xxxxxxxxxxxxxxxx'; // API chat room 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: free chat room send limit exceeded, etc.
		console.warn('uchat notify failed', res.status, result.error);
	}
	return result;
}

// Inside your payment completion handling
// await notifyPayment('Premium pass');
```

## Server: 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;
}

// Inside your payment completion handling
// notify_payment('Premium pass');
```

On success, it returns HTTP 202 and `{"data":{"sent":true}}`. It succeeds even when no one is in the chat room (there is just no one to receive it). On failure, you get an HTTP status and `{"error":{"code":…,"message":…}}`. For the full list of error codes, see [Server REST API](/en/doc/3.6#errors).

## Page: receive and display {#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) {
		// Trust only what the server API sent. Users can send to the same channel too
		if (!data.user || data.user.auth !== 4) return;
		if (data.type !== 0) return; // Skip saved values that arrive on join (type 1)
		var msg = data.data;
		if (msg[0] !== 'paid' || typeof msg[1] !== 'string') return;
		room.print('🎉 Someone just bought <b>' + esc(msg[1].slice(0, 50)) + '</b>!');
	});
});
</script>
```

## Things to note {#notes}

- `room.print` displays HTML, so always escape received values as in the code above.
- If you display without checking permissions, any user can show fake payment notices through the same channel. `data.user.auth === 4` is set only on data sent through the server API or values saved by a moderator or higher. Saved values arrive with `type` 1 on join, so the code above accepts only `type` 0 (sent to everyone through the server API). Server API data sent to a specific nickname is also `type` 1, so it can't be told apart from saved values.
- The start of the REST request body and the request IP are kept for 30 days in the call log of the **API** menu on the admin page. Do not send personal information such as the buyer's name, contact details, or card details.
- Data sent without `save` reaches only people connected now. The API responds with success even if no one is in the chat room, and even if a call fails, don't fail the payment itself; just log it. A payment notice is about something that already happened, so it should not be saved with `save`.
- To send to one person only, put `"to":"nickname"` in the body (Korean nicknames work too). Data sent this way arrives as `type` 1, so remove the `type` check from the code above.
- In free chat rooms, you get HTTP 429 `chat_rate_limited` if the total number of recipients over the last 60 seconds exceeds the limit ([Channel API and limits](/en/doc/3.5)).
- To just show it in the chat room without code, turn on the **Event alert box** plugin and send with `plugin` set to `alertBox` and `data` set to `["custom", "Purchase complete", product name]`.
- The general flow for sending server notifications is in [Sending notifications from your server to a chat room](/en/doc/2.5).
