支持客户和处理退款
Support customers and handle refunds
2021年6月8日
一句话判断
Apple 终于允许开发者在退款流程中有话语权了——你可以发起退款争议、查看退款原因、甚至主动发起退款,这是 App Store 内购管理体系多年来最重要的改变之一。
这场 Session 讲了什么
Session 介绍了 App Store 在客户支持和退款处理方面的新功能。涵盖了三个核心能力:退款争议(Dispute Refunds)、消费数据分析(Consumption API)、和主动退款(Refund API)。过去退款是 Apple 单方面决定的——用户申请退款、Apple 审批、钱被退回,开发者只能事后看到一条退款记录。现在你可以在退款审核期间提交证据争取保留这笔收入。
Consumption API 让你向 Apple 提供用户对已购内容的消费数据——比如这个用户是否使用了购买的虚拟物品、使用了多少次、是否已经完成关卡等。Apple 会综合你的数据和用户的退款理由来做出决定。Refund API 更直接——你可以主动为不满意的用户发起退款,不需要用户自己走 Apple 的退款流程。
值得深挖的点
Consumption API 的数据维度
当用户申请退款时,Apple 会通过 Server Notifications V2 发送一个 CONSUMPTION_REQUEST 通知。你的服务器需要在 12 小时内通过 Consumption API 提交该用户的消费数据。提交的数据维度包括:sampleContentProvided(是否提供了样本内容)、userStatus(用户账户状态,是否被封禁)、consumptionStatus(消费状态:已消费/部分消费/未消费)、deliveryStatus(交付状态:已交付/未交付/部分交付)、lifetimeDollarsPurchased(用户历史消费总额)、lifetimeDollarsRefunded(历史退款总额)、platform(购买平台)、accountTenure(账户注册时长)。
退款争议的策略选择
不是所有退款都值得争议。Session 建议对以下情况提起争议:用户已深度使用了购买的内容但仍然申请退款(恶意退款嫌疑)、购买金额较大且退款理由不合理。对于小额退款(比如 0.99 美元的内购),争议的成本可能大于收益。你需要根据业务特点制定策略。
代码片段
# 接收到 CONSUMPTION_REQUEST 通知后提交消费数据
import jwt
import requests
def submit_consumption_data(transaction_id, user_data):
"""向 Apple 提交用户的消费信息,辅助退款审核决策"""
jwt_token = create_jwt()
url = f"https://api.storekit.itunes.apple.com/v1/transactions/{transaction_id}/consumption"
payload = {
# 用户是否消费了购买的内容
"consumptionStatus": "CONSUMED",
# 用户账户的注册时长
"accountTenure": "DAY_0_TO_3",
# 用户在 App 中的历史消费总额
"lifetimeDollarsPurchased": "HUNDRED_TO_THOUSAND",
# 用户的历史退款总额
"lifetimeDollarsRefunded": "ZERO",
# 用户账户状态(正常/被封禁/受限)
"userStatus": "ACTIVE",
# 内容交付状态
"deliveryStatus": "DELIVERED",
# 购买平台
"platform": "IOS",
# 是否提供了样本内容(试用版)
"sampleContentProvided": True,
# 用户的自动续期状态
"autoRenewStatus": "AUTO_RENEW_ENABLED",
# 用户在免费试用期间是否进行了购买
"inTrialPeriod": False
}
headers = {"Authorization": f"Bearer {jwt_token}"}
response = requests.put(url, json=payload, headers=headers)
return response.status_code == 202 # 接受成功返回 202
# 发起退款争议
def dispute_refund(refund_id, reason, evidence):
"""对已批准的退款提起争议"""
jwt_token = create_jwt()
url = f"https://api.storekit.itunes.apple.com/v1/refunds/{refund_id}/dispute"
payload = {
# 争议原因
"reason": "UNJUSTIFIED_REFUND",
# 支持争议的证据说明
"explanation": "用户已消费所有购买的虚拟货币,并且在退款前仍在活跃使用应用",
# 附加证据(可选)
"attachments": [
{
"filename": "usage_logs.pdf",
"contentType": "application/pdf",
"data": base64_encoded_file_content
}
]
}
headers = {"Authorization": f"Bearer {jwt_token}"}
response = requests.post(url, json=payload, headers=headers)
return response.json()
# 主动为用户发起退款
def initiate_refund(transaction_id, reason="CUSTOMER_REQUEST"):
"""开发者主动发起退款,不需要用户走 Apple 流程"""
jwt_token = create_jwt()
url = "https://api.storekit.itunes.apple.com/v1/refunds"
payload = {
"originalTransactionId": transaction_id,
"reason": reason # CUSTOMER_REQUEST, OTHER
}
headers = {"Authorization": f"Bearer {jwt_token}"}
response = requests.post(url, json=payload, headers=headers)
# 返回退款请求的状态
return response.json()
最佳实践
- 收到
CONSUMPTION_REQUEST通知后,务必在 12 小时内提交数据。超时后 Apple 会按照默认逻辑做决策,你失去了发言权。 - 在你的用户数据库中预先维护消费统计数据(历史消费额、退款次数、使用频率),这样收到消费请求时可以快速组装数据提交。
- 主动退款是一个很好的客户服务工具——与其让不满的用户去 Apple 投诉(可能影响你的退款率指标),不如主动帮他们退款,留下好印象。
还有什么值得关注
- Apple 会根据开发者的退款率调整审核优先级——退款率过高的 App 可能面临更严格的审核。
REFUND_DECLINED通知表示你提交的争议成功阻止了退款,记得在此时恢复用户的访问权限。- Consumption API 的 sandbox 环境可以模拟各种退款场景,建议在上线前完整测试。