Day05 - 随意玩之 OrderCreate API

在昨天我们度过最大难关加密了,之後应该会轻松许多吧?


API 呼叫流程如下
https://ithelp.ithome.com.tw/upload/images/20210914/20141787QhUztimnhR.png

步骤 1, 2, 3 目前都是我们已经会的!

第 4 步骤的 API Request 需求如下
https://ithelp.ithome.com.tw/upload/images/20210914/20141787er8UL7FoRU.png

快速说明这六个参数

  1. Version: 固定值 1.0.0
  2. ShopNo: 固定值 根据永丰给你的编号输入
  3. APIService: 有三种变化,OrderCreate(建立订单)、OrderQuery(订单详情)、OrderPayQuery(订单付款资讯)
  4. Nonce: 利用 ShopNo 对 API 请求一个随机值
  5. Sign: 订单内容的签章,避免传送过程被窜改
  6. Message: 加密过後的订单内容

我们今天会聚集在 API Service 的 OrderCreate 上面


OrderCreate

  • 主要功能:建立订单
  • 订单付款方式有两种
    1. ATM
    2. 信用卡

我以信用卡为例,订单内容如下

{
    "ShopNo": shop_no,  ## 商家编号
    "OrderNo": "A202109150001",  ## 订单编号 (商家自行产生)
    "Amount": 50000,   ## 金额,最後两位为小数 (输入50000,实际上付款为 500.00)
    "CurrencyID": "TWD",  ## 币别,目前只有台币这个选项
    "PayType": "A",  ## A 代表 ATM 转帐
    "CardParam": {  ## 信用卡的参数
        "AutoBilling": "Y",  ## 自动请款
        "ExpBillingDays": "",   ## 自动请款天数
        "ExpMinutes": "",  ## 付款连结有效时间
        "PayTypeSub": "" ## 付款子项目 (一次付清)
    },
    "PrdtName": "OrderCreate_CreditCard_Example",  ## 产品名称
    "ReturnURL": "<URL>",  ## 付款成功时 redirect 的网页
    "BackendURL": "<URL>"  ## 订单完成付款时,会通知到这里
}

只要把上面的资讯加密後输入到下面 Message 栏位,就可以发出 Query 了!

{
    'Version': '1.0.0',
    'ShopNo': shop_no,
    'APIService': 'OrderCreate',
    'Sign': sign,
    'Nonce': nonce,
    'Message': msg
}

API 回传的内容也跟 上面一样,差别为内容是永丰要跟你说的讯息


程序码非常简单,东西填一填就可以发出 Request 了

def apiService(service, sign, nonce, msg):
	api_data = {
		'Version': '1.0.0',
		'ShopNo': shop_no,
		'APIService': service,
		'Sign': sign,
		'Nonce': nonce,
		'Message': msg
	}

	r = requests.post(order_url, json = api_data)
	resp = json.loads(r.content)

	return (resp['Sign'], resp['Nonce'], resp['Message'])
    
resp_sign, resp_nonce, resp_message = apiService('OrderCreate', signature, nonce, enc_message)

那我们今天就说到这里,明天会说明其他两个 API Service!


一样在最後附上程序码~

from __future__ import unicode_literals
import requests
import hashlib
import codecs
import json
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

shop_no = '<shop_no>'
sinopac_hash = {
	'a1': '',
	'a2': '',
	'b1': '',
	'b2': ''
}

nonce_url = 'https://apisbx.sinopac.com/funBIZ/QPay.WebAPI/api/Nonce'
order_url = 'https://apisbx.sinopac.com/funBIZ/QPay.WebAPI/api/Order'

def getNonce():
	nonce_data = {
		'ShopNo': shop_no
	}

	r = requests.post(nonce_url, json=nonce_data)

	return json.loads(r.content)['Nonce']

def calcHashID():
	a1 = sinopac_hash['a1']
	a2 = sinopac_hash['a2']
	b1 = sinopac_hash['b1']
	b2 = sinopac_hash['b2']

	xor1 = hex(int(a1, base=16)^int(a2, base=16))
	xor2 = hex(int(b1, base=16)^int(b2, base=16))

	hash_id = xor1[2:]+xor2[2:]
	return hash_id.upper()

def calcIV(nonce):
	s = hashlib.sha256()

	s.update(nonce.encode('utf-8'))
	h = s.hexdigest()
	return h[-16:].upper()

def calcSign(msg_content, nonce, hash_id):
	sign_msg = msg_content+nonce+hash_id

	s = hashlib.sha256()
	s.update(sign_msg.encode('utf-8'))
	h = s.hexdigest()
	return h.upper()

def parseQueryData(msg_param):
    if type(msg_param) != dict:
        return
    
    order_message = dict(sorted(msg_param.items(), key = lambda x: x[0]))
    message = ''

    for k, v in order_message.items():
        if type(v) == dict or v == '':
            continue
        message += f"{k}={v}&"

    return message[:-1]

def CBCEncrypt(key, iv, data):
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
    return (cipher.encrypt(pad(data.encode('utf-8'), AES.block_size)))

def CBCDecrypt(key, iv, data):
    data = codecs.decode(data, "hex")
    cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
    return unpad(cipher.decrypt(data), AES.block_size)

def apiService(service, sign, nonce, msg):
	api_data = {
		'Version': '1.0.0',
		'ShopNo': shop_no,
		'APIService': service,
		'Sign': sign,
		'Nonce': nonce,
		'Message': msg
	}

	r = requests.post(order_url, json = api_data)
	resp = json.loads(r.content)

	return (resp['Sign'], resp['Nonce'], resp['Message'])

def orderCreate(order_create):
	nonce = getNonce()
	hash_id = calcHashID()
	iv = calcIV(nonce)
	content = parseQueryData(order_create)
	signature = calcSign(content, nonce, hash_id)
	msg = json.dumps(order_create, ensure_ascii=False).replace(' ','')
	enc_message = CBCEncrypt(hash_id, iv, msg).hex().upper()
	resp_sign, resp_nonce, resp_message = apiService('OrderCreate', signature, nonce, enc_message)
	resp_iv = calcIV(resp_nonce)
	temp = CBCDecrypt(hash_id, resp_iv, resp_message)
	print(json.loads(temp))


order_create = {
    "ShopNo": shop_no,
    "OrderNo": "A202109150006",
    "Amount": 51000,
    "CurrencyID": "TWD",
    "PayType": "C",
    "CardParam": {
        "AutoBilling": "Y"
    },
    "PrdtName": "OrderCreate_CreditCard_Example",
    "ReturnURL": "http://10.11.22.113:8803/QPay.ApiClient/Store/Return",
    "BackendURL": "http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess"
}

orderCreate(order_create)

<<:  D01 / 为什麽要写这个? - 前言

>>:  [Day12] Vite 出小蜜蜂~ Spawn!

质询握手身份验证协议(CHAP)

质询握手身份验证协议(Challenge-Handshake Authentication Prot...

Day_27:让 Vite 来开启你的Vue之 跌入深坑偶像剧_ v-if & v-for 他俩不能在一起啊

Hi Dai Gei Ho~ 我是 Winnie ~ 今天的文章中,我们要来说说 v-if & v-...

DAY27: var、const、let 在作用域上有甚麽不一样?

在我们定义变量的时候都要加上像是 var、const、let等关键字, 那麽他们在作用域中又代表了甚...

Day11 - Gem-rqrcode 或 barby 产 QR Code

前言 本篇会示范如何在 Ruby on Rails 中产 QR Code,可透过 rqrcode 或...

[Android Studio] 每日小技巧 - 在 Project 中定位目前开启的 Class

常常有时候在阅读较大的专案时 没有定位档案位置的功能的话很难找到该 Class 的位置 大家可以找到...