@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
JavaScript
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