UNPKG

@sonatel-os/juf

Version:

The community SDK for Orange Money, SMS, Email & Sonatel APIs on the Orange Developer Platform.

350 lines (346 loc) 13.6 kB
import { authenticationService_default } from "./chunk-QDNKDHVC.js"; import { DECODE_QR_URI, GENERATE_PAYMENT_LINK_URI, GENERATE_QR_URI, buildAuthHeader, validate, validateUrls } from "./chunk-BXTDSY5Z.js"; import { CURRENCY_UNIT, envConfig, fromAxiosError, getApiUrl, logger, requester_default } from "./chunk-4T5F3RH2.js"; // src/payment/payment.structure.js import { define, object, string, number, optional, defaulted } from "superstruct"; var Merchant = object({ code: number(), sitename: string() }); var Bill = object({ amount: number(), reference: string() }); var Urls = object({ failed: optional(defaulted(string(), null)), cancel: optional(defaulted(string(), null)), success: optional(defaulted(string(), null)), callback: optional(defaulted(string(), null)) }); var CheckoutPaymentStructure = object({ merchant: Merchant, bill: Bill, urls: Urls }); var Metadata = define("object", (value) => { return typeof value === "object" && value !== null; }); var Validity = optional(number()); var QRCodePaymentStructure = object({ merchant: Merchant, bill: Bill, urls: optional(Urls), metadata: optional(Metadata), validity: Validity }); var QRCodeDecodePaymentStructure = object({ id: string() }); // src/payment/qrCodeDecoder.js var QRCodeDecoder = class _QRCodeDecoder { /** @private @type {import('axios').AxiosInstance} */ #client; /** @private @type {string} */ #authorization; /** @private */ #logger; /** * Creates a QRCodeDecoder instance with injectable dependencies. * * @param {object} deps - Dependencies. * @param {import('axios').AxiosInstance} deps.client - HTTP client instance. * @param {string} deps.authorization - Static SP authorization header value. * @param {object} deps.logger - Logger instance. */ constructor({ client, authorization, logger: logger2 }) { this.#client = client; this.#authorization = authorization; this.#logger = logger2; } /** * Factory method to initialize QRCodeDecoder with default dependencies. * @returns {QRCodeDecoder} An initialized instance. */ static init() { const apigeeConfig = envConfig.get("apigee"); return new _QRCodeDecoder({ client: requester_default.bootstrap({ baseURL: getApiUrl() }), authorization: apigeeConfig.decode_qr_sp_authorization, logger: logger.child("qr-decoder") }); } /** * Decodes a QR code by its ID. * * @async * @param {object} qrDetails - The details required to decode the QR code. * @param {string} qrDetails.id - The unique identifier of the QR code. * @returns {Promise<{ id: string, content: { merchantCode: string, merchantName: string, amount: number, reference: string, scope: string, type: string, metadata: object } }>} * @throws {import('../core/errors.js').ValidationError} When input validation fails. * @throws {import('../core/errors.js').ExternalServiceError} When the API request fails. * * @example * const { content } = await decoder.decode({ id: 'doyaT9sH3rGFph_ZuKIs' }); * console.log(content.amount, content.reference); */ async decode({ id }) { validate({ id }, QRCodeDecodePaymentStructure, "decodeQrCode"); try { const { data } = await this.#client.get(DECODE_QR_URI.replace(":id", id), { headers: { authorization: this.#authorization } }); return data; } catch (error) { if (error.code?.startsWith("JUF_")) throw error; this.#logger.error("QR code decoding failed", { qrId: id, status: error.response?.status }); throw fromAxiosError(error, "Error decoding QR code."); } } }; var qrCodeDecoder_default = QRCodeDecoder; // src/payment/paymentService.js var Payment = class _Payment { /** @private @type {import('axios').AxiosInstance} */ #client; /** @private @type {Authentication} */ #authService; /** @private */ #config; /** @private */ #logger; /** @private @type {QRCodeDecoder} */ #qrDecoder; /** * Creates a Payment instance with injectable dependencies. * * @param {object} deps - Dependencies for the payment service. * @param {Authentication} deps.authService - Authentication service instance. * @param {import('axios').AxiosInstance} deps.client - HTTP client instance. * @param {object} deps.config - Apigee configuration (for onProd, onPProd). * @param {object} deps.logger - Logger instance with error/warn/info/debug methods. * @param {QRCodeDecoder} [deps.qrDecoder] - Optional QR code decoder instance. */ constructor({ authService, client, config, logger: logger2, qrDecoder }) { this.#authService = authService; this.#client = client; this.#config = config; this.#logger = logger2; this.#qrDecoder = qrDecoder; } /** * Factory method to initialize Payment service with default dependencies. * @method init * @memberof Service\Payment * @param {object} [deps] - Optional shared dependencies. * @param {Authentication} [deps.authService] - Shared auth instance (avoids duplicate token fetches). * @returns {Payment} An initialized instance of Payment. */ static init({ authService } = {}) { const apigeeConfig = envConfig.get("apigee"); return new _Payment({ authService: authService || authenticationService_default.init(), client: requester_default.bootstrap({ baseURL: getApiUrl() }), config: apigeeConfig, logger: logger.child("payment"), qrDecoder: qrCodeDecoder_default.init() }); } /** * Retrieve Authorization headers from cached or fresh token. * * @private * @returns {Promise<{ Authorization: string }>} Authorization headers. */ async #getAuthHeaders() { const { access_token, token_type } = await this.#authService.debug(); return buildAuthHeader(access_token, token_type); } /** * Prepares a payment checkout for the OMPay payment gateway. * * @async * @method preparePaymentCheckout * @memberof Service\Payment * @param {object} paymentDetails - The payment details. * @param {object} paymentDetails.merchant - Merchant information. * @param {number} paymentDetails.merchant.code - The merchant code. * @param {string} paymentDetails.merchant.sitename - The merchant's site name. * @param {object} paymentDetails.bill - Bill information. * @param {number} paymentDetails.bill.amount - The amount to be paid. * @param {string} paymentDetails.bill.reference - The reference for the transaction. * @param {object} paymentDetails.urls - URLs for payment status. * @param {string} [paymentDetails.urls.failed] - URL to redirect if payment fails. * @param {string} [paymentDetails.urls.cancel] - URL to redirect if payment is canceled. * @param {string} [paymentDetails.urls.success] - URL to redirect upon successful payment. * @param {string} [paymentDetails.urls.callback] - Callback URL for payment updates. * @returns {Promise<{link: string, secret: number}>} The checkout link and secret. * @throws {import('../core/errors.js').ValidationError} When input validation fails. * @throws {import('../core/errors.js').ExternalServiceError} When the API request fails. * * @example * payment.preparePaymentCheckout({ * merchant: { code: 123456, sitename: 'your-sitename' }, * bill: { amount: 10, reference: '654321' }, * urls: { * failed: 'https://my.site/failed', * cancel: 'https://my.site/canceled', * success: 'https://my.site/success' * } * }) * .then(console.log) * .catch(console.log) */ async preparePaymentCheckout({ merchant, bill, urls }) { validate({ merchant, bill, urls }, CheckoutPaymentStructure, "preparePaymentCheckout"); validateUrls(urls); const { code: merchantCode, sitename } = merchant; const { reference, amount } = bill; const { success: successUrl, cancel: cancelUrl, failed: failedUrl, callback: callbackUrl } = urls; try { const { Authorization } = await this.#getAuthHeaders(); try { await this.createPaymentQRCode({ merchant, bill, urls, validity: 10 }); } catch { this.#logger.warn("QR code service unavailable, proceeding with USSD only", { reference }); } const payload = { merchantCode, sitename, amount, reference, onProd: Boolean(this.#config.onProd), onPProd: Boolean(this.#config.onPProd), urls: { ...cancelUrl && { cancelUrl }, ...failedUrl && { failedUrl }, ...successUrl && { successUrl }, ...callbackUrl && { callbackUrl } } }; const { data } = await this.#client.post(GENERATE_PAYMENT_LINK_URI, payload, { headers: { Authorization } }); return data; } catch (error) { if (error.code?.startsWith("JUF_")) throw error; this.#logger.error("Payment checkout failed", { reference, status: error.response?.status }); throw fromAxiosError(error, "Service not available. Check your setup and try again."); } } /** * Creates a QR code for a payment. * * @async * @method createPaymentQRCode * @memberof Service\Payment * @param {object} paymentDetails - The details for the QR code. * @param {object} paymentDetails.merchant - Merchant information. * @param {number} paymentDetails.merchant.code - The merchant code. * @param {string} paymentDetails.merchant.sitename - The merchant's site name. * @param {object} paymentDetails.bill - Bill information. * @param {number} paymentDetails.bill.amount - The amount to be paid. * @param {string} paymentDetails.bill.reference - The reference for the transaction. * @param {object} [paymentDetails.urls] - URLs for payment status. * @param {string} [paymentDetails.urls.failed] - URL to redirect if payment fails. * @param {string} [paymentDetails.urls.cancel] - URL to redirect if payment is canceled. * @param {string} [paymentDetails.urls.success] - URL to redirect upon successful payment. * @param {string} [paymentDetails.urls.callback] - Callback URL for payment updates. * @param {object} [paymentDetails.metadata] - Additional metadata for the QR code. * @param {number} [paymentDetails.validity] - Validity period for the QR code in seconds. * @returns {Promise<{ deepLink: string, deepLinks: { MAXIT: string, OM: string }, qrCode: string, validity: number, metadata: object, shortLink: string, qrId: string }>} * @throws {import('../core/errors.js').ValidationError} When input validation fails. * @throws {import('../core/errors.js').ExternalServiceError} When the API request fails. * * @example * payment.createPaymentQRCode({ * merchant: { code: 123456, sitename: 'your-sitename' }, * bill: { amount: 10, reference: '654321' }, * metadata: { myKey: 'value' }, * validity: 300 * }) * .then(console.log) * .catch(console.log) */ async createPaymentQRCode({ merchant, bill, urls = {}, metadata = {}, validity }) { validate({ merchant, bill, urls, metadata, validity }, QRCodePaymentStructure, "createPaymentQRCode"); validateUrls(urls); const { code: merchantCode, sitename } = merchant; const { reference, amount } = bill; const { success: callbackSuccessUrl, cancel: callbackCancelUrl, failed: callbackFailedUrl, callback: xCallbackUrl } = urls; try { const { Authorization } = await this.#getAuthHeaders(); const payload = { amount: { unit: CURRENCY_UNIT, value: amount }, callbackCancelUrl: callbackCancelUrl || callbackFailedUrl, callbackSuccessUrl, reference, code: merchantCode, metadata: { ...metadata, reference }, name: sitename, validity }; const headers = { Authorization, ...xCallbackUrl && { "X-Callback-Url": xCallbackUrl } }; const { data } = await this.#client.post(GENERATE_QR_URI, payload, { headers }); return data; } catch (error) { if (error.code?.startsWith("JUF_")) throw error; this.#logger.error("QR code generation failed", { reference, status: error.response?.status }); throw fromAxiosError(error, "QR code generation failed. Please try again."); } } /** * Decodes a QR code by its ID. * Delegates to {@link QRCodeDecoder} — a separate service that uses static SP authorization * instead of OAuth2 tokens. Only authorized applications can decode QR codes. * * @async * @method decodeQrCode * @memberof Service\Payment * @param {object} qrDetails - The details required to decode the QR code. * @param {string} qrDetails.id - The unique identifier of the QR code. * @returns {Promise<{ id: string, content: { merchantCode: string, merchantName: string, amount: number, reference: string, scope: string, type: string, metadata: object } }>} * @throws {import('../core/errors.js').ValidationError} When input validation fails. * @throws {import('../core/errors.js').ExternalServiceError} When the API request fails. * * @example * payment.decodeQrCode({ id: 'doyaT9sH3rGFph_ZuKIs' }) * .then(console.log) * .catch(console.log) */ async decodeQrCode({ id }) { return this.#qrDecoder.decode({ id }); } }; var paymentService_default = Payment; export { CheckoutPaymentStructure, QRCodePaymentStructure, QRCodeDecodePaymentStructure, qrCodeDecoder_default, paymentService_default }; //# sourceMappingURL=chunk-BLWGCKOL.js.map