tomoya 1 anno fa
parent
commit
6657e6e662

+ 2 - 2
backend/app/app/aianchor/utils2.py

@@ -5,9 +5,9 @@ import zipfile
 from io import BytesIO
 from io import BytesIO
 from chardet.universaldetector import UniversalDetector
 from chardet.universaldetector import UniversalDetector
 try:
 try:
-    from app.aianchor.config import *
+  from app.aianchor.config import *
 except ImportError:
 except ImportError:
-    from config import *
+  from config import *
 
 
 DEFAULT_ENCODING = "utf-8"
 DEFAULT_ENCODING = "utf-8"
 
 

+ 4 - 2
backend/app/app/api/api_v1/api.py

@@ -1,7 +1,8 @@
 from fastapi import APIRouter
 from fastapi import APIRouter
 
 
 from app.api.api_v1.endpoints import  login, users, utils, videos, images, reputations, ser_no
 from app.api.api_v1.endpoints import  login, users, utils, videos, images, reputations, ser_no
-from app.api.api_v1.endpoints import payment, ytviewspayment
+from app.api.api_v1.endpoints import ytviewspayment, payment
+from app.api.api_v1.endpoints import ecpay
 
 
 
 
 api_router = APIRouter()
 api_router = APIRouter()
@@ -13,4 +14,5 @@ api_router.include_router(images.router, prefix="/images", tags=["iamges"])
 api_router.include_router(reputations.router, prefix="/reputations", tags=["reputations"])
 api_router.include_router(reputations.router, prefix="/reputations", tags=["reputations"])
 api_router.include_router(ser_no.router, prefix="/ser_nos", tags=["serial numbers"])
 api_router.include_router(ser_no.router, prefix="/ser_nos", tags=["serial numbers"])
 api_router.include_router(ytviewspayment.router, prefix="/payment", tags=["yt views payment"])
 api_router.include_router(ytviewspayment.router, prefix="/payment", tags=["yt views payment"])
-api_router.include_router(payment.router, prefix="/payment", tags=["payment"])
+api_router.include_router(payment.router, prefix="/payment", tags=["payment"])
+api_router.include_router(ecpay.router, prefix="/ecpay", tags=["ecpay"])

+ 112 - 0
backend/app/app/api/api_v1/endpoints/ecpay.py

@@ -0,0 +1,112 @@
+from typing import Any, List, Optional
+from datetime import datetime
+
+from fastapi import APIRouter, Body, Depends, HTTPException, Form, status, Response
+from fastapi.encoders import jsonable_encoder
+from pydantic.networks import EmailStr
+from sqlalchemy.orm import Session
+
+from app import crud, models, schemas
+from app.api import deps
+from app.core.config import settings
+from app.core.ecpay_payment_sdk import ECPayPaymentSdk
+from app.utils import send_new_account_email
+
+import requests
+
+router = APIRouter()
+
+@router.post("/ecpay-result-return")
+def ecpay_return(
+    *,
+    MerchantID: Optional[str]=Form(None),
+    MerchantTradeNo: Optional[str]=Form(None),
+    StoreID: Optional[str]=Form(None),
+    RtnCode: Optional[int]=Form(None),
+    RtnMsg: Optional[str]=Form(None),
+    TradeNo: Optional[str]=Form(None),
+    TradeAmt: Optional[int]=Form(None),
+    PaymentDate: Optional[str]=Form(None),
+    PaymentType: Optional[str]=Form(None),
+    PaymentTypeChargeFee: Optional[int]=Form(None),
+    TradeDate: Optional[str]=Form(None),
+    SimulatePaid: Optional[int]=Form(None),
+    CustomField1: Optional[str]=Form(None),
+    CustomField2: Optional[str]=Form(None),
+    CustomField3: Optional[str]=Form(None),
+    CustomField4: Optional[str]=Form(None),
+    CheckMacValue: Optional[str]=Form(None),
+) -> Any:
+  #送email
+  print(f"\
+MerchantID: {MerchantID} \n\
+MerchantTradeNo: {MerchantTradeNo}\n\
+StoreID: {StoreID}\n\
+RtnCode: {RtnCode}\n\
+RtnMsg: {RtnMsg}\n\
+TradeNo: {TradeNo}\n\
+TradeAmt: {TradeAmt}\n\
+PaymentDate: {PaymentDate}\n\
+PaymentType: {PaymentType}\n\
+PaymentTypeChargeFee: {PaymentTypeChargeFee}\n\
+TradeDate: {TradeDate}\n\
+SimulatePaid: {SimulatePaid}\n\
+CustomField1: {CustomField1}\n\
+CustomField2: {CustomField2}\n\
+CustomField3: {CustomField3}\n\
+CustomField4: {CustomField4}\n\
+CheckMacValue: {CheckMacValue}\
+  ")
+  return Response(content='1', status_code=status.HTTP_200_OK)
+
+@router.post("/ecpay-test-result-return")
+def ecpay_return(
+    *,
+    MerchantID: Optional[str]=Form(None),
+    MerchantTradeNo: Optional[str]=Form(None),
+    StoreID: Optional[str]=Form(None),
+    RtnCode: Optional[int]=Form(None),
+    RtnMsg: Optional[str]=Form(None),
+    TradeNo: Optional[str]=Form(None),
+    TradeAmt: Optional[int]=Form(None),
+    PaymentDate: Optional[str]=Form(None),
+    PaymentType: Optional[str]=Form(None),
+    PaymentTypeChargeFee: Optional[int]=Form(None),
+    TradeDate: Optional[str]=Form(None),
+    SimulatePaid: Optional[int]=Form(None),
+    CustomField1: Optional[str]=Form(None),
+    CustomField2: Optional[str]=Form(None),
+    CustomField3: Optional[str]=Form(None),
+    CustomField4: Optional[str]=Form(None),
+    CheckMacValue: Optional[str]=Form(None),
+) -> Any:
+  #送email
+  print(f"\
+MerchantID: {MerchantID} \n\
+MerchantTradeNo: {MerchantTradeNo}\n\
+StoreID: {StoreID}\n\
+RtnCode: {RtnCode}\n\
+RtnMsg: {RtnMsg}\n\
+TradeNo: {TradeNo}\n\
+TradeAmt: {TradeAmt}\n\
+PaymentDate: {PaymentDate}\n\
+PaymentType: {PaymentType}\n\
+PaymentTypeChargeFee: {PaymentTypeChargeFee}\n\
+TradeDate: {TradeDate}\n\
+SimulatePaid: {SimulatePaid}\n\
+CustomField1: {CustomField1}\n\
+CustomField2: {CustomField2}\n\
+CustomField3: {CustomField3}\n\
+CustomField4: {CustomField4}\n\
+CheckMacValue: {CheckMacValue}\
+  ")
+  return Response(content='1', status_code=status.HTTP_200_OK)
+
+
+@router.get('/check-payment-list')
+def get_payment_list(
+  *,
+  db: Session = Depends(deps.get_db),
+  current_user: models.User = Depends(deps.get_current_active_user)
+):
+  pass

+ 276 - 56
backend/app/app/api/api_v1/endpoints/payment.py

@@ -3,65 +3,287 @@ from datetime import datetime
 
 
 from fastapi import APIRouter, Body, Depends, HTTPException, Form, status, Response
 from fastapi import APIRouter, Body, Depends, HTTPException, Form, status, Response
 from fastapi.encoders import jsonable_encoder
 from fastapi.encoders import jsonable_encoder
-from pydantic.networks import EmailStr
+from pydantic.networks import EmailStr, HttpUrl
 from sqlalchemy.orm import Session
 from sqlalchemy.orm import Session
 
 
-from app import crud, models, schemas
+import app.crud as crud
+import app.models as models
+import app.schemas as schemas 
 from app.api import deps
 from app.api import deps
 from app.core.config import settings
 from app.core.config import settings
 from app.core.ecpay_payment_sdk import ECPayPaymentSdk
 from app.core.ecpay_payment_sdk import ECPayPaymentSdk
 from app.utils import send_new_account_email
 from app.utils import send_new_account_email
-
+from pydantic import BaseModel
 import requests
 import requests
+from random import choice
+import string
+import json
 
 
 router = APIRouter()
 router = APIRouter()
 
 
-@router.post("/ecpay-result-return")
-def ecpay_return(
-    *,
-    MerchantID: Optional[str]=Form(None),
-    MerchantTradeNo: Optional[str]=Form(None),
-    StoreID: Optional[str]=Form(None),
-    RtnCode: Optional[int]=Form(None),
-    RtnMsg: Optional[str]=Form(None),
-    TradeNo: Optional[str]=Form(None),
-    TradeAmt: Optional[int]=Form(None),
-    PaymentDate: Optional[str]=Form(None),
-    PaymentType: Optional[str]=Form(None),
-    PaymentTypeChargeFee: Optional[int]=Form(None),
-    TradeDate: Optional[str]=Form(None),
-    SimulatePaid: Optional[int]=Form(None),
-    CustomField1: Optional[str]=Form(None),
-    CustomField2: Optional[str]=Form(None),
-    CustomField3: Optional[str]=Form(None),
-    CustomField4: Optional[str]=Form(None),
-    CheckMacValue: Optional[str]=Form(None),
-) -> Any:
-  #送email
-  print(f"\
-MerchantID: {MerchantID} \n\
-MerchantTradeNo: {MerchantTradeNo}\n\
-StoreID: {StoreID}\n\
-RtnCode: {RtnCode}\n\
-RtnMsg: {RtnMsg}\n\
-TradeNo: {TradeNo}\n\
-TradeAmt: {TradeAmt}\n\
-PaymentDate: {PaymentDate}\n\
-PaymentType: {PaymentType}\n\
-PaymentTypeChargeFee: {PaymentTypeChargeFee}\n\
-TradeDate: {TradeDate}\n\
-SimulatePaid: {SimulatePaid}\n\
-CustomField1: {CustomField1}\n\
-CustomField2: {CustomField2}\n\
-CustomField3: {CustomField3}\n\
-CustomField4: {CustomField4}\n\
-CheckMacValue: {CheckMacValue}\
-  ")
-  return Response(content='1', status_code=status.HTTP_200_OK)
+@router.post('/ecpay-test-payment')
+def ecpay_payment(
+  *,
+  db: Session = Depends(deps.get_db),
+  current_user: models.User = Depends(deps.get_current_active_user),
+  payment_data: schemas.PaymentCreate,
+  lang: str=''
+):  
+    print(payment_data)
+    MerchantTradeNo = 'test'+datetime.now().strftime("NO%Y%m%d%H%M%S")
+    remark = {'MerchantTradeNo':MerchantTradeNo}
+    remark_string = json.dumps(remark, ensure_ascii=False)
+    payment = crud.payment.create_with_payment_data(db, 
+                                                    obj_in=payment_data,
+                                                    owner_id=current_user.id, 
+                                                    epayment='ecpay',
+                                                    remark=remark_string)
+    order_params = {
+        'MerchantTradeNo': MerchantTradeNo,
+        'StoreID': 'SaaS',
+        'MerchantTradeDate': datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
+        'PaymentType': 'aio',
+        'TotalAmount': payment.amount,
+        'TradeDesc': 'SaaS訂單',
+        'ItemName': f'credit: {payment.item}',
+        'ReturnURL': 'https://cloud.choozmo.com/api/v1/payment/ecpay-result-return',
+        'ChoosePayment': 'ALL',
+        'ClientBackURL': 'https://cloud.choozmo.com/payment',
+        'ItemURL': 'https://cloud.choozmo.com/payment',
+        'Remark': '',
+        'ChooseSubPayment': '',
+        'OrderResultURL': '',
+        'NeedExtraPaidInfo': 'Y',
+        'DeviceSource': '',
+        'IgnorePayment': 'ATM#CVS#BARCODE',
+        'PlatformID': '',
+        'InvoiceMark': 'N',
+        'CustomField1': f'user id: {str(current_user.id)}',
+        'CustomField2': f'pyment id: {str(payment.id)}',
+        'CustomField3': '',
+        'CustomField4': '',
+        'EncryptType': 1,
+        'Language': lang,
+    }
+
+    extend_params_1 = {
+        'ExpireDate': 7,
+        'PaymentInfoURL': 'https://www.ecpay.com.tw/payment_info_url.php',
+        'ClientRedirectURL': '',
+    }
+
+    extend_params_2 = {
+        'StoreExpireDate': 15,
+        'Desc_1': '',
+        'Desc_2': '',
+        'Desc_3': '',
+        'Desc_4': '',
+        'PaymentInfoURL': 'https://www.ecpay.com.tw/payment_info_url.php',
+        'ClientRedirectURL': '',
+    }
+
+    extend_params_3 = {
+        'BindingCard': 0,
+        'MerchantMemberID': '',
+    }
+
+    extend_params_4 = {
+        'Redeem': 'N',
+        'UnionPay': 0,
+    }
+
+    inv_params = {
+        # 'RelateNumber': 'Tea0001', # 特店自訂編號
+        # 'CustomerID': 'TEA_0000001', # 客戶編號
+        # 'CustomerIdentifier': '53348111', # 統一編號
+        # 'CustomerName': '客戶名稱',
+        # 'CustomerAddr': '客戶地址',
+        # 'CustomerPhone': '0912345678', # 客戶手機號碼
+        # 'CustomerEmail': 'abc@ecpay.com.tw',
+        # 'ClearanceMark': '2', # 通關方式
+        # 'TaxType': '1', # 課稅類別
+        # 'CarruerType': '', # 載具類別
+        # 'CarruerNum': '', # 載具編號
+        # 'Donation': '1', # 捐贈註記
+        # 'LoveCode': '168001', # 捐贈碼
+        # 'Print': '1',
+        # 'InvoiceItemName': '測試商品1|測試商品2',
+        # 'InvoiceItemCount': '2|3',
+        # 'InvoiceItemWord': '個|包',
+        # 'InvoiceItemPrice': '35|10',
+        # 'InvoiceItemTaxType': '1|1',
+        # 'InvoiceRemark': '測試商品1的說明|測試商品2的說明',
+        # 'DelayDay': '0', # 延遲天數
+        # 'InvType': '07', # 字軌類別
+    }
+
+    # 建立實體
+    ecpay_payment_sdk = ECPayPaymentSdk(
+        MerchantID='3002607',
+        HashKey='pwFHCqoQZGmho4w6',
+        HashIV='EkRm7iFT261dpevs'
+    )
+
+    # 合併延伸參數
+    order_params.update(extend_params_1)
+    order_params.update(extend_params_2)
+    order_params.update(extend_params_3)
+    order_params.update(extend_params_4)
+
+    # 合併發票參數
+    order_params.update(inv_params)
+    try:
+        # 產生綠界訂單所需參數
+        final_order_params = ecpay_payment_sdk.create_order(order_params)
+
+        # 產生 html 的 form 格式
+        action_url = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'  # 測試環境
+        # action_url = 'https://payment.ecpay.com.tw/Cashier/AioCheckOut/V5' # 正式環境
+        html = ecpay_payment_sdk.gen_html_post_form(action_url, final_order_params)
+        
+        return html
+    except Exception as error:
+        print('An exception happened: ' + str(error))
+
+@router.post('/ecpay-payment')
+def ecpay_payment(
+  *,
+  db: Session = Depends(deps.get_db),
+  current_user: models.User = Depends(deps.get_current_active_user),
+  payment_data: schemas.PaymentCreate,
+  lang: str=''
+):
+    print(payment_data)
+    numbers = string.digits
+    randomNumber = ''.join(choice(numbers) for _ in range(4))
+    MerchantTradeNo = datetime.now().strftime("%Y%m%d%H%M%SNO")+randomNumber
+    remark = {'MerchantTradeNo':MerchantTradeNo}
+    remark_string = json.dumps(remark, ensure_ascii=False)
+    payment = crud.payment.create_with_payment_data(db, 
+                                                    obj_in=payment_data,
+                                                    owner_id=current_user.id, 
+                                                    epayment='ecpay',
+                                                    remark=remark_string)
+    order_params = {
+        'MerchantTradeNo': MerchantTradeNo,
+        'StoreID': 'SaaS',
+        'MerchantTradeDate': datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
+        'PaymentType': 'aio',
+        'TotalAmount': payment.amount,
+        'TradeDesc': 'SaaS訂單',
+        'ItemName': f'credit: {payment.item}',
+        'ReturnURL': 'https://cloud.choozmo.com/api/v1/payment/ecpay-result-return',
+        'ChoosePayment': 'ALL',
+        'ClientBackURL': 'https://cloud.choozmo.com/payment',
+        'ItemURL': 'https://cloud.choozmo.com/payment',
+        'Remark': '',
+        'ChooseSubPayment': '',
+        'OrderResultURL': '',
+        'NeedExtraPaidInfo': 'Y',
+        'DeviceSource': '',
+        'IgnorePayment': 'ATM#CVS#BARCODE',
+        'PlatformID': '',
+        'InvoiceMark': 'N',
+        'CustomField1': f'user id: {str(current_user.id)}',
+        'CustomField2': f'pyment id: {str(payment.id)}',
+        'CustomField3': '',
+        'CustomField4': '',
+        'EncryptType': 1,
+        'Language': lang,
+    }
+
+    extend_params_1 = {
+        'ExpireDate': 7,
+        'PaymentInfoURL': 'https://www.ecpay.com.tw/payment_info_url.php',
+        'ClientRedirectURL': '',
+    }
+
+    extend_params_2 = {
+        'StoreExpireDate': 15,
+        'Desc_1': '',
+        'Desc_2': '',
+        'Desc_3': '',
+        'Desc_4': '',
+        'PaymentInfoURL': 'https://www.ecpay.com.tw/payment_info_url.php',
+        'ClientRedirectURL': '',
+    }
 
 
-@router.post("/ecpay-test-result-return")
+    extend_params_3 = {
+        'BindingCard': 0,
+        'MerchantMemberID': '',
+    }
+
+    extend_params_4 = {
+        'Redeem': 'N',
+        'UnionPay': 0,
+    }
+
+    inv_params = {
+        # 'RelateNumber': 'Tea0001', # 特店自訂編號
+        # 'CustomerID': 'TEA_0000001', # 客戶編號
+        # 'CustomerIdentifier': '53348111', # 統一編號
+        # 'CustomerName': '客戶名稱',
+        # 'CustomerAddr': '客戶地址',
+        # 'CustomerPhone': '0912345678', # 客戶手機號碼
+        # 'CustomerEmail': 'abc@ecpay.com.tw',
+        # 'ClearanceMark': '2', # 通關方式
+        # 'TaxType': '1', # 課稅類別
+        # 'CarruerType': '', # 載具類別
+        # 'CarruerNum': '', # 載具編號
+        # 'Donation': '1', # 捐贈註記
+        # 'LoveCode': '168001', # 捐贈碼
+        # 'Print': '1',
+        # 'InvoiceItemName': '測試商品1|測試商品2',
+        # 'InvoiceItemCount': '2|3',
+        # 'InvoiceItemWord': '個|包',
+        # 'InvoiceItemPrice': '35|10',
+        # 'InvoiceItemTaxType': '1|1',
+        # 'InvoiceRemark': '測試商品1的說明|測試商品2的說明',
+        # 'DelayDay': '0', # 延遲天數
+        # 'InvType': '07', # 字軌類別
+    }
+
+    # 建立實體
+    ecpay_payment_sdk = ECPayPaymentSdk(
+        MerchantID='3226141',
+        HashKey='OhcjDTeXK9PKW9vb',
+        HashIV='AfOmUM06S0bt8KPE'
+    )
+
+    # 合併延伸參數
+    order_params.update(extend_params_1)
+    order_params.update(extend_params_2)
+    order_params.update(extend_params_3)
+    order_params.update(extend_params_4)
+
+    # 合併發票參數
+    order_params.update(inv_params)
+    try:
+        # 產生綠界訂單所需參數
+        final_order_params = ecpay_payment_sdk.create_order(order_params)
+
+        # 產生 html 的 form 格式
+        # action_url = 'https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5'  # 測試環境
+        action_url = 'https://payment.ecpay.com.tw/Cashier/AioCheckOut/V5' # 正式環境
+        html = ecpay_payment_sdk.gen_html_post_form(action_url, final_order_params)
+
+        return html
+    except Exception as error:
+        print('An exception happened: ' + str(error))
+
+
+@router.get('/list-all', response_model=List[schemas.YTViews])
+def get_list(
+  *,
+  db: Session = Depends(deps.get_db),
+):
+  ytviews_list = crud.ytviews.get_multi(db)
+  return ytviews_list
+
+@router.post("/ecpay-result-return")
 def ecpay_return(
 def ecpay_return(
     *,
     *,
+    db: Session = Depends(deps.get_db),
     MerchantID: Optional[str]=Form(None),
     MerchantID: Optional[str]=Form(None),
     MerchantTradeNo: Optional[str]=Form(None),
     MerchantTradeNo: Optional[str]=Form(None),
     StoreID: Optional[str]=Form(None),
     StoreID: Optional[str]=Form(None),
@@ -100,13 +322,11 @@ CustomField3: {CustomField3}\n\
 CustomField4: {CustomField4}\n\
 CustomField4: {CustomField4}\n\
 CheckMacValue: {CheckMacValue}\
 CheckMacValue: {CheckMacValue}\
   ")
   ")
-  return Response(content='1', status_code=status.HTTP_200_OK)
-
-
-@router.get('/check-payment-list')
-def get_payment_list(
-  *,
-  db: Session = Depends(deps.get_db),
-  current_user: models.User = Depends(deps.get_current_active_user)
-):
-  pass
+  
+  if RtnCode==1:
+    ytviews_id = int(CustomField1)
+    ytviews = crud.ytviews.get(db=db, id=ytviews_id)
+    remark = json.loads(ytviews.remark)
+    remark['TradeNo'] = TradeNo
+    crud.ytviews.update(db, db_obj=ytviews, obj_in={"payment_state":"succeeded", "remark":json.dumps(remark, ensure_ascii=False)})
+  return Response(content='1', status_code=status.HTTP_200_OK)

+ 3 - 0
backend/app/app/api/api_v1/endpoints/videos.py

@@ -86,6 +86,9 @@ def upload_plot(
     Create new video.
     Create new video.
     """
     """
     print(title)
     print(title)
+    print(anchor)
+    print(style)
+    print(lang)
     print(upload_file.filename)
     print(upload_file.filename)
     filename = crud.video.generate_file_name(db=db, n=20)
     filename = crud.video.generate_file_name(db=db, n=20)
     filepath = str(Path(BACKEND_ZIP_STORAGE).joinpath(filename+".zip"))
     filepath = str(Path(BACKEND_ZIP_STORAGE).joinpath(filename+".zip"))

+ 58 - 0
backend/app/app/core/video_utils.py

@@ -0,0 +1,58 @@
+import pandas as pd
+from pathlib import Path
+import subprocess
+import shutil
+import os
+import chardet
+import zipfile
+from test_chardet import guess_codec
+from io import BytesIO
+  
+def check_zip(zip_filepath:str):
+  path = Path(zip_filepath)
+  with zipfile.ZipFile(str(path)) as zf:
+    filenames = [x for x in zf.namelist() if not x.endswith('/')]
+    result = guess_codec(filenames)
+    true_filenames = [x.encode('cp437').decode(result) for x in zf.namelist() if not x.endswith('/')]
+    # print(true_filenames)
+    scenarios_files = [(x, i) for i, x in enumerate(true_filenames) if Path(x).suffix in [".xlsx", ".csv"] and not Path(x).name.startswith("._") and Path(x).stem != "style"]
+    # print(scenarios_files)
+    
+    if len(scenarios_files) == 0:
+      raise ValueError("no excel or csv file in zip.")
+    if len(scenarios_files) > 1:
+      raise ValueError("too many excel or csv file in zip.")
+    f = zf.read(filenames[scenarios_files[0][1]])
+    if Path(scenarios_files[0][0]).suffix == ".xlsx":
+      table = pd.read_excel(BytesIO(f), dtype=object)
+    elif Path(scenarios_files[0][0]).suffix == ".csv":
+      table = pd.read_csv(BytesIO(f), dtype=object)
+    table.reset_index(inplace=True)
+    # print(table)
+    
+    stems = [Path(x).stem for x in true_filenames]
+    for i in range(len(table)):
+      # excel 裡的圖檔跟zip裡的檔案要一致
+      if table.loc[i, ['素材']].isna().item():
+        img =  table.loc[i, ['素材']].item()
+        print(img)
+
+        img_files = [x.strip() for x in img.split(',')]
+        for img in img_files:
+          print(img)
+          n = stems.count(img)
+          if n == 0:
+            raise ValueError(f"{img}: no such media file in zip.")
+          elif n > 1:
+            raise ValueError(f'too many same name media files as {img} in zip')
+      
+      # 需要tts文字或音檔
+      if table.loc[i, ['字幕']].isna().item():
+        if table.loc[i, ['音檔']].isna().item():
+          raise ValueError(f'text or voice file is needed at scene {i+1}.')
+        voice_file = table.loc[i, ['音檔']].item()
+        n = stems.count(voice_file)
+        if n != 1:
+          raise ValueError(f"voice file is can't find is zip at scene {i+1}.")
+        
+    

+ 1 - 0
backend/app/app/crud/__init__.py

@@ -3,6 +3,7 @@ from .crud_video import video
 from .crud_article import article
 from .crud_article import article
 from .crud_ser_no import serial_number
 from .crud_ser_no import serial_number
 from .crud_ytviews import ytviews
 from .crud_ytviews import ytviews
+from .crud_payment import payment
 # For a new basic set of CRUD operations 
 # For a new basic set of CRUD operations 
 # you could just do
 # you could just do
 
 

+ 38 - 0
backend/app/app/crud/crud_payment.py

@@ -0,0 +1,38 @@
+from typing import List
+
+from fastapi.encoders import jsonable_encoder
+from sqlalchemy.orm import Session
+from sqlalchemy import desc
+from app.crud.base import CRUDBase
+from app.models.payment import Payment
+from app.schemas.payment import PaymentCreate, PaymentUpdate
+
+from app.utils import random_name
+
+class CRUDPayment(CRUDBase[Payment, PaymentCreate, PaymentUpdate]):
+    def create_with_payment_data(
+        self, db: Session, *, obj_in: PaymentCreate, 
+        owner_id:int,
+        epayment:str,
+        remark: str
+    ) -> Payment:
+        
+        obj_in_data = jsonable_encoder(obj_in)
+        db_obj = self.model(**obj_in_data, 
+                            owner_id=owner_id,
+                            epayment=epayment,
+                            remark=remark)
+        print(db_obj)
+        db.add(db_obj)
+        db.commit()
+        db.refresh(db_obj)
+        return db_obj
+      
+    def get_all_desc(self, db: Session) -> List[Payment]:
+        return (
+            db.query(self.model)
+            .order_by(desc(Payment.id))
+            .all()
+        )
+        
+payment = CRUDPayment(Payment)

+ 4 - 3
backend/app/app/models/payment.py

@@ -12,12 +12,13 @@ if TYPE_CHECKING:
 
 
 class Payment(Base):
 class Payment(Base):
   id = Column(Integer, primary_key=True, index=True)
   id = Column(Integer, primary_key=True, index=True)
-  order_number = Column(String(30), index=True, nullable=False)
-  datetime = Column(DateTime)
+  created_datetime = Column(DateTime(timezone=True), default=func.now())
+  item = Column(String(20), nullable=False)
   amount = Column(Integer, nullable=False)
   amount = Column(Integer, nullable=False)
-  payment_state = Column(DateTime(timezone=True), default=func.now())
+  payment_state = Column(String(10), default='waiting', nullable=False)
   epayment = Column(String(10), 
   epayment = Column(String(10), 
                     ForeignKey("epayment.name", ondelete="RESTRICT", onupdate="CASCADE"),
                     ForeignKey("epayment.name", ondelete="RESTRICT", onupdate="CASCADE"),
                     default="ecpay")
                     default="ecpay")
+  remark = Column(Text)
   owner_id = Column(Integer, ForeignKey("user.id"))
   owner_id = Column(Integer, ForeignKey("user.id"))
   owner = relationship("User", back_populates="payments")
   owner = relationship("User", back_populates="payments")

+ 2 - 1
backend/app/app/schemas/__init__.py

@@ -4,4 +4,5 @@ from .msg import Msg
 from .video import Video, VideoCreate, VideoInDB, VideoUpdate
 from .video import Video, VideoCreate, VideoInDB, VideoUpdate
 from .article import ArticleBase, ArticleCreate, ArticleUpdate
 from .article import ArticleBase, ArticleCreate, ArticleUpdate
 from .serial_number import SerialNumberBase, SerialNumberCreate, SerialNumberUpdate
 from .serial_number import SerialNumberBase, SerialNumberCreate, SerialNumberUpdate
-from .ytviews import YTViewsBase, YTViewsCreate, YTViews
+from .ytviews import YTViewsBase, YTViewsCreate, YTViews
+from .payment import PaymentBase, PaymentCreate, PaymentInDB, PaymentUpdate

+ 37 - 0
backend/app/app/schemas/payment.py

@@ -0,0 +1,37 @@
+from typing import Optional
+
+from pydantic import BaseModel, EmailStr, HttpUrl
+
+# Shared properties
+class PaymentBase(BaseModel):
+    item: Optional[str] = None
+    amount: Optional[str] = None
+    
+# Properties to receive on video creation
+class PaymentCreate(PaymentBase):
+    item: str 
+    amount: str 
+    
+# Properties to receive on video update
+class PaymentUpdate(PaymentBase):
+    pass
+
+# Properties shared by models stored in DB
+class PaymentBaseInDBBase(PaymentBase):
+    id: int
+    item: str
+    amount: str
+    payment_state: str
+    
+    class Config:
+        orm_mode = True
+
+
+# Properties to return to client
+class Payment(PaymentBaseInDBBase):
+    pass
+
+
+# Properties properties stored in DB
+class PaymentInDB(PaymentBaseInDBBase):
+    pass