Merge pull request #37 from rede5/codex/add-stripe-data-and-payment-gateway
Add payment gateway & LavinMQ credentials and lock one-time credential creation
This commit is contained in:
commit
68801ba3ca
5 changed files with 75 additions and 24 deletions
|
|
@ -38,6 +38,21 @@ CORS_ORIGINS=http://localhost:3000,http://localhost:8963
|
|||
CLOUDFLARE_API_TOKEN=your-cloudflare-api-token
|
||||
CLOUDFLARE_ZONE_ID=your-zone-id
|
||||
|
||||
# =============================================================================
|
||||
# Stripe
|
||||
# =============================================================================
|
||||
STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key
|
||||
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
|
||||
STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key
|
||||
|
||||
# =============================================================================
|
||||
# Gateway de Pagamento (Fictício)
|
||||
# =============================================================================
|
||||
PAYMENT_GATEWAY_MERCHANT_ID=merchant_demo
|
||||
PAYMENT_GATEWAY_API_KEY=fake_gateway_key
|
||||
PAYMENT_GATEWAY_ENDPOINT=https://payments.example.com/api
|
||||
PAYMENT_GATEWAY_WEBHOOK_SECRET=fake_webhook_secret
|
||||
|
||||
# =============================================================================
|
||||
# cPanel API (for email management)
|
||||
# =============================================================================
|
||||
|
|
@ -51,3 +66,8 @@ CPANEL_API_TOKEN=your-cpanel-api-token
|
|||
RESEND_API_KEY=re_xxxx_your_api_key
|
||||
EMAIL_FROM=noreply@gohorsejobs.com
|
||||
APP_URL=https://gohorsejobs.com
|
||||
|
||||
# =============================================================================
|
||||
# LavinMQ (AMQP)
|
||||
# =============================================================================
|
||||
AMQP_URL=amqps://nwigjply:nwEGZdcfz3--H8xc68IKmjiBCVtI09Cq@horse.lmq.cloudamqp.com/nwigjply
|
||||
|
|
|
|||
|
|
@ -268,6 +268,14 @@ func (s *CredentialsService) BootstrapCredentials(ctx context.Context) error {
|
|||
"publishableKey": os.Getenv("STRIPE_PUBLISHABLE_KEY"),
|
||||
}
|
||||
},
|
||||
"payment_gateway": func() interface{} {
|
||||
return map[string]string{
|
||||
"merchantId": os.Getenv("PAYMENT_GATEWAY_MERCHANT_ID"),
|
||||
"apiKey": os.Getenv("PAYMENT_GATEWAY_API_KEY"),
|
||||
"endpoint": os.Getenv("PAYMENT_GATEWAY_ENDPOINT"),
|
||||
"webhookSecret": os.Getenv("PAYMENT_GATEWAY_WEBHOOK_SECRET"),
|
||||
}
|
||||
},
|
||||
"storage": func() interface{} {
|
||||
return map[string]string{
|
||||
"endpoint": os.Getenv("AWS_ENDPOINT"),
|
||||
|
|
@ -277,6 +285,11 @@ func (s *CredentialsService) BootstrapCredentials(ctx context.Context) error {
|
|||
"region": os.Getenv("AWS_REGION"),
|
||||
}
|
||||
},
|
||||
"lavinmq": func() interface{} {
|
||||
return map[string]string{
|
||||
"amqpUrl": os.Getenv("AMQP_URL"),
|
||||
}
|
||||
},
|
||||
"cloudflare_config": func() interface{} {
|
||||
return map[string]string{
|
||||
"apiToken": os.Getenv("CLOUDFLARE_API_TOKEN"),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key
|
|||
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
|
||||
STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key
|
||||
|
||||
# =============================================================================
|
||||
# Gateway de Pagamento (Fictício)
|
||||
# =============================================================================
|
||||
PAYMENT_GATEWAY_MERCHANT_ID=merchant_demo
|
||||
PAYMENT_GATEWAY_API_KEY=fake_gateway_key
|
||||
PAYMENT_GATEWAY_ENDPOINT=https://payments.example.com/api
|
||||
PAYMENT_GATEWAY_WEBHOOK_SECRET=fake_webhook_secret
|
||||
|
||||
# =============================================================================
|
||||
# Database
|
||||
# =============================================================================
|
||||
|
|
@ -53,3 +61,8 @@ CLOUDFLARE_ZONE_ID=your-zone-id
|
|||
CPANEL_HOST=https://cpanel.yourdomain.com:2083
|
||||
CPANEL_USERNAME=your-cpanel-username
|
||||
CPANEL_API_TOKEN=your-cpanel-api-token
|
||||
|
||||
# =============================================================================
|
||||
# LavinMQ (AMQP)
|
||||
# =============================================================================
|
||||
AMQP_URL=amqps://nwigjply:nwEGZdcfz3--H8xc68IKmjiBCVtI09Cq@horse.lmq.cloudamqp.com/nwigjply
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ExternalServicesCredentials } from './credentials.entity';
|
||||
|
|
@ -35,6 +35,8 @@ export class CredentialsService {
|
|||
let cred = await this.credentialsRepo.findOne({ where: { serviceName } });
|
||||
if (!cred) {
|
||||
cred = this.credentialsRepo.create({ serviceName });
|
||||
} else {
|
||||
throw new BadRequestException('Credentials already configured for this service.');
|
||||
}
|
||||
cred.encryptedPayload = encrypted;
|
||||
cred.updatedBy = updatedBy;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Label } from "@/components/ui/label"
|
|||
import { Separator } from "@/components/ui/separator"
|
||||
import { settingsApi, credentialsApi, ConfiguredService, storageApi } from "@/lib/api"
|
||||
import { toast } from "sonner"
|
||||
import { Loader2, Check, Key, Trash2 } from "lucide-react"
|
||||
import { Loader2, Check, Key } from "lucide-react"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -107,19 +107,6 @@ export default function SettingsPage() {
|
|||
|
||||
|
||||
|
||||
const handleDeleteCredential = async (serviceName: string) => {
|
||||
if (!confirm(`Are you sure you want to remove credentials for ${serviceName}?`)) return
|
||||
|
||||
try {
|
||||
await credentialsApi.delete(serviceName)
|
||||
toast.success(`Credentials for ${serviceName} removed`)
|
||||
fetchCredentials()
|
||||
} catch (error) {
|
||||
console.error("Failed to delete credential", error)
|
||||
toast.error("Failed to delete credential")
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <div className="flex justify-center p-8"><Loader2 className="animate-spin" /></div>
|
||||
}
|
||||
|
|
@ -134,6 +121,15 @@ export default function SettingsPage() {
|
|||
{ key: "publishableKey", label: "Publishable Key (pk_...)", type: "text" },
|
||||
]
|
||||
},
|
||||
payment_gateway: {
|
||||
label: "Gateway de Pagamento (Fictício)",
|
||||
fields: [
|
||||
{ key: "merchantId", label: "Merchant ID", type: "text" },
|
||||
{ key: "apiKey", label: "API Key", type: "password" },
|
||||
{ key: "endpoint", label: "Endpoint URL", type: "text" },
|
||||
{ key: "webhookSecret", label: "Webhook Secret", type: "password" },
|
||||
]
|
||||
},
|
||||
storage: {
|
||||
label: "AWS S3 / Compatible",
|
||||
fields: [
|
||||
|
|
@ -152,6 +148,12 @@ export default function SettingsPage() {
|
|||
{ key: "apiToken", label: "API Token", type: "password" },
|
||||
]
|
||||
},
|
||||
lavinmq: {
|
||||
label: "LavinMQ (AMQP)",
|
||||
fields: [
|
||||
{ key: "amqpUrl", label: "AMQP URL", type: "password" },
|
||||
]
|
||||
},
|
||||
cloudflare_config: {
|
||||
label: "Cloudflare",
|
||||
fields: [
|
||||
|
|
@ -304,7 +306,7 @@ export default function SettingsPage() {
|
|||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>External Services</CardTitle>
|
||||
<CardDescription>Manage credentials for third-party integrations securely. Keys are encrypted.</CardDescription>
|
||||
<CardDescription>Manage credentials for third-party integrations securely. Keys are encrypted and locked after saving.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
|
|
@ -334,15 +336,16 @@ export default function SettingsPage() {
|
|||
</Button>
|
||||
)}
|
||||
<div className="flex w-full gap-2">
|
||||
<Button variant="outline" size="sm" className="w-full" onClick={() => handleOpenCredentialDialog(svc.service_name)}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => handleOpenCredentialDialog(svc.service_name)}
|
||||
disabled={svc.is_configured}
|
||||
>
|
||||
<Key className="w-3 h-3 mr-2" />
|
||||
{svc.is_configured ? "Edit" : "Setup"}
|
||||
{svc.is_configured ? "Configured" : "Setup"}
|
||||
</Button>
|
||||
{svc.is_configured && (
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-destructive hover:bg-destructive/10" onClick={() => handleDeleteCredential(svc.service_name)}>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -358,7 +361,7 @@ export default function SettingsPage() {
|
|||
<DialogHeader>
|
||||
<DialogTitle>Configure {selectedService && (schemas[selectedService]?.label || selectedService)}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter credentials. Keys are encrypted before storage and hidden after saving.
|
||||
Enter credentials. Keys are encrypted before storage, hidden after saving, and cannot be edited later.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
|
|
|
|||
Loading…
Reference in a new issue