ソースを参照

add ytviews payment

tomoya 1 年間 前
コミット
c6d847ecf2

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

@@ -1,6 +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, payment
+from app.api.api_v1.endpoints import  login, users, utils, videos, images, reputations, ser_no
+from app.api.api_v1.endpoints import payment, ytviewspayment
+
 
 
 api_router = APIRouter()
 api_router = APIRouter()
 api_router.include_router(login.router, tags=["login"])
 api_router.include_router(login.router, tags=["login"])
@@ -10,4 +12,5 @@ api_router.include_router(videos.router, prefix="/videos", tags=["videos"])
 api_router.include_router(images.router, prefix="/images", tags=["iamges"])
 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(payment.router, prefix="/payment", tags=["payment"])
 api_router.include_router(payment.router, prefix="/payment", tags=["payment"])

+ 53 - 115
backend/app/app/api/api_v1/endpoints/payment.py

@@ -12,126 +12,54 @@ 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
 
 
+import requests
+
 router = APIRouter()
 router = APIRouter()
 
 
-@router.post("/ecpayTestPay", response_model=str)
-def ecpay(
+@router.post("/ecpay-result-return")
+def ecpay_return(
     *,
     *,
-    db: Session = Depends(deps.get_db),
-    current_user: models.User = Depends(deps.get_current_active_user),
-    amount: str=Form(...),
+    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:
 ) -> Any:
-    print(amount)
-
-    order_params = {
-        'MerchantTradeNo': datetime.now().strftime("NO%Y%m%d%H%M%S"),
-        'StoreID': '3226141',
-        'MerchantTradeDate': datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
-        'PaymentType': 'aio',
-        'TotalAmount': amount,
-        'TradeDesc': '訂單測試',
-        'ItemName': '商品1#商品2',
-        'ReturnURL': 'https://cloud.choozmo.com/api/v1/payment/ecpayTestReturn',
-        'ChoosePayment': 'ALL',
-        'ClientBackURL': 'http://dev.cloud.choozmo.com:5173/main/admin/test-ecpay',
-        'ItemURL': 'http://dev.cloud.choozmo.com:5173/main/admin/test-ecpay',
-        'Remark': '交易備註',
-        'ChooseSubPayment': '',
-        'OrderResultURL': 'http://dev.cloud.choozmo.com:5173/main/admin/test-ecpay',
-        'NeedExtraPaidInfo': 'Y',
-        'DeviceSource': '',
-        'IgnorePayment': '',
-        'PlatformID': '',
-        'InvoiceMark': 'N',
-        'CustomField1': '',
-        'CustomField2': '',
-        'CustomField3': '',
-        'CustomField4': '',
-        'EncryptType': 1,
-        'Language': '',
-    }
-
-    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)
+  #送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)
 
 
-        # 產生 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)
-        print(html)
-        return html
-    except Exception as error:
-        print('An exception happened: ' + str(error))
-        
-@router.post("/ecpayTestReturn")
+@router.post("/ecpay-test-result-return")
 def ecpay_return(
 def ecpay_return(
     *,
     *,
     MerchantID: Optional[str]=Form(None),
     MerchantID: Optional[str]=Form(None),
@@ -152,6 +80,7 @@ def ecpay_return(
     CustomField4: Optional[str]=Form(None),
     CustomField4: Optional[str]=Form(None),
     CheckMacValue: Optional[str]=Form(None),
     CheckMacValue: Optional[str]=Form(None),
 ) -> Any:
 ) -> Any:
+  #送email
   print(f"\
   print(f"\
 MerchantID: {MerchantID} \n\
 MerchantID: {MerchantID} \n\
 MerchantTradeNo: {MerchantTradeNo}\n\
 MerchantTradeNo: {MerchantTradeNo}\n\
@@ -172,3 +101,12 @@ CustomField4: {CustomField4}\n\
 CheckMacValue: {CheckMacValue}\
 CheckMacValue: {CheckMacValue}\
   ")
   ")
   return Response(content='1', status_code=status.HTTP_200_OK)
   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

+ 328 - 0
backend/app/app/api/api_v1/endpoints/ytviewspayment.py

@@ -0,0 +1,328 @@
+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, HttpUrl
+from sqlalchemy.orm import Session
+
+import app.crud as crud
+import app.models as models
+import app.schemas as 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
+from pydantic import BaseModel
+import requests
+from random import choice
+import string
+import json
+
+router = APIRouter()
+
+@router.post('/ytviews-ecpay-test-payment')
+def ecpay_payment(
+  *,
+  db: Session = Depends(deps.get_db),
+  user_data: schemas.YTViewsCreate,
+  lang: str=''
+):  
+    print(user_data.url)
+    MerchantTradeNo = 'test'+datetime.now().strftime("NO%Y%m%d%H%M%S")
+    remark = {'MerchantTradeNo':MerchantTradeNo}
+    remark_string = json.dumps(remark, ensure_ascii=False)
+    ytviews = crud.ytviews.create_with_payment_data(db, 
+                                                        obj_in=user_data, 
+                                                        epayment='ecpay',
+                                                        remark=remark_string)
+    order_params = {
+        'MerchantTradeNo': MerchantTradeNo,
+        'StoreID': 'SaaS',
+        'MerchantTradeDate': datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
+        'PaymentType': 'aio',
+        'TotalAmount': user_data.amount,
+        'TradeDesc': 'YT0.4訂單測試',
+        'ItemName': user_data.item,
+        'ReturnURL': 'https://cloud.choozmo.comtest-yt-views/test-yt-views',
+        'ChoosePayment': 'ALL',
+        'ClientBackURL': 'https://cloud.choozmo.comtest-yt-views/test-yt-views',
+        'ItemURL': 'https://cloud.choozmo.comtest-yt-views/test-yt-views',
+        'Remark': '',
+        'ChooseSubPayment': '',
+        'OrderResultURL': 'https://cloud.choozmo.comtest-yt-views/test-yt-views',
+        'NeedExtraPaidInfo': 'Y',
+        'DeviceSource': '',
+        'IgnorePayment': 'ATM#CVS#BARCODE',
+        'PlatformID': '',
+        'InvoiceMark': 'N',
+        'CustomField1': str(ytviews.id),
+        'CustomField2': user_data.taxID if user_data.taxID else '',
+        '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('/ytviews-ecpay-payment')
+def ecpay_payment(
+  *,
+  db: Session = Depends(deps.get_db),
+  user_data: schemas.YTViewsCreate,
+  lang: str
+):
+    print(user_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.dump(remark, ensure_ascii=False)
+    ytviews = crud.ytviews.create_with_payment_data(db, 
+                                                obj_in=user_data, 
+                                                epayment='ecpay',
+                                                remark=remark_string)
+    order_params = {
+        'MerchantTradeNo': MerchantTradeNo,
+        'StoreID': 'SaaS',
+        'MerchantTradeDate': datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
+        'PaymentType': 'aio',
+        'TotalAmount': user_data.amount,
+        'TradeDesc': 'YT0.4訂單',
+        'ItemName': user_data.item,
+        'ReturnURL': 'https://cloud.choozmo.comtest-yt-views/yt-views',
+        'ChoosePayment': 'ALL',
+        'ClientBackURL': 'https://cloud.choozmo.comtest-yt-views/yt-views',
+        'ItemURL': 'https://cloud.choozmo.comtest-yt-views/yt-views',
+        'Remark': '',
+        'ChooseSubPayment': '',
+        'OrderResultURL': 'https://cloud.choozmo.comtest-yt-views/yt-views',
+        'NeedExtraPaidInfo': 'Y',
+        'DeviceSource': '',
+        'IgnorePayment': 'ATM#CVS#BARCODE',
+        'PlatformID': '',
+        'InvoiceMark': 'N',
+        'CustomField1': str(ytviews.id),
+        'CustomField2': user_data.taxID if user_data.taxID else '',
+        '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='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.post('/ytviews-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("/ytviews-ecpay-result-return")
+def ecpay_return(
+    *,
+    db: Session = Depends(deps.get_db),
+    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}\
+  ")
+  
+  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 - 1
backend/app/app/crud/__init__.py

@@ -2,7 +2,9 @@ from .crud_user import user
 from .crud_video import video
 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
-# For a new basic set of CRUD operations you could just do
+from .crud_ytviews import ytviews
+# For a new basic set of CRUD operations 
+# you could just do
 
 
 # from .base import CRUDBase
 # from .base import CRUDBase
 # from app.models.item import Item
 # from app.models.item import Item

+ 35 - 0
backend/app/app/crud/crud_ytviews.py

@@ -0,0 +1,35 @@
+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.ytviews import YTViews
+from app.schemas.ytviews import YTViewsCreate, YTViewsUpdate
+
+from app.utils import random_name
+
+class CRUDYTViews(CRUDBase[YTViews, YTViewsCreate, YTViewsUpdate]):
+    def create_with_payment_data(
+        self, db: Session, *, obj_in: YTViewsCreate, 
+        epayment:str,
+        remark: str
+    ) -> YTViews:
+        print("in create_with_payment_data")
+        obj_in_data = jsonable_encoder(obj_in)
+        db_obj = self.model(**obj_in_data, 
+                            epayment=epayment,
+                            remark=remark)
+        db.add(db_obj)
+        db.commit()
+        db.refresh(db_obj)
+        return db_obj
+      
+    def get_all_desc(self, db: Session) -> List[YTViews]:
+        return (
+            db.query(self.model)
+            .order_by(desc(YTViews.id))
+            .all()
+        )
+        
+ytviews = CRUDYTViews(YTViews)

+ 3 - 1
backend/app/app/db/base.py

@@ -3,8 +3,10 @@
 from app.db.base_class import Base  # noqa
 from app.db.base_class import Base  # noqa
 from app.models.user import User  # noqa
 from app.models.user import User  # noqa
 from app.models.video import Video
 from app.models.video import Video
-from app.models.enum import Progress, Membership
+from app.models.enum import Progress, Membership, Epayment
 from app.models.character import Character
 from app.models.character import Character
 from app.models.article import Article
 from app.models.article import Article
 from app.models.serial_number import SerialNumber
 from app.models.serial_number import SerialNumber
 from app.models.voice import Voice
 from app.models.voice import Voice
+from app.models.payment import Payment
+from app.models.ytviews import YTViews

+ 4 - 2
backend/app/app/models/__init__.py

@@ -1,6 +1,8 @@
 from .user import User
 from .user import User
 from .video import Video
 from .video import Video
-from .enum import Membership, Progress
+from .enum import Membership, Progress, Epayment
 from .article import Article
 from .article import Article
 from .serial_number import SerialNumber
 from .serial_number import SerialNumber
-from .voice import Voice
+from .voice import Voice
+from .payment import Payment
+from .ytviews import YTViews

+ 4 - 1
backend/app/app/models/enum.py

@@ -9,4 +9,7 @@ class Membership(Base):
   status = Column(String(10), primary_key=True)
   status = Column(String(10), primary_key=True)
 
 
 class Progress(Base):
 class Progress(Base):
-  state = Column(String(10), primary_key=True)
+  state = Column(String(10), primary_key=True)
+  
+class Epayment(Base):
+  name = Column(String(10), primary_key=True)

+ 23 - 0
backend/app/app/models/payment.py

@@ -0,0 +1,23 @@
+from typing import TYPE_CHECKING
+
+from sqlalchemy import Column, ForeignKey, Integer, String, Enum, DateTime, Text
+from sqlalchemy.orm import relationship
+from sqlalchemy.sql import func
+from app.db.base_class import Base
+
+
+if TYPE_CHECKING:
+  from .user import User  # noqa: F401
+
+
+class Payment(Base):
+  id = Column(Integer, primary_key=True, index=True)
+  order_number = Column(String(30), index=True, nullable=False)
+  datetime = Column(DateTime)
+  amount = Column(Integer, nullable=False)
+  payment_state = Column(DateTime(timezone=True), default=func.now())
+  epayment = Column(String(10), 
+                    ForeignKey("epayment.name", ondelete="RESTRICT", onupdate="CASCADE"),
+                    default="ecpay")
+  owner_id = Column(Integer, ForeignKey("user.id"))
+  owner = relationship("User", back_populates="payments")

+ 1 - 0
backend/app/app/models/user.py

@@ -21,3 +21,4 @@ class User(Base):
   is_superuser = Column(Boolean(), default=False)
   is_superuser = Column(Boolean(), default=False)
   videos = relationship("Video", back_populates="owner")
   videos = relationship("Video", back_populates="owner")
   articles = relationship("Article", back_populates="owner")
   articles = relationship("Article", back_populates="owner")
+  payments = relationship("Payment", back_populates="owner")

+ 33 - 0
backend/app/app/models/ytviews.py

@@ -0,0 +1,33 @@
+from typing import TYPE_CHECKING
+
+from sqlalchemy import Column, ForeignKey, Integer, String, Enum, DateTime, Text
+from sqlalchemy.orm import relationship
+from sqlalchemy.sql import func
+from app.db.base_class import Base
+
+
+if TYPE_CHECKING:
+  from .user import User  # noqa: F401
+  from .enum import Progress
+
+class YTViews(Base):
+  id = Column(Integer, primary_key=True, index=True)
+  email = Column(String(50), index=True, nullable=False)
+  name = Column(String(30), index=True)
+  company = Column(String(20))
+  url = Column(String(100), index=True, nullable=False)
+  area = Column(String(20))
+  language = Column(String(20))
+  ages = Column(String(30))
+  target = Column(String(20))
+  theme = Column(String(20))
+  taxID = Column(String(20))
+  item = Column(String(20), nullable=False)
+  amount = Column(Integer, nullable=False)
+  created_datetime = Column(DateTime(timezone=True), default=func.now())
+  payment_state = Column(String(10), default='waiting', nullable=False)
+  epayment = Column(String(10), 
+                    ForeignKey("epayment.name", ondelete="RESTRICT", onupdate="CASCADE"),
+                    default="ecpay")
+  remark = Column(Text)
+  

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

@@ -4,3 +4,4 @@ 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

+ 64 - 0
backend/app/app/schemas/ytviews.py

@@ -0,0 +1,64 @@
+from typing import Optional
+
+from pydantic import BaseModel, EmailStr, HttpUrl
+
+# Shared properties
+class YTViewsBase(BaseModel):
+    email: Optional[EmailStr] = None
+    name : Optional[str] = None
+    company: Optional[str] = None
+    url: Optional[HttpUrl] = None
+    area: Optional[str] = None
+    language: Optional[str] = None
+    ages: Optional[str] = None
+    target: Optional[str] = None
+    theme: Optional[str] = None
+    taxID: Optional[str] = None
+    item: Optional[str] = None
+    amount: Optional[str] = None
+    
+# Properties to receive on video creation
+class YTViewsCreate(YTViewsBase):
+    email: EmailStr
+    name : str
+    company: str
+    url: HttpUrl
+    area: str
+    language: str
+    ages: str
+    target: str
+    theme: str
+    item: Optional[str] = None
+    amount: Optional[str] = None
+    
+# Properties to receive on video update
+class YTViewsUpdate(YTViewsBase):
+    pass
+
+# Properties shared by models stored in DB
+class YTViewsInDBBase(YTViewsBase):
+    id: int
+    email: EmailStr
+    name : str
+    company: str
+    url: HttpUrl
+    area: str
+    language: str
+    ages: str
+    target: str
+    theme: str
+    item: str
+    amount: str
+    payment_state: str
+    class Config:
+        orm_mode = True
+
+
+# Properties to return to client
+class YTViews(YTViewsInDBBase):
+    pass
+
+
+# Properties properties stored in DB
+class YTViewsInDB(YTViewsInDBBase):
+    pass

+ 5 - 3
frontend/src/api.ts

@@ -140,8 +140,10 @@ export const api = {
     formData.append("amount", amount.toString());
     formData.append("amount", amount.toString());
     return axios.post<string>(`${apiUrl}/api/v1/payment/ecpayTestPay`, formData, authHeaders(token))
     return axios.post<string>(`${apiUrl}/api/v1/payment/ecpayTestPay`, formData, authHeaders(token))
   },
   },
-  async uploadPayment(token: string, user_data: YTViewsUserData, name: string, amount: number) {
-    let data = { items: name, amount: amount, remark: user_data }
-    return axios.post<{ msg: string }>(`${apiUrl}/api/v1/payment/ecpay-payment/`, data, authHeaders(token));
+  async YTViewsPayment(user_data: YTViewsUserData) {
+    return axios.post<string>(`${apiUrl}/api/v1/payment/ytviews-ecpay-payment`, user_data);
+  },
+  async YTViewsTestPayment(user_data: YTViewsUserData) {
+    return axios.post<string>(`${apiUrl}/api/v1/payment/ytviews-ecpay-test-payment`, user_data);
   },
   },
 };
 };

+ 21 - 0
frontend/src/components/imgBox/index.vue

@@ -0,0 +1,21 @@
+<script setup lang="ts">
+import {reactive} from "vue"
+
+  const src = defineProps(['src'])
+  console.log(src)
+  const data = reactive({
+    active: false,
+    x: 100,
+    y: 100,
+  })
+</script>
+<template>
+  <img :src="src" class="img-fluid"/>
+</template>
+
+<style>
+.img-fluid {
+  position: absolute;
+}
+</style>
+

+ 5 - 3
frontend/src/interfaces/index.ts

@@ -81,14 +81,16 @@ export interface ImageDownload {
 }
 }
 
 
 export interface YTViewsUserData {
 export interface YTViewsUserData {
+  item: string,
+  amount: number,
   email: string;
   email: string;
   name: string;
   name: string;
   company: string | null;
   company: string | null;
   url: string;
   url: string;
   area: string;
   area: string;
   language: string;
   language: string;
-  age: string | null;
-  object: string;
+  ages: string | null;
+  target: string;
   theme: string;
   theme: string;
-  tax: string | null;
+  taxID: string | null;
 }
 }

+ 5 - 0
frontend/src/router/index.ts

@@ -43,6 +43,11 @@ const router = createRouter({
           name: 'yt-views',
           name: 'yt-views',
           component: () => import('@/views/YTViews.vue'),
           component: () => import('@/views/YTViews.vue'),
         },
         },
+        {
+          path: 'test-yt-views',
+          name: 'test-yt-views',
+          component: () => import('@/views/TestYTViews.vue'),
+        },
         {
         {
           path: 'main',
           path: 'main',
           name: 'main',
           name: 'main',

+ 21 - 2
frontend/src/stores/main.ts

@@ -465,15 +465,34 @@ export const useMainStore = defineStore("MainStoreId", {
         await mainStore.checkApiError(error);
         await mainStore.checkApiError(error);
       }
       }
     },
     },
-    async uploadPayment(user_data: YTViewsUserData, name: string, amount: number) {
+    async YTViewsTestPayment(user_data: YTViewsUserData) {
       const mainStore = useMainStore();
       const mainStore = useMainStore();
       try {
       try {
         const response = (
         const response = (
           await Promise.all([
           await Promise.all([
-            api.uploadPayment(mainStore.token, user_data, name, amount),
+            api.YTViewsTestPayment(user_data),
             await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 0)),
             await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 0)),
           ])
           ])
         );
         );
+        if (response[0]) {
+          return response[0].data;
+        }
+      } catch (error) {
+        await mainStore.checkApiError(error);
+      }
+    },
+    async YTViewsPayment(user_data: YTViewsUserData) {
+      const mainStore = useMainStore();
+      try {
+        const response = (
+          await Promise.all([
+            api.YTViewsPayment(user_data),
+            await new Promise<void>((resolve, _) => setTimeout(() => resolve(), 0)),
+          ])
+        );
+        if (response[0]) {
+          return response[0].data;
+        }
       } catch (error) {
       } catch (error) {
         await mainStore.checkApiError(error);
         await mainStore.checkApiError(error);
       }
       }

+ 19 - 21
frontend/src/views/main/YTViews.vue → frontend/src/views/TestYTViews.vue

@@ -90,16 +90,16 @@ let userData = reactive({
   url: "",
   url: "",
   area: "",
   area: "",
   language: "",
   language: "",
-  age: [],
-  object: "",
+  ages: [],
+  target: "",
   theme: "",
   theme: "",
-  tax: "",
+  taxID: "",
 });
 });
 
 
 // 其他選項
 // 其他選項
-let object = ref("");
+let target = ref("");
 let theme = ref("");
 let theme = ref("");
-let otherObject = ref("");
+let otherTarget = ref("");
 let otherTheme = ref("");
 let otherTheme = ref("");
 
 
 // 檢查必填欄位
 // 檢查必填欄位
@@ -110,7 +110,7 @@ const isSubmitDisabled = computed(() => {
     !userData.url ||
     !userData.url ||
     !userData.area ||
     !userData.area ||
     !userData.language ||
     !userData.language ||
-    !object.value ||
+    !target.value ||
     !theme.value
     !theme.value
   );
   );
 });
 });
@@ -123,10 +123,10 @@ async function ECPaySubmit() {
     chooseError.value = false;
     chooseError.value = false;
   }
   }
 
 
-  if (object.value === "其他" && otherObject.value !== "") {
-    userData.object = otherObject.value;
+  if (target.value === "其他" && otherTarget.value !== "") {
+    userData.target = otherTarget.value;
   } else {
   } else {
-    userData.object = object.value;
+    userData.target = target.value;
   }
   }
 
 
   if (theme.value === "其他" && otherTheme.value !== "") {
   if (theme.value === "其他" && otherTheme.value !== "") {
@@ -136,21 +136,20 @@ async function ECPaySubmit() {
   }
   }
 
 
   let data: YTViewsUserData = {
   let data: YTViewsUserData = {
+    item: `YT0.4-(${assignView.value})`,
+    amount: assignPrice.value,
     email: userData.email,
     email: userData.email,
     name: userData.name,
     name: userData.name,
     company: userData.company,
     company: userData.company,
     url: userData.url,
     url: userData.url,
     area: userData.area,
     area: userData.area,
     language: userData.language,
     language: userData.language,
-    age: userData.age.join("、"),
-    object: userData.object,
+    ages: userData.ages.join(","),
+    target: userData.target,
     theme: userData.theme,
     theme: userData.theme,
-    tax: userData.tax,
+    taxID: userData.taxID,
   };
   };
-
-  await mainStore.uploadPayment(data, assignView.value, assignPrice.value);
-
-  const originalHTML = await mainStore.ecpayPaymentHTML(assignPrice.value);
+  const originalHTML = await mainStore.YTViewsTestPayment(data);
   let formHTML = originalHTML?.replace(
   let formHTML = originalHTML?.replace(
     '<script type="text/javascript">document.getElementById("data_set").submit();</scr',
     '<script type="text/javascript">document.getElementById("data_set").submit();</scr',
     ""
     ""
@@ -161,7 +160,6 @@ async function ECPaySubmit() {
   const ecpayForm: HTMLFormElement = <HTMLFormElement>(
   const ecpayForm: HTMLFormElement = <HTMLFormElement>(
     document.getElementById("data_set")
     document.getElementById("data_set")
   );
   );
-  console.log(ecpayForm);
   ecpayForm.submit();
   ecpayForm.submit();
 }
 }
 
 
@@ -303,7 +301,7 @@ async function ECPaySubmit() {
             <div class="checkbox ms-5">
             <div class="checkbox ms-5">
               <v-checkbox
               <v-checkbox
                 v-for="option in ageOptions"
                 v-for="option in ageOptions"
-                v-model="userData.age"
+                v-model="userData.ages"
                 :key="option.label"
                 :key="option.label"
                 :label="option.label"
                 :label="option.label"
                 :value="option.label"
                 :value="option.label"
@@ -315,7 +313,7 @@ async function ECPaySubmit() {
               目標對象區隔(興趣、習慣)<span class="text-red-darken-1">*</span>
               目標對象區隔(興趣、習慣)<span class="text-red-darken-1">*</span>
             </p>
             </p>
             <div class="ms-5">
             <div class="ms-5">
-              <v-radio-group v-model="object">
+              <v-radio-group v-model="target">
                 <v-radio
                 <v-radio
                   v-for="option in objectOptions"
                   v-for="option in objectOptions"
                   :key="option.label"
                   :key="option.label"
@@ -324,7 +322,7 @@ async function ECPaySubmit() {
                   color="primary"
                   color="primary"
                 ></v-radio>
                 ></v-radio>
                 <v-radio label="其他" value="其他" color="primary"></v-radio>
                 <v-radio label="其他" value="其他" color="primary"></v-radio>
-                <input v-model="otherObject" type="text" class="other" />
+                <input v-model="otherTarget" type="text" class="other" />
               </v-radio-group>
               </v-radio-group>
             </div>
             </div>
 
 
@@ -349,7 +347,7 @@ async function ECPaySubmit() {
             <v-text-field
             <v-text-field
               type="number"
               type="number"
               label="是否需要統編(可填寫統編號碼)"
               label="是否需要統編(可填寫統編號碼)"
-              v-model="userData.tax"
+              v-model="userData.taxID"
             ></v-text-field>
             ></v-text-field>
 
 
             <v-btn
             <v-btn

+ 19 - 20
frontend/src/views/YTViews.vue

@@ -91,16 +91,16 @@ let userData = reactive({
   url: "",
   url: "",
   area: "",
   area: "",
   language: "",
   language: "",
-  age: [],
-  object: "",
+  ages: [],
+  target: "",
   theme: "",
   theme: "",
-  tax: "",
+  taxID: "",
 });
 });
 
 
 // 其他選項
 // 其他選項
-let object = ref("");
+let target = ref("");
 let theme = ref("");
 let theme = ref("");
-let otherObject = ref("");
+let otherTarget = ref("");
 let otherTheme = ref("");
 let otherTheme = ref("");
 
 
 // 檢查必填欄位
 // 檢查必填欄位
@@ -111,7 +111,7 @@ const isSubmitDisabled = computed(() => {
     !userData.url ||
     !userData.url ||
     !userData.area ||
     !userData.area ||
     !userData.language ||
     !userData.language ||
-    !object.value ||
+    !target.value ||
     !theme.value
     !theme.value
   );
   );
 });
 });
@@ -124,10 +124,10 @@ async function ECPaySubmit() {
     chooseError.value = false;
     chooseError.value = false;
   }
   }
 
 
-  if (object.value === "其他" && otherObject.value !== "") {
-    userData.object = otherObject.value;
+  if (target.value === "其他" && otherTarget.value !== "") {
+    userData.target = otherTarget.value;
   } else {
   } else {
-    userData.object = object.value;
+    userData.target = target.value;
   }
   }
 
 
   if (theme.value === "其他" && otherTheme.value !== "") {
   if (theme.value === "其他" && otherTheme.value !== "") {
@@ -137,21 +137,20 @@ async function ECPaySubmit() {
   }
   }
 
 
   let data: YTViewsUserData = {
   let data: YTViewsUserData = {
+    item: `YT0.4-(${assignView.value})`,
+    amount: assignPrice.value,
     email: userData.email,
     email: userData.email,
     name: userData.name,
     name: userData.name,
     company: userData.company,
     company: userData.company,
     url: userData.url,
     url: userData.url,
     area: userData.area,
     area: userData.area,
     language: userData.language,
     language: userData.language,
-    age: userData.age.join("、"),
-    object: userData.object,
+    ages: userData.ages.join(","),
+    target: userData.target,
     theme: userData.theme,
     theme: userData.theme,
-    tax: userData.tax,
+    taxID: userData.taxID,
   };
   };
-
-  await mainStore.uploadPayment(data, assignView.value, assignPrice.value);
-
-  const originalHTML = await mainStore.ecpayPaymentHTML(assignPrice.value);
+  const originalHTML = await mainStore.YTViewsTestPayment(data);
   let formHTML = originalHTML?.replace(
   let formHTML = originalHTML?.replace(
     '<script type="text/javascript">document.getElementById("data_set").submit();</scr',
     '<script type="text/javascript">document.getElementById("data_set").submit();</scr',
     ""
     ""
@@ -306,7 +305,7 @@ async function ECPaySubmit() {
             <div class="checkbox ms-5">
             <div class="checkbox ms-5">
               <v-checkbox
               <v-checkbox
                 v-for="option in ageOptions"
                 v-for="option in ageOptions"
-                v-model="userData.age"
+                v-model="userData.ages"
                 :key="option.label"
                 :key="option.label"
                 :label="option.label"
                 :label="option.label"
                 :value="option.label"
                 :value="option.label"
@@ -318,7 +317,7 @@ async function ECPaySubmit() {
               目標對象區隔(興趣、習慣)<span class="text-red-darken-1">*</span>
               目標對象區隔(興趣、習慣)<span class="text-red-darken-1">*</span>
             </p>
             </p>
             <div class="ms-5">
             <div class="ms-5">
-              <v-radio-group v-model="object">
+              <v-radio-group v-model="target">
                 <v-radio
                 <v-radio
                   v-for="option in objectOptions"
                   v-for="option in objectOptions"
                   :key="option.label"
                   :key="option.label"
@@ -327,7 +326,7 @@ async function ECPaySubmit() {
                   color="primary"
                   color="primary"
                 ></v-radio>
                 ></v-radio>
                 <v-radio label="其他" value="其他" color="primary"></v-radio>
                 <v-radio label="其他" value="其他" color="primary"></v-radio>
-                <input v-model="otherObject" type="text" class="other" />
+                <input v-model="otherTarget" type="text" class="other" />
               </v-radio-group>
               </v-radio-group>
             </div>
             </div>
 
 
@@ -352,7 +351,7 @@ async function ECPaySubmit() {
             <v-text-field
             <v-text-field
               type="number"
               type="number"
               label="是否需要統編(可填寫統編號碼)"
               label="是否需要統編(可填寫統編號碼)"
-              v-model="userData.tax"
+              v-model="userData.taxID"
             ></v-text-field>
             ></v-text-field>
 
 
             <v-btn
             <v-btn

+ 1 - 1
frontend/src/views/main/Main.vue

@@ -238,7 +238,7 @@ const routeGuardAdmin = async (
     <v-footer class="pa-3" app>
     <v-footer class="pa-3" app>
       <v-spacer></v-spacer>
       <v-spacer></v-spacer>
       <div class="contact-icon">
       <div class="contact-icon">
-        <a href="https://discord.gg/C6Yk83ZA">
+        <a href="https://discord.gg/kHAEcu8T">
           <img src="@/assets/img/icon/discord.png" alt="discord" />
           <img src="@/assets/img/icon/discord.png" alt="discord" />
         </a>
         </a>
         <a href="mailto:service@choozmo.com">
         <a href="mailto:service@choozmo.com">

+ 0 - 58
frontend/src/views/main/admin/TestStylePreview.vue

@@ -1,58 +0,0 @@
-<script setup lang="ts">
-import { ref, reactive, watch, computed } from "vue";
-
-const inputFiles = ref();
-const preview_list:any = ref([]);
-const image_list:any = ref([]);
-
-
-function uploadImage() {
-  console.log(inputFiles.value[0])
-  const reader = new FileReader();
-  
-  reader.onload = function () {
-    preview_list.value.push(reader.result)
-  }
-  image_list.value.push(inputFiles.value[0]);
-  reader.readAsDataURL(inputFiles.value[0]);
-  console.log('finish uploadImage')
-  console.log(image_list.value[0].name)
-};
-</script>
-<template>
-  <div class="border p-2 mt-3">
-    <p>Style Preview</p>
-  </div>
-  
-  
-
-  <v-file-input
-    v-model="inputFiles"
-    accept="image/*, video/*"
-    :label="$t('fileInput')"
-    prepend-icon="add_photo_alternate"
-    @update:model-value="uploadImage"
-  ></v-file-input>
-  <v-sheet  class="canvas d-flex">
-    <template v-if="preview_list.length">
-      <div v-for="item in preview_list">
-        <img :src="item" class="img-fluid" />
-      </div>
-    </template>
-  </v-sheet> 
-  <v-sheet class="img-list"  color="grey-lighten-3">
-    <template v-if="image_list.length">
-      <div v-for="item in image_list">
-        <p class="mb-0">file name: {{ item.name }}</p>
-      </div>
-    </template>
-  </v-sheet>
-
-
-</template>
-<style lang="scss">
-.img-fluid {
-  
-}
-</style>
-