Multiple accounts
The Paymongo facade uses the account whose secret key is in PAYMONGO_SECRET_KEY. When one app takes payments for several PayMongo accounts, such as a marketplace where each merchant has their own, call Paymongo::withSecretKey() for a copy that uses another account's key:
use Luigel\Paymongo\Facades\Paymongo;
$merchantSecretKey = 'sk_test_merchant_account'; // e.g. decrypt($merchant->paymongo_secret_key)
$paymongo = Paymongo::withSecretKey($merchantSecretKey);
$intent = $paymongo->paymentIntents()->create([
'amount' => 150050,
'currency' => 'PHP',
'payment_method_allowed' => ['card', 'gcash'],
]);
// Paymongo::paymentIntents() still uses PAYMONGO_SECRET_KEY.
withSecretKey()returns a new, separatePaymongoManagerwith every service,paymentIntents()throughpayouts(). Pass that account's public key as the optionalpublicKey:argument if you useretrieveUsingClientKey(); without it, the copy has no public key. The timeout, retries and idempotency settings still come from your config.- The facade itself is unchanged, so one merchant's key never leaks into a request made for another. Keep the copy in a variable for as long as you work with that account.
- Store each merchant's secret key encrypted, for example with Laravel's
encryptedcast. Paymongo::fake()fakes the copies too, andPaymongo::assertSent()sees their requests. Check which account a request was made for with itsAuthorizationheader, which is HTTP Basic auth with the secret key as the username.
Webhooks for several accounts
Each account registers its own webhook endpoints, and each endpoint has its own secret. Register them with the copy for that account, for example Paymongo::withSecretKey($key)->webhooks()->create(...), then give each endpoint a route with its own named secret. See More than one endpoint.
A named secret lives in config/paymongo.php, so this suits a handful of accounts known up front. For accounts that come and go, register a route of your own that finds the account from the URL, and check each delivery against that account's secret with Luigel\Paymongo\Webhooks\SignatureVerifier:
use App\Models\Merchant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Luigel\Paymongo\Events\WebhookReceived;
use Luigel\Paymongo\Exceptions\InvalidWebhookSignatureException;
use Luigel\Paymongo\Webhooks\SignatureVerifier;
use Luigel\Paymongo\Webhooks\WebhookEvent;
// routes/api.php: register each merchant's endpoint as https://example.com/webhooks/paymongo/{merchant id}
Route::post('webhooks/paymongo/{merchant}', function (Request $request, Merchant $merchant) {
try {
(new SignatureVerifier(tolerance: 300))->verify(
$request->getContent(),
$request->header('Paymongo-Signature'),
$merchant->paymongo_webhook_secret,
livemode: (bool) config('paymongo.livemode'),
);
} catch (InvalidWebhookSignatureException) {
abort(401);
}
event(new WebhookReceived(WebhookEvent::fromArray($request->all())));
return response()->json(['received' => true]);
});
verify() throws an InvalidWebhookSignatureException for a delivery that fails the checks described under Signature verification. Dispatching WebhookReceived hands the event to the same listeners as Route::paymongoWebhooks(). This route does not drop repeat deliveries, so make those listeners idempotent.