Mixoo

Hi, 好久不见


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 搜索

javascript 高级程序设计 第四版 学习笔记 1

发表于 2021-07-23 | 分类于 javascript

javascript的简短历史回顾

  • 1995年Netscape公司为了解决前端业务逻辑验证必须与服务器通信而导致用户等待的问题,开发出了javascript这种脚本语言。
  • 1998年ISO和IEC将ECMAScript标准作为正式的脚本语言标准,1.0。
  • ECMAScript 3.0标志着Javascript作为一门真正的编程语言的时代到来。
  • 2009年 ECMAScript 5(ECMAScript 3.1)发布,新功能包括原生的解析和序列化 JSON 数据的 JSON 对象、方便继承和高级属性定义的方法,以及新的增强 ECMAScript 引擎解释和执行代码能力的严格模式。
  • 2015年发布 ES6,包含了大概这个规范有史以来最重要的一批增强特性。ES6 正式支持了类、模块、迭代器、生成器、箭头函数、期约、反射、代理和众多新的数据类型。
  • 2016年ES7发布,这次修订只包含少量语法层面的增强,如 Array.prototype.includes 和指数操作符。
  • 2017年ES8发布,主要增加了异步函数(async/ await)、SharedArrayBuffer 及 Atomics API,以及 Object.values()/Object.entries()/Object.getOwnPropertyDescriptors()和字符串填充方法,另外明确支持对象字面量最后的逗号。
  • 2018年ES9发布,修订包括异步迭代、剩余和扩展属性、一组新的正则表达式特性、Promise finally(),以及模板字面量修订。
  • 2019年ES10发布,增加了 Array.prototype. flat()/flatMap()、String.prototype.trimStart()/trimEnd()、Object.fromEntries()方法,以及 Symbol.prototype.description 属性,明确定义了 Function.prototype.toString()的返回值并固定了 Array.prototype.sort()的顺序。另外,这次修订解决了与 JSON 字符串兼容的问题,并定义了 catch 子句的可选绑定。

在HTML中使用script

  • 在“,否则会报错。
  • 外部代码放在src属性中,且外部文件的优先级高于行内代码。
  • 对于不支持javascript的浏览器,使用

基础语法

  • 严格区分大小写
  • 标识符以 $,_或字母开头,不能是关键字
  • 单行注释// 多行注释 / /
  • ES5新增了严格模式(restrict mode)
  • 语句以;结尾

关键字与保留字

  • break
  • do
  • in
  • typeof
  • case
  • else
  • instanceof
  • var
  • catch
  • export
  • new
  • void
  • class
  • extends
  • return
  • while
  • const
  • finally
  • super
  • with
  • continue
  • for
  • switch
  • yield
  • debugger
  • function
  • this
  • default
  • if
  • throw
  • delete
  • import
  • try

var、let和const

var

var是ES6之前用来定义变量的关键字,var的问题有两个,一是使用var声明的变量会成为包含它的函数的局部变量,二是使用var定义的变量存在声明提升的问题。

1
2
3
4
5
function test() {
var message = "Hi ,XX";
}
test();
console.log(message);

变量message声明在函数test内部,因此是test的内的局部变量,后面的cosole自然就访问不到该变量,因此输出的结果为ReferenceError: message is not defined。

1
2
3
4
5
function test() {
message = "Hi ,XX";
}
test();
console.log(message);

声明时,去掉var关键字,该变量会成为全局变量,最后输出的结果是: Hi, XX。

1
2
3
4
5
function test() {
console.log(message);
var message = "Hi ,XX";
}
test();

var变量存在的另外一个问题就是声明提升,这里的例子中,message虽然是在console.log后面声明的,但是会被提升到代码块的最顶部,因此,这里输出的结果是undefined. 相当于下面的代码:

1
2
3
4
5
6
function test() {
var message;
console.log(message);
var message = "Hi ,XX";
}
test();

另外,var声明的变量是可以重复声明的,即下面的代码是合法的:

1
2
3
4
5
6

var message = "Hi, XX";
var message = "Hi, HX";
var message = "Hi, QD";

conosle.log(message);

let

let和const是ES6中引入的,与var不同的是let的作用域仅限于当前代码块,且不存在声明提升的现象,也不允许重复声明。

1
2
3
4
if (true){
let message = "Hi ,XX";
}
console.log(message);

因为message使用let声明,其作用域仅限于if代码块中,因此后面的console代码获取不到message的值,因此输出的结果也就是ReferenceError: message is not defined

1
2
3
4
5
if (true){
let message = "Hi ,XX";
let message = "hi, hx";
}
console.log(message);

let不支持重复声明,重复的声明会报错: SyntaxError: Identifier 'message' has already been declared。

1
2
3
4
if (true){
console.log(message);
let message = "hi, hx";
}

let不存在声明提升,因此这里的代码会报错: ReferenceError: message is not defined

另外let与var的不同是,使用let声明的全局变量不会成为widnow对象的属性,而var声明的会是window的属性。

const

const的行为逻辑与let一致,唯一不同的是const在声明时必须给变量赋值,且赋值的变量不允许再改变。需要注意的是,虽然这里说变量不允许再改变的意思是变量的地址不允许再改变,如果const的是一个对象,那么对象内的属性不在这个限制内。

声明的风格与最佳实践

  • const优先,let次之
  • 不使用var

数据类型

ES6定义的6中简单数据类型

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol

一个复杂的类型

  • object

typeof 操作符

typeof操作符用来判断变量的类型,其返回值有如下几种:

  • undefined: 变量未定义
  • boolean: 布尔值
  • string: 字符
  • number: 数值
  • object: 对象
  • function: 函数
  • symbol:符号

Undefined

Undefined只有一个值: undefined,表示未定义。

1
2
var x;
let y;

不需要显式地声明undefined, 包含undefined的变量和未声明的变量是有区别的。

1
2
3
4
let x;
// y
console.log(x);
console.log(y);

x的值是undefined, 而y这句会报错。

Null

Null类型同样只有一个值: null。逻辑上标识一个空指针对象,可以显示地给一个对象赋值null.

null和undefined表面值相等,也就是

1
cosonle.lo(null == undefined) //true.

Boolean

Boolean类型只有两个值true,false。

Number类型

ES使用IEEE754数值格式,因此存在浮点数计算问题:

1
2
3
let a = 0.1
let b = 0.2
console.log(a + b == 0.3) //false

因为a+b的结果是0.300000000000004而非0.3

严格模式下 8进制无效。

ES可以表示的最小值存在于Number.MIN_VALUE中,最大值Number.MAX_VALUE。

如果超过可以标识的大小,则用Infinity和-Infinity表示。

NaN

NaN表示不是一个数值,例如 0/0

NaN不包含NaN在内的任何值,也就是说 NaN == NaN 始终等于false,因此ES提供了isNaN函数用来判断是否是NaN。

数值转换

Number()、parseInt() 和 parseFloat()函数。

字符类型 String

  • 字符的表示可以使用单引号(‘)、双引号(“)和反引号(`)。
  • 字符串是不可变量,因此任何操作都是先销毁后生成一个新的字符串对象。

字符串转换

几乎所有的值都有toString()方法,因此我们可以直接使用toString方法将值转换为字符串。

1
2
3
4
let x = 11;
console.log(x.toString())
console.log(1.2.toString())
consolel.log(true.toString())

这里需要注意10.toString()会报:SyntaxError: Invalid or unexpected token, 因为编译器无法断定10.应该解释为10.0还是 10 。 处理方法可以在10的后边加个空格:

1
console.log(10 .toString()) // 10

模板字面量

ES6引入了模板字面量,使用反引号保持内部字符串的格式,在定义模板时特别有用。

1
2
3
4
5
6
let text1 = "first line\n second line";
let text2 = `first line
second line`

console.log(text1);
console.log(text2);

字符串插值

字符串插值意思可以在模板字面量中使用${}将变量的值定义进去,举个例子,在之前我们拼接字符串最常使用的方法是:

1
2
3
4
5
6
7
let value = 5
let result = value + ' to the second power is ' + value * value

let result2 = `${value} to the second power is ${value * value}`

console.log(result);
console.log(result2)

两个输出的结果都是一样的,但是字符串插值看起来更容易理解和使用。

模板字面量标签函数

模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let a = 1;
let b = 2;

function testTag(strings, ...expressions) {
console.log(strings);
console.log(avalue);
console.log(bvalue);
console.log(sumvalue)

return "test";
}

let testResult = testTag`${a} + ${b} = ${a + b}`;

console.log(testResult)
------------------
[ '', ' + ', ' = ', '' ]
1
2
3
test

因为表达式参数的数量是可变的,所以通常应该使用剩余操作符(rest operator)将它们收集到一个数组中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = 1;
let b = 2;

function testTag(strings, ...expressions) {
console.log(strings);
for(const express of expressions){
console.log(express);
}

return "test";
}

let testResult = testTag`${a} + ${b} = ${a + b}`;

console.log(testResult)

其结果与之前的分开写的结果一致。

原始字符串

使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示。

1
2
3
4
5
console.log('\u00A9');
console.log(String.raw`\u00A9`);
-------------
©
\u00A9

另外,也可以通过标签函数的第一个参数,即字符串数组的.raw 属性取得每个字符串的原始内容。

符号类型

符号类型是ES6新增的类型,其实例是唯一的不可变的,作用是确保作为对象属性时,该属性是唯一的,不会跟其他属性发生冲突。不同于私有属性,符号属性是非字符串的唯一的对象属性。

创建符号的方式:

1
2
3
4
5
let s = Symbol();

console.log(typeof s);
---------
symbol

符号可以传入一个字符串作为描述,但这跟符号对象本身并无关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
let s1 = Symbol(),
s2 = Symbol();

console.log(s1 == s2);

let s3 = Symbol("a1"),
s4 = Symbol("a1");

console.log(s3 == s4);

----------
false
false

还可以使用 Symbol.keyFor()来查询全局注册表,这个方法接收符号,返回该全局符号对应的字
符串键。如果查询的不是全局符号,则返回 undefined

1
2
3
4
5
6
7
8
let s5 = Symbol.for("test");
let s6 = Symbol.for("test");

console.log(s5==s6)
console.log(Symbol.keyFor(s5))
-------------
true
test

全局符号是可以复用的。

使用符号作为属性

符号作为属性的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
et s7 = Symbol("a"),
s8 = Symbol("b"),
s9 = Symbol("c");

let o = {
[s7]: "test7",
[s9]: "test9"
}

o[s8] = 'test8'

console.log(o)
console.log(o[s9])
---------------
{ [Symbol(a)]: 'test7',
[Symbol(c)]: 'test9',
[Symbol(b)]: 'test8' }
test9

Object类型

object是一种复合的数据类型,通过new来创建。

1
let o = new Object();

比较重要的属性和方法列表:

  • constructor: 构造函数
  • hasOwnProperty: 判断当前对象实例(不是原型)上是否存在给定的实例,要检查的属性名必须是字符串(如 o.hasOwnProperty(“name”))或符号
  • isPropertyOf(object): 用于判断当前对象是否是另外一个对象的原型。
  • propertyIsEnumerable(propertyName): 用于判断给定的属性是否可以使用for-in 语句枚举。与 hasOwnProperty()一样,属性名必须是字符串。
  • toLocaleString()::返回对象的字符串表示,该字符串反映对象所在的本地化执行环境
  • toString(): 返回对象的字符串表示
  • valueOf(): 返回对象对应的字符串、数值或布尔值表示。通常与 toString()的返回值相同。

Odoo 电子公章/印章模块

发表于 2021-07-20 | 分类于 odoo

早在17年的时候,就有需求给odoo的PDF文件上面添加电子公章,其实添加电子公章并非什么难事,解决方案也比较简单,唯一比较繁琐的是当年的解决方案需要手动调整印章所在的位置,而且需要一个单据一个单据地进行调整。

时间来到现在,这个月有客户再次提出了希望在PDF上面添加公章的需求,虽然使用之前的方案解决了,但总归感觉不是那么丝滑,肯定不是这个需求的最佳解决方案,于是,最近抽时间在之前的基础上对解决方案进行了提升,提炼出了一个统一的模块来专门解决电子公章的问题。

odoo电子公章模块简介

首先,在系统中安装电子公章模块(report_emark):

emark

上传印章图片

电子公章模块原生支持4种单据的印章添加,分别是销售订单、发票、采购订单和送货单。由于不同公司采用的电子公章可能不同,因此,印章的上传依据公司设置而非系统统一设置。

upload

调整印章所在单据的位置

上传完印章图片之后,我们就可以在系统布局中设置印章所在单据的位置了,位置设置采用了可视化的方式,方便用户根据自己的需求进行调整。

setting

用户可以根据自己的需求调整印章的左边距和上边距,也可以调整印章的大小,以找到适合自己的最佳方案。

效果预览

销售订单:

sale

采购订单:

purchase

发票

invoice

送货单:

delivery

友情提示

使用该模块前,一定要先跟公司法务沟通清楚是否可以使用哦,以免给自己和公司造成法律上的后果。

有需要的同学欢迎来我的淘宝进行选购哦。

Odoo中的Enviroment对象

发表于 2021-06-24 | 分类于 odoo

接触过odoo开发的同学一定对Environment对象不陌生,但是你真的了解odoo中的Environment对象吗,不妨看看你能否回答出下面的问题:

  1. 我们都知道 sale_order_obj = self.env['sale.order'] 可以获取到销售订单的模型,但它背后的原理是什么?

  2. 我们可以通过self.env.user获取当前用户,self.env.company可以获取当前公司,那么还有什么其他的变量可以直接使用吗?

  3. 为什么self.env.ref可以获取到xmlid对应的对象?

  4. self.env默认是当前公司,如果想要切换到另外一个公司,应该怎么做?如果你知道答案,那么我再问,当前版本(14.0)中那个方法还有效吗?

  5. 我们知道self.env.context是个不可变值,那么如果我们就是想要改变context中的某些值,应该怎么做?其内部原理是怎样的?

  6. 模型对象中的env变量(即我们常用的self.env)是什么时候进行初始化赋值的?










如果你对上述6个问题的都了然于胸,那么你肯定是odoo领域的大牛,原谅我在您面前班门弄斧了,下面的文章就不用浪费时间了。如果你对问题的答案摸棱两可或是一头雾水,那么你可以接着往下读,看看笔者能否给你一次讲个明白。

Enviroment是什么?

odoo中的environment对象是从旧版本API演化到新版本API的产物,它封装了旧版本中一些琐碎的变量,例如cr, uid, ids, context等等。它的主要作用是封装ORM的环境变量,提供新版API模型的快速访问方法,以及管理一些需要计算的数据结构。

不知道读者是否注意过,我们在定义一个模型的时候,只定义了模型的字段数据和必要的方法,缺从未对模型进行过实例化(如果你有过Python编程经验,应该知道类只有被实例之后才可以使用)。既然odoo不需要我们手动进行实例化,那么它是在什么时候进行实例化的呢?

答案是odoo在启动之后,即对所有记录在案(数据库)的模型进行了注册,将模型名称与模型类的对应关系维护起来,当我们需要的时候就通过叫名字的方法将模型类调出来供我们使用。这种理念有没有很熟悉?没错,它很像我们数据库操作中线程池的概念,早期的odoo版本使用的变量名就是pool,随着版本的迭代,pool在系统中已经不多见了,取而代之的就是我们今天的主角—Environment。

事实上,我们在使用self.env['sale.order']方法获取到的即模型sale.order的实例,之后我们使用ORM的browse或者search方法对返回的记录集进行操作。

Environment对象的变量

我们知道可以通过self.env来或者模型实例,也可以获取当前用户,那么Environment对象有哪些常用的数量呢?

下面是笔者从代码中总结出来的常用的变量:

  • self.cr: 数据库查询cursor
  • self.uid: 当前用户的uid
  • self.context: 当前用户的上下文
  • self.su: 是否是超级用户
  • self.env.user: 获取当前用户对象
  • self.env.company: 获取当前用户的公司对象
  • self.env.companies: 获取当前用户可访问的所有公司
  • self.env.lang: 获取当前用户的语言代码

另外还有几个可以用来判断当前用户角色的方法:

  • is_superuser(): 判断当前用户是否是超级用户
  • is_admin(): 判断当前用户是否是管理员
  • is_system(): 判断当前用户是否是系统管理员

Environment获取模型实例的原理

Envronment对象在实例化的时候会实例化一个Registry对象,Registry对象是odoo用来管理注册模型的类,Environment对象使用Registery的实例通过传入的模型名称获取模型的类名称,然后再实例化一个空的记录集返回给调用者使用。

ref方法的原理

Environment的ref方法实际上调用的是ir.model.data的xmlid_to_object方法,做了简单的封装,以方便我们进行调用。

多公司条件的下的环境变量

如果启用了多公司,那么我们的self.env默认为当前的用户所在的公司环境。假设我们系统中目前有两个公司A和B,如果当前公司是A,想要操作B公司的数据,应该如何处理?

在13.0版本中,我们可以使用with_context(force_company=B公司的id)的方式进行强制的公司环境切换。但是14.0中已经弃用了force_company,取而代之的是使用with_company方法。

那么问题来了,with_context是什么,有什么作用?

with_context

我们知道,self.env.context是个不可变的字典,我们不能直接修改它的内容。如果某个需求要求你必须将它的内容改掉,怎么办?

答案也很简单,既然你不让我改,那我再重新制造一份新的副本不就可以了。这就是with_context的使用场景。

with_context方法将传入的上下文变量,使用之前的记录ids,重新生成了一份新的记录集,返回给调用方使用。

Enviroment的加载机制

那么现在来讨论最后一个问题,self.env是何时赋值给self的?

这个问题的答案非常隐晦,笔者翻遍了model和api的源码也没找到BaseModel中给env变量赋值的语句。起初,笔者以为既然BaseModel中没有,那么它可能隐藏在元类MetaModel和Meta中,后来证明BaseModel和Meta中也没有。

实际上,odoo在启动过程中对数据库中的模型(ir.model)进行了初始化,而初始化这一步骤中实例化了一个env对象,但这里没有model.env = env这种显式的赋值语句,实际上的赋值操作隐藏在了env[model_name]这里。

env[model_name]方法内部将当前的env变量传给了model的实例,也因此,我们在后续的操作中可以使用self.env直接调用环境变量。

一般情况下,我们不需要操心env的加载时机,因为对于常规的开发而言,操作时env变量已经是就绪状态,直接调用即可。

更详细的内容请关注公众号: odoohub 开发指南第二部分第十二章

Jenkins 安装

发表于 2021-05-29 | 分类于 jenkins

安装java8

我的环境是Ubuntu 18.04

1
apt-get install openjdk-8-jdk

安装jenkins

jenkins的安装方式很简单

1
wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -

然后 添加源

1
deb https://pkg.jenkins.io/debian-stable binary/

安装:

1
2
sudo apt-get update
sudo apt-get install jenkins

等待安装完成即可

##

Odoo 百度地图的bug

发表于 2021-05-20 | 分类于 odoo

在给百度地图模块添砖加瓦的时候,碰到一个问题,就是鼠标选取的坐标与实际的坐标存在误差,如下图所示:

不论我怎么调整百度地图的参数,这个偏差一直存在。

后来经过google,发现了其他人也有类似的情况出现,其原因在于百度地图的缩放是基于body层的,如果body层有滚动条,并且不在最顶端,在当前位置插入了百度地图,也就是出现该问题时的状态,这个时候滚轮缩放就会产生偏移。

知道了问题的原因,就好处理,既然存在这个偏差,那么我们在加载marker的时候把这个偏差去掉应该就可以了。

1
2
3
4
5
6
7
8
9
10
map.addEventListener('click', function (e) {
// 百度地图以滚动条开始位置做为起点,导致鼠标和marker距离有偏差
var offset = $('.o_content').scrollTop();
var newpixel = new BMapGL.Pixel(e.pixel.x, e.pixel.y + offset);
var newpoint = map.pixelToPoint(newpixel);
//清除地图上所有的覆盖物
map.clearOverlays();
var marker = new BMapGL.Marker(newpoint);
map.addOverlay(marker);
})

odoo的form表单的滚动条是在.o_content样式的div元素中产生的,因此我们计算出这个偏差值,然后把计算后的结果重新加载到地图中正确的位置就可以了。

处理后的效果:

odoo selection 字段更新错误

发表于 2021-04-15 | 分类于 odoo

由于设计上的问题,某模型的某个字段需要由最初的Selection字段变更成为Many2one。原本以为很简单的问题,却没想到宰了大跟头:

先看报错:

意思是在更新字段类型的时候需要将其中的某个值设置成no,但是我搜遍了代码中所有跟no相关的代码,全部干掉还是有这个问题。

之后又尝试 把整个模型注释掉,删掉字段的约束。通通无效。

无奈之际,看到了日志中的2853这个id,想必是存储在数据库中了。果然,当我把表ir_model_fields_selection中id为2853的行删掉以后,系统正常了。

总结,由此我们可以看出,odoo的Selection字段是会将Selection的值存储在表ir_model_fields_selection中的,当我们变更Selection字段类型的时候,一定要注意清空表中不合适的数据。

odoo 中国行政区域模块

发表于 2021-04-01 | 分类于 odoo

今天来说一个老生常谈的问题,中国的行政区域规划。

官方原生的问题

首先,我们来看官方原生的功能,从12.0开始,官方原生模块中集成了中国的行政区域规划,增加了城市列表。但是默认这个功能是没有开启的,要开启这个功能,我们需要先到联系人-设置-本地化-国家,找到你要更改设置的国家,然后激活城市,这里我们选择中国。

然后,我们安装它的中国城市数据模块:

装好之后的效果:

从上面的效果来说,我们可以看出一下几个缺点:

  1. 并没有县区字段,这不能满足我们实际使用过程中需要用到的县区三级行政规划
  2. 城市数据和省份并没有进行联动

优化模块

因此,笔者对此进行模块优化,根据2020年11月份最新的行政区域规划写了新的中国城市数据模块:base_address_china:

使用效果:

数据根据2020年11月最新国家行政区域规划设定。

有需要的同学可以到我的淘宝进行购买,C级及以上会员免费获取。

Odoo与微信Oauth免登录模块

发表于 2021-02-23 | 分类于 odoo

微信Oauth登录

首先明确一点,这里说的微信Oauth登录指的是微信开放平台注册的应用,不是微信公众号的应用。因此,我们需要先到微信开放平台注册一个应用,因为我们要在odoo搭建的网站中使用,因此这里选择网站应用:

注册完账号之后,我们会得到应用的ID和密钥。

配置微信Oauth登录

拿到应用ID和密钥之后,我们就可以配置我们的微信登录模块了:

然后我们打开开发者模式,在菜单设置-用户-Oauth服务商中,启用微信开放平台:

测试

然后我们回到登录页面,可以看到出现了一个微信登录的按钮:

点击登录后,将跳转到扫码页面,用户扫描完成后确认登录将登录到自己的门户页面。

Odoo 阿里云 OSS对象存储模块

发表于 2021-01-20 | 分类于 odoo

我们知道odoo的附件默认是存储在磁盘上,这对我们单机服务器来说并没有什么不妥,但是如果我们使用的是应用集群,那么一个显而易见的问题就来了,我们应该如何保证每台服务器上的静态文件保持一致?

要保证多台服务器的体验一致,那么我们势必就要把这些静态文件统一到某个地方进行集中管理,通常我们的方案是使用文件服务器,也是现在互联网企业的最常见的解决方案之一。但我们今天要讲的不是试用文件服务器,而是现在各大云服务器厂商提供的对象存储功能。至于什么是对象存储,这里就不过多的讲了,请同学们自己去查找相关资料。

我们今天以阿里云OSS对象存储为例,来看一下应该如何解决上述的问题。

注册阿里云OSS

我们首先要去阿里云注册一个OSS账号,拿到应用的Access Key和Access Secret,这是我们管理OSS文件的凭证。然后我们选择一个地区,创建一个bucket,拿到endpoint和bucket name等参数,然后我们把这些参数填到阿里云模块的配置中:

附件

最近,我写的<<odoo开发指北>>一书中上线了关于附件和binary字段的详细介绍,有兴趣的同学可以关注我的公众号进行阅读。

我们先要处理附件的上传,最近

odoo POS 集成微信支付和支付宝支付

发表于 2021-01-12 | 分类于 odoo

最近有小伙伴有这方面的需求,市场上又没有十分靠谱的现成的解决方案,于是花了一天的时间把微信和支付宝的POS集成模块做了出来。这里记录一下开发的大概流程和使用介绍。

支付宝

我们之前是对接过支付宝在线支付的,而且因为支付宝有沙箱环境,因此,对接的比较流畅。我们先去支付宝开发中心注册一个开发者账号,然后可以拿到支付宝的沙箱环境账号和密钥。这里需要一部安卓手机,因为支付宝的沙箱钱包只有安卓版没有苹果版本。

沙箱应用中可以找到 APPID,设置密钥 和商家密钥等关键参数。

根据我们之前对接支付宝在线支付的经验,我们需要的参数有一下几个:

  • App Seller Id: 支付宝商家ID
  • Alipay App Id: 支付宝应用ID
  • Mechant Private Key: 商户私钥
  • Alipay Public Key: 支付宝RSA公钥
  • Sign Type: 加密方式

其中支付宝RSA公钥可以通过支付宝提供的生成工具生成,也可以直接自己生成,具体方法参考官方文档,这里不赘述了。

接下来,我们开始对接。首先,我们要搞清楚POS条码支付的流程(区别于用户主动扫码支付,这里称之为条码支付):

  1. 用户出示条码/二维码,实际是出示了自己条码支付的授权码
  2. 商户在通过扫描到自己的系统中之后,系统通过条码支付接口发起扣款请求
  3. 用户在手机端确认金额
  4. 商户系统扣款成功
  5. 用户收到扣款通知

其中第3步,通常为了支付的便捷,小额支付通常会跳过这一步。

因为支付过程中可能会碰到失败的情况,因此需要商户在系统中获取到用户支付中这个状态的时候,进行主动查询确认,一般经过三次尝试如果仍旧支付不成功时,调用撤销接口取消交易。对应到后台,我们需要三个接口:

  1. 支付接口
  2. 查询接口
  3. 撤销接口

支付接口

1
2
3
4
5
6
7
8
9
def barcode_pay(self, order_no, subject, auth_code, amount):
"""条码支付"""
# 条码支付
alipay = self._get_alipay_client()
_logger.debug(f"POS支付宝条码支付:{order_no},{subject},{amount}")
res = alipay.pay.trade_pay(
order_no, 'bar_code', auth_code, subject, total_amount=amount)
_logger.debug(f"POS支付宝支付结果:{res}")
return res

查询接口

1
2
3
4
5
6
def query_payment(self, trade_no):
"""查询支付结果"""
alipay = self._get_alipay_client()
res = alipay.pay.trade_query(trade_no=trade_no)
_logger.debug(f"POS支付宝交易查询结果:{res}")
return res

撤销接口

1
2
3
4
5
6
def cancel_payment(self, trade_no):
"""取消交易"""
alipay = self._get_alipay_client()
res = alipay.pay.trade_cancel(trade_no=trade_no)
_logger.debug(f"POS支付宝交易取消:{trade_no}")
return res

其中,查询和取消接口均可以使用商户订单号或者支付宝流水号,这里我们为了方便起见统一用的时支付宝流水号。

为了在POS系统中新增一个支付方法,我们需要新增一个支付方法,支付宝,并将我们之前讲到的参数放到设置里:

然后我们在门店的设置里新增这个支付方法:

新增之后,我们就可以在POS界面看到新增的支付方法了。

前端的对接相对比较复杂,我们要继承PaymentInterface对象,新生成一个PaymentAlipay 对象,负责处理前端的业务请求。

其中最主要的方法是处理后端传过来的响应并作出合适的反馈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
_alipay_handle_response: function (response) {
var line = this.pos.get_order().selected_paymentline;
var self = this;
if (response.code == '10000') {
self._alipay_success(response.trade_no)
self.pos.gui.close_popup();
}
else if (response.code == '10003') {
line.set_payment_status("pending")
this.pos.gui.show_popup('confirm', {
'title': '支付中',
'body': '等待用户付款',
'confirm': this.check_payment,
'cancel': this.cancel_payment,
'payment_method': line.payment_method,
'trade_no': response.trade_no
});
}
else if (response.code = '40004') {
line.set_payment_status("pending")
this.pos.gui.show_popup('error', {
'title': '支付失败',
"body": response.sub_msg
});
}
},

支付宝在支付以后,通常会返回一下几种状态的结果:

  • 10000: 支付成功
  • 10003: 用户支付中
  • 40004: 支付失败

其中 10003代表等待用户确认支付,一般出现在大额支付或者长期未使用的客户中,此时商户应该等待用户支付完成后,进行查询操作,然后完成后续的业务处理。

微信支付

微信支付的整体流程与支付宝一致,区别在于微信支付的金额单位是分,因此我们需要在处理过程中做一些处理,另外,微信的商户订单号并部支持空格,因此对于odoo默认的”Order 12345”这种订单号也需要做适配,同样地,我们在交易查询和交易撤销的接口中也应该使用微信的交易号而不是商户订单号。

效果展示

支付成功:

支付失败:

需要的同学欢迎到我的淘宝购买哦

123…25
长腿叔叔

长腿叔叔

长腿叔叔的技术博客

246 日志
39 分类
43 标签
GitHub 淘宝
友情链接
  • BUG集散地
© 2022 长腿叔叔
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.2