Sfoglia il codice sorgente

add ytviews payment

tomoya 1 anno fa
parent
commit
c6d847ecf2

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

@@ -1,6 +1,8 @@
 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.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(reputations.router, prefix="/reputations", tags=["reputations"])
 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"])

+ 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.utils import send_new_account_email
 
+import requests
+
 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:
-    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(
     *,
     MerchantID: Optional[str]=Form(None),
@@ -152,6 +80,7 @@ def ecpay_return(
     CustomField4: Optional[str]=Form(None),
     CheckMacValue: Optional[str]=Form(None),
 ) -> Any:
+  #送email
   print(f"\
 MerchantID: {MerchantID} \n\
 MerchantTradeNo: {MerchantTradeNo}\n\
@@ -172,3 +101,12 @@ 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

+ 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_article import article
 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 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.models.user import User  # noqa
 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.article import Article
 from app.models.serial_number import SerialNumber
 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 .video import Video
-from .enum import Membership, Progress
+from .enum import Membership, Progress, Epayment
 from .article import Article
 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)
 
 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)
   videos = relationship("Video", 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 .article import ArticleBase, ArticleCreate, ArticleUpdate
 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());
     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 {
+  item: string,
+  amount: number,
   email: string;
   name: string;
   company: string | null;
   url: string;
   area: string;
   language: string;
-  age: string | null;
-  object: string;
+  ages: string | null;
+  target: 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',
           component: () => import('@/views/YTViews.vue'),
         },
+        {
+          path: 'test-yt-views',
+          name: 'test-yt-views',
+          component: () => import('@/views/TestYTViews.vue'),
+        },
         {
           path: 'main',
           name: 'main',

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

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

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

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

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

@@ -238,7 +238,7 @@ const routeGuardAdmin = async (
     <v-footer class="pa-3" app>
       <v-spacer></v-spacer>
       <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" />
         </a>
         <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>
-