You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Stripe Terminal物理读卡器连接异常:API与CLI可检测在线读卡器但前端发现失败

Stripe Terminal物理读卡器连接异常:API与CLI可检测在线读卡器但前端发现失败

我正在开发一个POS系统,想要连接真实的Stripe物理读卡器(不是模拟器)。我已经编写了初始化、发现和连接读卡器的JavaScript代码,而且通过Stripe API和CLI都能确认读卡器处于在线状态,但前端点击支付按钮时却一直弹出提示:Aucun terminal trouv茅. V茅rifiez la connectivit茅 et la configuration r茅seau.(英文翻译:No terminal found. Check connectivity and network configuration.),这实在让人摸不着头脑。


一、前端Stripe Terminal初始化与读卡器操作代码

以下是我用于初始化Stripe Terminal、发现和连接读卡器的JavaScript代码:

// Initialize Stripe Terminal
const terminal = StripeTerminal.create({
    onFetchConnectionToken: async () => {
        try {
            const response = await fetch("http://127.0.0.1:8000/create_connection_token/", { method: 'POST' });
            if (!response.ok) {
                throw new Error("Failed to fetch connection token");
            }
            const { secret } = await response.json();
            return secret;
        } catch (error) {
            console.error("Error fetching connection token:", error);
            throw error;
        }
    },
    onUnexpectedReaderDisconnect: () => {
        console.error("Reader unexpectedly disconnected.");
        alert("Le terminal s'est d茅connect茅 de mani猫re inattendue. Veuillez v茅rifier la connexion et r茅essayer.");
    },
});

console.log("Stripe Terminal initialized.");

// Discover readers
async function discoverReaders() {
    try {
        console.log("Discovering readers...");

        const config = { 
            simulated: false, 
            location: "LOCATION_ID" 
        };

        const discoverResult = await terminal.discoverReaders(config);
        console.log("Discover Result:", discoverResult);

        if (discoverResult.error) {
            console.error('Error discovering readers:', discoverResult.error.message);
            alert('Erreur lors de la d茅couverte des lecteurs. V茅rifiez votre configuration r茅seau.');
            return null;
        }

        if (discoverResult.discoveredReaders.length === 0) {
            console.warn("No available readers. Ensure the terminal is powered on and connected.");
            alert("Aucun terminal trouv茅. V茅rifiez la connectivit茅 et la configuration r茅seau.");
            return null;
        }

        console.log("Discovered readers:", discoverResult.discoveredReaders);
        alert("Lecteurs d茅couverts avec succ猫s.");
        return discoverResult.discoveredReaders[0];
    } catch (error) {
        console.error("Error during reader discovery:", error);
        alert("Une erreur inattendue s'est produite lors de la d茅couverte des lecteurs.");
        return null;
    }
}

// Connect to a reader
async function connectReader(reader) {
    try {
        console.log("Attempting to connect to reader:", reader.label);

        // Connect to the selected reader
        const connectResult = await terminal.connectReader(reader);

        // Handle connection errors
        if (connectResult.error) {
            console.error("Failed to connect:", connectResult.error.message);
            alert(`Connexion 茅chou茅e : ${connectResult.error.message}`);
            return false;
        }

        console.log("Connected to reader:", connectResult.reader.label);
        alert(`Connect茅 au lecteur : ${connectResult.reader.label}`);
        return true;
    } catch (error) {
        console.error("Error during reader connection:", error);
        alert("Une erreur inattendue s'est produite lors de la connexion au terminal. Consultez la console pour plus de d茅tails.");
        return false;
    }
}

// Example usage: Discover and connect to a reader
async function handleReaderSetup() {
    const reader = await discoverReaders();
    if (reader) {
        await connectReader(reader);
    }
}

二、API与CLI的验证结果

我用Python脚本调用Stripe API,能成功获取到在线的读卡器信息:

Python验证代码

import stripe
stripe.api_key = "sk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

try:
    # List all readers
    readers = stripe.terminal.Reader.list()
    print("Readers:", readers)
except stripe.error.StripeError as e:
    print("Stripe error:", e)
except Exception as e:
    print("An unexpected error occurred:", e)

API返回结果

Readers: {
  "data": [                                                                                                                                                                                                         
    {                                                                                                                                                                                                                 
      "action": null,                                                                                                                                                                                                
      "device_sw_version": "2.27.7.0",                                                                                                                                                                               
      "device_type": "bbpos_wisepos_e",
      "id": "tmr_XXXXXXXXXXXXXX",
      "ip_address": "x.0.0.xxx",
      "label": "Testing_Reader",
      "last_seen_at": 1735518518163,
      "livemode": true,
      "location": "tml_ZZZZZZZZZZZZ",
      "metadata": {},
      "object": "terminal.reader",
      "serial_number": "YYYYYYYYYYYYY",
      "status": "online"
    }
  ],
  "has_more": false,
  "object": "list",
  "url": "/v1/terminal/readers"
}

同时,使用Stripe CLI命令也能得到相同的在线读卡器信息:

stripe terminal readers list
{
  "object": "list",
  "data": [
    {
      "id": "tmr_XXXXXXXXXXXXXX",
      "object": "terminal.reader",
      "action": null,
      "device_sw_version": "2.27.7.0",
      "device_type": "bbpos_wisepos_e",
      "ip_address": "x.0.0.xxx",
      "label": "Testing_Reader",
      "last_seen_at": 1735517252951,
      "livemode": true,
      "location": "tml_ZZZZZZZZZZZZ",
      "metadata": {},
      "serial_number": "YYYYYYYYYYYYY",
      "status": "online"
    }
  ],
  "has_more": false,
  "url": "/v1/terminal/readers"
}

三、触发错误的页面按钮

点击以下按钮时,会调用handleReaderSetup方法,进而触发“未发现读卡器”的错误:

<button type="button" id="send-to-terminal" class="btn btn-primary" data-order-id="{{ order.id }}"> Envoyer au terminal</button>

四、后端PaymentService代码

我还编写了后端的支付服务代码,用于获取在线读卡器和创建支付意向:

import stripe
import logging
from decimal import Decimal
from django.conf import settings

class PaymentService:
    def __init__(self):
        """Initialize the PaymentService with the Stripe API key."""
        stripe.api_key = settings.STRIPE_SECRET_KEY
        self.logger = logging.getLogger(__name__)

    def get_online_reader(self):
        """
        Fetch the first online terminal reader from Stripe.
        :return: Stripe Terminal Reader object.
        :raises: ValueError if no online reader is found.
        """
        try:
            readers = stripe.terminal.Reader.list(status="online").data
            if not readers:
                self.logger.error("Aucun lecteur de terminal en ligne trouv茅.")
                raise ValueError("Aucun lecteur de terminal en ligne trouv茅.")
            return readers[0]  # Return the first online reader
        except stripe.error.StripeError as e:
            self.logger.error(f"Erreur Stripe lors de la r茅cup茅ration des lecteurs: {str(e)}")
            raise Exception(f"Erreur Stripe: {str(e)}")

    def create_payment_intent(self, amount, currency="CAD", payment_method_types=None, capture_method="automatic"):
        """
        Create a payment intent for a terminal transaction.
        :param amount: Decimal, total amount to charge.
        :param currency: str, currency code (default: "CAD").
        :param payment_method_types: list, payment methods (default: ["card_present"]).
        :param capture_method: str, capture method for the payment intent.
        :return: Stripe PaymentIntent object.
        """
        try:
            if payment_method_types is None:
                payment_method_types = ["card_present"]

            payment_intent = stripe.PaymentIntent.create(
                amount=int(round(amount, 2) * 100),  # Convert to cents
                currency=currency.lower(),
                payment_method_types=payment_method_types,
                capture_method=capture_method  # Explicitly include this argument
            )
            self.logger.info(f"PaymentIntent created: {payment_intent['id']}")
            return payment_intent
        except stripe.error.StripeError as e:
            self.logger.error(f"Stripe error while creating PaymentIntent: {str(e)}")
            raise Exception(f"Stripe error: {str(e)}")

可能的原因及解决方案

结合你的场景,我整理了几个最可能的问题点和排查步骤,你可以逐一验证:

1. Location ID配置不匹配

你的前端discoverReaders方法里用了location: "LOCATION_ID"这个占位符,必须替换为读卡器实际绑定的Location ID(从API返回的location字段:tml_ZZZZZZZZZZZZ)。Stripe Terminal在发现真实读卡器时,如果指定了Location,只会返回归属该Location的设备,不匹配的话会直接过滤掉。

2. 前端与读卡器不在同一局域网

真实Stripe读卡器的发现依赖本地局域网的mDNS广播,如果你的前端应用运行在和读卡器不同的网络环境(比如前端部署在公网服务器,读卡器在本地门店局域网),前端根本无法收到读卡器的广播信号,导致发现失败。

  • 解决方法:确保前端运行的设备(比如POS终端的浏览器)和读卡器连接到同一个Wi-Fi/以太网局域网。

3. 网络权限与浏览器环境限制

  • Stripe Terminal SDK需要浏览器支持WebRTC和mDNS功能,部分POS设备的内置浏览器可能禁用了这些特性,你可以在前端浏览器中访问chrome://webrtc-internals/(如果是Chrome系浏览器)来检查WebRTC是否正常工作。
  • 除了localhost,Stripe Terminal SDK要求前端运行在HTTPS环境下,否则会限制部分功能(比如真实设备发现)。如果你的前端是HTTP环境,建议切换到HTTPS测试。

4. 连接Token的获取问题

你的前端用http://127.0.0.1:8000获取connection token,如果前端是在其他设备上访问(比如POS终端的浏览器访问后端服务器的公网IP),这个地址是无法访问的,会导致Stripe Terminal初始化异常,间接影响读卡器发现。

  • 解决方法:把connection token的请求地址改成后端服务器的可访问IP/域名,确保前端能正常获取到token。

5. 路由器屏蔽了mDNS广播

部分企业级路由器会屏蔽mDNS(多播DNS)流量,而真实Stripe读卡器的发现依赖mDNS。你可以检查路由器的设置,开启mDNS支持,或者尝试用以太网连接读卡器和前端设备,绕过Wi-Fi的限制。


备注:内容来源于stack exchange,提问作者Mohamed Abdillah

火山引擎 最新活动