π― Digital Product System: Integration & Deployment Guide
Quick Navigationβ
- For Users: Just upload a file β Done! (See
.valoride/DIGITAL_PRODUCT_SYSTEM_COMPLETE.md) - For Frontend Devs: Integrate DigitalProductUploader component
- For Backend Devs: Configure webhooks for your payment provider
- For DevOps: Deploy using provided configurations
π¨ Frontend Integration (5 Minutes)β
Installationβ
The component is ready to use. Just import and render:
// pages/Products/Upload.tsx
import { DigitalProductUploader } from "@components/DigitalProducts/DigitalProductUploader";
export function ProductUploadPage() {
return (
<div>
<h1>Upload Digital Products</h1>
<DigitalProductUploader />
</div>
);
}
Customizationβ
Change pricing defaults:
const [config, setConfig] = useState({
price: 49.99, // Change from 9.99
salePrice: 39.99,
// ... other fields
});
Add custom file types:
// In validateAndSelectFile()
const validTypes = [
// ... existing types
"application/x-custom-format", // Add your format
];
Brand colors:
// Update gradient header
sx={{
background: 'linear-gradient(135deg, #yourColor1 0%, #yourColor2 100%)',
}}
π§ Backend Integrationβ
Configuration Fileβ
Create application-digital-products.yaml:
# application-digital-products.yaml
digital-products:
# Storage configuration
storage:
provider: s3 # or 'local'
s3:
bucket: my-digital-products
region: us-east-1
endpoint: https://s3.amazonaws.com
local:
path: /data/digital-products
# Virus scanning
virus-scan:
enabled: true
provider: clamav # or 'virustotal'
clamav:
host: localhost
port: 3310
# Email notifications
email:
enabled: true
from: noreply@yourdomain.com
provider: sendgrid # or 'ses', 'smtp'
sendgrid:
api-key: ${SENDGRID_API_KEY}
# Download access
download-access:
token-expiry-minutes: 60
max-downloads: unlimited
enable-tracking: true
# Fulfillment
fulfillment:
async-enabled: true
retry-policy:
max-attempts: 3
backoff-multiplier: 2
initial-delay-seconds: 5
parallel-fulfillment-max: 10
Environment Variablesβ
# .env or deployment environment
DIGITAL_PRODUCTS_STORAGE_PROVIDER=s3
DIGITAL_PRODUCTS_S3_BUCKET=my-products
DIGITAL_PRODUCTS_S3_REGION=us-east-1
DIGITAL_PRODUCTS_S3_ACCESS_KEY=${AWS_ACCESS_KEY_ID}
DIGITAL_PRODUCTS_S3_SECRET_KEY=${AWS_SECRET_ACCESS_KEY}
DIGITAL_PRODUCTS_EMAIL_FROM=noreply@yourdomain.com
SENDGRID_API_KEY=${YOUR_SENDGRID_KEY}
DIGITAL_PRODUCTS_VIRUS_SCAN_ENABLED=true
CLAMAV_HOST=localhost
CLAMAV_PORT=3310
π³ Payment Webhook Configurationβ
Stripe Integrationβ
Step 1: Create webhook endpoint
# Already at: POST /v1/webhooks/stripe/charge-succeeded
Step 2: Configure in Stripe Dashboard
Developers β Webhooks β Add endpoint
ββ Endpoint URL: https://yourdomain.com/v1/webhooks/stripe/charge-succeeded
ββ Events: charge.succeeded
ββ API version: Latest
ββ Click "Add endpoint"
Step 3: Add Stripe event metadata
Ensure your Stripe payment intent includes order ID in metadata:
// Client side (JavaScript)
stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: { name: "Jenny Rosen" },
},
metadata: {
order_id: orderId, // This must match SalesOrder.id
},
});
Step 4: Test
# Use Stripe CLI to test locally
stripe listen --forward-to localhost:8080/v1/webhooks/stripe/charge-succeeded
stripe trigger charge.succeeded --add metadata='order_id=550e8400-e29b-41d4-a716-446655440000'
PayPal Integrationβ
Step 1: Webhook already configured
# Already at: POST /v1/webhooks/paypal/payment-completed
Step 2: Register in PayPal Dashboard
Dashboard β Accounts β Webhooks
ββ Webhook URL: https://yourdomain.com/v1/webhooks/paypal/payment-completed
ββ Event types: PAYMENT.CAPTURE.COMPLETED
ββ Click "Create webhook"
Step 3: Ensure order ID in supplementary data
Configure PayPal to send order ID in response:
// Server side: When creating PayPal payment
{
intent: 'CAPTURE',
purchase_units: [
{
reference_id: orderId, // Will be in supplementary_data.related_ids.order_id
amount: {
currency_code: 'USD',
value: totalAmount,
},
},
],
}
Step 4: Test
# Use PayPal Sandbox
# Create payment β Capture β Webhook fires
Generic Webhook Providerβ
For any other payment provider, use the generic endpoint:
POST /v1/webhooks/payments/completed
Content-Type: application/json
{
"event": "payment.completed",
"orderId": "550e8400-e29b-41d4-a716-446655440000",
"paymentId": "pay_abc123",
"amount": 99.99,
"currency": "USD",
"paymentMethod": "custom_provider",
"timestamp": 1634829600
}
π Deploymentβ
Dockerβ
Dockerfile.digital-products:
FROM openjdk:17-slim
WORKDIR /app
# Copy backend
COPY valkyrai/target/valkyrai-*-exec.jar app.jar
# Copy frontend build
COPY web/dist /app/static
# Environment
ENV JAVA_OPTS="-Dspring.profiles.active=production,digital-products"
EXPOSE 8080
ENTRYPOINT ["java", "${JAVA_OPTS}", "-jar", "app.jar"]
Build and run:
# Build
docker build -f Dockerfile.digital-products -t valkyrai-digital:latest .
# Run
docker run -p 8080:8080 \
-e DATABASE_URL=jdbc:postgresql://db:5432/valkyr \
-e SENDGRID_API_KEY=your-key \
-e AWS_S3_BUCKET=your-bucket \
valkyrai-digital:latest
Kubernetesβ
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: valkyrai-digital
spec:
replicas: 3
selector:
matchLabels:
app: valkyrai-digital
template:
metadata:
labels:
app: valkyrai-digital
spec:
containers:
- name: valkyrai
image: valkyrai-digital:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "production,digital-products"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secrets
key: url
- name: SENDGRID_API_KEY
valueFrom:
secretKeyRef:
name: email-secrets
key: sendgrid-key
- name: AWS_S3_BUCKET
valueFrom:
configMapKeyRef:
name: storage-config
key: bucket
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: valkyrai-digital
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: valkyrai-digital
Deploy:
kubectl apply -f deployment.yaml
π§ͺ Testingβ
Unit Test Exampleβ
@SpringBootTest
@AutoConfigureMockMvc
class DigitalProductUploadTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private FileUploadService fileUploadService;
@Test
void testUploadAndCreateProduct() throws Exception {
// Arrange
MockMultipartFile file = new MockMultipartFile(
"file", "course.pdf", "application/pdf", "PDF content".getBytes());
// Act
mockMvc.perform(multipart("/v1/files/upload")
.file(file)
.param("name", "React Course")
.param("price", "29.99"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.product.name").value("React Course"))
.andExpect(jsonPath("$.product.price").value(29.99));
// Assert
verify(fileUploadService).scanForViruses(any());
}
@Test
void testWebhookFulfillment() throws Exception {
// Arrange
PaymentCompletedEvent event = new PaymentCompletedEvent(
UUID.randomUUID(), "pay_123", 99.99, "USD", "stripe", System.currentTimeMillis() / 1000);
// Act
mockMvc.perform(post("/v1/webhooks/payments/completed")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(event)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("success"))
.andExpect(jsonPath("$.fulfillmentTasks").value(greaterThan(0)));
}
}
π Monitoringβ
Health Checksβ
# Service health
curl http://localhost:8080/actuator/health
# Response
{
"status": "UP",
"components": {
"db": { "status": "UP" },
"diskSpace": { "status": "UP" },
"fileStorage": { "status": "UP" }
}
}
Metricsβ
# JVM metrics
curl http://localhost:8080/actuator/metrics/jvm.memory.used
# Request metrics
curl http://localhost:8080/actuator/metrics/http.server.requests
Logsβ
# Stream logs
docker logs -f valkyrai-digital
# Or from K8s
kubectl logs -f deployment/valkyrai-digital
π Debuggingβ
Enable Debug Loggingβ
# application.yaml
logging:
level:
com.valkyrlabs.valkyrai: DEBUG
com.valkyrlabs.workflow: DEBUG
org.springframework.security.acl: DEBUG
Webhook Debugβ
# Use ngrok to tunnel webhooks locally
ngrok http 8080
# Update webhook URL to ngrok URL
# Now webhooks are logged to console
# Or use webhook.cool to inspect payloads
# Update webhook URL to https://webhook.cool/unique-id
β Pre-Launch Checklistβ
- Database configured (PostgreSQL)
- File storage configured (S3 or local)
- Virus scanning configured (ClamAV or VirusTotal)
- Email provider configured (SendGrid, SES, etc.)
- Payment webhook URLs configured (Stripe/PayPal/other)
- Frontend built and deployed
- Backend tests passing
- Staging environment verified
- SSL/HTTPS configured
- Monitoring/alerting setup
- Backup strategy in place
- Security audit completed
- Load testing done
- Documentation reviewed
- Team trained
π¨ Troubleshootingβ
Webhook not triggeringβ
-
Check webhook URL is reachable:
curl -X POST https://yourdomain.com/v1/webhooks/payments/completed \
-H "Content-Type: application/json" \
-d '{"event": "test"}' -
Check payment processor logs
-
Verify orderId matches SalesOrder.id
-
Check firewall rules
Email not sendingβ
- Check SENDGRID_API_KEY set
- Verify email template exists in ContentData
- Check email provider logs
- Test with direct API call:
curl -X POST https://api.sendgrid.com/v3/mail/send \
-H "Authorization: Bearer ${SENDGRID_API_KEY}" \
-d '...'
File upload failsβ
- Check storage provider is configured
- Verify file size < 2GB
- Check file type is supported
- Verify disk/S3 space available
- Check firewall allows outbound to S3
Virus scan hangsβ
- Ensure ClamAV is running
- Check ClamAV port 3310 is open
- Increase timeout in configuration
- Consider disabling if testing