Skip to content

Commit

Permalink
refactor: billing UI improvements and error messages (#292)
Browse files Browse the repository at this point in the history
* refactor: billing UI improvements and better error message

* refactor: explicitly defined response errors
  • Loading branch information
amosmachora authored Aug 1, 2024
1 parent e172f62 commit 1fe65f6
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 68 deletions.
30 changes: 0 additions & 30 deletions packages/esm-billing-app/src/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ export interface BillingConfig {
patientExemptionCategories: Array<{ value: string; label: string }>;
excludedPaymentMode: Array<{ uuid: string; label: string }>;
enforceBillPayment: boolean;
mpesaCallbackUrl: string;
shortCode: string;
passKey: string;
authorizationUrl: string;
initiateUrl: string;
billingStatusQueryUrl: string;
mpesaAPIBaseUrl: string;
}
Expand Down Expand Up @@ -100,31 +95,6 @@ export const configSchema = {
_default: true,
_description: 'Whether to enforce bill payment or not for patient to receive service',
},
mpesaCallbackUrl: {
_type: Type.String,
_default: '',
_description: 'MPESA callback Url to receive confirmation payload from MPESA Daraja API',
},
shortCode: {
_type: Type.String,
_default: '',
_description: 'shortcode used to identify an organization and receive the transaction',
},
passKey: {
_type: Type.String,
_default: '',
_description: 'Passkey used for generating password for generation of access token to auth APIs',
},
authorizationUrl: {
_type: Type.String,
_default: '',
_description: 'MPESA Authenciation url gives you a time bound access token to call allowed APIs.',
},
initiateUrl: {
_type: Type.String,
_default: '',
_description: 'MPESA Initiator url which Initiates online payment on behalf of a customer.',
},
billingStatusQueryUrl: {
_type: Type.String,
_default: '${restBaseUrl}/cashier/billLineItem?orderUuid=${orderUuid}&v=full',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,31 @@ import {
InlineNotification,
InlineLoading,
Loading,
NumberInputSkeleton,
} from '@carbon/react';
import styles from './initiate-payment.scss';
import { Controller, useForm } from 'react-hook-form';
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { MappedBill } from '../../../types';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { formatPhoneNumber } from '../utils';
import { useSystemSetting } from '../../../hooks/getMflCode';
import { initiateStkPush } from '../../../m-pesa/mpesa-resource';
import { useRequestStatus } from '../../../hooks/useRequestStatus';
import { useConfig, usePatient } from '@openmrs/esm-framework';
import { useConfig } from '@openmrs/esm-framework';
import { BillingConfig } from '../../../config-schema';
import { usePatientAttributes } from '../../../hooks/usePatientAttributes';

const InitiatePaymentSchema = z.object({
const initiatePaymentSchema = z.object({
phoneNumber: z
.string()
.nonempty({ message: 'Phone number is required' })
.regex(/^\d{10}$/, { message: 'Phone number must be numeric and 10 digits' }),
billAmount: z.string().nonempty({ message: 'Amount is required' }),
});

type FormData = z.infer<typeof initiatePaymentSchema>;

export interface InitiatePaymentDialogProps {
closeModal: () => void;
bill: MappedBill;
Expand All @@ -51,17 +54,23 @@ const InitiatePaymentDialog: React.FC<InitiatePaymentDialogProps> = ({ closeModa
handleSubmit,
formState: { errors, isValid },
setValue,
} = useForm<any>({
watch,
reset,
} = useForm<FormData>({
mode: 'all',
defaultValues: { billAmount: String(bill.totalAmount), phoneNumber: phoneNumber },
resolver: zodResolver(InitiatePaymentSchema),
resolver: zodResolver(initiatePaymentSchema),
});

const watchedPhoneNumber = watch('phoneNumber');

useEffect(() => {
setValue('phoneNumber', phoneNumber);
}, [phoneNumber, setValue]);
if (!watchedPhoneNumber && phoneNumber) {
reset({ phoneNumber: watchedPhoneNumber });
}
}, [watchedPhoneNumber, setValue, phoneNumber, reset]);

const onSubmit = async (data: { phoneNumber: any; billAmount: any }) => {
const onSubmit: SubmitHandler<FormData> = async (data) => {
const phoneNumber = formatPhoneNumber(data.phoneNumber);
const amountBilled = data.billAmount;
const accountReference = `${mflCodeValue}#${bill.receiptNumber}`;
Expand All @@ -78,10 +87,6 @@ const InitiatePaymentDialog: React.FC<InitiatePaymentDialogProps> = ({ closeModa
pollingTrigger({ requestId, requestStatus: 'INITIATED' });
};

if (isLoadingPhoneNumber || isLoading) {
return <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />;
}

return (
<div>
<ModalHeader closeModal={closeModal} />
Expand All @@ -95,24 +100,28 @@ const InitiatePaymentDialog: React.FC<InitiatePaymentDialogProps> = ({ closeModa
onCloseButtonClick={() => setNotification(null)}
/>
)}
<section className={styles.section}>
<Controller
control={control}
name="phoneNumber"
render={({ field }) => (
<Layer>
<TextInput
{...field}
size="md"
labelText={t('Phone Number', 'Phone Number')}
placeholder={t('Phone Number', 'Phone Number')}
invalid={!!errors.phoneNumber}
invalidText={errors.phoneNumber?.message}
/>
</Layer>
)}
/>
</section>
{isLoadingPhoneNumber ? (
<NumberInputSkeleton className={styles.section} />
) : (
<section className={styles.section}>
<Controller
control={control}
name="phoneNumber"
render={({ field }) => (
<Layer>
<TextInput
{...field}
size="md"
labelText={t('Phone Number', 'Phone Number')}
placeholder={t('Phone Number', 'Phone Number')}
invalid={!!errors.phoneNumber}
invalidText={errors.phoneNumber?.message}
/>
</Layer>
)}
/>
</section>
)}
<section className={styles.section}>
<Controller
control={control}
Expand Down
24 changes: 16 additions & 8 deletions packages/esm-billing-app/src/m-pesa/mpesa-resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,27 @@ export const initiateStkPush = async (
}),
});

if (!res.ok && res.status === 403) {
const error = new Error('Health facility M-PESA data not configured.');
throw error;
if (res.ok) {
const response: { requestId: string } = await res.json();
setNotification({ message: 'STK Push sent successfully', type: 'success' });
return response.requestId;
}

const response: { requestId: string } = await res.json();
if (!res.ok && res.status === 403) {
setNotification({
message: 'Health facility M-PESA data not configured.',
type: 'error',
});

setNotification({ message: 'STK Push sent successfully', type: 'success' });
return response.requestId;
return;
}

if (!res.ok) {
throw new Error('Unable to initiate Lipa Na Mpesa, please try again later.');
}
} catch (err) {
const error = err as Error;
setNotification({
message: error.message ?? 'Unable to initiate Lipa Na Mpesa, please try again later.',
message: 'Unable to initiate Lipa Na Mpesa, please try again later.',
type: 'error',
});
}
Expand Down

0 comments on commit 1fe65f6

Please sign in to comment.