# 开始使用 Connect 嵌入式组件

了解如何在您的网站中嵌入管理平台功能。

使用 Connect 嵌入式组件向您的网站添加关联账户管理平台功能。这些库及其支持的 API 允许您直接在您的管理平台和移动应用中为用户提供对 Stripe 产品的访问权限。

- **添加集成用户界面**：使用嵌入式组件向您的管理平台添加集成用户界面。请参阅[所有可用组件](https://docs.stripe.com/connect/supported-embedded-components.md)。
- **自定义外观**：调整[外观](https://docs.stripe.com/connect/customize-connect-embedded-components.md)以匹配您的品牌。
- **保持最新**：Connect 嵌入式组件与 Stripe API 保持同步，有助于让您的集成保持最新。

## 设置 StripeConnect [客户端] [服务器端]

Stripe 使用 [AccountSession](https://docs.stripe.com/api/account_sessions.md) 来表达您将 API 访问权限授予 Connect 子账户的意图。

AccountSessions API 返回一个*客户端密钥* (The client secret is a unique string returned from Stripe as part of an AccountSession. This string lets the client access a specific Stripe account with Connect embedded components)，允许嵌入式组件访问关联账户的资源，就像您在为它们进行 API 调用一样。

### 创建 AccountSession (服务器)

您的应用必须向服务器发起请求以获取账户会话。您可以在服务器上创建一个新端点，将客户端私钥返回给应用：

#### Ruby

```ruby
require 'sinatra'
require 'stripe'
# This is a placeholder - it should be replaced with your API key.
# Sign in to see your own test API key embedded in code samples.
# Don't put any keys in code. We recommend using a restricted API key with access only to the account sessions resource. See https://docs.stripe.com/keys-best-practices
client = Stripe::StripeClient.new('<<YOUR_SECRET_KEY>>')

post '/account_session' do
  content_type 'application/json'

  # Create an AccountSession
  begin
    account_session = client.v1.account_sessions.create({
      account: {{CONNECTED_ACCOUNT_ID}},
      components: {
        account_onboarding: {
          enabled: true,
          features: {
            # We recommend disabling authentication for a better user experience when possible
            disable_stripe_user_authentication: true,
          }
        }
      }
    })

    {
      client_secret: account_session[:client_secret]
    }.to_json
  rescue => error
    puts "An error occurred when calling the Stripe API to create an account session: #{error.message}";
    return [500, { error: error.message }.to_json]
  end
end
```

### 创建 Account Session API

[创建账户会话 API](https://docs.stripe.com/api/account_sessions/create.md) 决定 Connect 嵌入组件的组件和功能访问权限。Stripe 会对与该账户会话对应的任何组件强制执行这些参数。如果您的应用支持多个用户角色，请确保该账户会话启用的组件和功能与当前用户的角色对应。例如，您可以仅为网站管理员启用[退款管理](https://docs.stripe.com/api/account_sessions/create.md#create_account_session-components-payments-features-refund_management)功能，而不允许其他用户访问。为了确保用户角色访问控制得到强制执行，您必须将网站的用户角色映射到账户会话组件上。

### 安装 StripeConnect SDK (客户端)

[Stripe iOS SDK](https://github.com/stripe/stripe-ios) 是开源的，[有完整的文档](https://stripe.dev/stripe-ios/index.html)，并且与支持 iOS 15 或更高版本操作系统的应用程序兼容。

#### Swift Package Manager

要安装 SDK，按这些步骤进行：

1. 在 Xcode 中，选择**文件** > **添加工具包依赖…**并输入 `https://github.com/stripe/stripe-ios-spm` 作为仓库 URL。
2. 从我们的[发布页面](https://github.com/stripe/stripe-ios/releases)选择最新的版本号。
3. 将 **StripeConnect** 产品添加到[您的目标应用程序](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app)。

#### CocoaPods

1. 如果您还没有[CocoaPods](https://guides.cocoapods.org/using/getting-started.html)，请安装其最新版本。
2. 如果您目前没有 [Podfile](https://guides.cocoapods.org/syntax/podfile.html)，请运行以下命令创建一个：
   ```bash
   pod init
   ```
3. 将这一行代码添加到您的 `Podfile`：
   ```podfile
   pod 'StripeConnect'
   ```
4. 运行以下命令：
   ```bash
   pod install
   ```
5. 今后，一定记得用 `.xcworkspace` 文件来打开您在 Xcode 中的项目，不要使用 `.xcodeproj` 文件。
6. 将来，要更新到 SDK 的最新版本，运行：
   ```bash
   pod update StripeConnect
   ```

#### Carthage

1. 如果您还没有[Carthage](https://github.com/Carthage/Carthage#installing-carthage)，请安装其最新版本。
2. 将这一行代码添加到您的 `Cartfile`。
   ```cartfile
   github "stripe/stripe-ios"
   ```
3. 按照 [Carthage 安装说明](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos)进行。确保嵌入[这里](https://github.com/stripe/stripe-ios/tree/master/StripeConnect#manual-linking)所列的所有必要框架。
4. 将来，要更新到 SDK 的最新版本，运行以下命令即可：
   ```bash
   carthage update stripe-ios --platform ios
   ```

#### 手动框架

1. 前往我们的 [GitHub 发布页面](https://github.com/stripe/stripe-ios/releases/latest)，下载并解压缩 **Stripe.xcframework.zip**。
2. 将 **StripeConnect.xcframework** 拖拽到您的 Xcode 项目中 **General**（常规）设置的 **Embedded Binaries**（嵌入式二进制文件）部分。一定要选择 **Copy items if needed**（需要时复制项目）。
3. 为[这里](https://github.com/stripe/stripe-ios/tree/master/StripeConnect#manual-linking)所列的所有必要框架重复第 2 步。
4. 将来，要更新到 SDK 的最新版本，重复第 1-3 步。

> 有关最新 SDK 发布及过往版本的详细信息，请查看 GitHub 上的[发布](https://github.com/stripe/stripe-ios/releases)页面。要想在新版本发布时接收通知，请[查看仓库的发布](https://help.github.com/en/articles/watching-and-unwatching-releases-for-a-repository#watching-releases-for-a-repository)。

### 设置相机授权 (客户端)

Stripe Connect iOS SDK 要求访问设备的摄像头来捕捉身份文件。要使您的应用能够请求相机访问权限，请执行以下操作：

1. 在 Xcode 中打开您项目的 **Info.plist**。
2. 添加 `NSCameraUsageDescription` 密钥。
3. 添加一个字符串值，向用户解释为什么您的应用程序需要相机访问权限，例如：

> 该应用程序将使用相机拍摄您的身份证件照片。

查看 [Apple 文档](https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_ios)，了解有关请求相机授权的更多信息。

### 初始化 EmbeddedComponentManager  (客户端)

使用 `StripeAPI.shared` 设置您的可发布密钥，并使用一个闭包来实例化 [EmbeddedComponentManager](https://stripe.dev/stripe-ios/stripeconnect/documentation/stripeconnect/embeddedcomponentmanager)，该闭包通过调用您在服务器上创建的新端点来检索客户端私钥。要创建组件，请在您上面实例化的 `EmbeddedComponentManager` 上调用相应的创建方法。

[账户入驻](https://docs.stripe.com/connect/supported-embedded-components/account-onboarding.md)会返回一个控制器，用于管理其自身的展示。其他组件（如 [Payments](https://docs.stripe.com/connect/supported-embedded-components/payments.md)）会返回一个 [UIViewController](https://developer.apple.com/documentation/uikit/uiviewcontroller)，您可以应用程序中更灵活地展示该组件。

#### UIKit

```swift
import StripeConnect
import UIKit

class MyViewController: UIViewController {
    let errorView: UIView

    func fetchClientSecret() async -> String? {
        let url = URL(string: "https://{{YOUR_SERVER}}/account_session")!

        var request = URLRequest(url: url)
        request.httpMethod = "POST"

        do {
            // Fetch the AccountSession client secret
            let (data, _) = try await URLSession.shared.data(for: request)
            let json = try JSONSerialization.jsonObject(with: data) as? [String : Any]

            errorView.isHidden = true

            return json?["client_secret"] as? String
        } catch let error {
            // Handle errors on the client side here
            print("An error occurred: \(error)")
            errorView.isHidden = false
            return nil
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // This is your test publishable API key.
        STPAPIClient.shared.publishableKey = "{{PUBLISHABLE_KEY}}"
        let embeddedComponentManager = EmbeddedComponentManager(
            fetchClientSecret: fetchClientSecret
        )

        // Account onboarding presents modally and fullscreen
        let controller = embeddedComponentManager.createAccountOnboardingController()
        controller.title = "Onboard with Stripe"
        controller.present(from: self)

        // All other components can be flexibly presented
        let paymentsViewController = embeddedComponentManager.createPaymentsViewController()
        present(paymentsViewController)
    }
}
```

## Configure the Embedded Component Manager [客户端]

[查看参考文档:external:](https://stripe.dev/stripe-ios/stripeconnect/documentation/stripeconnect/embeddedcomponentmanager/init\(apiClient:appearance:fonts:fetchClientSecret:\))。

### 自定义 Connect 嵌入式组件的外观

[嵌入式组件 Figma UI 工具包](https://www.figma.com/community/file/1438614134095442934)包含每个组件、常见模式和一个示例应用程序。您可以使用它来可视化和设计网站中的嵌入式用户界面。

我们提供了[一组选项](https://docs.stripe.com/connect/embedded-appearance-options.md)来定制 Connect 嵌入式组件的外观。这些定制会影响我们设计系统中的按钮、图标和其他元素。

> #### 必要的弹出窗口
> 
> 嵌入式组件中的某些行为（例如[用户验证](https://docs.stripe.com/connect/get-started-connect-embedded-components.md#user-authentication-in-connect-embedded-components)）必须在经过验证的 WebView 中呈现。您无法定制嵌入式组件以消除此类 WebView。

您可以在初始化 `EmbeddedComponentManager` 时，使用 [EmbeddedComponentManager.Appearance](https://stripe.dev/stripe-ios/stripeconnect/documentation/stripeconnect/embeddedcomponentmanager/appearance) 设置这些选项。

```swift
func fetchClientSecret() async -> String? {
    let url = URL(string: "https://{{YOUR_SERVER}}/account_session")!

    var request = URLRequest(url: url)
    request.httpMethod = "POST"

    do {
        let (data, _) = try await URLSession.shared.data(for: request)
        let json = try JSONSerialization.jsonObject(with: data) as? [String : Any]

        return json?["client_secret"] as? String
    } catch {
        return nil
    }
}

// Specify custom fonts
var customFonts: [CustomFontSource] = []
let myFont = UIFont(name: "My Font", size: 16)!
let fontUrl = Bundle.main.url(forResource: "my-font-2", withExtension: "woff")!
do {
    let customFontSource = try CustomFontSource(font: myFont, fileUrl: fontUrl)
    customFonts.append(customFontSource)
} catch {
    print("Error loading custom font: \(error)")
}

// Customize appearance
var appearance = EmbeddedComponentManager.Appearance()
appearance.typography.font.base = myFont
appearance.typography.fontSizeBase = 16 // Unscaled font size
appearance.colors.primary = UIColor { traitCollection in
    if traitCollection.userInterfaceStyle == .dark {
        return UIColor(red: 0.455, green: 0.424, blue: 1.000, alpha: 1.0)
    } else {
        return UIColor(red: 0.404, green: 0.365, blue: 1.000, alpha: 1.0)
    }
}

STPAPIClient.shared.publishableKey = "{{PUBLISHABLE_KEY}}"
let embeddedComponentManager = EmbeddedComponentManager(
    appearance: appearance,
    fonts: customFonts,
    fetchClientSecret: fetchClientSecret
)
```

当 Connect 嵌入式组件的 [UITraitCollection](https://developer.apple.com/documentation/uikit/uitraitcollection) 被更新时，使用[动态提供程序](https://developer.apple.com/documentation/uikit/uicolor/3238041-init)的外观颜色会自动应用于 Connect 嵌入式组件，包括[暗色模式](https://developer.apple.com/design/human-interface-guidelines/dark-mode)和[辅助功能对比度](https://developer.apple.com/documentation/uikit/uiaccessibilitycontrast)。默认外观不包括暗色模式颜色，因此您必须为 `EmbeddedComponentManager` 指定具有动态颜色的外观，才能在您的应用中支持暗色模式。

指定字体大小时，请使用针对设备的默认大小等级显示的未缩放字体大小。嵌入式组件会根据它的 [UITraitCollection](https://developer.apple.com/documentation/uikit/uitraitcollection) 自动缩放字号。

请参阅 iOS 上的 [外观选项完整列表](https://docs.stripe.com/connect/embedded-appearance-options.md)。

### 使用自定义字体

如果您的应用中使用自定义字体（例如，来自嵌入应用二进制文件的 `.otf` 或 `.tff` 文件），则必须在初始化 `EmbeddedComponentManager` 时，在传递给 `fonts` 参数的 [CustomFontSource](https://stripe.dev/stripe-ios/stripeconnect/documentation/stripeconnect/embeddedcomponentmanager/customfontsource) 中指定字体文件。这使 Connect 嵌入式组件能够访问字体文件，从而正确渲染字体。

在 `appearance` 中指定的字体必须使用 [支持的系统字体](https://developer.apple.com/fonts/system-fonts/)，或在初始化时传递给 `EmbeddedComponentManager` 的 [CustomFontSource](https://stripe.dev/stripe-ios/stripeconnect/documentation/stripeconnect/embeddedcomponentmanager/customfontsource)，才能正确渲染。

[查看参考文档:external:](https://stripe.dev/stripe-ios/stripeconnect/documentation/stripeconnect/embeddedcomponentmanager/customfontsource)。

### 初始化后更新 Connect 嵌入式组件

调用 `update` 方法，更改初始化后嵌入式组件的外观：

```swift
var appearance = EmbeddedComponentManager.Appearance()
appearance.colors.primary = UIColor.red

manager.update(appearance: appearance)
```

## 验证

我们提供了一组 API 来管理 Connect 嵌入式组件中的账户会话和用户凭证。

### 刷新客户端私钥

在长时间运行的会话中，来自最初提供的*客户端私钥* (The client secret is a unique string returned from Stripe as part of an AccountSession. This string lets the client access a specific Stripe account with Connect embedded components)的会话可能会过期。当它过期时，我们会自动用 `fetchClientSecret` 检索新的客户端私钥并刷新会话。您不需要传入任何额外参数。

```swift
func fetchClientSecret() async -> String? {
    var request = URLRequest(url: URL(string: "https://{{YOUR_SERVER}}/account_session")!)
    request.httpMethod = "POST"

    do {
        let (data, _) = try await URLSession.shared.data(for: request)
        let json = try JSONSerialization.jsonObject(with: data) as? [String : Any]
        return json?["client_secret"] as? String
    } catch let error {
        return nil
    }
}

STPAPIClient.shared.publishableKey = "{{PUBLISHABLE_KEY}}"
let embeddedComponentManager = EmbeddedComponentManager(
    fetchClientSecret: fetchClientSecret
)
```

## 本地化

Connect 嵌入式组件支持以下地区：

| 语言 | 区域代码 |
| --- | --- |
| 保加利亚语（保加利亚） | `bg-BG` |
| 中文（简体） | `zh-Hans` |
| 中文（繁体 - 香港） | `zh-Hant-HK` |
| 中文（繁体 - 台湾） | `zh-Hant-TW` |
| 克罗地亚语（克罗地亚） | `hr-HR` |
| 捷克语（捷克） | `cs-CZ` |
| 丹麦语（丹麦） | `da-DK` |
| 荷兰语（荷兰） | `nl-NL` |
| 英语（澳大利亚） | `en-AU` |
| 英语（印度） | `en-IN` |
| 英语（爱尔兰） | `en-IE` |
| 英语（新西兰） | `en-NZ` |
| 英语（新加坡） | `en-SG` |
| 英语（英国） | `en-GB` |
| 英语（美国） | `en-US` |
| 爱沙尼亚语（爱沙尼亚） | `et-EE` |
| 菲律宾语（菲律宾） | `fil-PH` |
| 芬兰语（芬兰） | `fi-FI` |
| 法语（加拿大） | `fr-CA` |
| 法语（法国） | `fr-FR` |
| 德语（德国） | `de-DE` |
| 希腊语（希腊） | `el-GR` |
| 匈牙利语（匈牙利） | `hu-HU` |
| 印度尼西亚语（印度尼西亚） | `id-ID` |
| 意大利语（意大利） | `it-IT` |
| 日语（日本） | `ja-JP` |
| 朝鲜语（韩国） | `ko-KR` |
| 拉脱维亚语（拉脱维亚） | `lv-LV` |
| 立陶宛语（立陶宛） | `lt-LT` |
| 马来语（马来西亚） | `ms-MY` |
| 马耳他语（马耳他） | `mt-MT` |
| 挪威语（波克默尔语）（挪威） | `nb-NO` |
| 波兰语（波兰） | `pl-PL` |
| 葡萄牙语（巴西） | `pt-BR` |
| 葡萄牙语（葡萄牙） | `pt-PT` |
| 罗马尼亚语（罗马尼亚） | `ro-RO` |
| 斯洛伐克语（斯洛伐克） | `sk-SK` |
| 斯洛文尼亚语（斯洛文尼亚） | `sl-SI` |
| 西班牙语（阿根廷） | `es-AR` |
| 西班牙语（巴西） | `es-BR` |
| 西班牙语（拉丁美洲） | `es-419` |
| 西班牙语（墨西哥） | `es-MX` |
| 西班牙语（西班牙） | `es-ES` |
| 瑞典语（瑞典） | `sv-SE` |
| 泰语（泰国） | `th-TH` |
| 土耳其语（土耳其） | `tr-TR` |
| 越南语（越南） | `vi-VN` |

## Connect 嵌入式组件中的用户身份验证

Connect 嵌入式组件通常不需要用户验证。在某些场景中，Connect 嵌入式组件要求关联账户在访问组件以提供必要功能之前使用其 Stripe 账户登录（例如，在[账户注册](https://docs.stripe.com/connect/supported-embedded-components/account-onboarding.md)组件中向账户法律实体写入信息的情况）。其他组件可能在首次呈现后需要在组件内进行验证。

当要求变更时，若由 Stripe 负责收集更新后的信息，则关联账户需要进行验证。对于要求到期或变更时由您负责收集更新后的信息的关联账户（如 Custom 账户），Stripe 验证由 [disable_stripe_user_authentication](https://docs.stripe.com/api/account_sessions/create.md#create_account_session-components-account_onboarding-features-disable_stripe_user_authentication) 账户会话功能控制。我们建议将实施双重验证 (2FA) 或等效安全措施作为[最佳实践](https://docs.stripe.com/connect/risk-management/best-practices.md#prevent-account-take-overs)。对于支持此功能的账户配置（如 Custom 账户），如果关联账户无法偿还[负余额](https://docs.stripe.com/connect/risk-management/best-practices.md#decide-your-approach-to-negative-balance-liability)，则由您承担责任。

### 需要验证的组件

关联账户将在您的应用中看到经过验证的 [WebView](https://developer.apple.com/documentation/authenticationservices/aswebauthenticationsession)。关联账户必须先进行验证，才能在 WebView 中继续其工作流程。

Stripe 托管的验证流程会显示您在 [Connect 设置](https://dashboard.stripe.com/account/applications/settings)中设置的品牌名称、颜色和图标，且在验证完成前不会使用来自[嵌入式组件管理器](https://docs.stripe.com/connect/get-started-connect-embedded-components.md#configuring-connect)的定制外观和字体。

以下组件要求关联账户在特定场景下进行验证：

- [账户入驻](https://docs.stripe.com/connect/supported-embedded-components/account-onboarding.md)
- [提现](https://docs.stripe.com/connect/supported-embedded-components/payouts.md)

## 处理加载错误 [客户端]

通过实现组件的 `onLoadError` 监听方法来响应组件加载失败。

由于组件加载失败的原因可能不同，`onLoadError` 方法可能会被多次调用，因此由 `onLoadError` 触发的任何逻辑都必须具备幂等性。

#### Swift

```swift
// All components emit load errors. This example uses AccountOnboarding.
// All components support didFailLoadWithError.
class MyViewController: UIViewController, AccountOnboardingControllerDelegate {
    func openAccountOnboarding() {
        let accountOnboardingController = embeddedComponentManager.createAccountOnboardingController();
        accountOnboardingController.delegate = self
        accountOnboardingController.present(from: self)
    }

    // MARK: - AccountOnboardingControllerDelegate
    func accountOnboarding(_ accountOnboarding: AccountOnboardingController, didFailLoadWithError error: Error) {
        print("Account onboarding failed to load with error '\(error)'")
    }
}
```
