Mua trong ứng dụng (IAP)
Gói GEM/GOLD, gỡ quảng cáo… bằng tiền thật. Portal là nơi quyết định cộng gì cho người chơi, sau khi tự xác minh với Google.
androidpublisher). Cron reconcile_voided_purchases.py 02:25 hằng ngày quét giao dịch bị hoàn tiền/hủy 48 giờ gần nhất và thu hồi.4.1 — Tạo sản phẩm (Play Console)
- Play Console → app → Monetize → Products → In-app products (cần đã upload một bản build có thư viện Billing; Drone Strike đã có qua SDK
portal_iap). Bật payments profile nếu được yêu cầu. - Create product: Product ID (ví dụ
gem_pack_100— không đổi được sau khi tạo), tên, mô tả, giá → Activate. Gói tiền tệ (mua nhiều lần) và sản phẩm mua một lần (gỡ quảng cáo) đều là in-app product loại managed; Portal tự consume gói tiền tệ (product_type = currency_pack), các loại khác được acknowledge. - Subscription không được hỗ trợ (Portal từ chối thông báo subscription).
4.2 — Tạo service account (Google Cloud Console)
- Cùng project ở mục 03 → APIs & Services → Library → bật Google Play Android Developer API.
- IAM & Admin → Service Accounts → Create service account, tên ví dụ
portal-play. Không cần cấp role IAM trong Cloud. - Mở service account → Keys → Add key → Create new key → JSON. Tải file về (ví dụ
play.json). File này là secret: không commit, không gửi qua chat công khai. Ghi lại email dạngportal-play@<project>.iam.gserviceaccount.com.
4.3 — Cấp quyền cho service account (Play Console)
- Play Console (cấp tài khoản developer, không phải trong app) → Users and permissions → Invite new users → dán email service account.
- Tab App permissions → thêm app Drone Strike, cấp tối thiểu: View app information, View financial data, Manage orders and subscriptions (để đọc giao dịch, acknowledge/consume và đọc voided purchases). Repo không quy định danh sách quyền; đây là bộ quyền Google yêu cầu cho các API đang gọi.
- Invite user. Quyền có thể mất tới vài giờ (đôi khi tới 24 giờ) mới có hiệu lực — trong thời gian đó Google trả 401/403.
4.4 — Cài vào VPS
- Thư mục credentials.
docker-compose.prod.ymlđã mount./credentials(thư mục cài đặt) vào backend tại/app/credentials, chỉ đọc;deploy_local.shtự tạo thư mục với quyền700. Backend chạy bằng UID 1000 — trùng user deployhuynqntrên VPS, nên file600của user đó đọc được. - Đặt file lên VPS (thư mục cài đặt production:
/home/huynqn/web/web-portal):Nếu user deploy không có UID 1000, đặt chủ file là UID 1000 ($ ssh huynqn@20.51.243.159 'mkdir -p /home/huynqn/web/web-portal/credentials && chmod 700 /home/huynqn/web/web-portal/credentials' $ scp play.json huynqn@20.51.243.159:/home/huynqn/web/web-portal/credentials/play.json $ ssh huynqn@20.51.243.159 'chmod 600 /home/huynqn/web/web-portal/credentials/play.json'
sudo chown 1000 play.json) để backend đọc được. - Thêm vào
.env(sao lưu trước:cp -p .env .env.bak.$(date -u +%Y%m%d_%H%M%S)):Sinh khóa Fernet:GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH=/app/credentials/play.json # Khóa Fernet riêng để mã hóa purchase token (khuyến nghị; bỏ trống thì dùng MFA_ENCRYPTION_KEY) IAP_TOKEN_ENCRYPTION_KEY=<khóa Fernet>
python3 -c 'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'(hoặc chạy trong container backend). Giữ khóa này cố định — đổi khóa thì token đã lưu không giải mã được. - Tạo lại backend:
docker compose -f docker-compose.prod.yml up -d backend. File credential cũng đượcbackup_config.shsao lưu (mã hóa) mỗi đêm; job báo lỗi nếu.envtrỏ tới file không tồn tại.
4.5 — Cấu hình trong Portal
- Shop: tạo sản phẩm giá REAL, trạng thái published, đang bán (listed), trường Google Play Product ID (SKU) = Product ID ở 4.1. Sản phẩm phải có icon thì cửa hàng trong game mới hiện.
- Feature Flags: bật
iap(mặc định tắt). Khi tắt, API trả403 iap_disabledvà cửa hàng không hiện sản phẩm tiền thật.
!
Bật cờ iap trước khi có service account thì mọi giao dịch đều xác minh lỗi (người chơi bị trừ tiền ở Google nhưng chưa được cộng, phải chờ cron/hoàn tiền). Chỉ bật sau khi 4.2–4.4 xong và mua thử thành công.
4.6 — Mua thử
- Play Console (cấp tài khoản) → Settings → License testing: thêm email Google của người test — mua không mất tiền thật.
- Cài game từ track Internal testing (cài qua link opt-in, không sideload) bằng tài khoản đó, mua một gói.
- Kiểm Portal → Purchases: giao dịch trạng thái thành công; số dư người chơi tăng đúng một lần; mua gói tiền tệ lần nữa được (đã consume).
- Hoàn tiền thử ở Play Console → Order management: sau lần chạy cron 02:25 (giờ VPS) phần thưởng bị thu hồi; trường hợp thiếu số dư hiện ở Economy Anomalies.
| Lỗi trả về game | Nguyên nhân | Xử lý |
|---|---|---|
403 iap_disabled | Cờ iap của game đang tắt | Feature Flags → bật iap |
404 unknown_product | SKU không khớp sản phẩm REAL đã publish và đang bán trong Shop | Đối chiếu Product ID ở Play Console với SKU trong Shop |
409 account_mismatch | Giao dịch thuộc người chơi khác (obfuscatedAccountId ≠ player_uid) | Bình thường (chống dùng lại token); app phải mua bằng đúng người chơi |
202 pending | Giao dịch đang chờ (thanh toán chậm) | Game thử lại sau |
| Xác minh lỗi, log backend có 401/403 từ Google | Thiếu/sai file service account (hoặc backend không đọc được file), service account chưa được mời hoặc quyền chưa có hiệu lực, chưa bật Android Developer API | Mục 4.2–4.4; chờ quyền có hiệu lực |
Áp dụng cho Portal v1.2.2