@ngandu-dev/flexpay 2.0.0 → 2.0.4

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/README.md CHANGED
@@ -13,7 +13,7 @@ For credentials and implementation details, contact [FlexPay](https://flexpay.cd
13
13
 
14
14
  - Typed mobile-money and card payment requests
15
15
  - Transaction status checks and callback validation
16
- - Development and production API environments
16
+ - Development and production API environments with optional custom base URLs
17
17
  - ESM and CommonJS distributions with TypeScript declarations
18
18
 
19
19
  ## Requirements
@@ -44,6 +44,26 @@ import { Client as Flexpay } from "@ngandu-dev/flexpay";
44
44
  const flexpay = new Flexpay("merchant_code", "token", "dev"); // use "prod" for production
45
45
  ```
46
46
 
47
+ ### Local simulator or custom endpoints
48
+
49
+ Pass optional versioned prefixes as the fourth constructor argument:
50
+
51
+ ```ts
52
+ const flexpay = new Flexpay("TESTMERCHANT", "test-token", "dev", {
53
+ apiBaseUrl: "http://localhost:8080/api/rest/v1",
54
+ cardBaseUrl: "http://localhost:8080/v1.1",
55
+ });
56
+ ```
57
+
58
+ `apiBaseUrl` is shared by mobile payments, payouts, and status checks; the client appends
59
+ `/paymentService`, `/merchantPayOutService`, or `/check/{orderNumber}`. `cardBaseUrl` gets `/pay`
60
+ appended. Include the version path in each prefix, and omit the final service path.
61
+ Trailing slashes are removed. Each omitted option keeps its selected `dev`/`prod` default,
62
+ so existing constructor calls continue to work. Overrides must be absolute HTTP(S) URLs without
63
+ credentials, query strings, or fragments; invalid values throw during construction.
64
+ The optional `BaseUrls` type is exported from the package root. `Environment` also accepts these
65
+ options as its second argument when generating endpoint URLs directly.
66
+
47
67
  ## Usage
48
68
 
49
69
  ### Create a Payment Request
package/dist/index.d.mts CHANGED
@@ -1,9 +1,16 @@
1
1
  import * as z from 'zod';
2
2
 
3
3
  type EnvironmentType = "prod" | "dev";
4
+ /** Versioned endpoint prefixes, without the service path (for example, /pay). */
5
+ interface BaseUrls {
6
+ apiBaseUrl?: string;
7
+ cardBaseUrl?: string;
8
+ }
4
9
  declare class Environment {
5
10
  readonly value: EnvironmentType;
6
- constructor(value: EnvironmentType);
11
+ private readonly apiBaseUrl;
12
+ private readonly cardBaseUrl;
13
+ constructor(value: EnvironmentType, baseUrls?: BaseUrls);
7
14
  getMobilePaymentUrl(): string;
8
15
  getPayoutUrl(): string;
9
16
  getCheckStatusUrl(orderNumber: string): string;
@@ -136,7 +143,7 @@ type PayoutRequest = z.infer<typeof PayoutRequestSchema>;
136
143
  declare class Client {
137
144
  private readonly credential;
138
145
  private readonly env;
139
- constructor(merchant: string, token: string, env?: EnvironmentType);
146
+ constructor(merchant: string, token: string, env?: EnvironmentType, baseUrls?: BaseUrls);
140
147
  mobile(request: MobileRequest): Promise<MobileResponse>;
141
148
  card(request: CardRequest): Promise<CardResponse>;
142
149
  pay(request: MobileRequest): Promise<MobileResponse>;
@@ -166,4 +173,4 @@ declare class ClientException extends NetworkException {
166
173
  declare class ServerException extends NetworkException {
167
174
  }
168
175
 
169
- export { AccountException, type CardRequest, CardRequestSchema, type CardResponse, CardResponseSchema, type CheckResponse, CheckResponseSchema, Client, ClientException, type Currency, CurrencySchema, Environment, type EnvironmentType, type MobileRequest, MobileRequestSchema, type MobileResponse, MobileResponseSchema, NetworkException, type PayoutRequest, PayoutRequestSchema, type PayoutResponse, PayoutResponseSchema, ServerException, Status, StatusSchema, type Transaction, TransactionSchema };
176
+ export { AccountException, type BaseUrls, type CardRequest, CardRequestSchema, type CardResponse, CardResponseSchema, type CheckResponse, CheckResponseSchema, Client, ClientException, type Currency, CurrencySchema, Environment, type EnvironmentType, type MobileRequest, MobileRequestSchema, type MobileResponse, MobileResponseSchema, NetworkException, type PayoutRequest, PayoutRequestSchema, type PayoutResponse, PayoutResponseSchema, ServerException, Status, StatusSchema, type Transaction, TransactionSchema };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  import * as z from 'zod';
2
2
 
3
3
  type EnvironmentType = "prod" | "dev";
4
+ /** Versioned endpoint prefixes, without the service path (for example, /pay). */
5
+ interface BaseUrls {
6
+ apiBaseUrl?: string;
7
+ cardBaseUrl?: string;
8
+ }
4
9
  declare class Environment {
5
10
  readonly value: EnvironmentType;
6
- constructor(value: EnvironmentType);
11
+ private readonly apiBaseUrl;
12
+ private readonly cardBaseUrl;
13
+ constructor(value: EnvironmentType, baseUrls?: BaseUrls);
7
14
  getMobilePaymentUrl(): string;
8
15
  getPayoutUrl(): string;
9
16
  getCheckStatusUrl(orderNumber: string): string;
@@ -136,7 +143,7 @@ type PayoutRequest = z.infer<typeof PayoutRequestSchema>;
136
143
  declare class Client {
137
144
  private readonly credential;
138
145
  private readonly env;
139
- constructor(merchant: string, token: string, env?: EnvironmentType);
146
+ constructor(merchant: string, token: string, env?: EnvironmentType, baseUrls?: BaseUrls);
140
147
  mobile(request: MobileRequest): Promise<MobileResponse>;
141
148
  card(request: CardRequest): Promise<CardResponse>;
142
149
  pay(request: MobileRequest): Promise<MobileResponse>;
@@ -166,4 +173,4 @@ declare class ClientException extends NetworkException {
166
173
  declare class ServerException extends NetworkException {
167
174
  }
168
175
 
169
- export { AccountException, type CardRequest, CardRequestSchema, type CardResponse, CardResponseSchema, type CheckResponse, CheckResponseSchema, Client, ClientException, type Currency, CurrencySchema, Environment, type EnvironmentType, type MobileRequest, MobileRequestSchema, type MobileResponse, MobileResponseSchema, NetworkException, type PayoutRequest, PayoutRequestSchema, type PayoutResponse, PayoutResponseSchema, ServerException, Status, StatusSchema, type Transaction, TransactionSchema };
176
+ export { AccountException, type BaseUrls, type CardRequest, CardRequestSchema, type CardResponse, CardResponseSchema, type CheckResponse, CheckResponseSchema, Client, ClientException, type Currency, CurrencySchema, Environment, type EnvironmentType, type MobileRequest, MobileRequestSchema, type MobileResponse, MobileResponseSchema, NetworkException, type PayoutRequest, PayoutRequestSchema, type PayoutResponse, PayoutResponseSchema, ServerException, Status, StatusSchema, type Transaction, TransactionSchema };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var J=Object.create;var R=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var W=Object.getOwnPropertyNames;var D=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var G=(r,t)=>{for(var n in t)R(r,n,{get:t[n],enumerable:!0})},j=(r,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of W(t))!F.call(r,a)&&a!==n&&R(r,a,{get:()=>t[a],enumerable:!(o=L(t,a))||o.enumerable});return r};var H=(r,t,n)=>(n=r!=null?J(D(r)):{},j(t||!r||!r.__esModule?R(n,"default",{value:r,enumerable:!0}):n,r)),Z=r=>j(R({},"__esModule",{value:!0}),r);var K={};G(K,{AccountException:()=>h,CardRequestSchema:()=>U,CardResponseSchema:()=>C,CheckResponseSchema:()=>x,Client:()=>q,ClientException:()=>d,CurrencySchema:()=>y,Environment:()=>m,MobileRequestSchema:()=>w,MobileResponseSchema:()=>f,NetworkException:()=>i,PayoutRequestSchema:()=>k,PayoutResponseSchema:()=>P,ServerException:()=>S,Status:()=>z,StatusSchema:()=>l,TransactionSchema:()=>v});module.exports=Z(K);var m=class{constructor(t){this.value=t}value;getMobilePaymentUrl(){return`${this.getBaseUrl()}/paymentService`}getPayoutUrl(){return`${this.getBaseUrl()}/merchantPayOutService`}getCheckStatusUrl(t){return`${this.getBaseUrl()}/check/${encodeURIComponent(t)}`}getCardPaymentUrl(){return this.value==="prod"?"https://cardpayment.flexpay.cd/v1.1/pay":"https://beta-cardpayment.flexpay.cd/v1.1/pay"}getBaseUrl(){return this.value==="prod"?"https://backend.flexpay.cd/api/rest/v1":"https://beta-backend.flexpay.cd/api/rest/v1"}};var i=class r extends Error{status;type;constructor(t,n,o){let a=o!=null?`${t} (HTTP ${o}/${n??"unknown"})`:t;super(a),this.name=this.constructor.name,this.status=o??void 0,this.type=n??void 0,Object.setPrototypeOf(this,new.target.prototype)}static create(t,n,o){let a=t.trim()===""?"No message was provided":t;return o===401||o===429?new h(a,n,o):o>=400&&o<=499?new d(a,n,o):o>=500&&o<=599?new S(a,n,o):new r(a,n,o)}},h=class extends i{},d=class extends i{},S=class extends i{};var e=H(require("zod")),z=(n=>(n[n.SUCCESS=0]="SUCCESS",n[n.FAILURE=1]="FAILURE",n))(z||{});var y=e.enum(["USD","CDF"]),O=e.object({merchant:e.string().min(1,"Merchant cannot be empty"),token:e.string().min(1,"The authorization token cannot be empty")}),l=e.union([e.literal(0),e.literal(1),e.literal("0"),e.literal("1")]).transform(r=>Number(r)),N=e.union([e.number().finite(),e.string().trim().min(1).transform(r=>Number(r)).pipe(e.number().finite())]),c=e.union([e.string(),e.null()]).optional(),f=e.object({code:l,message:e.string().optional().default(""),orderNumber:c,provider_reference:c,providerReference:c,reference:c,url:c}).transform(r=>({code:r.code,message:r.message??"",orderNumber:r.orderNumber??null,providerReference:r.providerReference??r.provider_reference??null,reference:r.reference??null,url:r.url??null})),C=e.object({code:l,message:e.string().optional().default(""),orderNumber:c,url:c}),v=e.object({amount:N,amountCustomer:N,channel:c,createdAt:e.string(),currency:y,orderNumber:c,reference:e.string(),status:l}),x=e.object({code:l,message:e.string().optional().default(""),transaction:e.union([v,e.null()]).optional().default(null)}),P=e.object({code:l,message:e.string().optional().default(""),orderNumber:c}),p=e.string().min(1),_=e.object({amount:e.number().gt(0,"The transaction amount should be greater than 0"),approveUrl:p.optional().nullable(),callbackUrl:p,cancelUrl:p.optional().nullable(),currency:y,declineUrl:p.optional().nullable(),description:e.string().optional().nullable(),reference:e.string().min(1,"The transaction reference is mandatory")}),w=_.extend({phone:e.string().length(12,"The phone number should be 12 characters long, eg: 243123456789")}),U=e.object({amount:e.number().gt(0),approveUrl:p,callbackUrl:p,cancelUrl:p,currency:y,declineUrl:p,description:e.string().min(1,"The description must be provided"),homeUrl:p,reference:e.string().min(1).max(25,"The reference must be between 1 and 25 characters")}),k=e.object({amount:e.number().gt(0),callbackUrl:p,currency:y,phone:e.string().length(12,"The phone number should be 12 characters long, eg: 243123456789"),reference:e.string().min(1)});var q=class{credential;env;constructor(t,n,o="dev"){this.credential=O.parse({merchant:t,token:n}),this.env=new m(o)}async mobile(t){let n={...w.parse(t),merchant:this.credential.merchant,type:1},o=await this.requestJson("POST",this.env.getMobilePaymentUrl(),n);return this.parseWith(f,o)}async card(t){let n={...U.parse(t),authorization:`Bearer ${this.credential.token}`,merchant:this.credential.merchant},o=await this.requestJson("POST",this.env.getCardPaymentUrl(),n);return this.parseWith(C,o)}async pay(t){if("phone"in t&&typeof t.phone=="string")return this.mobile(t);if("homeUrl"in t&&typeof t.homeUrl=="string")return this.card(t);throw new Error("Unsupported request shape")}async check(t){let n=await this.requestJson("GET",this.env.getCheckStatusUrl(t));return this.parseWith(x,n)}async payout(t){let n={...k.parse(t),merchant:this.credential.merchant,type:1},o=await this.requestJson("POST",this.env.getPayoutUrl(),n);return this.parseWith(P,o)}handleCallback(t){return this.parseWith(f,t)}isSuccessful(t){return t.code===0}async requestJson(t,n,o){let a={Accept:"application/json",Authorization:`Bearer ${this.credential.token}`,"Content-Type":"application/json"},T=3,B=500;for(let b=0;b<=T;b++)try{let s=await fetch(n,{body:t==="POST"?JSON.stringify(o??{}):void 0,headers:a,method:t}),M=(s.headers.get("content-type")||"").includes("application/json"),u={};if(M)try{u=await s.json()}catch{u={}}if(!s.ok){let g=typeof u=="object"&&u!==null?u:{},A=typeof g.message=="string"?g.message:"",$=typeof g.error=="string"?g.error:"unknown",I=s.status;throw i.create(A,$,I)}return u}catch(s){if(s instanceof h||s instanceof d||s instanceof i&&typeof s.status=="number"&&s.status>=400&&s.status<500)throw s;if(b<T){let E=B*2**b;await new Promise(M=>setTimeout(M,E));continue}throw s instanceof i?s:new i(s instanceof Error?s.message:"Network error")}throw new i("Unexpected request flow")}parseWith(t,n){return t.parse(n)}};0&&(module.exports={AccountException,CardRequestSchema,CardResponseSchema,CheckResponseSchema,Client,ClientException,CurrencySchema,Environment,MobileRequestSchema,MobileResponseSchema,NetworkException,PayoutRequestSchema,PayoutResponseSchema,ServerException,Status,StatusSchema,TransactionSchema});
1
+ "use strict";var J=Object.create;var R=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,H=Object.prototype.hasOwnProperty;var G=(n,e)=>{for(var r in e)R(n,r,{get:e[r],enumerable:!0})},E=(n,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of D(e))!H.call(n,o)&&o!==r&&R(n,o,{get:()=>e[o],enumerable:!(s=W(e,o))||s.enumerable});return n};var Z=(n,e,r)=>(r=n!=null?J(F(n)):{},E(e||!n||!n.__esModule?R(r,"default",{value:n,enumerable:!0}):r,n)),_=n=>E(R({},"__esModule",{value:!0}),n);var Q={};G(Q,{AccountException:()=>d,CardRequestSchema:()=>P,CardResponseSchema:()=>U,CheckResponseSchema:()=>C,Client:()=>T,ClientException:()=>h,CurrencySchema:()=>y,Environment:()=>m,MobileRequestSchema:()=>w,MobileResponseSchema:()=>f,NetworkException:()=>i,PayoutRequestSchema:()=>q,PayoutResponseSchema:()=>x,ServerException:()=>S,Status:()=>z,StatusSchema:()=>l,TransactionSchema:()=>v});module.exports=_(Q);var m=class{constructor(e,r={}){this.value=e;this.apiBaseUrl=j(r.apiBaseUrl),this.cardBaseUrl=j(r.cardBaseUrl)}value;apiBaseUrl;cardBaseUrl;getMobilePaymentUrl(){return`${this.getBaseUrl()}/paymentService`}getPayoutUrl(){return`${this.getBaseUrl()}/merchantPayOutService`}getCheckStatusUrl(e){return`${this.getBaseUrl()}/check/${encodeURIComponent(e)}`}getCardPaymentUrl(){return this.cardBaseUrl!==void 0?`${this.cardBaseUrl}/pay`:this.value==="prod"?"https://cardpayment.flexpay.cd/v1.1/pay":"https://beta-cardpayment.flexpay.cd/v1.1/pay"}getBaseUrl(){return this.apiBaseUrl!==void 0?this.apiBaseUrl:this.value==="prod"?"https://backend.flexpay.cd/api/rest/v1":"https://beta-backend.flexpay.cd/api/rest/v1"}};function j(n){if(n===void 0)return;let e=n.trim().replace(/\/+$/,""),r;try{r=new URL(e)}catch{throw new Error("Base URLs must be absolute HTTP(S) URLs without credentials, query, or fragment")}if(!/^https?:\/\//i.test(e)||!r.hostname||r.username||r.password||e.includes("?")||e.includes("#")||e.includes("\\"))throw new Error("Base URLs must be absolute HTTP(S) URLs without credentials, query, or fragment");return e}var i=class n extends Error{status;type;constructor(e,r,s){let o=s!=null?`${e} (HTTP ${s}/${r??"unknown"})`:e;super(o),this.name=this.constructor.name,this.status=s??void 0,this.type=r??void 0,Object.setPrototypeOf(this,new.target.prototype)}static create(e,r,s){let o=e.trim()===""?"No message was provided":e;return s===401||s===429?new d(o,r,s):s>=400&&s<=499?new h(o,r,s):s>=500&&s<=599?new S(o,r,s):new n(o,r,s)}},d=class extends i{},h=class extends i{},S=class extends i{};var t=Z(require("zod")),z=(r=>(r[r.SUCCESS=0]="SUCCESS",r[r.FAILURE=1]="FAILURE",r))(z||{});var y=t.enum(["USD","CDF"]),O=t.object({merchant:t.string().min(1,"Merchant cannot be empty"),token:t.string().min(1,"The authorization token cannot be empty")}),l=t.union([t.literal(0),t.literal(1),t.literal("0"),t.literal("1")]).transform(n=>Number(n)),N=t.union([t.number().finite(),t.string().trim().min(1).transform(n=>Number(n)).pipe(t.number().finite())]),c=t.union([t.string(),t.null()]).optional(),f=t.object({code:l,message:t.string().optional().default(""),orderNumber:c,provider_reference:c,providerReference:c,reference:c,url:c}).transform(n=>({code:n.code,message:n.message??"",orderNumber:n.orderNumber??null,providerReference:n.providerReference??n.provider_reference??null,reference:n.reference??null,url:n.url??null})),U=t.object({code:l,message:t.string().optional().default(""),orderNumber:c,url:c}),v=t.object({amount:N,amountCustomer:N,channel:c,createdAt:t.string(),currency:y,orderNumber:c,reference:t.string(),status:l}),C=t.object({code:l,message:t.string().optional().default(""),transaction:t.union([v,t.null()]).optional().default(null)}),x=t.object({code:l,message:t.string().optional().default(""),orderNumber:c}),p=t.string().min(1),K=t.object({amount:t.number().gt(0,"The transaction amount should be greater than 0"),approveUrl:p.optional().nullable(),callbackUrl:p,cancelUrl:p.optional().nullable(),currency:y,declineUrl:p.optional().nullable(),description:t.string().optional().nullable(),reference:t.string().min(1,"The transaction reference is mandatory")}),w=K.extend({phone:t.string().length(12,"The phone number should be 12 characters long, eg: 243123456789")}),P=t.object({amount:t.number().gt(0),approveUrl:p,callbackUrl:p,cancelUrl:p,currency:y,declineUrl:p,description:t.string().min(1,"The description must be provided"),homeUrl:p,reference:t.string().min(1).max(25,"The reference must be between 1 and 25 characters")}),q=t.object({amount:t.number().gt(0),callbackUrl:p,currency:y,phone:t.string().length(12,"The phone number should be 12 characters long, eg: 243123456789"),reference:t.string().min(1)});var T=class{credential;env;constructor(e,r,s="dev",o={}){this.credential=O.parse({merchant:e,token:r}),this.env=new m(s,o)}async mobile(e){let r={...w.parse(e),merchant:this.credential.merchant,type:1},s=await this.requestJson("POST",this.env.getMobilePaymentUrl(),r);return this.parseWith(f,s)}async card(e){let r={...P.parse(e),authorization:`Bearer ${this.credential.token}`,merchant:this.credential.merchant},s=await this.requestJson("POST",this.env.getCardPaymentUrl(),r);return this.parseWith(U,s)}async pay(e){if("phone"in e&&typeof e.phone=="string")return this.mobile(e);if("homeUrl"in e&&typeof e.homeUrl=="string")return this.card(e);throw new Error("Unsupported request shape")}async check(e){let r=await this.requestJson("GET",this.env.getCheckStatusUrl(e));return this.parseWith(C,r)}async payout(e){let r={...q.parse(e),merchant:this.credential.merchant,type:1},s=await this.requestJson("POST",this.env.getPayoutUrl(),r);return this.parseWith(x,s)}handleCallback(e){return this.parseWith(f,e)}isSuccessful(e){return e.code===0}async requestJson(e,r,s){let o={Accept:"application/json",Authorization:`Bearer ${this.credential.token}`,"Content-Type":"application/json"},M=3,L=500;for(let b=0;b<=M;b++)try{let a=await fetch(r,{body:e==="POST"?JSON.stringify(s??{}):void 0,headers:o,method:e}),k=(a.headers.get("content-type")||"").includes("application/json"),u={};if(k)try{u=await a.json()}catch{u={}}if(!a.ok){let g=typeof u=="object"&&u!==null?u:{},$=typeof g.message=="string"?g.message:"",A=typeof g.error=="string"?g.error:"unknown",I=a.status;throw i.create($,A,I)}return u}catch(a){if(a instanceof d||a instanceof h||a instanceof i&&typeof a.status=="number"&&a.status>=400&&a.status<500)throw a;if(b<M){let B=L*2**b;await new Promise(k=>setTimeout(k,B));continue}throw a instanceof i?a:new i(a instanceof Error?a.message:"Network error")}throw new i("Unexpected request flow")}parseWith(e,r){return e.parse(r)}};0&&(module.exports={AccountException,CardRequestSchema,CardResponseSchema,CheckResponseSchema,Client,ClientException,CurrencySchema,Environment,MobileRequestSchema,MobileResponseSchema,NetworkException,PayoutRequestSchema,PayoutResponseSchema,ServerException,Status,StatusSchema,TransactionSchema});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var m=class{constructor(t){this.value=t}value;getMobilePaymentUrl(){return`${this.getBaseUrl()}/paymentService`}getPayoutUrl(){return`${this.getBaseUrl()}/merchantPayOutService`}getCheckStatusUrl(t){return`${this.getBaseUrl()}/check/${encodeURIComponent(t)}`}getCardPaymentUrl(){return this.value==="prod"?"https://cardpayment.flexpay.cd/v1.1/pay":"https://beta-cardpayment.flexpay.cd/v1.1/pay"}getBaseUrl(){return this.value==="prod"?"https://backend.flexpay.cd/api/rest/v1":"https://beta-backend.flexpay.cd/api/rest/v1"}};var a=class o extends Error{status;type;constructor(t,n,r){let p=r!=null?`${t} (HTTP ${r}/${n??"unknown"})`:t;super(p),this.name=this.constructor.name,this.status=r??void 0,this.type=n??void 0,Object.setPrototypeOf(this,new.target.prototype)}static create(t,n,r){let p=t.trim()===""?"No message was provided":t;return r===401||r===429?new h(p,n,r):r>=400&&r<=499?new d(p,n,r):r>=500&&r<=599?new S(p,n,r):new o(p,n,r)}},h=class extends a{},d=class extends a{},S=class extends a{};import*as e from"zod";var z=(n=>(n[n.SUCCESS=0]="SUCCESS",n[n.FAILURE=1]="FAILURE",n))(z||{});var y=e.enum(["USD","CDF"]),E=e.object({merchant:e.string().min(1,"Merchant cannot be empty"),token:e.string().min(1,"The authorization token cannot be empty")}),u=e.union([e.literal(0),e.literal(1),e.literal("0"),e.literal("1")]).transform(o=>Number(o)),T=e.union([e.number().finite(),e.string().trim().min(1).transform(o=>Number(o)).pipe(e.number().finite())]),i=e.union([e.string(),e.null()]).optional(),g=e.object({code:u,message:e.string().optional().default(""),orderNumber:i,provider_reference:i,providerReference:i,reference:i,url:i}).transform(o=>({code:o.code,message:o.message??"",orderNumber:o.orderNumber??null,providerReference:o.providerReference??o.provider_reference??null,reference:o.reference??null,url:o.url??null})),C=e.object({code:u,message:e.string().optional().default(""),orderNumber:i,url:i}),j=e.object({amount:T,amountCustomer:T,channel:i,createdAt:e.string(),currency:y,orderNumber:i,reference:e.string(),status:u}),x=e.object({code:u,message:e.string().optional().default(""),transaction:e.union([j,e.null()]).optional().default(null)}),P=e.object({code:u,message:e.string().optional().default(""),orderNumber:i}),c=e.string().min(1),$=e.object({amount:e.number().gt(0,"The transaction amount should be greater than 0"),approveUrl:c.optional().nullable(),callbackUrl:c,cancelUrl:c.optional().nullable(),currency:y,declineUrl:c.optional().nullable(),description:e.string().optional().nullable(),reference:e.string().min(1,"The transaction reference is mandatory")}),w=$.extend({phone:e.string().length(12,"The phone number should be 12 characters long, eg: 243123456789")}),U=e.object({amount:e.number().gt(0),approveUrl:c,callbackUrl:c,cancelUrl:c,currency:y,declineUrl:c,description:e.string().min(1,"The description must be provided"),homeUrl:c,reference:e.string().min(1).max(25,"The reference must be between 1 and 25 characters")}),k=e.object({amount:e.number().gt(0),callbackUrl:c,currency:y,phone:e.string().length(12,"The phone number should be 12 characters long, eg: 243123456789"),reference:e.string().min(1)});var q=class{credential;env;constructor(t,n,r="dev"){this.credential=E.parse({merchant:t,token:n}),this.env=new m(r)}async mobile(t){let n={...w.parse(t),merchant:this.credential.merchant,type:1},r=await this.requestJson("POST",this.env.getMobilePaymentUrl(),n);return this.parseWith(g,r)}async card(t){let n={...U.parse(t),authorization:`Bearer ${this.credential.token}`,merchant:this.credential.merchant},r=await this.requestJson("POST",this.env.getCardPaymentUrl(),n);return this.parseWith(C,r)}async pay(t){if("phone"in t&&typeof t.phone=="string")return this.mobile(t);if("homeUrl"in t&&typeof t.homeUrl=="string")return this.card(t);throw new Error("Unsupported request shape")}async check(t){let n=await this.requestJson("GET",this.env.getCheckStatusUrl(t));return this.parseWith(x,n)}async payout(t){let n={...k.parse(t),merchant:this.credential.merchant,type:1},r=await this.requestJson("POST",this.env.getPayoutUrl(),n);return this.parseWith(P,r)}handleCallback(t){return this.parseWith(g,t)}isSuccessful(t){return t.code===0}async requestJson(t,n,r){let p={Accept:"application/json",Authorization:`Bearer ${this.credential.token}`,"Content-Type":"application/json"},M=3,N=500;for(let f=0;f<=M;f++)try{let s=await fetch(n,{body:t==="POST"?JSON.stringify(r??{}):void 0,headers:p,method:t}),R=(s.headers.get("content-type")||"").includes("application/json"),l={};if(R)try{l=await s.json()}catch{l={}}if(!s.ok){let b=typeof l=="object"&&l!==null?l:{},O=typeof b.message=="string"?b.message:"",B=typeof b.error=="string"?b.error:"unknown",A=s.status;throw a.create(O,B,A)}return l}catch(s){if(s instanceof h||s instanceof d||s instanceof a&&typeof s.status=="number"&&s.status>=400&&s.status<500)throw s;if(f<M){let v=N*2**f;await new Promise(R=>setTimeout(R,v));continue}throw s instanceof a?s:new a(s instanceof Error?s.message:"Network error")}throw new a("Unexpected request flow")}parseWith(t,n){return t.parse(n)}};export{h as AccountException,U as CardRequestSchema,C as CardResponseSchema,x as CheckResponseSchema,q as Client,d as ClientException,y as CurrencySchema,m as Environment,w as MobileRequestSchema,g as MobileResponseSchema,a as NetworkException,k as PayoutRequestSchema,P as PayoutResponseSchema,S as ServerException,z as Status,u as StatusSchema,j as TransactionSchema};
1
+ var m=class{constructor(t,r={}){this.value=t;this.apiBaseUrl=M(r.apiBaseUrl),this.cardBaseUrl=M(r.cardBaseUrl)}value;apiBaseUrl;cardBaseUrl;getMobilePaymentUrl(){return`${this.getBaseUrl()}/paymentService`}getPayoutUrl(){return`${this.getBaseUrl()}/merchantPayOutService`}getCheckStatusUrl(t){return`${this.getBaseUrl()}/check/${encodeURIComponent(t)}`}getCardPaymentUrl(){return this.cardBaseUrl!==void 0?`${this.cardBaseUrl}/pay`:this.value==="prod"?"https://cardpayment.flexpay.cd/v1.1/pay":"https://beta-cardpayment.flexpay.cd/v1.1/pay"}getBaseUrl(){return this.apiBaseUrl!==void 0?this.apiBaseUrl:this.value==="prod"?"https://backend.flexpay.cd/api/rest/v1":"https://beta-backend.flexpay.cd/api/rest/v1"}};function M(s){if(s===void 0)return;let t=s.trim().replace(/\/+$/,""),r;try{r=new URL(t)}catch{throw new Error("Base URLs must be absolute HTTP(S) URLs without credentials, query, or fragment")}if(!/^https?:\/\//i.test(t)||!r.hostname||r.username||r.password||t.includes("?")||t.includes("#")||t.includes("\\"))throw new Error("Base URLs must be absolute HTTP(S) URLs without credentials, query, or fragment");return t}var a=class s extends Error{status;type;constructor(t,r,n){let i=n!=null?`${t} (HTTP ${n}/${r??"unknown"})`:t;super(i),this.name=this.constructor.name,this.status=n??void 0,this.type=r??void 0,Object.setPrototypeOf(this,new.target.prototype)}static create(t,r,n){let i=t.trim()===""?"No message was provided":t;return n===401||n===429?new d(i,r,n):n>=400&&n<=499?new h(i,r,n):n>=500&&n<=599?new S(i,r,n):new s(i,r,n)}},d=class extends a{},h=class extends a{},S=class extends a{};import*as e from"zod";var z=(r=>(r[r.SUCCESS=0]="SUCCESS",r[r.FAILURE=1]="FAILURE",r))(z||{});var y=e.enum(["USD","CDF"]),E=e.object({merchant:e.string().min(1,"Merchant cannot be empty"),token:e.string().min(1,"The authorization token cannot be empty")}),u=e.union([e.literal(0),e.literal(1),e.literal("0"),e.literal("1")]).transform(s=>Number(s)),B=e.union([e.number().finite(),e.string().trim().min(1).transform(s=>Number(s)).pipe(e.number().finite())]),c=e.union([e.string(),e.null()]).optional(),g=e.object({code:u,message:e.string().optional().default(""),orderNumber:c,provider_reference:c,providerReference:c,reference:c,url:c}).transform(s=>({code:s.code,message:s.message??"",orderNumber:s.orderNumber??null,providerReference:s.providerReference??s.provider_reference??null,reference:s.reference??null,url:s.url??null})),U=e.object({code:u,message:e.string().optional().default(""),orderNumber:c,url:c}),j=e.object({amount:B,amountCustomer:B,channel:c,createdAt:e.string(),currency:y,orderNumber:c,reference:e.string(),status:u}),C=e.object({code:u,message:e.string().optional().default(""),transaction:e.union([j,e.null()]).optional().default(null)}),x=e.object({code:u,message:e.string().optional().default(""),orderNumber:c}),p=e.string().min(1),A=e.object({amount:e.number().gt(0,"The transaction amount should be greater than 0"),approveUrl:p.optional().nullable(),callbackUrl:p,cancelUrl:p.optional().nullable(),currency:y,declineUrl:p.optional().nullable(),description:e.string().optional().nullable(),reference:e.string().min(1,"The transaction reference is mandatory")}),w=A.extend({phone:e.string().length(12,"The phone number should be 12 characters long, eg: 243123456789")}),P=e.object({amount:e.number().gt(0),approveUrl:p,callbackUrl:p,cancelUrl:p,currency:y,declineUrl:p,description:e.string().min(1,"The description must be provided"),homeUrl:p,reference:e.string().min(1).max(25,"The reference must be between 1 and 25 characters")}),q=e.object({amount:e.number().gt(0),callbackUrl:p,currency:y,phone:e.string().length(12,"The phone number should be 12 characters long, eg: 243123456789"),reference:e.string().min(1)});var T=class{credential;env;constructor(t,r,n="dev",i={}){this.credential=E.parse({merchant:t,token:r}),this.env=new m(n,i)}async mobile(t){let r={...w.parse(t),merchant:this.credential.merchant,type:1},n=await this.requestJson("POST",this.env.getMobilePaymentUrl(),r);return this.parseWith(g,n)}async card(t){let r={...P.parse(t),authorization:`Bearer ${this.credential.token}`,merchant:this.credential.merchant},n=await this.requestJson("POST",this.env.getCardPaymentUrl(),r);return this.parseWith(U,n)}async pay(t){if("phone"in t&&typeof t.phone=="string")return this.mobile(t);if("homeUrl"in t&&typeof t.homeUrl=="string")return this.card(t);throw new Error("Unsupported request shape")}async check(t){let r=await this.requestJson("GET",this.env.getCheckStatusUrl(t));return this.parseWith(C,r)}async payout(t){let r={...q.parse(t),merchant:this.credential.merchant,type:1},n=await this.requestJson("POST",this.env.getPayoutUrl(),r);return this.parseWith(x,n)}handleCallback(t){return this.parseWith(g,t)}isSuccessful(t){return t.code===0}async requestJson(t,r,n){let i={Accept:"application/json",Authorization:`Bearer ${this.credential.token}`,"Content-Type":"application/json"},k=3,N=500;for(let f=0;f<=k;f++)try{let o=await fetch(r,{body:t==="POST"?JSON.stringify(n??{}):void 0,headers:i,method:t}),R=(o.headers.get("content-type")||"").includes("application/json"),l={};if(R)try{l=await o.json()}catch{l={}}if(!o.ok){let b=typeof l=="object"&&l!==null?l:{},O=typeof b.message=="string"?b.message:"",L=typeof b.error=="string"?b.error:"unknown",$=o.status;throw a.create(O,L,$)}return l}catch(o){if(o instanceof d||o instanceof h||o instanceof a&&typeof o.status=="number"&&o.status>=400&&o.status<500)throw o;if(f<k){let v=N*2**f;await new Promise(R=>setTimeout(R,v));continue}throw o instanceof a?o:new a(o instanceof Error?o.message:"Network error")}throw new a("Unexpected request flow")}parseWith(t,r){return t.parse(r)}};export{d as AccountException,P as CardRequestSchema,U as CardResponseSchema,C as CheckResponseSchema,T as Client,h as ClientException,y as CurrencySchema,m as Environment,w as MobileRequestSchema,g as MobileResponseSchema,a as NetworkException,q as PayoutRequestSchema,x as PayoutResponseSchema,S as ServerException,z as Status,u as StatusSchema,j as TransactionSchema};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngandu-dev/flexpay",
3
- "version": "2.0.0",
3
+ "version": "2.0.4",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/index.mjs",
@@ -41,12 +41,7 @@
41
41
  },
42
42
  "repository": {
43
43
  "type": "git",
44
- "url": "git+https://github.com/ngandu-dev/flexpay.git"
45
- },
46
- "commitlint": {
47
- "extends": [
48
- "@commitlint/config-conventional"
49
- ]
44
+ "url": "git+https://github.com/ngandu-dev/flexpay-ts.git"
50
45
  },
51
46
  "keywords": [
52
47
  "api",
@@ -56,34 +51,30 @@
56
51
  "author": "Bernard Ngandu <bernard@ngandu.dev>",
57
52
  "license": "MIT",
58
53
  "bugs": {
59
- "url": "https://github.com/ngandu-dev/flexpay/issues"
54
+ "url": "https://github.com/ngandu-dev/flexpay-ts/issues"
60
55
  },
61
- "homepage": "https://github.com/ngandu-dev/flexpay#readme",
56
+ "homepage": "https://github.com/ngandu-dev/flexpay-ts#readme",
62
57
  "description": "FlexPay API TypeScript client",
63
58
  "dependencies": {
64
- "zod": "^4.4.3"
59
+ "zod": "^4.5.4"
65
60
  },
66
61
  "devDependencies": {
67
62
  "@arethetypeswrong/cli": "^0.18.5",
68
- "@biomejs/biome": "^2.5.9",
69
- "@changesets/cli": "^3.0.1",
70
- "@commitlint/cli": "^21.2.2",
71
- "@commitlint/config-conventional": "^21.2.2",
72
- "@types/node": "^26.2.0",
73
- "@vitest/coverage-v8": "^4.1.11",
63
+ "@biomejs/biome": "^2.5.12",
64
+ "@changesets/cli": "^3.0.2",
65
+ "@types/node": "^26.5.0",
66
+ "@vitest/coverage-v8": "^5.0.0",
74
67
  "husky": "^9.1.7",
75
68
  "publint": "^0.3.24",
76
69
  "tsup": "^8.5.1",
77
- "typescript": "^5.9.3",
78
- "vitest": "^4.1.11"
70
+ "typescript": "^6.0.3",
71
+ "vitest": "^5.0.0"
79
72
  },
80
73
  "overrides": {
81
- "ajv": "8.18.0",
82
74
  "esbuild": "0.28.1",
83
- "fast-uri": "3.1.5",
84
75
  "js-yaml": "4.3.1",
85
76
  "nanoid": "3.3.18",
86
- "picomatch": "4.0.5",
77
+ "picomatch": "^4.0.7",
87
78
  "postcss": "8.5.26",
88
79
  "rollup": "4.62.4",
89
80
  "vite": "8.2.1"