iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
永豐金融APIs

試著讀懂與串接永豐金融APIs系列 第 29

Day 0x 1D - odoo addons 永豐金流開發(Part 4 - Website template, data... more)

  • 分享至 

  • xImage
  •  

*** 模組資料夾 payment_sinopac 以 "/" 來代表此資料夾 ***

0x1 註冊支付方式

https://ithelp.ithome.com.tw/upload/images/20211009/20141805kJqy5WaLvz.png

  • 在模組裡新增 data 資料夾,並建立 payment_acquirer_data.xml,有注意到 data 標籤有個 noupdate="1"嗎? 代表只有安裝模組的時候才會寫入,升級模組不會
    /data/payment_acquirer_data.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data noupdate="1">
        <record id="payment_acquirer_sinopac" model="payment.acquirer">
            <field name="name">永豐金流支付</field>
            <field name="provider">sinopac</field>
            <field name="company_id" ref="base.main_company"/>
            <field name="view_template_id" ref="sinopac_form"/>
            <field name="shop_no">ShopNo</field>
            <field name="sinopac_a1">0123456789ABCDEF</field>
            <field name="sinopac_a2">0123456789ABCDEF</field>
            <field name="sinopac_b1">0123456789ABCDEF</field>
            <field name="sinopac_b2">0123456789ABCDEF</field>
        </record>
    </data>
</odoo>

0x2 支付方式的 model

  • 接著繼承 payment.acquirer,這裡都還沒處理好,目前還卡在建立訂單的流程,不過還是先附上 code
    /models/payment_acquirer.py
# -*- coding: utf-8 -*-
from werkzeug import urls
from datetime import datetime, timedelta
from odoo import models, fields, api
from odoo.http import request
from odoo.exceptions import UserError


class PaymentAcquirer(models.Model):
    _inherit = 'payment.acquirer'

    provider = fields.Selection(
        selection_add=[('sinopac', '永豐金流')],
        ondelete={'sinopac': 'set default'})

    shop_no = fields.Char(string='商店代號')
    sinopac_a1 = fields.Char(string='Key A1')
    sinopac_a2 = fields.Char(string='Key A2')
    sinopac_b1 = fields.Char(string='Key B1')
    sinopac_b2 = fields.Char(string='Hash Key B2')
    domain = fields.Char(string='對外連結域名')

    def sinopac_form_generate_values(self, values):
        sale_order_name = values['reference'].split('-', 1)[0]

        sdk = self.env['payment.transaction']._sinopac_get_sdk()
        today = datetime.now()

        # 取得 domain
        # base_url = base_url.replace('http:', 'https:', 1)
        base_url = self.sinopac_domain if self.sinopac_domain else self.env['ir.config_parameter'].sudo().get_param('web.base.url')

        amount = values["amount"].__round__()
        order_sinopac_data = {
            'order_no': sale_order_name,
            'prdt_name': 'Odoo 電商消費',
            'amount': amount,
            'pay_type': '',
        }
        data = {
            'ShopNo': sdk.shop_no,
            'OrderNo': sale_order_name,
            'Amount': f'{amount}00',
            'CurrencyID': 'TWD',
            'PrdtName': 'Odoo 電商消費',
            'ReturnURL': f'{base_url}/payment/sinopac/payment',
            'BackendURL': f'{base_url}/payment/sinopac/receive_msg',
            'PayType': 'A',
            'ATMParam': {
                'ExpireDate': (today + timedelta(days=7)).strftime('%Y%m%d')
            }
        }
        pay_type = request.session.get('payment', 'undefined')
        if pay_type == 'C':
            data.update({
                'PayType': pay_type,
                'CardParam': {
                    'AutoBilling': 'Y'
                }
            })
            order_sinopac_data.update({
                'pay_type': pay_type,
                'auto_billing': 'Y',
            })
        elif pay_type == 'A':
            after_7_day = today + timedelta(days=7)
            data.update({
                'PayType': pay_type,
                'ATMParam': {
                    'ExpireDate': after_7_day.strftime('%Y%m%d')
                }
            })
            order_sinopac_data.update({
                'pay_type': pay_type,
                'expire_date': after_7_day
            })
        else:
            raise UserError('不存在的永豐金流交易類型 %s' % pay_type)

        # TODO 這裡要測寫進 order.sinopac 後回傳畫面看是否正常
        # reply = sdk.call_api(
        #     url='https://apisbx.sinopac.com/funBIZ/QPay.WebAPI/api/Order',
        #     data=self.request_dataset('OrderCreate', data)
        # )

        return order_sinopac_data

    @api.model
    def _get_sinopac_urls(self, environment):
        if environment == 'enabled':
            return {
                # 正式環境
                'sinopac_form_url': 'http://localhost',
            }
        else:
            return {
                # 測試環境
                'sinopac_form_url': 'https://payment-stage.sinopac.com.tw/Cashier/AioCheckOut/V5',
            }

    def sinopac_get_form_action_url(self):
        return self._get_sinopac_urls(self.state)['sinopac_form_url']
  • 接著繼承 payment.transaction,註冊方法,這裡還在理解怎麼丟(看著Paypal跟綠界的模組混亂中)
    /models/payment_transaction.py
import logging
import pprint

from odoo import api, fields, models, _
from odoo.addons.payment.models.payment_acquirer import ValidationError
from odoo.addons.payment_sinopac.controller.sinopac_sdk import SinopacSDK

_logger = logging.getLogger(__name__)


class TxSinopac(models.Model):
    _inherit = 'payment.transaction'

    sinopac_txn_type = fields.Char('Transaction type')

    @api.model
    def _sinopac_get_sdk(self):
        sinopac = self.env['payment.acquirer'].search([('provider', '=', 'sinopac')], limit=1)

        return SinopacSDK(
            shop_no=sinopac.shop_no,
            key_a1=sinopac.sinopac_a1,
            key_a2=sinopac.sinopac_a2,
            key_b1=sinopac.sinopac_b1,
            key_b2=sinopac.sinopac_b2
        )

    # --------------------------------------------------
    # FORM RELATED METHODS
    # --------------------------------------------------

    @api.model
    def _sinopac_form_get_tx_from_data(self, data):
        # TODO 取得傳入參數
        return {}

    def _sinopac_form_get_invalid_parameters(self, data):
        # TODO 取得無效參數
        return

    def _sinopac_form_validate(self, data):
        # TODO 表單驗證
        return True

  • 增加 sale.order的欄位,把 Day 0x1A 的 order.sinopac order_id建立反向關聯
    /models/sale_order.py
# -*- coding: utf-8 -*-

from odoo import models, fields, api


class SaleOrder(models.Model):
    _inherit = 'sale.order'

    order_sinopac_ids = fields.One2many(
        'order.sinopac',
        'order_id',
        string='永豐金流訂單'
    )
  • 別忘了在 __init__.py 增加關聯
    /models/__init__.py
from . import payment_acquirer
from . import payment_transaction
from . import sale_order

0x3 增加 view & template

  • 接著到 /views 建立 payment_templates.xml,基本上這個檔案都沒什麼動,綠界跟 Paypal 有動的也只有 template id,要注意這裡的也是 noupdate="1"
    /views/payment_templates.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data noupdate="1">
        <template id="sinopac_form">
            <div>
                <input type="hidden" name="data_set" t-att-data-action-url="tx_url" data-remove-me=""/>
                <t t-foreach="parameters" t-as="parameter">
                    <input type="hidden" t-att-name="parameter" t-att-value="parameter_value" />
                </t>
            </div>
        </template>
    </data>
</odoo>
  • 建立 payment_views.xml,這個是在支付方式裡面的 form 顯示,畫面如下
    https://ithelp.ithome.com.tw/upload/images/20211009/20141805UFwUnc81To.png
    /views/payment_views.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <data>
        <record id="payment_acquirer_sinopac" model="ir.ui.view">
            <field name="name">payment_acquirer_sinopac</field>
            <field name="model">payment.acquirer</field>
            <field name="inherit_id" ref="payment.acquirer_form"/>
            <field name="arch" type="xml">
                <xpath expr="//notebook/page[1]" position="inside">
                    <group attrs="{'invisible': [('provider', '!=', 'sinopac')]}">
                        <group name="group_interface_setting" string="基本設定">
                            <field name="shop_no"/>
                            <field name="sinopac_a1"/>
                            <field name="sinopac_a2"/>
                            <field name="sinopac_b1"/>
                            <field name="sinopac_b2"/>
                        </group>
                        <group name="group_domain_setting" string="網域">
                            <field name="domain"/>
                        </group>
                    </group>
                </xpath>
            </field>
        </record>

        <record id="inherit_view_order_form_sinopac" model="ir.ui.view">
            <field name="name">inherit_view_order_form_sinopac</field>
            <field name="model">sale.order</field>
            <field name="inherit_id" ref="sale.view_order_form"/>
            <field name="arch" type="xml">
                <xpath expr="//notebook" position="inside">
                    <page name="sinopac_order_line" string="永豐金流訂單資訊" groups="payment_sinopac.group_manager">
                        <field name="order_sinopac_ids">
                            <tree create="false" delete="false" editable="top">
                                <field name="prdt_name" readonly="1"/>
                                <field name="ts_no" readonly="1"/>
                                <field name="ts_date" readonly="1"/>
                                <field name="pay_date" readonly="1"/>
                                <field name="amount" readonly="1"/>
                                <field name="description" readonly="1"/>
                                <field name="pay_status" readonly="1"/>
                                <field name="pay_type" readonly="1"/>
                            </tree>
                        </field>
                    </page>
                </xpath>
            </field>
        </record>
    </data>
</odoo>
  • 增加網站訂單付款時的選項
    https://ithelp.ithome.com.tw/upload/images/20211009/20141805fTBf7y54Gu.png
    /views/payment_order_templates.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="assets_frontend" inherit_id="web.assets_common" name="assets_frontend_payment_sinopac">
        <xpath expr="." position="inside">
            <script type="text/javascript" src="/payment_sinopac/static/src/js/selection.js"/>
        </xpath>
    </template>

    <template id="sinopac_payment_type" name="sinopac payment type" inherit_id="payment.payment_tokens_list">
        <xpath expr="//div[hasclass('text-muted')]" position="before">
            <t t-if="acq.provider =='sinopac'">
                <div class="form-group">
                    <select class="form-control" id="sinopac_payment_method" name="sinopac_payment_method">
                        <option value="A">ATM 轉帳</option>
                        <option value="C">信用卡</option>
                    </select>
                </div>
            </t>
        </xpath>
    </template>
</odoo>
  • 有注意到剛剛的 xml 有加入一個 JS 嗎? 這個JS主要是針對表單送出前的動作,讓我們建立訂單時有些資訊沒有傳入的先傳進伺服器暫存,這就是昨天提到的 hold_payment_type 函數使用時機
    /static/src/js/selection.js
'use strict';
odoo.define('payment_sinopac.create_order', function (require) {
    var ajax = require('web.ajax');

    async function chose_payment_type(payment_type) {
        let response = await ajax.jsonRpc(
            '/payment/sinopac/hold_payment_type',
            'call',
            {
            payment: payment_type
        });

        console.log(response);
        return true;
    }

    $(document).ready(function () {
        let payment_list = document.querySelectorAll('div[class*="o_payment_acquirer_select"]'),
            hold_payment = false;

        if (payment_list) {
            payment_list.forEach(item => {
                let provider = item.querySelector('input[name="pm_id"]'),
                    payment = provider.dataset.provider;

                if (payment === 'sinopac') {
                    item.onclick = async function (event) {
                        let payment_method = document.querySelector('#sinopac_payment_method');

                        if (payment_method) {
                            hold_payment = await chose_payment_type(payment_method.value);
                        }
                    }
                }
            });
        }

        console.log('sinopac js init!');
    });
});
  • xml 都處理好後,別忘了到 __manifest__.py 註冊 xml 位置,這裡直接提供 data 的陣列給各位
    /__manifest__.py
"data": [
    'security/payment_sinopac_access_rule.xml',
    'security/ir.model.access.csv',
    'views/payment_views.xml',
    'views/payment_templates.xml',
    'views/payment_order_templates.xml',
    'views/payment_order_views.xml',
    'data/payment_acquirer_data.xml',
],

0x4 額外提醒

  • 記得重新安裝模組,然後到設定 -> 使用者 -> Admin 設定模組權限才看的到
    https://ithelp.ithome.com.tw/upload/images/20211009/20141805th7Gh5n39b.png
    https://ithelp.ithome.com.tw/upload/images/20211009/20141805QxZkIFzCBk.png
    https://ithelp.ithome.com.tw/upload/images/20211009/20141805MMV49e1XkW.png

  • 記得把支付方式永豐金流支付設定成測試模式,並輸入自己的商店代號key
    https://ithelp.ithome.com.tw/upload/images/20211009/20141805H7qM1Wsx0k.png

0x5 今日結語

odoo addons 目前就是開發到這樣,odoo 電商付款那邊還要看一下底層怎麼跑,建立訂單有開api測試過是正常的,明天就是整個鐵人賽的總結了,不會有程式,簡單回顧一下這30天的心路歷程。


上一篇
Day 0x 1C - odoo addons 永豐金流開發(Part 3 - controller)
下一篇
Day 0x1E - 結語
系列文
試著讀懂與串接永豐金融APIs30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言