如何在 iOS Objective-C 中集成 CCAvenue payment
结论
在 iOS Objective-C 项目中接入 CCAvenue,建议由服务端创建支付请求并完成加密,客户端只负责打开支付页面。不要把 working_key、商户密钥或解密逻辑放进 App。
旧版 CCAvenue iOS SDK 通常依赖 OpenSSL,接入时容易遇到头文件缺失、库未链接、模拟器架构不兼容或 OpenSSL API 过时等问题。应先向 CCAvenue 获取当前版本的 iOS 集成包和文档。如果商户方案不强制使用原生 SDK,可以直接用 WKWebView 加载服务端生成的支付请求,避免在客户端引入 OpenSSL。
推荐的接入结构
支付流程如下:
- App 请求业务服务器创建订单。
- 服务器生成唯一订单号并确定订单金额。
- 服务器使用 CCAvenue 提供的密钥生成加密请求。
- App 通过
WKWebView提交支付表单。 - 用户在 CCAvenue 页面完成支付。
- CCAvenue 将支付结果回调给业务服务器。
- 服务器验签或解密,并主动查询订单状态。
- App 从业务服务器获取最终支付结果。
不能只根据 WebView 的跳转地址或页面文字判断支付成功。最终结果必须以服务器确认的订单状态为准。
服务端准备
先从 CCAvenue 商户后台或技术支持处取得以下信息:
merchant_idaccess_codeworking_key- 支付网关地址
- 回调地址配置要求
- 测试环境与生产环境的参数
网关 URL、加密算法和字段要求可能因接入方案而异,应以当前商户账户对应的官方集成包为准,不要照搬旧项目中的地址或示例密钥。
服务端创建订单后,可以向 App 返回一个能够直接提交的支付页面 URL。例如:
{
"order_id": "ORDER_20260913_001",
"payment_url": "https://pay.example.com/ccavenue/checkout"
}
这里的 payment_url 指向自己的服务端页面。该页面在服务器端生成 encRequest,然后提交给 CCAvenue:
<form id="paymentForm"
method="post"
action="CCAvenue 提供的网关地址">
<input type="hidden" name="encRequest" value="服务端生成的加密请求">
<input type="hidden" name="access_code" value="商户 access_code">
</form>
<script>
document.getElementById('paymentForm').submit();
</script>
working_key 只能保存在服务器上。即使对 Objective-C 代码进行混淆,攻击者仍然可以提取 App 中的密钥。
Objective-C 中请求支付地址
以下示例假设业务服务器提供 /api/payments/ccavenue/create 接口,用来创建订单并返回支付页面地址:
#import <Foundation/Foundation.h>
- (void)createCCAvenueOrder {
NSURL *url = [NSURL URLWithString:@"https://api.example.com/api/payments/ccavenue/create"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *body = @{
@"amount": @"499.00",
@"currency": @"INR",
@"product_id": @"PRODUCT_001"
};
NSError *serializationError = nil;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:body
options:0
error:&serializationError];
if (serializationError != nil) {
NSLog(@"Serialize payment request failed: %@", serializationError);
return;
}
request.HTTPBody = bodyData;
NSURLSessionDataTask *task =
[[NSURLSession sharedSession] dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error != nil) {
NSLog(@"Create payment failed: %@", error);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode < 200 || httpResponse.statusCode >= 300) {
NSLog(@"Unexpected HTTP status: %ld", (long)httpResponse.statusCode);
return;
}
NSError *jsonError = nil;
NSDictionary *result =
[NSJSONSerialization JSONObjectWithData:data
options:0
error:&jsonError];
if (jsonError != nil) {
NSLog(@"Parse payment response failed: %@", jsonError);
return;
}
NSString *paymentURLString = result[@"payment_url"];
NSURL *paymentURL = [NSURL URLWithString:paymentURLString];
if (paymentURL == nil) {
NSLog(@"Invalid payment URL");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self openCCAvenuePaymentURL:paymentURL];
});
}];
[task resume];
}
实际项目不能完全信任客户端提交的金额。服务器应根据商品编号、优惠规则和订单数据重新计算应付金额。
使用 WKWebView 打开支付页面
先引入 WebKit:
#import <WebKit/WebKit.h>
让控制器实现导航代理:
@interface PaymentViewController () <WKNavigationDelegate>
@property (nonatomic, strong) WKWebView *webView;
@end
初始化 WebView 并加载支付页面:
- (void)viewDidLoad {
[super viewDidLoad];
WKWebViewConfiguration *configuration =
[[WKWebViewConfiguration alloc] init];
self.webView =
[[WKWebView alloc] initWithFrame:self.view.bounds
configuration:configuration];
self.webView.navigationDelegate = self;
self.webView.autoresizingMask =
UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.webView];
}
- (void)openCCAvenuePaymentURL:(NSURL *)paymentURL {
NSURLRequest *request = [NSURLRequest requestWithURL:paymentURL];
[self.webView loadRequest:request];
}
可以监听页面跳转。当页面进入自己的返回地址时,关闭支付界面:
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
NSString *host = url.host.lowercaseString;
NSString *path = url.path.lowercaseString;
BOOL isReturnURL =
[host isEqualToString:@"pay.example.com"] &&
[path hasPrefix:@"/ccavenue/return"];
if (isReturnURL) {
decisionHandler(WKNavigationActionPolicyCancel);
[self queryPaymentStatusFromServer];
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}
不要在这里直接把订单标记为支付成功。返回地址只说明支付流程发生了跳转,并不能证明资金已经到账。
向服务器查询最终状态
服务端应提供订单查询接口。App 在支付页面返回后调用该接口:
- (void)queryPaymentStatusFromServer {
NSString *orderID = @"ORDER_20260913_001";
NSString *encodedOrderID =
[orderID stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]];
NSString *urlString =
[NSString stringWithFormat:
@"https://api.example.com/api/payments/%@/status",
encodedOrderID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *task =
[[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error != nil) {
NSLog(@"Query payment status failed: %@", error);
return;
}
NSError *jsonError = nil;
NSDictionary *result =
[NSJSONSerialization JSONObjectWithData:data
options:0
error:&jsonError];
if (jsonError != nil) {
NSLog(@"Parse payment status failed: %@", jsonError);
return;
}
NSString *status = result[@"status"];
dispatch_async(dispatch_get_main_queue(), ^{
if ([status isEqualToString:@"PAID"]) {
NSLog(@"Payment completed");
} else if ([status isEqualToString:@"FAILED"]) {
NSLog(@"Payment failed");
} else {
NSLog(@"Payment pending: %@", status);
}
});
}];
[task resume];
}
PAID、FAILED 等名称只是业务接口的状态示例,并非 CCAvenue 的原始状态字段。服务器应把网关返回的状态转换为业务系统统一使用的订单状态。
如果必须使用 CCAvenue 原生 SDK
如果商户方案明确要求使用 CCAvenue iOS SDK,应先确认 SDK 是否兼容当前 Xcode、iOS Deployment Target 和设备架构。不要直接把多年前的示例工程拖进新项目。
需要检查以下配置:
.framework或.xcframework是否已完整加入工程。Build Phases > Link Binary With Libraries是否包含 SDK 依赖。- 动态 Framework 是否已加入
Embed Frameworks。 Framework Search Paths和Header Search Paths是否正确。- SDK 是否包含真机需要的
arm64架构。 - 在 Apple Silicon Mac 上运行模拟器时,SDK 是否包含相应的模拟器架构。
- SDK 是否启用了当前工程不支持的 Bitcode 配置。
Other Linker Flags是否需要加入-ObjC。- OpenSSL 头文件路径是否与实际使用的库版本一致。
OpenSSL 头文件可以按下面的形式引入,但具体路径取决于依赖的安装方式:
#import <openssl/aes.h>
#import <openssl/evp.h>
#import <openssl/rsa.h>
如果编译器提示:
'openssl/aes.h' file not found
说明工程没有正确包含头文件。只添加 .a 库无法解决头文件缺失,还需要设置正确的头文件搜索路径,或者改用包含 Headers 的 Framework/XCFramework。
如果链接阶段出现:
Undefined symbols for architecture arm64
常见原因包括:
- OpenSSL 库没有加入链接阶段;
- 当前编译的是真机版本,但加入的是模拟器库;
- 静态库不包含
arm64; - 头文件版本与二进制库版本不一致。
如果提示:
building for iOS Simulator, but linking in object file built for iOS
说明工程混用了设备库和模拟器库。应换用同时包含设备与模拟器切片的 XCFramework,不要长期依靠排除架构来绕过这个问题。
不建议直接手工替换 SDK 中的 OpenSSL
CCAvenue SDK 的加密实现可能依赖特定的 OpenSSL API 和数据格式。自行升级 OpenSSL 或修改加密代码,也许能通过编译,但生成的数据可能无法与网关兼容。
建议按以下顺序处理:
- 获取 CCAvenue 当前提供的最新版 iOS SDK。
- 确认该 SDK 支持当前 Xcode 和目标 iOS 版本。
- 按集成包要求导入相应的 Framework、Bundle 和资源。
- 清理旧版 OpenSSL 头文件、静态库及重复的搜索路径。
- 删除 Derived Data,然后重新构建。
- 如果新版 SDK 仍依赖不兼容的 OpenSSL,改用服务端加密配合
WKWebView的接入方式,或向 CCAvenue 技术支持索取兼容版本。
上线前需要检查的事项
- 测试环境和生产环境的
access_code、密钥及网关地址不能混用。 - 订单号必须唯一,并由服务器生成或校验。
- 服务器必须确认金额、币种和商品信息。
- 回调接口必须使用 HTTPS。
- 服务端需要验证、解密并记录 CCAvenue 返回的数据。
- 支付结果处理必须具备幂等性,防止重复发货或充值。
- 即使 App 被关闭、网络中断或 WebView 没有返回,系统仍应能通过服务器恢复订单状态。
- 日志不能输出
working_key、完整银行卡信息或未经脱敏的支付数据。 - 测试范围应覆盖成功、失败、取消、超时、处理中和重复回调等场景。
如果现有 SDK 已出现 OpenSSL 编译或架构错误,而且项目不依赖 SDK 提供的原生界面,通常可以迁移到“服务端生成加密请求 + WKWebView 支付 + 服务端确认结果”的接入方式。这种方案更安全,也更容易长期维护。
备注:内容仅供参考。