Added customer IP programatically
This commit is contained in:
parent
bf8c77f490
commit
52e219e258
@ -13,6 +13,7 @@ function Cashier() {
|
|||||||
currencies,
|
currencies,
|
||||||
selectedMethod,
|
selectedMethod,
|
||||||
formMetadata,
|
formMetadata,
|
||||||
|
customerIp,
|
||||||
setSelectedMethod,
|
setSelectedMethod,
|
||||||
initiatePayment,
|
initiatePayment,
|
||||||
} = useCashier();
|
} = useCashier();
|
||||||
@ -35,6 +36,50 @@ function Cashier() {
|
|||||||
additionalFields.account = formData.account as string;
|
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
|
// Preserve any additional fields
|
||||||
Object.entries(formData).forEach(([key, value]) => {
|
Object.entries(formData).forEach(([key, value]) => {
|
||||||
if (
|
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'> = {
|
const paymentRequest: Omit<IPaymentRequest, 'merchant_id'> = {
|
||||||
payment_type: (formData.payment_type as 'deposit' | 'withdrawal') || 'deposit',
|
payment_type: (formData.payment_type as 'deposit' | 'withdrawal') || 'deposit',
|
||||||
method: (formData.method as string) || '',
|
method: (formData.method as string) || '',
|
||||||
currency: (formData.currency as string) || '',
|
currency: (formData.currency as string) || '',
|
||||||
amount: (formData.amount as number) || 0,
|
amount: (formData.amount as number) || 0,
|
||||||
customer: (formData.customer as IPaymentRequest['customer']) || {
|
customer: customerData,
|
||||||
id: config.userId || '',
|
|
||||||
first_name: '',
|
|
||||||
last_name: '',
|
|
||||||
email: '',
|
|
||||||
},
|
|
||||||
redirect: formData.redirect as IPaymentRequest['redirect'],
|
redirect: formData.redirect as IPaymentRequest['redirect'],
|
||||||
...additionalFields,
|
...additionalFields,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,6 +16,7 @@ interface ICashierContextValue {
|
|||||||
currencies: IApiCurrency[];
|
currencies: IApiCurrency[];
|
||||||
selectedMethod: IPaymentMethod | null;
|
selectedMethod: IPaymentMethod | null;
|
||||||
formMetadata: IFormMetadataField[];
|
formMetadata: IFormMetadataField[];
|
||||||
|
customerIp: string | null;
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
paymentUrl: string | null;
|
paymentUrl: string | null;
|
||||||
loadData: (merchant?: TMerchant) => Promise<void>;
|
loadData: (merchant?: TMerchant) => Promise<void>;
|
||||||
@ -40,6 +41,7 @@ interface ICashierState {
|
|||||||
currencies: IApiCurrency[];
|
currencies: IApiCurrency[];
|
||||||
selectedMethod: IPaymentMethod | null;
|
selectedMethod: IPaymentMethod | null;
|
||||||
formMetadata: IFormMetadataField[];
|
formMetadata: IFormMetadataField[];
|
||||||
|
customerIp: string | null;
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
paymentUrl: string | null;
|
paymentUrl: string | null;
|
||||||
}
|
}
|
||||||
@ -51,6 +53,7 @@ export function CashierProvider({ children }: ICashierProviderProps) {
|
|||||||
currencies: cashierService.getCurrencies(),
|
currencies: cashierService.getCurrencies(),
|
||||||
selectedMethod: cashierService.getSelectedMethod(),
|
selectedMethod: cashierService.getSelectedMethod(),
|
||||||
formMetadata: cashierService.getFormMetadata(),
|
formMetadata: cashierService.getFormMetadata(),
|
||||||
|
customerIp: cashierService.getCustomerIp(),
|
||||||
error: cashierService.getError(),
|
error: cashierService.getError(),
|
||||||
paymentUrl: cashierService.getPaymentUrl(),
|
paymentUrl: cashierService.getPaymentUrl(),
|
||||||
}));
|
}));
|
||||||
@ -63,6 +66,7 @@ export function CashierProvider({ children }: ICashierProviderProps) {
|
|||||||
currencies: cashierService.getCurrencies(),
|
currencies: cashierService.getCurrencies(),
|
||||||
selectedMethod: cashierService.getSelectedMethod(),
|
selectedMethod: cashierService.getSelectedMethod(),
|
||||||
formMetadata: cashierService.getFormMetadata(),
|
formMetadata: cashierService.getFormMetadata(),
|
||||||
|
customerIp: cashierService.getCustomerIp(),
|
||||||
error: cashierService.getError(),
|
error: cashierService.getError(),
|
||||||
paymentUrl: cashierService.getPaymentUrl(),
|
paymentUrl: cashierService.getPaymentUrl(),
|
||||||
});
|
});
|
||||||
@ -106,6 +110,7 @@ export function CashierProvider({ children }: ICashierProviderProps) {
|
|||||||
currencies: cashierState.currencies,
|
currencies: cashierState.currencies,
|
||||||
selectedMethod: cashierState.selectedMethod,
|
selectedMethod: cashierState.selectedMethod,
|
||||||
formMetadata: cashierState.formMetadata,
|
formMetadata: cashierState.formMetadata,
|
||||||
|
customerIp: cashierState.customerIp,
|
||||||
error: cashierState.error,
|
error: cashierState.error,
|
||||||
paymentUrl: cashierState.paymentUrl,
|
paymentUrl: cashierState.paymentUrl,
|
||||||
loadData,
|
loadData,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ class CashierService {
|
|||||||
private currencies: IApiCurrency[] = [];
|
private currencies: IApiCurrency[] = [];
|
||||||
private selectedMethod: IPaymentMethod | null = null;
|
private selectedMethod: IPaymentMethod | null = null;
|
||||||
private formMetadata: IFormMetadataField[] = [];
|
private formMetadata: IFormMetadataField[] = [];
|
||||||
|
private customerIp: string | null = null;
|
||||||
private error: Error | null = null;
|
private error: Error | null = null;
|
||||||
private paymentUrl: string | null = null;
|
private paymentUrl: string | null = null;
|
||||||
private listeners: Set<StateChangeListener> = new Set();
|
private listeners: Set<StateChangeListener> = new Set();
|
||||||
@ -49,12 +50,17 @@ class CashierService {
|
|||||||
return this.formMetadata;
|
return this.formMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCustomerIp(): string | null {
|
||||||
|
return this.customerIp;
|
||||||
|
}
|
||||||
|
|
||||||
async setSelectedMethod(
|
async setSelectedMethod(
|
||||||
method: IPaymentMethod | null,
|
method: IPaymentMethod | null,
|
||||||
merchant: TMerchant = DEFAULT_MERCHANT
|
merchant: TMerchant = DEFAULT_MERCHANT
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.selectedMethod = method;
|
this.selectedMethod = method;
|
||||||
this.formMetadata = [];
|
this.formMetadata = [];
|
||||||
|
this.customerIp = null;
|
||||||
|
|
||||||
if (method) {
|
if (method) {
|
||||||
try {
|
try {
|
||||||
@ -68,7 +74,8 @@ class CashierService {
|
|||||||
apiService.getCurrencies(merchant, method.code),
|
apiService.getCurrencies(merchant, method.code),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.formMetadata = formMetadataData;
|
this.formMetadata = formMetadataData.fields;
|
||||||
|
this.customerIp = formMetadataData.customerIp;
|
||||||
this.currencies = currenciesData;
|
this.currencies = currenciesData;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If form metadata or currencies fetch fails, we still allow method selection
|
// If form metadata or currencies fetch fails, we still allow method selection
|
||||||
@ -149,6 +156,7 @@ class CashierService {
|
|||||||
this.selectedMethod = null;
|
this.selectedMethod = null;
|
||||||
this.formMetadata = [];
|
this.formMetadata = [];
|
||||||
this.currencies = [];
|
this.currencies = [];
|
||||||
|
this.customerIp = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
this.paymentUrl = null;
|
this.paymentUrl = null;
|
||||||
this.notifyListeners();
|
this.notifyListeners();
|
||||||
@ -158,6 +166,7 @@ class CashierService {
|
|||||||
this.selectedMethod = null;
|
this.selectedMethod = null;
|
||||||
this.formMetadata = [];
|
this.formMetadata = [];
|
||||||
this.currencies = [];
|
this.currencies = [];
|
||||||
|
this.customerIp = null;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
// Reset to ready state if we have methods available, otherwise keep current state
|
// Reset to ready state if we have methods available, otherwise keep current state
|
||||||
if (this.methods.length > 0) {
|
if (this.methods.length > 0) {
|
||||||
|
|||||||
@ -58,6 +58,18 @@ export function shouldShowField(
|
|||||||
return false;
|
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
|
// Show all other fields
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type {
|
|||||||
} from '@/features/cashier/types';
|
} from '@/features/cashier/types';
|
||||||
import { mapApiMethodsToPaymentMethods } from '@/features/cashier/utils/methodMapper';
|
import { mapApiMethodsToPaymentMethods } from '@/features/cashier/utils/methodMapper';
|
||||||
import type { IPaymentMethod } from '@/features/payment-methods/types';
|
import type { IPaymentMethod } from '@/features/payment-methods/types';
|
||||||
|
import { extractIpFromHeaders } from '@/utils/ipExtractor';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
private methods: IPaymentMethod[] = [];
|
private methods: IPaymentMethod[] = [];
|
||||||
@ -82,7 +83,7 @@ class ApiService {
|
|||||||
method: string,
|
method: string,
|
||||||
type: 'deposit' | 'withdrawal',
|
type: 'deposit' | 'withdrawal',
|
||||||
merchant: TMerchant = DEFAULT_MERCHANT
|
merchant: TMerchant = DEFAULT_MERCHANT
|
||||||
): Promise<IFormMetadataField[]> {
|
): Promise<{ fields: IFormMetadataField[]; customerIp: string | null }> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
method,
|
method,
|
||||||
type,
|
type,
|
||||||
@ -99,7 +100,12 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data: IFormMetadataResponse = await response.json();
|
const data: IFormMetadataResponse = await response.json();
|
||||||
return data.data;
|
const customerIp = extractIpFromHeaders(response.headers);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields: data.data,
|
||||||
|
customerIp,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async initiatePayment(
|
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