feat: 新增前端支付模块
- 新增payment/支付管理(PaymentManager、微信/支付宝Handler) - 新增wxapi/WXPayEntryActivity微信支付回调 - 扩展ApiService、PaymentModels、PaymentRepository Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
package com.huaga.life_echo.data.repository
|
package com.huaga.life_echo.data.repository
|
||||||
|
|
||||||
import com.huaga.life_echo.network.ApiService
|
import com.huaga.life_echo.network.ApiService
|
||||||
|
import com.huaga.life_echo.network.models.CreatePaymentOrderResponse
|
||||||
import com.huaga.life_echo.network.models.OrderDto
|
import com.huaga.life_echo.network.models.OrderDto
|
||||||
|
import com.huaga.life_echo.network.models.PaymentOrderStatusResponse
|
||||||
import com.huaga.life_echo.network.models.PlanDto
|
import com.huaga.life_echo.network.models.PlanDto
|
||||||
import com.huaga.life_echo.network.models.QuotaCheckDto
|
import com.huaga.life_echo.network.models.QuotaCheckDto
|
||||||
|
import com.huaga.life_echo.network.models.TestSubscriptionResponse
|
||||||
|
|
||||||
class PaymentRepository(
|
class PaymentRepository(
|
||||||
private val apiService: ApiService
|
private val apiService: ApiService
|
||||||
@@ -31,4 +34,37 @@ class PaymentRepository(
|
|||||||
suspend fun getOrderById(orderId: String): Result<OrderDto> {
|
suspend fun getOrderById(orderId: String): Result<OrderDto> {
|
||||||
return apiService.getOrderById(orderId)
|
return apiService.getOrderById(orderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 支付订单(微信/支付宝) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建支付订单(获取支付参数)
|
||||||
|
*/
|
||||||
|
suspend fun createPaymentOrder(
|
||||||
|
planId: String,
|
||||||
|
paymentMethod: String
|
||||||
|
): Result<CreatePaymentOrderResponse> {
|
||||||
|
return apiService.createPaymentOrder(planId, paymentMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询支付订单状态
|
||||||
|
*/
|
||||||
|
suspend fun getPaymentOrderStatus(orderId: String): Result<PaymentOrderStatusResponse> {
|
||||||
|
return apiService.getPaymentOrderStatus(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支付订单列表
|
||||||
|
*/
|
||||||
|
suspend fun getPaymentOrders(): Result<List<PaymentOrderStatusResponse>> {
|
||||||
|
return apiService.getPaymentOrders()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试订阅开关(仅当服务端开启时可用)
|
||||||
|
*/
|
||||||
|
suspend fun setTestSubscription(action: String, planId: String = "pro"): Result<TestSubscriptionResponse> {
|
||||||
|
return apiService.setTestSubscription(action, planId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,8 +237,76 @@ class ApiService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 支付订单API(微信/支付宝) ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建支付订单
|
||||||
|
* 返回微信支付参数或支付宝订单字符串
|
||||||
|
*/
|
||||||
|
suspend fun createPaymentOrder(
|
||||||
|
planId: String,
|
||||||
|
paymentMethod: String
|
||||||
|
): Result<CreatePaymentOrderResponse> {
|
||||||
|
return try {
|
||||||
|
val response = client.post("$BASE_URL/api/payment/create-order") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(CreatePaymentOrderRequest(planId, paymentMethod))
|
||||||
|
}
|
||||||
|
Result.success(response.body())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询支付订单状态
|
||||||
|
*/
|
||||||
|
suspend fun getPaymentOrderStatus(orderId: String): Result<PaymentOrderStatusResponse> {
|
||||||
|
return try {
|
||||||
|
val response = client.get("$BASE_URL/api/payment/order/$orderId/status") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
Result.success(response.body())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支付订单列表(新接口)
|
||||||
|
*/
|
||||||
|
suspend fun getPaymentOrders(): Result<List<PaymentOrderStatusResponse>> {
|
||||||
|
return try {
|
||||||
|
val response = client.get("$BASE_URL/api/payment/orders") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
}
|
||||||
|
Result.success(response.body())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 用户相关API ====================
|
// ==================== 用户相关API ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试订阅开关(仅当服务端 ENABLE_TEST_SUBSCRIPTION=1 时可用)
|
||||||
|
* 用于微信支付审核通过前模拟付费通过,体验订阅额度。
|
||||||
|
*/
|
||||||
|
suspend fun setTestSubscription(
|
||||||
|
action: String,
|
||||||
|
planId: String = "pro"
|
||||||
|
): Result<TestSubscriptionResponse> {
|
||||||
|
return try {
|
||||||
|
val response = client.post("$BASE_URL/api/user/test-subscription") {
|
||||||
|
contentType(ContentType.Application.Json)
|
||||||
|
setBody(TestSubscriptionRequest(action = action, planId = planId))
|
||||||
|
}
|
||||||
|
Result.success(response.body())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getUserProfile(): Result<UserProfileDto> {
|
suspend fun getUserProfile(): Result<UserProfileDto> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.get("$BASE_URL/api/user/profile") {
|
val response = client.get("$BASE_URL/api/user/profile") {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.huaga.life_echo.network.models
|
package com.huaga.life_echo.network.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,19 +19,22 @@ data class PlanBenefitDto(
|
|||||||
val features: List<String> = emptyList() // 功能列表
|
val features: List<String> = emptyList() // 功能列表
|
||||||
)
|
)
|
||||||
|
|
||||||
// 套餐信息DTO
|
// 套餐信息DTO(与后端 /api/plans 返回字段对应)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PlanDto(
|
data class PlanDto(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val displayName: String,
|
@SerialName("display_name") val displayName: String,
|
||||||
val price: Double,
|
val price: Double,
|
||||||
val currency: String = "CNY",
|
val currency: String = "CNY",
|
||||||
val billingCycle: String, // "monthly", "yearly"
|
@SerialName("billing_cycle") val billingCycle: String? = null,
|
||||||
val benefits: List<PlanBenefitDto> = emptyList(),
|
val benefits: List<PlanBenefitDto> = emptyList(),
|
||||||
val features: List<String> = emptyList(),
|
val features: List<String> = emptyList(),
|
||||||
val isActive: Boolean = true,
|
val isActive: Boolean = true,
|
||||||
val isCurrentPlan: Boolean = false
|
val isCurrentPlan: Boolean = false,
|
||||||
|
@SerialName("max_conversations") val maxConversations: Int? = null,
|
||||||
|
@SerialName("max_chapters") val maxChapters: Int? = null,
|
||||||
|
@SerialName("is_popular") val isPopular: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// 订单信息DTO
|
// 订单信息DTO
|
||||||
@@ -69,5 +73,84 @@ data class QuotaCheckDto(
|
|||||||
val remainingWords: Int? = null,
|
val remainingWords: Int? = null,
|
||||||
val remainingChapters: Int? = null,
|
val remainingChapters: Int? = null,
|
||||||
val remainingConversations: Int? = null,
|
val remainingConversations: Int? = null,
|
||||||
|
// 已用量与上限(用于展示 "已用 X / 共 Y")
|
||||||
|
val usedConversations: Int? = null,
|
||||||
|
val usedChapters: Int? = null,
|
||||||
|
val maxConversations: Int? = null,
|
||||||
|
val maxChapters: Int? = null,
|
||||||
val message: String? = null
|
val message: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ==================== 支付订单相关模型 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建支付订单请求
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class CreatePaymentOrderRequest(
|
||||||
|
@SerialName("plan_id") val planId: String,
|
||||||
|
@SerialName("payment_method") val paymentMethod: String // "wechat" / "alipay"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建支付订单响应
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class CreatePaymentOrderResponse(
|
||||||
|
@SerialName("order_id") val orderId: String,
|
||||||
|
@SerialName("payment_method") val paymentMethod: String,
|
||||||
|
// 微信支付参数(仅当 paymentMethod == "wechat" 时有值)
|
||||||
|
@SerialName("wechat_params") val wechatParams: WeChatPayParamsDto? = null,
|
||||||
|
// 支付宝订单字符串(仅当 paymentMethod == "alipay" 时有值)
|
||||||
|
@SerialName("alipay_order_string") val alipayOrderString: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付参数 DTO
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class WeChatPayParamsDto(
|
||||||
|
val appId: String = "",
|
||||||
|
val partnerId: String = "",
|
||||||
|
val prepayId: String = "",
|
||||||
|
val nonceStr: String = "",
|
||||||
|
val timeStamp: String = "",
|
||||||
|
val sign: String = "",
|
||||||
|
val packageValue: String = "Sign=WXPay"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单状态查询响应
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class PaymentOrderStatusResponse(
|
||||||
|
@SerialName("order_id") val orderId: String,
|
||||||
|
@SerialName("plan_id") val planId: String,
|
||||||
|
@SerialName("plan_name") val planName: String,
|
||||||
|
val amount: Int, // 金额(分)
|
||||||
|
val currency: String,
|
||||||
|
@SerialName("payment_method") val paymentMethod: String,
|
||||||
|
val status: String, // pending / paid / failed / cancelled / refunded
|
||||||
|
@SerialName("trade_no") val tradeNo: String? = null,
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("paid_at") val paidAt: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试订阅请求(仅开发/测试环境,微信支付审核通过前使用)
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class TestSubscriptionRequest(
|
||||||
|
val action: String, // "activate" | "deactivate"
|
||||||
|
@SerialName("plan_id") val planId: String? = "pro"
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试订阅响应
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class TestSubscriptionResponse(
|
||||||
|
val success: Boolean,
|
||||||
|
val message: String,
|
||||||
|
@SerialName("subscription_type") val subscriptionType: String
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package com.huaga.life_echo.payment
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.util.Log
|
||||||
|
import com.alipay.sdk.app.PayTask
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝支付处理器
|
||||||
|
*
|
||||||
|
* 负责:
|
||||||
|
* 1. 调起支付宝 APP 支付
|
||||||
|
* 2. 解析支付结果
|
||||||
|
*/
|
||||||
|
class AlipayHandler {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "AlipayHandler"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起支付宝支付
|
||||||
|
*
|
||||||
|
* 注意:此方法需要在协程中调用(内部会切换到 IO 线程)
|
||||||
|
*
|
||||||
|
* @param activity 当前 Activity
|
||||||
|
* @param orderString 从服务端获取的支付宝订单字符串
|
||||||
|
* @param orderId 内部订单号
|
||||||
|
* @return PaymentResult
|
||||||
|
*/
|
||||||
|
suspend fun startPay(
|
||||||
|
activity: Activity,
|
||||||
|
orderString: String,
|
||||||
|
orderId: String
|
||||||
|
): PaymentResult = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "发起支付宝支付: orderId=$orderId")
|
||||||
|
|
||||||
|
val payTask = PayTask(activity)
|
||||||
|
val result: Map<String, String> = payTask.payV2(orderString, true)
|
||||||
|
|
||||||
|
Log.d(TAG, "支付宝支付结果: $result")
|
||||||
|
|
||||||
|
parseResult(result, orderId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "支付宝支付异常", e)
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "alipay",
|
||||||
|
errorCode = -1,
|
||||||
|
errorMessage = e.message ?: "支付异常"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析支付宝支付结果
|
||||||
|
*/
|
||||||
|
private fun parseResult(result: Map<String, String>, orderId: String): PaymentResult {
|
||||||
|
val resultStatus = result["resultStatus"] ?: ""
|
||||||
|
val memo = result["memo"] ?: ""
|
||||||
|
|
||||||
|
return when (resultStatus) {
|
||||||
|
"9000" -> {
|
||||||
|
// 支付成功
|
||||||
|
Log.d(TAG, "支付宝支付成功: orderId=$orderId")
|
||||||
|
PaymentResult.Success(orderId = orderId, paymentMethod = "alipay")
|
||||||
|
}
|
||||||
|
"6001" -> {
|
||||||
|
// 用户取消
|
||||||
|
Log.d(TAG, "支付宝支付取消: orderId=$orderId")
|
||||||
|
PaymentResult.Cancelled(orderId = orderId, paymentMethod = "alipay")
|
||||||
|
}
|
||||||
|
"6002" -> {
|
||||||
|
// 网络连接出错
|
||||||
|
Log.e(TAG, "支付宝支付网络错误: orderId=$orderId")
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "alipay",
|
||||||
|
errorCode = 6002,
|
||||||
|
errorMessage = "网络连接异常,请检查网络后重试"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"4000" -> {
|
||||||
|
// 支付失败
|
||||||
|
Log.e(TAG, "支付宝支付失败: orderId=$orderId, memo=$memo")
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "alipay",
|
||||||
|
errorCode = 4000,
|
||||||
|
errorMessage = memo.ifEmpty { "支付失败" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "支付宝支付未知状态: resultStatus=$resultStatus, memo=$memo")
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "alipay",
|
||||||
|
errorCode = resultStatus.toIntOrNull() ?: -1,
|
||||||
|
errorMessage = memo.ifEmpty { "支付异常($resultStatus)" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.huaga.life_echo.payment
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一支付管理器
|
||||||
|
*
|
||||||
|
* 提供统一的支付入口,屏蔽微信/支付宝底层差异
|
||||||
|
*
|
||||||
|
* 使用方式:
|
||||||
|
* ```
|
||||||
|
* val manager = PaymentManager(context, wechatAppId)
|
||||||
|
*
|
||||||
|
* // 微信支付
|
||||||
|
* manager.startWeChatPay(activity, params, orderId) { result -> ... }
|
||||||
|
*
|
||||||
|
* // 支付宝支付
|
||||||
|
* val result = manager.startAlipay(activity, orderString, orderId)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class PaymentManager(
|
||||||
|
context: Context,
|
||||||
|
wechatAppId: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "PaymentManager"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val weChatPayHandler = WeChatPayHandler(context.applicationContext, wechatAppId)
|
||||||
|
private val alipayHandler = AlipayHandler()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查微信是否已安装
|
||||||
|
*/
|
||||||
|
fun isWeChatInstalled(): Boolean {
|
||||||
|
return weChatPayHandler.isWeChatInstalled()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起微信支付
|
||||||
|
*
|
||||||
|
* @param params 从服务端获取的微信支付参数
|
||||||
|
* @param orderId 内部订单号
|
||||||
|
* @param callback 支付结果回调(在主线程)
|
||||||
|
*/
|
||||||
|
fun startWeChatPay(
|
||||||
|
params: WeChatPayParams,
|
||||||
|
orderId: String,
|
||||||
|
callback: (PaymentResult) -> Unit
|
||||||
|
) {
|
||||||
|
Log.d(TAG, "发起微信支付: orderId=$orderId")
|
||||||
|
weChatPayHandler.startPay(params, orderId, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起支付宝支付
|
||||||
|
*
|
||||||
|
* 注意:此方法需要在协程中调用
|
||||||
|
*
|
||||||
|
* @param activity 当前 Activity
|
||||||
|
* @param orderString 从服务端获取的支付宝订单字符串
|
||||||
|
* @param orderId 内部订单号
|
||||||
|
* @return PaymentResult
|
||||||
|
*/
|
||||||
|
suspend fun startAlipay(
|
||||||
|
activity: Activity,
|
||||||
|
orderString: String,
|
||||||
|
orderId: String
|
||||||
|
): PaymentResult {
|
||||||
|
Log.d(TAG, "发起支付宝支付: orderId=$orderId")
|
||||||
|
return alipayHandler.startPay(activity, orderString, orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放资源,在不需要时调用
|
||||||
|
*/
|
||||||
|
fun release() {
|
||||||
|
weChatPayHandler.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.huaga.life_echo.payment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付结果封装
|
||||||
|
*/
|
||||||
|
sealed class PaymentResult {
|
||||||
|
/** 支付成功 */
|
||||||
|
data class Success(
|
||||||
|
val orderId: String,
|
||||||
|
val paymentMethod: String
|
||||||
|
) : PaymentResult()
|
||||||
|
|
||||||
|
/** 支付失败 */
|
||||||
|
data class Failure(
|
||||||
|
val orderId: String,
|
||||||
|
val paymentMethod: String,
|
||||||
|
val errorCode: Int = -1,
|
||||||
|
val errorMessage: String = "支付失败"
|
||||||
|
) : PaymentResult()
|
||||||
|
|
||||||
|
/** 用户取消支付 */
|
||||||
|
data class Cancelled(
|
||||||
|
val orderId: String,
|
||||||
|
val paymentMethod: String
|
||||||
|
) : PaymentResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付调起参数(从服务端获取)
|
||||||
|
*/
|
||||||
|
data class WeChatPayParams(
|
||||||
|
val appId: String,
|
||||||
|
val partnerId: String,
|
||||||
|
val prepayId: String,
|
||||||
|
val nonceStr: String,
|
||||||
|
val timeStamp: String,
|
||||||
|
val sign: String,
|
||||||
|
val packageValue: String = "Sign=WXPay"
|
||||||
|
)
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package com.huaga.life_echo.payment
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.util.Log
|
||||||
|
import com.tencent.mm.opensdk.modelpay.PayReq
|
||||||
|
import com.tencent.mm.opensdk.openapi.IWXAPI
|
||||||
|
import com.tencent.mm.opensdk.openapi.WXAPIFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付处理器
|
||||||
|
*
|
||||||
|
* 负责:
|
||||||
|
* 1. 注册微信 SDK
|
||||||
|
* 2. 调起微信支付
|
||||||
|
* 3. 接收支付结果(通过 WXPayEntryActivity 转发)
|
||||||
|
*/
|
||||||
|
class WeChatPayHandler(
|
||||||
|
private val context: Context,
|
||||||
|
private val appId: String
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "WeChatPayHandler"
|
||||||
|
|
||||||
|
/** 微信支付结果广播 Action */
|
||||||
|
const val ACTION_WECHAT_PAY_RESULT = "com.huaga.life_echo.WECHAT_PAY_RESULT"
|
||||||
|
const val EXTRA_ERROR_CODE = "error_code"
|
||||||
|
const val EXTRA_ERROR_MSG = "error_msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val wxApi: IWXAPI by lazy {
|
||||||
|
WXAPIFactory.createWXAPI(context, appId, true).also {
|
||||||
|
it.registerApp(appId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前等待支付结果的回调 */
|
||||||
|
private var pendingCallback: ((PaymentResult) -> Unit)? = null
|
||||||
|
private var pendingOrderId: String? = null
|
||||||
|
|
||||||
|
/** 广播接收器:接收来自 WXPayEntryActivity 的支付结果 */
|
||||||
|
private val payResultReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
val errCode = intent?.getIntExtra(EXTRA_ERROR_CODE, -1) ?: -1
|
||||||
|
val errMsg = intent?.getStringExtra(EXTRA_ERROR_MSG) ?: ""
|
||||||
|
handlePayResult(errCode, errMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isReceiverRegistered = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查微信是否已安装
|
||||||
|
*/
|
||||||
|
fun isWeChatInstalled(): Boolean {
|
||||||
|
return wxApi.isWXAppInstalled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起微信支付
|
||||||
|
*
|
||||||
|
* @param params 从服务端获取的支付参数
|
||||||
|
* @param orderId 内部订单号
|
||||||
|
* @param callback 支付结果回调
|
||||||
|
*/
|
||||||
|
fun startPay(
|
||||||
|
params: WeChatPayParams,
|
||||||
|
orderId: String,
|
||||||
|
callback: (PaymentResult) -> Unit
|
||||||
|
) {
|
||||||
|
if (!isWeChatInstalled()) {
|
||||||
|
callback(
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "wechat",
|
||||||
|
errorCode = -2,
|
||||||
|
errorMessage = "未安装微信客户端"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存回调
|
||||||
|
pendingCallback = callback
|
||||||
|
pendingOrderId = orderId
|
||||||
|
|
||||||
|
// 注册广播接收器
|
||||||
|
registerReceiver()
|
||||||
|
|
||||||
|
// 构建支付请求
|
||||||
|
val req = PayReq().apply {
|
||||||
|
appId = params.appId
|
||||||
|
partnerId = params.partnerId
|
||||||
|
prepayId = params.prepayId
|
||||||
|
nonceStr = params.nonceStr
|
||||||
|
timeStamp = params.timeStamp
|
||||||
|
sign = params.sign
|
||||||
|
packageValue = params.packageValue
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "发起微信支付: orderId=$orderId")
|
||||||
|
val result = wxApi.sendReq(req)
|
||||||
|
if (!result) {
|
||||||
|
Log.e(TAG, "微信支付调起失败")
|
||||||
|
pendingCallback?.invoke(
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "wechat",
|
||||||
|
errorCode = -3,
|
||||||
|
errorMessage = "微信支付调起失败"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pendingCallback = null
|
||||||
|
pendingOrderId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理微信支付结果
|
||||||
|
* 由广播接收器调用
|
||||||
|
*/
|
||||||
|
private fun handlePayResult(errCode: Int, errMsg: String) {
|
||||||
|
val orderId = pendingOrderId ?: ""
|
||||||
|
val callback = pendingCallback
|
||||||
|
|
||||||
|
// 清理状态
|
||||||
|
pendingCallback = null
|
||||||
|
pendingOrderId = null
|
||||||
|
|
||||||
|
val result = when (errCode) {
|
||||||
|
0 -> {
|
||||||
|
Log.d(TAG, "微信支付成功: orderId=$orderId")
|
||||||
|
PaymentResult.Success(orderId = orderId, paymentMethod = "wechat")
|
||||||
|
}
|
||||||
|
-1 -> {
|
||||||
|
Log.e(TAG, "微信支付失败: errCode=$errCode, errMsg=$errMsg")
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "wechat",
|
||||||
|
errorCode = errCode,
|
||||||
|
errorMessage = errMsg.ifEmpty { "支付失败" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
-2 -> {
|
||||||
|
Log.d(TAG, "微信支付取消: orderId=$orderId")
|
||||||
|
PaymentResult.Cancelled(orderId = orderId, paymentMethod = "wechat")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "微信支付未知错误: errCode=$errCode")
|
||||||
|
PaymentResult.Failure(
|
||||||
|
orderId = orderId,
|
||||||
|
paymentMethod = "wechat",
|
||||||
|
errorCode = errCode,
|
||||||
|
errorMessage = "未知错误($errCode)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback?.invoke(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerReceiver() {
|
||||||
|
if (!isReceiverRegistered) {
|
||||||
|
val filter = IntentFilter(ACTION_WECHAT_PAY_RESULT)
|
||||||
|
context.registerReceiver(payResultReceiver, filter, Context.RECEIVER_NOT_EXPORTED)
|
||||||
|
isReceiverRegistered = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放资源
|
||||||
|
*/
|
||||||
|
fun release() {
|
||||||
|
if (isReceiverRegistered) {
|
||||||
|
try {
|
||||||
|
context.unregisterReceiver(payResultReceiver)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
isReceiverRegistered = false
|
||||||
|
}
|
||||||
|
pendingCallback = null
|
||||||
|
pendingOrderId = null
|
||||||
|
wxApi.detach()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.huaga.life_echo.wxapi
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import com.huaga.life_echo.payment.WeChatPayHandler
|
||||||
|
import com.tencent.mm.opensdk.constants.ConstantsAPI
|
||||||
|
import com.tencent.mm.opensdk.modelbase.BaseReq
|
||||||
|
import com.tencent.mm.opensdk.modelbase.BaseResp
|
||||||
|
import com.tencent.mm.opensdk.openapi.IWXAPI
|
||||||
|
import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler
|
||||||
|
import com.tencent.mm.opensdk.openapi.WXAPIFactory
|
||||||
|
import com.huaga.life_echo.config.AppConfig
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付结果回调 Activity
|
||||||
|
*
|
||||||
|
* 重要说明:
|
||||||
|
* - 此类必须位于 {应用包名}.wxapi 目录下
|
||||||
|
* - 类名必须为 WXPayEntryActivity
|
||||||
|
* - 这是微信 SDK 的硬性要求,不可更改路径和类名
|
||||||
|
*
|
||||||
|
* 收到微信支付结果后,通过广播转发给 WeChatPayHandler
|
||||||
|
*/
|
||||||
|
class WXPayEntryActivity : Activity(), IWXAPIEventHandler {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "WXPayEntryActivity"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var wxApi: IWXAPI
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
wxApi = WXAPIFactory.createWXAPI(this, AppConfig.WECHAT_APP_ID)
|
||||||
|
wxApi.handleIntent(intent, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
setIntent(intent)
|
||||||
|
wxApi.handleIntent(intent, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信发送请求到第三方应用时回调(支付场景不会触发)
|
||||||
|
*/
|
||||||
|
override fun onReq(req: BaseReq?) {
|
||||||
|
Log.d(TAG, "onReq: ${req?.type}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方应用发送到微信的请求处理后的响应结果回调
|
||||||
|
* 支付结果在这里接收
|
||||||
|
*/
|
||||||
|
override fun onResp(resp: BaseResp?) {
|
||||||
|
Log.d(TAG, "onResp: type=${resp?.type}, errCode=${resp?.errCode}, errStr=${resp?.errStr}")
|
||||||
|
|
||||||
|
if (resp?.type == ConstantsAPI.COMMAND_PAY_BY_WX) {
|
||||||
|
// 通过广播将结果转发给 WeChatPayHandler
|
||||||
|
val intent = Intent(WeChatPayHandler.ACTION_WECHAT_PAY_RESULT).apply {
|
||||||
|
setPackage(packageName)
|
||||||
|
putExtra(WeChatPayHandler.EXTRA_ERROR_CODE, resp.errCode)
|
||||||
|
putExtra(WeChatPayHandler.EXTRA_ERROR_MSG, resp.errStr ?: "")
|
||||||
|
}
|
||||||
|
sendBroadcast(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭此透明 Activity
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user