pesafy 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +62 -40
- package/dist/adapters/express.d.ts +5 -81
- package/dist/adapters/express.js +127 -1
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.d.ts +10 -74
- package/dist/adapters/fastify.js +85 -1
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/hono.d.ts +4 -88
- package/dist/adapters/hono.js +105 -1
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/nextjs.d.ts +28 -176
- package/dist/adapters/nextjs.js +166 -1
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/cli.mjs +2387 -112
- package/dist/cli.mjs.map +1 -0
- package/dist/errors.mjs +6 -1
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.ts +4864 -136
- package/dist/index.js +4197 -1
- package/dist/index.js.map +1 -0
- package/dist/phone.mjs +5 -1
- package/dist/phone.mjs.map +1 -0
- package/dist/rolldown-runtime.mjs +18 -0
- package/dist/route-definitions.js +3292 -0
- package/dist/route-definitions.js.map +1 -0
- package/dist/types.d.ts +924 -5
- package/package.json +6 -1
- package/dist/chunk.js +0 -1
- package/dist/encryption.mjs +0 -22
- package/dist/utils.mjs +0 -32
- package/dist/webhook-guard.js +0 -1
|
@@ -1,82 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { FastifyPluginAsync } from "fastify";
|
|
1
|
+
import { i as Mpesa, n as StkFailurePayload, r as StkSuccessPayload, t as MpesaAdapterConfig } from "../types.js";
|
|
2
|
+
import * as _$fastify from "fastify";
|
|
4
3
|
|
|
5
4
|
//#region src/adapters/fastify.d.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
requireHMAC?: boolean;
|
|
13
|
-
signatureHeader?: string;
|
|
14
|
-
routePrefix?: string;
|
|
15
|
-
balance?: {
|
|
16
|
-
resultUrl?: string;
|
|
17
|
-
queueTimeoutUrl?: string;
|
|
18
|
-
partyA?: string;
|
|
19
|
-
};
|
|
20
|
-
reversal?: {
|
|
21
|
-
resultUrl?: string;
|
|
22
|
-
queueTimeoutUrl?: string;
|
|
23
|
-
};
|
|
24
|
-
txStatus?: {
|
|
25
|
-
resultUrl?: string;
|
|
26
|
-
queueTimeoutUrl?: string;
|
|
27
|
-
};
|
|
28
|
-
tax?: {
|
|
29
|
-
resultUrl?: string;
|
|
30
|
-
queueTimeoutUrl?: string;
|
|
31
|
-
partyA?: string;
|
|
32
|
-
};
|
|
33
|
-
b2c?: {
|
|
34
|
-
resultUrl?: string;
|
|
35
|
-
queueTimeoutUrl?: string;
|
|
36
|
-
partyA?: string;
|
|
37
|
-
};
|
|
38
|
-
c2b?: {
|
|
39
|
-
shortCode?: string;
|
|
40
|
-
confirmationUrl?: string;
|
|
41
|
-
validationUrl?: string;
|
|
42
|
-
responseType?: 'Completed' | 'Cancelled';
|
|
43
|
-
apiVersion?: 'v1' | 'v2';
|
|
44
|
-
};
|
|
45
|
-
b2b?: {
|
|
46
|
-
receiverShortCode?: string;
|
|
47
|
-
callbackUrl?: string;
|
|
48
|
-
};
|
|
49
|
-
onStkSuccess?: (data: StkSuccessPayload) => Awaitable<void>;
|
|
50
|
-
onStkFailure?: (data: StkFailurePayload) => Awaitable<void>;
|
|
51
|
-
onC2BValidation?: (payload: C2BValidationPayload) => Awaitable<C2BValidationResponse>;
|
|
52
|
-
onC2BConfirmation?: (payload: C2BConfirmationPayload) => Awaitable<void>;
|
|
53
|
-
onAccountBalanceResult?: (result: AccountBalanceResult) => Awaitable<void>;
|
|
54
|
-
onReversalResult?: (result: ReversalResult) => Awaitable<void>;
|
|
55
|
-
onTxStatusResult?: (result: TransactionStatusResult) => Awaitable<void>;
|
|
56
|
-
onTaxResult?: (result: TaxRemittanceResult) => Awaitable<void>;
|
|
57
|
-
onB2BCheckoutCallback?: (cb: B2BExpressCheckoutCallback) => Awaitable<void>;
|
|
58
|
-
onB2CResult?: (result: B2CResult) => Awaitable<void>;
|
|
59
|
-
onB2CDisbursementResult?: (result: B2CDisbursementResult) => Awaitable<void>;
|
|
60
|
-
}
|
|
61
|
-
type Awaitable<T> = T | Promise<T>;
|
|
62
|
-
interface StkSuccessPayload {
|
|
63
|
-
receiptNumber: string | null;
|
|
64
|
-
amount: number | null;
|
|
65
|
-
phone: string | null;
|
|
66
|
-
checkoutRequestId: string;
|
|
67
|
-
merchantRequestId: string;
|
|
68
|
-
}
|
|
69
|
-
interface StkFailurePayload {
|
|
70
|
-
resultCode: number;
|
|
71
|
-
resultDesc: string;
|
|
72
|
-
checkoutRequestId: string;
|
|
73
|
-
merchantRequestId: string;
|
|
74
|
-
}
|
|
75
|
-
declare const registerMpesaPlugin: FastifyPluginAsync<MpesaFastifyConfig>;
|
|
5
|
+
type MpesaFastifyConfig = MpesaAdapterConfig;
|
|
6
|
+
/**
|
|
7
|
+
* Fastify plugin — register all M-PESA routes.
|
|
8
|
+
* Wrapped with fastify-plugin so decorators are visible to the parent scope.
|
|
9
|
+
*/
|
|
10
|
+
declare const registerMpesaPlugin: _$fastify.FastifyPluginAsync<MpesaAdapterConfig>;
|
|
76
11
|
declare module 'fastify' {
|
|
77
12
|
interface FastifyInstance {
|
|
78
13
|
mpesa: Mpesa;
|
|
79
14
|
}
|
|
80
15
|
}
|
|
81
16
|
//#endregion
|
|
82
|
-
export { MpesaFastifyConfig, StkFailurePayload, StkSuccessPayload, registerMpesaPlugin };
|
|
17
|
+
export { MpesaFastifyConfig, type StkFailurePayload, type StkSuccessPayload, registerMpesaPlugin };
|
|
18
|
+
//# sourceMappingURL=fastify.d.ts.map
|
package/dist/adapters/fastify.js
CHANGED
|
@@ -1 +1,85 @@
|
|
|
1
|
-
import{t as e}from"../chunk.js";import{A as t,D as n,E as r,O as i,S as a,T as o,_ as s,a as c,b as l,c as u,d,f,g as p,h as m,i as h,j as g,k as _,l as v,m as y,n as b,o as x,p as S,r as C,s as w,t as T,u as E,w as D,x as O,y as k}from"../webhook-guard.js";function A(e){let t=e.headers[`x-forwarded-for`];return typeof t==`string`?t.split(`,`)[0]?.trim()??``:e.ip??``}function j(e,t){return t instanceof g?e.status(t.statusCode??400).send({ok:!1,error:t.code,message:t.message}):e.status(500).send({ok:!1,error:`INTERNAL_ERROR`,message:`Unexpected error`})}function M(...e){return e.find(e=>e?.trim())??``}function N(e,t){e?.().catch(e=>console.error(`[pesafy/fastify] ${t} hook error:`,e))}const P=e(`fastify-plugin`)(async(e,P)=>{let F=new b(P),I=P.routePrefix??``;e.decorate(`mpesa`,F),e.post(`${I}/mpesa/stk/push`,async(e,t)=>{try{let{amount:t,phoneNumber:n,accountReference:r,transactionDesc:i,transactionType:a,partyB:o}=e.body;if(!t||t<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!n)throw new g({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});return{ok:!0,data:await F.stkPush({amount:t,phoneNumber:n,callbackUrl:P.callbackUrl,accountReference:r??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:i??`Payment`,...a===void 0?{}:{transactionType:a},...o===void 0?{}:{partyB:o}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/stk/query`,async(e,t)=>{try{let{checkoutRequestId:t}=e.body;if(!t)throw new g({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return{ok:!0,data:await F.stkQuery({checkoutRequestId:t})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/stk/callback`,async e=>{let t=e.rawBody??(e.body===void 0?void 0:JSON.stringify(e.body));await T(A(e),t,t=>{let n=e.headers[t.toLowerCase()];return Array.isArray(n)?n[0]:n},P);let n=e.body,r=n?.Body?.stkCallback;if(!r)return{ResultCode:0,ResultDesc:`Accepted`};if(x(n)){let t={receiptNumber:c(n),amount:C(n),phone:h(n),checkoutRequestId:r.CheckoutRequestID,merchantRequestId:r.MerchantRequestID};e.log.info(t,`[pesafy] STK success`),N(P.onStkSuccess?()=>Promise.resolve(P.onStkSuccess(t)):void 0,`onStkSuccess`)}else{let t={resultCode:r.ResultCode,resultDesc:r.ResultDesc,checkoutRequestId:r.CheckoutRequestID,merchantRequestId:r.MerchantRequestID};e.log.warn(t,`[pesafy] STK failure`),N(P.onStkFailure?()=>Promise.resolve(P.onStkFailure(t)):void 0,`onStkFailure`)}return{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/c2b/register`,async(e,t)=>{try{let{shortCode:t,confirmationUrl:n,validationUrl:r,responseType:i,apiVersion:a}=e.body,o=t??P.c2b?.shortCode??``,s=n??P.c2b?.confirmationUrl??``,c=r??P.c2b?.validationUrl??``;if(!o)throw new g({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!s)throw new g({code:`VALIDATION_ERROR`,message:`confirmationUrl is required`});if(!c)throw new g({code:`VALIDATION_ERROR`,message:`validationUrl is required`});return{ok:!0,data:await F.registerC2BUrls({shortCode:o,responseType:i??P.c2b?.responseType??`Completed`,confirmationUrl:s,validationUrl:c,apiVersion:a??P.c2b?.apiVersion??`v2`})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/c2b/simulate`,async(e,t)=>{try{let{commandId:t,amount:n,msisdn:r,billRefNumber:i,shortCode:a}=e.body;if(!t)throw new g({code:`VALIDATION_ERROR`,message:`commandId is required`});if(!n||n<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!r)throw new g({code:`VALIDATION_ERROR`,message:`msisdn is required`});return{ok:!0,data:await F.simulateC2B({shortCode:a??P.c2b?.shortCode??``,commandId:t,amount:n,msisdn:r,apiVersion:P.c2b?.apiVersion??`v2`,...i===void 0?{}:{billRefNumber:i}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/c2b/validation`,async e=>{let t=e.rawBody??(e.body===void 0?void 0:JSON.stringify(e.body));await T(A(e),t,t=>{let n=e.headers[t.toLowerCase()];return Array.isArray(n)?n[0]:n},P);let n=e.body;return P.onC2BValidation?await P.onC2BValidation(n):y()}),e.post(`${I}/mpesa/c2b/confirmation`,async e=>{let t=e.body;return e.log.info({txId:t.TransID,amount:t.TransAmount},`[pesafy] C2B confirmation`),N(P.onC2BConfirmation?()=>Promise.resolve(P.onC2BConfirmation(t)):void 0,`onC2BConfirmation`),{ResultCode:0,ResultDesc:`Success`}}),e.post(`${I}/mpesa/balance/query`,async(e,t)=>{try{let{partyA:t,identifierType:n,remarks:r,resultUrl:i,queueTimeoutUrl:a}=e.body;return{ok:!0,data:await F.accountBalance({partyA:t??P.balance?.partyA??``,identifierType:n??`4`,resultUrl:M(i,P.balance?.resultUrl,P.resultUrl),queueTimeOutUrl:M(a,P.balance?.queueTimeoutUrl,P.queueTimeoutUrl),...r===void 0?{}:{remarks:r}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/balance/result`,async e=>{let n=e.body;if(_(n)){let r=i(n);e.log.info(r?t(r):n,`[pesafy] Account balance result`)}else e.log.warn(n,`[pesafy] Account balance failed`);return N(P.onAccountBalanceResult?()=>Promise.resolve(P.onAccountBalanceResult(n)):void 0,`onAccountBalanceResult`),{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/qr/generate`,async(e,t)=>{try{return{ok:!0,data:await F.generateDynamicQR(e.body)}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/reversal/request`,async(e,t)=>{try{let{transactionId:t,receiverParty:n,amount:r,remarks:i,occasion:a,resultUrl:o,queueTimeoutUrl:s}=e.body;if(!t)throw new g({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!n)throw new g({code:`VALIDATION_ERROR`,message:`receiverParty is required`});if(!r||r<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});return{ok:!0,data:await F.reverseTransaction({transactionId:t,receiverParty:n,amount:r,resultUrl:M(o,P.reversal?.resultUrl,P.resultUrl),queueTimeOutUrl:M(s,P.reversal?.queueTimeoutUrl,P.queueTimeoutUrl),...i===void 0?{}:{remarks:i},...a===void 0?{}:{occasion:a}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/reversal/result`,async e=>{let t=e.body;return f(t)&&(S(t)?e.log.info({txId:d(t)},`[pesafy] Reversal success`):e.log.warn({result:t.Result.ResultDesc},`[pesafy] Reversal failed`),N(P.onReversalResult?()=>Promise.resolve(P.onReversalResult(t)):void 0,`onReversalResult`)),{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/tx-status/query`,async(e,t)=>{try{let{transactionId:t,originalConversationId:n,partyA:r,identifierType:i,remarks:a,occasion:o,resultUrl:s,queueTimeoutUrl:c}=e.body;return{ok:!0,data:await F.transactionStatus({...t===void 0?{}:{transactionId:t},...n===void 0?{}:{originalConversationId:n},partyA:r,identifierType:i,resultUrl:M(s,P.txStatus?.resultUrl,P.resultUrl),queueTimeOutUrl:M(c,P.txStatus?.queueTimeoutUrl,P.queueTimeoutUrl),...a===void 0?{}:{remarks:a},...o===void 0?{}:{occasion:o}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/tx-status/result`,async e=>{let t=e.body;return w(t)&&(u(t)?e.log.info({txId:t.Result.TransactionID},`[pesafy] Tx-status success`):e.log.warn({desc:t.Result.ResultDesc},`[pesafy] Tx-status failed`),N(P.onTxStatusResult?()=>Promise.resolve(P.onTxStatusResult(t)):void 0,`onTxStatusResult`)),{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/tax/remit`,async(e,t)=>{try{let{amount:t,partyA:n,accountReference:r,remarks:i,partyB:a,queueTimeoutUrl:o,resultUrl:s}=e.body;if(!t||t<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!r)throw new g({code:`VALIDATION_ERROR`,message:`accountReference (KRA PRN) is required`});return{ok:!0,data:await F.remitTax({amount:t,partyA:n??P.tax?.partyA??``,accountReference:r,resultUrl:M(s,P.tax?.resultUrl,P.resultUrl),queueTimeOutUrl:M(o,P.tax?.queueTimeoutUrl,P.queueTimeoutUrl),...a===void 0?{}:{partyB:a},...i===void 0?{}:{remarks:i}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/tax/result`,async e=>{let t=e.body;return v(t)&&(E(t)?e.log.info({txId:t.Result.TransactionID},`[pesafy] Tax success`):e.log.warn({desc:t.Result.ResultDesc},`[pesafy] Tax failed`),N(P.onTaxResult?()=>Promise.resolve(P.onTaxResult(t)):void 0,`onTaxResult`)),{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/b2b/checkout`,async(e,t)=>{try{let{primaryShortCode:t,receiverShortCode:n,amount:r,paymentRef:i,partnerName:a,callbackUrl:o,requestRefId:s}=e.body;if(!t)throw new g({code:`VALIDATION_ERROR`,message:`primaryShortCode is required`});if(!r||r<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});let c=n??P.b2b?.receiverShortCode??``,l=o??P.b2b?.callbackUrl??``;if(!c)throw new g({code:`VALIDATION_ERROR`,message:`receiverShortCode is required`});if(!l)throw new g({code:`VALIDATION_ERROR`,message:`callbackUrl is required`});return{ok:!0,data:await F.b2bExpressCheckout({primaryShortCode:t,receiverShortCode:c,amount:r,paymentRef:i,callbackUrl:l,partnerName:a,...s===void 0?{}:{requestRefId:s}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/b2b/callback`,async e=>{let t=e.body;if(!o(t))return e.log.warn(`[pesafy] Unknown B2B callback payload`),{ResultCode:0,ResultDesc:`Accepted`};let i=t;return n(i)?e.log.info({txId:D(i),amount:a(i)},`[pesafy] B2B success`):r(i)?e.log.warn(`[pesafy] B2B cancelled`):e.log.warn({result:i.resultDesc},`[pesafy] B2B failed`),N(P.onB2BCheckoutCallback?()=>Promise.resolve(P.onB2BCheckoutCallback(i)):void 0,`onB2BCheckoutCallback`),{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/b2c/payment`,async(e,t)=>{try{let{commandId:t,amount:n,partyA:r,partyB:i,accountReference:a,requester:o,remarks:s,resultUrl:c,queueTimeoutUrl:l}=e.body;if(t!==`BusinessPayToBulk`)throw new g({code:`VALIDATION_ERROR`,message:`commandId must be "BusinessPayToBulk"`});if(!n||n<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!i)throw new g({code:`VALIDATION_ERROR`,message:`partyB is required`});if(!a)throw new g({code:`VALIDATION_ERROR`,message:`accountReference is required`});return{ok:!0,data:await F.b2cPayment({commandId:t,amount:n,partyA:r??P.b2c?.partyA??``,partyB:i,accountReference:a,resultUrl:M(c,P.b2c?.resultUrl,P.resultUrl),queueTimeOutUrl:M(l,P.b2c?.queueTimeoutUrl,P.queueTimeoutUrl),...o===void 0?{}:{requester:o},...s===void 0?{}:{remarks:s}})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/b2c/result`,async e=>{let t=e.body;return l(t)&&(O(t)?e.log.info({txId:k(t),amount:s(t)},`[pesafy] B2C success`):e.log.warn({desc:t.Result.ResultDesc},`[pesafy] B2C failed`),N(P.onB2CResult?()=>Promise.resolve(P.onB2CResult(t)):void 0,`onB2CResult`)),{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/b2c/disburse`,async(e,t)=>{try{let{queueTimeoutUrl:t,resultUrl:n,...r}=e.body;return{ok:!0,data:await F.b2cDisbursement({...r,resultUrl:M(n,P.b2c?.resultUrl,P.resultUrl),queueTimeOutUrl:M(t,P.b2c?.queueTimeoutUrl,P.queueTimeoutUrl)})}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/b2c/disburse/result`,async e=>{let t=e.body;return m(t)&&(p(t)?e.log.info({txId:t.Result.TransactionID},`[pesafy] Disbursement success`):e.log.warn({desc:t.Result.ResultDesc},`[pesafy] Disbursement failed`),N(P.onB2CDisbursementResult?()=>Promise.resolve(P.onB2CDisbursementResult(t)):void 0,`onB2CDisbursementResult`)),{ResultCode:0,ResultDesc:`Accepted`}}),e.post(`${I}/mpesa/bills/optin`,async(e,t)=>{try{return{ok:!0,data:await F.billManagerOptIn(e.body)}}catch(e){return j(t,e)}}),e.patch(`${I}/mpesa/bills/optin`,async(e,t)=>{try{return{ok:!0,data:await F.updateOptIn(e.body)}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/bills/invoice`,async(e,t)=>{try{return{ok:!0,data:await F.sendInvoice(e.body)}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/bills/invoice/bulk`,async(e,t)=>{try{return{ok:!0,data:await F.sendBulkInvoices(e.body)}}catch(e){return j(t,e)}}),e.delete(`${I}/mpesa/bills/invoice`,async(e,t)=>{try{return{ok:!0,data:await F.cancelInvoice(e.body)}}catch(e){return j(t,e)}}),e.delete(`${I}/mpesa/bills/invoice/bulk`,async(e,t)=>{try{return{ok:!0,data:await F.cancelBulkInvoices(e.body)}}catch(e){return j(t,e)}}),e.post(`${I}/mpesa/bills/reconcile`,async(e,t)=>{try{return{ok:!0,data:await F.reconcilePayment(e.body)}}catch(e){return j(t,e)}}),e.get(`${I}/mpesa/health`,async()=>({ok:!0,environment:F.environment,ts:new Date().toISOString()}))},{fastify:`>=4.0.0`,name:`pesafy-mpesa`});export{P as registerMpesaPlugin};
|
|
1
|
+
import { a as PesafyError, i as Mpesa, n as getRoutePaths, r as createRouteHandlers, t as ROUTE_DEFINITIONS } from "../route-definitions.js";
|
|
2
|
+
import fp from "fastify-plugin";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/shared/mount-fastify.ts
|
|
5
|
+
/** All paths mounted by this adapter (parity anchor). */
|
|
6
|
+
const FASTIFY_ROUTE_PATHS = getRoutePaths();
|
|
7
|
+
function getIP(req) {
|
|
8
|
+
const xff = req.headers["x-forwarded-for"];
|
|
9
|
+
if (typeof xff === "string") return xff.split(",")[0]?.trim() ?? "";
|
|
10
|
+
return req.ip ?? "";
|
|
11
|
+
}
|
|
12
|
+
function getRawBody(req) {
|
|
13
|
+
const raw = req.rawBody;
|
|
14
|
+
if (typeof raw === "string") return raw;
|
|
15
|
+
if (req.body !== void 0) return JSON.stringify(req.body);
|
|
16
|
+
}
|
|
17
|
+
function sendErr(reply, err) {
|
|
18
|
+
if (err instanceof PesafyError) return reply.status(err.statusCode ?? 400).send({
|
|
19
|
+
ok: false,
|
|
20
|
+
error: err.code,
|
|
21
|
+
message: err.message
|
|
22
|
+
});
|
|
23
|
+
return reply.status(500).send({
|
|
24
|
+
ok: false,
|
|
25
|
+
error: "INTERNAL_ERROR",
|
|
26
|
+
message: "Unexpected error"
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function sendHandlerResult(reply, result, routeId) {
|
|
30
|
+
if (routeId === "health") return reply.send(result.body);
|
|
31
|
+
if (result.type === "daraja") return reply.send(result.body);
|
|
32
|
+
return reply.send({
|
|
33
|
+
ok: true,
|
|
34
|
+
data: result.body
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function buildContext(req) {
|
|
38
|
+
const rawBody = getRawBody(req);
|
|
39
|
+
return {
|
|
40
|
+
body: req.body,
|
|
41
|
+
...rawBody !== void 0 ? { rawBody } : {},
|
|
42
|
+
requestIP: getIP(req),
|
|
43
|
+
getHeader: (name) => {
|
|
44
|
+
const v = req.headers[name.toLowerCase()];
|
|
45
|
+
return Array.isArray(v) ? v[0] : v;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const mpesaPlugin = async (app, config) => {
|
|
50
|
+
const mpesa = new Mpesa(config);
|
|
51
|
+
const prefix = config.routePrefix ?? "";
|
|
52
|
+
const handlers = createRouteHandlers(mpesa, config);
|
|
53
|
+
app.decorate("mpesa", mpesa);
|
|
54
|
+
for (const route of ROUTE_DEFINITIONS) {
|
|
55
|
+
const path = `${prefix}${route.path}`;
|
|
56
|
+
const handler = handlers[route.id];
|
|
57
|
+
app.route({
|
|
58
|
+
method: route.method,
|
|
59
|
+
url: path,
|
|
60
|
+
handler: async (req, reply) => {
|
|
61
|
+
try {
|
|
62
|
+
return sendHandlerResult(reply, await handler(buildContext(req)), route.id);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
return sendErr(reply, e);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const registerMpesaRoutes = fp(mpesaPlugin, {
|
|
71
|
+
fastify: ">=4.0.0",
|
|
72
|
+
name: "pesafy-mpesa"
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/adapters/fastify.ts
|
|
77
|
+
/**
|
|
78
|
+
* Fastify plugin — register all M-PESA routes.
|
|
79
|
+
* Wrapped with fastify-plugin so decorators are visible to the parent scope.
|
|
80
|
+
*/
|
|
81
|
+
const registerMpesaPlugin = registerMpesaRoutes;
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { registerMpesaPlugin };
|
|
85
|
+
//# sourceMappingURL=fastify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastify.js","names":[],"sources":["../../src/adapters/shared/mount-fastify.ts","../../src/adapters/fastify.ts"],"sourcesContent":["import type { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify'\nimport fp from 'fastify-plugin'\nimport { Mpesa } from '../../mpesa'\nimport { PesafyError } from '../../utils/errors'\nimport { createRouteHandlers } from './handlers'\nimport { ROUTE_DEFINITIONS, getRoutePaths } from './route-definitions'\n\n/** All paths mounted by this adapter (parity anchor). */\nexport const FASTIFY_ROUTE_PATHS = getRoutePaths()\nimport type { HandlerContext, HandlerResult, MpesaAdapterConfig } from './types'\n\nfunction getIP(req: FastifyRequest): string {\n const xff = req.headers['x-forwarded-for']\n if (typeof xff === 'string') return xff.split(',')[0]?.trim() ?? ''\n return req.ip ?? ''\n}\n\nfunction getRawBody(req: FastifyRequest): string | undefined {\n const raw = (req as { rawBody?: string }).rawBody\n if (typeof raw === 'string') return raw\n if (req.body !== undefined) return JSON.stringify(req.body)\n return undefined\n}\n\nfunction sendErr(reply: FastifyReply, err: unknown): FastifyReply {\n if (err instanceof PesafyError) {\n return reply\n .status(err.statusCode ?? 400)\n .send({ ok: false, error: err.code, message: err.message })\n }\n return reply.status(500).send({ ok: false, error: 'INTERNAL_ERROR', message: 'Unexpected error' })\n}\n\nfunction sendHandlerResult(\n reply: FastifyReply,\n result: HandlerResult,\n routeId: string,\n): FastifyReply | unknown {\n if (routeId === 'health') {\n return reply.send(result.body)\n }\n if (result.type === 'daraja') {\n return reply.send(result.body)\n }\n return reply.send({ ok: true, data: result.body })\n}\n\nfunction buildContext(req: FastifyRequest): HandlerContext {\n const rawBody = getRawBody(req)\n return {\n body: req.body,\n ...(rawBody !== undefined ? { rawBody } : {}),\n requestIP: getIP(req),\n getHeader: (name) => {\n const v = req.headers[name.toLowerCase()]\n return Array.isArray(v) ? v[0] : v\n },\n }\n}\n\nconst mpesaPlugin: FastifyPluginAsync<MpesaAdapterConfig> = async (\n app: FastifyInstance,\n config: MpesaAdapterConfig,\n) => {\n const mpesa = new Mpesa(config)\n const prefix = config.routePrefix ?? ''\n const handlers = createRouteHandlers(mpesa, config)\n\n app.decorate('mpesa', mpesa)\n\n for (const route of ROUTE_DEFINITIONS) {\n const path = `${prefix}${route.path}`\n const handler = handlers[route.id]\n\n app.route({\n method: route.method,\n url: path,\n handler: async (req, reply) => {\n try {\n const result = await handler(buildContext(req))\n return sendHandlerResult(reply, result, route.id)\n } catch (e) {\n return sendErr(reply, e)\n }\n },\n })\n }\n}\n\nexport const registerMpesaRoutes = fp(mpesaPlugin, {\n fastify: '>=4.0.0',\n name: 'pesafy-mpesa',\n})\n","/**\n * @file src/adapters/fastify.ts\n * Fastify adapter for pesafy — full M-PESA Daraja surface.\n *\n * Usage:\n * import Fastify from 'fastify'\n * import { registerMpesaPlugin } from 'pesafy/adapters/fastify'\n *\n * const app = Fastify()\n * await app.register(registerMpesaPlugin, config)\n */\n\nimport { Mpesa } from '../mpesa'\nimport { registerMpesaRoutes } from './shared/mount-fastify'\nimport type { MpesaAdapterConfig, StkFailurePayload, StkSuccessPayload } from './shared/types'\n\nexport type MpesaFastifyConfig = MpesaAdapterConfig\nexport type { StkSuccessPayload, StkFailurePayload }\n\n/**\n * Fastify plugin — register all M-PESA routes.\n * Wrapped with fastify-plugin so decorators are visible to the parent scope.\n */\nexport const registerMpesaPlugin = registerMpesaRoutes\n\ndeclare module 'fastify' {\n interface FastifyInstance {\n mpesa: Mpesa\n }\n}\n"],"mappings":";;;;;AAQA,MAAa,sBAAsB,eAAe;AAGlD,SAAS,MAAM,KAA6B;CAC1C,MAAM,MAAM,IAAI,QAAQ;AACxB,KAAI,OAAO,QAAQ,SAAU,QAAO,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI;AACjE,QAAO,IAAI,MAAM;;AAGnB,SAAS,WAAW,KAAyC;CAC3D,MAAM,MAAO,IAA6B;AAC1C,KAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,KAAI,IAAI,SAAS,OAAW,QAAO,KAAK,UAAU,IAAI,KAAK;;AAI7D,SAAS,QAAQ,OAAqB,KAA4B;AAChE,KAAI,eAAe,YACjB,QAAO,MACJ,OAAO,IAAI,cAAc,IAAI,CAC7B,KAAK;EAAE,IAAI;EAAO,OAAO,IAAI;EAAM,SAAS,IAAI;EAAS,CAAC;AAE/D,QAAO,MAAM,OAAO,IAAI,CAAC,KAAK;EAAE,IAAI;EAAO,OAAO;EAAkB,SAAS;EAAoB,CAAC;;AAGpG,SAAS,kBACP,OACA,QACA,SACwB;AACxB,KAAI,YAAY,SACd,QAAO,MAAM,KAAK,OAAO,KAAK;AAEhC,KAAI,OAAO,SAAS,SAClB,QAAO,MAAM,KAAK,OAAO,KAAK;AAEhC,QAAO,MAAM,KAAK;EAAE,IAAI;EAAM,MAAM,OAAO;EAAM,CAAC;;AAGpD,SAAS,aAAa,KAAqC;CACzD,MAAM,UAAU,WAAW,IAAI;AAC/B,QAAO;EACL,MAAM,IAAI;EACV,GAAI,YAAY,SAAY,EAAE,SAAS,GAAG,EAAE;EAC5C,WAAW,MAAM,IAAI;EACrB,YAAY,SAAS;GACnB,MAAM,IAAI,IAAI,QAAQ,KAAK,aAAa;AACxC,UAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,KAAK;;EAEpC;;AAGH,MAAM,cAAsD,OAC1D,KACA,WACG;CACH,MAAM,QAAQ,IAAI,MAAM,OAAO;CAC/B,MAAM,SAAS,OAAO,eAAe;CACrC,MAAM,WAAW,oBAAoB,OAAO,OAAO;AAEnD,KAAI,SAAS,SAAS,MAAM;AAE5B,MAAK,MAAM,SAAS,mBAAmB;EACrC,MAAM,OAAO,GAAG,SAAS,MAAM;EAC/B,MAAM,UAAU,SAAS,MAAM;AAE/B,MAAI,MAAM;GACR,QAAQ,MAAM;GACd,KAAK;GACL,SAAS,OAAO,KAAK,UAAU;AAC7B,QAAI;AAEF,YAAO,kBAAkB,OADV,MAAM,QAAQ,aAAa,IAAI,CAAC,EACP,MAAM,GAAG;aAC1C,GAAG;AACV,YAAO,QAAQ,OAAO,EAAE;;;GAG7B,CAAC;;;AAIN,MAAa,sBAAsB,GAAG,aAAa;CACjD,SAAS;CACT,MAAM;CACP,CAAC;;;;;;;;ACrEF,MAAa,sBAAsB"}
|
package/dist/adapters/hono.d.ts
CHANGED
|
@@ -1,100 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Mpesa } from "../index.js";
|
|
1
|
+
import { i as Mpesa, n as StkFailurePayload, r as StkSuccessPayload, t as MpesaAdapterConfig } from "../types.js";
|
|
3
2
|
import { Hono, MiddlewareHandler } from "hono";
|
|
4
3
|
|
|
5
4
|
//#region src/adapters/hono.d.ts
|
|
6
|
-
|
|
7
|
-
callbackUrl: string;
|
|
8
|
-
resultUrl?: string;
|
|
9
|
-
queueTimeoutUrl?: string;
|
|
10
|
-
skipIPCheck?: boolean;
|
|
11
|
-
webhookSecret?: string;
|
|
12
|
-
requireHMAC?: boolean;
|
|
13
|
-
signatureHeader?: string;
|
|
14
|
-
balance?: {
|
|
15
|
-
resultUrl?: string;
|
|
16
|
-
queueTimeoutUrl?: string;
|
|
17
|
-
partyA?: string;
|
|
18
|
-
};
|
|
19
|
-
reversal?: {
|
|
20
|
-
resultUrl?: string;
|
|
21
|
-
queueTimeoutUrl?: string;
|
|
22
|
-
};
|
|
23
|
-
txStatus?: {
|
|
24
|
-
resultUrl?: string;
|
|
25
|
-
queueTimeoutUrl?: string;
|
|
26
|
-
};
|
|
27
|
-
tax?: {
|
|
28
|
-
resultUrl?: string;
|
|
29
|
-
queueTimeoutUrl?: string;
|
|
30
|
-
partyA?: string;
|
|
31
|
-
};
|
|
32
|
-
b2c?: {
|
|
33
|
-
resultUrl?: string;
|
|
34
|
-
queueTimeoutUrl?: string;
|
|
35
|
-
partyA?: string;
|
|
36
|
-
};
|
|
37
|
-
c2b?: {
|
|
38
|
-
shortCode?: string;
|
|
39
|
-
confirmationUrl?: string;
|
|
40
|
-
validationUrl?: string;
|
|
41
|
-
responseType?: 'Completed' | 'Cancelled';
|
|
42
|
-
apiVersion?: 'v1' | 'v2';
|
|
43
|
-
};
|
|
44
|
-
b2b?: {
|
|
45
|
-
receiverShortCode?: string;
|
|
46
|
-
callbackUrl?: string;
|
|
47
|
-
};
|
|
48
|
-
onStkSuccess?: (data: StkSuccessPayload) => Awaitable<void>;
|
|
49
|
-
onStkFailure?: (data: StkFailurePayload) => Awaitable<void>;
|
|
50
|
-
onC2BValidation?: (payload: C2BValidationPayload) => Awaitable<C2BValidationResponse>;
|
|
51
|
-
onC2BConfirmation?: (payload: C2BConfirmationPayload) => Awaitable<void>;
|
|
52
|
-
onAccountBalanceResult?: (result: AccountBalanceResult) => Awaitable<void>;
|
|
53
|
-
onReversalResult?: (result: ReversalResult) => Awaitable<void>;
|
|
54
|
-
onTxStatusResult?: (result: TransactionStatusResult) => Awaitable<void>;
|
|
55
|
-
onTaxResult?: (result: TaxRemittanceResult) => Awaitable<void>;
|
|
56
|
-
onB2BCheckoutCallback?: (cb: B2BExpressCheckoutCallback) => Awaitable<void>;
|
|
57
|
-
onB2CResult?: (result: B2CResult) => Awaitable<void>;
|
|
58
|
-
onB2CDisbursementResult?: (result: B2CDisbursementResult) => Awaitable<void>;
|
|
59
|
-
}
|
|
60
|
-
type Awaitable<T> = T | Promise<T>;
|
|
61
|
-
interface StkSuccessPayload {
|
|
62
|
-
receiptNumber: string | null;
|
|
63
|
-
amount: number | null;
|
|
64
|
-
phone: string | null;
|
|
65
|
-
checkoutRequestId: string;
|
|
66
|
-
merchantRequestId: string;
|
|
67
|
-
}
|
|
68
|
-
interface StkFailurePayload {
|
|
69
|
-
resultCode: number;
|
|
70
|
-
resultDesc: string;
|
|
71
|
-
checkoutRequestId: string;
|
|
72
|
-
merchantRequestId: string;
|
|
73
|
-
}
|
|
5
|
+
type MpesaHonoConfig = MpesaAdapterConfig;
|
|
74
6
|
/**
|
|
75
7
|
* Creates a Hono app with all M-PESA routes.
|
|
76
8
|
* Mount it on your main app with `app.route('/api', createMpesaHono(config))`.
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* import { Hono } from 'hono'
|
|
80
|
-
* import { createMpesaHono } from 'pesafy/adapters/hono'
|
|
81
|
-
*
|
|
82
|
-
* const api = createMpesaHono({
|
|
83
|
-
* consumerKey: env.MPESA_CONSUMER_KEY,
|
|
84
|
-
* consumerSecret: env.MPESA_CONSUMER_SECRET,
|
|
85
|
-
* environment: 'sandbox',
|
|
86
|
-
* callbackUrl: 'https://yourdomain.com/api/mpesa/stk/callback',
|
|
87
|
-
* lipaNaMpesaShortCode: '174379',
|
|
88
|
-
* lipaNaMpesaPassKey: env.MPESA_PASSKEY,
|
|
89
|
-
* })
|
|
90
|
-
*
|
|
91
|
-
* const app = new Hono()
|
|
92
|
-
* app.route('/api', api)
|
|
93
9
|
*/
|
|
94
10
|
declare function createMpesaHono(config: MpesaHonoConfig): Hono;
|
|
95
11
|
/**
|
|
96
12
|
* Returns a Hono middleware that injects an Mpesa instance into context.
|
|
97
|
-
* Use when you want to call the SDK directly inside your own route handlers.
|
|
98
13
|
*/
|
|
99
14
|
declare function mpesaMiddleware(config: MpesaHonoConfig): MiddlewareHandler;
|
|
100
15
|
declare module 'hono' {
|
|
@@ -103,4 +18,5 @@ declare module 'hono' {
|
|
|
103
18
|
}
|
|
104
19
|
}
|
|
105
20
|
//#endregion
|
|
106
|
-
export { MpesaHonoConfig, StkFailurePayload, StkSuccessPayload, createMpesaHono, mpesaMiddleware };
|
|
21
|
+
export { MpesaHonoConfig, type StkFailurePayload, type StkSuccessPayload, createMpesaHono, createMpesaHono as createMpesaHonoRouter, mpesaMiddleware };
|
|
22
|
+
//# sourceMappingURL=hono.d.ts.map
|
package/dist/adapters/hono.js
CHANGED
|
@@ -1 +1,105 @@
|
|
|
1
|
-
import{t as e}from"../chunk.js";import{A as t,D as n,E as r,O as i,S as a,T as o,_ as s,a as c,b as l,c as u,d,f,g as p,h as m,i as h,j as g,k as _,l as v,m as y,n as b,o as x,p as S,r as C,s as w,t as T,u as E,w as D,x as O,y as k}from"../webhook-guard.js";function A(e){return e.req.header(`x-forwarded-for`)?.split(`,`)[0]?.trim()??e.req.header(`cf-connecting-ip`)??e.req.header(`x-real-ip`)??``}function j(e,t){return e.json({ok:!0,data:t})}function M(e,t){return t instanceof g?e.json({ok:!1,error:t.code,message:t.message},t.statusCode??400):e.json({ok:!1,error:`INTERNAL_ERROR`,message:`Unexpected error`},500)}function N(...e){return e.find(e=>e?.trim())??``}function P(e,t){e?.().catch(e=>console.error(`[pesafy/hono] ${t} hook error:`,e))}function F(F){let{Hono:I}=e(`hono`),L=new I,R=new b(F);return L.use(`*`,async(e,t)=>{e.set(`mpesa`,R),await t()}),L.post(`/mpesa/stk/push`,async e=>{try{let{amount:t,phoneNumber:n,accountReference:r,transactionDesc:i,transactionType:a,partyB:o}=await e.req.json();if(!t||t<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!n)throw new g({code:`VALIDATION_ERROR`,message:`phoneNumber is required`});return j(e,await R.stkPush({amount:t,phoneNumber:n,callbackUrl:F.callbackUrl,accountReference:r??`REF-${Date.now().toString(36).toUpperCase()}`,transactionDesc:i??`Payment`,...a===void 0?{}:{transactionType:a},...o===void 0?{}:{partyB:o}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/stk/query`,async e=>{try{let{checkoutRequestId:t}=await e.req.json();if(!t)throw new g({code:`VALIDATION_ERROR`,message:`checkoutRequestId is required`});return j(e,await R.stkQuery({checkoutRequestId:t}))}catch(t){return M(e,t)}}),L.post(`/mpesa/stk/callback`,async e=>{let t=await e.req.text();await T(A(e),t,t=>e.req.header(t),F);let n=JSON.parse(t),r=n?.Body?.stkCallback;if(!r)return e.json({ResultCode:0,ResultDesc:`Accepted`});if(x(n)){let e={receiptNumber:c(n),amount:C(n),phone:h(n),checkoutRequestId:r.CheckoutRequestID,merchantRequestId:r.MerchantRequestID};console.info(`[pesafy/hono] STK success:`,e),P(F.onStkSuccess?()=>Promise.resolve(F.onStkSuccess(e)):void 0,`onStkSuccess`)}else{let e={resultCode:r.ResultCode,resultDesc:r.ResultDesc,checkoutRequestId:r.CheckoutRequestID,merchantRequestId:r.MerchantRequestID};console.warn(`[pesafy/hono] STK failure:`,e),P(F.onStkFailure?()=>Promise.resolve(F.onStkFailure(e)):void 0,`onStkFailure`)}return e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/c2b/register`,async e=>{try{let{shortCode:t,confirmationUrl:n,validationUrl:r,responseType:i,apiVersion:a}=await e.req.json(),o=t??F.c2b?.shortCode??``,s=n??F.c2b?.confirmationUrl??``,c=r??F.c2b?.validationUrl??``;if(!o)throw new g({code:`VALIDATION_ERROR`,message:`shortCode is required`});if(!s)throw new g({code:`VALIDATION_ERROR`,message:`confirmationUrl is required`});if(!c)throw new g({code:`VALIDATION_ERROR`,message:`validationUrl is required`});return j(e,await R.registerC2BUrls({shortCode:o,responseType:i??F.c2b?.responseType??`Completed`,confirmationUrl:s,validationUrl:c,apiVersion:a??F.c2b?.apiVersion??`v2`}))}catch(t){return M(e,t)}}),L.post(`/mpesa/c2b/simulate`,async e=>{try{let{commandId:t,amount:n,msisdn:r,billRefNumber:i,shortCode:a}=await e.req.json();if(!t)throw new g({code:`VALIDATION_ERROR`,message:`commandId is required`});if(!n||n<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!r)throw new g({code:`VALIDATION_ERROR`,message:`msisdn is required`});return j(e,await R.simulateC2B({shortCode:a??F.c2b?.shortCode??``,commandId:t,amount:n,msisdn:r,apiVersion:F.c2b?.apiVersion??`v2`,...i===void 0?{}:{billRefNumber:i}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/c2b/validation`,async e=>{let t=await e.req.text();await T(A(e),t,t=>e.req.header(t),F);let n=JSON.parse(t),r=F.onC2BValidation?await F.onC2BValidation(n):y();return e.json(r)}),L.post(`/mpesa/c2b/confirmation`,async e=>{let t=await e.req.json();return console.info(`[pesafy/hono] C2B confirmation:`,{txId:t.TransID,amount:t.TransAmount}),P(F.onC2BConfirmation?()=>Promise.resolve(F.onC2BConfirmation(t)):void 0,`onC2BConfirmation`),e.json({ResultCode:0,ResultDesc:`Success`})}),L.post(`/mpesa/qr/generate`,async e=>{try{return j(e,await R.generateDynamicQR(await e.req.json()))}catch(t){return M(e,t)}}),L.post(`/mpesa/balance/query`,async e=>{try{let t=await e.req.json();return j(e,await R.accountBalance({partyA:t.partyA??F.balance?.partyA??``,identifierType:t.identifierType??`4`,resultUrl:N(t.resultUrl,F.balance?.resultUrl,F.resultUrl),queueTimeOutUrl:N(t.queueTimeoutUrl,F.balance?.queueTimeoutUrl,F.queueTimeoutUrl),...t.remarks===void 0?{}:{remarks:t.remarks}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/balance/result`,async e=>{let n=await e.req.json();if(_(n)){let e=i(n);console.info(`[pesafy/hono] Balance result:`,e?t(e):n)}else console.warn(`[pesafy/hono] Balance failed:`,n);return P(F.onAccountBalanceResult?()=>Promise.resolve(F.onAccountBalanceResult(n)):void 0,`onAccountBalanceResult`),e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/reversal/request`,async e=>{try{let t=await e.req.json();if(!t.transactionId)throw new g({code:`VALIDATION_ERROR`,message:`transactionId is required`});if(!t.receiverParty)throw new g({code:`VALIDATION_ERROR`,message:`receiverParty is required`});if(!t.amount||t.amount<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});return j(e,await R.reverseTransaction({transactionId:t.transactionId,receiverParty:t.receiverParty,amount:t.amount,resultUrl:N(t.resultUrl,F.reversal?.resultUrl,F.resultUrl),queueTimeOutUrl:N(t.queueTimeoutUrl,F.reversal?.queueTimeoutUrl,F.queueTimeoutUrl),...t.remarks===void 0?{}:{remarks:t.remarks},...t.occasion===void 0?{}:{occasion:t.occasion}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/reversal/result`,async e=>{let t=await e.req.json();return f(t)&&(S(t)?console.info(`[pesafy/hono] Reversal success:`,d(t)):console.warn(`[pesafy/hono] Reversal failed:`,t.Result.ResultDesc),P(F.onReversalResult?()=>Promise.resolve(F.onReversalResult(t)):void 0,`onReversalResult`)),e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/tx-status/query`,async e=>{try{let t=await e.req.json();return j(e,await R.transactionStatus({...t.transactionId===void 0?{}:{transactionId:t.transactionId},...t.originalConversationId===void 0?{}:{originalConversationId:t.originalConversationId},partyA:t.partyA,identifierType:t.identifierType,resultUrl:N(t.resultUrl,F.txStatus?.resultUrl,F.resultUrl),queueTimeOutUrl:N(t.queueTimeoutUrl,F.txStatus?.queueTimeoutUrl,F.queueTimeoutUrl),...t.remarks===void 0?{}:{remarks:t.remarks},...t.occasion===void 0?{}:{occasion:t.occasion}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/tx-status/result`,async e=>{let t=await e.req.json();return w(t)&&(u(t)?console.info(`[pesafy/hono] Tx-status success:`,t.Result.TransactionID):console.warn(`[pesafy/hono] Tx-status failed:`,t.Result.ResultDesc),P(F.onTxStatusResult?()=>Promise.resolve(F.onTxStatusResult(t)):void 0,`onTxStatusResult`)),e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/tax/remit`,async e=>{try{let t=await e.req.json();if(!t.amount||t.amount<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!t.accountReference)throw new g({code:`VALIDATION_ERROR`,message:`accountReference (KRA PRN) is required`});return j(e,await R.remitTax({amount:t.amount,partyA:t.partyA??F.tax?.partyA??``,accountReference:t.accountReference,resultUrl:N(t.resultUrl,F.tax?.resultUrl,F.resultUrl),queueTimeOutUrl:N(t.queueTimeoutUrl,F.tax?.queueTimeoutUrl,F.queueTimeoutUrl),...t.partyB===void 0?{}:{partyB:t.partyB},...t.remarks===void 0?{}:{remarks:t.remarks}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/tax/result`,async e=>{let t=await e.req.json();return v(t)&&(E(t)?console.info(`[pesafy/hono] Tax success:`,t.Result.TransactionID):console.warn(`[pesafy/hono] Tax failed:`,t.Result.ResultDesc),P(F.onTaxResult?()=>Promise.resolve(F.onTaxResult(t)):void 0,`onTaxResult`)),e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/b2b/checkout`,async e=>{try{let t=await e.req.json();if(!t.primaryShortCode)throw new g({code:`VALIDATION_ERROR`,message:`primaryShortCode is required`});if(!t.amount||t.amount<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});let n=t.receiverShortCode??F.b2b?.receiverShortCode??``,r=t.callbackUrl??F.b2b?.callbackUrl??``;if(!n)throw new g({code:`VALIDATION_ERROR`,message:`receiverShortCode is required`});if(!r)throw new g({code:`VALIDATION_ERROR`,message:`callbackUrl is required`});return j(e,await R.b2bExpressCheckout({primaryShortCode:t.primaryShortCode,receiverShortCode:n,amount:t.amount,paymentRef:t.paymentRef,callbackUrl:r,partnerName:t.partnerName,...t.requestRefId===void 0?{}:{requestRefId:t.requestRefId}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/b2b/callback`,async e=>{let t=await e.req.json();if(!o(t))return console.warn(`[pesafy/hono] Unknown B2B callback`),e.json({ResultCode:0,ResultDesc:`Accepted`});let i=t;return n(i)?console.info(`[pesafy/hono] B2B success:`,{txId:D(i),amount:a(i)}):r(i)?console.warn(`[pesafy/hono] B2B cancelled`):console.warn(`[pesafy/hono] B2B failed:`,i.resultDesc),P(F.onB2BCheckoutCallback?()=>Promise.resolve(F.onB2BCheckoutCallback(i)):void 0,`onB2BCheckoutCallback`),e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/b2c/payment`,async e=>{try{let t=await e.req.json();if(t.commandId!==`BusinessPayToBulk`)throw new g({code:`VALIDATION_ERROR`,message:`commandId must be "BusinessPayToBulk"`});if(!t.amount||t.amount<=0)throw new g({code:`VALIDATION_ERROR`,message:`amount must be > 0`});if(!t.partyB)throw new g({code:`VALIDATION_ERROR`,message:`partyB is required`});if(!t.accountReference)throw new g({code:`VALIDATION_ERROR`,message:`accountReference is required`});return j(e,await R.b2cPayment({commandId:`BusinessPayToBulk`,amount:t.amount,partyA:t.partyA??F.b2c?.partyA??``,partyB:t.partyB,accountReference:t.accountReference,resultUrl:N(t.resultUrl,F.b2c?.resultUrl,F.resultUrl),queueTimeOutUrl:N(t.queueTimeoutUrl,F.b2c?.queueTimeoutUrl,F.queueTimeoutUrl),...t.requester===void 0?{}:{requester:t.requester},...t.remarks===void 0?{}:{remarks:t.remarks}}))}catch(t){return M(e,t)}}),L.post(`/mpesa/b2c/result`,async e=>{let t=await e.req.json();return l(t)&&(O(t)?console.info(`[pesafy/hono] B2C success:`,{txId:k(t),amount:s(t)}):console.warn(`[pesafy/hono] B2C failed:`,t.Result.ResultDesc),P(F.onB2CResult?()=>Promise.resolve(F.onB2CResult(t)):void 0,`onB2CResult`)),e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/b2c/disburse`,async e=>{try{let{queueTimeoutUrl:t,resultUrl:n,...r}=await e.req.json();return j(e,await R.b2cDisbursement({...r,resultUrl:N(n,F.b2c?.resultUrl,F.resultUrl),queueTimeOutUrl:N(t,F.b2c?.queueTimeoutUrl,F.queueTimeoutUrl)}))}catch(t){return M(e,t)}}),L.post(`/mpesa/b2c/disburse/result`,async e=>{let t=await e.req.json();return m(t)&&(p(t)?console.info(`[pesafy/hono] Disbursement success:`,t.Result.TransactionID):console.warn(`[pesafy/hono] Disbursement failed:`,t.Result.ResultDesc),P(F.onB2CDisbursementResult?()=>Promise.resolve(F.onB2CDisbursementResult(t)):void 0,`onB2CDisbursementResult`)),e.json({ResultCode:0,ResultDesc:`Accepted`})}),L.post(`/mpesa/bills/optin`,async e=>{try{return j(e,await R.billManagerOptIn(await e.req.json()))}catch(t){return M(e,t)}}),L.patch(`/mpesa/bills/optin`,async e=>{try{return j(e,await R.updateOptIn(await e.req.json()))}catch(t){return M(e,t)}}),L.post(`/mpesa/bills/invoice`,async e=>{try{return j(e,await R.sendInvoice(await e.req.json()))}catch(t){return M(e,t)}}),L.post(`/mpesa/bills/invoice/bulk`,async e=>{try{return j(e,await R.sendBulkInvoices(await e.req.json()))}catch(t){return M(e,t)}}),L.delete(`/mpesa/bills/invoice`,async e=>{try{return j(e,await R.cancelInvoice(await e.req.json()))}catch(t){return M(e,t)}}),L.delete(`/mpesa/bills/invoice/bulk`,async e=>{try{return j(e,await R.cancelBulkInvoices(await e.req.json()))}catch(t){return M(e,t)}}),L.post(`/mpesa/bills/reconcile`,async e=>{try{return j(e,await R.reconcilePayment(await e.req.json()))}catch(t){return M(e,t)}}),L.get(`/mpesa/health`,e=>e.json({ok:!0,environment:R.environment,ts:new Date().toISOString()})),L}function I(e){let t=new b(e);return async(e,n)=>{e.set(`mpesa`,t),await n()}}export{F as createMpesaHono,I as mpesaMiddleware};
|
|
1
|
+
import { a as PesafyError, i as Mpesa, n as getRoutePaths, r as createRouteHandlers, t as ROUTE_DEFINITIONS } from "../route-definitions.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/adapters/shared/mount-hono.ts
|
|
5
|
+
const HONO_ROUTE_PATHS = getRoutePaths();
|
|
6
|
+
function getIP(c) {
|
|
7
|
+
return c.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? c.req.header("cf-connecting-ip") ?? c.req.header("x-real-ip") ?? "";
|
|
8
|
+
}
|
|
9
|
+
function sendErr(c, err) {
|
|
10
|
+
if (err instanceof PesafyError) return c.json({
|
|
11
|
+
ok: false,
|
|
12
|
+
error: err.code,
|
|
13
|
+
message: err.message
|
|
14
|
+
}, err.statusCode ?? 400);
|
|
15
|
+
return c.json({
|
|
16
|
+
ok: false,
|
|
17
|
+
error: "INTERNAL_ERROR",
|
|
18
|
+
message: "Unexpected error"
|
|
19
|
+
}, 500);
|
|
20
|
+
}
|
|
21
|
+
function sendHandlerResult(c, result, routeId) {
|
|
22
|
+
if (routeId === "health") return c.json(result.body);
|
|
23
|
+
if (result.type === "daraja") return c.json(result.body);
|
|
24
|
+
return c.json({
|
|
25
|
+
ok: true,
|
|
26
|
+
data: result.body
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function buildContext(c, isWebhook) {
|
|
30
|
+
if (isWebhook) {
|
|
31
|
+
const rawBody = await c.req.text();
|
|
32
|
+
return {
|
|
33
|
+
body: JSON.parse(rawBody),
|
|
34
|
+
rawBody,
|
|
35
|
+
requestIP: getIP(c),
|
|
36
|
+
getHeader: (name) => c.req.header(name)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
body: c.req.method === "GET" ? void 0 : await c.req.json().catch(() => ({})),
|
|
41
|
+
requestIP: getIP(c),
|
|
42
|
+
getHeader: (name) => c.req.header(name)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function mountHonoRoutes(config) {
|
|
46
|
+
const app = new Hono();
|
|
47
|
+
const mpesa = new Mpesa(config);
|
|
48
|
+
const prefix = config.routePrefix ?? "";
|
|
49
|
+
const handlers = createRouteHandlers(mpesa, config);
|
|
50
|
+
app.use("*", async (c, next) => {
|
|
51
|
+
c.set("mpesa", mpesa);
|
|
52
|
+
await next();
|
|
53
|
+
});
|
|
54
|
+
for (const route of ROUTE_DEFINITIONS) {
|
|
55
|
+
const handler = handlers[route.id];
|
|
56
|
+
const path = `${prefix}${route.path}`;
|
|
57
|
+
const isWebhook = route.webhook ?? false;
|
|
58
|
+
const run = async (c) => {
|
|
59
|
+
try {
|
|
60
|
+
return sendHandlerResult(c, await handler(await buildContext(c, isWebhook)), route.id);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return sendErr(c, e);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
switch (route.method) {
|
|
66
|
+
case "GET":
|
|
67
|
+
app.get(path, run);
|
|
68
|
+
break;
|
|
69
|
+
case "POST":
|
|
70
|
+
app.post(path, run);
|
|
71
|
+
break;
|
|
72
|
+
case "PATCH":
|
|
73
|
+
app.patch(path, run);
|
|
74
|
+
break;
|
|
75
|
+
case "DELETE":
|
|
76
|
+
app.delete(path, run);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return app;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/adapters/hono.ts
|
|
85
|
+
/**
|
|
86
|
+
* Creates a Hono app with all M-PESA routes.
|
|
87
|
+
* Mount it on your main app with `app.route('/api', createMpesaHono(config))`.
|
|
88
|
+
*/
|
|
89
|
+
function createMpesaHono(config) {
|
|
90
|
+
return mountHonoRoutes(config);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Returns a Hono middleware that injects an Mpesa instance into context.
|
|
94
|
+
*/
|
|
95
|
+
function mpesaMiddleware(config) {
|
|
96
|
+
const mpesa = new Mpesa(config);
|
|
97
|
+
return async (c, next) => {
|
|
98
|
+
c.set("mpesa", mpesa);
|
|
99
|
+
await next();
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
export { createMpesaHono, createMpesaHono as createMpesaHonoRouter, mpesaMiddleware };
|
|
105
|
+
//# sourceMappingURL=hono.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.js","names":["HonoApp"],"sources":["../../src/adapters/shared/mount-hono.ts","../../src/adapters/hono.ts"],"sourcesContent":["import { Hono as HonoApp, type Context, type Hono } from 'hono'\nimport { Mpesa } from '../../mpesa'\nimport { PesafyError } from '../../utils/errors'\nimport { createRouteHandlers } from './handlers'\nimport { ROUTE_DEFINITIONS, getRoutePaths } from './route-definitions'\n\nexport const HONO_ROUTE_PATHS = getRoutePaths()\nimport type { HandlerContext, HandlerResult, MpesaAdapterConfig } from './types'\n\nfunction getIP(c: Context): string {\n return (\n c.req.header('x-forwarded-for')?.split(',')[0]?.trim() ??\n c.req.header('cf-connecting-ip') ??\n c.req.header('x-real-ip') ??\n ''\n )\n}\n\nfunction sendErr(c: Context, err: unknown): Response {\n if (err instanceof PesafyError) {\n return c.json(\n { ok: false, error: err.code, message: err.message },\n (err.statusCode ?? 400) as 400 | 401 | 403 | 404 | 422 | 429 | 500,\n )\n }\n return c.json({ ok: false, error: 'INTERNAL_ERROR', message: 'Unexpected error' }, 500)\n}\n\nfunction sendHandlerResult(c: Context, result: HandlerResult, routeId: string): Response {\n if (routeId === 'health') {\n return c.json(result.body)\n }\n if (result.type === 'daraja') {\n return c.json(result.body)\n }\n return c.json({ ok: true, data: result.body })\n}\n\nasync function buildContext(c: Context, isWebhook: boolean): Promise<HandlerContext> {\n if (isWebhook) {\n const rawBody = await c.req.text()\n return {\n body: JSON.parse(rawBody) as unknown,\n rawBody,\n requestIP: getIP(c),\n getHeader: (name) => c.req.header(name),\n }\n }\n const body =\n c.req.method === 'GET' ? undefined : ((await c.req.json().catch(() => ({}))) as unknown)\n return {\n body,\n requestIP: getIP(c),\n getHeader: (name) => c.req.header(name),\n }\n}\n\nexport function mountHonoRoutes(config: MpesaAdapterConfig): Hono {\n const app = new HonoApp()\n const mpesa = new Mpesa(config)\n const prefix = config.routePrefix ?? ''\n const handlers = createRouteHandlers(mpesa, config)\n\n app.use('*', async (c, next) => {\n c.set('mpesa', mpesa)\n await next()\n })\n\n for (const route of ROUTE_DEFINITIONS) {\n const handler = handlers[route.id]\n const path = `${prefix}${route.path}`\n const isWebhook = route.webhook ?? false\n\n const run = async (c: Context) => {\n try {\n const ctx = await buildContext(c, isWebhook)\n const result = await handler(ctx)\n return sendHandlerResult(c, result, route.id)\n } catch (e) {\n return sendErr(c, e)\n }\n }\n\n switch (route.method) {\n case 'GET':\n app.get(path, run)\n break\n case 'POST':\n app.post(path, run)\n break\n case 'PATCH':\n app.patch(path, run)\n break\n case 'DELETE':\n app.delete(path, run)\n break\n }\n }\n\n return app\n}\n","/**\n * @file src/adapters/hono.ts\n * Hono adapter for pesafy — works on Node.js, Bun, Deno, and Cloudflare Workers.\n *\n * Usage:\n * import { Hono } from 'hono'\n * import { createMpesaHono } from 'pesafy/adapters/hono'\n *\n * const app = new Hono()\n * app.route('/api', createMpesaHono(config))\n */\n\nimport { type Hono, type MiddlewareHandler } from 'hono'\nimport { Mpesa } from '../mpesa'\nimport { mountHonoRoutes } from './shared/mount-hono'\nimport type { MpesaAdapterConfig, StkFailurePayload, StkSuccessPayload } from './shared/types'\n\nexport type MpesaHonoConfig = MpesaAdapterConfig\nexport type { StkSuccessPayload, StkFailurePayload }\n\n/**\n * Creates a Hono app with all M-PESA routes.\n * Mount it on your main app with `app.route('/api', createMpesaHono(config))`.\n */\nexport function createMpesaHono(config: MpesaHonoConfig): Hono {\n return mountHonoRoutes(config)\n}\n\nexport { createMpesaHono as createMpesaHonoRouter }\n\n/**\n * Returns a Hono middleware that injects an Mpesa instance into context.\n */\nexport function mpesaMiddleware(config: MpesaHonoConfig): MiddlewareHandler {\n const mpesa = new Mpesa(config)\n return async (c, next) => {\n c.set('mpesa', mpesa)\n await next()\n }\n}\n\ndeclare module 'hono' {\n interface ContextVariableMap {\n mpesa: Mpesa\n }\n}\n"],"mappings":";;;;AAMA,MAAa,mBAAmB,eAAe;AAG/C,SAAS,MAAM,GAAoB;AACjC,QACE,EAAE,IAAI,OAAO,kBAAkB,EAAE,MAAM,IAAI,CAAC,IAAI,MAAM,IACtD,EAAE,IAAI,OAAO,mBAAmB,IAChC,EAAE,IAAI,OAAO,YAAY,IACzB;;AAIJ,SAAS,QAAQ,GAAY,KAAwB;AACnD,KAAI,eAAe,YACjB,QAAO,EAAE,KACP;EAAE,IAAI;EAAO,OAAO,IAAI;EAAM,SAAS,IAAI;EAAS,EACnD,IAAI,cAAc,IACpB;AAEH,QAAO,EAAE,KAAK;EAAE,IAAI;EAAO,OAAO;EAAkB,SAAS;EAAoB,EAAE,IAAI;;AAGzF,SAAS,kBAAkB,GAAY,QAAuB,SAA2B;AACvF,KAAI,YAAY,SACd,QAAO,EAAE,KAAK,OAAO,KAAK;AAE5B,KAAI,OAAO,SAAS,SAClB,QAAO,EAAE,KAAK,OAAO,KAAK;AAE5B,QAAO,EAAE,KAAK;EAAE,IAAI;EAAM,MAAM,OAAO;EAAM,CAAC;;AAGhD,eAAe,aAAa,GAAY,WAA6C;AACnF,KAAI,WAAW;EACb,MAAM,UAAU,MAAM,EAAE,IAAI,MAAM;AAClC,SAAO;GACL,MAAM,KAAK,MAAM,QAAQ;GACzB;GACA,WAAW,MAAM,EAAE;GACnB,YAAY,SAAS,EAAE,IAAI,OAAO,KAAK;GACxC;;AAIH,QAAO;EACL,MAFA,EAAE,IAAI,WAAW,QAAQ,SAAc,MAAM,EAAE,IAAI,MAAM,CAAC,aAAa,EAAE,EAAE;EAG3E,WAAW,MAAM,EAAE;EACnB,YAAY,SAAS,EAAE,IAAI,OAAO,KAAK;EACxC;;AAGH,SAAgB,gBAAgB,QAAkC;CAChE,MAAM,MAAM,IAAIA,MAAS;CACzB,MAAM,QAAQ,IAAI,MAAM,OAAO;CAC/B,MAAM,SAAS,OAAO,eAAe;CACrC,MAAM,WAAW,oBAAoB,OAAO,OAAO;AAEnD,KAAI,IAAI,KAAK,OAAO,GAAG,SAAS;AAC9B,IAAE,IAAI,SAAS,MAAM;AACrB,QAAM,MAAM;GACZ;AAEF,MAAK,MAAM,SAAS,mBAAmB;EACrC,MAAM,UAAU,SAAS,MAAM;EAC/B,MAAM,OAAO,GAAG,SAAS,MAAM;EAC/B,MAAM,YAAY,MAAM,WAAW;EAEnC,MAAM,MAAM,OAAO,MAAe;AAChC,OAAI;AAGF,WAAO,kBAAkB,GADV,MAAM,QADT,MAAM,aAAa,GAAG,UAAU,CACX,EACG,MAAM,GAAG;YACtC,GAAG;AACV,WAAO,QAAQ,GAAG,EAAE;;;AAIxB,UAAQ,MAAM,QAAd;GACE,KAAK;AACH,QAAI,IAAI,MAAM,IAAI;AAClB;GACF,KAAK;AACH,QAAI,KAAK,MAAM,IAAI;AACnB;GACF,KAAK;AACH,QAAI,MAAM,MAAM,IAAI;AACpB;GACF,KAAK;AACH,QAAI,OAAO,MAAM,IAAI;AACrB;;;AAIN,QAAO;;;;;;;;;AC3ET,SAAgB,gBAAgB,QAA+B;AAC7D,QAAO,gBAAgB,OAAO;;;;;AAQhC,SAAgB,gBAAgB,QAA4C;CAC1E,MAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,QAAO,OAAO,GAAG,SAAS;AACxB,IAAE,IAAI,SAAS,MAAM;AACrB,QAAM,MAAM"}
|