I’m developing an POS system and I would like to connect a Stripe physical reader, not a simulation.
Here is my JavaScript codes for initializing, Redears discovering and connecting reader.
// 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);
}
}
I was able to connect the reader using the Stripe API, and from my stripe I can see if reader is or not online.
However, when I run the application and try to send the money to the reader for payment, it’s showing Aucun terminal trouvé. Vérifiez la connectivité et la configuration réseau. which means that in English No terminal found. Check connectivity and network configuration. The confusing thing is, when I run this:
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)
I’m getting this output:
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"
}
In addition, this command: stripe terminal readers list
shows this result:
{
"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"
I really don’t understand, why clicking in this button
<button type="button" id="send-to-terminal" class="btn btn-primary" data-order-id="{{ order.id }}"> Envoyer au terminal</button> gives me the error I mentioned above.
For more details, I’ve this service as well:
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)}")
except Exception as e:
self.logger.error(f"Unexpected error while creating PaymentIntent: {str(e)}")
raise Exception(f"Unexpected error: {str(e)}")
def send_to_terminal(self, payment_intent_id):
"""
Send a payment intent to the online terminal reader for processing.
:param payment_intent_id: str, ID of the PaymentIntent.
:return: Stripe response from the terminal reader.
"""
try:
# Retrieve the Reader ID from settings
reader_id = settings.STRIPE_READER_ID # Ensure this is correctly set in your configuration
# Send the payment intent to the terminal
response = stripe.terminal.Reader.process_payment_intent(
reader_id, {"payment_intent": payment_intent_id}
)
self.logger.info(f"PaymentIntent {payment_intent_id} sent to reader {reader_id}.")
return response
except stripe.error.StripeError as e:
self.logger.error(f"Erreur Stripe lors de l'envoi au terminal: {str(e)}")
raise Exception(f"Erreur Stripe: {str(e)}")
except Exception as e:
self.logger.error(f"Unexpected error while sending to terminal: {str(e)}")
raise Exception(f"Unexpected error: {str(e)}")
and these views:
@login_required
def send_to_terminal(request, order_id):
"""
Send the payment amount to the terminal.
"""
if request.method == "POST":
try:
# Validate amount
amount = Decimal(request.POST.get('amount', 0))
if amount <= 0:
return JsonResponse({'success': False, 'error': 'Montant non valide.'}, status=400)
# Create PaymentIntent
payment_intent = stripe.PaymentIntent.create(
amount=int(amount * 100),
currency="CAD",
payment_method_types=["card_present"]
)
# List online readers dynamically
readers = stripe.terminal.Reader.list(status="online").data
if not readers:
return JsonResponse({'success': False, 'error': 'Aucun lecteur en ligne trouvé.'}, status=404)
# Use the first available reader
reader = readers[0]
# Send PaymentIntent to the terminal
response = stripe.terminal.Reader.process_payment_intent(
reader["id"], {"payment_intent": payment_intent["id"]}
)
# Handle the response
if response.get("status") == "succeeded":
return JsonResponse({
'success': True,
'payment_intent_id': payment_intent["id"],
'message': 'Paiement envoyé avec succès au terminal.'
})
else:
return JsonResponse({
'success': False,
'error': response.get("error", "Erreur inconnue du terminal.")
}, status=400)
except stripe.error.StripeError as e:
return JsonResponse({'success': False, 'error': f"Erreur Stripe : {str(e)}"}, status=500)
except Exception as e:
return JsonResponse({'success': False, 'error': f"Une erreur inattendue s'est produite: {str(e)}"}, status=500)
return JsonResponse({'success': False, 'error': 'Méthode non autorisée.'}, status=405)
@csrf_exempt # Allow requests from the frontend if CSRF tokens are not included
def create_connection_token(request):
try:
# Create a connection token
connection_token = stripe.terminal.ConnectionToken.create()
return JsonResponse({"secret": connection_token.secret})
except stripe.error.StripeError as e:
# Handle Stripe API errors
return JsonResponse({"error": str(e)}, status=500)
except Exception as e:
# Handle other unexpected errors
return JsonResponse({"error": f"Unexpected error: {str(e)}"}, status=500)