generated from vignesh/searchengine
feat: create core payment service microservice with Cashfree gateway integration and operations portal
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s
This commit is contained in:
parent
369f9c0383
commit
493b204d32
5 changed files with 204 additions and 58 deletions
|
|
@ -150,13 +150,32 @@ MAILERS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cashfree Payment Gateway Configuration
|
# Cashfree Payment Gateway Configuration
|
||||||
CASHFREE_APP_ID = os.environ.get('CASHFREE_APP_ID', 'TEST1118017027fc875a9485063df8b007108111')
|
# Production API credentials (for requests originating from beta.tradhox.com & tradhox.com)
|
||||||
CASHFREE_SECRET_KEY = os.environ.get('CASHFREE_SECRET_KEY', 'cfsk_ma_test_3701af3af410228b8e707765dbb68c32_1cf9fb86')
|
CASHFREE_PROD_APP_ID = os.environ.get('CASHFREE_PROD_APP_ID', '1370798f950e30514fa051e93868970731')
|
||||||
|
CASHFREE_PROD_SECRET_KEY = os.environ.get('CASHFREE_PROD_SECRET_KEY', 'cfsk_ma_prod_d441689923a71877068fee6b6e2e0d05_a626ee00')
|
||||||
|
|
||||||
|
# Sandbox / Test API credentials (for local testing and sandbox simulation)
|
||||||
|
CASHFREE_TEST_APP_ID = os.environ.get('CASHFREE_TEST_APP_ID', 'TEST1118017027fc875a9485063df8b007108111')
|
||||||
|
CASHFREE_TEST_SECRET_KEY = os.environ.get('CASHFREE_TEST_SECRET_KEY', 'cfsk_ma_test_3701af3af410228b8e707765dbb68c32_1cf9fb86')
|
||||||
|
|
||||||
|
# Default active fallback
|
||||||
|
CASHFREE_APP_ID = os.environ.get('CASHFREE_APP_ID', CASHFREE_PROD_APP_ID)
|
||||||
|
CASHFREE_SECRET_KEY = os.environ.get('CASHFREE_SECRET_KEY', CASHFREE_PROD_SECRET_KEY)
|
||||||
CASHFREE_ENVIRONMENT = os.environ.get('CASHFREE_ENVIRONMENT', 'sandbox') # 'sandbox' or 'production'
|
CASHFREE_ENVIRONMENT = os.environ.get('CASHFREE_ENVIRONMENT', 'sandbox') # 'sandbox' or 'production'
|
||||||
CASHFREE_API_VERSION = os.environ.get('CASHFREE_API_VERSION', '2023-08-01')
|
CASHFREE_API_VERSION = os.environ.get('CASHFREE_API_VERSION', '2023-08-01')
|
||||||
|
|
||||||
|
# Domains that trigger automatic Cashfree production API routing
|
||||||
|
TRADHOX_PRODUCTION_DOMAINS = [
|
||||||
|
'beta.tradhox.com',
|
||||||
|
'tradhox.com',
|
||||||
|
'www.tradhox.com',
|
||||||
|
'betapayments.tradhox.com',
|
||||||
|
'payments.tradhox.com',
|
||||||
|
]
|
||||||
|
|
||||||
if CASHFREE_ENVIRONMENT == 'production':
|
if CASHFREE_ENVIRONMENT == 'production':
|
||||||
CASHFREE_BASE_URL = 'https://api.cashfree.com/pg'
|
CASHFREE_BASE_URL = 'https://api.cashfree.com/pg'
|
||||||
else:
|
else:
|
||||||
CASHFREE_BASE_URL = 'https://sandbox.cashfree.com/pg'
|
CASHFREE_BASE_URL = 'https://sandbox.cashfree.com/pg'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@ class PaymentEngine:
|
||||||
"""Allows dynamic registration of gateway adapters (e.g. Razorpay)."""
|
"""Allows dynamic registration of gateway adapters (e.g. Razorpay)."""
|
||||||
self.gateway_registry[name.upper()] = gateway_class
|
self.gateway_registry[name.upper()] = gateway_class
|
||||||
|
|
||||||
def get_adapter(self, config: PaymentGatewayConfig) -> Optional[BasePaymentGateway]:
|
def get_adapter(self, config: PaymentGatewayConfig, request=None, environment=None) -> Optional[BasePaymentGateway]:
|
||||||
gateway_class = self.gateway_registry.get(config.name.upper())
|
gateway_class = self.gateway_registry.get(config.name.upper())
|
||||||
if gateway_class:
|
if gateway_class:
|
||||||
|
if config.name.upper() == 'CASHFREE':
|
||||||
|
return gateway_class(config=config, request=request, environment=environment)
|
||||||
return gateway_class(config=config)
|
return gateway_class(config=config)
|
||||||
logger.warning(f"No adapter registered for gateway: {config.name}")
|
logger.warning(f"No adapter registered for gateway: {config.name}")
|
||||||
return None
|
return None
|
||||||
|
|
@ -77,6 +79,8 @@ class PaymentEngine:
|
||||||
notify_url: str,
|
notify_url: str,
|
||||||
order_note: Optional[str] = None,
|
order_note: Optional[str] = None,
|
||||||
metadata: Optional[Dict[str, Any]] = None,
|
metadata: Optional[Dict[str, Any]] = None,
|
||||||
|
request=None,
|
||||||
|
environment=None,
|
||||||
) -> Tuple[bool, PaymentOrder, Optional[PaymentAttempt], Optional[str]]:
|
) -> Tuple[bool, PaymentOrder, Optional[PaymentAttempt], Optional[str]]:
|
||||||
"""
|
"""
|
||||||
Creates an order with automatic failover across registered gateways.
|
Creates an order with automatic failover across registered gateways.
|
||||||
|
|
@ -103,12 +107,13 @@ class PaymentEngine:
|
||||||
last_error = "No active gateways available"
|
last_error = "No active gateways available"
|
||||||
|
|
||||||
for config in candidates:
|
for config in candidates:
|
||||||
adapter = self.get_adapter(config)
|
adapter = self.get_adapter(config, request=request, environment=environment)
|
||||||
if not adapter:
|
if not adapter:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f"Attempting order {order_id} via {adapter.gateway_name} (Priority {config.priority})")
|
logger.info(f"Attempting order {order_id} via {adapter.gateway_name} (Priority {config.priority})")
|
||||||
|
|
||||||
|
|
||||||
# Execute order creation with gateway
|
# Execute order creation with gateway
|
||||||
resp: GatewayOrderResponse = adapter.create_order(
|
resp: GatewayOrderResponse = adapter.create_order(
|
||||||
order_id=order_id,
|
order_id=order_id,
|
||||||
|
|
|
||||||
|
|
@ -17,27 +17,54 @@ class CashfreeGateway(BasePaymentGateway):
|
||||||
|
|
||||||
GATEWAY_NAME = 'CASHFREE'
|
GATEWAY_NAME = 'CASHFREE'
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config=None, environment: Optional[str] = None, request=None):
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
# Use database config if provided, else fall back to django settings
|
|
||||||
if config:
|
|
||||||
self.app_id = config.app_id or getattr(settings, 'CASHFREE_APP_ID', '')
|
|
||||||
self.secret_key = config.secret_key or getattr(settings, 'CASHFREE_SECRET_KEY', '')
|
|
||||||
self.environment = config.environment or getattr(settings, 'CASHFREE_ENVIRONMENT', 'sandbox')
|
|
||||||
self.api_version = config.api_version or getattr(settings, 'CASHFREE_API_VERSION', '2023-08-01')
|
|
||||||
else:
|
|
||||||
self.app_id = getattr(settings, 'CASHFREE_APP_ID', '')
|
|
||||||
self.secret_key = getattr(settings, 'CASHFREE_SECRET_KEY', '')
|
|
||||||
self.environment = getattr(settings, 'CASHFREE_ENVIRONMENT', 'sandbox')
|
|
||||||
self.api_version = getattr(settings, 'CASHFREE_API_VERSION', '2023-08-01')
|
|
||||||
|
|
||||||
|
# 1. Determine target environment (production vs sandbox)
|
||||||
|
resolved_env = None
|
||||||
|
if environment:
|
||||||
|
resolved_env = environment
|
||||||
|
elif request:
|
||||||
|
req_data = getattr(request, 'data', {}) or {}
|
||||||
|
if isinstance(req_data, dict) and req_data.get('environment') in ('production', 'sandbox'):
|
||||||
|
resolved_env = req_data.get('environment')
|
||||||
|
else:
|
||||||
|
# Inspect Origin, Referer, Host headers for Tradhox production domains
|
||||||
|
origin = request.META.get('HTTP_ORIGIN', '').lower()
|
||||||
|
referer = request.META.get('HTTP_REFERER', '').lower()
|
||||||
|
host = request.META.get('HTTP_HOST', '').lower()
|
||||||
|
prod_domains = getattr(settings, 'TRADHOX_PRODUCTION_DOMAINS', [
|
||||||
|
'beta.tradhox.com', 'tradhox.com', 'www.tradhox.com', 'betapayments.tradhox.com'
|
||||||
|
])
|
||||||
|
for domain in prod_domains:
|
||||||
|
if domain in origin or domain in referer or domain in host:
|
||||||
|
resolved_env = 'production'
|
||||||
|
break
|
||||||
|
|
||||||
|
if not resolved_env:
|
||||||
|
if config and config.environment:
|
||||||
|
resolved_env = config.environment
|
||||||
|
else:
|
||||||
|
resolved_env = getattr(settings, 'CASHFREE_ENVIRONMENT', 'sandbox')
|
||||||
|
|
||||||
|
self.environment = resolved_env
|
||||||
|
self.api_version = (config.api_version if config else None) or getattr(settings, 'CASHFREE_API_VERSION', '2023-08-01')
|
||||||
|
|
||||||
|
# 2. Select credentials according to resolved environment
|
||||||
if self.environment == 'production':
|
if self.environment == 'production':
|
||||||
|
self.app_id = (config.app_id if config and config.environment == 'production' and config.app_id else getattr(settings, 'CASHFREE_PROD_APP_ID', '1370798f950e30514fa051e93868970731'))
|
||||||
|
self.secret_key = (config.secret_key if config and config.environment == 'production' and config.secret_key else getattr(settings, 'CASHFREE_PROD_SECRET_KEY', 'cfsk_ma_prod_d441689923a71877068fee6b6e2e0d05_a626ee00'))
|
||||||
self.base_url = 'https://api.cashfree.com/pg'
|
self.base_url = 'https://api.cashfree.com/pg'
|
||||||
self.checkout_base = 'https://payments.cashfree.com/order/#'
|
self.checkout_base = 'https://payments.cashfree.com/order/#'
|
||||||
else:
|
else:
|
||||||
|
self.app_id = (config.app_id if config and config.environment == 'sandbox' and config.app_id else getattr(settings, 'CASHFREE_TEST_APP_ID', 'TEST1118017027fc875a9485063df8b007108111'))
|
||||||
|
self.secret_key = (config.secret_key if config and config.environment == 'sandbox' and config.secret_key else getattr(settings, 'CASHFREE_TEST_SECRET_KEY', 'cfsk_ma_test_3701af3af410228b8e707765dbb68c32_1cf9fb86'))
|
||||||
self.base_url = 'https://sandbox.cashfree.com/pg'
|
self.base_url = 'https://sandbox.cashfree.com/pg'
|
||||||
self.checkout_base = 'https://payments-test.cashfree.com/order/#'
|
self.checkout_base = 'https://payments-test.cashfree.com/order/#'
|
||||||
|
|
||||||
|
logger.info(f"CashfreeGateway initialized in [{self.environment.upper()}] mode (App ID: {self.app_id[:8]}...)")
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def gateway_name(self) -> str:
|
def gateway_name(self) -> str:
|
||||||
return self.GATEWAY_NAME
|
return self.GATEWAY_NAME
|
||||||
|
|
@ -183,13 +210,28 @@ class CashfreeGateway(BasePaymentGateway):
|
||||||
).digest()
|
).digest()
|
||||||
computed_signature = base64.b64encode(computed_hmac).decode('utf-8')
|
computed_signature = base64.b64encode(computed_hmac).decode('utf-8')
|
||||||
|
|
||||||
if not hmac.compare_digest(computed_signature, signature):
|
is_matched = hmac.compare_digest(computed_signature, signature)
|
||||||
|
if not is_matched:
|
||||||
|
# Try alternate key (production vs test) in case order was placed under different environment
|
||||||
|
alt_key = getattr(settings, 'CASHFREE_PROD_SECRET_KEY', '') if self.environment != 'production' else getattr(settings, 'CASHFREE_TEST_SECRET_KEY', '')
|
||||||
|
if alt_key and alt_key != self.secret_key:
|
||||||
|
alt_hmac = hmac.new(
|
||||||
|
key=alt_key.encode('utf-8'),
|
||||||
|
msg=payload_to_sign,
|
||||||
|
digestmod=hashlib.sha256
|
||||||
|
).digest()
|
||||||
|
alt_sig = base64.b64encode(alt_hmac).decode('utf-8')
|
||||||
|
if hmac.compare_digest(alt_sig, signature):
|
||||||
|
is_matched = True
|
||||||
|
|
||||||
|
if not is_matched:
|
||||||
return GatewayVerifyResult(
|
return GatewayVerifyResult(
|
||||||
is_valid=False,
|
is_valid=False,
|
||||||
event_type="SIGNATURE_MISMATCH",
|
event_type="SIGNATURE_MISMATCH",
|
||||||
error_message="Computed signature does not match header signature"
|
error_message="Computed signature does not match header signature"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Parse JSON
|
# Parse JSON
|
||||||
try:
|
try:
|
||||||
body_json = json.loads(raw_body.decode('utf-8'))
|
body_json = json.loads(raw_body.decode('utf-8'))
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ class CreatePaymentOrderView(APIView):
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
|
requested_env = request.data.get('environment')
|
||||||
success, order, attempt, error_msg = payment_engine.initiate_payment_order(
|
success, order, attempt, error_msg = payment_engine.initiate_payment_order(
|
||||||
order_id=data['order_id'],
|
order_id=data['order_id'],
|
||||||
amount=float(data['amount']),
|
amount=float(data['amount']),
|
||||||
|
|
@ -49,6 +50,8 @@ class CreatePaymentOrderView(APIView):
|
||||||
notify_url=data.get('notify_url', ''),
|
notify_url=data.get('notify_url', ''),
|
||||||
order_note=data.get('order_note', ''),
|
order_note=data.get('order_note', ''),
|
||||||
metadata=data.get('metadata', {}),
|
metadata=data.get('metadata', {}),
|
||||||
|
request=request,
|
||||||
|
environment=requested_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
|
|
@ -64,8 +67,12 @@ class CreatePaymentOrderView(APIView):
|
||||||
if attempt and attempt.raw_response:
|
if attempt and attempt.raw_response:
|
||||||
checkout_url = attempt.raw_response.get("payment_session_id")
|
checkout_url = attempt.raw_response.get("payment_session_id")
|
||||||
if checkout_url and attempt.gateway_name == 'CASHFREE':
|
if checkout_url and attempt.gateway_name == 'CASHFREE':
|
||||||
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
origin = request.META.get('HTTP_ORIGIN', '').lower()
|
||||||
base = 'https://payments.cashfree.com/order/#' if (config and config.environment == 'production') else 'https://payments-test.cashfree.com/order/#'
|
referer = request.META.get('HTTP_REFERER', '').lower()
|
||||||
|
host = request.META.get('HTTP_HOST', '').lower()
|
||||||
|
prod_domains = getattr(settings, 'TRADHOX_PRODUCTION_DOMAINS', ['beta.tradhox.com', 'tradhox.com'])
|
||||||
|
is_prod = (requested_env == 'production') or any(d in origin or d in referer or d in host for d in prod_domains)
|
||||||
|
base = 'https://payments.cashfree.com/order/#' if is_prod else 'https://payments-test.cashfree.com/order/#'
|
||||||
checkout_url = f"{base}{attempt.payment_session_id}"
|
checkout_url = f"{base}{attempt.payment_session_id}"
|
||||||
|
|
||||||
return Response({
|
return Response({
|
||||||
|
|
@ -441,7 +448,7 @@ class CashfreeDirectPaySessionView(APIView):
|
||||||
return Response({"error": "payment_session_id is required"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": "payment_session_id is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
||||||
gateway = CashfreeGateway(config=config)
|
gateway = CashfreeGateway(config=config, request=request, environment=request.data.get('environment'))
|
||||||
|
|
||||||
url = f"{gateway.base_url}/orders/sessions"
|
url = f"{gateway.base_url}/orders/sessions"
|
||||||
method_payload = {"channel": channel}
|
method_payload = {"channel": channel}
|
||||||
|
|
@ -473,7 +480,7 @@ class CashfreeDirectPaymentsListView(APIView):
|
||||||
|
|
||||||
def get(self, request, order_id):
|
def get(self, request, order_id):
|
||||||
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
||||||
gateway = CashfreeGateway(config=config)
|
gateway = CashfreeGateway(config=config, request=request, environment=request.query_params.get('environment'))
|
||||||
|
|
||||||
url = f"{gateway.base_url}/orders/{order_id}/payments"
|
url = f"{gateway.base_url}/orders/{order_id}/payments"
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -494,7 +501,7 @@ class CashfreeDirectSettlementsView(APIView):
|
||||||
|
|
||||||
def get(self, request, order_id):
|
def get(self, request, order_id):
|
||||||
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
||||||
gateway = CashfreeGateway(config=config)
|
gateway = CashfreeGateway(config=config, request=request, environment=request.query_params.get('environment'))
|
||||||
|
|
||||||
url = f"{gateway.base_url}/orders/{order_id}/settlements"
|
url = f"{gateway.base_url}/orders/{order_id}/settlements"
|
||||||
import requests
|
import requests
|
||||||
|
|
@ -509,7 +516,7 @@ class CashfreeDirectSettlementsView(APIView):
|
||||||
class CashfreeDirectCreateOrderView(APIView):
|
class CashfreeDirectCreateOrderView(APIView):
|
||||||
"""
|
"""
|
||||||
POST /api/payments/cashfree/orders/create/
|
POST /api/payments/cashfree/orders/create/
|
||||||
Creates an order directly against Cashfree Sandbox, bypassing orchestrator routing.
|
Creates an order directly against Cashfree, bypassing orchestrator routing.
|
||||||
Records the order and attempt in the database for tracking and refunds.
|
Records the order and attempt in the database for tracking and refunds.
|
||||||
"""
|
"""
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
@ -529,7 +536,7 @@ class CashfreeDirectCreateOrderView(APIView):
|
||||||
return Response({"error": "order_id and amount are required"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": "order_id and amount are required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
||||||
gateway = CashfreeGateway(config=config)
|
gateway = CashfreeGateway(config=config, request=request, environment=request.data.get('environment'))
|
||||||
|
|
||||||
# Call Cashfree API directly
|
# Call Cashfree API directly
|
||||||
resp = gateway.create_order(
|
resp = gateway.create_order(
|
||||||
|
|
@ -598,7 +605,7 @@ class CashfreeDirectRawOrderView(APIView):
|
||||||
|
|
||||||
def get(self, request, order_id):
|
def get(self, request, order_id):
|
||||||
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
||||||
gateway = CashfreeGateway(config=config)
|
gateway = CashfreeGateway(config=config, request=request, environment=request.query_params.get('environment'))
|
||||||
raw_order = gateway.get_order(order_id)
|
raw_order = gateway.get_order(order_id)
|
||||||
return Response(raw_order, status=status.HTTP_200_OK)
|
return Response(raw_order, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
@ -606,7 +613,7 @@ class CashfreeDirectRawOrderView(APIView):
|
||||||
class CashfreeQuickSessionView(APIView):
|
class CashfreeQuickSessionView(APIView):
|
||||||
"""
|
"""
|
||||||
POST /api/payments/cashfree/quick-session/
|
POST /api/payments/cashfree/quick-session/
|
||||||
Automatically generates a live Cashfree Sandbox order and returns the payment_session_id.
|
Automatically generates a live Cashfree order and returns the payment_session_id.
|
||||||
Requires zero manual parameters, enabling 1-click dynamic session creation.
|
Requires zero manual parameters, enabling 1-click dynamic session creation.
|
||||||
"""
|
"""
|
||||||
permission_classes = [permissions.AllowAny]
|
permission_classes = [permissions.AllowAny]
|
||||||
|
|
@ -615,7 +622,7 @@ class CashfreeQuickSessionView(APIView):
|
||||||
amount = float(request.data.get('amount') or 100.00)
|
amount = float(request.data.get('amount') or 100.00)
|
||||||
rand_id = f"CF-UPI-{int(timezone.now().timestamp())}"
|
rand_id = f"CF-UPI-{int(timezone.now().timestamp())}"
|
||||||
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
config = PaymentGatewayConfig.objects.filter(name='CASHFREE').first()
|
||||||
gateway = CashfreeGateway(config=config)
|
gateway = CashfreeGateway(config=config, request=request, environment=request.data.get('environment'))
|
||||||
|
|
||||||
resp = gateway.create_order(
|
resp = gateway.create_order(
|
||||||
order_id=rand_id,
|
order_id=rand_id,
|
||||||
|
|
|
||||||
|
|
@ -985,20 +985,43 @@
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2 class="section-title" style="color: var(--cashfree-color);">
|
<h2 class="section-title" style="color: var(--cashfree-color);">
|
||||||
<svg width="22" height="22" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
<svg width="22" height="22" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
||||||
Cashfree Dedicated Vendor Studio (Direct Sandbox)
|
Cashfree Dedicated Vendor Studio
|
||||||
</h2>
|
</h2>
|
||||||
<span class="badge" style="background: var(--cashfree-glow); color: #F472B6; border: 1px solid var(--border-cashfree);">Isolated Gateway Engine</span>
|
<div style="display: flex; gap: 10px; align-items: center;">
|
||||||
|
<span class="badge" id="cf-env-badge" style="background: var(--cashfree-glow); color: #F472B6; border: 1px solid var(--border-cashfree);">Mode: Sandbox</span>
|
||||||
|
<span class="badge" style="background: rgba(16, 185, 129, 0.15); color: #34D399; border: 1px solid rgba(16, 185, 129, 0.3);">
|
||||||
|
🌐 Auto-Routing: beta.tradhox.com & tradhox.com ➔ Production
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cashfree Direct Live Config & Connectivity -->
|
<!-- Cashfree Direct Live Config & Environment Switcher -->
|
||||||
<div style="background: rgba(236, 72, 153, 0.08); border: 1px solid var(--border-cashfree); border-radius: var(--radius-md); padding: 18px 24px; margin-bottom: 24px; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 16px;">
|
<div style="background: rgba(236, 72, 153, 0.08); border: 1px solid var(--border-cashfree); border-radius: var(--radius-md); padding: 18px 24px; margin-bottom: 24px; display: flex; flex-direction: column; gap: 14px;">
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: 24px; font-size: 13px;">
|
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 14px;">
|
||||||
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">App ID:</span> <code style="color: #F472B6; font-family: var(--font-mono);">TEST1118017027fc875a9485063df8b007108111</code></div>
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">Secret:</span> <code style="color: #F472B6; font-family: var(--font-mono);">cfsk_ma_test_3701...1cf9fb86</code></div>
|
<label style="font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0;">Target Environment:</label>
|
||||||
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">API Base:</span> <code style="color: #F472B6; font-family: var(--font-mono);">https://sandbox.cashfree.com/pg</code></div>
|
<select id="cf-studio-env-select" onchange="onCfStudioEnvChange()" class="form-control" style="width: auto; padding: 6px 14px; font-size: 13px; font-weight: 600; background: #0A0D14; border-color: var(--border-cashfree); color: #F472B6; border-radius: var(--radius-sm); cursor: pointer;">
|
||||||
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">Version:</span> <code style="color: #F472B6; font-family: var(--font-mono);">2023-08-01</code></div>
|
<option value="sandbox" selected>Sandbox Mode (Test Simulator)</option>
|
||||||
|
<option value="production">Production Mode (api.cashfree.com/pg)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 10px; align-items: center;">
|
||||||
|
<button onclick="pingCashfreeDirectApi()" class="btn btn-cashfree btn-sm" id="btn-ping-cf">⚡ Ping Active Gateway</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 20px; font-size: 12px; background: rgba(0,0,0,0.3); padding: 12px 16px; border-radius: var(--radius-sm); border: 1px solid rgba(255,255,255,0.05);">
|
||||||
|
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">Active App ID:</span> <code id="cf-disp-appid" style="color: #F472B6; font-family: var(--font-mono); font-weight: 600;">TEST1118017027fc875a9485063df8b007108111</code></div>
|
||||||
|
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">Secret Key:</span> <code id="cf-disp-secret" style="color: #F472B6; font-family: var(--font-mono);">cfsk_ma_test_3701...1cf9fb86</code></div>
|
||||||
|
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">API Base URL:</span> <code id="cf-disp-baseurl" style="color: #A5B4FC; font-family: var(--font-mono);">https://sandbox.cashfree.com/pg</code></div>
|
||||||
|
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">Checkout Base:</span> <code id="cf-disp-checkout" style="color: #34D399; font-family: var(--font-mono);">https://payments-test.cashfree.com/order/#</code></div>
|
||||||
|
<div><span style="color: var(--text-muted); font-size: 11px; text-transform: uppercase; font-weight: 600;">API Version:</span> <code style="color: #F472B6; font-family: var(--font-mono);">2023-08-01</code></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; font-size: 11px; color: var(--text-dim);">
|
||||||
|
<span>🔒 <strong>Credentials in Memory:</strong> Prod: <code>1370798f...970731</code> (api.cashfree.com) | Sandbox: <code>TEST1118...8111</code> (sandbox.cashfree.com)</span>
|
||||||
|
<span style="color: #34D399;">✓ Automatic origin-based routing active for beta.tradhox.com & tradhox.com</span>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="pingCashfreeDirectApi()" class="btn btn-cashfree btn-sm" id="btn-ping-cf">⚡ Ping Cashfree Direct API</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sandbox-container" style="border-color: var(--border-cashfree);">
|
<div class="sandbox-container" style="border-color: var(--border-cashfree);">
|
||||||
|
|
@ -1653,15 +1676,57 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// CASHFREE DEDICATED VENDOR STUDIO HANDLERS
|
// CASHFREE DEDICATED VENDOR STUDIO HANDLERS
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
|
function getCfStudioEnv() {
|
||||||
|
const el = document.getElementById('cf-studio-env-select');
|
||||||
|
return el ? el.value : 'sandbox';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCfStudioEnvChange() {
|
||||||
|
const env = getCfStudioEnv();
|
||||||
|
const badge = document.getElementById('cf-env-badge');
|
||||||
|
const appIdEl = document.getElementById('cf-disp-appid');
|
||||||
|
const secretEl = document.getElementById('cf-disp-secret');
|
||||||
|
const baseEl = document.getElementById('cf-disp-baseurl');
|
||||||
|
const checkoutEl = document.getElementById('cf-disp-checkout');
|
||||||
|
|
||||||
|
if (env === 'production') {
|
||||||
|
if (badge) {
|
||||||
|
badge.textContent = 'Mode: Production (Live)';
|
||||||
|
badge.style.background = 'rgba(239, 68, 68, 0.15)';
|
||||||
|
badge.style.color = '#F87171';
|
||||||
|
badge.style.borderColor = 'rgba(239, 68, 68, 0.3)';
|
||||||
|
}
|
||||||
|
if (appIdEl) appIdEl.textContent = '1370798f950e30514fa051e93868970731';
|
||||||
|
if (secretEl) secretEl.textContent = 'cfsk_ma_prod_d441...a626ee00';
|
||||||
|
if (baseEl) baseEl.textContent = 'https://api.cashfree.com/pg';
|
||||||
|
if (checkoutEl) checkoutEl.textContent = 'https://payments.cashfree.com/order/#';
|
||||||
|
showToast('Switched Cashfree Studio to Production Mode (api.cashfree.com)', 'info');
|
||||||
|
} else {
|
||||||
|
if (badge) {
|
||||||
|
badge.textContent = 'Mode: Sandbox';
|
||||||
|
badge.style.background = 'var(--cashfree-glow)';
|
||||||
|
badge.style.color = '#F472B6';
|
||||||
|
badge.style.borderColor = 'var(--border-cashfree)';
|
||||||
|
}
|
||||||
|
if (appIdEl) appIdEl.textContent = 'TEST1118017027fc875a9485063df8b007108111';
|
||||||
|
if (secretEl) secretEl.textContent = 'cfsk_ma_test_3701...1cf9fb86';
|
||||||
|
if (baseEl) baseEl.textContent = 'https://sandbox.cashfree.com/pg';
|
||||||
|
if (checkoutEl) checkoutEl.textContent = 'https://payments-test.cashfree.com/order/#';
|
||||||
|
showToast('Switched Cashfree Studio to Sandbox Mode', 'info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleDirectCashfreeOrder(e) {
|
async function handleDirectCashfreeOrder(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const btn = document.getElementById('btn-cf-direct-create');
|
const btn = document.getElementById('btn-cf-direct-create');
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
|
const env = getCfStudioEnv();
|
||||||
const payload = {
|
const payload = {
|
||||||
order_id: document.getElementById('cf-direct-order-id').value,
|
order_id: document.getElementById('cf-direct-order-id').value,
|
||||||
amount: document.getElementById('cf-direct-amount').value,
|
amount: document.getElementById('cf-direct-amount').value,
|
||||||
|
|
@ -1670,12 +1735,13 @@
|
||||||
customer_name: document.getElementById('cf-direct-cust-name').value,
|
customer_name: document.getElementById('cf-direct-cust-name').value,
|
||||||
customer_email: document.getElementById('cf-direct-email').value,
|
customer_email: document.getElementById('cf-direct-email').value,
|
||||||
customer_phone: document.getElementById('cf-direct-phone').value,
|
customer_phone: document.getElementById('cf-direct-phone').value,
|
||||||
order_note: 'Direct Cashfree Vendor Sandbox Order',
|
order_note: `Direct Cashfree Vendor Order [${env.toUpperCase()}]`,
|
||||||
return_url: window.location.origin + '/portal/?order_id={order_id}'
|
return_url: window.location.origin + '/portal/?order_id={order_id}',
|
||||||
|
environment: env
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logConsole('Sending Direct Order to Cashfree API...', payload);
|
logConsole(`Sending Direct Order to Cashfree API [${env.toUpperCase()}]...`, payload);
|
||||||
const res = await fetch('/api/payments/cashfree/orders/create/', {
|
const res = await fetch('/api/payments/cashfree/orders/create/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -1688,12 +1754,12 @@
|
||||||
currentOrderId = data.order_id;
|
currentOrderId = data.order_id;
|
||||||
currentSessionId = data.payment_session_id;
|
currentSessionId = data.payment_session_id;
|
||||||
|
|
||||||
showToast(`Cashfree order ${data.order_id} created successfully!`, 'success');
|
showToast(`Cashfree order ${data.order_id} created successfully [${env.toUpperCase()}]!`, 'success');
|
||||||
|
|
||||||
const drawer = document.getElementById('cf-direct-drawer');
|
const drawer = document.getElementById('cf-direct-drawer');
|
||||||
drawer.style.display = 'block';
|
drawer.style.display = 'block';
|
||||||
document.getElementById('cf-drawer-info').textContent =
|
document.getElementById('cf-drawer-info').textContent =
|
||||||
`Cashfree Order ID: ${data.gateway_order_id} | Session ID: ${data.payment_session_id}`;
|
`Order: ${data.gateway_order_id || data.order_id} | Session: ${data.payment_session_id} [${env}]`;
|
||||||
|
|
||||||
document.getElementById('btn-cf-direct-hosted').href = data.checkout_url || '#';
|
document.getElementById('btn-cf-direct-hosted').href = data.checkout_url || '#';
|
||||||
document.getElementById('upi-session-id').value = data.payment_session_id;
|
document.getElementById('upi-session-id').value = data.payment_session_id;
|
||||||
|
|
@ -1799,11 +1865,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logConsole(`Auto-generating live Cashfree Payment Session for ₹${amount}...`);
|
const env = getCfStudioEnv();
|
||||||
|
logConsole(`Auto-generating live Cashfree Payment Session for ₹${amount} [${env}]...`);
|
||||||
const res = await fetch('/api/payments/cashfree/quick-session/', {
|
const res = await fetch('/api/payments/cashfree/quick-session/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ amount: amount })
|
body: JSON.stringify({ amount: amount, environment: env })
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
|
|
@ -1816,9 +1883,9 @@
|
||||||
|
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.style.color = 'var(--success)';
|
statusEl.style.color = 'var(--success)';
|
||||||
statusEl.textContent = `✓ Generated Session for ${data.order_id} (₹${amount})`;
|
statusEl.textContent = `✓ Generated Session for ${data.order_id} (₹${amount}) [${env}]`;
|
||||||
}
|
}
|
||||||
showToast(`Live Session ID generated for order ${data.order_id}!`, 'success');
|
showToast(`Live Session ID generated for order ${data.order_id} [${env}]!`, 'success');
|
||||||
logConsole('Cashfree Quick Session Generated:', data);
|
logConsole('Cashfree Quick Session Generated:', data);
|
||||||
refreshDashboard();
|
refreshDashboard();
|
||||||
return data.payment_session_id;
|
return data.payment_session_id;
|
||||||
|
|
@ -1871,13 +1938,15 @@
|
||||||
out.textContent = 'Calling Cashfree /orders/sessions endpoint...';
|
out.textContent = 'Calling Cashfree /orders/sessions endpoint...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const env = getCfStudioEnv();
|
||||||
const res = await fetch('/api/payments/cashfree/pay/', {
|
const res = await fetch('/api/payments/cashfree/pay/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
payment_session_id: sessionId,
|
payment_session_id: sessionId,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
upi_id: upiId
|
upi_id: upiId,
|
||||||
|
environment: env
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
@ -1916,15 +1985,16 @@
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.style.display = 'inline';
|
statusEl.style.display = 'inline';
|
||||||
statusEl.style.color = '#FBBF24';
|
statusEl.style.color = '#FBBF24';
|
||||||
statusEl.textContent = 'Creating sandbox order on Cashfree...';
|
statusEl.textContent = 'Creating order on Cashfree...';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logConsole('Auto-generating Cashfree test order for inspection...');
|
const env = getCfStudioEnv();
|
||||||
|
logConsole(`Auto-generating Cashfree test order for inspection [${env}]...`);
|
||||||
const res = await fetch('/api/payments/cashfree/quick-session/', {
|
const res = await fetch('/api/payments/cashfree/quick-session/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ amount: 150 })
|
body: JSON.stringify({ amount: 150, environment: env })
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (res.ok && data.success) {
|
if (res.ok && data.success) {
|
||||||
|
|
@ -1937,9 +2007,9 @@
|
||||||
|
|
||||||
if (statusEl) {
|
if (statusEl) {
|
||||||
statusEl.style.color = 'var(--success)';
|
statusEl.style.color = 'var(--success)';
|
||||||
statusEl.textContent = `✓ Generated ${data.order_id}`;
|
statusEl.textContent = `✓ Generated ${data.order_id} [${env}]`;
|
||||||
}
|
}
|
||||||
showToast(`Generated order ${data.order_id} on Cashfree!`, 'success');
|
showToast(`Generated order ${data.order_id} on Cashfree [${env}]!`, 'success');
|
||||||
refreshDashboard();
|
refreshDashboard();
|
||||||
|
|
||||||
if (target === 'inspector') {
|
if (target === 'inspector') {
|
||||||
|
|
@ -2007,11 +2077,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const env = getCfStudioEnv();
|
||||||
const out = document.getElementById('cf-inspector-output');
|
const out = document.getElementById('cf-inspector-output');
|
||||||
out.textContent = `Querying Cashfree API for raw order ${orderId}...`;
|
out.textContent = `Querying Cashfree API for raw order ${orderId} [${env}]...`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/payments/cashfree/orders/${orderId}/raw/`);
|
const res = await fetch(`/api/payments/cashfree/orders/${orderId}/raw/?environment=${env}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
out.textContent = JSON.stringify(data, null, 2);
|
out.textContent = JSON.stringify(data, null, 2);
|
||||||
logConsole('Raw Cashfree Server Order Object:', data);
|
logConsole('Raw Cashfree Server Order Object:', data);
|
||||||
|
|
@ -2027,11 +2098,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const env = getCfStudioEnv();
|
||||||
const out = document.getElementById('cf-inspector-output');
|
const out = document.getElementById('cf-inspector-output');
|
||||||
out.textContent = `Querying Cashfree /orders/${orderId}/payments directly...`;
|
out.textContent = `Querying Cashfree /orders/${orderId}/payments directly [${env}]...`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/payments/cashfree/orders/${orderId}/payments/`);
|
const res = await fetch(`/api/payments/cashfree/orders/${orderId}/payments/?environment=${env}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
out.textContent = JSON.stringify(data, null, 2);
|
out.textContent = JSON.stringify(data, null, 2);
|
||||||
logConsole('Direct Cashfree Payments Array:', data);
|
logConsole('Direct Cashfree Payments Array:', data);
|
||||||
|
|
@ -2048,11 +2120,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const env = getCfStudioEnv();
|
||||||
const out = document.getElementById('cf-inspector-output');
|
const out = document.getElementById('cf-inspector-output');
|
||||||
out.textContent = `Querying Cashfree /orders/${orderId}/settlements directly...`;
|
out.textContent = `Querying Cashfree /orders/${orderId}/settlements directly [${env}]...`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/payments/cashfree/orders/${orderId}/settlements/`);
|
const res = await fetch(`/api/payments/cashfree/orders/${orderId}/settlements/?environment=${env}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
out.textContent = JSON.stringify(data, null, 2);
|
out.textContent = JSON.stringify(data, null, 2);
|
||||||
logConsole('Direct Cashfree Settlement Details:', data);
|
logConsole('Direct Cashfree Settlement Details:', data);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue