Skip to main content

🎯 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​

  1. Check webhook URL is reachable:

    curl -X POST https://yourdomain.com/v1/webhooks/payments/completed \
    -H "Content-Type: application/json" \
    -d '{"event": "test"}'
  2. Check payment processor logs

  3. Verify orderId matches SalesOrder.id

  4. Check firewall rules

Email not sending​

  1. Check SENDGRID_API_KEY set
  2. Verify email template exists in ContentData
  3. Check email provider logs
  4. 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​

  1. Check storage provider is configured
  2. Verify file size < 2GB
  3. Check file type is supported
  4. Verify disk/S3 space available
  5. Check firewall allows outbound to S3

Virus scan hangs​

  1. Ensure ClamAV is running
  2. Check ClamAV port 3310 is open
  3. Increase timeout in configuration
  4. Consider disabling if testing

πŸ“ˆ Performance Tuning​

Database​

-- Index for fast lookups
CREATE INDEX idx_digital_asset_product_id ON digital_asset(product_id);
CREATE INDEX idx_download_access_asset_id ON download_access(digital_asset_id);
CREATE INDEX idx_order_fulfillment_task_order_id ON order_fulfillment_task(sales_order_id);

-- Connection pool
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5

File Storage (S3)​

# application.yaml
aws:
s3:
transfer-acceleration: true
multipart-upload-threshold: 104857600 # 100MB
multipart-upload-part-size: 52428800 # 50MB

Async Processing​

# application.yaml
spring:
task:
execution:
pool:
core-size: 10
max-size: 20
queue-capacity: 100
scheduling:
pool:
size: 5

πŸŽ“ Best Practices​

  1. Always validate signatures on production webhooks
  2. Implement idempotency for webhook processing
  3. Log all fulfillment attempts for auditing
  4. Monitor webhook success rates continuously
  5. Set up alerts for fulfillment failures
  6. Backup file storage regularly
  7. Test payment flow in staging first
  8. Document your customizations
  9. Use CDN for file delivery
  10. Scale async workers for high volume

πŸŽ‰ Done!​

Your digital product system is ready to deploy. Follow these steps and your customers will get their purchases delivered instantly with beautiful emails.

Questions? Check the comprehensive guide in .valoride/DIGITAL_PRODUCT_SYSTEM_COMPLETE.md

Ready to go live! πŸš€