Payment Intents
A Payment Intent tracks one payment from start to finish: you create it for an amount, attach a payment method to it, and it moves through its statuses until the payment succeeds. Use it when you build the payment form yourself instead of sending the customer to a Checkout Session.
Every method lives on Paymongo::paymentIntents() and returns a PaymentIntent. For every attribute PayMongo accepts, see its Payment Intent reference.
The flow:
- Create the intent on your server, for the amount and the payment methods you accept.
- Create a payment method for what the customer pays with, and attach it to the intent.
- If the intent comes back
awaiting_next_action, redirect the customer to authorize the payment. PayMongo sends them back to your return URL. - Wait for the
payment.paidwebhook to confirm it.
Create an intent
create() takes the intent's attributes:
use Luigel\Paymongo\Facades\Paymongo;
$intent = Paymongo::paymentIntents()->create([
'amount' => 150050, // PHP 1,500.50, in centavos
'currency' => 'PHP',
'payment_method_allowed' => ['card', 'gcash', 'paymaya'],
'payment_method_options' => [
'card' => ['request_three_d_secure' => 'automatic'],
],
'description' => 'Order ORDER-1234',
'statement_descriptor' => 'LUIGEL STORE',
'metadata' => ['order_id' => '1234'],
], idempotencyKey: 'order-1234-payment');
$intent->id; // "pi_hsJNpsRFU1LxgVbxW4YJHRs6"
$intent->clientKey; // for your frontend, if it attaches the payment method
amountis integer centavos, at least100(PHP 1.00).currencyisPHP.payment_method_allowedlists what the intent may be paid with:card,gcash,paymaya,grab_pay,shopee_pay,qrph,dob,brankas, orbillease.idempotencyKey:makes a retried request return the same intent instead of creating a second one. Use a key unique to the order. Without it the package sends a random key per call.
Attach a payment method
attach() attaches a payment method to the intent, and that starts the payment. Pass returnUrl:, the page PayMongo sends the customer back to after they authorize.
For a card, your frontend usually creates the payment method with your public key, so the card number never reaches your server, and sends you its id. A card that needs 3D Secure comes back awaiting_next_action:
use Luigel\Paymongo\Enums\PaymentIntentStatus;
use Luigel\Paymongo\Facades\Paymongo;
$intent = Paymongo::paymentIntents()->attach(
'pi_hsJNpsRFU1LxgVbxW4YJHRs6',
'pm_wr98R2gwWroVxfkcNVZBuXg2', // created by your frontend with the public key
returnUrl: 'https://example.com/orders/1234',
);
if ($intent->status === PaymentIntentStatus::AwaitingNextAction) {
return redirect()->away($intent->nextAction->url); // the card's 3D Secure check
}
E-wallets, online banking, and buy now, pay later always need the customer to authorize on the provider's page, and returnUrl: is required for them. Their payment method takes only a type:
use Luigel\Paymongo\Enums\PaymentIntentStatus;
use Luigel\Paymongo\Facades\Paymongo;
$method = Paymongo::paymentMethods()->create(['type' => 'gcash']);
$intent = Paymongo::paymentIntents()->attach(
'pi_hsJNpsRFU1LxgVbxW4YJHRs6',
$method->id,
returnUrl: 'https://example.com/orders/1234',
);
if ($intent->status === PaymentIntentStatus::AwaitingNextAction) {
return redirect()->away($intent->nextAction->url); // GCash's authorization page
}
When your frontend attaches with the public key instead, it passes the intent's clientKey. On the server, attach() also accepts clientKey:, but the secret key does not need it.
When the customer lands back on your return URL, retrieve the intent to show its status. Treat it as a hint only: the payment.paid webhook is the proof of payment.
Retrieve an intent
retrieve() returns the intent with its status, its payments, and the last payment error:
use Luigel\Paymongo\Facades\Paymongo;
$intent = Paymongo::paymentIntents()->retrieve('pi_hsJNpsRFU1LxgVbxW4YJHRs6');
$intent->status; // ?PaymentIntentStatus
$intent->money()?->format(); // "₱1,500.50"
$intent->lastPaymentError; // ?array: why the last attempt failed
foreach ($intent->payments as $payment) {
$payment->id; // "pay_..."
}
retrieveUsingClientKey() retrieves an intent the way a browser does, with your public key and the intent's clientKey instead of the secret key. It needs PAYMONGO_PUBLIC_KEY set and throws AuthenticationException without it:
use Luigel\Paymongo\Facades\Paymongo;
$intent = Paymongo::paymentIntents()->retrieveUsingClientKey(
'pi_hsJNpsRFU1LxgVbxW4YJHRs6',
'pi_hsJNpsRFU1LxgVbxW4YJHRs6_client_Kh6AjSNGfZDoLw5wBWKqpP8u',
);
Authorize now, capture later
Create the intent with 'capture_type' => 'manual' to hold the amount on the customer's card without charging it. After the customer attaches a card and passes 3D Secure, the intent waits in awaiting_capture until capture() charges the full amount, or a smaller one in centavos:
use Luigel\Paymongo\Facades\Paymongo;
$intent = Paymongo::paymentIntents()->create([
'amount' => 150050, // PHP 1,500.50, in centavos
'currency' => 'PHP',
'payment_method_allowed' => ['card'],
'capture_type' => 'manual',
]);
// ...attach a card; the intent then waits in awaiting_capture.
$intent = Paymongo::paymentIntents()->capture($intent->id);
// Or capture less than was authorized, in centavos:
$intent = Paymongo::paymentIntents()->capture($intent->id, 100000);
PayMongo releases a hold it has not captured after 7 days. Holds work for Visa and Mastercard only, and PayMongo must enable them on your account first. See PayMongo's Hold then capture guide.
Cancel an intent
cancel() cancels an intent that has not succeeded, such as a hold you decide not to capture. Nothing is charged:
use Luigel\Paymongo\Facades\Paymongo;
$intent = Paymongo::paymentIntents()->cancel('pi_hsJNpsRFU1LxgVbxW4YJHRs6');
Statuses
$intent->status is a Luigel\Paymongo\Enums\PaymentIntentStatus:
| Case | Value | Meaning |
|---|---|---|
AwaitingPaymentMethod |
awaiting_payment_method |
Created, or the last attempt failed. Attach a payment method. |
AwaitingNextAction |
awaiting_next_action |
The customer must authorize at $intent->nextAction->url. |
Processing |
processing |
The provider is confirming the payment. |
AwaitingCapture |
awaiting_capture |
Authorized with manual capture. Capture or cancel it. |
Succeeded |
succeeded |
Paid. $intent->payments holds the payment. |
Cancelled |
cancelled |
Cancelled. It cannot be paid. |
After a failed attempt, the intent goes back to awaiting_payment_method and $intent->lastPaymentError says why, so the customer can try another method on the same intent.
Know when it was paid
PayMongo sends payment.paid when a payment succeeds and payment.failed when an attempt fails. The package dispatches them as Luigel\Paymongo\Events\PaymentPaid and PaymentFailed. See Webhooks.