Added customer IP programatically
This commit is contained in:
parent
bf8c77f490
commit
52e219e258
@ -13,6 +13,7 @@ function Cashier() {
|
||||
currencies,
|
||||
selectedMethod,
|
||||
formMetadata,
|
||||
customerIp,
|
||||
setSelectedMethod,
|
||||
initiatePayment,
|
||||
} = useCashier();
|
||||
@ -35,6 +36,50 @@ function Cashier() {
|
||||
additionalFields.account = formData.account as string;
|
||||
}
|
||||
|
||||
// Auto-fill customer IP if available and not already set
|
||||
if (customerIp) {
|
||||
// Check if IP field exists in form metadata and populate it
|
||||
const ipField = formMetadata.find(
|
||||
field =>
|
||||
field.code.toLowerCase().includes('ip') ||
|
||||
field.code === 'customer.ip' ||
|
||||
field.code === 'customer_ip' ||
|
||||
field.code === 'ip_address'
|
||||
);
|
||||
|
||||
if (ipField) {
|
||||
// Build nested path for IP field
|
||||
const ipPath = ipField.code.split('.');
|
||||
if (ipPath.length === 1) {
|
||||
// Top-level field like 'customer_ip' or 'ip_address'
|
||||
additionalFields[ipField.code] = customerIp;
|
||||
} else if (ipPath[0] === 'customer') {
|
||||
// Nested field like 'customer.ip'
|
||||
if (!additionalFields.customer) {
|
||||
additionalFields.customer = {};
|
||||
}
|
||||
(additionalFields.customer as Record<string, unknown>)[ipPath[1]] = customerIp;
|
||||
} else {
|
||||
// Other nested paths
|
||||
let current: Record<string, unknown> = additionalFields;
|
||||
for (let i = 0; i < ipPath.length - 1; i++) {
|
||||
if (!current[ipPath[i]]) {
|
||||
current[ipPath[i]] = {};
|
||||
}
|
||||
current = current[ipPath[i]] as Record<string, unknown>;
|
||||
}
|
||||
current[ipPath[ipPath.length - 1]] = customerIp;
|
||||
}
|
||||
} else {
|
||||
// If no IP field found in metadata, try common field names
|
||||
// Try customer.ip first (most common)
|
||||
if (!additionalFields.customer) {
|
||||
additionalFields.customer = {};
|
||||
}
|
||||
(additionalFields.customer as Record<string, unknown>).ip = customerIp;
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve any additional fields
|
||||
Object.entries(formData).forEach(([key, value]) => {
|
||||
if (
|
||||
@ -52,18 +97,25 @@ function Cashier() {
|
||||
}
|
||||
});
|
||||
|
||||
// Merge customer data from formData with customer data in additionalFields
|
||||
const customerData = (formData.customer as IPaymentRequest['customer']) || {
|
||||
id: config.userId || '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
};
|
||||
|
||||
if (additionalFields.customer) {
|
||||
Object.assign(customerData, additionalFields.customer);
|
||||
delete additionalFields.customer;
|
||||
}
|
||||
|
||||
const paymentRequest: Omit<IPaymentRequest, 'merchant_id'> = {
|
||||
payment_type: (formData.payment_type as 'deposit' | 'withdrawal') || 'deposit',
|
||||
method: (formData.method as string) || '',
|
||||
currency: (formData.currency as string) || '',
|
||||
amount: (formData.amount as number) || 0,
|
||||
customer: (formData.customer as IPaymentRequest['customer']) || {
|
||||
id: config.userId || '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
email: '',
|
||||
},
|
||||
customer: customerData,
|
||||
redirect: formData.redirect as IPaymentRequest['redirect'],
|
||||
...additionalFields,
|
||||
};
|
||||
|
||||
@ -16,6 +16,7 @@ interface ICashierContextValue {
|
||||
currencies: IApiCurrency[];
|
||||
selectedMethod: IPaymentMethod | null;
|
||||
formMetadata: IFormMetadataField[];
|
||||
customerIp: string | null;
|
||||
error: Error | null;
|
||||
paymentUrl: string | null;
|
||||
loadData: (merchant?: TMerchant) => Promise<void>;
|
||||
@ -40,6 +41,7 @@ interface ICashierState {
|
||||
currencies: IApiCurrency[];
|
||||
selectedMethod: IPaymentMethod | null;
|
||||
formMetadata: IFormMetadataField[];
|
||||
customerIp: string | null;
|
||||
error: Error | null;
|
||||
paymentUrl: string | null;
|
||||
}
|
||||
@ -51,6 +53,7 @@ export function CashierProvider({ children }: ICashierProviderProps) {
|
||||
currencies: cashierService.getCurrencies(),
|
||||
selectedMethod: cashierService.getSelectedMethod(),
|
||||
formMetadata: cashierService.getFormMetadata(),
|
||||
customerIp: cashierService.getCustomerIp(),
|
||||
error: cashierService.getError(),
|
||||
paymentUrl: cashierService.getPaymentUrl(),
|
||||
}));
|
||||
@ -63,6 +66,7 @@ export function CashierProvider({ children }: ICashierProviderProps) {
|
||||
currencies: cashierService.getCurrencies(),
|
||||
selectedMethod: cashierService.getSelectedMethod(),
|
||||
formMetadata: cashierService.getFormMetadata(),
|
||||
customerIp: cashierService.getCustomerIp(),
|
||||
error: cashierService.getError(),
|
||||
paymentUrl: cashierService.getPaymentUrl(),
|
||||
});
|
||||
@ -106,6 +110,7 @@ export function CashierProvider({ children }: ICashierProviderProps) {
|
||||
currencies: cashierState.currencies,
|
||||
selectedMethod: cashierState.selectedMethod,
|
||||
formMetadata: cashierState.formMetadata,
|
||||
customerIp: cashierState.customerIp,
|
||||
error: cashierState.error,
|
||||
paymentUrl: cashierState.paymentUrl,
|
||||
loadData,
|
||||
|
||||
@ -17,6 +17,7 @@ class CashierService {
|
||||
private currencies: IApiCurrency[] = [];
|
||||
private selectedMethod: IPaymentMethod | null = null;
|
||||
private formMetadata: IFormMetadataField[] = [];
|
||||
private customerIp: string | null = null;
|
||||
private error: Error | null = null;
|
||||
private paymentUrl: string | null = null;
|
||||
private listeners: Set<StateChangeListener> = new Set();
|
||||
@ -49,12 +50,17 @@ class CashierService {
|
||||
return this.formMetadata;
|
||||
}
|
||||
|
||||
getCustomerIp(): string | null {
|
||||
return this.customerIp;
|
||||
}
|
||||
|
||||
async setSelectedMethod(
|
||||
method: IPaymentMethod | null,
|
||||
merchant: TMerchant = DEFAULT_MERCHANT
|
||||
): Promise<void> {
|
||||
this.selectedMethod = method;
|
||||
this.formMetadata = [];
|
||||
this.customerIp = null;
|
||||
|
||||
if (method) {
|
||||
try {
|
||||
@ -68,7 +74,8 @@ class CashierService {
|
||||
apiService.getCurrencies(merchant, method.code),
|
||||
]);
|
||||
|
||||
this.formMetadata = formMetadataData;
|
||||
this.formMetadata = formMetadataData.fields;
|
||||
this.customerIp = formMetadataData.customerIp;
|
||||
this.currencies = currenciesData;
|
||||
} catch (err) {
|
||||
// If form metadata or currencies fetch fails, we still allow method selection
|
||||
@ -149,6 +156,7 @@ class CashierService {
|
||||
this.selectedMethod = null;
|
||||
this.formMetadata = [];
|
||||
this.currencies = [];
|
||||
this.customerIp = null;
|
||||
this.error = null;
|
||||
this.paymentUrl = null;
|
||||
this.notifyListeners();
|
||||
@ -158,6 +166,7 @@ class CashierService {
|
||||
this.selectedMethod = null;
|
||||
this.formMetadata = [];
|
||||
this.currencies = [];
|
||||
this.customerIp = null;
|
||||
this.error = null;
|
||||
// Reset to ready state if we have methods available, otherwise keep current state
|
||||
if (this.methods.length > 0) {
|
||||
|
||||
@ -58,6 +58,18 @@ export function shouldShowField(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide customer IP fields - they're set programmatically from API response headers
|
||||
const lowerCode = code.toLowerCase();
|
||||
if (
|
||||
lowerCode.includes('ip') ||
|
||||
lowerCode === 'customer.ip' ||
|
||||
lowerCode === 'customer_ip' ||
|
||||
lowerCode === 'ip_address' ||
|
||||
(field.path[0] === 'customer' && field.path[1]?.toLowerCase() === 'ip')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show all other fields
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import type {
|
||||
} from '@/features/cashier/types';
|
||||
import { mapApiMethodsToPaymentMethods } from '@/features/cashier/utils/methodMapper';
|
||||
import type { IPaymentMethod } from '@/features/payment-methods/types';
|
||||
import { extractIpFromHeaders } from '@/utils/ipExtractor';
|
||||
|
||||
class ApiService {
|
||||
private methods: IPaymentMethod[] = [];
|
||||
@ -82,7 +83,7 @@ class ApiService {
|
||||
method: string,
|
||||
type: 'deposit' | 'withdrawal',
|
||||
merchant: TMerchant = DEFAULT_MERCHANT
|
||||
): Promise<IFormMetadataField[]> {
|
||||
): Promise<{ fields: IFormMetadataField[]; customerIp: string | null }> {
|
||||
const params = new URLSearchParams({
|
||||
method,
|
||||
type,
|
||||
@ -99,7 +100,12 @@ class ApiService {
|
||||
}
|
||||
|
||||
const data: IFormMetadataResponse = await response.json();
|
||||
return data.data;
|
||||
const customerIp = extractIpFromHeaders(response.headers);
|
||||
|
||||
return {
|
||||
fields: data.data,
|
||||
customerIp,
|
||||
};
|
||||
}
|
||||
|
||||
async initiatePayment(
|
||||
|
||||
28
src/utils/ipExtractor.ts
Normal file
28
src/utils/ipExtractor.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Extracts the client IP address from response headers.
|
||||
* Checks headers in order: CF-Connecting-IP, X-Forwarded-For, X-Real-IP
|
||||
* @param headers - Response headers object
|
||||
* @returns IP address string or null if not found
|
||||
*/
|
||||
export function extractIpFromHeaders(headers: Headers): string | null {
|
||||
// Check Cloudflare header first (most reliable when behind Cloudflare)
|
||||
const cfIp = headers.get('CF-Connecting-IP');
|
||||
if (cfIp) {
|
||||
// X-Forwarded-For can contain multiple IPs, take the first one
|
||||
return cfIp.split(',')[0].trim();
|
||||
}
|
||||
|
||||
// Check X-Forwarded-For (can contain multiple IPs, first is usually the original client)
|
||||
const forwardedFor = headers.get('X-Forwarded-For');
|
||||
if (forwardedFor) {
|
||||
return forwardedFor.split(',')[0].trim();
|
||||
}
|
||||
|
||||
// Check X-Real-IP (nginx proxy header)
|
||||
const realIp = headers.get('X-Real-IP');
|
||||
if (realIp) {
|
||||
return realIp.split(',')[0].trim();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user