Day 0x 1D - odoo addons 永丰金流开发(Part 4 - Website template, data... more)

*** 模组资料夹 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 25 用 WebMock + VCR 来实作测试

>>:  【Day 27】C String - Practice 2

【资料库系统】 L3 SQL 入门

L3 SQL 入门 3-1 SQL 概述 资料操作语言(DML):提供资料修改删除等操作指令 完整性...

【PHP Telegram Bot】Day13 - 基础(2):数学运算与乱数

$x = 3 + 2 * 8 - 2 ** 3; echo $x; // 11 在程序里最常做的事...

0 day 安全笔记 第一章 1.4 crack 小实验

第一次发文,不知道会不会触犯版规,如有错误欢迎告知,谢谢。 OS:XP SP3 编译器: DEV C...

【Day30】挑战回顾 & 铁人练成心得分享

挑战最後一日的题目真的让我想了很久,倒底该放什麽元件来压轴才好?要写一个综合演练,把前面的元件都拿出...

Windows event 59 sidebyside invalid

工作上遇到了些问题纪录一下 因作业关系,修改了服务中的设定档 服务启动发生错误: 错误14001 使...