如何结合Firebase身份认证使用支付中心
如已使用Firebase在应用程序中实现用户身份认证,可在Firebase侧生成一个支付令牌,然后将其传入应用程序的客户端侧来打开支付UI。
如使用此集成方案,需独立实现决定用户国家/地区和支付所使用的货币的逻辑。
集成过程:
- 创建项目。
- 注册发布商帐户并新建一个项目。后续步骤需要用到所创建项目的ID。
- 设置目录:
- 在艾克索拉侧创建一个商品目录。您可以手动添加商品或从Google Play或PlayFab导入。
- 使用
Store 库实现在应用程序客户端侧获取和显示目录。
- 设置商品购买:
- 通过Firebase云函数使用用户和商品数据在应用程序客户端侧创建一个订单。
- 使用
Payments 库实现在应用程序客户端侧打开支付UI。
要完成集成并开始接收真实付款,您需要签署与艾克索拉的许可协议。
您可以在集成的任意阶段签署协议,但请知晓审核过程最长可能需要3个工作日。
请使用样本Web应用程序作为示例来实现Firebase身份认证和支付中心的结合使用。样本Web应用程序的源代码在GitHub上提供。
创建项目
发布商帐户是配置艾克索拉功能以及对分析和交易进行操作的主要工具。
注册时指定的公司及应用程序信息将用于创建与艾克索拉许可协议的草稿以及生成合适解决方案的推荐。后续您可以更改这些数据,但在注册时提供正确的信息可以加快签署许可协议的速度。
要创建项目:
- 注册发布商帐户。
密码生成规则
发布商帐户的密码可以包含英文字母、数字和特殊字符,且必须至少包含:
- 8个字符
- 一个数字
- 一个大写字母
- 一个小写字母
为确保密码安全性,建议您:
- 每90天至少更改一次密码
- 使用与帐户中过去4个密码不同的新密码
- 使用与其他地方的密码不同的特殊密码
- 勿将密码保存在可以轻易访问的地方
- 使用密码管理器来保存您的密码
发布商帐户使用双因素认证,每次认证尝试都会发送验证码。
- 提供以下信息创建您的帐户:
- 您的名和姓。
- 您在公司中的职位。
- 公司名称。如果以个人身份注册帐户,请输入您的全名。
- 关于您和您的游戏或产品的详细信息链接(可选)。
- 您的国家/地区。
- 单击下一步。

- 要自动创建您的首个项目,请指定关于项目的主要信息:
- 选择项目类型:游戏、游戏平台或其他。
- 用英文输入项目名称。
- 选择一个或多个发布平台。
- 选择您正在使用或计划使用的盈利方式。
- 选择开发阶段。
- 如果您的项目类型是游戏,请选择其类型和游戏引擎。
- 添加您的游戏或产品链接(可选)。
- 单击完成。

项目创建后,您将被重定向到公司页面。创建的项目会显示在侧边栏中:
- 单击其名称开始集成艾克索拉产品和解决方案。
- 前往项目设置部分添加其他语言和本地化项目名称(可选)。

如果有多个游戏或产品,请为每个创建单独的项目。为此,在公司页面的侧边栏中,单击创建项目并指定必要信息。

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

设置目录
在发布商帐户中创建商品
您需要在艾克索拉侧创建一个目录。您可以手动添加商品或从App Store、Google Play或PlayFab导入商品。从Google Play导入时,一次最多可以导入100个商品。
以下说明提供的是设置虚拟物品的基本步骤。您可以在后来向目录添加其他商品(虚拟货币、捆绑包、游戏密钥等)、创建商品组、设置促销活动和区域价格等。
要向目录添加具有基本设置的虚拟物品:
- 在发布商帐户中打开您的项目,前往商品目录 > 虚拟物品部分。
- 在下拉菜单中选择创建物品。
- 在以下字段中完成物品的基本设置:
- 图片(可选)
- SKU(物品唯一ID)
- 物品名称
- 描述(可选)
- 指定物品价格:
- 将真实货币定价开关设置为开。
- 在默认货币字段,更改货币(可选)并指定物品价格。
- 如更改了默认货币字段中的货币,请在真实货币定价字段中选择同样的货币。
- 将物品状态更改为可用。
- 单击创建物品。
在应用程序客户端侧显示目录
- 将
Store 库添加到您的项目。方法是打开build.gradle,然后在依赖关系部分添加以下行:
1implementation("com.xsolla.android:store:latest.release")
- 在应用程序的客户端侧,添加显示产品目录的UI。
- 实现向艾克索拉服务器请求商品目录。
XStore.getVirtualItems方法来获取虚拟物品列表。您也可以使用其他示例(样本web应用程序的
- kotlin
1package com.xsolla.androidsample
2
3import androidx.appcompat.app.AppCompatActivity
4import android.os.Bundle
5import android.widget.Toast
6import androidx.recyclerview.widget.LinearLayoutManager
7import androidx.recyclerview.widget.RecyclerView
8import com.xsolla.android.store.XStore
9import com.xsolla.android.store.callbacks.GetVirtualItemsCallback
10import com.xsolla.android.store.entity.response.items.VirtualItemsResponse
11import com.xsolla.androidsample.adapter.BuyItemAdapter
12
13class StoreActivity : AppCompatActivity() {
14
15 private lateinit var itemsView: RecyclerView
16
17 override fun onCreate(savedInstanceState: Bundle?) {
18 super.onCreate(savedInstanceState)
19 setContentView(R.layout.activity_store)
20
21 XStore.init(<projectId>)
22
23 initUI()
24 loadVirtualItems()
25 }
26
27 private fun initUI() {
28 itemsView = findViewById(R.id.buy_recycler_view)
29 itemsView.layoutManager = LinearLayoutManager(this)
30 }
31
32 private fun loadVirtualItems() {
33 val parentActivity = this
34 XStore.getVirtualItems(object : GetVirtualItemsCallback {
35 override fun onSuccess(response: VirtualItemsResponse) {
36 itemsView.adapter = BuyItemAdapter(parentActivity, response.items.filter { item -> item.virtualPrices.isEmpty() && !item.isFree })
37 }
38
39 override fun onError(throwable: Throwable?, errorMessage: String?) {
40 showNotificationMessage(errorMessage ?: throwable?.javaClass?.name ?: "Error")
41 }
42 })
43 }
44
45 private fun showNotificationMessage(message: String) {
46 Toast.makeText(
47 baseContext,
48 message,
49 Toast.LENGTH_SHORT,
50 ).show()
51 }
52}
在脚本的XStore.init()初始化区块,指定可在发布商帐户项目名称旁边找到的项目ID。

设置商品购买
使用云函数创建订单
要使用用户和商品数据在艾克索拉侧创建订单,请添加使用为购买创建支付令牌API调用的云函数至项目。该调用将返回一个支付令牌,用于打开支付UI并进行购买。
限制:
- 需在请求支付令牌时传入用户国家/地区或用户IP地址。
- 如未在令牌中传入货币,则货币由国家/地区决定。
- 如在令牌中传入了货币,则用户用该货币进行支付。
要将云函数添加至项目:
- 安装Firebase CLI(命令行界面)。方法是运行以下CLI命令:
1npm install -g firebase-tools
- 要将您的项目关联至Firebase项目,请运行以下CLI命令来初始化Firebase项目:
1firebase init functions
- 按照安装程序的说明来配置设置:
- 选择一个现有代码库。
- 指定JavaScript作为创建云函数的语言。
- 安装依赖项。
- 打开
functions/index.js并进行修改:
- javascript
1// The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.
2const functions = require('firebase-functions/v1');
3
4const projectId = <projectId>;
5const apiKey = <apiKey>;
6
7exports.getXsollaPaymentToken = functions.https.onRequest((req, res) => {
8
9 const requestBody = req.body;
10 if (!requestBody) {
11 res.status(400).send('Request body is missing');
12 return;
13 }
14
15 const userId = requestBody.data.uid;
16 const email = requestBody.data.email;
17 const sku = requestBody.data.sku;
18 const returnUrl = requestBody.data.returnUrl;
19
20 const payload = {
21 user: {
22 id: {value: userId},
23 name: {
24 value: email
25 },
26 email: {
27 value: email
28 },
29 country: {
30 value: 'US',
31 allow_modify: false
32 }
33 },
34 purchase: {
35 items: [
36 {
37 sku: sku,
38 quantity: 1
39 }
40 ]
41 },
42 sandbox: true,
43 settings: {
44 language: 'en',
45 currency: 'USD',
46 return_url: returnUrl,
47 ui: {
48 theme: '63295aab2e47fab76f7708e3'
49 }
50 }
51 }
52
53 let url = `https://store.xsolla.com/api/v3/project/${projectId.toString()}/admin/payment/token`;
54
55 fetch(
56 url,
57 {
58 method: "POST",
59 headers: {
60 'Content-Type': 'application/json',
61 Authorization: `Basic ${btoa(`${projectId}:${apiKey}`)}`
62 },
63 body: JSON.stringify(payload)
64 },
65 )
66 .then(xsollaRes => {
67 // Handle the response data
68 if (xsollaRes.ok) {
69 return xsollaRes.json();
70 } else {
71 throw new Error(`HTTP request failed with status ${xsollaRes.status} and statusText: ${xsollaRes.statusText}`)
72 }
73 })
74 .then(data => {
75 res.send(JSON.stringify(data));
76 })
77 .catch(error => {
78 res.send(`Error = ${error}`);
79 });
80});
81
82exports.webhookFakeResponse = functions.https.onRequest((request, response) => {
83 response.status(200).send()
84})
- 在脚本中,指定变量的值:
projectId— 可在发布商帐户中项目名称旁边找到的项目ID。

apiKey— API密钥。密钥仅在创建它时在发布商帐户中显示一次,必须存储在己侧。您可以在以下部分中创建新的密钥:
- 要使用模拟器测试云函数,请运行CLI命令:
1firebase emulators:start
- 运行云函数后,您可以在应用程序客户端侧调用以下方法:
- 要在本地调用方法,请使用
https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken和https://localhost:5001/{firebase-project-id}/us-central1/webhookFakeResponseURL,其中{firebase-project-id}是Firebase项目ID(Firebase控制台 > Project Settings > Project ID)。
- 要在生产中部署云函数,请运行CLI命令:
1firebase deploy --only functions
- 在生产中部署后,即可通过
https://us-central1-{firebase-project-id}.cloudfunctions.net/getXsollaPaymentToken和https://us-central1-{firebase-project-id}.cloudfunctions.net/webhookFakeResponseURL调用方法,其中{firebase-project-id}是Firebase项目ID(Firebase控制台 > Project Settings > Project ID)。关于在生产环境中运行功能的详细信息,请参阅Firebase文档。
设置支付UI的启动
- 将
Payments 库添加到您的项目。方法是打开build.gradle,然后在依赖关系部分添加以下行:
1implementation("com.xsolla.android:payments:latest.release")
- 打开
AndroidManifest.xml,然后添加网络访问的权限:
- xml
1<uses-permission android:name="android.permission.INTERNET" />
- 添加订单创建逻辑(调用云函数的
XStore.getXsollaPaymentToken方法)并使用收到的支付令牌(XPayments.createIntentBuilder()类打开支付UI)。
- 要调用
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应用程序的
- kotlin
1package com.xsolla.androidsample.adapter
2
3import android.R.attr.duration
4import android.os.Handler
5import android.os.Looper
6import android.view.LayoutInflater
7import android.view.ViewGroup
8import android.widget.Toast
9import androidx.recyclerview.widget.RecyclerView
10import com.bumptech.glide.Glide
11import com.xsolla.android.payments.XPayments
12import com.xsolla.android.payments.data.AccessToken
13import com.xsolla.android.store.entity.response.items.VirtualItemsResponse
14import com.xsolla.androidsample.R
15import com.xsolla.androidsample.StoreActivity
16import org.json.JSONObject
17import java.io.BufferedReader
18import java.io.BufferedWriter
19import java.io.OutputStream
20import java.io.OutputStreamWriter
21import java.net.HttpURLConnection
22import java.net.URL
23
24
25class BuyItemAdapter(private val parentActivity: StoreActivity, private val items: List<VirtualItemsResponse.Item>) :
26 RecyclerView.Adapter<BuyItemViewHolder>() {
27 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BuyItemViewHolder {
28 return BuyItemViewHolder( LayoutInflater.from(parent.context)
29 .inflate(R.layout.buy_item_sample, parent, false))
30 }
31
32 override fun onBindViewHolder(holder: BuyItemViewHolder, position: Int) {
33 val item = items[position]
34 Glide.with(holder.view).load(item.imageUrl).into(holder.itemImage)
35 holder.itemName.text = item.name
36 holder.itemDescription.text = item.description
37 var priceText: String
38 if(item.virtualPrices.isNotEmpty()) {
39 priceText = "${item.virtualPrices[0].getAmountRaw()} ${item.virtualPrices[0].name}"
40 } else {
41 priceText = "${item.price?.getAmountRaw()} ${item.price?.currency.toString()}"
42 }
43
44 holder.itemPrice.text = priceText
45
46 holder.itemButton.setOnClickListener {
47 Thread {
48 purchase(item.sku!!)
49 }.start()
50 }
51 }
52
53 private fun purchase(sku: String) {
54
55 val uid = parentActivity.intent.getStringExtra("uid")
56 val email = parentActivity.intent.getStringExtra("email")
57
58 val jsonBody = JSONObject()
59 jsonBody.put("data", JSONObject().apply {
60 put("uid", uid)
61 put("email", email)
62 put("sku", sku)
63 put("returnUrl", "app://xpayment.${parentActivity.packageName}")
64 })
65
66 val connection = URL(https://localhost:5001/{firebase-project-id}/us-central1/getXsollaPaymentToken).openConnection() as HttpURLConnection
67 connection.requestMethod = "POST"
68 connection.setRequestProperty("Content-Type", "application/json")
69 connection.doOutput = true
70
71 val outputStream: OutputStream = connection.outputStream
72 val writer = BufferedWriter(OutputStreamWriter(outputStream))
73 writer.write(jsonBody.toString())
74 writer.flush()
75 writer.close()
76
77 val responseCode = connection.responseCode
78
79 if (responseCode == HttpURLConnection.HTTP_OK) {
80 val response = connection.inputStream.bufferedReader().use(BufferedReader::readText)
81 connection.disconnect()
82
83 val jsonObject = JSONObject(response)
84 val token = jsonObject.getString("token")
85 val orderId = jsonObject.getString("order_id")
86
87 val intent = XPayments.createIntentBuilder(parentActivity)
88 .accessToken(AccessToken(token))
89 .isSandbox(true)
90 .build()
91 parentActivity.startActivityForResult(intent, 1)
92 } else {
93 Handler(Looper.getMainLooper()).post {
94 showNotificationMessage("HTTP request failed with error: $responseCode")
95 }
96 }
97 }
98
99 override fun getItemCount() = items.size
100
101 private fun showNotificationMessage(message: String) {
102 Toast.makeText(
103 parentActivity,
104 message,
105 Toast.LENGTH_SHORT,
106 ).show()
107 }
108}
- 添加
onActivityResult()方法以处理支付结果。
示例(样本web应用程序的
- kotlin
1override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
2 super.onActivityResult(requestCode, resultCode, data)
3 if (requestCode == 1) {
4 val (status, _) = XPayments.Result.fromResultIntent(data)
5 when (status) {
6 XPayments.Status.COMPLETED -> showNotificationMessage("Payment completed")
7 XPayments.Status.CANCELLED -> showNotificationMessage("Payment canceled")
8 XPayments.Status.UNKNOWN -> showNotificationMessage("Payment error")
9 }
10 }
11 }
设置订单状态跟踪
您需要跟踪项目状态,以确保支付成功并向用户发放商品。
获取客户端侧订单状态
要订阅应用程序客户端侧状态更改,请调用XStore.getOrderStatus方法,并向方法传入以下参数:
listener—OrderStatusListener类型的侦听器对象。orderId— 从购物车下单、一键购买或用虚拟货币购买收到的订单ID。
关于方法的详细使用信息,请参阅跟踪订单状态部分。
在服务器侧获取订单状态
要在艾克索拉侧配置Webhook:
- 在发布商帐户中打开您的项目,然后前往项目设置 > Webhooks部分。
- 在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)。
- 复制并保存密钥字段的值。该密钥默认生成,并用于Webhook签名。如要更改该密钥,请单击更新图标。
- 单击启用Webhook。

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