Create Pakasir payment edge function to fix CORS issue
- Create create-pakasir-payment edge function to handle payment creation server-side - Update ConsultingBooking.tsx to use edge function instead of direct API call - Update Checkout.tsx to use edge function instead of direct API call - Add config.toml entry for create-pakasir-payment function - Removes CORS errors when calling Pakasir API from frontend 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -129,46 +129,37 @@ export default function Checkout() {
|
|||||||
|
|
||||||
setOrderId(order.id);
|
setOrderId(order.id);
|
||||||
|
|
||||||
// Build description from product titles
|
// Build description from product titles
|
||||||
const productTitles = items.map(item => item.title).join(", ");
|
const productTitles = items.map(item => item.title).join(", ");
|
||||||
|
|
||||||
if (paymentMethod === "qris") {
|
if (paymentMethod === "qris") {
|
||||||
// Call Pakasir API for QRIS
|
// Call edge function to create Pakasir QRIS payment (avoids CORS)
|
||||||
try {
|
const { data: paymentData, error: paymentError } = await supabase.functions.invoke('create-pakasir-payment', {
|
||||||
const response = await fetch(`https://app.pakasir.com/api/transactioncreate/qris`, {
|
body: {
|
||||||
method: "POST",
|
order_id: order.id,
|
||||||
headers: { "Content-Type": "application/json" },
|
amount: amountInRupiah,
|
||||||
body: JSON.stringify({
|
description: productTitles,
|
||||||
project: PAKASIR_PROJECT_SLUG,
|
},
|
||||||
order_id: order.id,
|
});
|
||||||
amount: amountInRupiah,
|
|
||||||
api_key: getPakasirApiKey(),
|
|
||||||
description: productTitles,
|
|
||||||
callback_url: PAKASIR_CALLBACK_URL,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
if (paymentError) {
|
||||||
|
console.error('Payment creation error:', paymentError);
|
||||||
if (result.qr_string || result.qr) {
|
|
||||||
setPaymentData({
|
|
||||||
qr_string: result.qr_string || result.qr,
|
|
||||||
expired_at: result.expired_at,
|
|
||||||
order_id: order.id,
|
|
||||||
});
|
|
||||||
setStep("waiting");
|
|
||||||
clearCart();
|
|
||||||
} else {
|
|
||||||
// Fallback to redirect if API doesn't return QR
|
|
||||||
const pakasirUrl = `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${amountInRupiah}?order_id=${order.id}`;
|
|
||||||
clearCart();
|
|
||||||
window.location.href = pakasirUrl;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Fallback to redirect
|
// Fallback to redirect
|
||||||
const pakasirUrl = `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${amountInRupiah}?order_id=${order.id}`;
|
const pakasirUrl = `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${amountInRupiah}?order_id=${order.id}`;
|
||||||
clearCart();
|
clearCart();
|
||||||
window.location.href = pakasirUrl;
|
window.location.href = pakasirUrl;
|
||||||
|
} else if (paymentData?.data?.qr_string) {
|
||||||
|
// QRIS available - show QR code
|
||||||
|
setPaymentData({
|
||||||
|
qr_string: paymentData.data.qr_string,
|
||||||
|
expired_at: paymentData.data.expired_at,
|
||||||
|
order_id: order.id,
|
||||||
|
});
|
||||||
|
setStep("waiting");
|
||||||
|
clearCart();
|
||||||
|
} else {
|
||||||
|
// No QR code - redirect to payment page
|
||||||
|
window.location.href = paymentData.data.payment_url;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// PayPal - redirect to Pakasir PayPal URL
|
// PayPal - redirect to Pakasir PayPal URL
|
||||||
|
|||||||
@@ -226,38 +226,25 @@ export default function ConsultingBooking() {
|
|||||||
const { error: slotsError } = await supabase.from('consulting_slots').insert(slotsToInsert);
|
const { error: slotsError } = await supabase.from('consulting_slots').insert(slotsToInsert);
|
||||||
if (slotsError) throw slotsError;
|
if (slotsError) throw slotsError;
|
||||||
|
|
||||||
// Call Pakasir API directly to create QRIS payment
|
// Call edge function to create Pakasir payment (avoids CORS)
|
||||||
const PAKASIR_PROJECT_SLUG = import.meta.env.VITE_PAKASIR_PROJECT_SLUG || '';
|
const { data: paymentData, error: paymentError } = await supabase.functions.invoke('create-pakasir-payment', {
|
||||||
const PAKASIR_API_KEY = import.meta.env.VITE_PAKASIR_API_KEY || '';
|
body: {
|
||||||
const PAKASIR_CALLBACK_URL = `${import.meta.env.VITE_SUPABASE_URL}/functions/v1/pakasir-webhook`;
|
order_id: order.id,
|
||||||
|
amount: totalPrice,
|
||||||
|
description: `Konsultasi 1-on-1 (${totalBlocks} blok)`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
if (paymentError) {
|
||||||
const response = await fetch(`https://app.pakasir.com/api/transactioncreate/qris`, {
|
console.error('Payment creation error:', paymentError);
|
||||||
method: 'POST',
|
throw new Error(paymentError.message || 'Gagal membuat pembayaran');
|
||||||
headers: { 'Content-Type': 'application/json' },
|
}
|
||||||
body: JSON.stringify({
|
|
||||||
project: PAKASIR_PROJECT_SLUG,
|
|
||||||
order_id: order.id,
|
|
||||||
amount: totalPrice,
|
|
||||||
api_key: PAKASIR_API_KEY,
|
|
||||||
description: `Konsultasi 1-on-1 (${totalBlocks} blok)`,
|
|
||||||
callback_url: PAKASIR_CALLBACK_URL,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
if (paymentData?.success && paymentData?.data?.payment_url) {
|
||||||
|
// Redirect to Pakasir payment page
|
||||||
if (result.qr_string || result.qr) {
|
window.location.href = paymentData.data.payment_url;
|
||||||
// QRIS available - redirect to Pakasir payment page
|
} else {
|
||||||
const pakasirPayUrl = `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${totalPrice}?order_id=${order.id}`;
|
throw new Error('Gagal membuat URL pembayaran');
|
||||||
window.location.href = pakasirPayUrl;
|
|
||||||
} else {
|
|
||||||
throw new Error('Failed to create payment');
|
|
||||||
}
|
|
||||||
} catch (pakasirError) {
|
|
||||||
// Fallback: redirect directly to Pakasir
|
|
||||||
const pakasirPayUrl = `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${totalPrice}?order_id=${order.id}`;
|
|
||||||
window.location.href = pakasirPayUrl;
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
||||||
|
|||||||
@@ -44,3 +44,6 @@ verify_jwt = false
|
|||||||
|
|
||||||
[functions.delete-order]
|
[functions.delete-order]
|
||||||
verify_jwt = false
|
verify_jwt = false
|
||||||
|
|
||||||
|
[functions.create-pakasir-payment]
|
||||||
|
verify_jwt = false
|
||||||
|
|||||||
102
supabase/functions/create-pakasir-payment/index.ts
Normal file
102
supabase/functions/create-pakasir-payment/index.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||||
|
|
||||||
|
const corsHeaders = {
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PakasirPaymentRequest {
|
||||||
|
order_id: string;
|
||||||
|
amount: number;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
serve(async (req: Request) => {
|
||||||
|
// Handle CORS preflight
|
||||||
|
if (req.method === "OPTIONS") {
|
||||||
|
return new Response(null, { headers: corsHeaders });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
return new Response("Method not allowed", { status: 405, headers: corsHeaders });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body: PakasirPaymentRequest = await req.json();
|
||||||
|
const { order_id, amount, description } = body;
|
||||||
|
|
||||||
|
if (!order_id || !amount) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ success: false, error: "order_id and amount are required" }),
|
||||||
|
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PAKASIR_PROJECT_SLUG = Deno.env.get("PAKASIR_PROJECT_SLUG") || "";
|
||||||
|
const PAKASIR_API_KEY = Deno.env.get("PAKASIR_API_KEY") || "";
|
||||||
|
const PAKASIR_CALLBACK_URL = `${Deno.env.get("SUPABASE_URL")}/functions/v1/pakasir-webhook`;
|
||||||
|
|
||||||
|
if (!PAKASIR_PROJECT_SLUG || !PAKASIR_API_KEY) {
|
||||||
|
console.error("[PAKASIR] Missing credentials");
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ success: false, error: "Pakasir credentials not configured" }),
|
||||||
|
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[PAKASIR] Creating payment transaction:", { order_id, amount });
|
||||||
|
|
||||||
|
// Call Pakasir API to create QRIS transaction
|
||||||
|
const pakasirResponse = await fetch(`https://app.pakasir.com/api/transactioncreate/qris`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
project: PAKASIR_PROJECT_SLUG,
|
||||||
|
order_id: order_id,
|
||||||
|
amount: amount,
|
||||||
|
api_key: PAKASIR_API_KEY,
|
||||||
|
description: description || `Order ${order_id}`,
|
||||||
|
callback_url: PAKASIR_CALLBACK_URL,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!pakasirResponse.ok) {
|
||||||
|
const errorText = await pakasirResponse.text();
|
||||||
|
console.error("[PAKASIR] API error:", pakasirResponse.status, errorText);
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: "Pakasir API error",
|
||||||
|
details: errorText
|
||||||
|
}),
|
||||||
|
{ status: pakasirResponse.status, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await pakasirResponse.json();
|
||||||
|
console.log("[PAKASIR] Payment created:", result);
|
||||||
|
|
||||||
|
// Return payment URL and QR data
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
payment_url: `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${amount}?order_id=${order_id}`,
|
||||||
|
qr_string: result.qr_string || result.qr || null,
|
||||||
|
order_id: order_id,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("[PAKASIR] Unexpected error:", error);
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: error.message || "Internal server error"
|
||||||
|
}),
|
||||||
|
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user