Skip to content

Commit 4b4e549

Browse files
gee6809coderyworld
authored andcommitted
playstore_in_app_purchase: rtdn added
1 parent b047062 commit 4b4e549

File tree

8 files changed

+143
-0
lines changed

8 files changed

+143
-0
lines changed

_posts/2025-10-13-play_store_in_app_purchase.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,4 +421,147 @@ class _IapTestPageState extends State<IapTestPage> {
421421

422422
![alt text](/assets/img/in_app_purchase/result_screen.png)
423423

424+
---
425+
426+
## Google Play 실시간 구독 알림 (RTDN) 설정
427+
428+
구독이 갱신되거나 취소·만료될 때마다 서버가 자동으로 이를 감지하려면
429+
**Real-Time Developer Notifications (RTDN)** 기능을 활성화해야 합니다.
430+
Google Play → Cloud Pub/Sub → 서버로 이벤트가 전달되는 구조입니다.
431+
432+
```mermaid
433+
sequenceDiagram
434+
participant Play as "Google Play"
435+
participant PubSub as "Cloud Pub/Sub"
436+
participant BE as "서버(FastAPI)"
437+
participant DevAPI as "Google Play Developer API"
438+
participant DB as "Database"
439+
440+
Play->>PubSub: "RTDN 이벤트 게시 (base64 JSON)"
441+
PubSub->>BE: "HTTP POST /rtdn/google"
442+
BE->>BE: "base64 디코딩 및 알림 파싱"
443+
BE->>DevAPI: "subscriptionsv2.get 호출"
444+
DevAPI-->>BE: "현재 구독 상태 응답"
445+
BE->>DB: "상태 업데이트 (갱신·취소·만료)"
446+
BE-->>PubSub: "200 OK (처리 완료 응답)"
447+
```
448+
449+
---
450+
451+
### 5-1. Cloud Pub/Sub 설정
452+
453+
#### ① API 활성화
454+
455+
1. [Google Cloud Console](https://console.cloud.google.com)
456+
2. **Pub/Sub API** 검색 → **Enable**
457+
![alt text](/assets/img/in_app_purchase/cloud_pub_sub_api.png)
458+
459+
#### ② 토픽(Topic) 생성
460+
461+
1. **Pub/Sub → Topics → Create topic**
462+
2. ID: `rtdn-notifications`
463+
3. 생성 후 표시되는 전체 경로를 복사해둡니다.
464+
![alt text](/assets/img/in_app_purchase/pub_sub.png)
465+
![alt text](/assets/img/in_app_purchase/pub_sub_create_topic.png)
466+
```
467+
projects/<PROJECT_ID>/topics/rtdn-notifications
468+
```
469+
470+
#### ③ 권한 부여
471+
472+
1. 생성한 토픽 → **Permissions** 탭 → **Add principal**
473+
![alt text](/assets/img/in_app_purchase/add_principal.png)
474+
2. 아래 이메일을 추가하고 **Pub/Sub Publisher** 역할 부여
475+
```
476+
477+
```
478+
![alt text](/assets/img/in_app_purchase/pub_sub_publisher.png)
479+
3. 저장 후 1~2분 정도 기다리면 반영됩니다.
480+
481+
> 이 과정을 통해 Google Play가 해당 토픽으로 알림 메시지를 발행할 수 있게 됩니다.
482+
{: .prompt-info}
483+
484+
#### ④ 구독(Subscription) 생성
485+
486+
1. 토픽 상세 페이지 → **Create subscription**
487+
![alt text](/assets/img/in_app_purchase/make_subscription.png)
488+
2. ID: `rtdn-subscription`
489+
3. **Delivery type**: Push
490+
4. **Push endpoint URL**:
491+
492+
```
493+
https://{서버도메인}/rtdn/google
494+
```
495+
5. 나머지는 기본값 그대로 두고 **Create**
496+
497+
---
498+
499+
### 5-2. Play Console 연결
500+
501+
1. [Play Console](https://play.google.com/console) → 앱 선택
502+
2. **Monetize → Monetization setup → Real-time developer notifications**
503+
3. **Enable real-time notifications** 체크
504+
4. Topic name 입력:
505+
506+
```
507+
projects/<PROJECT_ID>/topics/rtdn-notifications
508+
```
509+
5. **Save changes**
510+
![alt text](/assets/img/in_app_purchase/play_console_monetization_rtdn_setup.png)
511+
6. **Send test message** 버튼으로 연결 성공 여부를 확인합니다.
424512

513+
---
514+
515+
### 5-3. 서버 구현 (FastAPI 예시)
516+
517+
아래 코드는 Pub/Sub이 전송한 RTDN 알림을 받는 `/rtdn/google` 엔드포인트 예시입니다.
518+
알림 본문은 base64 인코딩된 JSON이므로, 디코딩 후 구독 상태를 다시 조회해야 합니다.
519+
520+
```python
521+
# iap/rtdn.py
522+
from fastapi import APIRouter, Request, HTTPException
523+
import base64, json
524+
from google.oauth2 import service_account
525+
from googleapiclient.discovery import build
526+
527+
router = APIRouter()
528+
SCOPES = ["https://www.googleapis.com/auth/androidpublisher"]
529+
530+
def _android_publisher():
531+
creds = service_account.Credentials.from_service_account_file(
532+
"service-account.json", scopes=SCOPES
533+
)
534+
return build("androidpublisher", "v3", credentials=creds, cache_discovery=False)
535+
536+
@router.post("/rtdn/google")
537+
async def handle_rtdn(req: Request):
538+
body = await req.json()
539+
msg = body.get("message")
540+
if not msg or "data" not in msg:
541+
raise HTTPException(status_code=400, detail="invalid format")
542+
543+
decoded = base64.b64decode(msg["data"]).decode()
544+
data = json.loads(decoded)
545+
546+
if "subscriptionNotification" not in data:
547+
return {"ok": True, "msg": "ignored (non-subscription event)"}
548+
549+
n = data["subscriptionNotification"]
550+
purchase_token = n["purchaseToken"]
551+
notif_type = n["notificationType"]
552+
553+
pub = _android_publisher()
554+
result = pub.purchases().subscriptionsv2().get(
555+
packageName=data["packageName"], token=purchase_token
556+
).execute()
557+
558+
# 구독 상태(result["subscriptionState"])에 따라 DB 업데이트 수행
559+
# 예: ACTIVE → 활성화, CANCELED/EXPIRED → 권한 회수 등
560+
561+
return {"ok": True, "notificationType": notif_type}
562+
```
563+
564+
> RTDN은 “상태 변경”만 알리므로, 반드시 `subscriptionsv2.get`으로 **실제 구독 상태를 재조회**해야 합니다.
565+
> {: .prompt-info}
566+
567+
---
139 KB
Loading
109 KB
Loading
48.9 KB
Loading
144 KB
Loading
28 KB
Loading
94 KB
Loading
145 KB
Loading

0 commit comments

Comments
 (0)