--- title: Defold 中的内支付 brief: 应用内支付(也称内购买)允许用户付费以获得额外的内容或功能。本手册介绍了相关功能的 Defold API。 --- # 应用内支付 Defold 提供了方便的 iOS Appstore "in-app purchases" 和 Google Play 及 Amazon "in-app billing" 系统接口. Facebook Canvas 的 "game payments" 用于 Facebook Canvas 游戏. 这些服务能让你销售: * 标准应用内支付(一次性购买)的消耗品和非消耗品 * 订阅(自动循环购买) ::: sidenote 目前 Defold 接口可以与 Apple 的 Storekit 完整交互. 对于 Google Play 和 Facebook Canvas, 接口是一致的, 这有助于代码跨平台. 但是不同平台需要的流程有所不同. 而且注意目前没有针对 macOS 系统上 Mac Appstore 的支付接口. ::: 关于 Apple, Google, Amazon 和 Facebook 的详细文档参考: * [In-App Purchase Programming Guide](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction.html) * [Google Play In-app Billing documentation](http://developer.android.com/google/play/billing/index.html) * [Amazon In-app Purchase documentation](https://developer.amazon.com/public/apis/earn/in-app-purchasing) * [Facebook game payments documentation](https://developers.facebook.com/docs/payments) ## 安装扩展 首先要在 *game.project* 文件中加入内支付扩展依赖。最新稳定版本地址如下: ``` https://github.com/defold/extension-iap/archive/master.zip ``` 推荐使用 [正式发布版](https://github.com/defold/extension-iap/releases) 的 zip 连接作为依赖引用。 其 API 文档详见 [扩展项目首页](https://defold.github.io/extension-iap/)。 ## 使用静态报告测试 Google Play Billing Android 建议使用 Google Play 的静态报告来实现 IAP。这样在正式发布之前就可以确保内支付工作正常。测试用静态报告包括 4 个 ID: `android.test.purchased` : Google Play 报告模拟支付完成。此报告包含一个 JSON 字符串,内含模拟支付信息(比如模拟用户 ID 等)。 `android.test.canceled` : Google Play 报告模拟支付取消。这种情况可能因为付款时出现错误,比如信用卡无效,或者用户付款前订单被取消。 `android.test.refunded` : Google Play 报告模拟退款完成。 `android.test.item_unavailable` : Google Play 报告 app 可购买物品列表里找不到用户购买的项目。 ## 内支付准备流程 iOS 和 Android 内支付的准备流程差不多: 1. 注册为 Apple 或 Google Play 开发者。 2. 为目标平台进行项目配置。参考 [iOS 开发教程](/manuals/ios) 与 [Android 开发教程](/manuals/android)。 3. 为应用商店进行测试配置: - Android 上,使用 [Google Play Developer Console](https://play.google.com/apps/publish/)。 - iOS 上,使用 [iTunes Connect](https://itunesconnect.apple.com/)。注意你的 App ID(于 https://developer.apple.com 上的 "成员中心" 配置)要开启 "In-App Purchase" 选项。 ![iTunes Connect and Google Play Dev Console](images/iap/itunes_connect_google_play.png) 4. 对于 Google Play,你需要 _上传并发布_ 测试版 *.apk* 文件。对于 iTunes Connect,直到应用审批之前你 _不用上传_ 应用文件给 iTunes Connect。加入上传了未完成的应用文件给 iTunes Connect,Apple 会拒绝该应用。 5. 设置应用内消费品。 ![iTunes 消费品](images/iap/itunes_products.png) ![Google Play 消费品](images/iap/google_play_products.png) 6. 设置测试用账户。 - 在 iTunes Connect 页面 *用户与规则* 中可以设置专门用于 _沙盒环境_ 的模拟支付用户。要给应用做开发者证书签名然后在测试设备上登录 Appstore 沙盒账户进行测试。 - 在 Google Play Developer Console 上,选择 *设置 > 账户详情* 的 License Testing 部分设置测试用户 email。多个 email 用逗号分隔。然后测试者可以登录并购买消费品而不会真的扣款。 - 在 Google Play 上,还需要为测试者们建立 Google Group。Google 使用组来管理应用测试组成员。在 *Alpha Testing* 页面的 *Manage list of testers* 设置测试者 Google Group。应用上线前必须通过 Alpha 测试。 ![Alpha testers](images/iap/alpha_testers.png) Facebook 内支付准备流程: 1. 注册为 Facebook 开发者。登录 [Facebook for developers](https://developers.facebook.com/),在 "My Apps" 下 "Register as a developer",完成注册步骤。 2. Facebook 要求支持同步和异步两种支付方式。详情请见 [Payment overview](https://developers.facebook.com/docs/payments/overview) 3. 建立应用托管和回调服务器: * 建立托管应用的加密 canvas 地址。详情请见 [Games on Facebook](https://developers.facebook.com/docs/games/gamesonfacebook/hosting)。 * 建立回调服务器。详情请见 [Setting up your callback server](https://developers.facebook.com/docs/payments/realtimeupdates#yourcallbackserver)。 4. 配置应用。详情请见 [Facebook Developer Dashboard](https://developers.facebook.com/quickstarts/?platform=canvas)。 5. 加入测试用户。在应用面板的 "Canvas Payments" 部分进行设置。 6. 建立应用消费品。详情请见 [Defining products](https://developers.facebook.com/docs/payments/implementation-guide/defining-products/)。 ## 异步的内支付 API 内支付 API 是异步的,也就是说应用发送信息给服务器,然后能继续运行。等到从服务器传回的信息时,一个 _回调_ 函数会被调用,从中可以根据回调数据进行各种处理工作。 获得消费品列表: ```lua local COINS_ID = "com.defold.examples.coins" local LOGO_ID = "com.defold.examples.logo" local function product_list(self, products, error) if error == nil then for i,p in pairs(products) do print(p.ident) print(p.title) print(p.description) print(p.currency_code) print(p.price_string) end else print(error.error) end end function init(self) -- 初始化消费品列表(对于 Google Play 来说一次能获取 20 个) iap.list({ COINS_ID, LOGO_ID }, product_list) end ``` 对于正式交易,首先要注册交易结果监听器,然后在玩家购买时调用内支付函数: ```lua local function iap_listener(self, transaction, error) if error == nil then if transaction.state == iap.TRANS_STATE_PURCHASING then print("Purchasing...") elseif transaction.state == iap.TRANS_STATE_PURCHASED then print("Purchased!") elseif transaction.state == iap.TRANS_STATE_UNVERIFIED then print("Unverified!") elseif transaction.state == iap.TRANS_STATE_FAILED then print("Failed!") elseif transaction.state == iap.TRANS_STATE_RESTORED then print("Restored") end else print(error.error) end end function on_message(self, message_id, message, sender) ... -- 注册内支付结果监听器。 iap.set_listener(iap_listener) -- 买一个金币... iap.buy(COINS_ID) ... end ``` 操作系统会自动弹出支付界面。要是处于测试/沙盒环境下,界面中会明确标明。 ![Confirm purchase](images/iap/ios_confirm_purchase.png) ![Android purchase](images/iap/android_purchase.png) ![Confirm purchase](images/iap/ios_purchase_done.png) ## 实时付款 大多数付款系统都是实时的。支付完成时应用会收到一个消息,TRANS_STATE_PURCHASED。这是交易的最终状态,表明对于这笔交易不会再有其他消息了。 ## 非实时付款 有的付款系统需要非实时的支持。也就是说你的应用只在付款开始时收到一个消息。为了验证付款完成与否,需要你的服务器(或者客户端)与付款系统进行更多的交流。 这种情况下支付完成时应用会收到一个 TRANS_STATE_UNVERIFIED 消息(而不是 TRANS_STATE_PURCHASED)。这也是交易的最终状态,表明对于这笔交易不会再有其他消息了。 ## 购买交付 玩家购买消费品之后,应用有义务告知玩家支付已完成(比如服务器验证支付成功之后)。 内支付支持 auto-completion,也就是自动产生支付完成的消息(默认设置)。也可以在项目配置里关闭 auto-completion。支付完成时手动调用 `iap.finish()`,来产生完成消息。 ### 消耗品与非消耗品 Google Play 应用商店只支持消耗品。必须使用非消耗品的话就关闭 auto-completion 并且不要调用完成函数。这样在 `iap.set_listener()` 被调用时,消费品购买总是保持着购买中的状态。 Apple App Store 支持非消耗品,也就是说购买完成时才向玩家提供消费品交付。这种情况下 auto-completion 可以开启也可以关闭(比如你需要自己验证支付成功)之后调用 `iap.finish()`。 ## 支付收据 收据是一堆加密数据,可以把它发送给 App Store 来验证付款是否真正完成。这是用开发者服务器验证付款必须使用的东西。 ## 除错 Android `iap.list()` 返回 "failed to fetch product" : 你需要上传 *.apk* 文件到 Google Play Developer Console 的 alpha 或者 beta 频道。还要注意确保你的设配上 _日期和时间_ 是正确的。 Android (Google Play) `iap.list()` 只返回 20 个消费品 : Google 有 [每次请求返回最多 20 个消费品的限制](https://github.com/googlesamples/android-play-billing/blob/master/TrivialDrive/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl#L62)。可以多次调用 `iap.list()` 然后合并返回结果以实现 20 个以上的消费品列表。 iOS `iap.list()` 返回空值 : 确保你的账户是 iOS 付款应用账户,并且签署好一切所需文件。没有经过认证的话,你的 iOS 应用内支付(甚至测试支付)操作不会正确执行。 检查 "Member Center" 上的应用 ID 是否开启了 in-app purchases(检查 "Member Center" 里 "Certificates, Identifiers & Profiles" 的 "Enabled Services:" 项)并且该应用(或者调试版应用)已用供应商档案进行了签名而且签名没过期。 内支付消费品信息传到沙盒环境需要几小时时间,请耐心等待。 iOS `iap.list()` 产生错误日志 "Unexpected callback set" : `iap.list()` 调用不可以嵌套。从 `iap.list()` 调用 `iap.list()` 就会产生这个错误日志。 在 iOS 上, "price_string" 里有个 '~' 字符 : '~' 是未找到字体的字符占位符. 调用 `iap.list()` 返回的数据使用 _不间断空白_ (`\u00a0`) 分割价格和货币符号并填充 "price_string" 变量. 使用 GUI 组件渲染的话, 需要在字体 *extra_characters* 属性里设置所需字符. 在 macOS 上按 Option + SPACE 就是不间断空白. 详情请见 http://en.wikipedia.org/wiki/Non-breaking_space.