@gmisoftware/przelewy24-payments-medusa
Version:
Przelewy24 (P24) payment integration plugin for Medusa e-commerce with BLIK, cards, and general P24 support.
498 lines • 38.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = __importDefault(require("crypto"));
const utils_1 = require("@medusajs/framework/utils");
const p24_api_1 = require("../services/p24-api");
class P24Base extends utils_1.AbstractPaymentProvider {
static validateOptions(options) {
if (!(0, utils_1.isDefined)(options.merchant_id)) {
throw new Error("Required option `merchant_id` is missing in P24 plugin");
}
if (!(0, utils_1.isDefined)(options.pos_id)) {
throw new Error("Required option `pos_id` is missing in P24 plugin");
}
if (!(0, utils_1.isDefined)(options.api_key)) {
throw new Error("Required option `api_key` is missing in P24 plugin");
}
if (!(0, utils_1.isDefined)(options.crc)) {
throw new Error("Required option `crc` is missing in P24 plugin");
}
if (!(0, utils_1.isDefined)(options.frontend_url)) {
throw new Error("Required option `frontend_url` is missing in P24 plugin");
}
if (!(0, utils_1.isDefined)(options.backend_url)) {
throw new Error("Required option `backend_url` is missing in P24 plugin");
}
}
constructor(cradle, options) {
// @ts-ignore
super(...arguments);
this.container_ = cradle;
this.options_ = options;
this.p24Api = new p24_api_1.P24ApiService(options);
}
get options() {
return this.options_;
}
/**
* Normalizes payment create parameters for P24 transaction registration.
*
* This method prepares the common parameters that will be used across all
* P24 payment methods (cards, BLIK, etc.) based on the provider-specific options.
*
* @returns Normalized P24 transaction parameters
*/
normalizePaymentCreateParams() {
const res = {};
res.description =
this.paymentIntentOptions.description ?? "Payment via Przelewy24";
res.channel = this.paymentIntentOptions.channel;
return res;
}
/**
* Initiates a new payment with Przelewy24.
*
* This method creates a payment session with P24 by registering a transaction.
* It uses provider-specific options (channel, method) from the concrete implementations
* and normalizes common parameters for all P24 payment methods.
*
* @param input - The payment initiation input containing amount, currency, and context
* @returns The initiated payment details with session ID and redirect URL
*/
async initiatePayment({ currency_code, amount, data, context, }) {
try {
console.log(`Initiating P24 payment - Amount: ${amount} ${currency_code}`);
console.log(`Context:`, context);
console.log("data: ", data);
const normalizedParams = this.normalizePaymentCreateParams();
console.log("normalizedParams: ", normalizedParams);
const customerEmail = context?.customer?.email ||
data?.customer?.email ||
"customer@example.com";
const country = data?.country?.toUpperCase() || "PL";
const language = data?.language?.toLowerCase() || "pl";
console.log(`Using country: ${country}, language: ${language}`);
const transactionRequest = {
sessionId: context?.idempotency_key,
amount: Number(amount),
country: country,
language: language,
currency: currency_code.toUpperCase(),
description: normalizedParams.description || `Payment ${context?.idempotency_key}`,
email: customerEmail,
channel: normalizedParams.channel,
urlReturn: `${this.options_.frontend_url}/payment/return?cart_id=${data?.cart_id}`,
urlStatus: `${this.options_.backend_url}/hooks/payment/${this.getProviderKey()}_przelewy24`,
};
console.log(`Registering P24 transaction with session ID: ${transactionRequest.sessionId}`);
const sessionData = await this.p24Api.registerTransaction(transactionRequest);
console.log(`P24 transaction response:`, sessionData);
// Validate the response - check if P24 returned an error code
if (sessionData.responseCode !== 0) {
throw this.buildError("Failed to register P24 transaction", new Error(`P24 API error: ${sessionData.responseCode} - ${sessionData.message || "Unknown error"}`));
}
console.log(`P24 payment ${transactionRequest.sessionId} successfully initiated`);
return {
id: transactionRequest.sessionId,
data: {
session_id: transactionRequest.sessionId,
token: sessionData.data.token,
redirect_url: `${this.p24Api.getBaseRedirectURL()}/${sessionData.data.token}`,
amount: amount,
currency: currency_code,
description: transactionRequest.description,
email: transactionRequest.email,
country: country,
language: language,
channel: transactionRequest.channel,
...sessionData,
},
};
}
catch (error) {
console.error(`Error initiating P24 payment: ${error.message}`);
throw error;
}
}
/**
* Authorizes a payment session using the third-party payment provider.
*
* During checkout, the customer may need to perform actions required by the payment provider,
* such as entering their card details or confirming the payment. Once that is done, the
* customer can place their order.
*
* During cart-completion before placing the order, this method is used to authorize the
* cart's payment session with the third-party payment provider. The payment can later be
* captured using the capturePayment method.
*
* For P24, this method validates the payment status by fetching transaction details
* from P24 and mapping P24 statuses (0-3) to Medusa payment statuses:
* - 0: pending
* - 1: authorized
* - 2: captured
* - 3: canceled
*
* @param input - The authorize payment input containing the payment data
* @returns The payment data with authorization status
*/
async authorizePayment(input) {
console.log("----- REACHED TO THE AUTHORIZE PAYMENT -----");
console.log("input from authorize: ", input);
const sessionId = input.data?.session_id;
if (!sessionId) {
throw this.buildError("Session ID is required for payment authorization", new Error("No session ID provided"));
}
try {
const { transactionDetails, p24Status, medusaStatus } = await this.getTransactionDetailsAndStatus(sessionId);
console.log("transactionDetails (in authorize payment): ", transactionDetails);
if (!["authorized", "captured", "pending"].includes(medusaStatus)) {
throw this.buildError(`Payment is not in a valid state for authorization: current status is ${medusaStatus}`, new Error(`Invalid payment status: ${medusaStatus}`));
}
return {
data: {
...input.data,
...transactionDetails.data,
p24_status: p24Status,
},
status: medusaStatus,
};
}
catch (error) {
console.error(`Error authorizing payment ${sessionId}: ${error.message}`);
throw error;
}
}
/**
* Captures a payment using the third-party provider.
*
* This method provides manual payment capture capability for admin users.
* In normal operation, payments are automatically verified and captured via webhook
* processing in getWebhookActionAndData. This method serves as a backup option.
*
* According to P24 documentation, transaction/verify is required to confirm the payment
* and transfer the amount to the merchant. Without verification, the amount stays as
* an advance payment at the customer's disposal.
*
* @param input - The capture payment input containing the payment data
* @returns The payment data with capture confirmation
*/
async capturePayment(input) {
console.log("----- REACHED TO THE CAPTURE PAYMENT -----");
console.log("input from capture: ", input);
const sessionId = input.data?.sessionId;
const amount = input.data?.amount;
const currency = input.data?.currency;
const orderId = input.data?.orderId;
if (!sessionId || !amount || !currency || !orderId) {
throw this.buildError("Missing required data for payment capture", new Error("Session ID, amount, currency, and order ID are required"));
}
try {
console.log(`Capturing payment ${sessionId} with order ID ${orderId}...`);
// Call transaction/verify to confirm and capture the payment
const verification = await this.p24Api.verifyTransaction(sessionId, amount, currency, orderId);
if (verification.responseCode !== 0 ||
verification.data.status !== "success") {
throw this.buildError("Payment capture failed", new Error(`Verification failed - responseCode: ${verification.responseCode}, status: ${verification.data.status}`));
}
console.log(`Payment ${sessionId} successfully captured`);
return {
data: {
...input.data,
status: "captured",
captured_at: new Date().toISOString(),
order_id: orderId,
capture_verified: true,
},
};
}
catch (error) {
console.error(`Error capturing payment ${sessionId}: ${error.message}`);
throw error;
}
}
/**
* Deletes a payment session in the third-party payment provider.
*
* P24 doesn't support programmatic deletion of payment sessions.
* Payment sessions expire automatically after 15 minutes.
*
* When a customer chooses a payment method during checkout, then chooses a different one,
* this method is triggered to delete the previous payment session.
*
* @param input - The delete payment input containing the payment data
* @returns The same payment data (no deletion performed)
*/
async deletePayment(input) {
return {
data: input.data,
};
}
/**
* Cancels a payment in the third-party payment provider.
*
* P24 doesn't support programmatic cancellation of payments.
* Payments can only be canceled through the P24 merchant panel.
*
* This method is used when the admin user cancels an order.
* The order can only be canceled if the payment is not captured yet.
*
* @param input - The cancel payment input containing the payment data
* @returns The same payment data (no cancellation performed)
*/
async cancelPayment(input) {
return {
data: input.data,
};
}
/**
* Refunds an amount using the P24 payment provider.
*
* This method is triggered when the admin user refunds a payment of an order.
* It creates a refund request to P24 using the transaction/refund endpoint.
*
* The method uses the session ID and order ID from the payment data to identify
* the transaction to refund, and generates a unique UUID for tracking the refund.
*
* @param input - The input to refund the payment containing amount and payment data
* @returns The payment data with refund information and status
*/
async refundPayment(input) {
const { data: paymentData, amount: refundAmount, context } = input;
const sessionId = paymentData?.session_id;
const orderId = paymentData?.order_id;
if (!sessionId || !orderId) {
throw this.buildError("No session ID or order ID provided while refunding payment", new Error("Missing session ID or order ID"));
}
try {
const currencyCode = paymentData?.currency || "PLN";
const refundsUuid = crypto_1.default.randomUUID().substring(0, 35);
const requestId = context?.idempotency_key || `refund-${Date.now()}`;
const refundData = {
requestId: requestId,
refunds: [
{
orderId: orderId,
sessionId: sessionId,
amount: Number(refundAmount),
description: `Refund for order ${sessionId}`,
},
],
refundsUuid: refundsUuid,
// urlStatus: this.options_.backend_url
// ? `${
// this.options_.backend_url
// }/hooks/refund/${this.getProviderKey()}_przelewy24`
// : undefined,
};
const refundResult = await this.p24Api.processRefund(refundData);
// Check if refund was successful
const refundStatus = refundResult.data[0]?.status;
const refundMessage = refundResult.data[0]?.message;
if (refundResult.responseCode !== 0 || !refundStatus) {
throw this.buildError("Refund request failed", new Error(`P24 API error: ${refundResult.responseCode} - ${refundMessage || "Unknown error"}`));
}
return {
data: {
...paymentData,
refund_amount: refundAmount,
refunded_at: new Date().toISOString(),
refunds_uuid: refundsUuid,
refund_request_id: requestId,
refund_status: refundStatus,
refund_message: refundMessage,
status: "refund_requested",
},
};
}
catch (e) {
throw this.buildError("An error occurred in refundPayment", e);
}
}
async retrievePayment(input) {
try {
const { data: paymentSessionData } = input;
// Note: This would need the full transaction data to verify
return {
data: paymentSessionData,
};
}
catch (e) {
throw this.buildError("An error occurred in retrievePayment", e);
}
}
async updatePayment(input) {
const { data, amount } = input;
const amountNumeric = Number(amount);
if (data?.amount === amountNumeric) {
return { data };
}
// P24 doesn't support updating payment amounts after creation
// We need to create a new payment session
throw this.buildError("P24 doesn't support updating payment amounts. Please create a new payment session.", new Error("Update not supported"));
}
/**
* Processes webhook data from P24
*
* @param webhookData - The webhook payload from P24
* @returns The action and data to be processed
*/
async getWebhookActionAndData(webhookData) {
const { data } = webhookData;
try {
const payload = data;
const { sessionId, orderId, amount, currency, sign } = payload;
console.log("----------- P24 WEBHOOK RECEIVED -----------");
console.log("Session ID:", sessionId);
console.log("Order ID:", orderId);
console.log("Amount:", amount);
console.log("Currency:", currency);
if (!this.p24Api.verifyWebhookSignature(payload, sign)) {
console.error(`Invalid webhook signature for session ${sessionId}`);
return { action: utils_1.PaymentActions.NOT_SUPPORTED };
}
console.log("Verifying transaction with P24...");
const verification = await this.p24Api.verifyTransaction(sessionId, amount, currency, orderId);
if (verification.responseCode !== 0 ||
verification.data.status !== "success") {
console.error(`Transaction verification failed - responseCode: ${verification.responseCode}, status: ${verification.data.status}`);
return {
action: utils_1.PaymentActions.FAILED,
data: {
session_id: sessionId,
amount: amount,
},
};
}
console.log("Transaction verified successfully");
const { medusaStatus } = await this.getTransactionDetailsAndStatus(sessionId);
const webhookData = {
session_id: sessionId,
amount: amount,
};
switch (medusaStatus) {
case "authorized":
return {
action: utils_1.PaymentActions.AUTHORIZED,
data: webhookData,
};
case "captured":
return {
action: utils_1.PaymentActions.SUCCESSFUL,
data: webhookData,
};
case "pending":
return {
action: utils_1.PaymentActions.PENDING,
data: webhookData,
};
case "canceled":
return {
action: utils_1.PaymentActions.CANCELED,
data: webhookData,
};
case "error":
return {
action: utils_1.PaymentActions.FAILED,
data: webhookData,
};
default:
return {
action: utils_1.PaymentActions.NOT_SUPPORTED,
data: webhookData,
};
}
}
catch (error) {
console.error("Error processing P24 webhook:", error);
// Try to extract basic information even if there's an error
try {
const fallbackPayload = webhookData.data;
const { sessionId, amount, currency } = fallbackPayload;
if (sessionId && amount && currency) {
return {
action: utils_1.PaymentActions.FAILED,
data: {
session_id: sessionId,
amount: amount,
},
};
}
}
catch (fallbackError) {
console.error("Failed to extract fallback data:", fallbackError);
}
throw error;
}
}
/**
* Gets the payment status from P24 based on the session ID.
* This method fetches the current status from P24 and maps it to Medusa's format.
*
* @param input - The get payment status input containing session data
* @returns The current payment status from P24
*/
async getPaymentStatus(input) {
const sessionId = input.context?.idempotency_key;
if (!sessionId) {
console.warn("No session ID provided for getPaymentStatus, returning pending");
return { status: "pending" };
}
console.log(`Fetching payment status for session: ${sessionId}`);
try {
const { medusaStatus } = await this.getTransactionDetailsAndStatus(sessionId);
return { status: medusaStatus };
}
catch (error) {
console.error(`Error getting payment status for session ${sessionId}: ${error.message}`);
throw error;
}
}
/**
* Gets transaction details and maps status from P24
* This is a reusable method to avoid code duplication across payment methods
*
* @param sessionId - The P24 session ID
* @returns Object containing transaction details and mapped Medusa status
*/
async getTransactionDetailsAndStatus(sessionId) {
const transactionDetails = await this.p24Api.getTransactionBySessionId(sessionId);
if (transactionDetails.responseCode !== 0) {
throw this.buildError("Failed to retrieve transaction details", new Error(`P24 API error: ${transactionDetails.responseCode}`));
}
const p24Status = parseInt(transactionDetails.data.status);
const medusaStatus = this.mapP24StatusToMedusaStatus(p24Status);
console.log(`P24 Status: ${p24Status} -> Medusa Status: ${medusaStatus}`);
return {
transactionDetails,
p24Status,
medusaStatus,
};
}
/**
* Maps P24 transaction status to Medusa payment status
* P24 statuses: 0 - no payment, 1 - advance payment, 2 - payment made, 3 - payment returned
* Medusa statuses: "authorized" | "captured" | "pending" | "requires_more" | "error" | "canceled"
*/
mapP24StatusToMedusaStatus(p24Status) {
switch (p24Status) {
case 0: // no payment
return "pending";
case 1: // advance payment
return "authorized";
case 2: // payment made
return "captured";
case 3: // payment returned
return "canceled";
default:
return "error";
}
}
buildError(message, error) {
return new Error(`${message}: ${error?.message || "Unknown error"}`.trim());
}
}
exports.default = P24Base;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicDI0LWJhc2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3ByemVsZXd5MjQvY29yZS9wMjQtYmFzZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLG9EQUE0QjtBQU01QixxREFJbUM7QUFTbkMsaURBQW9EO0FBdUJwRCxNQUFlLE9BQVEsU0FBUSwrQkFBbUM7SUFLaEUsTUFBTSxDQUFDLGVBQWUsQ0FBQyxPQUFtQjtRQUN4QyxJQUFJLENBQUMsSUFBQSxpQkFBUyxFQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDO1lBQ3BDLE1BQU0sSUFBSSxLQUFLLENBQUMsd0RBQXdELENBQUMsQ0FBQztRQUM1RSxDQUFDO1FBQ0QsSUFBSSxDQUFDLElBQUEsaUJBQVMsRUFBQyxPQUFPLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQztZQUMvQixNQUFNLElBQUksS0FBSyxDQUFDLG1EQUFtRCxDQUFDLENBQUM7UUFDdkUsQ0FBQztRQUNELElBQUksQ0FBQyxJQUFBLGlCQUFTLEVBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDaEMsTUFBTSxJQUFJLEtBQUssQ0FBQyxvREFBb0QsQ0FBQyxDQUFDO1FBQ3hFLENBQUM7UUFDRCxJQUFJLENBQUMsSUFBQSxpQkFBUyxFQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQzVCLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0RBQWdELENBQUMsQ0FBQztRQUNwRSxDQUFDO1FBQ0QsSUFBSSxDQUFDLElBQUEsaUJBQVMsRUFBQyxPQUFPLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUNyQyxNQUFNLElBQUksS0FBSyxDQUNiLHlEQUF5RCxDQUMxRCxDQUFDO1FBQ0osQ0FBQztRQUNELElBQUksQ0FBQyxJQUFBLGlCQUFTLEVBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUM7WUFDcEMsTUFBTSxJ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