How can I implement Stripe physical reader discovery in JavaScript?

I’m working on a point-of-sale system and need to connect a Stripe physical reader. Below is my JavaScript implementation for initializing the reader and managing its discovery process.

// Initialize Stripe Terminal
const stripeTerminal = StripeTerminal.create({
    onFetchConnectionToken: async () => {
        try {
            const response = await fetch("http://localhost:8000/get_connection_token/", { method: 'POST' });
            if (!response.ok) {
                throw new Error("Unable to retrieve connection token");
            }
            const { secret } = await response.json();
            return secret;
        } catch (error) {
            console.error("Connection token retrieval error:", error);
            throw error;
        }
    },
    onUnexpectedReaderDisconnect: () => {
        console.error("Reader lost connection unexpectedly.");
        alert("The reader has disconnected unexpectedly. Please check the connection and try again.");
    },
});

console.log("Stripe Terminal ready to use.");

// Discover available readers
async function findReaders() {
    try {
        console.log("Searching for readers...");

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

        const discoverResult = await stripeTerminal.discoverReaders(options);
        console.log("Reader Discovery Result:", discoverResult);

        if (discoverResult.error) {
            console.error('Discovery error:', discoverResult.error.message);
            alert('Error during reader discovery. Please check your network setup.');
            return null;
        }

        if (discoverResult.discoveredReaders.length === 0) {
            console.warn("No readers found. Ensure the device is powered on and properly connected.");
            alert("No reader found. Check connectivity and configuration settings.");
            return null;
        }

        console.log("Found readers:", discoverResult.discoveredReaders);
        alert("Readers successfully discovered.");
        return discoverResult.discoveredReaders[0];
    } catch (error) {
        console.error("Error during reader discovery:", error);
        alert("An unexpected issue occurred while discovering readers.");
        return null;
    }
}

// Connect to a chosen reader
async function linkReader(reader) {
    try {
        console.log("Connecting to reader:", reader.label);

        const connectionResult = await stripeTerminal.connectReader(reader);

        if (connectionResult.error) {
            console.error("Connection failed:", connectionResult.error.message);
            alert(`Connection failed: ${connectionResult.error.message}`);
            return false;
        }

        console.log("Successfully connected to reader:", connectionResult.reader.label);
        alert(`Connected to reader: ${connectionResult.reader.label}`);
        return true;
    } catch (error) {
        console.error("Error while connecting to reader:", error);
        alert("An unexpected error occurred during the reader connection. Check console for details.");
        return false;
    }
}

// Example flow to discover and connect to a reader
async function setupReader() {
    const reader = await findReaders();
    if (reader) {
        await linkReader(reader);
    }
}

I have managed to connect the reader using the Stripe API; I can check if the reader is online from my Stripe dashboard. However, upon attempting to process a payment to the reader, I receive an error stating No reader found. Check connectivity and network configuration..

Interestingly, when executing this code:

import stripe
stripe.api_key = "sk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

try:
    # Retrieve all connected readers
    available_readers = stripe.terminal.Reader.list()
    print("Available Readers:", available_readers)
except stripe.error.StripeError as error:
    print("Stripe error occurred:", error)
except Exception as error:
    print("Unexpected error occurred:", error)

The output displays:

Available Readers: {
  "data": [{
      "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",
      "status": "online"
    }],
  "has_more": false
}

Further, executing this command: stripe terminal readers list yields:

{
  "object": "list",
  "data": [{
      "id": "tmr_XXXXXXXXXXXXXX",
      "status": "online"
    }]
}

This leads me to question why the button with the ID send-to-terminal causes the earlier mentioned error when clicked.

More context: I’ve implemented the following service:

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

class PaymentHandler:
    def __init__(self):
        stripe.api_key = settings.STRIPE_SECRET_KEY
        self.logger = logging.getLogger(__name__)

    def fetch_online_reader(self):
        try:
            readers = stripe.terminal.Reader.list(status="online").data
            if not readers:
                self.logger.error("No online terminal reader found.")
                raise ValueError("No online terminal reader found.")
            return readers[0]
        except stripe.error.StripeError as error:
            self.logger.error(f"Stripe error while fetching readers: {str(error)}")
            raise Exception(f"Stripe error: {str(error)}")

    def initialize_payment_intent(self, amount, currency="CAD", methods=None, capture="automatic"):
        try:
            if methods is None:
                methods = ["card_present"]

            intent = stripe.PaymentIntent.create(
                amount=int(round(amount, 2) * 100),
                currency=currency.lower(),
                payment_method_types=methods,
                capture_method=capture
            )
            self.logger.info(f"PaymentIntent created: {intent['id']}")
            return intent
        except stripe.error.StripeError as error:
            self.logger.error(f"Error creating PaymentIntent: {str(error)}")
            raise Exception(f"Error: {str(error)}")
        except Exception as error:
            self.logger.error(f"Unexpected error: {str(error)}")
            raise Exception(f"Unexpected error: {str(error)}")

    def process_payment(self, intent_id):
        try:
            reader_id = settings.STRIPE_READER_ID
            response = stripe.terminal.Reader.process_payment_intent(reader_id, {"payment_intent": intent_id})
            self.logger.info(f"PaymentIntent {intent_id} processed for reader {reader_id}.")
            return response
        except stripe.error.StripeError as error:
            self.logger.error(f"Stripe error during payment process: {str(error)}")
            raise Exception(f"Stripe error: {str(error)}")
        except Exception as error:
            self.logger.error(f"Unexpected error during payment: {str(error)}")
            raise Exception(f"Unexpected error: {str(error)}")

And I have these views implemented:

@login_required
def send_payment(request, order_id):
    if request.method == "POST":
        try:
            amount = Decimal(request.POST.get('amount', 0))
            if amount <= 0:
                return JsonResponse({'success': False, 'error': 'Invalid amount.'}, status=400)

            payment_intent = stripe.PaymentIntent.create(
                amount=int(amount * 100),
                currency="CAD",
                payment_method_types=["card_present"]
            )
            online_readers = stripe.terminal.Reader.list(status="online").data
            if not online_readers:
                return JsonResponse({'success': False, 'error': 'No online reader found.'}, status=404)

            reader = online_readers[0]
            response = stripe.terminal.Reader.process_payment_intent(reader["id"], {"payment_intent": payment_intent["id"]})

            if response.get("status") == "succeeded":
                return JsonResponse({'success': True, 'payment_intent_id': payment_intent["id"], 'message': 'Payment successfully sent to terminal.'})
            else:
                return JsonResponse({'success': False, 'error': response.get("error", "Unknown terminal error.")}, status=400)
        except stripe.error.StripeError as error:
            return JsonResponse({'success': False, 'error': f"Stripe Error: {str(error)}"}, status=500)
        except Exception as error:
            return JsonResponse({'success': False, 'error': f"Unexpected error: {str(error)}"}, status=500)

    return JsonResponse({'success': False, 'error': 'Method not allowed.'}, status=405)

@csrf_exempt
def generate_connection_token(request):
    try:
        token = stripe.terminal.ConnectionToken.create()
        return JsonResponse({"secret": token.secret})
    except stripe.error.StripeError as error:
        return JsonResponse({"error": str(error)}, status=500)
    except Exception as error:
        return JsonResponse({"error": f"Unexpected error: {str(error)}"}, status=500)

I’m looking for assistance in understanding why the connection fails when attempting to send data to the terminal.

To ensure your Stripe reader discovery and payment process work smoothly, let’s address potential issues with a few steps:

  1. Network Configuration: Ensure that your reader and your server are on the same network. Network inconsistencies often cause connectivity issues.

  2. Reader Discovery: Your JavaScript implementation for discovering readers seems correct. The error message suggests the reader isn’t being found during the attempted connection. Double-check the LOCATION_ID, and ensure your readers are set up correctly in that location.

  3. Validating the Reader Status: Since your Python script and CLI confirm that the reader is online, the issue may lie between discovery and the reader’s availability to process the intent.

  4. Ensure Consistent Reader ID: Make sure that the reader ID used in your JavaScript implementation matches the STRIPE_READER_ID setting and the one fetched via the API for processing payments in Python.

// Before linking reader, verify its status
async function setupReader() {
    const reader = await findReaders();
    if (reader && reader.status === 'online') {  // Ensure reader status is suitable
        await linkReader(reader);
    } else {
        console.warn('Selected reader not available for connection.');
        alert('Reader is not ready to connect.');
    }
}
  1. Payment Intent Process: Ensure the Python function process_payment references the proper reader, check for any overlooked restrictions or parameters that Stripe may impose on payment intent processing.

  2. Console Logs for Debugging: Verify your browser’s console and server logs to catch any additional error output that might hint at underlying causes.

By closely reviewing these steps and ensuring all configurations are correct, you should be able to resolve the connectivity and communication issues with the Stripe terminal.

It appears the process is falling short during the discovery or communication with the reader despite the provisioning checks being successful via the API. Here are tailored steps to refine troubleshooting:

  1. Network Alignment: Double-check that the Stripe reader and your development system are operating under the same Wi-Fi network to eliminate potential network barriers.
  2. <li><strong>Synchronize Location IDs:</strong> Ensure the <code>LOCATION_ID</code> used in JavaScript matches precisely with what you have designated in your Stripe Dashboard for the readers. Any mismatch here could prevent the discovery of eligible devices.</li>
    
    <li><strong>Integrate Consistency Checks:</strong> Re-evaluate your flow to ensure the consistent use of the reader ID utilized throughout your JavaScript and Python scripts. Here is an update to check the reader status when setting up the reader:</li>
    
    // Modified reader setup function
    async function setupReader() {
        const reader = await findReaders();
        if (reader && reader.status === 'online') {  // Ensuring the reader is online
            await linkReader(reader);
        } else {
            console.warn('Reader is not ready for connection.');
            alert('Reader is not ready to connect. Check status and try again.');
        }
    }
    
    <li><strong>Ensure API and SDK Coherence:</strong> Validate that the client-side <code>StripeTerminal</code> configuration corresponds with the server-side Python settings. This includes versions and communication token methods.</li>
    
    <li><strong>Comprehensive Logging:</strong> Augment your logging strategy in both client-side and server-side scripts to gather more insight into failures or unexpected conditions, both in browser developer tools and server logs.</li>
    
    <li><strong>Consult Stripe’s Documentation:</strong> Consider revisiting Stripe’s comprehensive documentation or forums for potential updates or community-sourced troubleshooting tips relevant to point-of-sale integrations.</li>
    

Through refining these setup elements and paying close attention to logs and network conformity, you should enhance connectivity reliability for the reader within your application.

Your connection issues might stem from a few potential areas. Here's a streamlined approach to troubleshoot:

  • Network Check: Ensure both your Stripe reader and server are on the same network. Network mismatches often disrupt connections.
  • Location ID: Verify LOCATION_ID in your JavaScript is accurate. This should match your Stripe Dashboard settings for the reader.
  • Reader Status: Confirm the reader is truly 'online' before connecting. Modify the setup function:
  • async function setupReader() {
        const reader = await findReaders();
        if (reader && reader.status === 'online') {
            await linkReader(reader);
        } else {
            alert('Reader is not ready. Check connection.');
        }
    }
    
  • ID Consistency: Ensure the same reader ID from your Python logic is used throughout.
  • Logging: Enhance logs in both JavaScript (console) and Python (server) to catch subtle issues.
  • Stripe Documentation: Consult Stripe's resources for any updates or common pitfalls.

Following these steps should help in identifying and resolving the potential communication discrepancies with your reader.

To address the issue of your Stripe reader not being found during the payment process, try these focused steps:

  1. Ensure Network Compatibility: Verify that both the Stripe reader and your application server are on the same network. Network mismatches can cause communication breakdowns.
  2. <li><strong>Verify Location ID:</strong> Double-check that your <code>LOCATION_ID</code> is correct and matches what is configured in your Stripe Dashboard. Any mismatch can hinder device discovery.</li>
    
    <li><strong>Check Reader's Online Status:</strong> It’s crucial that the reader is online and available for processing. Before attempting to link, modify your setup function to verify its status:</li>
    
    async function setupReader() {
        const reader = await findReaders();
        if (reader && reader.status === 'online') {  // Confirm the reader is online before linking
            await linkReader(reader);
        } else {
            console.warn('Selected reader not available for connection.');
            alert('Reader is not ready to connect.');
        }
    }
    
    <li><strong>Consistency in Reader ID:</strong> Ensure the reader ID used across your JavaScript and Python implementations is the same. This will prevent conflicting operations.</li>
    
    <li><strong>Logging and Debugging:</strong> Enhance your logging to spot unexpected conditions. Check console outputs and server logs for comprehensive error tracking.</li>
    
    <li><strong>Stripe Documentation:</strong> Explore Stripe's official documentation for any new insights or troubleshooting tips specific to terminal integration.</li>
    

Implementing these steps should help in diagnosing the reader connectivity issues effectively.