@madapay/papi 0.0.0-stage → 0.1.1
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/LICENSE +176 -0
- package/README.md +29 -2
- package/dist/index.cjs +418 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +413 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/http.ts","../src/status.ts","../src/index.ts"],"names":["ProviderTimeoutError","ProviderUnavailableError","AuthenticationError","PaymentNotFoundError","DuplicatePaymentError","ValidationError","PROVIDER","UnsupportedOperationError","toInternationalFormat","executeWithRetry","WebhookVerificationError","createHmac","timingSafeEqual"],"mappings":";;;;;;AAuBA,IAAM,QAAA,GAAW,MAAA;AAEjB,SAAS,kBAAkB,OAAA,EAA2C;AACpE,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,KAAY,IAAA,SAAa,EAAC;AAC7D,EAAA,MAAM,CAAA,GAAI,OAAA;AACV,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,MAAA,EAAQ,OAAA,EAAS,SAAS,CAAA,EAAG;AAC5C,IAAA,IAAI,OAAO,CAAA,CAAE,CAAC,CAAA,KAAM,UAAU,GAAA,CAAI,CAAA,SAAA,EAAY,CAAC,CAAA,CAAE,IAAK,CAAA,CAAE,CAAC,CAAA,CAAa,KAAA,CAAM,GAAG,GAAG,CAAA;AAAA,EACpF;AACA,EAAA,OAAO,GAAA;AACT;AAEA,eAAsB,YAAY,IAAA,EAA4C;AAC5E,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,SAAS,CAAA;AACjE,EAAA,MAAM,OAAA,GAAU,MAAM,UAAA,CAAW,KAAA,EAAM;AACvC,EAAA,IAAA,CAAK,QAAQ,gBAAA,CAAiB,OAAA,EAAS,SAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAE9D,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,GAAA,EAAK;AAAA,MACxC,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,SAAS,IAAA,CAAK,OAAA;AAAA,MACd,IAAA,EAAM,KAAK,IAAA,KAAS,KAAA,CAAA,GAAY,SAAY,IAAA,CAAK,SAAA,CAAU,KAAK,IAAI,CAAA;AAAA,MACpE,QAAQ,UAAA,CAAW;AAAA,KACpB,CAAA;AAAA,EACH,CAAA,CAAA,MAAQ;AACN,IAAA,IAAI,KAAK,MAAA,EAAQ,OAAA,EAAS,MAAM,IAAI,MAAM,6BAA6B,CAAA;AACvE,IAAA,MAAM,QAAA,GAAW,WAAW,MAAA,CAAO,OAAA;AACnC,IAAA,IAAI,IAAA,CAAK,sBAAsB,QAAA,EAAU;AACvC,MAAA,MAAM,IAAIA,yBAAA;AAAA,QACR,QAAA,GACI,CAAA,6BAAA,EAAgC,IAAA,CAAK,SAAS,CAAA,EAAA,CAAA,GAC9C,0EAAA;AAAA,QACJ,EAAE,SAAA,EAAW,IAAA,CAAK,SAAA,EAAU;AAAA,QAC5B;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,IAAIC,6BAAA,CAAyB,qBAAA,EAAuB,MAAA,EAAW,QAAQ,CAAA;AAAA,EAC/E,CAAA,SAAE;AACA,IAAA,YAAA,CAAa,KAAK,CAAA;AAClB,IAAA,IAAA,CAAK,MAAA,EAAQ,mBAAA,CAAoB,OAAA,EAAS,OAAO,CAAA;AAAA,EACnD;AAEA,EAAA,MAAM,OAAO,MAAM,QAAA,CAAS,MAAK,CAAE,KAAA,CAAM,MAAM,EAAE,CAAA;AACjD,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACF,IAAA,OAAA,GAAU,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,GAAI,KAAA,CAAA;AAAA,EACtC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAA,GAAU,MAAA;AAAA,EACZ;AAEA,EAAA,IAAI,SAAS,EAAA,EAAI;AACf,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,MAAM,IAAA,CAAK,qBACP,IAAID,yBAAA;AAAA,QACF,oEAAA;AAAA,QACA,EAAE,MAAA,EAAQ,QAAA,CAAS,MAAA,EAAO;AAAA,QAC1B;AAAA,OACF,GACA,IAAIC,6BAAA,CAAyB,sCAAA,EAAwC,QAAW,QAAQ,CAAA;AAAA,IAC9F;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAA,GAAU,EAAE,MAAA,EAAQ,QAAA,CAAS,QAAQ,GAAG,iBAAA,CAAkB,OAAO,CAAA,EAAE;AACzE,EAAA,MAAM,IAAI,QAAA,CAAS,MAAA;AACnB,EAAA,IAAI,CAAA,KAAM,GAAA,IAAO,CAAA,KAAM,GAAA,EAAK;AAC1B,IAAA,MAAM,IAAIC,wBAAA,CAAoB,+BAAA,EAAiC,OAAA,EAAS,QAAQ,CAAA;AAAA,EAClF;AACA,EAAA,IAAI,MAAM,GAAA,EAAK,MAAM,IAAIC,yBAAA,CAAqB,wBAAA,EAA0B,SAAS,QAAQ,CAAA;AACzF,EAAA,IAAI,MAAM,GAAA,EAAK;AACb,IAAA,MAAM,IAAIC,0BAAA;AAAA,MACR,mEAAA;AAAA,MACA,OAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,MAAM,GAAA,EAAK,MAAM,IAAIH,6BAAA,CAAyB,yBAAA,EAA2B,SAAS,QAAQ,CAAA;AAC9F,EAAA,IAAI,KAAK,GAAA,EAAK;AACZ,IAAA,IAAI,KAAK,kBAAA,EAAoB;AAC3B,MAAA,MAAM,IAAID,yBAAA;AAAA,QACR,wDAAA;AAAA,QACA,OAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,IAAIC,6BAAA,CAAyB,mBAAA,EAAqB,OAAA,EAAS,QAAQ,CAAA;AAAA,EAC3E;AACA,EAAA,MAAM,IAAII,oBAAA,CAAgB,2BAAA,EAA6B,mBAAA,EAAqB,SAAS,QAAQ,CAAA;AAC/F;;;AC/GA,IAAM,UAAA,GAA4C;AAAA,EAChD,OAAA,EAAS,SAAA;AAAA,EACT,UAAA,EAAY,YAAA;AAAA,EACZ,OAAA,EAAS,WAAA;AAAA,EACT,MAAA,EAAQ,QAAA;AAAA,EACR,OAAA,EAAS,SAAA;AAAA,EACT,SAAA,EAAW;AACb,CAAA;AAQO,SAAS,cAAc,GAAA,EAA4B;AACxD,EAAA,MAAM,MAAM,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,CAAI,aAAY,GAAI,EAAA;AAC1D,EAAA,MAAM,MAAA,GAAS,WAAW,GAAG,CAAA;AAC7B,EAAA,OAAO,MAAA,GAAS,EAAE,MAAA,EAAQ,KAAA,EAAO,IAAA,KAAS,EAAE,MAAA,EAAQ,YAAA,EAAc,KAAA,EAAO,KAAA,EAAM;AACjF;;;ACMO,IAAM,cAAA,GAAkD;AAAA,EAC7D,OAAA,EAAS,6BAAA;AAAA,EACT,UAAA,EAAY;AACd;AAsBA,IAAM,OAAA,GAA2B,CAAC,OAAA,EAAS,cAAA,EAAgB,cAAc,CAAA;AACzE,IAAMC,SAAAA,GAAW,MAAA;AAEjB,SAAS,eAAe,MAAA,EAA4B;AAClD,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,CAAO,MAAA,KAAW,YAAY,MAAA,CAAO,MAAA,CAAO,IAAA,EAAK,KAAM,EAAA,EAAI;AAC/E,IAAA,MAAM,IAAIJ,wBAAAA,CAAoB,yBAAA,EAA2B,MAAA,EAAWI,SAAQ,CAAA;AAAA,EAC9E;AACA,EAAA,IAAI,MAAA,CAAO,WAAA,KAAgB,SAAA,IAAa,MAAA,CAAO,gBAAgB,YAAA,EAAc;AAC3E,IAAA,MAAM,IAAID,oBAAAA;AAAA,MACR,+DAAA;AAAA,MACA,mBAAA;AAAA,MACA,MAAA;AAAA,MACAC;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,OAAA,GAAA,CAAW,OAAO,OAAA,IAAW,cAAA,CAAe,OAAO,WAAW,CAAA,EAAG,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACzF,EAAA,IAAI,CAAC,OAAA,CAAQ,UAAA,CAAW,UAAU,CAAA,EAAG;AACnC,IAAA,MAAM,IAAID,oBAAAA;AAAA,MACR,6BAAA;AAAA,MACA,mBAAA;AAAA,MACA,MAAA;AAAA,MACAC;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,eAAe,mBAAA,CAAoB,IAAA,CAAK,IAAI,GAAA,CAAI,OAAO,EAAE,QAAQ,CAAA;AACvE,EAAA,IAAI,MAAA,CAAO,WAAA,KAAgB,YAAA,IAAgB,YAAA,EAAc;AACvD,IAAA,MAAM,IAAID,oBAAAA;AAAA,MACR,gEAAA;AAAA,MACA,mBAAA;AAAA,MACA,MAAA;AAAA,MACAC;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,MAAA,CAAO,WAAA,KAAgB,SAAA,IAAa,CAAC,YAAA,EAAc;AACrD,IAAA,MAAM,IAAID,oBAAAA;AAAA,MACR,iEAAA;AAAA,MACA,mBAAA;AAAA,MACA,MAAA;AAAA,MACAC;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAEA,SAAS,SAAS,KAAA,EAAyC;AACzD,EAAA,IAAI,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACvE,IAAA,MAAM,IAAID,oBAAAA;AAAA,MACR,gCAAA;AAAA,MACA,mBAAA;AAAA,MACA,MAAA;AAAA,MACAC;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAEO,IAAM,sBAAN,MAAqD;AAAA,EAC1C,EAAA,GAAKA,SAAAA;AAAA,EACL,IAAA,GAAOA,SAAAA;AAAA,EACN,MAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EACA,WAAA;AAAA,EACA,eAAA;AAAA,EACA,SAAA;AAAA,EACA,kBAAA;AAAA,EACA,mBAAA;AAAA,EACA,SAAA;AAAA,EACA,eAAA;AAAA,EAEjB,YAAY,MAAA,EAAqF;AAC/F,IAAA,IAAA,CAAK,OAAA,GAAU,eAAe,MAAM,CAAA;AACpC,IAAA,IAAA,CAAK,SAAS,MAAA,CAAO,MAAA;AACrB,IAAA,IAAA,CAAK,cAAc,MAAA,CAAO,WAAA;AAC1B,IAAA,IAAA,CAAK,gBAAgB,MAAA,CAAO,aAAA;AAC5B,IAAA,IAAA,CAAK,kBAAkB,MAAA,CAAO,eAAA;AAC9B,IAAA,IAAA,CAAK,SAAA,GAAY,OAAO,SAAA,IAAa,IAAA;AACrC,IAAA,IAAA,CAAK,kBAAA,GAAqB,OAAO,kBAAA,IAAsB,CAAA;AACvD,IAAA,IAAA,CAAK,mBAAA,GAAsB,OAAO,mBAAA,IAAuB,GAAA;AACzD,IAAA,IAAA,CAAK,SAAA,GAAY,OAAO,KAAA,KAAU,CAAC,KAAK,IAAA,KAAS,KAAA,CAAM,KAAK,IAAI,CAAA,CAAA;AAChE,IAAA,IAAA,CAAK,kBAAkB,MAAA,CAAO,eAAA;AAAA,EAChC;AAAA;AAAA,EAGO,MAAA,GAAkC;AACvC,IAAA,OAAO,EAAE,IAAI,IAAA,CAAK,EAAA,EAAI,aAAa,IAAA,CAAK,WAAA,EAAa,OAAA,EAAS,IAAA,CAAK,OAAA,EAAQ;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,eAAA,GAAoC;AACzC,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,IAAA;AAAA,MACT,wBAAA,EAA0B,IAAA;AAAA,MAC1B,uBAAA,EAAyB,IAAA;AAAA;AAAA,MACzB,QAAA,EAAU,IAAA;AAAA,MACV,OAAA,EAAS,IAAA;AAAA,MACT,OAAA,EAAS,KAAA;AAAA,MACT,OAAA,EAAS,KAAA;AAAA,MACT,YAAA,EAAc,KAAA;AAAA,MACd,cAAA,EAAgB,KAAA;AAAA,MAChB,QAAA,EAAU,KAAA;AAAA,MACV,YAAA,EAAc,KAAA;AAAA,MACd,gBAAA,EAAkB,CAAC,GAAG,OAAO,CAAA;AAAA,MAC7B,gBAAA,EAAkB,IAAA;AAAA,MAClB,iBAAA,EAAmB,IAAA;AAAA,MACnB,aAAA,EAAe,IAAA;AAAA,MACf,cAAA,EAAgB,KAAA;AAAA,MAChB,kBAAA,EAAoB;AAAA,KACtB;AAAA,EACF;AAAA,EAEQ,OAAA,CAAQ,KAAA,GAAgC,EAAC,EAA2B;AAC1E,IAAA,OAAO;AAAA,MACL,aAAA,EAAe,CAAA,OAAA,EAAU,IAAA,CAAK,MAAM,CAAA,CAAA;AAAA,MACpC,cAAA,EAAgB,kBAAA;AAAA,MAChB,MAAA,EAAQ,kBAAA;AAAA,MACR,GAAG;AAAA,KACL;AAAA,EACF;AAAA,EAEQ,UAAU,OAAA,EAAgE;AAChF,IAAA,IAAI,CAAC,OAAA,CAAQ,QAAA,CAAS,OAAA,CAAQ,aAAa,CAAA,EAAG;AAC5C,MAAA,MAAM,IAAIC,8BAAA;AAAA,QACR,CAAA,sCAAA,EAAyC,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAC,CAAA,CAAA,CAAA;AAAA,QACtE,EAAE,aAAA,EAAe,OAAA,CAAQ,aAAA,EAAc;AAAA,QACvCD;AAAA,OACF;AAAA,IACF;AACA,IAAA,IAAI,CAAC,QAAQ,cAAA,EAAgB;AAC3B,MAAA,MAAM,IAAID,oBAAAA;AAAA,QACR,2CAAA;AAAA,QACA,mBAAA;AAAA,QACA,MAAA;AAAA,QACAC;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,YAAA,IAAgB,EAAC;AAC9C,IAAA,MAAM,gBAAgB,YAAA,CAAa,aAAA;AACnC,IAAA,MAAM,IAAA,GAAgC;AAAA,MACpC,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,0BAA0B,OAAA,CAAQ,SAAA;AAAA,MAClC,eAAe,OAAA,CAAQ,aAAA;AAAA,MACvB,QAAA,EAAU;AAAA,QACR,KAAA,EAAOE,0BAAA,CAAsB,OAAA,CAAQ,QAAA,CAAS,KAAK,CAAA;AAAA,QACnD,GAAI,OAAA,CAAQ,QAAA,CAAS,IAAA,GAAO,EAAE,MAAM,OAAA,CAAQ,QAAA,CAAS,IAAA,EAAK,GAAI,EAAC;AAAA,QAC/D,GAAI,OAAA,CAAQ,QAAA,CAAS,KAAA,GAAQ,EAAE,OAAO,OAAA,CAAQ,QAAA,CAAS,KAAA,EAAM,GAAI;AAAC;AACpE,KACF;AACA,IAAA,IAAI,IAAA,CAAK,eAAA,EAAiB,IAAA,CAAK,eAAA,GAAkB,IAAA,CAAK,eAAA;AACtD,IAAA,IAAI,OAAA,CAAQ,WAAA,EAAa,IAAA,CAAK,WAAA,GAAc,OAAA,CAAQ,WAAA;AAEpD,IAAA,IAAI,OAAA,CAAQ,kBAAkB,cAAA,EAAgB;AAC5C,MAAA,IAAI,OAAO,aAAA,KAAkB,QAAA,IAAY,CAAC,WAAA,CAAY,IAAA,CAAK,aAAa,CAAA,EAAG;AACzE,QAAA,MAAM,IAAIH,oBAAAA;AAAA,UACR,2FAAA;AAAA,UACA,mBAAA;AAAA,UACA,MAAA;AAAA,UACAC;AAAA,SACF;AAAA,MACF;AACA,MAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AAAA,IACvB,CAAA,MAAA,IAAW,kBAAkB,MAAA,EAAW;AACtC,MAAA,MAAM,IAAID,oBAAAA;AAAA,QACR,2DAAA;AAAA,QACA,mBAAA;AAAA,QACA,MAAA;AAAA,QACAC;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEQ,iBAAA,CAAkB,SAAkB,cAAA,EAAiD;AAC3F,IAAA,MAAM,CAAA,GAAI,SAAS,OAAO,CAAA;AAC1B,IAAA,MAAM,oBAAoB,CAAA,CAAE,gBAAA;AAC5B,IAAA,IAAI,OAAO,iBAAA,KAAsB,QAAA,IAAY,iBAAA,KAAsB,EAAA,EAAI;AACrE,MAAA,MAAM,IAAID,oBAAAA;AAAA,QACR,wCAAA;AAAA,QACA,mBAAA;AAAA,QACA,MAAA;AAAA,QACAC;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,SAAU,OAAA,CAAsB,QAAA,CAAS,EAAE,aAAa,CAAA,GACzD,EAAE,aAAA,GACH,cAAA;AACJ,IAAA,IAAI,CAAC,MAAA,IAAU,OAAO,CAAA,CAAE,WAAW,QAAA,EAAU;AAC3C,MAAA,MAAM,IAAID,oBAAAA;AAAA,QACR,+CAAA;AAAA,QACA,mBAAA;AAAA,QACA,MAAA;AAAA,QACAC;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,GAAA,GAAM,OAAO,CAAA,CAAE,aAAA,KAAkB,WAAW,CAAA,CAAE,aAAA,GAAgB,MAAA,CAAO,CAAA,CAAE,aAAa,CAAA;AAC1F,IAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAM,GAAI,aAAA,CAAc,EAAE,aAAa,CAAA;AACvD,IAAA,MAAM,YACJ,OAAO,CAAA,CAAE,wBAAA,KAA6B,QAAA,GAAW,EAAE,wBAAA,GAA2B,EAAA;AAChF,IAAA,IAAI,CAAC,KAAA,EAAO,IAAA,CAAK,eAAA,GAAkB,KAAK,iBAAiB,CAAA;AACzD,IAAA,OAAO;AAAA,MACL,QAAA,EAAUA,SAAAA;AAAA,MACV,iBAAA;AAAA,MACA,SAAA;AAAA,MACA,QAAQ,CAAA,CAAE,MAAA;AAAA,MACV,QAAA,EAAU,KAAA;AAAA,MACV,MAAA;AAAA,MACA,aAAA,EAAe,MAAA;AAAA,MACf,iBAAA,EAAmB;AAAA,KACrB;AAAA,EACF;AAAA,EAEA,MAAa,cAAc,OAAA,EAAiE;AAC1F,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA;AACnC,IAAA,MAAM,IAAA,GAAO,MACX,WAAA,CAAY;AAAA,MACV,MAAA,EAAQ,MAAA;AAAA,MACR,GAAA,EAAK,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,YAAA,CAAA;AAAA,MACpB,SAAS,IAAA,CAAK,OAAA,CAAQ,EAAE,iBAAA,EAAmB,OAAA,CAAQ,gBAAgB,CAAA;AAAA,MACnE,IAAA;AAAA,MACA,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,kBAAA,EAAoB;AAAA,KACrB,CAAA;AAGH,IAAA,MAAM,OAAA,GAAU,MAAMG,qBAAA,CAAiB,IAAA,EAAM;AAAA,MAC3C,YAAY,IAAA,CAAK,kBAAA;AAAA,MACjB,gBAAgB,IAAA,CAAK,mBAAA;AAAA,MACrB,WAAA,EAAa,CAAC,CAAA,KAAM;AAClB,QAAA,MAAM,OAAQ,CAAA,CAAwB,IAAA;AACtC,QAAA,OAAO,IAAA,KAAS,sBAAsB,IAAA,KAAS,sBAAA;AAAA,MACjD;AAAA,KACD,CAAA;AACD,IAAA,OAAO,IAAA,CAAK,iBAAA,CAAkB,OAAA,EAAS,OAAA,CAAQ,aAAa,CAAA;AAAA,EAC9D;AAAA,EAEA,MAAa,WAAW,WAAA,EAA+D;AACrF,IAAA,MAAM,GAAA,GAAM,OAAO,WAAA,KAAgB,QAAA,GAAW,cAAc,WAAA,CAAY,iBAAA;AACxE,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,MAAM,IAAIF,8BAAA;AAAA,QACR,2DAAA;AAAA,QACA,MAAA;AAAA,QACAD;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,UAAU,MAAMG,qBAAA;AAAA,MACpB,MACE,WAAA,CAAY;AAAA,QACV,MAAA,EAAQ,KAAA;AAAA,QACR,KAAK,CAAA,EAAG,IAAA,CAAK,OAAO,CAAA,aAAA,EAAgB,kBAAA,CAAmB,GAAG,CAAC,CAAA,CAAA;AAAA,QAC3D,OAAA,EAAS,KAAK,OAAA,EAAQ;AAAA,QACtB,WAAW,IAAA,CAAK,SAAA;AAAA,QAChB,WAAW,IAAA,CAAK;AAAA,OACjB,CAAA;AAAA,MACH;AAAA,QACE,UAAA,EAAY,CAAA;AAAA,QACZ,gBAAgB,IAAA,CAAK,mBAAA;AAAA,QACrB,WAAA,EAAa,CAAC,CAAA,KAAM;AAClB,UAAA,MAAM,OAAQ,CAAA,CAAwB,IAAA;AACtC,UAAA,OAAO,IAAA,KAAS,sBAAsB,IAAA,KAAS,sBAAA;AAAA,QACjD;AAAA;AACF,KACF;AACA,IAAA,OAAO,IAAA,CAAK,kBAAkB,OAAO,CAAA;AAAA,EACvC;AAAA;AAAA,EAGA,MAAa,cACX,OAAA,EACkE;AAClE,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACvB,MAAA,MAAM,IAAIP,wBAAAA,CAAoB,sCAAA,EAAwC,MAAA,EAAWI,SAAQ,CAAA;AAAA,IAC3F;AACA,IAAA,IAAI,EAAE,OAAA,CAAQ,OAAA,YAAmB,UAAA,CAAA,EAAa;AAC5C,MAAA,MAAM,IAAII,6BAAA,CAAyB,4BAAA,EAA8B,MAAA,EAAWJ,SAAQ,CAAA;AAAA,IACtF;AAEA,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,CAAE,IAAA;AAAA,MAC7C,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,EAAY,KAAM;AAAA,KAC7B;AACA,IAAA,MAAM,WAAA,GAAc,SAAA,GAAY,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA,GAAI,MAAA;AAC7D,IAAA,MAAM,YAAY,KAAA,CAAM,OAAA,CAAQ,WAAW,CAAA,GAAI,WAAA,CAAY,CAAC,CAAA,GAAI,WAAA;AAChE,IAAA,IAAI,OAAO,cAAc,QAAA,IAAY,CAAC,oBAAoB,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,CAAA,EAAG;AAChF,MAAA,MAAM,IAAII,6BAAA;AAAA,QACR,uCAAA;AAAA,QACA,MAAA;AAAA,QACAJ;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAWK,iBAAA,CAAW,QAAA,EAAU,IAAA,CAAK,aAAa,EAAE,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,CAAE,MAAA,EAAO;AACzF,IAAA,MAAM,WAAW,MAAA,CAAO,IAAA,CAAK,SAAA,CAAU,IAAA,IAAQ,KAAK,CAAA;AACpD,IAAA,IAAI,CAACC,sBAAA,CAAgB,QAAA,EAAU,QAAQ,CAAA,EAAG;AACxC,MAAA,MAAM,IAAIF,6BAAA,CAAyB,2BAAA,EAA6B,MAAA,EAAWJ,SAAQ,CAAA;AAAA,IACrF;AAEA,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,CAAE,QAAA,CAAS,MAAM,CAAC,CAAC,CAAA;AAAA,IAC9E,CAAA,CAAA,MAAQ;AACN,MAAA,MAAM,IAAII,6BAAA,CAAyB,mCAAA,EAAqC,MAAA,EAAWJ,SAAQ,CAAA;AAAA,IAC7F;AACA,IAAA,IACE,OAAO,OAAA,CAAQ,OAAA,KAAY,QAAA,IAC3B,OAAO,OAAA,CAAQ,gBAAA,KAAqB,QAAA,IACpC,OAAO,OAAA,CAAQ,UAAA,KAAe,QAAA,EAC9B;AACA,MAAA,MAAM,IAAII,6BAAA;AAAA,QACR,yCAAA;AAAA,QACA,MAAA;AAAA,QACAJ;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAA;AACxC,IAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAM,GAAI,aAAA,CAAc,QAAQ,aAAa,CAAA;AAC7D,IAAA,IAAI,CAAC,KAAA,EAAO,IAAA,CAAK,eAAA,GAAkB,GAAA,EAAK,QAAQ,gBAAgB,CAAA;AAChE,IAAA,OAAO;AAAA,MACL,iBAAiB,OAAA,CAAQ,OAAA;AAAA,MACzB,mBAAmB,OAAA,CAAQ,gBAAA;AAAA,MAC3B,WACE,OAAO,OAAA,CAAQ,wBAAA,KAA6B,QAAA,GACxC,QAAQ,wBAAA,GACR,MAAA;AAAA,MACN,MAAA;AAAA,MACA,YAAY,OAAA,CAAQ,UAAA;AAAA,MACpB,iBAAA,EAAmB,GAAA;AAAA,MACnB,aAAa,IAAA,CAAK;AAAA,KACpB;AAAA,EACF;AACF","file":"index.cjs","sourcesContent":["import {\n AuthenticationError,\n DuplicatePaymentError,\n PaymentNotFoundError,\n ProviderTimeoutError,\n ProviderUnavailableError,\n ValidationError,\n} from '@madapay/core';\n\nexport type PapiFetch = (url: string, init: RequestInit) => Promise<Response>;\n\nexport interface PapiRequestOptions {\n method: 'GET' | 'POST';\n url: string;\n headers: Record<string, string>;\n body?: unknown;\n timeoutMs: number;\n signal?: AbortSignal;\n fetchImpl: PapiFetch;\n /** True for payment creation: failures after dispatch are an uncertain outcome. */\n uncertainOnFailure?: boolean;\n}\n\nconst PROVIDER = 'papi';\n\nfunction safeProviderError(payload: unknown): Record<string, unknown> {\n if (typeof payload !== 'object' || payload === null) return {};\n const p = payload as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const k of ['code', 'error', 'message']) {\n if (typeof p[k] === 'string') out[`provider_${k}`] = (p[k] as string).slice(0, 200);\n }\n return out;\n}\n\nexport async function papiRequest(opts: PapiRequestOptions): Promise<unknown> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), opts.timeoutMs);\n const onAbort = () => controller.abort();\n opts.signal?.addEventListener('abort', onAbort, { once: true });\n\n let response: Response;\n try {\n response = await opts.fetchImpl(opts.url, {\n method: opts.method,\n headers: opts.headers,\n body: opts.body === undefined ? undefined : JSON.stringify(opts.body),\n signal: controller.signal,\n });\n } catch {\n if (opts.signal?.aborted) throw new Error('Operation aborted by caller');\n const timedOut = controller.signal.aborted;\n if (opts.uncertainOnFailure || timedOut) {\n throw new ProviderTimeoutError(\n timedOut\n ? `Papi request timed out after ${opts.timeoutMs}ms`\n : 'Papi request failed after dispatch; outcome is uncertain (network error)',\n { timeoutMs: opts.timeoutMs },\n PROVIDER,\n );\n }\n throw new ProviderUnavailableError('Papi is unreachable', undefined, PROVIDER);\n } finally {\n clearTimeout(timer);\n opts.signal?.removeEventListener('abort', onAbort);\n }\n\n const text = await response.text().catch(() => '');\n let payload: unknown;\n try {\n payload = text ? JSON.parse(text) : undefined;\n } catch {\n payload = undefined;\n }\n\n if (response.ok) {\n if (payload === undefined) {\n throw opts.uncertainOnFailure\n ? new ProviderTimeoutError(\n 'Papi returned an unreadable success response; outcome is uncertain',\n { status: response.status },\n PROVIDER,\n )\n : new ProviderUnavailableError('Papi returned an unreadable response', undefined, PROVIDER);\n }\n return payload;\n }\n\n const details = { status: response.status, ...safeProviderError(payload) };\n const s = response.status;\n if (s === 401 || s === 403) {\n throw new AuthenticationError('Papi rejected the credentials', details, PROVIDER);\n }\n if (s === 404) throw new PaymentNotFoundError('Papi payment not found', details, PROVIDER);\n if (s === 409) {\n throw new DuplicatePaymentError(\n 'Papi idempotency conflict: same key used with a different payload',\n details,\n PROVIDER,\n );\n }\n if (s === 429) throw new ProviderUnavailableError('Papi rate limit reached', details, PROVIDER);\n if (s >= 500) {\n if (opts.uncertainOnFailure) {\n throw new ProviderTimeoutError(\n 'Papi server error after dispatch; outcome is uncertain',\n details,\n PROVIDER,\n );\n }\n throw new ProviderUnavailableError('Papi server error', details, PROVIDER);\n }\n throw new ValidationError('Papi rejected the request', 'VALIDATION_FAILED', details, PROVIDER);\n}\n","import type { PaymentStatus } from '@madapay/core';\n\nconst STATUS_MAP: Record<string, PaymentStatus> = {\n PENDING: 'pending',\n PROCESSING: 'processing',\n SUCCESS: 'succeeded',\n FAILED: 'failed',\n EXPIRED: 'expired',\n CANCELLED: 'cancelled',\n};\n\nexport interface MappedStatus {\n status: PaymentStatus;\n known: boolean;\n}\n\n/** Unknown statuses stay nonterminal ('processing') and are flagged for diagnostics. */\nexport function mapPapiStatus(raw: unknown): MappedStatus {\n const key = typeof raw === 'string' ? raw.toUpperCase() : '';\n const status = STATUS_MAP[key];\n return status ? { status, known: true } : { status: 'processing', known: false };\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport {\n AuthenticationError,\n UnsupportedOperationError,\n ValidationError,\n WebhookVerificationError,\n executeWithRetry,\n toInternationalFormat,\n} from '@madapay/core';\nimport type {\n PaymentLookup,\n PaymentMethod,\n PaymentProvider,\n ProviderCapabilities,\n ProviderCreatePaymentRequest,\n ProviderPayment,\n ProviderWebhookEvent,\n ProviderWebhookRequest,\n} from '@madapay/core';\nimport { papiRequest } from './http.js';\nimport type { PapiFetch } from './http.js';\nimport { mapPapiStatus } from './status.js';\n\nexport { mapPapiStatus } from './status.js';\n\nexport type PapiEnvironment = 'sandbox' | 'production';\n\nexport const PAPI_BASE_URLS: Record<PapiEnvironment, string> = {\n sandbox: 'https://api.sandbox.papi.mg',\n production: 'https://api.papi.mg',\n};\n\nexport interface PapiConfig {\n /** Server-side API key. Never expose to browsers or clients. */\n apiKey: string;\n /** Selects the base URL and guards against sandbox/production mixups. */\n environment: PapiEnvironment;\n /** Per-application secret used to verify `X-Papi-Signature`. */\n webhookSecret?: string;\n /** Override only for a documented gateway; must match `environment`. */\n baseUrl?: string;\n /** Registered HTTPS notification URL sent with each payment. */\n notificationUrl?: string;\n timeoutMs?: number;\n /** Retries for creation reuse the same Idempotency-Key (verified 24h Papi dedup). */\n maxCreationRetries?: number;\n retryInitialDelayMs?: number;\n fetch?: PapiFetch;\n}\n\ntype PapiCapabilities = ProviderCapabilities & { idempotentCreation: boolean };\n\nconst METHODS: PaymentMethod[] = ['mvola', 'orange_money', 'airtel_money'];\nconst PROVIDER = 'papi';\n\nfunction validateConfig(config: PapiConfig): string {\n if (!config || typeof config.apiKey !== 'string' || config.apiKey.trim() === '') {\n throw new AuthenticationError('Papi apiKey is required', undefined, PROVIDER);\n }\n if (config.environment !== 'sandbox' && config.environment !== 'production') {\n throw new ValidationError(\n \"Papi environment must be explicitly 'sandbox' or 'production'\",\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n const baseUrl = (config.baseUrl ?? PAPI_BASE_URLS[config.environment]).replace(/\\/+$/, '');\n if (!baseUrl.startsWith('https://')) {\n throw new ValidationError(\n 'Papi baseUrl must use https',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n const looksSandbox = /sandbox|test|dev/i.test(new URL(baseUrl).hostname);\n if (config.environment === 'production' && looksSandbox) {\n throw new ValidationError(\n 'Papi production environment configured with a sandbox base URL',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n if (config.environment === 'sandbox' && !looksSandbox) {\n throw new ValidationError(\n 'Papi sandbox environment configured with a non-sandbox base URL',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n return baseUrl;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new ValidationError(\n 'Unexpected Papi response shape',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n return value as Record<string, unknown>;\n}\n\nexport class PapiPaymentProvider implements PaymentProvider {\n public readonly id = PROVIDER;\n public readonly name = PROVIDER;\n private readonly apiKey: string;\n private readonly webhookSecret?: string;\n private readonly baseUrl: string;\n private readonly environment: PapiEnvironment;\n private readonly notificationUrl?: string;\n private readonly timeoutMs: number;\n private readonly maxCreationRetries: number;\n private readonly retryInitialDelayMs: number;\n private readonly fetchImpl: PapiFetch;\n private readonly onUnknownStatus?: (raw: string, reference: string) => void;\n\n constructor(config: PapiConfig & { onUnknownStatus?: (raw: string, reference: string) => void }) {\n this.baseUrl = validateConfig(config);\n this.apiKey = config.apiKey;\n this.environment = config.environment;\n this.webhookSecret = config.webhookSecret;\n this.notificationUrl = config.notificationUrl;\n this.timeoutMs = config.timeoutMs ?? 15_000;\n this.maxCreationRetries = config.maxCreationRetries ?? 2;\n this.retryInitialDelayMs = config.retryInitialDelayMs ?? 200;\n this.fetchImpl = config.fetch ?? ((url, init) => fetch(url, init));\n this.onUnknownStatus = config.onUnknownStatus;\n }\n\n /** Never exposes credentials. */\n public toJSON(): Record<string, unknown> {\n return { id: this.id, environment: this.environment, baseUrl: this.baseUrl };\n }\n\n /**\n * Capability truth table. `feeQuote` is false: no Papi fee schedule is verified,\n * so no fees are reported or hardcoded (PAPI-07).\n */\n public getCapabilities(): PapiCapabilities {\n return {\n payment: true,\n asynchronousConfirmation: true,\n synchronousConfirmation: true, // Orange temporary-code flow answers synchronously\n webhooks: true,\n polling: true,\n refunds: false,\n payouts: false,\n paymentLinks: false,\n hostedCheckout: false,\n feeQuote: false,\n cancellation: false,\n supportedMethods: [...METHODS],\n syncConfirmation: true,\n asyncConfirmation: true,\n webhookEvents: true,\n feeCalculation: false,\n idempotentCreation: true,\n };\n }\n\n private headers(extra: Record<string, string> = {}): Record<string, string> {\n return {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...extra,\n };\n }\n\n private buildBody(request: ProviderCreatePaymentRequest): Record<string, unknown> {\n if (!METHODS.includes(request.paymentMethod)) {\n throw new UnsupportedOperationError(\n `Papi does not support payment method '${String(request.paymentMethod)}'`,\n { paymentMethod: request.paymentMethod },\n PROVIDER,\n );\n }\n if (!request.idempotencyKey) {\n throw new ValidationError(\n 'Papi creation requires an idempotency key',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n\n const providerData = request.providerData ?? {};\n const temporaryCode = providerData.temporaryCode;\n const body: Record<string, unknown> = {\n amount: request.amount,\n currency: request.currency,\n merchantPaymentReference: request.reference,\n paymentMethod: request.paymentMethod,\n customer: {\n phone: toInternationalFormat(request.customer.phone),\n ...(request.customer.name ? { name: request.customer.name } : {}),\n ...(request.customer.email ? { email: request.customer.email } : {}),\n },\n };\n if (this.notificationUrl) body.notificationUrl = this.notificationUrl;\n if (request.description) body.description = request.description;\n\n if (request.paymentMethod === 'orange_money') {\n if (typeof temporaryCode !== 'string' || !/^\\d{4,8}$/.test(temporaryCode)) {\n throw new ValidationError(\n 'Orange Money requires providerData.temporaryCode (numeric code generated by the customer)',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n body.temporaryCode = temporaryCode;\n } else if (temporaryCode !== undefined) {\n throw new ValidationError(\n 'providerData.temporaryCode is only valid for orange_money',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n return body;\n }\n\n private toProviderPayment(payload: unknown, fallbackMethod?: PaymentMethod): ProviderPayment {\n const p = asRecord(payload);\n const providerReference = p.paymentReference;\n if (typeof providerReference !== 'string' || providerReference === '') {\n throw new ValidationError(\n 'Papi response missing paymentReference',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n const method = (METHODS as unknown[]).includes(p.paymentMethod)\n ? (p.paymentMethod as PaymentMethod)\n : fallbackMethod;\n if (!method || typeof p.amount !== 'number') {\n throw new ValidationError(\n 'Papi response missing amount or paymentMethod',\n 'VALIDATION_FAILED',\n undefined,\n PROVIDER,\n );\n }\n const raw = typeof p.paymentStatus === 'string' ? p.paymentStatus : String(p.paymentStatus);\n const { status, known } = mapPapiStatus(p.paymentStatus);\n const reference =\n typeof p.merchantPaymentReference === 'string' ? p.merchantPaymentReference : '';\n if (!known) this.onUnknownStatus?.(raw, providerReference);\n return {\n provider: PROVIDER,\n providerReference,\n reference,\n amount: p.amount,\n currency: 'MGA',\n status,\n paymentMethod: method,\n rawProviderStatus: raw,\n };\n }\n\n public async createPayment(request: ProviderCreatePaymentRequest): Promise<ProviderPayment> {\n const body = this.buildBody(request);\n const send = () =>\n papiRequest({\n method: 'POST',\n url: `${this.baseUrl}/v1/payments`,\n headers: this.headers({ 'Idempotency-Key': request.idempotencyKey }),\n body,\n timeoutMs: this.timeoutMs,\n fetchImpl: this.fetchImpl,\n uncertainOnFailure: true,\n });\n\n // Safe only because Papi dedupes on Idempotency-Key for 24h; identical key + body => same payment.\n const payload = await executeWithRetry(send, {\n maxRetries: this.maxCreationRetries,\n initialDelayMs: this.retryInitialDelayMs,\n shouldRetry: (e) => {\n const code = (e as { code?: string }).code;\n return code === 'PROVIDER_TIMEOUT' || code === 'PROVIDER_UNAVAILABLE';\n },\n });\n return this.toProviderPayment(payload, request.paymentMethod);\n }\n\n public async getPayment(lookupOrRef: string | PaymentLookup): Promise<ProviderPayment> {\n const ref = typeof lookupOrRef === 'string' ? lookupOrRef : lookupOrRef.providerReference;\n if (!ref) {\n throw new UnsupportedOperationError(\n 'Papi status lookup requires the provider paymentReference',\n undefined,\n PROVIDER,\n );\n }\n const payload = await executeWithRetry(\n () =>\n papiRequest({\n method: 'GET',\n url: `${this.baseUrl}/v1/payments/${encodeURIComponent(ref)}`,\n headers: this.headers(),\n timeoutMs: this.timeoutMs,\n fetchImpl: this.fetchImpl,\n }),\n {\n maxRetries: 2,\n initialDelayMs: this.retryInitialDelayMs,\n shouldRetry: (e) => {\n const code = (e as { code?: string }).code;\n return code === 'PROVIDER_TIMEOUT' || code === 'PROVIDER_UNAVAILABLE';\n },\n },\n );\n return this.toProviderPayment(payload);\n }\n\n /** Verifies HMAC-SHA256 over the exact raw bytes, then parses. Nothing is parsed before verification. */\n public async verifyWebhook(\n request: ProviderWebhookRequest,\n ): Promise<ProviderWebhookEvent & { environment: PapiEnvironment }> {\n if (!this.webhookSecret) {\n throw new AuthenticationError('Papi webhookSecret is not configured', undefined, PROVIDER);\n }\n if (!(request.rawBody instanceof Uint8Array)) {\n throw new WebhookVerificationError('Webhook requires raw bytes', undefined, PROVIDER);\n }\n\n const headerKey = Object.keys(request.headers).find(\n (k) => k.toLowerCase() === 'x-papi-signature',\n );\n const headerValue = headerKey ? request.headers[headerKey] : undefined;\n const signature = Array.isArray(headerValue) ? headerValue[0] : headerValue;\n if (typeof signature !== 'string' || !/^[0-9a-fA-F]{64}$/.test(signature.trim())) {\n throw new WebhookVerificationError(\n 'Missing or malformed X-Papi-Signature',\n undefined,\n PROVIDER,\n );\n }\n\n const expected = createHmac('sha256', this.webhookSecret).update(request.rawBody).digest();\n const received = Buffer.from(signature.trim(), 'hex');\n if (!timingSafeEqual(expected, received)) {\n throw new WebhookVerificationError('Invalid webhook signature', undefined, PROVIDER);\n }\n\n let payload: Record<string, unknown>;\n try {\n payload = asRecord(JSON.parse(Buffer.from(request.rawBody).toString('utf8')));\n } catch {\n throw new WebhookVerificationError('Webhook payload is not valid JSON', undefined, PROVIDER);\n }\n if (\n typeof payload.eventId !== 'string' ||\n typeof payload.paymentReference !== 'string' ||\n typeof payload.occurredAt !== 'string'\n ) {\n throw new WebhookVerificationError(\n 'Webhook payload missing required fields',\n undefined,\n PROVIDER,\n );\n }\n\n const raw = String(payload.paymentStatus);\n const { status, known } = mapPapiStatus(payload.paymentStatus);\n if (!known) this.onUnknownStatus?.(raw, payload.paymentReference);\n return {\n providerEventId: payload.eventId,\n providerReference: payload.paymentReference,\n reference:\n typeof payload.merchantPaymentReference === 'string'\n ? payload.merchantPaymentReference\n : undefined,\n status,\n occurredAt: payload.occurredAt,\n rawProviderStatus: raw,\n environment: this.environment,\n };\n }\n}\n\nexport { PapiPaymentProvider as PapiProvider };\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { PaymentStatus, PaymentProvider, ProviderCapabilities, ProviderCreatePaymentRequest, ProviderPayment, PaymentLookup, ProviderWebhookRequest, ProviderWebhookEvent } from '@madapay/core';
|
|
2
|
+
|
|
3
|
+
type PapiFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
4
|
+
|
|
5
|
+
interface MappedStatus {
|
|
6
|
+
status: PaymentStatus;
|
|
7
|
+
known: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Unknown statuses stay nonterminal ('processing') and are flagged for diagnostics. */
|
|
10
|
+
declare function mapPapiStatus(raw: unknown): MappedStatus;
|
|
11
|
+
|
|
12
|
+
type PapiEnvironment = 'sandbox' | 'production';
|
|
13
|
+
declare const PAPI_BASE_URLS: Record<PapiEnvironment, string>;
|
|
14
|
+
interface PapiConfig {
|
|
15
|
+
/** Server-side API key. Never expose to browsers or clients. */
|
|
16
|
+
apiKey: string;
|
|
17
|
+
/** Selects the base URL and guards against sandbox/production mixups. */
|
|
18
|
+
environment: PapiEnvironment;
|
|
19
|
+
/** Per-application secret used to verify `X-Papi-Signature`. */
|
|
20
|
+
webhookSecret?: string;
|
|
21
|
+
/** Override only for a documented gateway; must match `environment`. */
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
/** Registered HTTPS notification URL sent with each payment. */
|
|
24
|
+
notificationUrl?: string;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
/** Retries for creation reuse the same Idempotency-Key (verified 24h Papi dedup). */
|
|
27
|
+
maxCreationRetries?: number;
|
|
28
|
+
retryInitialDelayMs?: number;
|
|
29
|
+
fetch?: PapiFetch;
|
|
30
|
+
}
|
|
31
|
+
type PapiCapabilities = ProviderCapabilities & {
|
|
32
|
+
idempotentCreation: boolean;
|
|
33
|
+
};
|
|
34
|
+
declare class PapiPaymentProvider implements PaymentProvider {
|
|
35
|
+
readonly id = "papi";
|
|
36
|
+
readonly name = "papi";
|
|
37
|
+
private readonly apiKey;
|
|
38
|
+
private readonly webhookSecret?;
|
|
39
|
+
private readonly baseUrl;
|
|
40
|
+
private readonly environment;
|
|
41
|
+
private readonly notificationUrl?;
|
|
42
|
+
private readonly timeoutMs;
|
|
43
|
+
private readonly maxCreationRetries;
|
|
44
|
+
private readonly retryInitialDelayMs;
|
|
45
|
+
private readonly fetchImpl;
|
|
46
|
+
private readonly onUnknownStatus?;
|
|
47
|
+
constructor(config: PapiConfig & {
|
|
48
|
+
onUnknownStatus?: (raw: string, reference: string) => void;
|
|
49
|
+
});
|
|
50
|
+
/** Never exposes credentials. */
|
|
51
|
+
toJSON(): Record<string, unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* Capability truth table. `feeQuote` is false: no Papi fee schedule is verified,
|
|
54
|
+
* so no fees are reported or hardcoded (PAPI-07).
|
|
55
|
+
*/
|
|
56
|
+
getCapabilities(): PapiCapabilities;
|
|
57
|
+
private headers;
|
|
58
|
+
private buildBody;
|
|
59
|
+
private toProviderPayment;
|
|
60
|
+
createPayment(request: ProviderCreatePaymentRequest): Promise<ProviderPayment>;
|
|
61
|
+
getPayment(lookupOrRef: string | PaymentLookup): Promise<ProviderPayment>;
|
|
62
|
+
/** Verifies HMAC-SHA256 over the exact raw bytes, then parses. Nothing is parsed before verification. */
|
|
63
|
+
verifyWebhook(request: ProviderWebhookRequest): Promise<ProviderWebhookEvent & {
|
|
64
|
+
environment: PapiEnvironment;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { PAPI_BASE_URLS, type PapiConfig, type PapiEnvironment, PapiPaymentProvider, PapiPaymentProvider as PapiProvider, mapPapiStatus };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { PaymentStatus, PaymentProvider, ProviderCapabilities, ProviderCreatePaymentRequest, ProviderPayment, PaymentLookup, ProviderWebhookRequest, ProviderWebhookEvent } from '@madapay/core';
|
|
2
|
+
|
|
3
|
+
type PapiFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
4
|
+
|
|
5
|
+
interface MappedStatus {
|
|
6
|
+
status: PaymentStatus;
|
|
7
|
+
known: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Unknown statuses stay nonterminal ('processing') and are flagged for diagnostics. */
|
|
10
|
+
declare function mapPapiStatus(raw: unknown): MappedStatus;
|
|
11
|
+
|
|
12
|
+
type PapiEnvironment = 'sandbox' | 'production';
|
|
13
|
+
declare const PAPI_BASE_URLS: Record<PapiEnvironment, string>;
|
|
14
|
+
interface PapiConfig {
|
|
15
|
+
/** Server-side API key. Never expose to browsers or clients. */
|
|
16
|
+
apiKey: string;
|
|
17
|
+
/** Selects the base URL and guards against sandbox/production mixups. */
|
|
18
|
+
environment: PapiEnvironment;
|
|
19
|
+
/** Per-application secret used to verify `X-Papi-Signature`. */
|
|
20
|
+
webhookSecret?: string;
|
|
21
|
+
/** Override only for a documented gateway; must match `environment`. */
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
/** Registered HTTPS notification URL sent with each payment. */
|
|
24
|
+
notificationUrl?: string;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
/** Retries for creation reuse the same Idempotency-Key (verified 24h Papi dedup). */
|
|
27
|
+
maxCreationRetries?: number;
|
|
28
|
+
retryInitialDelayMs?: number;
|
|
29
|
+
fetch?: PapiFetch;
|
|
30
|
+
}
|
|
31
|
+
type PapiCapabilities = ProviderCapabilities & {
|
|
32
|
+
idempotentCreation: boolean;
|
|
33
|
+
};
|
|
34
|
+
declare class PapiPaymentProvider implements PaymentProvider {
|
|
35
|
+
readonly id = "papi";
|
|
36
|
+
readonly name = "papi";
|
|
37
|
+
private readonly apiKey;
|
|
38
|
+
private readonly webhookSecret?;
|
|
39
|
+
private readonly baseUrl;
|
|
40
|
+
private readonly environment;
|
|
41
|
+
private readonly notificationUrl?;
|
|
42
|
+
private readonly timeoutMs;
|
|
43
|
+
private readonly maxCreationRetries;
|
|
44
|
+
private readonly retryInitialDelayMs;
|
|
45
|
+
private readonly fetchImpl;
|
|
46
|
+
private readonly onUnknownStatus?;
|
|
47
|
+
constructor(config: PapiConfig & {
|
|
48
|
+
onUnknownStatus?: (raw: string, reference: string) => void;
|
|
49
|
+
});
|
|
50
|
+
/** Never exposes credentials. */
|
|
51
|
+
toJSON(): Record<string, unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* Capability truth table. `feeQuote` is false: no Papi fee schedule is verified,
|
|
54
|
+
* so no fees are reported or hardcoded (PAPI-07).
|
|
55
|
+
*/
|
|
56
|
+
getCapabilities(): PapiCapabilities;
|
|
57
|
+
private headers;
|
|
58
|
+
private buildBody;
|
|
59
|
+
private toProviderPayment;
|
|
60
|
+
createPayment(request: ProviderCreatePaymentRequest): Promise<ProviderPayment>;
|
|
61
|
+
getPayment(lookupOrRef: string | PaymentLookup): Promise<ProviderPayment>;
|
|
62
|
+
/** Verifies HMAC-SHA256 over the exact raw bytes, then parses. Nothing is parsed before verification. */
|
|
63
|
+
verifyWebhook(request: ProviderWebhookRequest): Promise<ProviderWebhookEvent & {
|
|
64
|
+
environment: PapiEnvironment;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { PAPI_BASE_URLS, type PapiConfig, type PapiEnvironment, PapiPaymentProvider, PapiPaymentProvider as PapiProvider, mapPapiStatus };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
import { UnsupportedOperationError, ValidationError, toInternationalFormat, executeWithRetry, AuthenticationError, WebhookVerificationError, ProviderTimeoutError, ProviderUnavailableError, PaymentNotFoundError, DuplicatePaymentError } from '@madapay/core';
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
var PROVIDER = "papi";
|
|
6
|
+
function safeProviderError(payload) {
|
|
7
|
+
if (typeof payload !== "object" || payload === null) return {};
|
|
8
|
+
const p = payload;
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const k of ["code", "error", "message"]) {
|
|
11
|
+
if (typeof p[k] === "string") out[`provider_${k}`] = p[k].slice(0, 200);
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
async function papiRequest(opts) {
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
18
|
+
const onAbort = () => controller.abort();
|
|
19
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
20
|
+
let response;
|
|
21
|
+
try {
|
|
22
|
+
response = await opts.fetchImpl(opts.url, {
|
|
23
|
+
method: opts.method,
|
|
24
|
+
headers: opts.headers,
|
|
25
|
+
body: opts.body === void 0 ? void 0 : JSON.stringify(opts.body),
|
|
26
|
+
signal: controller.signal
|
|
27
|
+
});
|
|
28
|
+
} catch {
|
|
29
|
+
if (opts.signal?.aborted) throw new Error("Operation aborted by caller");
|
|
30
|
+
const timedOut = controller.signal.aborted;
|
|
31
|
+
if (opts.uncertainOnFailure || timedOut) {
|
|
32
|
+
throw new ProviderTimeoutError(
|
|
33
|
+
timedOut ? `Papi request timed out after ${opts.timeoutMs}ms` : "Papi request failed after dispatch; outcome is uncertain (network error)",
|
|
34
|
+
{ timeoutMs: opts.timeoutMs },
|
|
35
|
+
PROVIDER
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
throw new ProviderUnavailableError("Papi is unreachable", void 0, PROVIDER);
|
|
39
|
+
} finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
42
|
+
}
|
|
43
|
+
const text = await response.text().catch(() => "");
|
|
44
|
+
let payload;
|
|
45
|
+
try {
|
|
46
|
+
payload = text ? JSON.parse(text) : void 0;
|
|
47
|
+
} catch {
|
|
48
|
+
payload = void 0;
|
|
49
|
+
}
|
|
50
|
+
if (response.ok) {
|
|
51
|
+
if (payload === void 0) {
|
|
52
|
+
throw opts.uncertainOnFailure ? new ProviderTimeoutError(
|
|
53
|
+
"Papi returned an unreadable success response; outcome is uncertain",
|
|
54
|
+
{ status: response.status },
|
|
55
|
+
PROVIDER
|
|
56
|
+
) : new ProviderUnavailableError("Papi returned an unreadable response", void 0, PROVIDER);
|
|
57
|
+
}
|
|
58
|
+
return payload;
|
|
59
|
+
}
|
|
60
|
+
const details = { status: response.status, ...safeProviderError(payload) };
|
|
61
|
+
const s = response.status;
|
|
62
|
+
if (s === 401 || s === 403) {
|
|
63
|
+
throw new AuthenticationError("Papi rejected the credentials", details, PROVIDER);
|
|
64
|
+
}
|
|
65
|
+
if (s === 404) throw new PaymentNotFoundError("Papi payment not found", details, PROVIDER);
|
|
66
|
+
if (s === 409) {
|
|
67
|
+
throw new DuplicatePaymentError(
|
|
68
|
+
"Papi idempotency conflict: same key used with a different payload",
|
|
69
|
+
details,
|
|
70
|
+
PROVIDER
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (s === 429) throw new ProviderUnavailableError("Papi rate limit reached", details, PROVIDER);
|
|
74
|
+
if (s >= 500) {
|
|
75
|
+
if (opts.uncertainOnFailure) {
|
|
76
|
+
throw new ProviderTimeoutError(
|
|
77
|
+
"Papi server error after dispatch; outcome is uncertain",
|
|
78
|
+
details,
|
|
79
|
+
PROVIDER
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
throw new ProviderUnavailableError("Papi server error", details, PROVIDER);
|
|
83
|
+
}
|
|
84
|
+
throw new ValidationError("Papi rejected the request", "VALIDATION_FAILED", details, PROVIDER);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/status.ts
|
|
88
|
+
var STATUS_MAP = {
|
|
89
|
+
PENDING: "pending",
|
|
90
|
+
PROCESSING: "processing",
|
|
91
|
+
SUCCESS: "succeeded",
|
|
92
|
+
FAILED: "failed",
|
|
93
|
+
EXPIRED: "expired",
|
|
94
|
+
CANCELLED: "cancelled"
|
|
95
|
+
};
|
|
96
|
+
function mapPapiStatus(raw) {
|
|
97
|
+
const key = typeof raw === "string" ? raw.toUpperCase() : "";
|
|
98
|
+
const status = STATUS_MAP[key];
|
|
99
|
+
return status ? { status, known: true } : { status: "processing", known: false };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/index.ts
|
|
103
|
+
var PAPI_BASE_URLS = {
|
|
104
|
+
sandbox: "https://api.sandbox.papi.mg",
|
|
105
|
+
production: "https://api.papi.mg"
|
|
106
|
+
};
|
|
107
|
+
var METHODS = ["mvola", "orange_money", "airtel_money"];
|
|
108
|
+
var PROVIDER2 = "papi";
|
|
109
|
+
function validateConfig(config) {
|
|
110
|
+
if (!config || typeof config.apiKey !== "string" || config.apiKey.trim() === "") {
|
|
111
|
+
throw new AuthenticationError("Papi apiKey is required", void 0, PROVIDER2);
|
|
112
|
+
}
|
|
113
|
+
if (config.environment !== "sandbox" && config.environment !== "production") {
|
|
114
|
+
throw new ValidationError(
|
|
115
|
+
"Papi environment must be explicitly 'sandbox' or 'production'",
|
|
116
|
+
"VALIDATION_FAILED",
|
|
117
|
+
void 0,
|
|
118
|
+
PROVIDER2
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const baseUrl = (config.baseUrl ?? PAPI_BASE_URLS[config.environment]).replace(/\/+$/, "");
|
|
122
|
+
if (!baseUrl.startsWith("https://")) {
|
|
123
|
+
throw new ValidationError(
|
|
124
|
+
"Papi baseUrl must use https",
|
|
125
|
+
"VALIDATION_FAILED",
|
|
126
|
+
void 0,
|
|
127
|
+
PROVIDER2
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const looksSandbox = /sandbox|test|dev/i.test(new URL(baseUrl).hostname);
|
|
131
|
+
if (config.environment === "production" && looksSandbox) {
|
|
132
|
+
throw new ValidationError(
|
|
133
|
+
"Papi production environment configured with a sandbox base URL",
|
|
134
|
+
"VALIDATION_FAILED",
|
|
135
|
+
void 0,
|
|
136
|
+
PROVIDER2
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (config.environment === "sandbox" && !looksSandbox) {
|
|
140
|
+
throw new ValidationError(
|
|
141
|
+
"Papi sandbox environment configured with a non-sandbox base URL",
|
|
142
|
+
"VALIDATION_FAILED",
|
|
143
|
+
void 0,
|
|
144
|
+
PROVIDER2
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
return baseUrl;
|
|
148
|
+
}
|
|
149
|
+
function asRecord(value) {
|
|
150
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
151
|
+
throw new ValidationError(
|
|
152
|
+
"Unexpected Papi response shape",
|
|
153
|
+
"VALIDATION_FAILED",
|
|
154
|
+
void 0,
|
|
155
|
+
PROVIDER2
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
var PapiPaymentProvider = class {
|
|
161
|
+
id = PROVIDER2;
|
|
162
|
+
name = PROVIDER2;
|
|
163
|
+
apiKey;
|
|
164
|
+
webhookSecret;
|
|
165
|
+
baseUrl;
|
|
166
|
+
environment;
|
|
167
|
+
notificationUrl;
|
|
168
|
+
timeoutMs;
|
|
169
|
+
maxCreationRetries;
|
|
170
|
+
retryInitialDelayMs;
|
|
171
|
+
fetchImpl;
|
|
172
|
+
onUnknownStatus;
|
|
173
|
+
constructor(config) {
|
|
174
|
+
this.baseUrl = validateConfig(config);
|
|
175
|
+
this.apiKey = config.apiKey;
|
|
176
|
+
this.environment = config.environment;
|
|
177
|
+
this.webhookSecret = config.webhookSecret;
|
|
178
|
+
this.notificationUrl = config.notificationUrl;
|
|
179
|
+
this.timeoutMs = config.timeoutMs ?? 15e3;
|
|
180
|
+
this.maxCreationRetries = config.maxCreationRetries ?? 2;
|
|
181
|
+
this.retryInitialDelayMs = config.retryInitialDelayMs ?? 200;
|
|
182
|
+
this.fetchImpl = config.fetch ?? ((url, init) => fetch(url, init));
|
|
183
|
+
this.onUnknownStatus = config.onUnknownStatus;
|
|
184
|
+
}
|
|
185
|
+
/** Never exposes credentials. */
|
|
186
|
+
toJSON() {
|
|
187
|
+
return { id: this.id, environment: this.environment, baseUrl: this.baseUrl };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Capability truth table. `feeQuote` is false: no Papi fee schedule is verified,
|
|
191
|
+
* so no fees are reported or hardcoded (PAPI-07).
|
|
192
|
+
*/
|
|
193
|
+
getCapabilities() {
|
|
194
|
+
return {
|
|
195
|
+
payment: true,
|
|
196
|
+
asynchronousConfirmation: true,
|
|
197
|
+
synchronousConfirmation: true,
|
|
198
|
+
// Orange temporary-code flow answers synchronously
|
|
199
|
+
webhooks: true,
|
|
200
|
+
polling: true,
|
|
201
|
+
refunds: false,
|
|
202
|
+
payouts: false,
|
|
203
|
+
paymentLinks: false,
|
|
204
|
+
hostedCheckout: false,
|
|
205
|
+
feeQuote: false,
|
|
206
|
+
cancellation: false,
|
|
207
|
+
supportedMethods: [...METHODS],
|
|
208
|
+
syncConfirmation: true,
|
|
209
|
+
asyncConfirmation: true,
|
|
210
|
+
webhookEvents: true,
|
|
211
|
+
feeCalculation: false,
|
|
212
|
+
idempotentCreation: true
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
headers(extra = {}) {
|
|
216
|
+
return {
|
|
217
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
218
|
+
"Content-Type": "application/json",
|
|
219
|
+
Accept: "application/json",
|
|
220
|
+
...extra
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
buildBody(request) {
|
|
224
|
+
if (!METHODS.includes(request.paymentMethod)) {
|
|
225
|
+
throw new UnsupportedOperationError(
|
|
226
|
+
`Papi does not support payment method '${String(request.paymentMethod)}'`,
|
|
227
|
+
{ paymentMethod: request.paymentMethod },
|
|
228
|
+
PROVIDER2
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
if (!request.idempotencyKey) {
|
|
232
|
+
throw new ValidationError(
|
|
233
|
+
"Papi creation requires an idempotency key",
|
|
234
|
+
"VALIDATION_FAILED",
|
|
235
|
+
void 0,
|
|
236
|
+
PROVIDER2
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const providerData = request.providerData ?? {};
|
|
240
|
+
const temporaryCode = providerData.temporaryCode;
|
|
241
|
+
const body = {
|
|
242
|
+
amount: request.amount,
|
|
243
|
+
currency: request.currency,
|
|
244
|
+
merchantPaymentReference: request.reference,
|
|
245
|
+
paymentMethod: request.paymentMethod,
|
|
246
|
+
customer: {
|
|
247
|
+
phone: toInternationalFormat(request.customer.phone),
|
|
248
|
+
...request.customer.name ? { name: request.customer.name } : {},
|
|
249
|
+
...request.customer.email ? { email: request.customer.email } : {}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
if (this.notificationUrl) body.notificationUrl = this.notificationUrl;
|
|
253
|
+
if (request.description) body.description = request.description;
|
|
254
|
+
if (request.paymentMethod === "orange_money") {
|
|
255
|
+
if (typeof temporaryCode !== "string" || !/^\d{4,8}$/.test(temporaryCode)) {
|
|
256
|
+
throw new ValidationError(
|
|
257
|
+
"Orange Money requires providerData.temporaryCode (numeric code generated by the customer)",
|
|
258
|
+
"VALIDATION_FAILED",
|
|
259
|
+
void 0,
|
|
260
|
+
PROVIDER2
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
body.temporaryCode = temporaryCode;
|
|
264
|
+
} else if (temporaryCode !== void 0) {
|
|
265
|
+
throw new ValidationError(
|
|
266
|
+
"providerData.temporaryCode is only valid for orange_money",
|
|
267
|
+
"VALIDATION_FAILED",
|
|
268
|
+
void 0,
|
|
269
|
+
PROVIDER2
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
return body;
|
|
273
|
+
}
|
|
274
|
+
toProviderPayment(payload, fallbackMethod) {
|
|
275
|
+
const p = asRecord(payload);
|
|
276
|
+
const providerReference = p.paymentReference;
|
|
277
|
+
if (typeof providerReference !== "string" || providerReference === "") {
|
|
278
|
+
throw new ValidationError(
|
|
279
|
+
"Papi response missing paymentReference",
|
|
280
|
+
"VALIDATION_FAILED",
|
|
281
|
+
void 0,
|
|
282
|
+
PROVIDER2
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
const method = METHODS.includes(p.paymentMethod) ? p.paymentMethod : fallbackMethod;
|
|
286
|
+
if (!method || typeof p.amount !== "number") {
|
|
287
|
+
throw new ValidationError(
|
|
288
|
+
"Papi response missing amount or paymentMethod",
|
|
289
|
+
"VALIDATION_FAILED",
|
|
290
|
+
void 0,
|
|
291
|
+
PROVIDER2
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
const raw = typeof p.paymentStatus === "string" ? p.paymentStatus : String(p.paymentStatus);
|
|
295
|
+
const { status, known } = mapPapiStatus(p.paymentStatus);
|
|
296
|
+
const reference = typeof p.merchantPaymentReference === "string" ? p.merchantPaymentReference : "";
|
|
297
|
+
if (!known) this.onUnknownStatus?.(raw, providerReference);
|
|
298
|
+
return {
|
|
299
|
+
provider: PROVIDER2,
|
|
300
|
+
providerReference,
|
|
301
|
+
reference,
|
|
302
|
+
amount: p.amount,
|
|
303
|
+
currency: "MGA",
|
|
304
|
+
status,
|
|
305
|
+
paymentMethod: method,
|
|
306
|
+
rawProviderStatus: raw
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async createPayment(request) {
|
|
310
|
+
const body = this.buildBody(request);
|
|
311
|
+
const send = () => papiRequest({
|
|
312
|
+
method: "POST",
|
|
313
|
+
url: `${this.baseUrl}/v1/payments`,
|
|
314
|
+
headers: this.headers({ "Idempotency-Key": request.idempotencyKey }),
|
|
315
|
+
body,
|
|
316
|
+
timeoutMs: this.timeoutMs,
|
|
317
|
+
fetchImpl: this.fetchImpl,
|
|
318
|
+
uncertainOnFailure: true
|
|
319
|
+
});
|
|
320
|
+
const payload = await executeWithRetry(send, {
|
|
321
|
+
maxRetries: this.maxCreationRetries,
|
|
322
|
+
initialDelayMs: this.retryInitialDelayMs,
|
|
323
|
+
shouldRetry: (e) => {
|
|
324
|
+
const code = e.code;
|
|
325
|
+
return code === "PROVIDER_TIMEOUT" || code === "PROVIDER_UNAVAILABLE";
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
return this.toProviderPayment(payload, request.paymentMethod);
|
|
329
|
+
}
|
|
330
|
+
async getPayment(lookupOrRef) {
|
|
331
|
+
const ref = typeof lookupOrRef === "string" ? lookupOrRef : lookupOrRef.providerReference;
|
|
332
|
+
if (!ref) {
|
|
333
|
+
throw new UnsupportedOperationError(
|
|
334
|
+
"Papi status lookup requires the provider paymentReference",
|
|
335
|
+
void 0,
|
|
336
|
+
PROVIDER2
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const payload = await executeWithRetry(
|
|
340
|
+
() => papiRequest({
|
|
341
|
+
method: "GET",
|
|
342
|
+
url: `${this.baseUrl}/v1/payments/${encodeURIComponent(ref)}`,
|
|
343
|
+
headers: this.headers(),
|
|
344
|
+
timeoutMs: this.timeoutMs,
|
|
345
|
+
fetchImpl: this.fetchImpl
|
|
346
|
+
}),
|
|
347
|
+
{
|
|
348
|
+
maxRetries: 2,
|
|
349
|
+
initialDelayMs: this.retryInitialDelayMs,
|
|
350
|
+
shouldRetry: (e) => {
|
|
351
|
+
const code = e.code;
|
|
352
|
+
return code === "PROVIDER_TIMEOUT" || code === "PROVIDER_UNAVAILABLE";
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
return this.toProviderPayment(payload);
|
|
357
|
+
}
|
|
358
|
+
/** Verifies HMAC-SHA256 over the exact raw bytes, then parses. Nothing is parsed before verification. */
|
|
359
|
+
async verifyWebhook(request) {
|
|
360
|
+
if (!this.webhookSecret) {
|
|
361
|
+
throw new AuthenticationError("Papi webhookSecret is not configured", void 0, PROVIDER2);
|
|
362
|
+
}
|
|
363
|
+
if (!(request.rawBody instanceof Uint8Array)) {
|
|
364
|
+
throw new WebhookVerificationError("Webhook requires raw bytes", void 0, PROVIDER2);
|
|
365
|
+
}
|
|
366
|
+
const headerKey = Object.keys(request.headers).find(
|
|
367
|
+
(k) => k.toLowerCase() === "x-papi-signature"
|
|
368
|
+
);
|
|
369
|
+
const headerValue = headerKey ? request.headers[headerKey] : void 0;
|
|
370
|
+
const signature = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
371
|
+
if (typeof signature !== "string" || !/^[0-9a-fA-F]{64}$/.test(signature.trim())) {
|
|
372
|
+
throw new WebhookVerificationError(
|
|
373
|
+
"Missing or malformed X-Papi-Signature",
|
|
374
|
+
void 0,
|
|
375
|
+
PROVIDER2
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const expected = createHmac("sha256", this.webhookSecret).update(request.rawBody).digest();
|
|
379
|
+
const received = Buffer.from(signature.trim(), "hex");
|
|
380
|
+
if (!timingSafeEqual(expected, received)) {
|
|
381
|
+
throw new WebhookVerificationError("Invalid webhook signature", void 0, PROVIDER2);
|
|
382
|
+
}
|
|
383
|
+
let payload;
|
|
384
|
+
try {
|
|
385
|
+
payload = asRecord(JSON.parse(Buffer.from(request.rawBody).toString("utf8")));
|
|
386
|
+
} catch {
|
|
387
|
+
throw new WebhookVerificationError("Webhook payload is not valid JSON", void 0, PROVIDER2);
|
|
388
|
+
}
|
|
389
|
+
if (typeof payload.eventId !== "string" || typeof payload.paymentReference !== "string" || typeof payload.occurredAt !== "string") {
|
|
390
|
+
throw new WebhookVerificationError(
|
|
391
|
+
"Webhook payload missing required fields",
|
|
392
|
+
void 0,
|
|
393
|
+
PROVIDER2
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const raw = String(payload.paymentStatus);
|
|
397
|
+
const { status, known } = mapPapiStatus(payload.paymentStatus);
|
|
398
|
+
if (!known) this.onUnknownStatus?.(raw, payload.paymentReference);
|
|
399
|
+
return {
|
|
400
|
+
providerEventId: payload.eventId,
|
|
401
|
+
providerReference: payload.paymentReference,
|
|
402
|
+
reference: typeof payload.merchantPaymentReference === "string" ? payload.merchantPaymentReference : void 0,
|
|
403
|
+
status,
|
|
404
|
+
occurredAt: payload.occurredAt,
|
|
405
|
+
rawProviderStatus: raw,
|
|
406
|
+
environment: this.environment
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export { PAPI_BASE_URLS, PapiPaymentProvider, PapiPaymentProvider as PapiProvider, mapPapiStatus };
|
|
412
|
+
//# sourceMappingURL=index.js.map
|
|
413
|
+
//# sourceMappingURL=index.js.map
|