Create App Clips for other businesses
App Store & Distribution 进阶 22m

为其他企业创建 App Clips

Create App Clips for other businesses

2020年6月25日

在 Apple 官方观看视频

一句话判断

如果你是一家 SaaS 公司(比如点餐系统、停车缴费平台),你可以为你的商户创建带有他们品牌和配置的 App Clips,让他们的客户扫码即用——这是一个全新的 B2B2C 分发模式。

这场 Session 讲了什么

App Clips 通常被认为是一种让用户快速体验你的 App 功能的方式。但 Session 10118 揭示了 App Clips 的另一个重要用途:作为平台型公司为你的企业客户提供的即用型服务。

具体场景是:假设你经营一个点餐平台(类似美团外卖的 SaaS 版),你的商户(餐厅)需要一个点餐入口。传统做法是让商户的用户下载你的 App。但有了 App Clips,你可以为每个商户生成一个专属的 App Clip——餐厅把二维码贴在桌上,顾客扫码就能直接点餐,无需安装任何 App。关键的技术点是:App Clip 的 URL 可以携带商户 ID、桌号等参数,App Clip 启动后直接进入对应的商户页面。

值得深挖的点

多商户的 URL 路由设计

当你为成百上千个商户提供 App Clip 时,URL 路由设计变得关键。你需要一个统一的 URL 模式,通过路径参数来区分不同的商户和场景。比如 https://order.app/restaurant/{id}/table/{number}。AASA 文件只需要配置一次通配符路径,就能覆盖所有商户。

App Clip 的配置分发

你可以为每个商户生成不同的 App Clip Code(二维码),URL 中包含商户特有的参数。当用户扫码时,App Clip 收到 URL 后解析参数,直接加载对应商户的菜单和配置。商户的品牌信息(Logo、颜色)可以存储在你的服务器上,App Clip 启动后动态加载。

代码片段

配置支持多商户的 AASA 文件

场景:在服务器端配置通配的 AASA 文件。

{
    "appclips": {
        "apps": ["ABCDE12345.com.orderapp.Clip"],
        "details": [
            {
                "appID": "ABCDE12345.com.orderapp.Clip",
                "paths": [
                    "/restaurant/*",
                    "/order/*",
                    "/pay/*"
                ]
            }
        ]
    },
    "applinks": {
        "details": [
            {
                "appIDs": ["ABCDE12345.com.orderapp"],
                "components": [
                    { "/": "/restaurant/*" },
                    { "/": "/order/*" }
                ]
            }
        ]
    }
}

处理多商户的 URL 路由

场景:App Clip 启动时解析商户信息和参数。

import UIKit

class ClipSceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    
    func scene(_ scene: UIScene,
               openURLContexts URLContexts: Set<UIOpenURLContext>) {
        guard let url = URLContexts.first?.url else { return }
        handleClipURL(url)
    }
    
    private func handleClipURL(_ url: URL) {
        // URL 格式: https://order.app/restaurant/{id}/table/{number}
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
              let pathComponents = components.path.components(separatedBy: "/").filter({ !$0.isEmpty }) else {
            showError()
            return
        }
        
        // 解析路径
        guard pathComponents.count >= 2,
              pathComponents[0] == "restaurant" else {
            showError()
            return
        }
        
        let restaurantId = pathComponents[1]
        var tableNumber: String?
        
        // 解析桌号
        if pathComponents.count >= 4,
           pathComponents[2] == "table" {
            tableNumber = pathComponents[3]
        }
        
        // 加载商户配置
        loadMerchantConfig(restaurantId: restaurantId, tableNumber: tableNumber)
    }
    
    private func loadMerchantConfig(restaurantId: String, tableNumber: String?) {
        // 从服务器获取商户的品牌配置(Logo、颜色、菜单等)
        MerchantService.fetchConfig(restaurantId: restaurantId) { config in
            DispatchQueue.main.async {
                self.showOrderingView(config: config, tableNumber: tableNumber)
            }
        }
    }
    
    private func showOrderingView(config: MerchantConfig, tableNumber: String?) {
        // 直接展示点餐页面,应用商户的品牌样式
        let orderingVC = OrderingViewController(
            merchantConfig: config,
            tableNumber: tableNumber
        )
        orderingVC.modalPresentationStyle = .fullScreen
        
        window?.rootViewController?.present(orderingVC, animated: true)
    }
    
    private func showError() {
        // URL 解析失败的处理
    }
}

生成商户专属的 App Clip Code

场景:为商户生成可打印的二维码。

import UIKit

class QRCodeGenerator {
    
    // 为特定餐厅的特定桌号生成二维码
    static func generateCode(
        restaurantId: String,
        tableNumber: String? = nil
    ) -> UIImage? {
        
        var urlString = "https://order.app/restaurant/\(restaurantId)"
        if let table = tableNumber {
            urlString += "/table/\(table)"
        }
        
        guard let url = URL(string: urlString),
              let data = urlString.data(using: .ascii) else {
            return nil
        }
        
        // 使用 CIFilter 生成二维码
        let filter = CIFilter(name: "CIQRCodeGenerator")
        filter?.setValue(data, forKey: "inputMessage")
        filter?.setValue("M", forKey: "inputCorrectionLevel")  // 中等容错
        
        guard let outputImage = filter?.outputImage else { return nil }
        
        // 缩放到合适大小
        let transform = CGAffineTransform(scaleX: 10, y: 10)
        let scaledImage = outputImage.transformed(by: transform)
        
        return UIImage(ciImage: scaledImage)
    }
}

为商户配置 Apple Pay

场景:让每个商户都能通过 App Clip 接收 Apple Pay 支付。

import PassKit

class PaymentHandler: NSObject, PKPaymentAuthorizationViewControllerDelegate {
    
    private var merchantConfig: MerchantConfig?
    
    func startPayment(config: MerchantConfig, amount: Decimal) {
        self.merchantConfig = config
        
        // 检查是否支持 Apple Pay
        guard PKPaymentAuthorizationViewController.canMakePayments(
            usingNetworks: [.visa, .masterCard, .chinaUnionPay]
        ) else {
            showAlternativePayment()
            return
        }
        
        let request = PKPaymentRequest()
        request.merchantIdentifier = "merchant.com.orderapp"
        request.supportedNetworks = [.visa, .masterCard, .chinaUnionPay]
        request.merchantCapabilities = .capability3DS
        
        // 使用商户的配置
        request.paymentSummaryItems = [
            PKPaymentSummaryItem(
                label: config.name,  // 显示商户名称
                amount: NSDecimalNumber(decimal: amount)
            )
        ]
        
        // 设置为 App Clip 的即时支付
        request.requiredShippingContactFields = [.name, .phone]
        
        guard let vc = PKPaymentAuthorizationViewController(paymentRequest: request) else {
            return
        }
        vc.delegate = self
        
        // 展示支付界面
        // present(vc, animated: true)
    }
    
    func paymentAuthorizationViewController(
        _ controller: PKPaymentAuthorizationViewController,
        didAuthorizePayment payment: PKPayment,
        handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) {
        
        // 处理支付
        // 将 payment.token 发送到你的支付服务器
        // 使用商户的支付配置进行处理
        
        completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
    }
    
    func paymentAuthorizationViewControllerDidFinish(
        _ controller: PKPaymentAuthorizationViewController) {
        controller.dismiss(animated: true)
    }
    
    private func showAlternativePayment() {
        // 如果不支持 Apple Pay,显示其他支付方式
    }
}

最佳实践

已有项目:如果你在经营一个面向商户的 SaaS 平台(点餐、停车、预约等),立即评估 App Clips 的集成方案。核心改动是:添加 App Clip target、配置 URL 路由、为每个商户生成专属的 App Clip Code。商户端只需要打印和放置二维码。

新项目:从架构上就把”商户配置化”考虑进去。商户的品牌、菜单、支付方式都应该是动态配置的,而不是硬编码的。App Clip 只是前端的展示层,所有的配置数据来自你的后端 API。

还有什么值得关注

  • App Clip 的 10MB 体积限制意味着你不能把所有商户的配置都打包进去,必须通过网络动态加载。
  • Apple 提供了 App Clip Code 生成器(Xcode 内置),可以生成带有 Apple Logo 的标准二维码。
  • App Clip 支持 NFC 标签触发,适合商户的线下场景(如 NFC 贴纸贴在桌上)。
WWDC 2020