광고 추가 보상 서버 시청 검증 · 사용자 퀘스트 승인 후 공개 · TIME ATTACK 모드 · 만보기 퀘스트 · 위치 퀘스트 지도 선택 · 운영 퀘스트 보상 재원

This commit is contained in:
Sn-Kinos
2026-10-05 22:48:09 +09:00
parent 4bb92c2ee7
commit fbf9a2c3d7
39 changed files with 1871 additions and 477 deletions
+12 -2
View File
@@ -35,7 +35,13 @@ abstract interface class RewardedAdHandle {
///
/// 불러오지 못하면(채울 광고 없음 · 네트워크 · 시간 초과) `E-AD-01` 로 끊는다 —
/// 원장 B-03 「광고 로드 실패 시 기본 보상은 지급하고 추가 보상·횟수는 차감하지 않음」.
///
/// [userId] · [customData] 는 **서버측 시청 검증(AdMob SSV)** 에 실린다 — Google 이 시청 완료를
/// 서버(`fn-ad-ssv`)로 직접 알릴 때 이 두 값이 「누가 · 어느 기본 보상(txId)에 붙여」 본 광고인지
/// 가리킨다. 서버는 그 기록이 있어야만 증분을 지급한다(앱의 보상 콜백은 근거가 아니다).
Future<RewardedAdHandle> loadGoogleRewardedAd({
required String userId,
required String customData,
Duration timeout = const Duration(seconds: 15),
}) async {
await (_initialized ??= MobileAds.instance.initialize());
@@ -49,7 +55,11 @@ Future<RewardedAdHandle> loadGoogleRewardedAd({
),
);
try {
return _GoogleRewardedAd(await loaded.future.timeout(timeout));
final ad = await loaded.future.timeout(timeout);
await ad.setServerSideOptions(
ServerSideVerificationOptions(userId: userId, customData: customData),
);
return _GoogleRewardedAd(ad);
} catch (_) {
// 시간 초과 뒤에 늦게 도착한 광고는 띄우지 않고 버린다.
unawaited(loaded.future.then((ad) => ad.dispose(), onError: (_) {}));
@@ -75,7 +85,7 @@ class _GoogleRewardedAd implements RewardedAdHandle {
try {
await _ad.show(
onUserEarnedReward: (_, _) {
// 서버가 이 값으로 같은 노출의 이중 지급을 막는다(`adRewardLogs/{id}`).
// 화면 분기용(끝까지 봤는가)일 뿐 지급 근거가 아니다 — 근거는 SSV 콜백이다.
impression = _ad.responseInfo?.responseId ??
'gma-${DateTime.now().microsecondsSinceEpoch}';
},