PAYATHON 2026

如何在 iOS Objective-C 中集成 CCAvenue payment

支付阿杰

结论

在 iOS Objective-C 项目中接入 CCAvenue,建议由服务端创建支付请求并完成加密,客户端只负责打开支付页面。不要把 working_key、商户密钥或解密逻辑放进 App。

旧版 CCAvenue iOS SDK 通常依赖 OpenSSL,接入时容易遇到头文件缺失、库未链接、模拟器架构不兼容或 OpenSSL API 过时等问题。应先向 CCAvenue 获取当前版本的 iOS 集成包和文档。如果商户方案不强制使用原生 SDK,可以直接用 WKWebView 加载服务端生成的支付请求,避免在客户端引入 OpenSSL。

推荐的接入结构

支付流程如下:

  1. App 请求业务服务器创建订单。
  2. 服务器生成唯一订单号并确定订单金额。
  3. 服务器使用 CCAvenue 提供的密钥生成加密请求。
  4. App 通过 WKWebView 提交支付表单。
  5. 用户在 CCAvenue 页面完成支付。
  6. CCAvenue 将支付结果回调给业务服务器。
  7. 服务器验签或解密,并主动查询订单状态。
  8. App 从业务服务器获取最终支付结果。

不能只根据 WebView 的跳转地址或页面文字判断支付成功。最终结果必须以服务器确认的订单状态为准。

服务端准备

先从 CCAvenue 商户后台或技术支持处取得以下信息:

  • merchant_id
  • access_code
  • working_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 或修改加密代码,也许能通过编译,但生成的数据可能无法与网关兼容。

建议按以下顺序处理:

  1. 获取 CCAvenue 当前提供的最新版 iOS SDK。
  2. 确认该 SDK 支持当前 Xcode 和目标 iOS 版本。
  3. 按集成包要求导入相应的 Framework、Bundle 和资源。
  4. 清理旧版 OpenSSL 头文件、静态库及重复的搜索路径。
  5. 删除 Derived Data,然后重新构建。
  6. 如果新版 SDK 仍依赖不兼容的 OpenSSL,改用服务端加密配合 WKWebView 的接入方式,或向 CCAvenue 技术支持索取兼容版本。

上线前需要检查的事项

  • 测试环境和生产环境的 access_code、密钥及网关地址不能混用。
  • 订单号必须唯一,并由服务器生成或校验。
  • 服务器必须确认金额、币种和商品信息。
  • 回调接口必须使用 HTTPS。
  • 服务端需要验证、解密并记录 CCAvenue 返回的数据。
  • 支付结果处理必须具备幂等性,防止重复发货或充值。
  • 即使 App 被关闭、网络中断或 WebView 没有返回,系统仍应能通过服务器恢复订单状态。
  • 日志不能输出 working_key、完整银行卡信息或未经脱敏的支付数据。
  • 测试范围应覆盖成功、失败、取消、超时、处理中和重复回调等场景。

如果现有 SDK 已出现 OpenSSL 编译或架构错误,而且项目不依赖 SDK 提供的原生界面,通常可以迁移到“服务端生成加密请求 + WKWebView 支付 + 服务端确认结果”的接入方式。这种方案更安全,也更容易长期维护。

备注:内容仅供参考。