## 商家分析 (前端) [前端操作說明](https://hackmd.io/@shaomin/jba) ## 商家分析 (後端) ### 權限 > user 的 group 若包含 > - 'DS'則可以看到全部使用的資料。 (內部使用) > - 'CUS_DS' 則可以看到自己及shop_user的資料。 (客戶使用) ### 統計資料表和執行及排程 | model name | Desc. | Update time| file name| | -------- | -------- | -------- | -------- | | CommodityStatistic| 商品銷售狀況統計 | 每日清晨4:50 | update_statistic_commodity.py| | OrderStatistic |訂單成立數量、訂單成立金額、訂單付款金額統計| 每日清晨4:53 |update_statistic_order.py| | LiveStatistic |直播留言觀看人次統計| 每日清晨4:56 |update_statistic_comment.py| | CustomerStatistic | 紀錄每日每位消費者是否為新客、距離上次購買天數是幾天,以及當日消費金額、數量。| 每日清晨4:58 |update_statistic_customer.py| | CustomerStatisticExtra | 因為一開始沒有記錄到棄單狀況,故使用這個資料表補上。| 每日清晨4:58 |update_statistic_customer.py| | CustomerStatisticSession| 紀錄每場次每位消費者是否為新客、距離上次購買天數是幾天,以及該場次消費金額、數量。| 每日清晨4:40 | update_statistic_customer_session.py| | StreamStatistic | 使用FB Graph API `read_insights` 於直播結束後讀取影片分析資訊。 model有註解說明各欄位用意。| 每日清晨4:45 |update_statistic_stream.py| | CommodityPredictCategory | 依照模型儲存預測結果,若有人工修改也會存入。| 每日清晨6:51 | predict_commodity_category.py| ### 開發注意事項 #### 前端 其他地方沒使用過的JS套件`tablesorter`, `echarts` > [Apache ECharts](https://echarts.apache.org/zh/index.html) > 圓餅圖、柱狀圖,後端算好資料,依照格式套入這個套件呈現出來。 ```javascript= var CloudChart = echarts.init(document.getElementById('plot_word_cloud')); // init 一個echarts可以使用的畫板 var data = []; {% if word_cloud %} data = {{ word_cloud|safe }}; //接收後端資料 {% endif %} var maskImage = new Image(); var option = { tooltip: {}, series: [ { type: 'wordCloud', // 使用文字雲 sizeRange: [12, 150], // ... 省略 data: data // 將資料放入套件 }] }; CloudChart.setOption(option); // 將欲呈現的圖形參數呈現在畫板上。 ``` > > [tablesorter](https://mottie.github.io/tablesorter/docs/#Demo) > 該處為了減少查詢壓力,並不會在表格點選排序時重新查詢,僅會透過前端套件,針對現有資料排序。 > `filterChange` 這個函示會用來控制顯示哪些東西。原則上是一個功能會用一個 css name 去包住全部。 所以filter改變的時候,會全部隱藏,再調整顯示對應內容。 > EX. `search_user`: 這個是用來顯示搜尋使用者的功能。 ```javascript= function filterChange() { let option_dates = document.getElementById('search_dates'); let option_keyword = document.getElementById('search_commodity_keyword'); let option_sessions = document.getElementById('search_sessions'); option_dates.setAttribute('hidden', 'hidden'); option_keyword.setAttribute('hidden', 'hidden'); option_sessions.setAttribute('hidden', 'hidden'); $('.commodity-display').hide(); $('#commodity-table').hide(); $('#plot_word_cloud').hide(); $('.live-display').hide(); $('.category-display').hide(); $('.customer-display').hide(); $('.store-recommendation-display').hide(); $('.fb-stream-display').hide(); $('.order-amount-display').hide(); $('.business-report-display').hide(); $('.search_user').hide(); $('#search_commodity_keyword').removeAttr('disabled'); $('#search_sessions').removeAttr('disabled'); on_time_period_changed(); if ($('#filter').val() == 'commodity') { $('.commodity-display').show(); $('.search_user').show(); option_dates.removeAttribute('hidden'); option_keyword.removeAttribute('hidden'); if ($("select[name='search']").val() == 'sessions'){ document.querySelector('#search-select').value='dates'; } if($('#commodity_display').val() == 'word_cloud') { $('#plot_word_cloud').show(); } else { $('#commodity-table').show(); } } else if ($('#filter').val() == 'live') { $('.live-display').show(); document.querySelector('#search-select').value='dates'; option_dates.removeAttribute('hidden'); } else if ($('#filter').val() == 'category') { $('.category-display').show(); $('.search_user').show(); document.querySelector('#search-select').value='dates'; option_dates.removeAttribute('hidden'); } else if ($('#filter').val() == 'customer') { $('.customer-display').show(); $('.search_user').show(); if ($("select[name='search']").val() == 'commodity_keyword'){ document.querySelector('#search-select').value='dates'; } option_dates.removeAttribute('hidden'); option_sessions.removeAttribute('hidden'); } else if ($('#filter').val() == 'live_store_recommendation') { $('.store-recommendation-display').show(); document.querySelector('#search-select').value='commodity_keyword'; option_keyword.removeAttribute('hidden'); } else if ($('#filter').val() == 'fb_stream') { $('.fb-stream-display').show(); $('.search_user').show(); document.querySelector('#search-select').value='dates'; option_dates.removeAttribute('hidden'); } else if ($('#filter').val() == 'order_amount') { $('.order-amount-display').show(); $('.search_user').show(); document.querySelector('#search-select').value='dates'; option_dates.removeAttribute('hidden'); } else if ($('#filter').val() == 'business_report') { $('.business-report-display').show(); $('.search_user').show(); document.querySelector('#search-select').value='dates'; option_dates.removeAttribute('hidden'); } change_datepicker(); selectSearch(); } ``` #### 後端 > 針對`search_table`去排查。 ![](https://hackmd.io/_uploads/H1ougeWYn.png) ## ES 相關設定及使用 * 安裝套件 [[安裝篇] Elasticsearch Logstash Kibana](https://hackmd.io/@shaomin/ELK_install) * 同步資料及管理設定 [[使用/管理篇] Elasticsearch Logstash Kibana](https://hackmd.io/@shaomin/ELK_usage) * ES 語法 [Elasticsearch 語法](https://hackmd.io/@shaomin/es_query) ##### STEP 1. 透過安裝套件教學,逐步安裝 ES 及 Logstash 的套件。 2. 確認服務預作正常後,填寫設定檔,並開始透過Logstash將資料倒入ES。 3. 透過 ES 語法查看確認 ES 內的資料及服務狀況。 ## 粉絲小幫手 ### 粉絲訂單資料統計排程 >每秒執行`update_user_transaction_summary_aggregate.py` 去從 `Api_UserTransactionChangeLog` 撈取訂單變化量加總存入`Api_UserTransactionSummaryAggregate` >> 執行的時候會透過 `Api_config` 去紀錄執行到Log的哪一筆資料,以及最後更新時間,其中 utsa update status 若取消勾選則停止同步。 >> ![](https://hackmd.io/_uploads/Sy4BGlWKn.png) `Api_UserTransactionChangeLog` 會在 api_order 執行 save 的時候做一個變化量的計算。下圖函示寫於`api/utils.py`中。 ![](https://hackmd.io/_uploads/BkZqXgZY3.png) `Api_UserTransactionSummaryAggregate`會在粉絲小幫手,如下圖方式顯示。 ![](https://hackmd.io/_uploads/SJWtMgbYh.png) ![](https://hackmd.io/_uploads/HkWpGxbKh.png) ## 東南亞 ### 購物車一頁式結單 :::warning > 設定方式 > - > - PC 開啟 <font color="#f00">`enable_one_page_checkout`</font> > > 設計架構 > - > - 一個連結(hash_key)代表一次付款流程。結帳之後該連結轉為追蹤訂單狀態(訂單處理中 or 歷史訂單)。 > > ```python > class OnePageCheckout(models.Model): > user = models.ForeignKey(User) > from_id = models.CharField(max_length=64, db_index=True) > from_name = models.CharField(max_length=128, null=True, blank=True) > comment_user = models.ForeignKey(CommentUser, on_delete=models.SET_NULL, null=True, blank=True) > hash_key = models.CharField(max_length=16, null=True, blank=True, unique=True, db_index=True) > orders = models.ManyToManyField('Order', related_name='order_one_pages', blank=True) > bill = models.ForeignKey('pay.Bill', on_delete=models.SET_NULL, null=True, blank=True) > session = models.ForeignKey(Session, on_delete=models.SET_NULL, null=True, blank=True) > created_time = models.DateTimeField(auto_now_add=True) > ``` > > - 連結中的 hash_key 主要是用來查找 from_id 以及是否有 bill,若沒有bill,這個hash_key可以一直加入新訂單。 > - 訂單建立時,若有找到可用的 hash_key,便不會再新增,直到該hash_key 被賦予 bill。 > > 得標/結單... 通知連結 > - > 1. 開啟一頁式結單後,基本上發送的結單連結都會是一頁式。 > 邏輯為 `https://{DOMAIN_NAME}/op/{USER_ID}/{HASH_KEY}` > 其中 op 代表 `one page` > ex. https://sea.jambolive.tv/op/2853/86763d49f6/ > 2. 一頁式結單連結的通知內容是加總的,並不是發送單次order資訊,而是所有未結單 order 的資訊。 > > 3. 取得或建立連結方式 > - 以下函示可透過order或from_id取得連結。若資料查不到既有可用連結,則會建立新的。 > File: <font color="Blue"> **`comment/utils.py`** </font> > ```python > def get_one_page_checkout_url(pc, order=None, from_id=None): > ``` > - bill 可以透過models中如下的函示取得。 > `checked` => 為真時,不會重抓PC。 > `origin_url` => 若設定沒開,或是bill沒有對應的link,則回傳購物車或訂單處理中連結。 > ```python > def get_opc_url(self, checked=False, origin_url=None): > ``` > > 登入邏輯 > - > - 若為一頁式結單,會將消費者的session中`is_one_page`標示為真,並轉往一頁式結單,不進行後續登入比對。 > File: <font color="Blue"> **`pay/decorators.py`** </font> > > ```python > def facebook_login(view): > #...省略 > # One Page Checkout > is_one_page = request.session.get('is_one_page', False) > one_page_do_pay = request.session.get('one_page_do_pay', False) > if is_one_page and ('/op/' in path_info): > return view(request, *args, **kwargs) > elif is_one_page and one_page_do_pay: > qd = request.GET.copy() > if user_id: > return redirect(reverse('one_page_checkout', kwargs={'user_id': user_id, 'hash_key': request.session.get('one_page_key')}) + '?' + qd.urlencode()) > else: > return redirect(reverse('opc_redirect', kwargs={'hash_key': > request.session.get('one_page_key')}) + '?' + qd.urlencode()) > elif is_one_page and not from_name and not from_id and '/checkout/' not in path_info and ('/shop/' in path_info or '/store/' in path_info): > return view(request, *args, **kwargs) > else: > clear_OPC_session(request) > ``` > 需注意事項 > - > - <font color="green">```一頁式購物車的跳轉```</font> 目前一頁式連結進入後,會在session紀錄,若有跳轉的連結,將session清掉了,便會無法回到需要登入購物車。 Ex. 例如購物車前往商城,商城再點選購物車。 > - <font color="green">```psid的功能使用問題```</font> 因為一頁式結單主要是psid免登入,所以有些asid在用的功能可能會有衝突。 Ex. 專屬折扣,原先只能在asid上設置。 ::: ### 購物車合併結單 :::warning > 設定方式 > - > - PC 開啟 <font color="#f00">`enable_checkout_merge_bill`</font> > > 設計架構 > - > - 進入購物車後,會抓取上次已成立未確認付款的付款單。並在前後端做金額及折扣的計算。 > - 其中扣除前次已付款金額,並告知當前應付款金額,為該功能主要賣點。 > ![](https://hackmd.io/_uploads/Hk2PlSGY3.png =250x120) > > - 前端會同時列出,已成立跟未成立的訂單。上方為未成立,下方為已成立。 > ![](https://hackmd.io/_uploads/rJx1AyBzY2.png =500x380) > > 開發邏輯 > - > - 若開啟該功能,會去抓取符合條件的付款單。 > ```python > could_merge_bills = models.Bill.objects.filter(payment_method__in=payment_method_supported_for_checkout_bill_merge,user_id=user_id,from_id=from_id).filter(status=models.Bill.Status.ESTABLISHED, is_paid=False) > ``` > - 若有可以合併的付款單,會用變數```could_merge_bill```, ```could_merge_bill_orders```, ```could_merge_bill_orders_total```作為參數去計算。 > 1. could_merge_bill: 紀錄最新一筆可以合併的付款單。 > 2. could_merge_bill_orders: 紀錄可合併付款單的訂單資料。 > 3. could_merge_bill_orders_total: 訂單金額總和,不包含運費、手續費...等 > > 需注意事項 > - > - <font color="green">```僅支援轉帳付款方式使用```</font> 因為泰國僅能透過轉帳方式去驗證實際付款金額,驗證後於前端做計算,告知建議的應付款金額。 > - <font color="green">```紅利功能未開放```</font> 因為合併後計算折扣即實際付款金額尚未穩定,故紅利部分先暫緩。 > - <font color="green">``````</font> 因為合併後計算折扣即實際付款金額尚未穩定,故紅利部分先暫緩。 ::: ## LINE ### 購物 :::info > <font color="Green">**商品倒入LINE購物可被搜尋**</font>[color=#f24f9e] > > 操作流程 > - > 1. 賣家在商品小幫手點選```LINE上架商品```,便會將商品的```line_on_shelf_time``` 更新。 > 2. LINE 定期呼叫 API 取得資料後,便可以出現在LINE購物APP上。 > > - 下方為我們提供給LINE的API網址。 > File: <font color="Blue"> **`api/lineshopping.py`** </font> > https://jambolive.tv/api/lineshopping/product_full/{USER_ID}/ https://jambolive.tv/api/lineshopping/product_partial/{USER_ID}/ https://jambolive.tv/api/lineshopping/cate_full/{USER_ID}/ > > - 文件我找不到了QQ 檔名如下 ![](https://hackmd.io/_uploads/BkJqErztn.png) > > 需注意事項 > - > - 若未來使用量大,應當先處理好資料,存成檔案,直接回傳給LINE。 ::: ### 直播 - GLC :::info > 設定方式 > - > table: <font color="Blue"> **`api_lineaccount`** </font> > ![](https://hackmd.io/_uploads/HkbLb9Gt2.png =300x250) > 1. 基本LINE帳號資料。 > 2. Live api version: 選擇 GLC。 (JP為日本團隊開發,GLC為韓國團隊開發) > 3. Live product display status: 卡牌顯示方式。 > 4. Post back mode: 訂單及付款單建立後回拋給LINE的邏輯。 > 訂單建立時,會回傳資料給LINE,顯式於LINE購物車。 > 付款單建立時,會回傳資料給LINE,用於回饋點數計算。 > | 回拋方式 | 付款單 | 訂單 | | -------- | -------- | -------- | | All Bill, LINE Order | 全回傳 | 僅LINE帳號建立訂單回傳 | | All Bill, All Order | 全回傳 | 全回傳 | | LINE Bill, LINE Order| 僅LINE帳號建立付款單回傳 | 僅LINE帳號建立訂單回傳 | | LINE Bill, All Order | 僅LINE帳號建立付款單回傳 | 全回傳 | > > 操作流程 > - > ![](https://hackmd.io/_uploads/SyjRxUQt3.png) > > 0. 請先確保OA帳號有申請 GLC 直播功能。 > 1. 直播小幫手點選LINE直播。其中`直播首圖`、`直播標題`、`開播訊息` 為必填。 > 2. 點選建立後,進入[LINE GLC 後台](https://global-live-commerce.line.biz/)取得串流金鑰、串流網址。 > ![](https://hackmd.io/_uploads/HkfcW8Qt3.png) > 3. 透過直播串流工具,傳送直播畫面到GLC後台。 > 4. 確定GLC後台有影像,可以點選開始直播,或點進入JamboLive直播小幫手點選`LINE直播設定`中的開始直播。 > ![](https://hackmd.io/_uploads/SJPUGLmK2.png) > > 開發事項 > - > #### 啟用直播的API > Path: <font color="Blue"> **`pay/linebuy.py`** </font> => `def live_settings(live, line_account, action=False):` >> >> ![](https://hackmd.io/_uploads/Hk98SUQF2.png) >> >> - 基本上沿用先前開發JP版本直播的功能,僅需注意若`line_account.live_api_version == GLC` 會有不同的API server,並且參數也會些微不同。 > > #### 啟用直播的參數 > Path: <font color="Blue"> **`api/views.py`** </font> => `def line_live(request):` >> >> ![](https://hackmd.io/_uploads/rkjs4ImYh.png) >> - BC=order by chat(關鍵字收單)、PL=Product list(商品列表收單) >> - PL mode 不用送關鍵字。 > > #### LINE ecid 資料交流 >> LINE ecid 有點類似 psid。是在直播喊單的時候,LINE傳訂單給我們的識別碼。 >> - 在LINE點選進入購物車的時候,連結會帶上ecid,進入購物車LINE帳號登入後,可以用 LINE uid 綁定 ecid,將訂單歸戶。 >> - ecid 有時效且同用戶每場直播都不同,所以我們並沒有儲存。 >> ``` >> https://jambolive.tv/pay/checkout/12305/line/?ecid=800ffeae8895c32f0aa62ab21fe5fc5c8440bc714b34b26ec3e678acac48bc89 >> ``` > #### 手動回拋訂單給LINE > Path: <font color="Blue"> **`pay/tasks.py`** </font> => `def linePostBackOrder(bill_id, ecid, kind):` >> ```=python >> bs = pay_models.Bill.objects.filter(user_id=13665).exclude(Q(status=pay_models.Bill.Status.NOT_ESTABLISHED) | Q(status=pay_models.Bill.Status.TRASHED)).order_by('-id') >> for b in bs: >> linePostBackOrder(b.id, 'jambolive0line0order0fb0checkout', 'custom') > > 文件 > - > [[LINE ECAC][LINE TW Shopping] GLC LIVE Commerce Product data feed API Specification (zh-tw) ver. 1.5 .pdf](https://drive.google.com/file/d/1aUKTza9nxsrwajrBb5hxiPT_WSWtW8ea/view?usp=sharing) ::: ### LINE 通知 * 未結單通知包含按鈕(內文160字元)。 * 一般通知(內文5000字元)。 #### Refference 1. [LINE OA 後台](https://manager.line.biz/) 2. [LINE GLC 後台](https://global-live-commerce.line.biz/) 3. [LINE Developers 後台](https://developers.line.biz/console/) ## Fasney 後支付 > 設定方式 > - 1. 於 admin後台api/personalconfig 中搜尋 fasney pay account,並選取或建立帳號 。 2. 於 admin後台api/personalconfig 中搜尋 After pay percentage,用於建立後支付手續費。 3. 若需開啟後支付分期 1. 於admin後台 api/personalconfig 中搜尋Enable after pay installment,並將其勾選。 2. 於 admin後台api/personalconfig 中搜尋 After pay installment list,輸入與合作廠商簽約之分期期數 (EX. 簽約3,6期,則輸入 3,6)。 3. 於 admin後台api/personalconfig 中搜尋 After pay installment percentage 3/6/12/18/24,輸入後支付分期之手續費。 4. 代後台設定完成後,商家便可於商家設定中,將付款設定勾選「後支付」啟用。 > 開發注意事項 > - - 交易流程跟其他第三方支付一樣,唯一差異是消費者第一次使用Fasney後支付會需要驗證個人資料,驗證個人資料這段期間,付款單會屬於未成立狀態,等待付款完成的API送過來,該筆付款單的訂單也沒有被其他付款方式付款,才會轉成成立。 ![](https://hackmd.io/_uploads/rk-phYQF2.png) - 有設定`ip_white_list`去限制可以回傳交易狀況給我們的IP. - 可提供測試環境,給他們窗口信箱,會寄測試APP的連結。 ![](https://hackmd.io/_uploads/HkAuaY7Kh.png) #### refference 1. [官網](https://www.fasney.com/) 2. [開發文件](https://docs.google.com/document/d/1mkA50JUpwIMbtz2m6fTRTpsVu2LpteDf/edit?usp=sharing&ouid=101567832984958542928&rtpof=true&sd=true) ## FB 權限申請 :::success ![](https://hackmd.io/_uploads/H1oICYXK2.png) 1. 上線中的APP必須提供測試帳號,或使用測試環境送審。 2. 登入過程中必須跳出可以讓使用者勾選權限的畫面。(每次要送審時,都要確保測試帳號的token清空權限,讓審核人員登入平台時可以出現取得權限畫面。 3. FB開發人員中雖然有提供的測試帳號,但那個測試環境是封閉的,而且我這次一直沒辦法在那個封閉環境中建立粉絲專頁。雖然我有在送審文案中提出這點,也請審核人員用我們提供的測試用戶檢查登入機制,再用可以獲取粉專分析資料的審核帳號登入。 綜合以上原因,最後是我自己在FB正式環境中新建一個全新的帳號,並且將粉專應該要有的分析資料也都準備好,再將這個帳好設定為開發人員,讓審核人員用這個帳號登入平台,待審核通過後再更改密碼及移除測試人員。 送審範例申請了很多權限,後續可以參考這個範例取得權限。 ::: ![](https://hackmd.io/_uploads/BkyP7grtn.png) #### refference 1. [送審範例](https://xeroneit.net/blog/xerochat-how-to-submit-app-for-review) 2. [應用程式審查說明文件](https://developers.facebook.com/docs/app-review/introduction) ## 政大資料匯出 ### 連線方式 [政大NAS 連結](https://uoclabnas720.synology.me:5001/#/signin) - 帳號: uoclab_jambo - 密碼: Uoclab85024jambo?? ### 腳本 ``` 53 21 * * * /bin/sh /home/web/projects/scpbackend/django/scripts/nccu/cronjob_export.sh ``` - cronjob_export.sh => 因為留言cache存兩天,所以兩天匯出一次前兩天得留言即可,用判斷`/tmp/altday` 這個存不存在去判斷要不要匯出留言。 - nccu_export_comments.py => 匯出留言資料,存入/home/web/nccu_comment - nccu_export_comments_email.py => 匯出留言資料,寄送mail - nccu_export_commodity.py => 匯出商品資料 - nccu_export_order_info.py => 匯出訂單資料 ## Kbank ### 驗證流程 #### i. 從圖片中解析QRCode. :::warning Here is our read slip qrcode solution now. We use this one for frontend scan. 1. https://github.com/nimiq/qr-scanner And if not found, we will do it by opencv package on backend. 2. https://note.nkmk.me/en/python-opencv-qrcode/ If above function can't get any result, then we call following link. 3. https://zxing.org/w/decode.jspx ::: #### ii. Kbank 驗證 Path: <font color="Blue"> **`pay/kbank.py`** </font> ::: warning 1. function ```do_verify``` => - 取得欲查詢的付款單及其對應的所有匯款紀錄。 - 驗證當前帶入的匯款編碼(QR Code 解碼內容)。 - 取得匯款單資料後,檢驗`時間`, `銀行代碼`, `重複上傳`, `金額` - 若有啟用發送通知,執行發送。 2. function ```readQR``` => - 執行上述解析QRCode的第二、第三步驟。 3. function ``re_verify``` => - 購物車前端抓不到匯款單編碼時執行。 - 出貨小幫手,賣家手動執行。 :::