nnsdk 使用说明

nnsdk 是一个用于简化应用初始化、用户归因、内购支付和埋点上报的 Flutter 插件,封装了 iOS 原生能力,对外提供统一的 Dart API。


支持环境

  • Flutter 版本:推荐 3.19.6 及以上
  • iOS
    • 最低支持 iOS 15.0+
    • 需要开启 In-App Purchase 能力,正确配置 Bundle Id、签名证书等

1. 引用与初始化

1.1 导入头文件 / 包

Dart 侧:

import 'package:nnsdk_with_ad/nnsdk.dart';
import 'package:nnsdk_with_ad/Entity/ConfigEntity.dart';
import 'package:nnsdk_with_ad/Entity/BridgingEntity.dart';
import 'package:nnsdk_with_ad/Entity/InitResultEntity.dart';

如果你在原生侧iOS SDK 提供的接口,请根据对应平台在工程中引入生成的插件代码(通常 Flutter 工程会自动处理,一般无需手动导入)。

1.2 Flutter侧初始化配置与桥接参数

ConfigEntity 用于配置基础参数,BridgingEntity 用于配置服务端接口地址等桥接信息。示例:

final config = ConfigEntity(
  isDebug: true, // 是否调试模式
  isLog: true,   // 是否打印日志
  host: 'https://api.example.com', // 隔离域名
  encryptedHeader: true, // 是否加密请求头
  prdId: 'your_product_id',
  gdtAppId: 'your_gdt_app_id', // 广点通 APP ID
  gdtAppKey: 'your_gdt_app_key', // 广点通 APP KEY
);

final bridging = BridgingEntity(
  userLogin: '/user/login',
  addOrder: '/order/add',
  verifyOrderV2: '/order/verify',
  reportState: '/order/reportState',
  goodsList: '/goods/list',
  appStartNew: '/app/start',
  recoverOrder: '/order/recover',
  updateFields: '/user/updateDigitalUnionId',
  // 其余字段根据你的后端实际定义填写
);

1.3 Flutter侧初始化 SDK

建议在应用启动后尽早初始化,例如在 main() 或首页 initState 中:

Future<void> initNnSdk() async {
  final result = await Nnsdk.instance.initSdk(
    config: config,
    bridging: bridging,
  );

  if (result.success == true) {
    // 初始化成功,可以安全调用支付、埋点等接口
    print('nnsdk 初始化成功');
  } else if (result.errorCode == -1) {
    // 网络问题,初始化失败 (未授权网络/未知网络)
    print('nnsdk 初始化失败,未授权网络/未知网络');
  }else {
    // 初始化失败,可根据 errorCode / errorMsg 做相应处理或提示
    print('nnsdk 初始化失败: code=${result.errorCode}, msg=${result.errorMsg}');
  }
}

1.4 原生AppDelegate初始化

import UIKit
import Flutter
import nnsdk

@main
@objc class AppDelegate: FlutterAppDelegate {

    let nnsdk = NnsdkPlugin()

    override func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }


    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        /// 1.2.7版本新增 广点通融合归因处理,需要验证广点通后台日志,查看接入情况
        nnsdk.onOpenUrl(url: url)
        return true
    }

    override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        /// 1.2.7版本新增 广点通融合归因处理,需要验证广点通后台日志,查看接入情况
        nnsdk.onContinueUserActivity(userActivity: userActivity)
        return true;
    }

}

2. 获取基础信息

final sdk = Nnsdk.instance;

final deviceId = sdk.deviceId;             // 设备唯一标识
final deviceInfo = sdk.deviceInfo;         // 设备信息实体 DeviceInfoEntity
final attribution = sdk.attributionEntity; // 归因信息
final isNewUser = sdk.isNewUser;           // 是否新用户
final userId = sdk.userId;                 // 中台用户 ID

3. 商品与支付

3.1 获取商品列表

initSdk 成功后,SDK 会自动拉取商品并与原生商店信息合并,可通过:

final products = sdk.productList; // List<CommodityItemEntity>

3.2 发起下单与支付

Future<void> startPurchase(String commodityId) async {
  final result = await Nnsdk.instance.createOrder(
    commodityId: commodityId,
    transferParameter: 'optional_extra_param', // 商品透传参数 jsonString,由商品接口下发
    userIdentifier: 'user_123', // 可选,不传则使用 deviceId
  );

  if (result.status) {
    // 下单 + 支付 + 验单成功
    print('支付成功,订单号: ${result.orderId}');
  } else {
    // 支付或验单失败,可结合 code / message 做处理
    print('支付失败: code=${result.code}, msg=${result.message}');
  }
}

3.3 恢复购买

Future<void> restorePurchase() async {
  final restoreResult = await Nnsdk.instance.restore('user_123'); // 可选 userIdentifier

  if (restoreResult.status == 1) {
    print('恢复购买成功');
  } else {
    print('恢复购买失败,status=${restoreResult.status}');
  }
}

3.4 处理未完成订单

Future<void> handleUnfinishedOrders() async {
  // isDiscard = false: 继续验单,失败再 finish;true: 直接 finish 掉
  final ok = await Nnsdk.instance.finishUnVerifyOrders(false, 'user_123');
  print('处理未完成订单结果: $ok');
}

4. 埋点上报

Future<void> trackExample() async {
  await Nnsdk.instance.track(
    eventName: 'app_open',
    params: {
      'scene': 'home',
      'from': 'push',
    },
  );
}

5. 苹果商品价格与优惠展示示例

nnsdk 在拉取商品后,会将服务端商品信息与苹果商店的价格、订阅促销信息合并到 CommodityItemEntity / CommodityStoreEntity 中,你可以基于这些字段来展示原价、促销价、首期优惠等信息。

5.1 展示基础价格

import 'package:nnsdk_with_ad/Entity/CommodityItemEntity.dart';

void showBasePrice(CommodityItemEntity item) {
  final store = item.storeEntity; // CommodityStoreEntity?,根据你的实体结构获取
  if (store == null) return;

  final currency = store.storeCurrency ?? 'CNY';
  final price = store.storePrice ?? 0;

  print('原价:$price $currency');
}

5.2 展示订阅首期免费试用(freeTrial)

CommodityStoreEntity 中已经提供了 isFreeIntroTrialintroductoryRoundValueintroductoryRoundUnitText() 等辅助方法,可以方便地判断并展示“免费试用 X 天/周/月/年”:

import 'package:nnsdk_with_ad/Entity/CommodityStoreEntity.dart';

String buildIntroFreeTrialText(CommodityStoreEntity store) {
  if (!store.isFreeIntroTrial) {
    return '';
  }

  final roundValue = store.introductoryRoundValue ?? 0;
  final unitText = store.introductoryRoundUnitText(); // 返回 天/周/月/年/空字符串

  if (roundValue <= 0 || unitText.isEmpty) {
    return '免费试用';
  }

  return '$roundValue$unitText 免费试用';
}

在商品卡片中展示示例:

Widget buildIntroTag(CommodityStoreEntity store) {
  final text = buildIntroFreeTrialText(store);
  if (text.isEmpty) return const SizedBox.shrink();

  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
    decoration: BoxDecoration(
      color: Colors.redAccent,
      borderRadius: BorderRadius.circular(10),
    ),
    child: Text(
      text,
      style: const TextStyle(color: Colors.white, fontSize: 12),
    ),
  );
}

5.3 展示按期付款优惠(Pay As You Go)

对于 introductoryPaymentMode == "payAsYouGo" 的商品,可以利用 isPayAsYouGoIntro 来展示“前 X 期 Y 元/期”之类的文案:

String buildPayAsYouGoText(CommodityStoreEntity store) {
  if (!store.isPayAsYouGoIntro) {
    return '';
  }

  final currency = store.storeCurrency ?? 'CNY';
  final introPrice = store.introductoryOfferPrice ?? 0;
  final roundValue = store.introductoryRoundValue ?? 0;
  final unitText = store.introductoryRoundUnitText(); // 天/周/月/年

  if (roundValue <= 0 || unitText.isEmpty) {
    return '限时优惠价:$introPrice $currency';
  }

  return '前$roundValue$unitText $introPrice $currency / $unitText';
}

你可以在 UI 中同时展示原价与首期优惠,例如:

Widget buildPriceSection(CommodityStoreEntity store) {
  final currency = store.storeCurrency ?? 'CNY';
  final originPrice = store.storePrice ?? 0;
  final payAsYouGoText = buildPayAsYouGoText(store);

  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      if (payAsYouGoText.isNotEmpty)
        Text(
          payAsYouGoText,
          style: const TextStyle(
            fontSize: 14,
            fontWeight: FontWeight.bold,
            color: Colors.redAccent,
          ),
        ),
      const SizedBox(height: 2),
      Text(
        '原价:$originPrice $currency',
        style: const TextStyle(
          fontSize: 12,
          color: Colors.grey,
          decoration: TextDecoration.lineThrough,
        ),
      ),
    ],
  );
}

6. 广告组件

nnsdk 提供了两个广告 Widget,封装了野马广告 SDK 的原生广告和开屏广告能力。

6.1 NNAdWidget(原生信息流广告)

NNAdWidget 用于在页面中展示原生信息流广告(App 自渲染),支持多种布局样式。广告加载完成前不占用空间,加载成功后自动展示。

基本用法:

import 'package:nnsdk_with_ad/NNAdWidget.dart';

NNAdWidget(
  placeId: "33001",          // 广告位 ID(必填)
  width: 345,                // 广告宽度
  height: 87,                // 广告高度
  viewStyle: "common",       // 布局样式:common / homeBottom / countTopBanner / skinList / personalList
  margin: EdgeInsets.only(bottom: 9),
  onAdLoaded: () {
    print("广告加载成功");
  },
  onAdFailed: () {
    print("广告加载失败");
  },
  onClose: () {
    print("广告关闭");
  },
)

参数说明:

参数 类型 必填 说明
placeId String 广告位 ID
width double 广告视图宽度
height double 广告视图高度
viewStyle String 布局样式,决定原生视图的排版方式
identifier String 页面标识,用于广告上报
margin EdgeInsetsGeometry 外边距
adBackgroundColor String 背景色,十六进制字符串如 "#FFFFFF"
adBorderRadius int 广告容器圆角,默认 14
adBorderColor String 广告图片描边颜色,十六进制如 "#F8F8F8"
adBorderWidth double 广告图片描边宽度(pt)
adImageBorderRadius int 广告图片描边圆角
onAdLoaded VoidCallback 广告加载成功回调
onAdFailed VoidCallback 广告加载失败回调
onClose VoidCallback 用户点击关闭按钮回调

注意事项:

  • NNAdWidget 仅支持 iOS 平台,Android 会返回 SizedBox.shrink()
  • 广告在加载完成前通过 Offstage 隐藏,不占用页面布局空间。
  • 使用 GlobalKey 可以在 widget 树移动时保持原生视图状态,避免重复加载。
  • 广告关闭后 widget 会自动隐藏(内部管理 _isVisible 状态)。

6.2 NNSplashAdWidget(开屏广告)

NNSplashAdWidget 用于展示全屏开屏广告,通常在 App 启动时使用。

基本用法:

import 'package:nnsdk_with_ad/NNAdWidget.dart';

NNSplashAdWidget(
  placeId: "your_splash_ad_place_id",  // 广告位 ID(必填)
  onAdShowed: () {
    print("开屏广告已展示");
  },
  onAdFail: (String error) {
    print("开屏广告加载失败: $error");
  },
  onClose: () {
    print("开屏广告已关闭");
    // 跳转到首页
  },
)

参数说明:

参数 类型 必填 说明
placeId String 开屏广告位 ID
onAdShowed VoidCallback 广告展示成功回调
onAdFail Function(String) 广告加载失败回调,返回错误信息
onClose VoidCallback 广告关闭回调(用户点击跳过或倒计时结束)

典型使用场景:

class SplashPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NNSplashAdWidget(
        placeId: "your_splash_ad_place_id",
        onClose: () {
          Navigator.of(context).pushReplacementNamed('/home');
        },
        onAdFail: (error) {
          // 广告加载失败,直接进入首页
          Navigator.of(context).pushReplacementNamed('/home');
        },
      ),
    );
  }
}

注意事项:

  • NNSplashAdWidget 使用 SizedBox.expand() 填满父容器,请确保父容器有明确尺寸。
  • 广告关闭或加载失败后 widget 会自动隐藏。
  • 建议在 onCloseonAdFail 中都处理页面跳转逻辑,确保用户不会卡在开屏页。

7. 常见注意事项

  • 务必先调用 initSdk 并确保成功后,再进行支付、恢复购买等操作
  • 确保 ConfigEntityBridgingEntity 中的各项配置(尤其是域名、接口路径、产品 ID 等)与后端实际环境保持一致。
  • 生产环境请关闭 isDebug 或减少日志输出,避免泄露敏感信息。

以上示例仅为展示文案示例,你可以根据产品需求自由调整样式与文案,如在“免费试用”之后追加自动续费说明等。

Libraries

CryptUtil
Entity/AttributionEntity
Entity/BridgingEntity
YApi QuickType插件生成,具体参考文档:https://plugins.jetbrains.com/plugin/18847-yapi-quicktype/documentation
Entity/CommodityItemEntity
Entity/CommodityStoreEntity
Entity/ConfigEntity
Entity/DeviceInfoEntity
Entity/InitResultEntity
Entity/PayResultEntity
Entity/RestoreResultEntity
YApi QuickType插件生成,具体参考文档:https://plugins.jetbrains.com/plugin/18847-yapi-quicktype/documentation
Entity/UserLoginEntity
GlobalKey
http/NnRequest
NativeUseFlutter
NNAdWidget
nnsdk
nnsdk_method_channel
nnsdk_platform_interface
NNUtil
TrackEventRule
TrackEventUtil