This guide explains how to integrate the PayPal payment gateway using the unified-pay-core package. It covers setup, configuration, and key operations like creating payments, capturing payments, processing refunds, and handling webhooks.
Prerequisites
- Node.js and npm installed.
- A PayPal account with API credentials (Client ID and Secret).
- A webhook ID for verifying webhook events.
- Install the unified-pay-core package:
npm install unified-pay-core
Setup
Obtain PayPal Credentials
- Log in to your PayPal Developer Dashboard.
- Create a sandbox or live app to get your Client ID and Secret.
- Set up a webhook in the PayPal Dashboard and obtain the Webhook ID.
Install Dependencies
Ensure you have express and cors for the example server:
npm install express cors
Configure the Gateway
Initialize the PayPal gateway using the createGateways
function.
const express = require("express");
const cors = require("cors");
const { createGateways } = require("unified-pay-core");
const app = express();
app.use(cors());
app.use("/webhook", express.raw({ type: "application/json" }));
app.use(express.json());
const { paypal } = createGateways([
{
gateway: "paypal",
config: {
apiKey: process.env.API_KEY,
apiSecret: process.env.API_SECRET,
environment: "sandbox",
returnUrl: "https://example.com/success",
cancelUrl: "http://example.com/cancel",
webhookId: process.env.WEBHOOK_ID,
},
},
]);
Key Operations
1. Create a Payment
Initiate a payment request to create a PayPal order.
app.post("/create-payment", async (req, res) => {
try {
const { amount = 20 } = req.body;
const payment = await paypal.processPayment(amount, "USD", {
test_run: "Sample Payment",
});
res.json({
success: true,
transactionId: payment.transactionId,
approveLink: payment.approveLink || null,
});
} catch (err) {
console.error("Payment creation error:", err);
res.status(500).json({ success: false, error: "Payment creation failed" });
}
});
Input:
amount
: Payment amount (e.g., 20 for $20.00).currency
: Currency code (e.g., "USD").metadata
: Optional key-value pairs for additional data.
Output:
transactionId
: PayPal Order ID.approveLink
: URL for user approval (redirect the user to this link).
2. Capture a Payment
Capture a previously created order after user approval.
app.get("/return", async (req, res) => {
try {
const { token } = req.query;
if (!token) return res.status(400).json({ error: "Missing token" });
const capture = await paypal.capturePayment(token);
const verification = await paypal.verifyPayment(capture.transactionId);
res.json({ capture, verification });
} catch (err) {
console.error("Payment capture error:", err);
res.status(500).json({ error: "Payment capture failed" });
}
});
Input:
token
: The Order ID from the payment creation response.
Output:
capture
: Payment response with status, amount, and currency.verification
: Verification details confirming the payment status.
3. Process a Refund
Refund a captured payment (full or partial).
app.post("/refund", async (req, res) => {
try {
const { transactionId, amount } = req.body;
if (!transactionId) {
return res.status(400).json({ error: "transactionId is required" });
}
const refund = await paypal.processRefund(
transactionId,
amount ? parseFloat(amount) : undefined
);
res.json({
success: refund.success,
refundId: refund.refundId,
transactionId,
status: refund.status,
amount: refund.amount,
currency: refund.currency,
});
} catch (err) {
console.error("Refund error:", err);
res.status(500).json({ success: false, error: "Refund failed" });
}
});
Input:
transactionId
: The Capture ID from the payment capture response.amount
: Optional partial refund amount (e.g., 10 for $10.00).
Output:
refundId
: PayPal Refund ID.status
: Refund status (succeeded, pending, failed, cancelled).
4. Verify a Payment
Verify the status of a captured payment.
const verification = await paypal.verifyPayment(transactionId);
Input:
transactionId
: The Capture ID to verify.
Output:
verified
: Boolean indicating if the verification was successful.status
: Payment status (succeeded, pending, failed).isValid
: Boolean indicating if the payment is valid (e.g., COMPLETED).
Warning:ā ļø The webhook implementation for paypal has unresolved bugs and is not production-ready. Use only in a sandbox/testing environment.
5. Handle Webhooks
Set up a webhook endpoint to receive and process PayPal events.
app.post("/webhook/paypal", async (req, res) => {
try {
const result = await paypal.handleWebhookEvent(req.body, req.headers);
if (result === "INVALID") {
return res.status(400).send("Invalid webhook signature");
}
const parsedBody = JSON.parse(req.body.toString());
const eventType = parsedBody.event_type;
switch (eventType) {
case "PAYMENT.CAPTURE.COMPLETED":
console.log("Payment captured:", parsedBody.resource.id);
break;
case "PAYMENT.CAPTURE.DENIED":
console.log("Payment denied:", parsedBody.resource.id);
break;
default:
console.log("Unhandled event:", eventType);
}
res.status(200).send("OK");
} catch (error) {
console.error("Webhook processing error:", error);
res.status(500).send("Webhook processing failed");
}
});
Setup:
- Use
express.raw({ type: "application/json" })
for the webhook route. - Configure the webhook URL in the PayPal Dashboard (e.g.,
https://yourdomain.com/webhook/paypal
).
Supported Events:
PAYMENT.CAPTURE.COMPLETED
PAYMENT.CAPTURE.DENIED
Webhook Testing
- Use ngrok to expose your local server:
ngrok http 3000
. - Update the webhook URL in the PayPal Dashboard.
- Test events like
PAYMENT.CAPTURE.COMPLETED
using PayPal's webhook simulator.
Notes
- Order vs. Capture ID:
processPayment
returns an Order ID;capturePayment
uses this Order ID and returns a Capture ID for refunds and verification. - Webhook ID: Ensure
webhookId
is set for signature verification. - Return/Cancel URLs: Configure
returnUrl
andcancelUrl
for user redirection after approval or cancellation. - Error Handling: Check
success
anderror
fields in responses to handle failures.