Перейти к содержанию

Nfc tag lifecycle

Рецепт: полный жизненный цикл NFC метки.

Регистрация

Endpoint: NfcTagEndpoint.registerTag — только consumer аккаунты, NFC должен быть включён (assertNfcEnabled).

Квота: максимум 3 активные метки на пользователя. При превышении — CloseloopException(code: 'NFC_TAG_QUOTA').

// Клиент → Auth Center (через Serverpod client)
final registration = await client.nfcTag.registerTag(label: 'Браслет');
// registration.token — открытый токен, отдаётся ОДИН РАЗ
// registration.tagId — ID метки в БД
// registration.expiresAt — +90 дней от создания

После получения token клиент записывает его на NFC-чип, затем:

// Зафиксировать UID физического чипа (идемпотентно)
await client.nfcTag.confirmTagWritten(tagId, chipUid: chipUid);

chipUid необходим для последующего сброса чипа сотрудником. Без него метка считается «сиротой».

Установка лимитов

Endpoint: NfcTagEndpoint.setTagLimits — только владелец, только при включённом NFC. Лимиты в сатанах (1 THB = 100 satang).

// Установить лимиты
final tag = await client.nfcTag.setTagLimits(
  tagId,
  perTapLimit: 50000,   // 500 THB за одно касание
  dailyLimit: 200000,   // 2000 THB в день
);

// Снять лимиты (передать null)
final tag = await client.nfcTag.setTagLimits(tagId); // perTapLimit и dailyLimit = null

Лимиты хранятся только на сервере в таблице nfc_tag, чип не модифицируется.

Оплата через NFC

Endpoint: MerchantEndpoint.chargeByNfc — только merchant/agent аккаунты.

Flow: 1. Мерчант считывает токен с NFC-чипа покупателя 2. Вызывает chargeByNfc(token, amountSatang, currency) 3. Auth Center: находит метку по hashToken(token), проверяет статус, counter, лимиты 4. Auth Center: вызывает PM POST /intents с подписанным HMAC запросом 5. Возвращает NfcChargeResult с intentId и статусом

// Мерчант
final result = await client.merchant.chargeByNfc(
  token: tokenFromChip,
  amountSatang: 50000,  // 500 THB
  currency: 'THB',
);

Ротация

Endpoint: NfcTagEndpoint.rotateTag — перевыпуск токена для существующей метки. Используется если чип нужно перезаписать (смена устройства).

final registration = await client.nfcTag.rotateTag(tagId);
// Новый token, новый expiresAt (+90 дней), lastCounter сброшен в 0
// Лимиты (perTapLimit, dailyLimit) сохраняются

После ротации — записать новый токен на чип, вызвать confirmTagWritten.

Когда ротировать: если токен скомпрометирован, устройство сменилось, или срок действия истёк.

Отзыв

Endpoint: NfcTagEndpoint.revokeTag — доступен даже при выключенном NFC (аварийный kill-switch).

Ситуация Поведение
chipUid == null (сирота) Hard delete: строка удаляется из реестра
chipUid != null Soft revoke: status='revoked', revokedAt=now
await client.nfcTag.revokeTag(tagId);

Отозванная метка с привязанным chipUid ждёт физического сброса сотрудником через NfcChipResetEndpoint.

Сброс чипа (сотрудник)

Доступен аккаунтам agent и ролям operator/superadmin.

Шаг 1: Найти чип

// По UID чипа (основной путь)
final creds = await client.nfcChipReset.getChipResetCredentials(
  chipUid: '04:A1:B2:C3:D4:E5:F6',
);

// Fallback: по остаточному токену с чипа
final creds = await client.nfcChipReset.getChipResetCredentials(
  token: tokenReadFromChip,
);
// creds.tagId, creds.status, creds.label

Каждый вызов записывается в audit_log.

Шаг 2: Физически сбросить чип

Сотрудник стирает страницы NFC-чипа через мобильное устройство (без PWD_AUTH — write-protection не используется).

Шаг 3: Подтвердить сброс

await client.nfcChipReset.confirmChipReset(chipUid: chipUid);
// Строка nfc_tag удаляется из реестра (hard delete)
// Следующий пользователь чипа сможет зарегистрировать его заново

Идемпотентно: повторный вызов после удаления — тихий успех.