patment part ui complated
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
// ─── Pricing Constants ─────────────────────────────────────────────────────────
|
||||
|
||||
export const PRICING = {
|
||||
SERVICE_FEE: 45_000,
|
||||
CERTIFICATE_FEE: 15_000,
|
||||
CURRENCY: 'UZS',
|
||||
// Payme works in tiyin (1 UZS = 100 tiyin)
|
||||
TIYIN_MULTIPLIER: 100,
|
||||
} as const;
|
||||
|
||||
// ─── Payme Config ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const PAYME_CONFIG = {
|
||||
MERCHANT_ID: process.env.NEXT_PUBLIC_PAYME_MERCHANT_ID ?? 'your_merchant_id',
|
||||
BASE_URL: 'https://checkout.paycom.uz',
|
||||
// In development, point to your own backend
|
||||
API_ENDPOINT: '/api/payments/payme/create',
|
||||
RETURN_URL:
|
||||
typeof window !== 'undefined'
|
||||
? `${window.location.origin}/payment/success`
|
||||
: 'https://yourapp.uz/payment/success',
|
||||
} as const;
|
||||
@@ -7,11 +7,6 @@ export interface PaymePaymentRequest {
|
||||
returnUrl: string;
|
||||
}
|
||||
|
||||
export interface PaymePaymentResponse {
|
||||
redirectUrl: string;
|
||||
transactionId: string;
|
||||
}
|
||||
|
||||
export type PaymentStatus = 'idle' | 'loading' | 'success' | 'error';
|
||||
|
||||
// ─── Component Props ───────────────────────────────────────────────────────────
|
||||
@@ -29,10 +24,3 @@ export interface PaymentModalProps {
|
||||
onConfirmPayment: () => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export interface PaymeButtonProps {
|
||||
amount: number;
|
||||
orderId: string;
|
||||
onSuccess?: (response: PaymePaymentResponse) => void;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
'use client';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { PaymentStatus, PaymePaymentResponse } from './types';
|
||||
import {
|
||||
calculateTotal,
|
||||
createPaymePayment,
|
||||
generateOrderId,
|
||||
redirectToPayme,
|
||||
toTiyin,
|
||||
} from './utils';
|
||||
import { PAYME_CONFIG } from './constant';
|
||||
|
||||
interface UsePaymentOptions {
|
||||
hasCertificate: boolean;
|
||||
onSuccess?: (response: PaymePaymentResponse) => void;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
interface UsePaymentReturn {
|
||||
status: PaymentStatus;
|
||||
error: string | null;
|
||||
totalAmount: number;
|
||||
handlePaymePayment: () => Promise<void>;
|
||||
resetError: () => void;
|
||||
}
|
||||
|
||||
export const usePayment = ({
|
||||
hasCertificate,
|
||||
onSuccess,
|
||||
onError,
|
||||
}: UsePaymentOptions): UsePaymentReturn => {
|
||||
const [status, setStatus] = useState<PaymentStatus>('idle');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const totalAmount = calculateTotal(hasCertificate);
|
||||
|
||||
const handlePaymePayment = useCallback(async () => {
|
||||
setStatus('loading');
|
||||
setError(null);
|
||||
|
||||
const orderId = generateOrderId();
|
||||
|
||||
try {
|
||||
const response = await createPaymePayment({
|
||||
amount: toTiyin(totalAmount),
|
||||
orderId,
|
||||
description: `Service fee${hasCertificate ? ' + Certificate' : ''}`,
|
||||
returnUrl: PAYME_CONFIG.RETURN_URL,
|
||||
});
|
||||
|
||||
setStatus('success');
|
||||
onSuccess?.(response);
|
||||
redirectToPayme(response.redirectUrl);
|
||||
} catch (err) {
|
||||
const paymentError =
|
||||
err instanceof Error
|
||||
? err
|
||||
: new Error('Payment failed. Please try again.');
|
||||
setStatus('error');
|
||||
setError(paymentError.message);
|
||||
onError?.(paymentError);
|
||||
}
|
||||
}, [totalAmount, hasCertificate, onSuccess, onError]);
|
||||
|
||||
const resetError = useCallback(() => {
|
||||
setError(null);
|
||||
setStatus('idle');
|
||||
}, []);
|
||||
|
||||
return { status, error, totalAmount, handlePaymePayment, resetError };
|
||||
};
|
||||
@@ -2,14 +2,6 @@
|
||||
export const formatPrice = (amount: number, currency: string): string =>
|
||||
`${amount.toLocaleString('uz-UZ')} ${currency}`;
|
||||
|
||||
// ─── Order ID Generator ────────────────────────────────────────────────────────
|
||||
|
||||
export const generateOrderId = (): string => {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.random().toString(36).slice(2, 8).toUpperCase();
|
||||
return `ORDER-${timestamp}-${random}`;
|
||||
};
|
||||
|
||||
// ─── Payme API ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,38 +34,6 @@ const CloseButton: React.FC<{ onClick: () => void }> = ({ onClick }) => (
|
||||
</button>
|
||||
);
|
||||
|
||||
// ─── Error Banner ──────────────────────────────────────────────────────────────
|
||||
|
||||
// const ErrorBanner: React.FC<{ message: string; onDismiss: () => void }> = ({
|
||||
// message,
|
||||
// onDismiss,
|
||||
// }) => (
|
||||
// <div
|
||||
// role="alert"
|
||||
// className="flex items-start gap-3 rounded-lg bg-red-50 border border-red-200 px-4 py-3"
|
||||
// >
|
||||
// <svg
|
||||
// className="shrink-0 mt-0.5 text-red-500"
|
||||
// width="16"
|
||||
// height="16"
|
||||
// viewBox="0 0 24 24"
|
||||
// fill="currentColor"
|
||||
// >
|
||||
// <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
|
||||
// </svg>
|
||||
// <p className="text-sm text-red-700 flex-1">{message}</p>
|
||||
// <button
|
||||
// onClick={onDismiss}
|
||||
// aria-label="Dismiss error"
|
||||
// className="text-red-400 hover:text-red-600 transition-colors"
|
||||
// >
|
||||
// <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
// <line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
||||
// </svg>
|
||||
// </button>
|
||||
// </div>
|
||||
// );
|
||||
|
||||
// ─── Security Badge ────────────────────────────────────────────────────────────
|
||||
|
||||
const SecurityBadge: React.FC<{ securityText: string }> = ({
|
||||
|
||||
Reference in New Issue
Block a user