Rui Ling Koh
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    ### 3. Adding Serial Ports & Supporting Customer Presented Transactions - 添加串行端口并支持客户提交的交易 This section guides you through adding support for connecting to external peripherals using serial ports, and how to perform Customer Presented transactions, one of the key use cases of serial communications. At the end of this section, you **SHOULD** be able to: - Understand how the SDK connects and works with serial ports - Handle requests from the BeepConnector to connect to serial ports - Implement best practices for serial port connectivity - Understand and implement Customer Presented type Transactions 本部分将指导您添加对使用串行端口连接到外部外围设备的支持,以及如何执行客户提交的交易,这是串行通信的关键用例之一。在本部分结束时,您**应该**能够: - 了解 SDK 如何连接和使用串口 - 处理来自 BeepConnector 的请求以连接到串行端口 - 实施串行端口连接的最佳实践 - 了解并实施客户提交类型的交易 #### 3.1. SerialPort and SerialSource - 串行端口和串行源 Before we dive in, it is best to understand how the SDK represents Serial Ports and how it communicates with them. **What is the purpose of `SerialPort`?** In Section 1, we ran through the `register(...)` function, and instructed you to ignore the `serialPortLists: List<SerialPort>` parameter by leaving it blank. Now, we'll take a closer look at what it does and its main purpose. Different Devices and operating systems have different names and paths for the physical and virtual ports available to user programs. Even within the same type of operating system, different software versions can have varying names for the same ports. A DB9 RS232 physical port might appear as "tty/S1" on one Device, but register as "tty/mxc0" on a similar but newer Device. Serial ports are used by the SDK to connect to a large variety of external Devices, including but not limited to: - Printers - Barcode Scanners - QR Code Scanners - RFID NFC Readers - EM Card Readers - Bluetooth Beacon Sensors - Infrared Sensors - Payment Terminals The SDK handles all the functional logic and lifecycle management for all of the peripherals it connects to, and only needs your App to help provide the connectivity link to them to send and receive commands. Therefore, the `SerialPort` object helps to provide a consistent layer of abstraction for the SDK, by mapping the SDK provided enumeration `SerialPortNumber` to the real serial port in each respective Device. This allows us to provide a standardised configuration panel on our web dashboard for operators to map each external peripheral to each port per Device. Instead of setting Device "A" to connect to a QR Code Scanner via "tty/S1" and Device "B" to connect to the same QR Code Scanner via "tty/mxc0" even though the physical port is the same on both Devices, operators can set them to connect to `SerialPortNumber.ONE` for both. **Class Definition:** - `SerialPort(serialPortNumber: SerialPortNumber, realPortName: String)` - Your App **SHOULD** map each actual available serial port's name (`ttyS1`, `ttyACM0`, `ttyUSB0`, etc.) to Beep's `SerialPortNumber` (`ZERO`, `ONE`, `TWO`, etc.) in a 1 to 1 relationship. - `serialPortNumber`: Beep's standardized port number - `realPortName`: Actual serial port reference - Here is an example to provide a clearer understanding: - Your App runs on an Android Device that has 2 RS232C Serial Ports and 2 USB Serial Ports. - This appears on the Android system as `/dev/ttyS1`, `/dev/ttyS2`, `/dev/ttyUSB0` and `/dev/ttyUSB1`. Your App is free to decide how to map each port to each `serialPortNumber` in the SDK by default, but it **SHOULD** also give the user the ability to edit this configuration on its own UI, as explained in Section 3.3. - In this case, your App can decide to give an array list of `SerialPorts` as seen below: - `realPortName`: `/dev/ttyS1`, `serialPortNumber`: `SerialPortNumber.ZERO` - `realPortName`: `/dev/ttyS2`, `serialPortNumber`: `SerialPortNumber.ONE` - `realPortName`: `/dev/ttyUSB0`, `serialPortNumber`: `SerialPortNumber.TWO` - `realPortName`: `/dev/ttyUSB1`, `serialPortNumber`: `SerialPortNumber.THREE` **Parameter Definition:** - `serialPortLists: List<SerialPort>` - A list of all serial ports this Device can provide to the SDK for use. The SDK **MAY** use one or more serial ports provided; it **MAY** also use none, depending on the payment settings for this Device. - It is **RECOMMENDED** to have at least 2 serial ports. - The App **SHOULD** allow user to set the serial ports mapping on its own UI. - The App is responsible for ensuring that it has the correct permissions and access to the Serial Ports it provides to the SDK. - SDK will request to connect to serial ports according to what is provided here through `accessSerialPort`. **Usage:** Now that you are aware of what `SerialPort` is and the parameter definition, you **SHOULD** update your `register(...)` code to provide all available ports to the SDK when initializing it. 在我们继续前,我们最好先了解 SDK 如何表示串行端口以及它如何与它们通信。 **`SerialPort` 的目的是什么?** 在第 1 部分中,我们解释了 `register(...)` 函数,并指示您忽略 `serialPortLists: List<SerialPort>` 参数,将其留空。现在,我们将仔细研究它的作用及其主要目的。 对于用户程序可用的物理和虚拟端口,不同的设备和操作系统具有不同的名称和路径。即使在同一类型的操作系统中,不同的软件版本也可以为相同的端口使用不同的名称。 DB9 RS232 物理端口在一个设备上可能显示为 “tty/S1”,但在类似但较新的设备上注册为 “tty/mxc0”。 SDK 使用串行端口连接各种外部设备,包括但不限于: - 打印机 - 条码扫描仪 - 二维码扫描仪 - RFID NFC 阅读器 - EM 读卡器 - 蓝牙信标传感器 - 红外线传感器 - 支付终端 SDK 为它连接的所有外围设备处理所有功能逻辑和生命周期管理,并且只需要您的应用程序帮助提供连接链接以发送和接收命令。 因此,`SerialPort` 对象通过将 SDK 提供的枚举 `SerialPortNumber` 映射到每个相应设备中的真实串行端口,有助于为 SDK 提供一致的抽象层。这使我们能够在 仪表板上提供标准化的配置面板,供操作员将每个外部外围设备映射到每个设备的每个端口。 即使两个设备上的物理端口相同,运营商可以将它们设置为连接到 `SerialPortNumber.ONE`,而不是将设备 “A” 设置为通过 “tty/S1” 连接到二维码扫描器与将设备 “B” 设置为通过 “tty/mxc0” 连接到同一个二维码扫描器。 **类定义:** - `SerialPort(serialPortNumber: SerialPortNumber, realPortName: String)` - 您的应用程序**应该**将每个实际可用的串行端口的名称(`ttyS1`、`ttyACM0`、`ttyUSB0` 等)映射到 Beep 的 `SerialPortNumber`(`ZERO`、`ONE`、`TWO` 等) 1 对 1 的关系。 - `serialPortNumber`: Beep 的标准化端口号 - `realPortName`: 实际串口 - 以下是一个例子,以提供更清晰的理解: - 您的应用程序在具有 2 个 RS232C 串行端口和 2 个 USB 串行端口的安卓设备上运行。 - 这在安卓系统上显示为 `/dev/ttyS1`、`/dev/ttyS2`、`/dev/ttyUSB0` 和 `/dev/ttyUSB1`。默认情况下,您的应用程序可以自由决定如何将每个端口映射到 SDK 中的每个 `serialPortNumber`,但它**应该**还允许用户在自己的 UI 上编辑此配置,如第 3.3 部分所述。 - 在这种情况下,您的应用程序可以决定提供一个 `SerialPorts` 数组列表,如下所示: - `realPortName`:`/dev/ttyS1`,`serialPortNumber`:`SerialPortNumber.ZERO` - `realPortName`:`/dev/ttyS2`,`serialPortNumber`:`SerialPortNumber.ONE` - `realPortName`:`/dev/ttyUSB0`,`serialPortNumber`:`SerialPortNumber.TWO` - `realPortName`:`/dev/ttyUSB1`,`serialPortNumber`:`SerialPortNumber.THREE` **枚举定义:** - `serialPortLists: List<SerialPort>` - 此设备可以提供给 SDK 使用的所有串行端口的列表。 SDK **可以**使用提供的一个或多个串口;它也**可以**不使用任何串口,具体取决于此设备的付款设置。 - **推荐**至少有 2 个串口。 - 应用程序**应该**允许用户在自己的 UI 上设置串口映射。 - 应用程序负责确保它拥有正确的权限并访问它提供给 SDK 的串行端口。 - SDK 将根据这里提供的内容通过 `accessSerialPort` 请求连接到串口。 **用法:** 现在您已经了解了 `SerialPort` 是什么以及参数定义,您**应该**更新您的 `register(...)` 代码,以便在初始化 SDK 时为其提供所有可用端口。 #### 3.2. Handling BeepConnector requests using `accessSerialPort` - 使用 `accessSerialPort` 处理 BeepConnector 请求 As the SDK runs and sets itself up, the configurations set for this particular Device **MAY** require it to connect to one or more `SerialPort` for different uses, tagged by `SerialPortNumber`. The SDK will reference all `SerialPort` objects given through the `serialPortLists` parameter from the App's `register(...)` call, and match the `SerialPortNumber` configured against those in the list. For each match, it will call the `accessSerialPort(...)` callback to request a connection. **Function Definition:** - `accessSerialPort(serialPort: SerialPort, serialDetail: SerialDetail)` - Expects `SerialSource` to be returned. - Provides the required `SerialPort` to be connected, and configuration parameters through `SerialDetail`. **Class Definition:** - `SerialDetail` - `baudRate: Int` - Requested baud rate for this serial port (e.g. 9600, 115200). All commonly used baud rates may be requested. - `dataBits: Int` - Requested number of data bits for this serial port (e.g. 8 data bits). - `stopBits: Int` - Requested number of stop bits for this serial port (e.g. 1, 2 stop bits). - `parity: Int` - Requested parity condition for this serial port. 0 for None, 1 for Odd, 2 for Even. - `serialFacilitator: SerialFacilitator` - Internal SDK reference for alternate connectivity methods. App **SHOULD** ignore this field, as it **SHOULD** always be set to `SerialFacilitator.SERIAL` for all requests by the SDK to the App. **Example Call:** The `SerialDetail` provides the necessary contextual information needed to make the connection to the `SerialPort` specified. For example, a standard connection to an RS232 port uses the configuration `96008N1`. If this is to be used to connect to a port with the real name and path `/dev/tty/S1` that was mapped by your App to `SerialPortNumber.ONE`, then the SDK's request to `accessSerialPort(...)` will look like this: - `serialPort: SerialPort` - `serialPortNumber` - `SerialPortNumber.ONE` - `realPortName` - `/dev/tty/S1` - `serialDetail: SerialDetail` - `baudRate` - `9600` - `dataBits` - `8` - `stopBits` - `1` - `parity` - `0` - `serialFacilitator` - `SerialFacilitator.SERIAL` **Expected Response:** `accessSerialPort(...)` expects a `SerialSource` to be returned, although the return value is an `Optional`. **Class Definition:** - `SerialSource` - `inputStream: InputStream` - Any implementation of the `InputStream` abstract class. - `outputStream: OutputStream` - Any implementation of the `OutputStream` abstract class. Simply put, the `SerialSource` provides the I/O handlers for the SDK to directly communicate with the Device it is trying to connect to. Upon receipt of the `accessSerialPort(...)` callback, the App **MUST** attempt to honour the request by performing the connection attempt, and returning the `SerialSource` object for the SDK to utilise. If the App encounters any issues with opening the connection (e.g., the port ceased to exist as the peripheral was removed, if it is a virtual port), it **SHOULD** return a `null` value so that the SDK can record and acknowledge its failure to connect. The SDK **MAY** continuously attempt to connect to a `SerialPort` if prior attempts to connect fail, so your App **SHOULD** account for such repetitive behaviour. The SDK **MAY** also attempt to connect to a previously connected `SerialPort` if it was internally shutdown or restarted. 当 SDK 运行并自行设置时,为此特定设备**可能**设置的配置需要它连接到一个或多个用于不同用途的 `SerialPort`,由 `SerialPortNumber` 标记。 SDK 将引用通过应用程序的 `register(...)` 调用中通过 `serialPortLists` 参数给出的所有 `SerialPort` 对象,并将配置的 `SerialPortNumber` 与列表中的对象相匹配。对于每个匹配项,它将调用 `accessSerialPort(...)` 回调来请求连接。 **函数定义:** - `accessSerialPort(serialPort: SerialPort, serialDetail: SerialDetail)` - 期望返回 `SerialSource`。 - 提供需要连接的 `SerialPort`,通过 `SerialDetail` 配置参数。 **类定义:** - `SerialDetail` - `baudRate: Int` - 此串行端口的请求波特率(例如 9600、115200)。可以要求所有常用的波特率。 - `dataBits: Int` - 该串行端口请求的数据位数(例如 8 个数据位)。 - `stopBits: Int` - 请求此串行端口的停止位数量(例如 1、2 个停止位)。 - `parity: Int` - 请求此串行端口的奇偶校验条件。 0 表示无,1 表示奇数,2 表示偶数。 - `serialFacilitator: SerialFacilitator` - 替代连接方法的内部 SDK 参考。应用程序**应该**忽略此字段,因为对于 SDK 对应用程序的所有请求,它**应该**始终设置为 `SerialFacilitator.SERIAL`。 **示例调用:** `SerialDetail` 提供了连接到指定的 `SerialPort` 所需的必要上下文信息。例如,与 RS232 端口的标准连接使用配置 `96008N1`。如果这是用来连接到一个真实名称和路径 `/dev/tty/S1` 的端口,该端口被您的应用程序映射到 `SerialPortNumber.ONE`,那么 SDK 对 `accessSerialPort(...)` 的请求将如下所示: - `serialPort: SerialPort` - `serialPortNumber` - `SerialPortNumber.ONE` - `realPortName` - `/dev/tty/S1` - `serialDetail: SerialDetail` - `baudRate` - `9600` - `dataBits` - `8` - `stopBits` - `1` - `parity` - `0` - `serialFacilitator` - `SerialFacilitator.SERIAL` **预期回应:** `accessSerialPort(...)` 期望返回一个 `SerialSource`,尽管返回值是一个 `Optional`。 **类定义:** - `SerialSource` - `inputStream: InputStream` - 任何 `InputStream` 抽象类的实现。 - `outputStream: OutputStream` - 任何 `OutputStream` 抽象类的实现。 简单地说,`SerialSource` 为 SDK 提供了 I/O 处理程序,以直接与它尝试连接的设备进行通信。收到 `accessSerialPort(...)` 回调后,应用程序**必须**尝试通过执行连接尝试来接受请求,并返回 `SerialSource` 对象供 SDK 使用。 如果应用程序在连接时遇到任何问题(例如,由于外围设备被移除,端口不再存在,如果它是虚拟端口),它**应该**返回一个 `null` 值,以便 SDK 可以记录并确认其连接失败。 如果之前的连接尝试失败,SDK **可能**会不断尝试连接到 `SerialPort`,因此您的应用程序**应该**考虑这种重复行为。如果内部关闭或重新启动,SDK **可能**还尝试连接到先前连接的 `SerialPort`。 #### 3.3. User Configurable Mapping for Serial Ports - 串行端口的用户可配置映射 Whilst your App **SHOULD** have a default map of each available port to `SerialPort`, it **SHOULD** also allow operators to add, remove or re-map them. Therefore, we strongly **RECOMMEND** that your App has a Graphical User Interface (GUI) in a configurations or settings page which allows operators to map the serial ports to available ports. A simple layout would be to have a UI with a drop down menu showing all available ports, and allowing operators to select which `SerialPortNumber` to map to each port. For example, the default mapping of `/dev/ttyS1` to `SerialPortNumber.ZERO` can then be changed to `SerialPortNumber.ONE`. This **SHOULD** be used for as many ports as the App can support. 虽然您的应用程序**应该**有每个可用端口到 `SerialPort` 的默认映射,它**应该**还允许操作员添加、删除或重新映射它们。因此,我们强烈**推荐**您的应用程序在配置或设置页面中具有图形用户界面 (GUI),允许运营商将串行端口映射到可用端口。 一个简单的布局是有一个带有下拉菜单的 UI,显示所有可用的端口,并允许操作员选择哪个 `SerialPortNumber` 映射到每个端口。例如,`/dev/ttyS1` 到 `SerialPortNumber.ZERO` 的默认映射可以更改为 `SerialPortNumber.ONE`。这个**应该**用于应用程序可以支持的尽可能多的端口。 #### 3.4. Customer Presented Payment Type Recognition (`PaymentType.CP`) - 客户提交的付款类型识别(`PaymentType.CP`) As mentioned in Section 2.5, `PaymentType.CP` is a Customer Presented Transaction, where customers **MUST** present identification or payment information to the Device, and where the authentication and processing is handled by Beep's server, not locally on the Device. Some examples are customer's payment application QR code, staff pass, student pass, promotion or redemption code, national identification card. `TransactionInfo` as mentioned in Section 2.6.3, has parameters `isQr` and `url`. For `PaymentType.CP`, `isQr` is set to `false` and hence, there **SHOULD** be a `url` of the image depicting the instructions for this Transaction. When receiving a `paymentUpdate(...)` with this `TransactionInfo`, the App **MUST** retrieve the image and display it together with the `instruction` text. 如第 2.5 部分所述,`PaymentType.CP` 是客户提交的交易,客户**必须**向设备提供身份或支付信息,并且身份验证和处理由 Beep 的服务器处理,而不是在设备本身。一些例子是客户的支付应用程序二维码、员工通行证、学生通行证、促销或兑换码、国民身份证。 如第 2.6.3 部分所述,`TransactionInfo` 具有参数 `isQr` 和 `url`。对于 `PaymentType.CP`,`isQr` 设置为 `false`,因此,**应该**是有描述此交易说明的图像的 `url`。当收到带有此 `TransactionInfo` 的 `paymentUpdate(...)` 时,应用程序**必须**检索图像并将其与 `instruction` 文本一起显示。 #### 3.5. Usage flow - 使用流程 To have a clearer understanding of performing Customer Presented transactions, we will use a simple example. **`PaymentOption` Definition:** The SDK supports WeChat Pay payments through both Dynamic and Customer Presented methods. If a particular Device is configured to accept Customer Presented WeChat Pay payments, then the `PaymentOption` could look like: - `PaymentOption` - `displayName` - ``"WeChat Pay"`` - `paymentMethodCode` - ``"wxpay"`` - `paymentTypeCode` - `PaymentType.CP.type` ("CP") - `paymentFlowId` - `102` (Randomly assigned by SDK) - `imageUrl` - https://beepbeep3.s3.ap-southeast-1.amazonaws.com/payment-methods/wechatpay_logo.png **Transaction Attempt:** If the customer proceeds to make a Selection, and chooses this `PaymentOption` to fulfill the transaction, then the SDK will proceed to send a `paymentUpdate(PaymentResultState.PAYMENT_INFO)`, and the `TransactionInfo` could look like: - `TransactionInfo` - `isQR` - `false` - `url` - https://beepbeep3-dev.s3.ap-southeast-1.amazonaws.com/images/T2195884/user-avatar/1d805524-a6db-4e48-89ed-0d2252b9d709-1626702382.jpeg - `paymentTypeCode` - `PaymentType.CP.type` - `qrInfo` - ``""`` (Blank) - `instruction` - `"Please Present Your WeChat Pay QR code to the QR Scanner on this Device"` - `action` - `Action.REQUIRE` ("required") - `outTradeNo` - `"2004000244_b9c4b926-0d34-44f9-a967-6fff903b21c7"` (Randomly generated) - `paymentFlowId` - `102` - `timeout` - `90` (90 seconds to complete this transaction stage) If the customer presents his/her WeChat Pay QR code within the specified `timeout`, the SDK will fire a `PAYMENT_LOCK` to notify the App. The App **SHOULD** prevent the customer from navigating away from the payment page or selecting a different Selection and/or `PaymentOption`. **Intermediate Processing:** The SDK **MAY** also fire a new `TransactionInfo` after the customer has presented his/her WeChat Pay QR code before the payment has been processed, to update the user interface and inform the customer that their QR code has been scanned successfully and is pending processing. In this case, a new `TransactionInfo` **MAY** be fired, and it could look like: - `TransactionInfo` - `isQR` - `false` - `url` - https://beepbeep3-live.s3.ap-southeast-1.amazonaws.com/images/A7816397/user-avatar/4e482560-948a-4693-87ed-c64786165418-1636518167.png - `paymentTypeCode` - `PaymentType.CP.type` - `qrInfo` - ``""`` (Blank) - `instruction` - `"Please Wait. Your Payment Is Being Processed."` - `action` - `Action.REQUIRE` ("required") - `outTradeNo` - `"2004000244_b9c4b926-0d34-44f9-a967-6fff903b21c7"` (Randomly generated) - `paymentFlowId` - `102` - `timeout` - `30` (Payment processing should take no longer than 30 seconds) **In general, any number of `TransactionInfo` may be fired for each particular transaction step.** **Payment Stage Passed:** If the customer presented an errorneous QR code, the SDK will proceed to send a `paymentFailure(errorMessage: String)` that **MUST** be displayed to the customer, and the App **SHOULD** return to the home screen or equivalent to allow the customer to make a new Selection or Selections. If the customer presented a valid QR code and successfully completed the payment stage, the SDK will check if there are subsequent payment stages that **MUST** be completed before this payment flow is considered successful. - If there are further payment stages **REQUIRED**, a `paymentUpdate(PaymentResultState.PAYMENT_INFO)` **SHALL** be sent again. - If there are no further payment stages **REQUIRED**, a `paymentUpdate(PaymentResultState.PAYMENT_SUCCESS)` **SHALL** be sent by the SDK to the App. **Selection Fulfillment:** When the App receives `PAYMENT_SUCCESS`, it **MUST** then proceed to attempt to fulfill the delivery of all Selection(s) specified for this Transaction. The SDK will await the App’s update on the outcome of the delivery attempts for all Selection(s) before proceeding. The SDK **SHALL NOT** allow any further operations until all such updates are received. You App **MUST** attempt to fulfill all the Selections in the `BeepOrder`, and it **MUST** call the `updateTransactionResult(vararg item: Triple<Double, String, OrderItemStatus>)` function to update the transaction result. After all Selection(s) are attempted and reported, the SDK **SHALL** perform the final record of this Transaction, and consider this Transaction to be complete. A `paymentUpdate(PaymentResultState.PAYMENT_COMPLETE)` **SHALL** also be sent to signify the transaction is completed. After receiving `PAYMENT_COMPLETE`, the App **SHOULD** return to the home screen or equivalent to allow the customer to make a new Selection or Selections. 为了更清楚地了解执行客户提交的交易,我们将使用一个简单的示例。 **`PaymentOption` 定义:** SDK 支持通过动态和客户呈现两种方式进行微信支付。如果特定设备配置为接受客户提交的微信支付,则 `PaymentOption` 可能如下所示: - `PaymentOption` - `displayName` - ``"WeChat Pay"`` - `paymentMethodCode` - ``"wxpay"`` - `paymentTypeCode` - `PaymentType.CP.type` ("CP") - `paymentFlowId` - `102`(由SDK随机分配) - `imageUrl` - https://beepbeep3.s3.ap-southeast-1.amazonaws.com/payment-methods/wechatpay_logo.png **交易尝试:** 如果客户继续进行选择,并选择此 `PaymentOption` 来完成交易,那么 SDK 将继续发送 `paymentUpdate(PaymentResultState.PAYMENT_INFO)`,并且 `TransactionInfo` 可能如下所示: - `TransactionInfo` - `isQR` - `false` - `url` - https://beepbeep3-dev.s3.ap-southeast-1.amazonaws.com/images/T2195884/user-avatar/1d805524-a6db-4e48-89ed-0d2252b9d709-1626702382.jpeg - `paymentTypeCode` - `PaymentType.CP.type` - `qrInfo` - ``""``(空的) - `instruction` - `"Please Present Your WeChat Pay QR code to the QR Scanner on this Device"` - `action` - `Action.REQUIRE` ("required") - `outTradeNo` - `"2004000244_b9c4b926-0d34-44f9-a967-6fff903b21c7"`(随机生成) - `paymentFlowId` - `102` - `timeout` - `90`(90 秒完成此交易阶段) 如果客户在指定的 `timeout` 时间内出示他/她的微信支付二维码,SDK 将触发 `PAYMENT_LOCK` 通知应用程序。该应用程序**应该**防止客户离开付款页面或选择不同的选择和/或 `PaymentOption`。 **中介处理:** SDK **可以**在客户在付款处理之前出示他/她的微信支付二维码后,还会触发一个新的 `TransactionInfo`,以更新用户界面并通知客户他们的二维码已被成功扫描并且正在等待处理。在这种情况下,一个新的 `TransactionInfo` **可以**会被触发,它可能看起来像: - `TransactionInfo` - `isQR` - `false` - `url` - https://beepbeep3-live.s3.ap-southeast-1.amazonaws.com/images/A7816397/user-avatar/4e482560-948a-4693-87ed-c64786165418-1636518167.png - `paymentTypeCode` - `PaymentType.CP.type` - `qrInfo` - ``""``(空的) - `instruction` - `"Please Wait. Your Payment Is Being Processed."` - `action` - `Action.REQUIRE` ("required") - `outTradeNo` - `"2004000244_b9c4b926-0d34-44f9-a967-6fff903b21c7"`(随机生成) - `paymentFlowId` - `102` - `timeout` - `30`(付款处理时间不应超过 30 秒) **一般来说,可以为每个特定的交易步骤触发任意数量的 `TransactionInfo`。** **支付阶段通过:** 如果客户提供了错误的二维码,SDK 将继续发送一个 `paymentFailure(errorMessage: String)`,**必须**显示给客户,并且应用程序**应该**返回主屏幕或相当于允许客户进行新的选择。 如果客户出示了有效的二维码并成功完成了支付阶段,SDK 将检查是否有后续支付阶段**必须**在此支付流程被视为成功之前完成。 - 如果**需要**有进一步的支付阶段,`paymentUpdate(PaymentResultState.PAYMENT_INFO)` **应**将再次发送。 - 如果没有**需要**进一步的支付阶段,`paymentUpdate(PaymentResultState.PAYMENT_SUCCESS)` **应**由 SDK 发送到应用程序。 **选择履行:** 当应用程序收到 `PAYMENT_SUCCESS` 时,它**必须**继续尝试完成为该交易指定的所有选择的交付。 在继续之前,SDK 将等待应用程序对所有选择的交付尝试结果的更新。在收到所有此类更新之前,SDK **不应**允许任何进一步的操作。您的应用程序**必须**尝试完成 `BeepOrder` 中的所有选择,并且它**必须**调用 `updateTransactionResult(vararg item: Triple<Double, String, OrderItemStatus>)` 函数来更新交易结果. 在尝试并报告所有选择后,SDK **应**执行本次交易的最终记录,并认为本次交易已完成。`paymentUpdate(PaymentResultState.PAYMENT_COMPLETE)` **应**也被发送以表示交易已完成。 收到 `PAYMENT_COMPLETE` 后,应用程序**应该**返回主屏幕或等效屏幕,以允许客户进行新的选择。 **Testing:** **🎉🎉Congratulations on reaching this point! We have come to the end of Section 3. This is also an important section enabling your Device to connect to a wide range of peripherals to significantly broaden the range of payments and authentications it can perform. Let's take it for a spin!🎉🎉** Once these are all setup, you **SHOULD** be able to schedule a test with Beep where Beep configures 1 or more Customer Presented `PaymentOption` choices that you can use to perform a full Transaction with 1 or more items. Beep's staff will guide you on the type of peripheral(s) you can use to perform the test. **测试:** **🎉🎉恭喜您达到这里!我们已经到了第 3 部分的结尾。这也是一个重要的部分,使您的设备能够连接到各种外围设备,从而显着扩大它可以执行的支付和身份验证的范围。让我们来兜兜风吧!🎉🎉** 完成这些设置后,您**应该**可以使用 Beep 安排测试,其中 Beep 配置 1 个或多个客户提供的 `PaymentOption` 选项,您可以使用这些选项来执行具有 1 个或多个项目的完整交易。 Beep 的工作人员将指导您选择可用于执行测试的外围设备类型。 ### 4. Adding Automatic Selection - 添加自动选择 This section guides you through what Automatic Selections are, adding support for Automatic Selection requests, and how to perform Automatic Selection transactions. At the end of this section, you **SHOULD** be able to: - Understand how Automatic Selection works - Understand different special rules related to Automatic Selection - Implement Automatic Selection type Transactions 本部分将指导您了解什么是自动选择、添加对自动选择请求的支持以及如何执行自动选择交易。在本部分结束时,您**应该**能够: - 了解自动选择的工作原理 - 了解与自动选择相关的不同特殊规则 - 实施自动选择类型交易 #### 4.1. Concept of Automatic Selection - 自动选择的概念 Before we dive in, it is best to understand how Automatic Selection works and why it exists. So far, for all the transaction flows we have run through (including `CP` and `Dynamic` which were covered above), the customer is expected to interact with the Machine through the App's user interface to make their selection(s). Therefore, this is considered as a **Manual Selection** flow where the App sends the selection requests to the SDK **(App --> SDK)** and the SDK proceeds to run through the transaction steps before approving or denying the requests. **Automatic Selection**, however, reverses the flow by instead getting the SDK to send selection requests directly to the App **(SDK --> App)** after it has already run through the transaction steps necessary to approve the request. This means that all the App has to do is to listen for the transaction from the SDK, and instruct the Machine to immediately attempt to fulfill the selection(s) defined, skipping the need for the customer to make their choices through the App's user interface. 在我们深入研究之前,我们最好先了解自动选择的工作原理及其存在的原因。到目前为止,对于我们所经历的所有交易流程(包括以上介绍的 `CP` 和 `Dynamic`),客户需要通过应用程序的用户界面与机器交互以做出他们的选择。因此,这被视为一个**手动选择**流程,其中应用程序将选择请求发送到 SDK **(应用程序 --> SDK)** 并且 SDK 在批准或拒绝之前继续运行交易步骤要求。 但是**自动选择**时通过在已经完成批准请求所需的交易步骤之后,让 SDK 将选择请求直接发送到应用程序 **(SDK --> 应用程序)**来颠倒流程。这意味着应用程序所要做的就是侦听来自 SDK 的交易,并指示机器立即尝试完成定义的选择,而无需客户通过应用程序的用户界面做出选择. #### 4.2. How & Why Automatic Selection exists - 自动选择的存在方式和原因 Since the customer does not interact with the App's user interface to perform an Automatic Selection, it is the SDK's duty to perform the verification or input from the customer. Typically, the SDK handles this by using the external Devices it is connected to (as covered in Section 3) to actively search for various types of input (e.g. staff pass, ID card, promotion QR code, etc.) from the customer that it then uses to identify them and determine what selection(s) should be requested for them. **Automatic Selection is especially useful when there is a need to control or determine what each customer should receive from the Machine without their interaction.** For example, there may be a pre-determined quota or a particular type of good that is fixed for each customer, or the customer may have already made their choice on a seperate platform like a website or application, and expect that the Machine should automatically know their selection(s) when they physically interact with it. 由于客户不与应用程序的用户界面交互来执行自动选择,因此 SDK 有责任执行客户的验证或输入。通常,SDK 通过使用它所连接的外部设备(如第 3 部分所述)来主动搜索来自它的客户的各种类型的输入(例如员工通行证、身份证、促销二维码等)来处理此问题。然后用于识别它们并确定应为它们请求哪些选择。 **自动选择特别有用当需要控制或确定每个客户应该在没有他们交互的情况下从机器收到什么时。** 例如,可能有一个预先确定的配额或特定类型的商品对于每个客户,或者客户可能已经在网站或应用程序等独立平台上做出了选择,并期望机器在与机器进行物理交互时自动知道他们的选择。 #### 4.3. Automatic Selection's special rules - 自动选择的特殊规则 Automatic Selection transactions skip or remove certain steps that Manual Selection transactions must follow. In order to have a deeper understanding and knowing how to interpret it, here are some rules to take note of: 自动选择交易跳过或删除手动选择交易必须遵循的某些步骤。为了更深入的理解和理解如何解释它,以下有一些规则需要注意: ##### 4.3.1 Automatically started by SDK in idle state - 空闲状态下由 SDK 自动启动 If any Automatic Selection `PaymentOptions` are configured for this Machine, the SDK will automatically fire the `requestTransaction(...)` call internally for each relevant `PaymentOption` when the Machine and App is in an Idle state. This means that, so long as there is no active selection(s) by the customer, and there are no active Transaction requests from the App to the SDK, then the SDK will be actively searching for any relevant customer input through connected external Devices (e.g. QR code scanners or RFID card readers) that can be used to validate an Automatic Selection. **Therefore, your App SHOULD NOT need to handle the requesting of any Automatic Selection type Transactions.** 如果为此机器配置了任何自动选择 `PaymentOptions`,当机器和应用程序处于空闲状态时,SDK 将在内部为每个相关的 `PaymentOption` 自动触发`requestTransaction(...)` 调用。这意味着,只要客户没有主动选择,并且没有从应用程序到 SDK 的主动交易请求,那么 SDK 就会通过连接的外部设备主动搜索任何相关的客户输入(例如 QR 码扫描仪或 RFID 读卡器),用来验证自动选择。 **因此,您的应用程序不应需要处理任何自动选择类型交易的请求。** ##### 4.3.2. No `TransactionInfo` displayed - 没有显示 `TransactionInfo` As the SDK internally handles the transaction requests, there is nothing for the Machine to display to the customer. Therefore, a `TransactionInfo` **SHALL NOT** be sent by the SDK for Automatic Selection Transactions. The Machine **SHOULD** remain on the main UI page and **SHOULD NOT** need to handle any information with regards to the Automatic Selection transaction(s). 由于 SDK 在内部处理交易请求,因此机器不会向客户显示任何内容。因此,SDK **不应**为自动选择交易发送 `TransactionInfo`。机器**应该**保留在主 UI 页面上,**不应该**需要处理与自动选择交易有关的任何信息。 ##### 4.3.3. Automatically cancelled by SDK if other Transactions are requested - 如果有请求其他交易,则由 SDK 自动取消 Just as the SDK automatically handles the internal `requstTransaction(...)` for Automatic Selection transactions, it will also automatically cancel all such transactions if a Manual Transaction is requested by the App through user input. The only exception is if an Automatic Selection is already in the middle of being processed (e.g. customer already tapped their card or presented their QR code for Automatic Selection). In that event, the SDK will reject the new Manual Transaction request by sending a `paymentFailure` response and continue processing the existing transaction. 正如 SDK 自动处理自动选择交易的内部 `requstTransaction(...)` 一样,如果应用程序通过用户输入请求手动交易,它也会自动取消所有此类交易。 唯一的例外是如果自动选择已经在处理中(例如,客户已经点击了他们的卡或出示了自动选择的二维码)。在这种情况下,SDK 将通过发送 `paymentFailure` 响应来拒绝新的手动交易请求并继续处理现有交易。 ##### 4.3.4. Does not timeout - 不超时 As all Automatic Selection transactions can happen at anytime when the Machine and App are Idle, there is no timeout duration for such transactions and they will always remain requested until cancelled or processed. Since `TransactionInfo` is not sent in the first place, the App **SHOULD NOT** be aware of this, but it is good to know the rationale behind this. 由于所有自动选择交易都可以在机器和应用程序空闲时随时发生,因此此类交易没有超时期限,并且在取消或处理之前它们将始终保持请求状态。由于首先没有发送 `TransactionInfo`,应用程序**不应该**意识到这,但知道这背后的基本原理是好的。 ##### 4.3.5. Sends command to dispense (Payment Success) with `BeepOrder` WITHOUT corresponding `requestTransaction` - 使用 `BeepOrder` 发送命令以分配(支付成功)但没有相应的 `requestTransaction` If an Automatic Selection is processed successfully, the SDK **SHALL** send the `paymentUpdate(PaymentResultState.PAYMENT_SUCCESS)` callback with the corresponding `BeepOrder` object as the second parameter to the App to request the App to fulfill the given selection(s) in the `BeepOrder`. **Hence, your App MUST be prepared to accept this without any corresponding `requestTransaction` sent, as this is the only step where your App is made aware of an Automatic Selection transaction.** 如果自动选择处理成功,SDK **应**将带有相应的 `BeepOrder` 对象作为第二个参数的 `paymentUpdate(PaymentResultState.PAYMENT_SUCCESS)` 回调发送到应用程序,以请求应用程序完成给定的选择在 `BeepOrder` 中。 **因此,您的应用程序必须准备好在不发送任何相应的 `requestTransaction` 的情况下接受这一点,因为这是您的应用程序知道自动选择交易的唯一步骤。** ##### 4.3.6. May send more than one selection per `BeepOrder` - 每个 `BeepOrder` 可以发送多个选择 As is the case with `requestTransaction` where more than 1 selection **MAY** be sent in the `BeepOrder`, the SDK **MAY** also notify more than 1 selection in the `BeepOrder` in the `PAYMENT_SUCCESS` callback. **Your Machine SHOULD be able to handle this scenario gracefully, even if it is unable to fulfill all or some of the selections.** For example, if the selections sent by the SDK do not exist or cannot be fulfilled due to Machine limitations, then the App **MUST** report each selection that cannot be fulfilled using `updateTransactionResult(...)` and status `MACHINE_ORDER_ITEM_FAIL`. Your Machine **SHOULD NOT** crash or glitch unexpectedly. Furthermore, if your Machine cannot support more than the number of selections sent in the single `BeepOrder`, then it **SHOULD** try to fulfill as many as it can, and report those outcomes individually. The remaining items which cannot be fulfilled **SHOULD** be reported as `MACHINE_ORDER_ITEM_FAIL`. 与 `requestTransaction` 的情况一样,在 `BeepOrder` 中**可以**发送超过 1 个选择,SDK **可以**在 `PAYMENT_SUCCESS` 回调中通知超过 1 个选择在 `BeepOrder` 中.。**您的机器应该能够优雅地处理这种情况,即使它无法完成所有或部分选择。** 例如,如果 SDK 发送的选择不存在或由于机器限制而无法完成,那么应用程序**必须**使用 `updateTransactionResult(...)` 和状态`MACHINE_ORDER_ITEM_FAIL` 报告每个无法完成的选择。您的机器**不应该**意外崩溃或故障。此外,如果您的机器不能支持超过单个 `BeepOrder` 中发送的选择数量,那么它**应该**尝试尽可能多地完成,并单独报告这些结果。其余无法完成的项目**应该**报告为 `MACHINE_ORDER_ITEM_FAIL`。 #### 4.4. Current Payment Type (`PaymentType.AUTO_CP`) - 当前付款类型(`PaymentType.AUTO_CP`) As mentioned in Section 2.5, `PaymentType.AUTO_CP` is currently the only `PaymentType` that is used for Automatic Selection transactions. Unlike other `PaymentTypes` covered in earlier Sections where your App determines the Selection(s) by calling `requestTransaction(...)`, transactions using `AUTO_CP` automatically determine the Selection(s) based on the customer's payment information, and instructs your App to proceed with fulfilling the request. 如第 2.5 部分所述,`PaymentType.AUTO_CP` 是目前唯一用于自动选择交易的 `PaymentType`。与前面部分中介绍的其他 `PaymentTypes` 不同,您的应用程序通过调用 `requestTransaction(...)` 来确定选择,使用 `AUTO_CP` 的交易会根据客户的支付信息自动确定选择,并指示您的应用程序继续满足请求。 ##### 4.4.1. Other `PaymentTypes` will also implement this behaviour in the future - 其他 `PaymentTypes` 也将在未来实现此行为 Whilst there is currently only one `PaymentType` (`AUTO_CP`) that behaves this way, other types (both existing and new) will also use this behaviour in the future. Hence, it is important that you **SHOULD NOT** hardcode your App's logic such that it will only work for `AUTO_CP` type transaction and not for other types. 虽然目前只有一种 `PaymentType`(`AUTO_CP`)具有这种行为,其他类型(现有的和新的)将来也会使用这种行为。因此,您**不应该**硬编码您的应用程序的逻辑,使其仅适用于 `AUTO_CP` 类型的事务而不适用于其他类型。 #### 4.5. Usage flow - 使用流程 To have a clearer understanding of performing Automatic Selection transactions, we will use a simple example. **`PaymentOption` Definition:** The Machine supports an Automatic Selection `PaymentOption`: - `PaymentOption` - `displayName` - ``"WeChat Pay"`` - `paymentMethodCode` - ``"wxpay"`` - `paymentTypeCode` - `PaymentType.AUTO_CP.type` ("AUTO_CP") - `paymentFlowId` - `103` (Randomly assigned by SDK) - `imageUrl` - https://beepbeep3.s3.ap-southeast-1.amazonaws.com/payment-methods/wechatpay_logo.png **Transaction Attempt and Processing:** After the customer has scanned the QR code through a connected QR code scanner, the SDK will validate this Automatic Selection transaction request and the SDK will automatically fire the `requestTransaction(...)` call internally if the Machine and App is in Idle states. As the SDK internally handles this Automatic Selection transaction request, the Machine does not need to display `TransactionInfo` to the customer and **SHOULD** remain on the main UI page. **Payment Stage Passed:** If the customer presented an errorneous QR code, the SDK will proceed to send a `paymentFailure(errorMessage: String)` that **MUST** be displayed to the customer. If the customer presented a valid QR code and the Automatic Selection request is processed successfully, the SDK will send `paymentUpdate(PaymentResultState.PAYMENT_SUCCESS)` callback with the corresponding `BeepOrder` object to request the App to fulfill the given selection(s) in the `BeepOrder`. If there is more than 1 selection sent in the `BeepOrder`, the SDK can also notify more than 1 selection in the `BeepOrder` in the `PAYMENT_SUCCESS` callback. **Selection Fulfillment:** When the App receives `PAYMENT_SUCCESS`, it **MUST** then proceed to attempt to fulfill the delivery of all Selection(s) specified for this Transaction. The SDK will await the App’s update on the outcome of the delivery attempts for all Selection(s) before proceeding. The SDK **SHALL NOT** allow any further operations until all such updates are received. You App **MUST** attempt to fulfill all the Selections in the `BeepOrder`, and it **MUST** call the `updateTransactionResult(vararg item: Triple<Double, String, OrderItemStatus>)` function to update the transaction result. If your Machine cannot support more than the number of Selection(s) sent in the `BeepOrder`, then it **SHOULD** try to fulfill as many as it can, and report those outcomes individually. The remaining items which cannot be fulfilled **SHOULD** be reported as `MACHINE_ORDER_ITEM_FAIL`. After all Selection(s) are attempted and reported, the SDK **SHALL** perform the final record of this Transaction, and consider this Transaction to be complete. A `paymentUpdate(PaymentResultState.PAYMENT_COMPLETE)` **SHALL** also be sent to signify the transaction is completed. After receiving `PAYMENT_COMPLETE`, the customer is now able to make a new Selection or Selections. 为了更清楚地了解执行自动选择交易,我们将使用一个简单的示例。 **`PaymentOption` 定义:** 机器支持自动选择 `PaymentOption`: - `PaymentOption` - `displayName` - ``"WeChat Pay"`` - `paymentMethodCode` - ``"wxpay"`` - `paymentTypeCode` - `PaymentType.AUTO_CP.type` ("AUTO_CP") - `paymentFlowId` - `103`(由 SDK 随机分配) - `imageUrl` - https://beepbeep3.s3.ap-southeast-1.amazonaws.com/payment-methods/wechatpay_logo.png **交易尝试和处理:** 客户通过连接的二维码扫描器扫描二维码后,SDK 将验证此自动选择交易请求,如果机器和应用程序处于空闲状态,SDK 将在内部自动触发 `requestTransaction(...)` 调用. 由于 SDK 在内部处理此自动选择交易请求,因此机器不需要向客户显示 `TransactionInfo` 并且**应该**保留在主 UI 页面上。 **支付阶段通过:** 如果客户提供了错误的二维码,SDK 将继续发送一个 `paymentFailure(errorMessage: String)`,这**必须**显示给客户。 如果客户提供了有效的二维码并且自动选择请求被成功处理,SDK 将发送带有相应的 `BeepOrder` 对象的 `paymentUpdate(PaymentResultState.PAYMENT_SUCCESS)` 回调,以请求应用程序完成给定的选择 `BeepOrder`。如果 `BeepOrder` 中发送的选择超过 1 个,SDK 也可以在 `PAYMENT_SUCCESS` 回调中的 `BeepOrder` 中通知超过 1 个选择。 **选择履行:** 当应用程序收到 `PAYMENT_SUCCESS `时,它**必须**继续尝试完成为该交易指定的所有选择的交付。 在继续之前,SDK 将等待应用程序对所有选择的交付尝试结果的更新。在收到所有此类更新之前,SDK **不应**允许任何进一步的操作。您的应用程序**必须**尝试完成 `BeepOrder` 中的所有选择,并且它**必须**调用 `updateTransactionResult(vararg item: Triple<Double, String, OrderItemStatus>)` 函数来更新交易结果. 如果您的机器不能支持超过 `BeepOrder` 中发送的选择数量,那么它**应该**尝试尽可能多地完成,并单独报告这些结果。其余无法完成的项目**应该**报告为`MACHINE_ORDER_ITEM_FAIL`。 在尝试并报告所有选择后,SDK **应**执行本次交易的最终记录,并认为本次交易已完成。`paymentUpdate(PaymentResultState.PAYMENT_COMPLETE)` **应**也被发送以表示交易已完成。 收到 `PAYMENT_COMPLETE` 后,客户现在可以进行新的选择。 **Testing:** **🎉🎉Congratulations on reaching this point! We have come to the end of Section 4. This is also an important section enabling your Device to support automatic selection requests.🎉🎉** Once these are all setup, you **SHOULD** be able to schedule a test with Beep where Beep configures 1 or more Automatic Selection `PaymentOption` choices that you can use to perform a full Transaction with 1 or more items. Beep's staff will guide you on the type of peripheral(s) you can use to perform the test. **测试:** **🎉🎉恭喜您达到了这里!我们已经到了第 4 部分的结尾。这也是一个重要的部分,使您的设备能够支持自动选择请求。🎉🎉** 完成这些设置后,您**应该**可以使用 Beep 安排测试,其中 Beep 配置 1 个或多个自动选择 `PaymentOption` 选项,您可以使用这些选项来执行具有 1 个或多个项目的完整交易。 Beep 的工作人员将指导您选择可用于执行测试的外围设备类型。

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully