UNPKG

@sahabaplus/moyasar

Version:

A comprehensive TypeScript SDK for integrating with the Moyasar payment gateway

285 lines 9.09 kB
import { PaymentValidation } from "./constants"; import { PaymentStatus, PaymentSource, CardScheme } from "./enums"; import { CreatePaymentSchema, UpdatePaymentSchema, RefundPaymentSchema, CapturePaymentSchema, PaymentSchema, listPaymentResponseSchema, } from "./validation/schemas"; export class PaymentUtils { /** * Validate payment creation request using Zod */ static validateCreatePaymentRequest(request) { const result = CreatePaymentSchema.safeParse(request); if (result.success) { return { success: true, data: result.data, errors: [], }; } const errors = result.error.issues.map((err) => { const path = err.path.length > 0 ? `${err.path.join(".")}: ` : ""; return `${path}${err.message}`; }); return { success: false, errors, }; } /** * Validate payment update request using Zod */ static validateUpdatePaymentRequest(request) { const result = UpdatePaymentSchema.safeParse(request); if (result.success) { return { success: true, data: result.data, errors: [], }; } const errors = result.error.issues.map((err) => { const path = err.path.length > 0 ? `${err.path.join(".")}: ` : ""; return `${path}${err.message}`; }); return { success: false, errors, }; } /** * Validate refund request using Zod */ static validateRefundRequest(request) { const result = RefundPaymentSchema.safeParse(request); if (result.success) { return { success: true, data: result.data, errors: [], }; } const errors = result.error.issues.map((err) => { const path = err.path.length > 0 ? `${err.path.join(".")}: ` : ""; return `${path}${err.message}`; }); return { success: false, errors, }; } /** * Validate capture request using Zod */ static validateCaptureRequest(request) { const result = CapturePaymentSchema.safeParse(request); if (result.success) { return { success: true, data: result.data, errors: [], }; } const errors = result.error.issues.map((err) => { const path = err.path.length > 0 ? `${err.path.join(".")}: ` : ""; return `${path}${err.message}`; }); return { success: false, errors, }; } /** * Format amount for display */ static formatAmount(amount, currency) { const divisors = { KWD: 1000, JPY: 1, SAR: 100, USD: 100, EUR: 100, }; const divisor = divisors[currency] ?? 100; const formattedAmount = (amount / divisor).toFixed(divisor === 1 ? 0 : 2); return `${formattedAmount} ${currency}`; } /** * Parse amount from display format to smallest unit */ static parseAmount(formattedAmount, currency) { const divisors = { KWD: 1000, JPY: 1, SAR: 100, USD: 100, EUR: 100, }; const amount = parseFloat(formattedAmount.replace(/[^\d.]/g, "")); const divisor = divisors[currency.toUpperCase()] || 100; return Math.round(amount * divisor); } /** * Check if payment is in a final state */ static isPaymentFinal(status) { const finalStatuses = [ PaymentStatus.PAID, PaymentStatus.FAILED, PaymentStatus.REFUNDED, PaymentStatus.CAPTURED, PaymentStatus.VOIDED, PaymentStatus.VERIFIED, ]; return finalStatuses.includes(status); } /** * Check if payment can be refunded */ static canRefundPayment(payment) { const refundableStatuses = [ PaymentStatus.PAID, PaymentStatus.CAPTURED, ]; // Check if status allows refund and if there's refundable amount const statusAllowed = refundableStatuses.includes(payment.status); const hasRefundableAmount = payment.captured - payment.refunded > 0; // Check if within refund timeout (if refunded_at exists, can't refund again) const notFullyRefunded = payment.refunded < payment.captured; return statusAllowed && hasRefundableAmount && notFullyRefunded; } /** * Check if payment can be captured */ static canCapturePayment(payment) { return payment.status === PaymentStatus.AUTHORIZED; } /** * Check if payment can be voided */ static canVoidPayment(payment) { return payment.status === PaymentStatus.AUTHORIZED; } /** * Get maximum refund amount for a payment */ static getMaxRefundAmount(payment) { return Math.max(0, payment.captured - payment.refunded); } /** * Get maximum capture amount for an authorized payment */ static getMaxCaptureAmount(payment) { if (payment.status !== PaymentStatus.AUTHORIZED) { return 0; } return payment.amount; } /** * Check if card scheme matches expected CVV length */ static validateCvcLength(cvc, scheme) { if (scheme === CardScheme.AMEX) { return cvc.length === PaymentValidation.AMEX_CVV_LENGTH; } return cvc.length === PaymentValidation.CVV_LENGTH; } /** * Mask card number for display (show first 6 and last 4 digits) */ static maskCardNumber(cardNumber) { if (cardNumber.length < 10) { return cardNumber; } const first6 = cardNumber.substring(0, 6); const last4 = cardNumber.substring(cardNumber.length - 4); const middle = "*".repeat(cardNumber.length - 10); return `${first6}${middle}${last4}`; } /** * Get last 4 digits of card number */ static getCardLast4(cardNumber) { return cardNumber.substring(cardNumber.length - 4); } /** * Build metadata query parameters for filtering */ static buildMetadataQuery(metadata) { const query = {}; Object.entries(metadata).forEach(([key, value]) => { query[`metadata[${key}]`] = value; }); return query; } /** * Sanitize payment description */ static sanitizeDescription(description) { return description .trim() .substring(0, PaymentValidation.DESCRIPTION_MAX_LENGTH); } /** * Generate idempotency key for payment */ static generateIdempotencyKey(prefix = "pay") { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substring(2, 8); return `${prefix}_${timestamp}_${random}`; } /** * Check if payment method requires 3DS by default */ static requires3DS(source) { // Credit cards typically require 3DS unless explicitly bypassed if (source.type === PaymentSource.CREDITCARD) return source["3ds"] !== false; // Undefined maps to true by default // Wallet payments usually handle their own authentication return false; } /** * Parse and validate a Payment response, ensuring all data types are correct */ static parsePayment(payment) { return PaymentSchema.parse(payment); } static parseListPaymentsResponse(response) { return listPaymentResponseSchema.parse(response); } /** * Parse and validate an array of Payment responses */ static parsePayments(payments) { if (!Array.isArray(payments)) { throw new Error("Expected payments to be an array"); } return payments.map((payment) => this.parsePayment(payment)); } /** * Safely parse a Payment response with error handling */ static safeParsePayment(payment) { try { const parsedPayment = this.parsePayment(payment); return { success: true, data: parsedPayment, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown parsing error", }; } } static parseCreatePaymentRequest(request) { return this.validateCreatePaymentRequest(request); } /** * Parse and validate an UpdatePaymentRequest, returning sanitized data */ static parseUpdatePaymentRequest(request) { return this.validateUpdatePaymentRequest(request); } } export * as PaymentSchemas from "./validation/schemas"; //# sourceMappingURL=utils.js.map