Интеграция Google Wallet для купонов и скидок в мобильном приложении
Купон в Google Wallet — это OfferObject поверх OfferClass. В отличие от Apple Wallet, где купоны — просто другой тип .pkpass, Google Wallet разделяет шаблоны (класс) и экземпляры (объект). Один OfferClass описывает акцию, тысячи OfferObject — персонализированные купоны для конкретных пользователей с индивидуальными штрихкодами.
OfferClass: шаблон акции
def create_offer_class(campaign_id: str, title: str, discount_value: str) -> dict:
issuer_id = "YOUR_ISSUER_ID"
return {
"id": f"{issuer_id}.offer_{campaign_id}",
"issuerName": "Your Store",
"title": title, # "Скидка 15% на электронику"
"redemptionChannel": "BOTH", # ONLINE, INSTORE или BOTH
"provider": "Your Store",
"titleImage": {
"sourceUri": {"uri": "https://yourapp.com/offer-banner.jpg"},
"contentDescription": {
"defaultValue": {"language": "ru", "value": title}
}
},
"details": f"Скидка {discount_value} на весь ассортимент. Не суммируется с другими акциями.",
"finePrint": "Действует до 31.12.2024. Одноразовый.",
"validTimeInterval": {
"start": {"date": "2024-06-01T00:00:00Z"},
"end": {"date": "2024-12-31T23:59:59Z"}
},
"hexBackgroundColor": "#1A73E8",
"reviewStatus": "UNDER_REVIEW"
}
OfferObject: персонализированный купон
def create_offer_object(class_id: str, user_id: str, coupon_code: str) -> dict:
issuer_id = "YOUR_ISSUER_ID"
return {
"id": f"{issuer_id}.coupon_{user_id}_{coupon_code}",
"classId": class_id,
"state": "ACTIVE",
"barcode": {
"type": "CODE_128", # или QR_CODE, PDF_417
"value": coupon_code,
"alternateText": coupon_code
},
"validTimeInterval": {
"start": {"date": "2024-06-01T00:00:00Z"},
"end": {"date": "2024-12-31T23:59:59Z"}
},
"textModulesData": [
{
"header": "Ваш промокод",
"body": coupon_code,
"id": "coupon_code"
}
]
}
textModulesData отображается в нижней части купона — удобно для промокода, который кассир вводит вручную как резерв при проблеме со сканером.
JWT с купоном
def generate_offer_jwt(offer_object: dict) -> str:
payload = {
"iss": service_account_email,
"aud": "google",
"typ": "savetowallet",
"iat": int(time.time()),
"payload": {
"offerObjects": [offer_object]
}
}
return jwt.encode(payload, private_key, algorithm="RS256")
Android: кнопка добавления
// Показываем кнопку только если Wallet доступен
walletClient.getPayApiAvailabilityStatus(
PayApiAvailabilityStatusRequest.newBuilder()
.setRequestType(PayApiAvailabilityStatusRequest.RequestType.SAVE_PASSES)
.build()
).addOnSuccessListener { status ->
binding.addCouponToWalletBtn.isVisible = status.isAvailable
}
// Добавление купона
fun addCouponToWallet(jwt: String) {
walletClient.savePassesViaIntent(
SavePassesRequest.newBuilder().setJwt(jwt).build()
) { result ->
result.intentSender?.let { sender ->
addToWalletLauncher.launch(
IntentSenderRequest.Builder(sender).build()
)
}
}
}
Деактивация купона после использования
После того как кассир сканирует купон, бекенд должен перевести объект в состояние EXPIRED:
def redeem_coupon(object_id: str):
service = build('walletobjects', 'v1', credentials=credentials)
service.offerobject().patch(
resourceId=object_id,
body={"state": "EXPIRED"}
).execute()
Пасс в Wallet пользователя автоматически получит плашку «Использован» без удаления из кошелька — важно для истории покупок.
Массовая выдача купонов
Для рассылки купонов большому числу пользователей не нужно генерировать JWT на каждого отдельно в момент нажатия кнопки. Можно предсоздать OfferObject через REST API и хранить objectId в базе. Кнопка «Добавить в Wallet» генерирует JWT уже с существующим objectId — Google вернёт intentSender для уже созданного объекта.
Сроки
1–3 дня. Шаблон акции + персонализированные купоны + деактивация — 2 дня. Массовая предгенерация объектов — дополнительно 0,5 дня. Стоимость рассчитывается индивидуально.







