Skip to content

Commit

Permalink
Merge pull request #35 from iamshabell/dev
Browse files Browse the repository at this point in the history
ref(marupay,docs): renaming `request` to `purchase` for better readability
  • Loading branch information
iamshabell authored Dec 15, 2023
2 parents f7f92f0 + 6fe65b7 commit 4580ccd
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 154 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-flies-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"marupay": patch
---

rename `request` to `purchase` for better understanding
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ app.get('/purchase', async (req, res) => {
const handler = getPaymentHandler(chosenHandler)(paymentConfig[chosenHandler]!);

// Make a purchase request
const paymentInfo = await handler.request({
const paymentInfo = await handler.purchase({
accountNumber: "+2526512312341", // must start with `+` followed by country code
amount: 500,
currency: Currency.SLSH,
Expand Down Expand Up @@ -99,7 +99,7 @@ app.listen(port, () => {

### Responses

The `credit` and `request` methods both returns a `PaymentInfo` object. It'll return these details:
The `credit` and `purchase` methods both returns a `PaymentInfo` object. It'll return these details:

- **`transactionId`:** This identifier is obtained from the vendor's transaction ID. It uniquely identifies the transaction in the vendor's system.

Expand Down
4 changes: 2 additions & 2 deletions apps/docs/pages/guide/credit.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ Once you have configured the payment handlers, you can utilize this configuratio
Here's a quick example of how you can use the configuration when making a credit request:

```typescript
app.get('/purchase', async (req, res) => {
app.get('/credit', async (req, res) => {
try {
const chosenHandler: HandlerName = 'edahab';
const handler = getPaymentHandler(chosenHandler)(paymentConfig[chosenHandler]!);

const paymentInfo = await handler.request({
const paymentInfo = await handler.credit({
accountNumber: "+2526512312341", // must start with `+` followed by country code
amount: 500,
currency: Currency.SLSH,
Expand Down
10 changes: 5 additions & 5 deletions apps/docs/pages/guide/purchase.mdx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Making Our First Request
*Request is sending a C2B or charging a customer.*
# Making Our First Purchase
*Purchase is sending a C2B or charging a customer.*

Now, let's add a route for making a credit request:
Now, let's add a route for making a purchase request:

```typescript
app.get('/credit', async (req, res) => {
app.get('/purchase', async (req, res) => {
try {
const chosenHandler: HandlerName = 'edahab';
const handler = getPaymentHandler(chosenHandler)(paymentConfig[chosenHandler]!);

const paymentInfo = await handler.request({
const paymentInfo = await handler.purchase({
accountNumber: "+2526512312341", // must start with `+` followed by country code
amount: 500,
currency: Currency.SLSH,
Expand Down
2 changes: 1 addition & 1 deletion examples/express-ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ app.get('/purchase', async (req, res) => {
const handler = getPaymentHandler(chosenHandler)(paymentConfig[chosenHandler]!);

// Make a purchase request
const paymentInfo = await handler.request({
const paymentInfo = await handler.purchase({
accountNumber: "+2526512312341", // must start with `+` followed by country code
amount: 500,
currency: Currency.SLSH,
Expand Down
4 changes: 2 additions & 2 deletions packages/marupay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ app.get('/purchase', async (req, res) => {
const handler = getPaymentHandler(chosenHandler)(paymentConfig[chosenHandler]!);

// Make a purchase request
const paymentInfo = await handler.request({
const paymentInfo = await handler.purchase({
accountNumber: "+2526512312341", // must start with `+` followed by country code
amount: 500,
currency: Currency.SLSH,
Expand Down Expand Up @@ -98,7 +98,7 @@ app.listen(port, () => {
```
### Responses

The `credit` and `request` methods both returns a `PaymentInfo` object. It'll return these details:
The `credit` and `purchase` methods both returns a `PaymentInfo` object. It'll return these details:

- **`transactionId`:** This identifier is obtained from the vendor's transaction ID. It uniquely identifies the transaction in the vendor's system.

Expand Down
45 changes: 29 additions & 16 deletions packages/marupay/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,58 +12,71 @@ interface IPaymentInfo {
export const baseConfigSchema = z.object({});
export type BaseConfigOptions = z.infer<typeof baseConfigSchema>;

export const baseRequestSchema = z.object({
export const basePurchaseSchema = z.object({
accountNumber: z.string().startsWith('+'),
amount: z.number(),
currency: z.nativeEnum(Currency),
description: z.string().optional(),
});

export type BaseRequestOptions = z.infer<typeof baseRequestSchema>;
export type BasePurchaseOptions = z.infer<typeof basePurchaseSchema>;

type HandlerParams<
HandlerConfigSchema extends ZodSchema,
HandlerRequestSchema extends ZodSchema,
HandlerPurchaseSchema extends ZodSchema,
HandlerCreditSchema extends ZodSchema,
IConfig extends z.infer<HandlerConfigSchema> & BaseConfigOptions,
IRequest extends z.infer<HandlerRequestSchema> & BaseRequestOptions,
ICredit extends z.infer<HandlerCreditSchema> & BaseRequestOptions,
IPurchase extends z.infer<HandlerPurchaseSchema> & BasePurchaseOptions,
ICredit extends z.infer<HandlerCreditSchema> & BasePurchaseOptions,
DefaultConfig extends Partial<IConfig>
> = {
schema: {
config: HandlerConfigSchema;
request: HandlerRequestSchema;
purchase: HandlerPurchaseSchema;
credit: HandlerCreditSchema;
};
defaultConfig: DefaultConfig;
request: (arg: { ctx: IConfig; options: IRequest }) => Promise<IPaymentInfo>,
purchase: (arg: { ctx: IConfig; options: IPurchase }) => Promise<IPaymentInfo>,

Check warning on line 39 in packages/marupay/src/handler.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'arg' is defined but never used
credit: (arg: { ctx: IConfig; options: ICredit }) => Promise<IPaymentInfo>

Check warning on line 40 in packages/marupay/src/handler.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'arg' is defined but never used
};


export const defineHandler = <
HandlerConfigSchema extends ZodSchema,
HandlerRequestSchema extends ZodSchema,
HandlerPurchaseSchema extends ZodSchema,
HandlerCreditSchema extends ZodSchema,
IConfig extends z.infer<HandlerConfigSchema> & BaseConfigOptions,
IRequest extends z.infer<HandlerRequestSchema> & BaseRequestOptions,
ICredit extends z.infer<HandlerCreditSchema> & BaseRequestOptions,
IPurchase extends z.infer<HandlerPurchaseSchema> & BasePurchaseOptions,
ICredit extends z.infer<HandlerCreditSchema> & BasePurchaseOptions,
DefaultConfig extends Partial<IConfig>
>({ schema, defaultConfig, request, credit }: HandlerParams<HandlerConfigSchema, HandlerRequestSchema, HandlerCreditSchema, IConfig, IRequest, ICredit, DefaultConfig>) => {
>({
schema,
defaultConfig,
purchase,
credit
}: HandlerParams<
HandlerConfigSchema,
HandlerPurchaseSchema,
HandlerCreditSchema,
IConfig,
IPurchase,
ICredit,
DefaultConfig
>) => {
return (config: Omit<IConfig, keyof DefaultConfig> & Partial<DefaultConfig>) => {
console.log(`config: ${JSON.stringify(config)}`);
const ctx = safeParse(schema.config, { ...defaultConfig, ...config }) as IConfig;
console.log(`parsed config: ${JSON.stringify(config)}`);
const requestPayment = async (options: Parameters<typeof request>['0']['options']) => {
options = safeParse(baseRequestSchema, options) as IRequest;
const paymentInfo = await request({ ctx, options });
const purchasePayment = async (options: Parameters<typeof purchase>['0']['options']) => {
options = safeParse(basePurchaseSchema, options) as IPurchase;
const paymentInfo = await purchase({ ctx, options });
return {
...paymentInfo,
};
};

const creditPayment = async (options: Parameters<typeof credit>['0']['options']) => {
options = safeParse(baseRequestSchema, options) as ICredit;
options = safeParse(basePurchaseSchema, options) as ICredit;
console.log(`credit options: ${JSON.stringify(options)}`);
const creditInfo = await credit({ ctx, options });
return {
Expand All @@ -72,7 +85,7 @@ export const defineHandler = <
};

return {
request: requestPayment,
purchase: purchasePayment,
credit: creditPayment
};
};
Expand Down
2 changes: 1 addition & 1 deletion packages/marupay/src/handlers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { z } from "zod";

export const SO_ACCOUNT_NUMBER = '+252';

export const soRequestNumber = z.string().startsWith(SO_ACCOUNT_NUMBER);
export const soPurchaseNumber = z.string().startsWith(SO_ACCOUNT_NUMBER);
10 changes: 5 additions & 5 deletions packages/marupay/src/handlers/edahab/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface RequestPaymentReq {
export interface PurchasePaymentReq {
apiKey: string;
edahabNumber: string;
amount: number;
Expand All @@ -7,7 +7,7 @@ export interface RequestPaymentReq {
ReturnUrl?: string;
}

export interface RequestPaymentRes {
export interface PurchasePaymentRes {
InvoiceStatus: string;
TransactionId: string;
InvoiceId: number;
Expand All @@ -33,13 +33,13 @@ export interface CreditPaymentRes {
}


export type RequestData = {
export type PurchaseData = {
apiKey: string,
currency: string,
description?: string,
amount?: number,
agentCode?: string,
}

export type RequestPaymentData = RequestData & { edahabNumber: string }
export type CreditPaymentData = RequestData & { phoneNumber: string, transactionAmount: number, transactionId: string }
export type PurchasePaymentData = PurchaseData & { edahabNumber: string }
export type CreditPaymentData = PurchaseData & { phoneNumber: string, transactionAmount: number, transactionId: string }
106 changes: 54 additions & 52 deletions packages/marupay/src/handlers/edahab/edahab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,57 @@ import * as API from './api';
import { hashSecretKey } from './hash';
import { PaymentCtx, PaymentOptions } from '../types';
import { prepareRequest } from './prepareRequest';
import { SO_ACCOUNT_NUMBER, soRequestNumber } from '../constants'
import { SO_ACCOUNT_NUMBER, soPurchaseNumber } from '../constants'
import { safeParse } from '../../utils/safeParser';

const edahabRequest = z.object({
accountNumber: soRequestNumber,
const edahabPurchase = z.object({
accountNumber: soPurchaseNumber,
});

const requestFn = async (url: string, data: any, referenceId: string) => {
const response = await axios.post(url, data);
const { TransactionId, InvoiceStatus, StatusCode, StatusDescription } = response.data;
const responseCode = `${StatusCode}`;
if (responseCode !== '0') {
console.log(`${StatusDescription}`);
throw responseCode;
}
return {
transactionId: TransactionId,
paymentStatus: InvoiceStatus,
referenceId,
raw: response.data,
};
};

const creditFn = async (url: string, data: any, referenceId: string) => {
const response = await axios.post(url, data).catch((e) => {
return {
data: {
PhoneNumber: data.phoneNumber,
TransactionId: referenceId,
TransactionStatus: 'error',
TransactionMesage: e.message,
} as API.CreditPaymentRes,
};
});

const { TransactionId, TransactionMesage, TransactionStatus } = response.data;
const responseCode = `${TransactionStatus}`;

if (responseCode === 'error') {
console.log(`credit error: ${TransactionMesage}`);
throw TransactionMesage;
}

return {
transactionId: TransactionId,
paymentStatus: TransactionStatus,
referenceId: generateUuid(),
raw: response.data,
};
};

export const createEdahabHandler = defineHandler({
schema: {
config: z.object({
Expand All @@ -25,8 +69,8 @@ export const createEdahabHandler = defineHandler({
creditUrl: z.string(),
}),
}),
request: edahabRequest,
credit: edahabRequest,
purchase: edahabPurchase,
credit: edahabPurchase,
},
defaultConfig: {
links: {
Expand All @@ -35,72 +79,30 @@ export const createEdahabHandler = defineHandler({
creditUrl: '/api/agentPayment?hash=',
},
},
request: async ({ ctx, options }: { ctx: PaymentCtx, options: PaymentOptions }) => {
const parsedData = safeParse(edahabRequest.pick({ accountNumber: true }), { accountNumber: options.accountNumber });
purchase: async ({ ctx, options }: { ctx: PaymentCtx, options: PaymentOptions }) => {
const parsedData = safeParse(edahabPurchase.pick({ accountNumber: true }), { accountNumber: options.accountNumber });
const accountNumber = parsedData.accountNumber.replace(SO_ACCOUNT_NUMBER, '');
const requestFn = async (url: string, data: API.RequestPaymentData, referenceId: string) => {
const response = await axios.post<API.RequestPaymentReq, { data: API.RequestPaymentRes }>(url, data);
const { TransactionId, InvoiceStatus, StatusCode, StatusDescription } = response.data;
const responseCode = `${StatusCode}`;
if (responseCode !== '0') {
console.log(`${StatusDescription}`);
throw responseCode;
}
return {
transactionId: TransactionId,
paymentStatus: InvoiceStatus,
referenceId,
raw: response.data,
};
};
const { links } = ctx;
const referenceId = generateUuid();

const requestData = prepareRequest('request', { ...options, accountNumber }, ctx, referenceId) as API.RequestPaymentData;
const requestData = prepareRequest('request', { ...options, accountNumber }, ctx, referenceId) as API.PurchasePaymentData;
const hashCode = hashSecretKey(requestData, ctx.secretKey);

const requestUrl = `${links.baseUrl + links.requestUrl + hashCode}`;

return await requestFn(requestUrl, requestData, referenceId);
},
credit: async ({ ctx, options }: { ctx: PaymentCtx, options: PaymentOptions }) => {
const parsedData = safeParse(edahabRequest.pick({ accountNumber: true }), { accountNumber: options.accountNumber });
const parsedData = safeParse(edahabPurchase.pick({ accountNumber: true }), { accountNumber: options.accountNumber });
const accountNumber = parsedData.accountNumber.replace(SO_ACCOUNT_NUMBER, '');
const requestFn = async (url: string, data: API.CreditPaymentData, referenceId: string) => {
const response = await axios.post<API.CreditPaymentReq, { data: API.CreditPaymentRes }>(url, data).catch((e) => {
return {
data: {
PhoneNumber: data.phoneNumber,
TransactionId: referenceId,
TransactionStatus: 'error',
TransactionMesage: e.message,
} as API.CreditPaymentRes,
};
});

const { TransactionId, TransactionMesage, TransactionStatus } = response.data;
const responseCode = `${TransactionStatus}`;

if (responseCode === 'error') {
console.log(`credit error: ${TransactionMesage}`);
throw TransactionMesage;
}

return {
transactionId: TransactionId,
paymentStatus: TransactionStatus,
referenceId: generateUuid(),
raw: response.data,
};
};
const { links } = ctx;
const referenceId = generateUuid();
const requestData = prepareRequest('credit', { ...options, accountNumber, }, ctx, referenceId) as API.CreditPaymentData;

const hashCode = hashSecretKey(requestData, ctx.secretKey);
const creditUrl = `${links.baseUrl + links.creditUrl + hashCode}`;

return await requestFn(creditUrl, requestData, referenceId);
return await creditFn(creditUrl, requestData, referenceId);
},
});

Expand Down
2 changes: 1 addition & 1 deletion packages/marupay/src/handlers/edahab/hash.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as API from './api'
import CryptoJS from 'crypto-js';

export const hashSecretKey = (data: API.RequestPaymentData | API.CreditPaymentData, secretKey: string) => {
export const hashSecretKey = (data: API.PurchasePaymentData | API.CreditPaymentData, secretKey: string) => {
const hash = JSON.stringify(data) + secretKey;

return CryptoJS.SHA256(hash).toString(
Expand Down
Loading

0 comments on commit 4580ccd

Please sign in to comment.