Errors
Every request that does not succeed throws a subclass of Luigel\Paymongo\Exceptions\PaymongoException, chosen by the HTTP status PayMongo answered with. Catch the ones you can do something about, and let the rest reach your exception handler:
use Illuminate\Support\Facades\Log;
use Luigel\Paymongo\Exceptions\ConnectionException;
use Luigel\Paymongo\Exceptions\InvalidRequestException;
use Luigel\Paymongo\Exceptions\PaymentDeclinedException;
use Luigel\Paymongo\Exceptions\PaymongoException;
use Luigel\Paymongo\Exceptions\RateLimitException;
use Luigel\Paymongo\Exceptions\ServerException;
use Luigel\Paymongo\Facades\Paymongo;
try {
$intent = Paymongo::paymentIntents()->create([
'amount' => 150050,
'currency' => 'PHP',
'payment_method_allowed' => ['card', 'gcash'],
]);
} catch (InvalidRequestException $e) {
// 400, 403, 422, ...: fix the request, retrying it will not help.
foreach ($e->errors() as $error) {
$error->code; // "parameter_below_minimum"
$error->detail; // "The minimum value for the amount is 2000."
$error->attribute; // "amount", when PayMongo names one
$error->pointer; // e.g. "/data/attributes/amount"
}
} catch (PaymentDeclinedException $e) {
$reason = $e->firstError()?->code; // 402: ask for another payment method
} catch (RateLimitException $e) {
$wait = $e->retryAfter ?? 60; // 429: seconds from Retry-After, when sent
} catch (ServerException|ConnectionException $e) {
// 5xx, or PayMongo unreachable: already retried; try again later.
} catch (PaymongoException $e) {
// Authentication (401) or not found (404).
Log::error('PayMongo request failed', ['status' => $e->status, 'message' => $e->getMessage()]);
}
| Exception | Status | What to do |
|---|---|---|
InvalidRequestException |
400, 403, 422, and any other 4xx not below | Fix the request: a missing or invalid attribute, or a resource in the wrong state. Retrying the same request fails the same way. |
AuthenticationException |
401 | Check PAYMONGO_SECRET_KEY, and that the key belongs to the mode you expect. |
PaymentDeclinedException |
402 | Ask the customer for another payment method. |
ResourceNotFoundException |
404 | Check the id, and that it was created with the same key and mode. |
RateLimitException |
429 | Slow down. retryAfter holds the seconds from PayMongo's Retry-After header, when it sends one. |
ServerException |
5xx | PayMongo failed. Try again later. |
ConnectionException |
none | PayMongo could not be reached: DNS, TLS, or a timeout. status is null, and getPrevious() is Laravel's connection exception. |
InvalidResponseException |
2xx | PayMongo returned a successful HTTP status with malformed JSON, or a resource endpoint returned a missing or malformed resource payload. The response status is available, and errors() is empty because PayMongo did not return an API error. |
The package retries a request before throwing when repeating it cannot act twice: any request after a 429 or a connection that never opened, and a GET, a DELETE, or a POST with an idempotency key after a 5xx or a timeout as well. See Idempotency & retries.
Successful responses are checked before resource DTOs are built. A malformed response throws InvalidResponseException instead of returning a resource with empty fields. Bodyless action endpoints and successful DELETE responses are supported where the endpoint has no response resource.
InvalidWebhookSignatureException is the one exception not thrown by an API request. It is thrown for a webhook delivery that fails signature verification, and the webhook route answers those with 401 itself. See Webhooks.
What PayMongo said
Every exception carries what PayMongo sent back:
statusis the HTTP status, anint, ornullfor aConnectionException.errors()is every entry of PayMongo'serrorsarray, asLuigel\Paymongo\Data\ApiErrorobjects with acode, adetail, and, for an invalid attribute, itsattributename and JSONpointer. Any of them can benull.firstError()is the first of them, ornull.getMessage()is the first error'sdetail. When PayMongo's response has no errors, it isPayMongo request failed with status {status}., and when the body is not PayMongo's JSON, the body itself is the one error'sdetail. AConnectionException's message startsCould not connect to PayMongo:.
Branch on code, which is stable, rather than on detail, which is written for people. PayMongo lists its codes by product: Payment Intent and Payment Method errors, Refund errors, and the payment method pages under Errors.
Failed payments do not always throw
A payment that is declined after a redirect, or fails while the customer authorizes it in their e-wallet or bank, does not throw anywhere: the request that started it had already succeeded. You learn about it from the resource's status and from the payment.failed webhook (Luigel\Paymongo\Events\PaymentFailed). PayMongo's Key concepts explains that a payment intent has no failed status: it goes back to awaiting_payment_method so the customer can try another method, and its last_payment_error attribute, read with $intent->attribute('last_payment_error'), says why. See Payment Intents.
The Exceptions reference shows every exception class.