medusa-payment-pesapal
Version:
Pesapal payment provider plugin for Medusa v2 - Accept payments via Pesapal gateway with support for M-Pesa, Airtel Money, and other East African payment methods
350 lines • 28.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PesapalProviderService = void 0;
const utils_1 = require("@medusajs/framework/utils");
class PesapalProviderService extends utils_1.AbstractPaymentProvider {
constructor(container, options) {
super(container, options);
this.authToken = null;
this.tokenExpiry = null;
this.options_ = options;
this.logger_ = container.logger;
// Validate required options
if (!options.consumer_key || !options.consumer_secret) {
throw new Error("Pesapal consumer_key and consumer_secret are required");
}
}
get baseUrl() {
return this.options_.environment === "live"
? "https://pay.pesapal.com/v3"
: "https://cybqa.pesapal.com/pesapalv3";
}
async getAuthToken() {
// Check if we have a valid token
if (this.authToken && this.tokenExpiry && new Date() < this.tokenExpiry) {
return this.authToken;
}
try {
const response = await fetch(`${this.baseUrl}/api/Auth/RequestToken`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify({
consumer_key: this.options_.consumer_key,
consumer_secret: this.options_.consumer_secret,
}),
});
const data = await response.json();
if (data.error || !data.token) {
throw new Error(`Pesapal authentication failed: ${data.message || data.error}`);
}
this.authToken = data.token;
this.tokenExpiry = new Date(data.expiryDate);
return this.authToken;
}
catch (error) {
this.logger_.error("Failed to get Pesapal auth token", error);
throw new Error("Failed to authenticate with Pesapal");
}
}
async makeAuthenticatedRequest(endpoint, options = {}) {
const token = await this.getAuthToken();
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
"Accept": "application/json",
...options.headers,
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Pesapal API error: ${response.status} ${errorText}`);
}
return response.json();
}
async initiatePayment(input) {
try {
const { currency_code, amount, context: sessionContext } = input;
const { customer, billing_address } = (sessionContext || {});
// Generate a unique merchant reference
const merchantReference = `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Prepare order data
const orderData = {
id: merchantReference,
currency: currency_code.toUpperCase(),
amount: Number(amount) / 100, // Convert from cents to major currency unit
description: `Payment for order ${merchantReference}`,
callback_url: this.options_.ipn_url || `${process.env.BACKEND_URL}/store/pesapal/webhook`,
notification_id: await this.getIpnId(),
billing_address: {
email_address: customer?.email || billing_address?.email || "customer@example.com",
phone_number: billing_address?.phone || customer?.phone || "",
country_code: billing_address?.country_code || "KE",
first_name: billing_address?.first_name || customer?.first_name || "Customer",
middle_name: billing_address?.address_2 || "",
last_name: billing_address?.last_name || customer?.last_name || "",
line_1: billing_address?.address_1 || "",
line_2: billing_address?.address_2 || "",
city: billing_address?.city || "",
state: billing_address?.province || "",
postal_code: billing_address?.postal_code || "",
zip_code: billing_address?.postal_code || "",
},
};
const response = await this.makeAuthenticatedRequest("/api/Transactions/SubmitOrderRequest", {
method: "POST",
body: JSON.stringify(orderData),
});
if (response.error) {
throw new Error(`Pesapal order submission failed: ${response.message}`);
}
return {
id: response.order_tracking_id,
data: {
order_tracking_id: response.order_tracking_id,
merchant_reference: response.merchant_reference,
redirect_url: response.redirect_url,
amount: amount,
currency: currency_code,
status: "pending",
},
};
}
catch (error) {
this.logger_.error("Failed to initiate Pesapal payment", error);
throw new Error("Failed to initiate payment");
}
}
async authorizePayment(input) {
try {
const { order_tracking_id } = (input.data || {});
// Get transaction status from Pesapal
const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`);
const status = this.mapPesapalStatus(statusResponse.payment_status_description);
return {
status: status,
data: {
...input.data,
payment_status: statusResponse.payment_status_description,
confirmation_code: statusResponse.confirmation_code,
payment_method: statusResponse.payment_method,
payment_account: statusResponse.payment_account,
status: status,
},
};
}
catch (error) {
this.logger_.error("Failed to authorize Pesapal payment", error);
throw new Error("Failed to authorize payment");
}
}
async capturePayment(input) {
try {
// Pesapal doesn't have a separate capture step - payment is captured upon authorization
const { order_tracking_id } = (input.data || {});
const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`);
return {
data: {
...input.data,
payment_status: statusResponse.payment_status_description,
confirmation_code: statusResponse.confirmation_code,
status: "captured",
},
};
}
catch (error) {
this.logger_.error("Failed to capture Pesapal payment", error);
throw new Error("Failed to capture payment");
}
}
async refundPayment(input) {
try {
const { confirmation_code } = (input.data || {});
const { amount } = input;
// Prepare refund data
const refundData = {
confirmation_code: confirmation_code,
amount: Number(amount) / 100, // Convert from cents
username: this.options_.merchant_name || "merchant",
remarks: `Refund for payment ${confirmation_code}`,
};
const response = await this.makeAuthenticatedRequest("/api/Transactions/RefundRequest", {
method: "POST",
body: JSON.stringify(refundData),
});
return {
data: {
...input.data,
refund_status: "refunded",
refund_amount: amount,
refund_response: response,
},
};
}
catch (error) {
this.logger_.error("Failed to refund Pesapal payment", error);
throw new Error("Failed to refund payment");
}
}
async cancelPayment(input) {
try {
// Pesapal doesn't have explicit cancel - we just mark it as cancelled
return {
data: {
...input.data,
status: "canceled",
canceled_at: new Date().toISOString(),
},
};
}
catch (error) {
this.logger_.error("Failed to cancel Pesapal payment", error);
throw new Error("Failed to cancel payment");
}
}
async deletePayment(input) {
// Return the session data as-is since Pesapal doesn't support deleting payments
return {
data: input.data,
};
}
async retrievePayment(input) {
try {
const { order_tracking_id } = (input.data || {});
// Get transaction status from Pesapal
const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`);
return {
data: {
...input.data,
payment_status: statusResponse.payment_status_description,
confirmation_code: statusResponse.confirmation_code,
payment_method: statusResponse.payment_method,
payment_account: statusResponse.payment_account,
amount: statusResponse.amount,
currency: statusResponse.currency,
created_date: statusResponse.created_date,
},
};
}
catch (error) {
this.logger_.error("Failed to retrieve Pesapal payment", error);
throw new Error("Failed to retrieve payment");
}
}
async updatePayment(input) {
// Return the current session data since Pesapal doesn't support updating payments
return {
data: input.data,
};
}
async getPaymentStatus(input) {
try {
const { order_tracking_id } = (input.data || {});
// Get transaction status from Pesapal
const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${order_tracking_id}`);
const status = this.mapPesapalStatus(statusResponse.payment_status_description);
return {
status: status,
data: {
...input.data,
payment_status: statusResponse.payment_status_description,
confirmation_code: statusResponse.confirmation_code,
status: status,
},
};
}
catch (error) {
this.logger_.error("Failed to get Pesapal payment status", error);
return {
status: "pending",
data: input.data,
};
}
}
async getWebhookActionAndData(data) {
try {
const webhookData = data.data;
const { OrderTrackingId, OrderMerchantReference } = webhookData;
if (!OrderTrackingId) {
return { action: "not_supported" };
}
// Get the current status from Pesapal
const statusResponse = await this.makeAuthenticatedRequest(`/api/Transactions/GetTransactionStatus?orderTrackingId=${OrderTrackingId}`);
const status = this.mapPesapalStatus(statusResponse.payment_status_code);
let action = "not_supported";
switch (status) {
case "authorized":
action = "authorized";
break;
case "captured":
action = "captured";
break;
case "error":
action = "failed";
break;
default:
action = "not_supported";
}
return {
action: action,
data: {
session_id: String(OrderMerchantReference),
amount: statusResponse.amount * 100, // Convert to cents
},
};
}
catch (error) {
this.logger_.error("Failed to process Pesapal webhook", error);
return { action: "failed" };
}
}
mapPesapalStatus(pesapalStatus) {
switch (pesapalStatus) {
case "0":
return "pending";
case "1":
return "authorized";
case "2":
return "captured";
case "3":
return "canceled";
default:
return "error";
}
}
async getIpnId() {
try {
// First, try to get existing IPN registrations
const ipnList = await this.makeAuthenticatedRequest("/api/URLSetup/GetIpnList");
// Check if we already have an IPN registered for our URL
const existingIpn = ipnList.find((ipn) => ipn.url === this.options_.ipn_url && ipn.status === "Active");
if (existingIpn) {
return existingIpn.ipn_id;
}
// Register new IPN if none exists
const ipnData = {
url: this.options_.ipn_url || `${process.env.BACKEND_URL}/store/pesapal/webhook`,
ipn_notification_type: "GET",
};
const response = await this.makeAuthenticatedRequest("/api/URLSetup/RegisterIPN", {
method: "POST",
body: JSON.stringify(ipnData),
});
return response.ipn_id;
}
catch (error) {
this.logger_.error("Failed to get/register IPN", error);
// Return a default IPN ID or throw error based on your preference
throw new Error("Failed to setup IPN registration");
}
}
}
exports.PesapalProviderService = PesapalProviderService;
PesapalProviderService.identifier = "pesapal";
PesapalProviderService.displayName = "Pesapal";
exports.default = PesapalProviderService;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9wcm92aWRlcnMvcGVzYXBhbC9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFEQUFtRTtBQWtFbkUsTUFBYSxzQkFBdUIsU0FBUSwrQkFBdUM7SUFTakYsWUFDRSxTQUFjLEVBQ2QsT0FBdUI7UUFFdkIsS0FBSyxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQTtRQVBuQixjQUFTLEdBQWtCLElBQUksQ0FBQTtRQUMvQixnQkFBVyxHQUFnQixJQUFJLENBQUE7UUFRckMsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUE7UUFDdkIsSUFBSSxDQUFDLE9BQU8sR0FBRyxTQUFTLENBQUMsTUFBTSxDQUFBO1FBRS9CLDRCQUE0QjtRQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLFlBQVksSUFBSSxDQUFDLE9BQU8sQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUN0RCxNQUFNLElBQUksS0FBSyxDQUFDLHVEQUF1RCxDQUFDLENBQUE7UUFDMUUsQ0FBQztJQUNILENBQUM7SUFFRCxJQUFZLE9BQU87UUFDakIsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLFdBQVcsS0FBSyxNQUFNO1lBQ3pDLENBQUMsQ0FBQyw0QkFBNEI7WUFDOUIsQ0FBQyxDQUFDLHFDQUFxQyxDQUFBO0lBQzNDLENBQUM7SUFFTyxLQUFLLENBQUMsWUFBWTtRQUN4QixpQ0FBaUM7UUFDakMsSUFBSSxJQUFJLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQyxXQUFXLElBQUksSUFBSSxJQUFJLEVBQUUsR0FBRyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDeEUsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFBO1FBQ3ZCLENBQUM7UUFFRCxJQUFJLENBQUM7WUFDSCxNQUFNLFFBQVEsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLElBQUksQ0FBQyxPQUFPLHdCQUF3QixF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