Electron inAppPurchase 中 ProductDiscount 结构体详解:字段语义、StoreKit 映射与源码实现

发布时间:2026/10/30 8:23:07

Electron inAppPurchase 中 ProductDiscount 结构体详解:字段语义、StoreKit 映射与源码实现 Electron inAppPurchase 中 ProductDiscount 结构体详解字段语义、StoreKit 映射与源码实现【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electronElectron 的ProductDiscount对象是 Mac App Store 应用内购买inAppPurchaseAPI 中的核心数据结构用于描述一个商品的折扣信息包括促销价格、支付方式按量付费、预付、免费试用以及订阅周期。掌握它的每个字段就能正确渲染首周半价之类的促销 UI、计算用户实际付费金额并在inAppPurchase.getProducts()返回结果中定位introductoryPrice与discounts两个折扣入口。本文基于仓库中的 ProductDiscount 结构文档、Product 结构文档、inAppPurchase API 文档 以及 C/Objective-C 源码in_app_purchase_product.h、in_app_purchase_product.mm、electron_api_in_app_purchase.cc逐字段说明该结构的语义、取值来源和边界行为。ProductDiscount 在 API 体系中的位置inAppPurchase是 Electron 提供的主进程模块仅用于 Mac App StoreMAS场景。调用inAppPurchase.getProducts(productIDs)后返回PromiseProduct[]而每个 Product 对象通过两个字段携带ProductDiscountintroductoryPriceProductDiscount可选—— 商品的首次引入价格introductory price即首单优惠价discountsProductDiscount[] —— 该商品所有在售折扣优惠的数组。从源码结构看这一映射关系在 Objective-C 层由 skProductToStruct: 完成当SKProduct.introductoryPrice非 nil 时转换并填入introductoryPrice当product.discounts非 nil 时逐个转换所有SKProductDiscount并 push 进discounts向量。字段逐一说明ProductDiscount共 7 个字段其中 6 个必选、1 个可选字段类型必选说明identifierstring是用于唯一标识一个折扣优惠的字符串typenumber是折扣类型pricenumber是折扣价以本地货币计priceLocalestring是用于格式化折扣价的语言环境paymentModestring是支付模式payAsYouGo/payUpFront/freeTrialnumberOfPeriodsnumber是折扣可用的期数整数subscriptionPeriodProductSubscriptionPeriod否折扣适用的订阅周期对象identifier与type折扣身份标识identifier是 App Store 为该折扣分配的唯一标识符可用于在折扣数组中区分不同优惠例如同一个季度订阅下可能同时挂有2 周试用和首月半价两个折扣。type表示折扣类型。从 skProductDiscountToStruct: 的实现可以看到这两个字段都是对 StoreKitSKProductDiscount对应属性的直接透传identifier通过base::SysNSStringToUTF8转成 UTF-8 字符串type则直接以(int)productDiscount.type强转为整数。也就是说 Electron 层不解析type的枚举含义原始枚举值会原样到达 JS 侧开发者若需区分类型可结合paymentMode一起判断。price与priceLocale价格数值与格式化price是折扣的本地货币数值double。转换源码中通过[productDiscount.price doubleValue]从NSDecimalNumber取出双精度数值因此 JS 侧拿到的是一个可直接参与算术运算的 number。priceLocale字段的实际填充方式值得特别注意在 in_app_purchase_product.mm 中当productDiscount.priceLocale非 nil 时Electron 会调用内部的formatPrice:withLocal:方法基于NSNumberFormatter的CurrencyStyle货币格式化把按该语言环境格式化后的价格字符串写入priceLocale。这与同文件内Product结构formattedPrice字段的处理逻辑一致——文档将其描述为用于格式化价格的语言环境而源码中该位置携带的是格式化结果字符串实际使用时应以其展示用价格文本的角色来对待而不是拿它做数值解析。paymentMode三种支付模式paymentMode是开发者最常使用的字段之一取值只有三种payAsYouGo—— 按量付费按订阅周期持续扣费payUpFront—— 预付一次性支付整个折扣期的费用freeTrial—— 免费试用。这一枚举映射在 skProductDiscountToStruct: 中通过比较SKProductDiscountPaymentModePayAsYouGo、SKProductDiscountPaymentModePayUpFront、SKProductDiscountPaymentModeFreeTrial三个 StoreKit 常量完成。一个典型的产品定价策略组合是订阅商品同时提供一个freeTrial折扣numberOfPeriods为试用次数加一个payAsYouGo折扣试用转正后的按周期扣费价JS 侧通常通过遍历discounts并过滤paymentMode来分别渲染。numberOfPeriods折扣可用期数numberOfPeriods指示该折扣可用多少期例如首 3 个月半价对应numberOfPeriods: 3。源码中以(int)productDiscount.numberOfPeriods转为整型存储。subscriptionPeriod可选的订阅周期subscriptionPeriod是唯一的可选字段类型为 ProductSubscriptionPeriod包含两个子字段numberOfUnitsnumber —— 订阅周期中的单位数量unitstring —— 时间增量单位可取day、week、month、year。从源码看只有当SKProductDiscount.subscriptionPeriod非 nil 时才会填充该字段见 in_app_purchase_product.mm且单位字符串由SKProductPeriodUnitDay/Week/Month/Year四个枚举逐一映射而来。对于非订阅类折扣如一次性购买的商品促销JS 侧应做undefined防御。C 数据结构与 JS 暴露链路ProductDiscount在 Electron 内部经历两层转换理解这条链路有助于排查字段为什么是这样。第一层是 C 结构体定义位于 in_app_purchase_product.hstruct ProductDiscount { std::string identifier; int type; double price 0.0; std::string priceLocale; std::string paymentMode; int numberOfPeriods; std::optionalProductSubscriptionPeriod subscriptionPeriod; };可以推断几个默认行为price默认初始化为0.0subscriptionPeriod使用std::optional表达可选性ProductDiscount的拷贝构造与默认构造均为default便于在std::vectorProductDiscount中按值传递。第二层是 gin 的 V8 转换器位于 electron_api_in_app_purchase.cc。Converterin_app_purchase::ProductDiscount::ToV8将上述 7 个字段逐个dict.Set写入 JS 对象其中subscriptionPeriod仅在has_value()为真时才写入——这保证了可选字段在 JS 侧表现为属性不存在而非null。外层Product的转换器electron_api_in_app_purchase.cc以同样方式处理introductoryPrice可选有值才 Set与discounts数组整体 Set。实战获取并解析商品的折扣信息以下示例整合了 In-App Purchases 教程 的完整工作流并展示了如何解析introductoryPrice与discounts。开发前需注意测试 IAP 必须修改node_modules/electron/dist/Electron.app/Contents/Info.plist中的CFBundleIdentifier替换为你在 iTunes Connect 中创建的 bundle identifier教程原文给出的示例keyCFBundleIdentifier/key stringcom.example.app/string// Main process const { inAppPurchase } require(electron) const PRODUCT_IDS [com.example.app.product1] // 尽早监听交易事件教程强调务必在调用 purchaseProduct 之前 inAppPurchase.on(transactions-updated, (event, transactions) { if (!Array.isArray(transactions)) return for (const transaction of transactions) { const payment transaction.payment switch (transaction.transactionState) { case purchasing: console.log(Purchasing ${payment.productIdentifier}...) break case purchased: { const receiptURL inAppPurchase.getReceiptURL() // 将 receiptURL 提交服务端校验校验通过后交付商品 inAppPurchase.finishTransactionByDate(transaction.transactionDate) break } case failed: console.log(Failed to purchase ${payment.productIdentifier}.) inAppPurchase.finishTransactionByDate(transaction.transactionDate) break // restored / deferred ... } } }) if (!inAppPurchase.canMakePayments()) { console.log(The user is not allowed to make in-app purchase.) } // 获取商品信息并解析 ProductDiscount 结构 inAppPurchase.getProducts(PRODUCT_IDS).then((products) { if (!Array.isArray(products) || products.length 0) { console.log(Unable to retrieve the product information.) return } for (const product of products) { console.log(${product.localizedTitle} 标准价: ${product.formattedPrice}) // 1. 引入价可选字段可能不存在 if (product.introductoryPrice) { const intro product.introductoryPrice console.log( 引入价 ${intro.priceLocale}${intro.paymentMode} 共 ${intro.numberOfPeriods} 期) if (intro.subscriptionPeriod) { console.log( 引入价周期: ${intro.subscriptionPeriod.numberOfUnits} ${intro.subscriptionPeriod.unit}) } } // 2. 全部在售折扣 for (const discount of product.discounts) { console.log( 折扣[${discount.identifier}] ${discount.priceLocale}) // 用 paymentMode 区分展示策略 // freeTrial - 免费试用 N 期 // payAsYouGo - N 期内每期优惠价 // payUpFront - 一次付清 if (discount.paymentMode freeTrial) { console.log( - 免费试用 ${discount.numberOfPeriods} 期) } } } // 购买教程示例用法 const selectedProduct products[0] inAppPurchase.purchaseProduct(selectedProduct.productIdentifier, 1) .then((isProductValid) { if (!isProductValid) { console.log(The product is not valid.) return } console.log(The payment has been added to the payment queue.) }) })关于行为的边界验证仓库测试 api-in-app-purchase-spec.ts 确认了getProducts()对无效商品 id 会解析为空数组——因此解析discounts前应先对products本身做非空判断示例中已包含。适用前提与限制平台限制inAppPurchase模块的创建逻辑被#if BUILDFLAG(IS_MAC)编译保护见 electron_api_in_app_purchase.ccinAppPurchase属性仅在 macOS 构建中注入且该模块面向 Mac App Store 分发签名/公证/沙箱要求见 code-signing 教程。Windows 与 Linux 上require(electron).inAppPurchase为undefined。数据源依赖ProductDiscount的所有字段均来自 App Store Connect 后台配置的 StoreKit 产品本地代码无法伪造或修改折扣字段缺失如subscriptionPeriod不存在是配置决定的正常现象必须做可选性判断。字段语义以当前仓库源码为准本文涉及的映射关系如paymentMode三值、priceLocale的填充方式均基于当前版本的 in_app_purchase_product.mm 实现升级 Electron 大版本后建议重新核对转换器代码。相关文档inAppPurchase API 完整方法列表Product 结构 / ProductSubscriptionPeriod 结构 / ProductDiscount 原始文档In-App Purchases 开发教程源码ProductDiscount 结构体定义 / StoreKit 转换实现 / gin V8 转换器【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/25 11:54:56

复活的开源项目如何安全接入?一套可复用的评估与迁移指南

如果你最近在技术社区、资源分享帖或者聊天群里看到“真神复活,想要的自己来拿”这类标题,先别急着点下载。这两年,经典工具重新回到视野的事情并不少见,有些是原作者回归,有些是社区接手,有些是公司收购后…

2026/10/24 8:20:55

用大模型打造电商商品资料包自动体检助手

运营同事抱着一叠资料过来的时候,我以为这就是一次普通的上架前核对——6份文档加1张商品图,按以前的节奏,半小时怎么都看完了。结果那一次,她来回翻了三遍只找出两三处明显问题,真正让整个链接被平台打回的&#xff0…

2026/10/30 8:21:21

AI人才缺口400万!高中生现在学什么,2026最新报告讲透了

本文基于《人工智能信息服务产业人才需求分析报告(2026)》,解析了AI产业的现状与未来,指出AI不是风口而是基本盘,存在400万人才缺口,尤其在芯片架构师、底层算法科学家等上游底座岗位。文章详细分析了六大岗…

2026/10/30 8:21:21

英伟达:让智能体在可验证环境中自我进化

📖标题:VERA: Scaling Verifiable Environments for Agentic co-Evolution 🌐来源:arXiv, 2610.05923v1 🛎️文章简介 🔸研究问题:如何让AI智能体在缺乏中间步骤反馈的长周期复杂任务&#xff0…

2026/10/30 8:21:21

小白程序员必学:用Prompt Engineering让AI瞬间变身“数字战友”

本文通过“超级天才”比喻解析Prompt Engineering的核心逻辑,即如何将模糊需求转化为AI可执行的高确定性指令。文章对比了坏提示词(意识流提问)与好提示词(结构化任务清单)的输出差异,并给出三套实用公式&a…

2026/10/30 8:21:21

大模型AI深度解析:小白也能看懂的技术逻辑,收藏这篇就够了!

本文用通俗语言硬核技术拆解真实案例,解析了大模型AI的底层逻辑,包括预训练、微调和推理三个步骤,并探讨了它在职场办公、内容创作、学习提升等场景中的应用。文章还讨论了大模型是否会取代人类工作的问题,指出大模型更可能取代重…

2026/10/30 8:21:21

6个AI追一张火车票退款,把议员的邮箱淹了

⚠️ 说明:本文基于公开报道整理,涉及企业与政策信息以官方发布为准。一张350英镑的火车票。 6个AI助手。 一句"我不在乎付出什么代价"。 然后,事情失控了。一、实验是怎么设计的 先交代背景。 独立AI安全研究员玛蒂尔达格林-亨利&…

2026/10/29 17:05:52

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/29 17:06:02

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/29 17:06:13

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/30 0:00:46

NCP1342高频准谐振反激控制器:从设计约束到验收的完整方案

简介:NCP1342 高频准谐振反激控制器应用介绍译文文档,面向电源设计工程师、硬件研发人员及开关电源学习者,帮助读者跨越英文原版资料的语言门槛,系统理解这款高集成度 QR Flyback 控制器的设计要点。文档围绕芯片核心特性展开&…

2026/10/30 0:00:46

NCP1342准谐振反激电源设计:从选型到验收的全流程解析

简介:这份文档面向电源设计工程师与开关电源学习者,系统梳理NCP1342高频准谐振反激控制器的应用要点,帮助读者快速理解这款高集成度离线电源变换器控制芯片的工作机制与设计方法。内容涵盖芯片核心特性、引脚功能定义、电气特性参数表、最大额…

2026/10/30 0:00:46

Web系统自动登出机制设计与实现全解析

1. 系统自动登出机制的设计初衷在各类Web应用和后台管理系统中,自动登出功能早已成为标配。这个看似简单的功能背后,实际上涉及用户安全、资源管理和体验优化的多重考量。作为从业十多年的老开发,我见过太多因为登出逻辑设计不当导致的安全事…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑