# Payment Intents

A Payment Intent tracks one payment from start to finish: you create it for an amount, attach a [payment method](/payment-methods.md) 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](/checkout-sessions.md).

Every method lives on `Paymongo::paymentIntents()` and returns a [`PaymentIntent`](/data-objects.md#paymentintent). For every attribute PayMongo accepts, see its [Payment Intent reference](https://docs.paymongo.com/reference/create-a-paymentintent).

The flow:

1. Create the intent on your server, for the amount and the payment methods you accept.
2. Create a payment method for what the customer pays with, and attach it to the intent.
3. If the intent comes back `awaiting_next_action`, redirect the customer to authorize the payment. PayMongo sends them back to your return URL.
4. Wait for the `payment.paid` webhook to confirm it.

## Create an intent

`create()` takes the intent's attributes:

```php
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
```

- `amount` is integer centavos, at least `100` (PHP 1.00). `currency` is `PHP`.
- `payment_method_allowed` lists what the intent may be paid with: `card`, `gcash`, `paymaya`, `grab_pay`, `shopee_pay`, `qrph`, `dob`, `brankas`, or `billease`.
- `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`:

```php
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:

```php
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:

```php
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:

```php
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:

```php
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](https://docs.paymongo.com/docs/payment-acceptance-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:

```php
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](/webhooks.md).
