aceraven777/laravel-paymaya
A Laravel package for integrating PayMaya (Maya) payments into your application. It offers configuration and API helpers to create payment requests, manage checkout flows, and handle PayMaya responses, making it easier to add PayMaya support to Laravel projects with less boilerplate.
Add the package and let Composer pull in the underlying PayMaya SDK:
composer require <vendor>/laravel-paymaya
php artisan vendor:publish --tag=paymaya-config
Set credentials in .env (never commit them):
PAYMAYA_PUBLIC_KEY=pk_test_...
PAYMAYA_SECRET_KEY=sk_test_...
PAYMAYA_SANDBOX=true
Look first at these three places:
config/paymaya.php — base URL selection (sandbox vs. production), key names, timeout and retry settings.composer.json / composer.lock — since v1.1.2 the PayMaya SDK base code arrives as a Composer dependency rather than being bundled. Confirm the resolved SDK version before debugging anything else.First use case: create a checkout and redirect the customer.
public function pay(Order $order, PayMayaClient $paymaya)
{
$checkout = $paymaya->checkouts()->create([
'totalAmount' => [
'value' => $order->total_cents,
'currency' => 'PHP',
],
'buyer' => [
'firstName' => $order->customer->first_name,
'lastName' => $order->customer->last_name,
],
'requestReferenceNumber' => (string) $order->id,
'redirectUrl' => [
'success' => route('checkout.success', $order),
'failure' => route('checkout.failure', $order),
'cancel' => route('checkout.cancel', $order),
],
]);
$order->update(['paymaya_checkout_id' => $checkout['checkoutId']]);
return redirect($checkout['redirectUrl']);
}
Verify the round trip in sandbox with the test keys before touching production.
Wrap the SDK, don't scatter it. Keep PayMayaClient calls inside an application-level service (App\Services\PayMayaGateway) that returns your own DTOs. When the SDK changes shape, you edit one file instead of forty controllers. Resolve the client from the container rather than calling new so you can swap in a fake during tests.
Persist identifiers, not payloads. Store checkoutId, paymentId, requestReferenceNumber and status on your orders/payments table. Webhook retries are normal; your handler must be idempotent — look up by the PayMaya ID, skip if already in a terminal state, and return 200 regardless so PayMaya stops retrying.
Treat webhooks as the source of truth, redirects as UX. The success redirect only means the browser came back. Fulfil the order from the webhook (or from an explicit "retrieve checkout" call) so a customer closing the tab mid-redirect doesn't leave a paid order unfulfilled.
Queue the webhook work. Validate the signature in the controller, persist the raw event, dispatch a job, return 200. Slow handlers cause PayMaya to retry and, eventually, to flag your endpoint.
Guard against double charges. Generate your own idempotency/reference key per attempt (order_id . ':' . attempt_uuid) and send it as requestReferenceNumber. If a network timeout happens after the request left your server, you need a way to reconcile without creating a second checkout.
Use a state machine. Model statuses explicitly (pending → authorized → captured → failed/refunded) and only allow legal transitions. Payment integrations rot fastest when status is a free-form string column.
Test against the sandbox with HTTP faking. Because the SDK is now a Composer dependency, Http::fake() on the SDK's underlying Guzzle client — or a container-bound fake client — lets you test failure branches (402, timeout, malformed response) without hitting the network.
app/ or packages/, you now have two definitions of the same classes. Symptom: Cannot declare class ... already in use, or worse, silent resolution of the old copy. Delete the vendored directory and composer dump-autoload.composer update can move it under you. Lock the constraint and read the SDK changelog on patch bumps — payment SDKs quietly change required fields.10000 is ₱100.00, not ₱10,000. This is the single most common sandbox-to-production bug; store amounts as integers end to end and format only at the view layer.pk_test_/sk_test_ with sandbox). A mismatch often surfaces as an opaque 401 rather than a clear config error.config/paymaya.php, run php artisan config:clear. Stale cached config is the usual cause of "it works locally but not on the server."Authorization, secretKey, and card-like fields before anything reaches your log driver.5xx — never blindly retry a POST that creates a charge.hash_equals. Reject unsigned requests with 401 and log the attempt.composer show, (2) sandbox/production base URL, (3) key prefix match, (4) raw request/response body from a redacted log, (5) whether the duplicate-class warning is present. That order resolves most integration failures in minutes.How can I help you explore Laravel packages today?