This commit is contained in:
gpt-engineer-app[bot]
2025-12-19 06:35:21 +00:00
parent b8a53e40e2
commit 4e188b2bc6
3 changed files with 71 additions and 9 deletions

View File

@@ -15,7 +15,15 @@ import { QRCodeSVG } from "qrcode.react";
// Pakasir configuration
const PAKASIR_PROJECT_SLUG = "dewengoding";
const PAKASIR_API_KEY = "iP13osgh7lAzWWIPsj7TbW5M3iGEAQMo";
const SANDBOX_API_KEY = "iP13osgh7lAzWWIPsj7TbW5M3iGEAQMo";
// Centralized API key retrieval - uses env var with sandbox fallback
const getPakasirApiKey = (): string => {
return import.meta.env.VITE_PAKASIR_API_KEY || SANDBOX_API_KEY;
};
// TODO: Replace with actual Supabase Edge Function URL after creation
const PAKASIR_CALLBACK_URL = "https://lovable.backoffice.biz.id/functions/v1/pakasir-webhook";
type PaymentMethod = "qris" | "paypal";
type CheckoutStep = "cart" | "payment" | "waiting";
@@ -117,6 +125,9 @@ export default function Checkout() {
setOrderId(order.id);
// Build description from product titles
const productTitles = items.map(item => item.title).join(", ");
if (paymentMethod === "qris") {
// Call Pakasir API for QRIS
try {
@@ -127,7 +138,9 @@ export default function Checkout() {
project: PAKASIR_PROJECT_SLUG,
order_id: order.id,
amount: amountInRupiah,
api_key: PAKASIR_API_KEY,
api_key: getPakasirApiKey(),
description: productTitles,
callback_url: PAKASIR_CALLBACK_URL,
}),
});
@@ -222,7 +235,7 @@ export default function Checkout() {
</Button>
</div>
<p className="text-xs text-muted-foreground text-center">
Pembayaran akan otomatis terkonfirmasi setelah Anda scan dan bayar QR code di atas
Pembayaran diproses melalui Pakasir dan akan dikonfirmasi otomatis setelah berhasil.
</p>
</CardContent>
</Card>

View File

@@ -121,13 +121,35 @@ export default function ProductDetail() {
const checkUserAccess = async () => {
if (!product || !user) return;
const { data } = await supabase
// Check user_access table first
const { data: accessData } = await supabase
.from('user_access')
.select('id')
.eq('user_id', user.id)
.eq('product_id', product.id)
.maybeSingle();
setHasAccess(!!data);
if (accessData) {
setHasAccess(true);
setCheckingAccess(false);
return;
}
// Also check for paid orders containing this product
const { data: paidOrders } = await supabase
.from('orders')
.select(`
id,
order_items!inner (product_id)
`)
.eq('user_id', user.id)
.eq('payment_status', 'paid')
.eq('payment_provider', 'pakasir')
.eq('order_items.product_id', product.id)
.limit(1);
setHasAccess(!!(paidOrders && paidOrders.length > 0));
setCheckingAccess(false);
};

View File

@@ -42,11 +42,38 @@ export default function MemberDashboard() {
}, [user, authLoading]);
const fetchData = async () => {
const [accessRes, ordersRes] = await Promise.all([
const [accessRes, ordersRes, paidOrdersRes] = await Promise.all([
supabase.from('user_access').select(`id, product:products (id, title, slug, type, meeting_link, recording_url)`).eq('user_id', user!.id),
supabase.from('orders').select('*').eq('user_id', user!.id).order('created_at', { ascending: false }).limit(3)
supabase.from('orders').select('*').eq('user_id', user!.id).order('created_at', { ascending: false }).limit(3),
// Also get products from paid orders (via order_items)
supabase.from('orders')
.select(`
order_items (
product:products (id, title, slug, type, meeting_link, recording_url)
)
`)
.eq('user_id', user!.id)
.eq('payment_status', 'paid')
.eq('payment_provider', 'pakasir')
]);
if (accessRes.data) setAccess(accessRes.data as unknown as UserAccess[]);
// Combine access from user_access and paid orders
const directAccess = accessRes.data as unknown as UserAccess[] || [];
const paidProductAccess: UserAccess[] = [];
if (paidOrdersRes.data) {
const existingIds = new Set(directAccess.map(a => a.product.id));
paidOrdersRes.data.forEach((order: any) => {
order.order_items?.forEach((item: any) => {
if (item.product && !existingIds.has(item.product.id)) {
existingIds.add(item.product.id);
paidProductAccess.push({ id: `paid-${item.product.id}`, product: item.product });
}
});
});
}
setAccess([...directAccess, ...paidProductAccess]);
if (ordersRes.data) setRecentOrders(ordersRes.data);
setLoading(false);
};