☰
Unity 接入微信支付与支付宝:Android/iOS 原生桥接实战
2026/10/15 6:29:38 网站建设 项目流程

1. 为什么 Unity 项目绕不开支付 SDK 这件事

做过 Unity 商业项目的人都有一个共识:只要涉及虚拟商品、会员订阅、道具购买,支付环节就是绕不过去的坎。而国内做移动端游戏或应用,微信支付和支付宝几乎是标配。问题在于,Unity 本身是一个跨平台引擎,它的强项在渲染、物理、动画这些层面,对于原生支付这种强依赖平台特性的功能,Unity 并没有提供开箱即用的方案。

我刚接触这类需求的时候,第一反应是去 Asset Store 找现成插件。找了一圈发现,要么是年久失修、要么是封装得太重、要么是只支持单一平台。后来想明白了:支付 SDK 的接入本质上是一个“桥接”问题——把 Android 的原生 Java 接口和 iOS 的原生 Objective-C 接口,通过 Unity 提供的互操作机制暴露给 C# 层调用。理解了这一点,整个接入思路就清晰了。

这篇文章适合两类人看:一类是刚接手 Unity 项目、需要快速把支付跑通的开发者;另一类是对 Unity 原生互操作不太熟悉、想系统了解 Android 和 iOS 桥接机制的技术人员。我会从架构设计讲到具体实现,把微信支付和支付宝两条线都拆开讲清楚,包括我实际踩过的坑和验证过的方案。

需要提前说明的是,支付 SDK 的接入涉及签名、验签、订单管理等环节,这些逻辑放在客户端做是有安全风险的。我的建议始终是:客户端只负责拉起支付和接收回调,订单创建、签名、验签全部放在服务端。这个原则后面会反复提到。

2. 接入前的架构决策:哪些事客户端做,哪些事服务端做

2.1 支付流程的职责划分

很多新手一上来就想着“怎么在 Unity 里调起微信支付”,但真正重要的是先搞清楚整个支付链路里,客户端和服务端各自该承担什么。我见过不少项目把商户私钥直接写在客户端代码里,这种做法一旦被反编译,后果不堪设想。

一个合理的支付流程应该是这样的:

  1. 用户在游戏内点击购买,客户端把商品 ID、用户 ID 等信息发给自己的服务端
  2. 服务端根据商品信息生成订单,调用微信或支付宝的服务端 API 创建预支付订单
  3. 服务端拿到预支付订单后,进行签名处理,把签名后的参数返回给客户端
  4. 客户端拿到签名后的参数,调用 SDK 拉起支付
  5. 用户完成支付后,SDK 回调客户端,同时支付平台会异步通知服务端
  6. 服务端收到异步通知后验签、更新订单状态、发货
  7. 客户端收到回调后,向服务端查询订单状态,确认发货结果

这个流程里,客户端做的事情其实很少:拉起支付、接收回调、查询结果。所有涉及密钥和签名的操作都在服务端完成。这样即使客户端被破解,攻击者拿到的也只是一次性的支付参数,无法伪造订单。

2.2 Unity 与原生交互的技术选型

Unity 和原生平台交互,主要有几种方式:

交互方式适用场景优点缺点
AndroidJavaClass/AndroidJavaObjectAndroid 平台调用 Java 接口Unity 内置支持,无需额外插件反射调用有性能开销,代码冗长
JNI 直接调用高性能场景效率高实现复杂,容易出错
iOS DllImportiOS 平台调用 Objective-C直接高效需要写 C 桥接代码
第三方桥接插件快速集成省事黑盒,出问题难排查

我的选择是:Android 端用AndroidJavaClass和AndroidJavaObject,iOS 端用DllImport加 C 桥接。原因很简单——这两种方式都是 Unity 官方支持的,不依赖第三方插件,出了问题自己能查。虽然写起来麻烦一点,但可控性最强。

2.3 目录结构设计

在动手写代码之前,先把目录结构规划好,后面维护会轻松很多。我通常这样组织:

Assets/ Plugins/ Android/ WeChatPay.androidlib/ libs/ wechat-sdk-android.aar AndroidManifest.xml project.properties Alipay.androidlib/ libs/ alipaySdk.aar AndroidManifest.xml iOS/ WeChatPay/ WeChatPayBridge.h WeChatPayBridge.m Alipay/ AlipayBridge.h AlipayBridge.m Scripts/ Payment/ IPayment.cs PaymentManager.cs WeChatPayAndroid.cs WeChatPayiOS.cs AlipayAndroid.cs AlipayiOS.cs

这个结构的好处是平台相关的代码全部隔离在 Plugins 目录下,业务层通过接口调用,切换平台时不需要改业务代码。

3. Android 端桥接:从 AAR 集成到 Java 层封装

3.1 微信支付 AAR 的集成细节

微信支付 Android SDK 官方提供的是一个 AAR 文件。Unity 从 2018 版本开始支持.androidlib格式的插件目录,把 AAR 放进去就能自动参与构建。

具体操作步骤:

  1. 在Assets/Plugins/Android/下新建WeChatPay.androidlib目录
  2. 在目录内创建libs文件夹,把wechat-sdk-android.aar放进去
  3. 创建AndroidManifest.xml,内容如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.yourcompany.wechatpay"> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> </manifest>
  1. 创建project.properties,指定编译目标:
target=android-32 android.library=true

这里有个容易忽略的点:package属性不能和主工程的包名冲突,否则构建时会报错。我一般用com.公司名.模块名的格式。

3.2 微信支付 Java 封装层

AAR 集成好之后,需要在 Java 层写一个封装类,把微信支付的 API 暴露给 Unity 调用。这个类要放在Assets/Plugins/Android/下的 Java 源码目录里,Unity 构建时会自动编译。

package com.yourcompany.wechatpay; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import com.tencent.mm.opensdk.openapi.IWXAPI; import com.tencent.mm.opensdk.openapi.WXAPIFactory; import com.tencent.mm.opensdk.modelpay.PayReq; import com.tencent.mm.opensdk.constants.ConstantsAPI; import com.unity3d.player.UnityPlayer; public class WeChatPayBridge { private static IWXAPI api; private static String gameObjectName; private static BroadcastReceiver receiver; public static void init(String appId, String goName) { gameObjectName = goName; Activity activity = UnityPlayer.currentActivity; api = WXAPIFactory.createWXAPI(activity, appId, true); api.registerApp(appId); registerReceiver(activity); } public static void pay(String partnerId, String prepayId, String nonceStr, String timeStamp, String sign, String packageValue) { PayReq req = new PayReq(); req.appId = api.getWXAppSupportAPI() > 0 ? getAppId() : ""; req.partnerId = partnerId; req.prepayId = prepayId; req.nonceStr = nonceStr; req.timeStamp = timeStamp; req.sign = sign; req.packageValue = packageValue; api.sendReq(req); } private static void registerReceiver(final Activity activity) { receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int code = intent.getIntExtra("errCode", -1); if (gameObjectName != null) { UnityPlayer.UnitySendMessage(gameObjectName, "OnWeChatPayResult", String.valueOf(code)); } } }; IntentFilter filter = new IntentFilter( ConstantsAPI.ACTION_PAY_RESULT); activity.registerReceiver(receiver, filter); } }

这段代码有几个关键点需要解释:

UnityPlayer.currentActivity拿到的是当前 Unity 的 Activity,微信 SDK 需要它来拉起支付界面。UnitySendMessage是 Unity 提供的从 Java 层回调 C# 层的机制,第一个参数是 GameObject 的名字,第二个是方法名,第三个是参数。

微信支付的结果不是通过onActivityResult返回的,而是通过广播。所以必须注册一个BroadcastReceiver来接收支付结果。这个广播的 Action 是ConstantsAPI.ACTION_PAY_RESULT,在微信 SDK 的常量类里定义。

3.3 支付宝的差异处理

支付宝 Android SDK 的集成方式和微信类似,但回调机制不同。支付宝是通过onActivityResult返回结果的,所以封装类需要继承并处理 Activity 的生命周期。

package com.yourcompany.alipay; import android.app.Activity; import android.content.Intent; import android.os.Handler; import android.os.Message; import com.alipay.sdk.app.PayTask; import com.unity3d.player.UnityPlayer; import java.util.Map; public class AlipayBridge { private static String gameObjectName; private static final int SDK_PAY_FLAG = 1; public static void init(String goName) { gameObjectName = goName; } public static void pay(final String orderInfo) { final Activity activity = UnityPlayer.currentActivity; Runnable payRunnable = new Runnable() { @Override public void run() { PayTask alipay = new PayTask(activity); Map<String, String> result = alipay.payV2(orderInfo, true); Message msg = new Message(); msg.what = SDK_PAY_FLAG; msg.obj = result; mHandler.sendMessage(msg); } }; Thread payThread = new Thread(payRunnable); payThread.start(); } private static Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == SDK_PAY_FLAG) { Map<String, String> result = (Map<String, String>) msg.obj; String resultStatus = result.get("resultStatus"); String resultInfo = result.get("result"); if (gameObjectName != null) { UnityPlayer.UnitySendMessage(gameObjectName, "OnAlipayResult", resultStatus + "|" + resultInfo); } } } }; }

支付宝的payV2方法必须在子线程调用,因为它内部有网络请求。拿到结果后通过 Handler 切回主线程,再回调 Unity。resultStatus的值需要特别注意:9000表示支付成功,6001表示用户取消,4000表示支付失败,8000表示正在处理中。

4. iOS 端桥接:DllImport 与 C 桥接的配合

4.1 iOS 桥接的整体思路

iOS 端的桥接比 Android 要复杂一些,因为 Unity 的 C# 层不能直接调用 Objective-C 的方法。中间需要一层 C 函数作为桥梁:C# 通过DllImport调用 C 函数,C 函数再调用 Objective-C 的代码。

这个桥接层通常写在一个.m文件里,同时包含 C 函数和 Objective-C 代码。Unity 构建 iOS 项目时,会把Assets/Plugins/iOS/下的.m、.h、.mm文件自动拷贝到 Xcode 工程里参与编译。

4.2 微信支付 iOS 桥接实现

先看头文件:

// WeChatPayBridge.h #ifndef WeChatPayBridge_h #define WeChatPayBridge_h extern "C" { void _initWeChatPay(const char* appId, const char* gameObjectName); void _sendWeChatPayRequest(const char* partnerId, const char* prepayId, const char* nonceStr, const char* timeStamp, const char* sign, const char* packageValue); } #endif

再看实现文件:

// WeChatPayBridge.m #import "WeChatPayBridge.h" #import <WechatOpenSDK/WXApi.h> static NSString *gGameObjectName = nil; @interface WeChatPayDelegate : NSObject <WXApiDelegate> @end @implementation WeChatPayDelegate - (void)onResp:(BaseResp *)resp { if ([resp isKindOfClass:[PayResp class]]) { PayResp *payResp = (PayResp *)resp; NSString *result = [NSString stringWithFormat:@"%d", payResp.errCode]; UnitySendMessage([gGameObjectName UTF8String], "OnWeChatPayResult", [result UTF8String]); } } @end static WeChatPayDelegate *gDelegate = nil; void _initWeChatPay(const char* appId, const char* gameObjectName) { gGameObjectName = [NSString stringWithUTF8String:gameObjectName]; gDelegate = [[WeChatPayDelegate alloc] init]; [WXApi registerApp:[NSString stringWithUTF8String:appId] universalLink:@"https://yourdomain.com/app/"]; } void _sendWeChatPayRequest(const char* partnerId, const char* prepayId, const char* nonceStr, const char* timeStamp, const char* sign, const char* packageValue) { PayReq *req = [[PayReq alloc] init]; req.partnerId = [NSString stringWithUTF8String:partnerId]; req.prepayId = [NSString stringWithUTF8String:prepayId]; req.nonceStr = [NSString stringWithUTF8String:nonceStr]; req.timeStamp = [[NSString stringWithUTF8String:timeStamp] intValue]; req.sign = [NSString stringWithUTF8String:sign]; req.package = [NSString stringWithUTF8String:packageValue]; [WXApi sendReq:req completion:nil]; }

这里有几个关键细节。UnitySendMessage是 Unity 在 iOS 端提供的 C 函数,声明在UnityInterface.h里,不需要额外引入。微信 SDK 从某个版本开始要求配置universalLink,这是苹果的通用链接机制,用于从微信跳回你的 App。如果不配置,支付完成后无法自动跳回。

还有一个容易踩的坑:WXApiDelegate必须在 AppDelegate 里正确设置。Unity 生成的 Xcode 工程里,AppDelegate 是自动生成的,你需要手动修改它,把handleOpenURL和handleUniversalLink转发给微信 SDK。

4.3 支付宝 iOS 桥接实现

支付宝 iOS SDK 的桥接思路类似,但回调方式不同。支付宝是通过 URL Scheme 回调的,需要在 AppDelegate 的openURL方法里处理。

// AlipayBridge.m #import "AlipayBridge.h" #import <AlipaySDK/AlipaySDK.h> static NSString *gGameObjectName = nil; void _initAlipay(const char* gameObjectName) { gGameObjectName = [NSString stringWithUTF8String:gameObjectName]; } void _sendAlipayRequest(const char* orderInfo) { NSString *orderString = [NSString stringWithUTF8String:orderInfo]; [[AlipaySDK defaultService] payOrder:orderString fromScheme:@"yourAppScheme" callback:^(NSDictionary *resultDic) { NSString *status = resultDic[@"resultStatus"]; NSString *result = resultDic[@"result"]; NSString *callback = [NSString stringWithFormat:@"%@|%@", status, result]; UnitySendMessage([gGameObjectName UTF8String], "OnAlipayResult", [callback UTF8String]); }]; }

支付宝的回调有两种情况:一种是在当前 App 内完成支付(比如安装了支付宝客户端但用户选择在应用内支付),回调会直接触发;另一种是跳转到支付宝客户端完成支付,然后通过 URL Scheme 跳回你的 App,这时候需要在 AppDelegate 的openURL里调用AlipaySDK的处理方法。

5. C# 层的统一封装与平台分发

5.1 接口设计

C# 层要做的事情是:定义统一的支付接口,根据当前平台调用对应的原生实现,处理回调分发。

public interface IPayment { void Init(string appId, string gameObjectName); void Pay(PaymentParams param); } public class PaymentParams { public string PartnerId; public string PrepayId; public string NonceStr; public string TimeStamp; public string Sign; public string PackageValue; public string OrderInfo; // 支付宝用 }

5.2 Android 实现

public class WeChatPayAndroid : IPayment { private AndroidJavaClass bridgeClass; public void Init(string appId, string gameObjectName) { bridgeClass = new AndroidJavaClass( "com.yourcompany.wechatpay.WeChatPayBridge"); bridgeClass.CallStatic("init", appId, gameObjectName); } public void Pay(PaymentParams param) { bridgeClass.CallStatic("pay", param.PartnerId, param.PrepayId, param.NonceStr, param.TimeStamp, param.Sign, param.PackageValue); } }

5.3 iOS 实现

public class WeChatPayiOS : IPayment { [DllImport("__Internal")] private static extern void _initWeChatPay(string appId, string goName); [DllImport("__Internal")] private static extern void _sendWeChatPayRequest( string partnerId, string prepayId, string nonceStr, string timeStamp, string sign, string packageValue); public void Init(string appId, string gameObjectName) { _initWeChatPay(appId, gameObjectName); } public void Pay(PaymentParams param) { _sendWeChatPayRequest(param.PartnerId, param.PrepayId, param.NonceStr, param.TimeStamp, param.Sign, param.PackageValue); } }

注意DllImport的参数是"__Internal",这是 Unity iOS 平台的特殊写法,表示调用的是静态链接的库,而不是动态库。

5.4 回调接收与分发

回调接收需要一个挂载在场景里的 GameObject,并且名字要和初始化时传给原生的名字一致。

public class PaymentManager : MonoBehaviour { private static PaymentManager instance; private IPayment payment; private Action<int, string> onPayResult; void Awake() { instance = this; DontDestroyOnLoad(gameObject); } public static void StartPay(PaymentParams param, Action<int, string> callback) { instance.onPayResult = callback; #if UNITY_ANDROID && !UNITY_EDITOR instance.payment = new WeChatPayAndroid(); #elif UNITY_IOS && !UNITY_EDITOR instance.payment = new WeChatPayiOS(); #else Debug.Log("Editor 模式不支持支付"); return; #endif instance.payment.Pay(param); } // 由原生层通过 UnitySendMessage 调用 public void OnWeChatPayResult(string result) { int code = int.Parse(result); onPayResult?.Invoke(code, ""); } public void OnAlipayResult(string result) { string[] parts = result.Split('|'); int code = int.Parse(parts[0]); string info = parts.Length > 1 ? parts[1] : ""; onPayResult?.Invoke(code, info); } }

这里有个细节:UnitySendMessage只能调用 GameObject 上挂载的脚本的公开方法,方法名必须完全匹配,参数只能是字符串。所以回调方法的设计要尽量简单,复杂的数据结构在 C# 层再做解析。

6. 那些文档里不会写的踩坑记录

6.1 Android 构建时的 AAR 冲突

微信支付 AAR 里包含了一些基础库,如果你的项目同时集成了其他 SDK,很可能会出现类冲突。我遇到过一次,微信支付和某个推送 SDK 都引用了同一个网络库的不同版本,构建时报Duplicate class错误。

解决办法是在mainTemplate.gradle里加排除规则:

android { packagingOptions { exclude 'META-INF/proguard/androidx-annotations.pro' } }

或者在dependencies里强制指定版本:

configurations.all { resolutionStrategy { force 'com.android.support:appcompat-v7:28.0.0' } }

6.2 iOS 的 Universal Link 配置

微信支付从 2019 年开始要求配置 Universal Link,否则支付完成后无法跳回 App。这个配置涉及三个地方:

  1. 微信开放平台后台填写 Universal Link
  2. Xcode 工程里配置 Associated Domains
  3. 服务器上放置apple-app-site-association文件

最容易出问题的是第三步。这个文件必须放在域名的根目录或者.well-known目录下,而且必须是application/json格式,不能有重定向。我见过有人放在 CDN 上,结果 CDN 返回了text/html,导致验证失败。

6.3 支付回调的时序问题

微信支付的回调是通过广播发送的,但广播的接收时机和 Activity 的生命周期有关。如果用户在支付过程中切到后台,再切回来,广播可能会丢失。

我的处理方式是:在OnApplicationPause里记录状态,当应用从后台恢复时,主动向服务端查询订单状态。这样即使广播丢了,也能通过服务端确认支付结果。

void OnApplicationPause(bool pause) { if (!pause && pendingOrderId != null) { StartCoroutine(QueryOrderStatus(pendingOrderId)); } }

6.4 支付宝的 resultStatus 陷阱

支付宝的resultStatus返回9000只代表支付成功,但不代表你的服务端已经收到通知。有些开发者看到9000就直接发货,这是很危险的。正确的做法是:客户端收到9000后,向服务端查询订单状态,服务端确认收到支付平台的异步通知后,才真正发货。

还有一个坑:支付宝在某些情况下会返回8000(正在处理中),这时候不能当作失败处理,应该轮询查询订单状态。

6.5 Unity Editor 下的调试方案

在 Editor 里调试支付是很麻烦的,因为原生 SDK 根本不存在。我的做法是写一个 Mock 实现:

public class MockPayment : IPayment { public void Init(string appId, string gameObjectName) { } public void Pay(PaymentParams param) { // 模拟支付成功 PaymentManager.instance.OnWeChatPayResult("0"); } }

在 Editor 下用 Mock,真机上用真实实现。这样业务逻辑可以在 Editor 里快速验证,不需要每次都打包。

7. 上线前的检查清单与个人经验

支付功能上线前,有几个检查项是必须过的:

检查项微信支付支付宝备注
应用签名一致性必须必须签名不对无法调起
包名一致性必须必须开放平台后台配置
Universal LinkiOS 必须不需要微信特有要求
URL Scheme不需要iOS 必须支付宝回跳用
网络权限必须必须Android 6.0+ 动态权限
混淆规则需要需要防止 SDK 类被混淆

关于混淆,微信和支付宝的 SDK 都提供了 proguard 规则,但很多人会忘记加。如果混淆了 SDK 的类,运行时会出现ClassNotFoundException。微信的规则是:

-keep class com.tencent.mm.opensdk.** { *; } -keep class com.tencent.wxop.** { *; }

支付宝的规则是:

-keep class com.alipay.android.app.** { *; } -keep class com.alipay.sdk.** { *; }

最后分享一个我自己的经验:支付功能的测试一定要用真实的小额支付来验证,不要只依赖沙箱环境。沙箱环境和生产环境在签名、证书、回调地址上都有差异,沙箱跑通不代表生产没问题。我一般会在上线前用一分钱的商品做一次完整的真实支付测试,确认整个链路没有问题。

另外,支付结果的回调一定要做幂等处理。用户可能因为网络问题重复收到回调,服务端要保证同一笔订单只发货一次。这个逻辑虽然是在服务端做的,但客户端在设计回调处理时也要考虑到,避免重复触发业务逻辑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询