Maksymilian Jankowski commited on
Commit
5fed607
Β·
1 Parent(s): 3858e2a

update physical orders: colors

Browse files
Files changed (1) hide show
  1. routers/payments.py +57 -46
routers/payments.py CHANGED
@@ -38,15 +38,16 @@ class UpdateSubscriptionRequest(BaseModel):
38
  class PhysicalProductRequest(BaseModel):
39
  generated_model_id: int
40
  size_scale: float = 1.0
41
- material: str = "pla"
42
 
43
  class PriceQuote(BaseModel):
44
  material: str
 
45
  mass_g: float
46
  time_min: int
47
  amount_gbp: float
48
  client_secret: str
49
- print_job_id: int
50
 
51
  # Subscription plan configurations
52
  SUBSCRIPTION_PLANS = {
@@ -199,11 +200,12 @@ async def quote_and_pay_physical_product(
199
  """
200
  Get a price quote for 3D printing a generated model and create payment intent.
201
  Retrieves the model from database and applies size scaling to calculate costs.
 
202
  """
203
  try:
204
- if request.material not in MATERIALS:
205
- raise HTTPException(status_code=400, detail="Unknown material")
206
-
207
  if request.size_scale <= 0:
208
  raise HTTPException(status_code=400, detail="Size scale must be positive")
209
 
@@ -311,11 +313,11 @@ async def quote_and_pay_physical_product(
311
  def estimate_fullness(vol_cm3: float) -> float:
312
  """Return a heuristic fullness (0–1) based on total volume."""
313
  if vol_cm3 <= 500:
314
- return 0.8 # small parts tend to be more solid
315
  elif vol_cm3 <= 1500:
316
- return 0.6
317
  elif vol_cm3 <= 3000:
318
- return 0.5
319
  else:
320
  return 0.3 # large parts are mostly hollow / sparse infill
321
 
@@ -328,7 +330,7 @@ async def quote_and_pay_physical_product(
328
  fullness_factor = estimate_fullness(bbox_volume_cm3)
329
  scaled_volume_cm3 = bbox_volume_cm3 * fullness_factor
330
 
331
- density = MATERIALS[request.material]["density"]
332
  mass_g = scaled_volume_cm3 * density
333
 
334
  # ───────────────────────────────────────────────────────────────
@@ -349,7 +351,7 @@ async def quote_and_pay_physical_product(
349
  # – 0.2 mm layer height
350
  # – Deposition rate = nozzle Γ— layer_height Γ— print_speed (mmΒ³/s)
351
 
352
- print_speed = MATERIALS[request.material]["speed_mm_s"]
353
  nozzle_diameter_mm = 0.4
354
  layer_height_mm = 0.2
355
  deposition_area_mm2 = nozzle_diameter_mm * layer_height_mm # cross-sectional area of extrusion
@@ -371,7 +373,7 @@ async def quote_and_pay_physical_product(
371
  )
372
 
373
  # 4️⃣ Calculate pricing (server-authoritative)
374
- material_price = mass_g * MATERIALS[request.material]["ppg"]
375
 
376
  # Pricing model: material cost + setup fee, with a minimum total price.
377
  MIN_TOTAL_PRICE_GBP = 2.00
@@ -394,7 +396,8 @@ async def quote_and_pay_physical_product(
394
  metadata={
395
  "user_id": current_user.id,
396
  "generated_model_id": request.generated_model_id,
397
- "material": request.material,
 
398
  "size_scale": str(request.size_scale),
399
  "mass_g": f"{mass_g:.2f}",
400
  "time_min": str(time_min),
@@ -404,30 +407,14 @@ async def quote_and_pay_physical_product(
404
  automatic_payment_methods={"enabled": True},
405
  )
406
 
407
- # 6️⃣ Store preliminary print job record
408
- print_job_result = supabase.from_("Print_Jobs").insert({
409
- "user_id": current_user.id,
410
- "generated_model_id": request.generated_model_id,
411
- "stripe_payment_intent_id": intent.id,
412
- "status": "awaiting_payment",
413
- "material": request.material,
414
- "size_scale": request.size_scale,
415
- "mass_g": mass_g,
416
- "time_min": time_min,
417
- "price_gbp": amount_gbp,
418
- "model_name": model_data.get("model_name", ""),
419
- "created_at": datetime.now().isoformat()
420
- }).execute()
421
-
422
- print_job_id = print_job_result.data[0]["id"] if print_job_result.data else None
423
-
424
  return PriceQuote(
425
- material=request.material,
 
426
  mass_g=round(mass_g, 2),
427
  time_min=time_min,
428
  amount_gbp=amount_gbp,
429
  client_secret=intent.client_secret,
430
- print_job_id=print_job_id
431
  )
432
 
433
  except HTTPException:
@@ -463,20 +450,30 @@ async def confirm_physical_payment(
463
 
464
  # Check if this payment has already been processed
465
  existing_job = supabase.from_("Print_Jobs").select("*").eq("stripe_payment_intent_id", request.payment_intent_id).execute()
466
- if existing_job.data and existing_job.data[0]["status"] != "awaiting_payment":
467
  return {"message": "Payment already processed", "print_job_id": existing_job.data[0]["id"]}
468
 
469
- # Update print job status to paid/processing
470
- update_result = supabase.from_("Print_Jobs").update({
 
 
 
471
  "status": "paid",
472
- "payment_confirmed_at": datetime.now().isoformat(),
473
- "updated_at": datetime.now().isoformat()
474
- }).eq("stripe_payment_intent_id", request.payment_intent_id).execute()
 
 
 
 
 
 
 
475
 
476
- if not update_result.data:
477
- raise HTTPException(status_code=404, detail="Print job not found")
478
 
479
- print_job = update_result.data[0]
480
 
481
  # Record in Model_Order_History for tracking
482
  supabase.from_("Model_Order_History").insert({
@@ -844,12 +841,26 @@ async def handle_physical_payment_succeeded(payment_intent):
844
  return
845
 
846
  try:
847
- # Update print job status to paid
848
- supabase.from_("Print_Jobs").update({
849
- "status": "paid",
850
- "payment_confirmed_at": datetime.now().isoformat(),
851
- "updated_at": datetime.now().isoformat()
852
- }).eq("stripe_payment_intent_id", payment_intent["id"]).execute()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
853
 
854
  # Update Model_Order_History if exists
855
  supabase.from_("Model_Order_History").update({
 
38
  class PhysicalProductRequest(BaseModel):
39
  generated_model_id: int
40
  size_scale: float = 1.0
41
+ color: str = "gray"
42
 
43
  class PriceQuote(BaseModel):
44
  material: str
45
+ color: str
46
  mass_g: float
47
  time_min: int
48
  amount_gbp: float
49
  client_secret: str
50
+ print_job_id: Optional[int] = None
51
 
52
  # Subscription plan configurations
53
  SUBSCRIPTION_PLANS = {
 
200
  """
201
  Get a price quote for 3D printing a generated model and create payment intent.
202
  Retrieves the model from database and applies size scaling to calculate costs.
203
+ Material is always PLA, but user can select color.
204
  """
205
  try:
206
+ # Material is always PLA
207
+ material = "pla"
208
+
209
  if request.size_scale <= 0:
210
  raise HTTPException(status_code=400, detail="Size scale must be positive")
211
 
 
313
  def estimate_fullness(vol_cm3: float) -> float:
314
  """Return a heuristic fullness (0–1) based on total volume."""
315
  if vol_cm3 <= 500:
316
+ return 1.5 # small parts tend to be more solid
317
  elif vol_cm3 <= 1500:
318
+ return 1.2
319
  elif vol_cm3 <= 3000:
320
+ return 0.6
321
  else:
322
  return 0.3 # large parts are mostly hollow / sparse infill
323
 
 
330
  fullness_factor = estimate_fullness(bbox_volume_cm3)
331
  scaled_volume_cm3 = bbox_volume_cm3 * fullness_factor
332
 
333
+ density = MATERIALS[material]["density"]
334
  mass_g = scaled_volume_cm3 * density
335
 
336
  # ───────────────────────────────────────────────────────────────
 
351
  # – 0.2 mm layer height
352
  # – Deposition rate = nozzle Γ— layer_height Γ— print_speed (mmΒ³/s)
353
 
354
+ print_speed = MATERIALS[material]["speed_mm_s"]
355
  nozzle_diameter_mm = 0.4
356
  layer_height_mm = 0.2
357
  deposition_area_mm2 = nozzle_diameter_mm * layer_height_mm # cross-sectional area of extrusion
 
373
  )
374
 
375
  # 4️⃣ Calculate pricing (server-authoritative)
376
+ material_price = mass_g * MATERIALS[material]["ppg"]
377
 
378
  # Pricing model: material cost + setup fee, with a minimum total price.
379
  MIN_TOTAL_PRICE_GBP = 2.00
 
396
  metadata={
397
  "user_id": current_user.id,
398
  "generated_model_id": request.generated_model_id,
399
+ "material": material,
400
+ "color": request.color,
401
  "size_scale": str(request.size_scale),
402
  "mass_g": f"{mass_g:.2f}",
403
  "time_min": str(time_min),
 
407
  automatic_payment_methods={"enabled": True},
408
  )
409
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  return PriceQuote(
411
+ material=material,
412
+ color=request.color,
413
  mass_g=round(mass_g, 2),
414
  time_min=time_min,
415
  amount_gbp=amount_gbp,
416
  client_secret=intent.client_secret,
417
+ print_job_id=None # Will be created only when payment is confirmed
418
  )
419
 
420
  except HTTPException:
 
450
 
451
  # Check if this payment has already been processed
452
  existing_job = supabase.from_("Print_Jobs").select("*").eq("stripe_payment_intent_id", request.payment_intent_id).execute()
453
+ if existing_job.data:
454
  return {"message": "Payment already processed", "print_job_id": existing_job.data[0]["id"]}
455
 
456
+ # Create print job record now that payment is confirmed
457
+ print_job_result = supabase.from_("Print_Jobs").insert({
458
+ "user_id": current_user.id,
459
+ "generated_model_id": intent.metadata.get("generated_model_id"),
460
+ "stripe_payment_intent_id": request.payment_intent_id,
461
  "status": "paid",
462
+ "material": intent.metadata.get("material"),
463
+ "color": intent.metadata.get("color"),
464
+ "size_scale": float(intent.metadata.get("size_scale")),
465
+ "mass_g": float(intent.metadata.get("mass_g")),
466
+ "time_min": int(intent.metadata.get("time_min")),
467
+ "price_gbp": float(intent.amount) / 100, # Convert from pence to pounds
468
+ "model_name": intent.metadata.get("model_name", ""),
469
+ "created_at": datetime.now().isoformat(),
470
+ "payment_confirmed_at": datetime.now().isoformat()
471
+ }).execute()
472
 
473
+ if not print_job_result.data:
474
+ raise HTTPException(status_code=500, detail="Failed to create print job record")
475
 
476
+ print_job = print_job_result.data[0]
477
 
478
  # Record in Model_Order_History for tracking
479
  supabase.from_("Model_Order_History").insert({
 
841
  return
842
 
843
  try:
844
+ # Check if print job already exists (from confirm_physical_payment endpoint)
845
+ existing_job = supabase.from_("Print_Jobs").select("*").eq("stripe_payment_intent_id", payment_intent["id"]).execute()
846
+
847
+ if not existing_job.data:
848
+ # Create print job record if it doesn't exist (webhook processed before API call)
849
+ supabase.from_("Print_Jobs").insert({
850
+ "user_id": user_id,
851
+ "generated_model_id": generated_model_id,
852
+ "stripe_payment_intent_id": payment_intent["id"],
853
+ "status": "paid",
854
+ "material": payment_intent.metadata.get("material"),
855
+ "color": payment_intent.metadata.get("color"),
856
+ "size_scale": float(payment_intent.metadata.get("size_scale")),
857
+ "mass_g": float(payment_intent.metadata.get("mass_g")),
858
+ "time_min": int(payment_intent.metadata.get("time_min")),
859
+ "price_gbp": float(payment_intent["amount"]) / 100,
860
+ "model_name": payment_intent.metadata.get("model_name", ""),
861
+ "created_at": datetime.now().isoformat(),
862
+ "payment_confirmed_at": datetime.now().isoformat()
863
+ }).execute()
864
 
865
  # Update Model_Order_History if exists
866
  supabase.from_("Model_Order_History").update({