如何结合Firebase身份认证使用支付中心

如已使用Firebase在应用程序中实现用户身份认证,可在Firebase侧生成一个支付令牌,然后将其传入应用程序的客户端侧来打开支付UI。

如使用此集成方案,需独立实现决定用户国家/地区和支付所使用的货币的逻辑。

集成过程:

  1. 创建项目
  1. 注册发布商帐户并新建一个项目。后续步骤需要用到所创建项目的ID。

  1. 设置目录
    • 在艾克索拉侧创建一个商品目录。您可以手动添加商品或从Google Play或PlayFab导入。
    • 使用Store库实现在应用程序客户端侧获取和显示目录。

  1. 设置商品购买
    • 通过Firebase云函数使用用户和商品数据在应用程序客户端侧创建一个订单。
    • 使用Payments库实现在应用程序客户端侧打开支付UI。

  1. 设置订单状态跟踪

注意

要完成集成并开始接收真实付款,您需要签署与艾克索拉的许可协议。

您可以在集成的任意阶段签署许可协议,但请知晓审核过程最长可能需要3个工作日。

请使用样本Web应用程序作为示例来实现Firebase身份认证和支付中心的结合使用。样本Web应用程序的源代码在GitHub上提供。

创建项目

注册发布商帐户

发布商帐户是配置艾克索拉功能以及对分析和交易进行操作的主要工具。

注册时指定的公司及应用程序信息将用于创建与艾克索拉许可协议的草稿以及生成合适解决方案的推荐。后续您可以更改这些数据,但在注册时提供正确的信息可以加快签署许可协议的速度。

要注册,请前往发布商帐户,然后创建一个帐户。

注:

发布商帐户的密码可以由英文字母、数字和特殊字符组成,必须包含至少:

  • 8个字符
  • 一个数字
  • 一个大写字母
  • 一个小写字母

为确保密码安全性,建议您:

  • 每90天至少更改一次密码
  • 使用与帐户中过去4个密码不同的新密码
  • 使用与其他地方的密码不同的特殊密码
  • 勿将密码保存在可以轻易访问的地方
  • 使用密码管理器来保存您的密码

发布商帐户使用双因素认证,每次尝试认证时会向您发送一个验证码。

在发布商帐户中创建项目

如有多个应用程序,建议您为每个应用程序创建单独的项目。艾克索拉会根据项目创建过程中指定的数据为您推荐适合的解决方案。

要创建新项目:

  1. 打开发布商帐户
  2. 在侧边栏中,单击创建项目

  1. 用英文输入您的项目名称(必需)。

注:
创建项目后,可在项目设置部分中添加其他语言和本地化项目名称。

  1. 选择游戏的一个或多个发布平台(必需)。
  2. 添加游戏的链接。如游戏没有网站,请添加一个包含游戏信息的资源链接(必需)。
  3. 选择游戏引擎。
  4. 选择使用或打算使用的盈利方式。
  5. 指定游戏是否已发布。如游戏尚未发布,则指定计划发布日期。
  6. 单击创建项目。您将看到一个包含推荐艾克索拉推荐产品的页面。

集成过程中需提供项目ID,可在发布商帐户中项目名称的旁边找到。

设置目录

在发布商帐户中创建商品

注意

您需要在艾克索拉侧创建一个目录。您可以手动添加商品从Google Play或PlayFab导入商品。从Google Play导入时,一次最多可以导入100个商品。

以下说明提供的是设置虚拟物品的基本步骤。您可以在后来向目录添加其他商品(虚拟货币、捆绑包、游戏密钥等)、创建商品组、设置促销活动和区域价格等。

要向目录添加具有基本设置的虚拟物品:

  1. 发布商帐户中打开您的项目。
  2. 在侧边栏中单击商店
  3. 虚拟物品窗格中,单击连接
  4. 在下拉列表中,选择创建物品

  1. 在以下字段中完成物品的基本设置:
    • 图片(可选)
    • SKU(物品唯一ID)
    • 物品名称
    • 描述(可选)

  1. 指定物品价格:
    1. 真实货币定价开关设置为
    2. 默认货币字段,更改货币(可选)并指定物品价格。
    3. 如更改了默认货币字段中的货币,请在真实货币定价字段中选择同样的货币。

注:
为确保获取目录的API调用正常工作,请确保所有物品的默认货币和指定价格的货币列表一致。

  1. 将物品状态更改为可用

  1. 单击创建物品

在应用程序客户端侧显示目录

  1. Store库添加到您的项目。方法是打开build.gradle,然后在依赖关系部分添加以下行:

Copy
Full screen
Small screen
    implementation("com.xsolla.android:store:latest.release")
    

    1. 在应用程序的客户端侧,添加显示产品目录的UI。
    2. 实现向艾克索拉服务器请求商品目录。

    注:
    示例中使用了XStore.getVirtualItems方法来获取虚拟物品列表。您也可以使用其他Store库方法来获取目录商品的信息。

    示例(样本web应用程序的StoreActivity类):

    Copy
    Full screen
    Small screen
    package com.xsolla.androidsample
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.widget.Toast
    import androidx.recyclerview.widget.LinearLayoutManager
    import androidx.recyclerview.widget.RecyclerView
    import com.xsolla.android.store.XStore
    import com.xsolla.android.store.callbacks.GetVirtualItemsCallback
    import com.xsolla.android.store.entity.response.items.VirtualItemsResponse
    import com.xsolla.androidsample.adapter.BuyItemAdapter
    
    class StoreActivity : AppCompatActivity() {
    
        private lateinit var itemsView: RecyclerView
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_store)
    
            XStore.init(<projectId>)
    
            initUI()
            loadVirtualItems()
        }
    
        private fun initUI() {
            itemsView = findViewById(R.id.buy_recycler_view)
            itemsView.layoutManager = LinearLayoutManager(this)
        }
    
        private fun loadVirtualItems() {
            val parentActivity = this
            XStore.getVirtualItems(object : GetVirtualItemsCallback {
                override fun onSuccess(response: VirtualItemsResponse) {
                    itemsView.adapter = BuyItemAdapter(parentActivity, response.items.filter { item -> item.virtualPrices.isEmpty() && !item.isFree })
                }
    
                override fun onError(throwable: Throwable?, errorMessage: String?) {
                    showNotificationMessage(errorMessage ?: throwable?.javaClass?.name ?: "Error")
                }
            })
        }
    
        private fun showNotificationMessage(message: String) {
            Toast.makeText(
                baseContext,
                message,
                Toast.LENGTH_SHORT,
            ).show()
        }
    }
    

    在脚本的XStore.init()初始化区块,指定可在发布商帐户项目名称旁边找到的项目ID。

    设置商品购买

    使用云函数创建订单

    要使用用户和商品数据在艾克索拉侧创建订单,请添加使用为购买创建支付令牌API调用的云函数至项目。该调用将返回一个支付令牌,用于打开支付UI并进行购买。

    限制:

    • 需在请求支付令牌时传入用户国家/地区或用户IP地址。
    • 如未在令牌中传入货币,则货币由国家/地区决定。
    • 如在令牌中传入了货币,则用户用该货币进行支付。

    注:
    您需要先创建并初始化一个Firebase项目,然后使用Firebase激活用户认证。关于上述步骤的详细信息,请参阅以下Firebase说明:

    要将云函数添加至项目:

    1. 安装Firebase CLI(命令行界面)。方法是运行以下CLI命令:

    Copy
    Full screen
    Small screen
      npm install -g firebase-tools
      

      1. 要将您的项目关联至Firebase项目,请运行以下CLI命令来初始化Firebase项目:

      Copy
      Full screen
      Small screen
        firebase init functions
        

        1. 按照安装程序的说明来配置设置:
          1. 选择一个现有代码库。
          2. 指定JavaScript作为创建云函数的语言。
          3. 安装依赖项。

        1. 打开functions/index.js并进行修改:

        Copy
        Full screen
        Small screen
        // The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.
        const functions = require('firebase-functions/v1');
        
        const projectId = <projectId>;
        const apiKey = <apiKey>;
        
        exports.getXsollaPaymentToken = functions.https.onRequest((req, res) => {
        
          const requestBody = req.body;
            if (!requestBody) {
              res.status(400).send('Request body is missing');
              return;
            }
        
          const userId = requestBody.data.uid;
          const email = requestBody.data.email;
          const sku = requestBody.data.sku;
          const returnUrl = requestBody.data.returnUrl;
        
          const payload = {
            user: {
              id: {value: userId},
              name: {
                value: email
              },
              email: {
                value: email
              },
              country: {
                value: 'US',
                allow_modify: false
              }
            },
            purchase: {
              items: [
                {
                  sku: sku,
                  quantity: 1
                }
              ]
            },
            sandbox: true,
            settings: {
              language: 'en',
              currency: 'USD',
              return_url: returnUrl,
              ui: {
                theme: '63295aab2e47fab76f7708e3'
              }
            }
          }
        
          let url = "https://store.xsolla.com/api/v3/project/" + projectId.toString() + "/admin/payment/token";
        
          fetch(
            url,
            {
              method: "POST",
              headers: {
                'Content-Type': 'application/json',
                Authorization: 'Basic ' + btoa(`${projectId}:${apiKey}`)
              },
              body: JSON.stringify(payload)
            },
          )
          .then(xsollaRes => {
            // Handle the response data
            if (xsollaRes.ok) {
              return xsollaRes.json();
            } else {
              throw new Error(`HTTP request failed with status ${xsollaRes.status} and statusText: ${xsollaRes.statusText}`)
            }
          })
          .then(data => {
            res.send(JSON.stringify(data));
          })
          .catch(error => {
            res.send("Error = " + error);
          });
        });
        
        exports.webhookFakeResponse = functions.https.onRequest((request, response) => {
          response.status(200).send()
        })
        

        1. 在脚本中,指定变量的值:
          • projectId — 可在发布商帐户中项目名称旁边找到的项目ID。

          • apiKey — API密钥。密钥仅在创建它时在发布商帐户中显示一次,必须存储在己侧。您可以在以下部分中创建新的密钥:
            • 公司设置 > API密钥
            • 项目设置 > API密钥

        1. 要使用模拟器测试云函数,请运行CLI命令:

        Copy
        Full screen
        Small screen
          firebase emulators:start
          

          1. 运行云函数后,您可以在应用程序客户端侧调用以下方法:
            • getXsollaPaymentToken — 返回用于打开支付界面的支付令牌。
            • webhookFakeResponse — 在对支付Webhook的响应中发送HTTP代码200。该方法不包含购买验证逻辑:请仅在测试中使用。对于Webhook的完整列表以及常规使用信息,请参阅Webhook文档

          1. 要在本地调用方法,请使用https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentTokenhttps://localhost:5001/{firebase-project-id}/us-central1/webhookFakeResponse URL,其中{firebase-project-id}是Firebase项目ID(Firebase控制台 > Project Settings > Project ID)。

          1. 要在生产中部署云函数,请运行CLI命令:

          Copy
          Full screen
          Small screen
            firebase deploy --only functions
            

            1. 在生产中部署后,即可通过https://us-central1-{firebase-project-id}.cloudfunctions.net/getXsollaPaymentTokenhttps://us-central1-{firebase-project-id}.cloudfunctions.net/webhookFakeResponse URL调用方法,其中{firebase-project-id}是Firebase项目ID(Firebase控制台 > Project Settings > Project ID)。关于在生产环境中运行功能的详细信息,请参阅Firebase文档

            设置支付UI的启动

            1. Payments库添加到您的项目。方法是打开build.gradle,然后在依赖关系部分添加以下行:

            Copy
            Full screen
            Small screen
              implementation("com.xsolla.android:payments:latest.release")
              

              1. 打开AndroidManifest.xml,然后添加网络访问的权限:

              Copy
              Full screen
              Small screen
              <uses-permission android:name="android.permission.INTERNET" />
              

              1. 添加订单创建逻辑(调用云函数的XStore.getXsollaPaymentToken方法)并使用收到的支付令牌(XPayments.createIntentBuilder()类打开支付UI)。

              1. 要调用getXsollaPaymentToken方法,请提供以下URL之一,其中{firebase-project-id}是Firebase项目ID(Firebase控制台 > Project Settings > Project ID):
                • 对于本地访问 — https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken
                • 对于生产环境中的访问 — https://us-central1-{firebase-project-id}.cloudfunctions.net/getXsollaPaymentToken

              示例(样本web应用程序的BuyItemAdapter类):

              Copy
              Full screen
              Small screen
              package com.xsolla.androidsample.adapter
              
              import android.R.attr.duration
              import android.os.Handler
              import android.os.Looper
              import android.view.LayoutInflater
              import android.view.ViewGroup
              import android.widget.Toast
              import androidx.recyclerview.widget.RecyclerView
              import com.bumptech.glide.Glide
              import com.xsolla.android.payments.XPayments
              import com.xsolla.android.payments.data.AccessToken
              import com.xsolla.android.store.entity.response.items.VirtualItemsResponse
              import com.xsolla.androidsample.R
              import com.xsolla.androidsample.StoreActivity
              import org.json.JSONObject
              import java.io.BufferedReader
              import java.io.BufferedWriter
              import java.io.OutputStream
              import java.io.OutputStreamWriter
              import java.net.HttpURLConnection
              import java.net.URL
              
              
              class BuyItemAdapter(private val parentActivity: StoreActivity, private val items: List<VirtualItemsResponse.Item>) :
                  RecyclerView.Adapter<BuyItemViewHolder>() {
                  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BuyItemViewHolder {
                      return BuyItemViewHolder( LayoutInflater.from(parent.context)
                          .inflate(R.layout.buy_item_sample, parent, false))
                  }
              
                  override fun onBindViewHolder(holder: BuyItemViewHolder, position: Int) {
                      val item = items[position]
                      Glide.with(holder.view).load(item.imageUrl).into(holder.itemImage)
                      holder.itemName.text = item.name
                      holder.itemDescription.text = item.description
                      var priceText: String
                      if(item.virtualPrices.isNotEmpty()) {
                          priceText = item.virtualPrices[0].getAmountRaw() + " " + item.virtualPrices[0].name
                      } else {
                          priceText = item.price?.getAmountRaw() + " " + item.price?.currency.toString()
                      }
              
                      holder.itemPrice.text = priceText
              
                      holder.itemButton.setOnClickListener {
                          Thread {
                              purchase(item.sku!!)
                          }.start()
                      }
                  }
              
                  private fun purchase(sku: String) {
              
                      val uid = parentActivity.intent.getStringExtra("uid")
                      val email = parentActivity.intent.getStringExtra("email")
              
                      val jsonBody = JSONObject()
                      jsonBody.put("data", JSONObject().apply {
                          put("uid", uid)
                          put("email", email)
                          put("sku", sku)
                          put("returnUrl", "app://xpayment." + parentActivity.packageName)
                      })
              
                      val connection = URL(https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken).openConnection() as HttpURLConnection
                      connection.requestMethod = "POST"
                      connection.setRequestProperty("Content-Type", "application/json")
                      connection.doOutput = true
              
                      val outputStream: OutputStream = connection.outputStream
                      val writer = BufferedWriter(OutputStreamWriter(outputStream))
                      writer.write(jsonBody.toString())
                      writer.flush()
                      writer.close()
              
                      val responseCode = connection.responseCode
              
                      if (responseCode == HttpURLConnection.HTTP_OK) {
                          val response = connection.inputStream.bufferedReader().use(BufferedReader::readText)
                          connection.disconnect()
              
                          val jsonObject = JSONObject(response)
                          val token = jsonObject.getString("token")
                          val orderId = jsonObject.getString("order_id")
              
                          val intent = XPayments.createIntentBuilder(parentActivity)
                              .accessToken(AccessToken(token))
                              .isSandbox(true)
                              .build()
                          parentActivity.startActivityForResult(intent, 1)
                      } else {
                          Handler(Looper.getMainLooper()).post {
                              showNotificationMessage("HTTP request failed with error: $responseCode")
                          }
                      }
                  }
              
                  override fun getItemCount() = items.size
              
                  private fun showNotificationMessage(message: String) {
                      Toast.makeText(
                          parentActivity,
                          message,
                          Toast.LENGTH_SHORT,
                      ).show()
                  }
              }
              

              1. 添加onActivityResult()方法以处理支付结果。

              示例(样本web应用程序的StoreActivity类):

              Copy
              Full screen
              Small screen
              override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
                      super.onActivityResult(requestCode, resultCode, data)
                      if (requestCode == 1) {
                          val (status, _) = XPayments.Result.fromResultIntent(data)
                          when (status) {
                              XPayments.Status.COMPLETED -> showNotificationMessage("Payment completed")
                              XPayments.Status.CANCELLED -> showNotificationMessage("Payment canceled")
                              XPayments.Status.UNKNOWN -> showNotificationMessage("Payment error")
                          }
                      }
                  }
              

              设置订单状态跟踪

              您需要跟踪项目状态,以确保支付成功并向用户发放商品。

              获取客户端侧订单状态

              要订阅应用程序客户端侧状态更改,请调用XStore.getOrderStatus方法,并向方法传入以下参数:

              • listenerOrderStatusListener类型的侦听器对象。
              • orderId — 从购物车下单、一键购买或用虚拟货币购买收到的订单ID。

              关于方法的详细使用信息,请参阅跟踪订单状态部分。

              在服务器侧获取订单状态

              注意

              通过本SDK,您可以在应用程序的客户端侧跟踪订单状态。但是,我们建议您设置支付Webhook处理程序在应用程序后端接收订单信息。这样您可以实现对完成的购买的额外验证。

              关于Webhook的完整列表及常规使用信息,请参阅Webhook文档

              要在艾克索拉侧配置Webhook:

              1. 发布商帐户中打开您的项目。
              2. 在侧边栏中单击项目设置,然后前往Webhooks部分。
              3. Webhook服务器字段中,输入艾克索拉要向其发送Webhook的URL。

              注:

              进行测试时,可指定https://us-central1-{firebase-project-id}.cloudfunctions.net/webhookFakeResponse,其中{firebase-project-id}是Firebase项目ID(Firebase控制台 > Project Settings > Project ID)。本例中,Firebase模拟了对Webhook的成功处理。对于真实项目,您需要添加购买验证逻辑。

              要测试Webhook,您还可以选择任意专门网站(如webhook.site)或平台(如ngrok)。

              1. 复制并保存密钥字段的值。该密钥默认生成,并用于Webhook签名。如要更改该密钥,请单击更新图标。
              2. 单击启用Webhook

              本文对您的有帮助吗?
              谢谢!
              我们还有其他可改进之处吗? 留言
              非常抱歉
              请说明为何本文没有帮助到您。 留言
              感谢您的反馈!
              我们会查看您的留言并运用它改进用户体验。
              上次更新时间: 2024年10月3日

              发现了错别字或其他内容错误? 请选择文本,然后按Ctrl+Enter。

              报告问题
              我们非常重视内容质量。您的反馈将帮助我们做得更好。
              请留下邮箱以便我们后续跟进
              感谢您的反馈!