Skip to content

Common Errors and Debugging

This guide covers the most frequently encountered errors in the Upvendo backend, their causes, and how to diagnose and resolve them.

HTTP Status Codes Used

CodeMeaningWhen Returned
200SuccessStandard successful response
400Bad RequestInvalid input, malformed payload, failed validation
401UnauthenticatedMissing/invalid/expired JWT token
403ForbiddenValid token but insufficient permissions or wrong user type
404Not FoundResource doesn't exist or wrong tenant database
409ConflictDuplicate resource, idempotency conflict (Viva Wallet)
422Unprocessable EntityLaravel validation errors
429Too Many RequestsRate limiting (online ordering endpoints)
500Server ErrorUnhandled exceptions, database errors

Authentication Errors

401 "Unauthenticated"

Code Path: JwtAuthenticate middleware or Authenticate middleware

Causes:

  1. No Authorization: Bearer {token} header in request
  2. Token has expired (check exp claim)
  3. Token signature invalid (JWT secret mismatch)
  4. Token payload malformed

Diagnosis:

  • Decode the JWT token at jwt.io (use the debugger, not the verifier unless you have the secret)
  • Check the exp timestamp against current time
  • Verify JWT_SECRET in .env matches what was used to sign the token
  • Check if the user/device/customer model exists in the database
Files to check:
  app/Http/Middleware/JwtAuthenticate.php (line 40-48)
  app/Services/JwtService.php

401 "Invalid token"

Same middleware as above. The token was present but JwtService::validateToken() returned null.

Common Causes:

  • Token signed with a different secret (e.g., staging token used against production)
  • Token structure is corrupt
  • Token was manually modified

403 "Unauthorized: Invalid user type"

Code Path: JwtAuthenticate middleware (type guard check)

Cause: Token type doesn't match the route's required type. For example:

  • Customer token used on a backoffice route (requires type:backoffice)
  • User token used on a kiosk route (requires type:kiosk)

Diagnosis:

  • Decode the JWT, check the type field
  • Verify the route middleware stack to see expected types

403 "Unauthorized: Insufficient permissions"

Code Path: CheckPermission middleware

Cause: The authenticated user's role does not include the required permission.

Diagnosis:

  1. Check which permission the route requires (look at route definition)
  2. Check the user's assigned roles in MongoDB
  3. Check the role's permission list
  4. Verify the user has access to the specific location (for location-scoped permissions)
Files to check:
  app/Http/Middleware/CheckPermission.php
  app/Services/PermissionService.php
  app/Constants/Permissions.php (for permission constant names)

Validation Errors (422)

Laravel returns 422 with validation error details:

json
{
  "message": "The given data was invalid.",
  "errors": {
    "field_name": ["The field_name field is required."]
  }
}

Where validation happens:

  • Request classes in app/Http/Requests/ (FormRequest objects)
  • Each controller action that accepts input has a corresponding Request class

Common Validation Failures:

  • Required fields missing
  • Invalid UUID/ObjectId format
  • Enum value not in allowed set
  • Numeric fields receiving string values
  • Image upload exceeding size limits

Database Errors

"No tenant database configured"

Cause: The SetTenantDatabase middleware didn't set the tenant database, or it was set to null/empty.

Diagnosis:

  • Check JWT token for tenant_database claim
  • Verify the vendor's database name exists
  • Check the middleware order in the route group

MongoDB Write Conflict

Error: Write conflict or duplicate key error during transactions.

Code Path: DBTransactionTrait::executeWithTransactionRetry()

Cause: Two concurrent operations tried to modify the same document. The retry mechanism handles this automatically (up to 5 retries).

If retries are exhausted:

  • Check for hot documents (frequently updated by multiple requests)
  • Consider redesigning the data access pattern
  • Check if webhook handlers are processing duplicates

"Model not found" / 404 on Retrieve

Code Path: Repository retrieve() methods

Causes:

  1. Document was deleted
  2. Wrong tenant database (query running against wrong DB)
  3. ObjectId format incorrect (string vs MongoDB ObjectId)
  4. Soft-deleted document (need withTrashed: true)

Diagnosis:

  • Verify the document exists in MongoDB directly
  • Check which database the query is running against
  • Enable query logging to see the actual database being queried

Payment Errors

"Square Terminal device not configured for this device"

Code Path: PaymentService::processSquarePayment()

Cause: Device doesn't have a Square Terminal ID assigned.

Fix: Configure the Square Terminal device code in the BackOffice device settings.

409 Conflict on Viva Wallet Terminal Sale

Code Path: PaymentService::processVivaWalletPayment()

Cause: The idempotency key was already used for a previous terminal sale attempt.

Resolution: The code automatically regenerates the idempotency key and retries. If the error persists:

  • Check all_idempotency_keys on the transaction
  • Verify the terminal isn't stuck on a previous payment
  • May need to abort the terminal session manually

"Error verifying payment"

Code Path: PaymentService::verifyPayment()

Cause: Viva Wallet API returned an error when attempting to retrieve or capture the transaction.

Diagnosis:

  • Check payment_snapshot.orderCode exists
  • Verify the merchant ID is correct
  • Check Viva Wallet dashboard for the transaction status

"Payment capture failed"

Code Path: PaymentCaptureService::capturePayment()

Cause: Exception during the capture flow. Logged with full trace.

Diagnosis:

  • Search logs for the idempotency key
  • Check if the transaction was already captured (duplicate webhook)
  • Look for database write conflicts in the trace

Integration Errors

Webhook Signature Verification Failures

Middleware: VerifyDeliverooWebhook, VerifyShopifyWebhook, VerifySquareWebhook, VerifyUberEatsWebhook

Cause: The webhook secret in .env doesn't match the provider's configuration.

Fix:

  • Re-check the webhook secret in the provider's dashboard
  • Ensure the raw request body is used for signature computation
  • For Shopify: verify HMAC is computed correctly against X-Shopify-Hmac-Sha256

"Shopify integration not found"

Code Path: WebhookController::shopifyWebhook()

Cause: Webhook received from a shop domain that doesn't match any enabled integration.

Fix:

  • Verify the shop name in the ThirdPartyIntegration collection
  • Check if the integration was disabled

Third-Party API Timeouts

Cause: External API (Viva Wallet, Square, Deliveroo, Uber Eats, Shopify) is slow or down.

Diagnosis:

  • Check the provider's status page
  • Look for timeout exceptions in logs
  • Verify network connectivity from the server

Rate Limiting

429 Too Many Requests

Affected endpoints:

  • POST /customer/{slug}/order-history -- 5 requests per minute
  • GET /customer/{slug}/order-detail/{orderId} -- 10 requests per minute

Rate limiting is disabled in local environment.


Multi-Tenant Errors

Queries returning wrong data

Cause: Tenant database not properly set before query execution.

Diagnosis:

  1. Check middleware order ensures SetTenantDatabase runs before controller
  2. In jobs, verify tenant database is set in handle() method
  3. In webhook handlers, verify tenant resolution logic

Cross-tenant data leakage

Prevention:

  • All tenant-scoped queries go through the tenant database connection
  • The SetTenantDatabase middleware resets the connection per-request
  • Never use the default connection for tenant data

Debugging Toolkit

Log Searching

Key log patterns to search for:

"Payment capture failed"        -> Payment processing errors
"Error processing Viva webhook" -> Viva Wallet webhook failures
"Error processing Uber Eats"    -> Uber Eats webhook failures
"Failed to abort terminal"      -> Terminal session issues
"Slow payment capture"          -> Performance issues (>2s capture)
"Transaction not found"         -> Missing transactions during webhook
"Location not found"            -> Invalid location in webhook
"Write conflict"                -> Database concurrency issues

Database Inspection

MongoDB queries for common investigations:

javascript
// Find transaction by order number
db.transactions.findOne({ order_no: "ORDER-123" })

// Find transaction by idempotency key
db.transactions.findOne({ "payment_snapshot.idempotency_key": "idemp_xxx" })

// Find recent failed transactions
db.transactions.find({ status: "unpaid", updated_at: { $gte: new Date(Date.now() - 3600000) } })

// Check device activation status
db.devices.findOne({ activation_code: "ABC123" })

Environment-Specific Behavior

FeatureLocalStagingProduction
Auto-success paymentsConfigurableOffOff
Rate limitingDisabledEnabledEnabled
Stripe webhook verificationSkippedEnabledEnabled
Queue processingsync (optional)redisredis
Debug endpointsAvailableAvailableDisabled
phpinfo endpointAvailableAvailableDisabled

Sentry Integration

Production errors are tracked in Sentry:

  • Payment capture operations have Sentry tracing spans
  • SentryBeforeSendCallback.php filters/enriches events before sending
  • Slow payment captures (>2s) generate Sentry performance alerts

Error Response Format

Standard Success

json
{
  "success": true
}

Standard Error (from handleException)

json
{
  "message": "Error description"
}

Validation Error (422)

json
{
  "message": "The given data was invalid.",
  "errors": {
    "field": ["Validation message"]
  }
}

Controller Error Handling Pattern

All controllers use the same exception handling:

php
try {
    $result = $this->service->doSomething($request->validated());
} catch (\Throwable $th) {
    $this->handleException($th);  // Base Controller method
}
return response()->json($result);

The handleException() method in the base Controller class:

  • Logs the exception
  • Returns appropriate HTTP status code
  • Returns error message in JSON format
  • Reports to Sentry in production