🛠️ Potential Solutions
Assumptions
#1 - Create new fields for the new data needed.
The idea is to create a new input for the user to fill in and we send this information in addition to other relevant information such as the height and width of the browser (Paycomet is working to include height and width in their system, so this solution does not include those properties yet.). It was also agreed that the email and telephone fields will be sent via without showing these fields to the user and getting this information in user details.
✅ The user will fill in the correct information.
✅ We guarantee that all information provided to Paycomet/Visa belongs to the user.
The user has more fields to fill in.
#2 - Send the pre-filled data of user details.
✅ The user does not need to fill out all the forms.
The user data may not be the same as their Visa credit card information.
✅ Proposed Solution
#1 - Create new fields for the new data needed.
Whitelabel App
Implement new input fields for users to provide additional data necessary for payment processing, specifically: billing address, email, and phone number. This data will be captured, validated, and transmitted to the
Diagram
https://sequencediagram.org/
title Make purchase/No pre-auth
FE->Graphql:CommitOrder
Graphql->Payment-Service:MakePayment
Payment-Service->Paycomet-Service:MakePayment
Paycomet-Service->Paycomet: executePurchase
Paycomet-Service<-Paycomet: result
Payment-Service<-Paycomet-Service:result
Graphql<-Payment-Service:result
FE<-Graphql:result
https://sequencediagram.org/
title Pre-auth
FE->Graphql:CreatePreAuth
Graphql->Paycomet-Service:CreatePreAuth
Paycomet-Service->Paycomet: createPreauthorization
Paycomet-Service<-Paycomet: result
Graphql<-Paycomet-Service:result
FE<-Graphql:result
For this solution, we will only modify the payment request part, as shown in the diagram above. The entire flow after sending payment to paycomet will not be .
https://www.figma.com/design/RtD0UFtwuwXx14lbLCAPlr/branch/UfNaSRm5ctVoAPBMsTBcE2/9.-Cart-%26-Checkout?m=auto&node-id=45887-5479&t=pImkF9wfuOgTzFgD-1
Development
Frontend
paycomet-credit-card-form/paycomet-credit-card-form.tsx
For postal/zip code we already have a field for this purpose, and it is hidden by the payment variation fields. We can use the same flag to enable or disable these new fields.
Create useState for each field
<TextInputPaycomet
data-testid="address-city"
id="addressCity"
name="addressCity"
type="hidden"
value={addressCity}
/>
<TextInputPaycomet
data-testid="address-country"
id="addressCountry"
name="addressCountry"
type="hidden"
value={addressCountry}
/>
<TextInputPaycomet
data-testid="address-line"
id="addressLine"
name="addressLine"
type="hidden"
value={addressLine}
/>
<TextInputPaycomet
data-testid="address-state"
id="addressState"
name="addressState"
type="hidden"
value={addressState}
/>
<TextInputPaycomet
data-testid="phone-number"
id="phoneNumber"
name="phoneNumber"
type="hidden"
value={phoneNumber}
/>
<TextInputPaycomet
data-testid="email"
id="email"
name="email"
type="hidden"
value={email}
/>
Save the input in state ( best option )
You could found other references in IPaymentState/IPaycometState
and following the reference below.
<TextInput
...
onChange={handleChange}
value={paymentValues.billingStreetAddress}
required
data-paycomet="billingStreetAddress"
name="billingStreetAddress"
/>
IPaycometState - If saving the input in state
If you use the state solution, you must add the user field with phone number and email
export interface IPaycometState extends IPaymentState {
...,
user?: {
phoneNumber: string;
email: string;
};
}
Update CreatePreAuthorization
generateCreatePreAuthorization({
variables: {
input: {
rbiOrderId: rbiOrder.rbiOrderId,
cardHolderName: paymentValues.nameOnCard,
...,
userPaymentDetails,
billingAddress
},
},
});
order-payment/use-order-payment.ts
commitInput = {
...,
payment: {
...,
billingAddress: {
locality: payCometValues.billingCity, //city
country: payCometValues.billingCountry, //country
streetAddress: payCometValues.billingStreetAddress, //addressLine
postalCode: payCometValues.billingZip, //postalCode
region: payCometValues.billingState, //state
},
userPaymentDetails: {
phoneNumber: payCometValues.phoneNumber,
email: payCometValues.email,
}
},
...
};
Graphql
utils/make-payment.ts
The graphql CommitOrder function has already been made to deal with billingAddress, I put the code here just for reference for name changes.
function mapBillingAddress(
region: string,
billing: IBillingAddress | undefined,
): PaymentClient.IAddress | undefined {
if (!billing) {
return {
regionCode: region,
};
}
return {
postalCode: billing.postalCode || undefined, // normalize empty string //postalCode
administrativeArea: billing.region, //state
locality: billing.locality, //city
regionCode: billing.country ?? region, //country
streetNumber: billing.streetAddress, //address
};
}
graphql/schemas/payments.gql
input AdditionalPayment {
"""
Represents the phone number of the user who owns the requested payment
"""
phoneNumber: String
"""
Represents the email of the user who owns the requested payment
"""
email: String
}
input PaycometPayment {
...
"""
Additional Payment Information
"""
userPaymentDetails: UserPaymentDetails
}
graphql/resolvers/orders.ts
paycometSale = {
...,
userDetails: {
email: payment?.paycometInput?.userPaymentDetails?.email || '',
phoneNumber: payment?.paycometInput?.userPaymentDetails?.phoneNumber || '',
},
};
src/functions/graphql/providers/payments.ts
export interface IUserPaymentDetails {
email: string;
phoneNumber: string;
}
export interface ICreatePreAuthPaycometRequest {
userPaymentDetails: IUserPaymentDetails
}
public async createPreAuthorizationPaycomet({
....
}) {
....
const paycometPreauth = await this.paymentsClient.paycometClient.request<ICreatePreAuthorizationResponse>(
(apis) =>
apis.paymentsApi.createPreAuthorization({
iCreatePreAuthorizationRequest: {
...,
billingAddress,
user: {
email,
phonenumber,
}
},
region: regionCountry,
}),
);
...
}
Intl-Packages
packages/apis-psp-service/src/payment.dtos.ts
export class PaymentBaseRequestDto extends PaymentBase {
@IsObject()
@IsOptional()
@ApiProperty({
required: false,
type: MerchantDataDto,
description: "Payment additional information",
})
public merchantData?: MerchantDataDto;
}
export class MerchantDataDto {
@IsObject()
@IsOptional()
@ApiProperty({
required: false,
type: BillingDataDto,
description: "User's billing information",
})
public billing?: BillingDataDto;
@IsObject()
@IsOptional()
@ApiProperty({
required: false,
type: BillingDataDto,
description: "User's additional information",
})
public customer?: CustomerDataDto;
}
export class BillingDataDto {
//Include the decorators following the pattern
public billAddrCity;
public billAddrCountry;
public billAddrLine1;
public billAddrPostCode;
public billAddrState;
}
export class CustomerDataDto {
//Include the decorators following the pattern
public mobilePhone?: string;
public email?: string;
}
packages/packages/payments/src/services/paycomet/generated/api.ts
export interface IUserPaymentDetails {
email: string;
phoneNumber: string;
}
export interface ICreatePreAuthPaycometRequest {
userPaymentDetails: IUserPaymentDetails
}
export interface ICreatePreAuthorizationRequest {
billingAddress: IBillingAddress,
userPaymentDetails: IUserPaymentDetails
}
Payment-service
payments/payments.service.ts
const pspRequestDto: PaymentBaseRequestDto = {
...,
billingAddress: requestDto.billingAddress, //already exists
userPaymentDetails: {
email: requestDto.user.email,
phoneNumber: requestDto.user.phoneNumber
}
...,
};
try {
const resp = await this.pspClient.makePayment(region, psp, pspRequestDto);
...
}
...
}
Paycomet-service
src/payment/payment.service.ts
const response: ExecutePurchase.Response = await this.client.exec(
region,
new ExecutePurchase.Request({
payment: {
...,
merchantData: {
billing: {
billAddrCity: request.billingAddress?.locality,
billAddrCountry: request.billingAddress?.regionCode,
billAddrLine1: request.billingAddress?.streetNumber,
billAddrPostCode: request.billingAddress?.postalCode,
billAddrState: request.billingAddress?.administrativeArea,
},
customer: {
mobilePhone: {
cc: mappedPhone.cc
subscriber: mappedPhone.subscriber
}
email: request.user.email,
}
},
},
}),
);
src/paycomet-core/api/api.post-execute-purchase.ts
interface IBillingData {
billAddrCity?: string;
billAddrCountry?: string;
billAddrLine1?: string;
billAddrPostCode?: string;
billAddrState?: string;
}
interface ICustomerData{
mobilePhone: IMobilePhone;
email: string;
}
interface IMobilePhone{
cc: string;
subscriber: string;
}
interface IMerchantData {
billing?: IBillingData;
customer?: ICustomerData;
}
interface IRequestPaymentBody {
...
merchantData: IMerchantData;
...
}
payment/dtos/preauth.dto.ts
export abstract class PreAuthBodyDto {
...
@ApiProperty({
required: true,
description: 'User required payment details',
oneOf: [{ $ref: getSchemaPath(UserPaymentDetails) }],
})
@IsObject()
@ValidateNested()
public userPaymentDetails!: UserPaymentDetailsDto;
}
export class UserPaymentDetailsDto {
@ApiProperty({
required: true,
example: 'john@gmail.com',
description: 'User payment email ',
})
@IsString()
public email!: string;
@ApiProperty({
required: true,
example: '351333399999',
description: 'User payment phone number',
})
@IsString()
public phoneNumber!: string;
}
src/payment/preauth/preauth.service.ts
const authRequest = new TransactionAuthPre.Request({
payment: {
...,
merchantData: {
billing: {
billAddrCity: request.billingAddress?.locality,
billAddrCountry: request.billingAddress?.regionCode,
billAddrLine1: request.billingAddress?.streetNumber,
billAddrPostCode: request.billingAddress?.postalCode,
billAddrState: request.billingAddress?.administrativeArea,
},
customer: {
mobilePhone: {
cc: mappedPhone.cc,
subscriber: mappedPhone.subscriber,
}
email: request.userPaymentDetails?.email,
}
},
...,
},
});
src/paycomet-core/api/api.post-pre-auth.ts
interface IBillingData {
billAddrCity?: string;
billAddrCountry?: string;
billAddrLine1?: string;
billAddrPostCode?: string;
billAddrState?: string;
}
interface ICustomerData{
mobilePhone: IMobilePhone;
email: string;
}
interface IMobilePhone{
cc: string;
subscriber: string;
}
interface IMerchantData {
billing?: IBillingData;
customer?: ICustomerData;
}
interface IRequestPaymentBody {
...
merchantData: IMerchantData;
}
🎛️ Configuration
🧑🚒 QA Plan
[Test Cases] Visa/Sibs - new mandatory fields
⚠️ Call-outs
🖌️ Figma File
https://www.figma.com/design/RtD0UFtwuwXx14lbLCAPlr/branch/UfNaSRm5ctVoAPBMsTBcE2/9.-Cart-%26-Checkout?m=auto&node-id=45865-10714&t=pImkF9wfuOgTzFgD-1
0 Comments