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




