修改歷程
版本 | 異動日期 | 修訂內容 | 位置 |
---|---|---|---|
1.0 | 2021.01.01 | 新版文件 | |
1.0.1 | 2023.04.07 | 新增開發前請先閱讀 | 20230407_01 |
開發前請先閱讀
金鑰的注意事項
Q:金鑰的有效期限
A:MyPay的金鑰有效期限只有一年,如果要延長,請自行至金鑰管理系統延長
Q:金鑰可以在什麼時候延長
A:到期日7天前,都可以延長(不包含第7天),一但超過,系統自動產生新金鑰後,即不可再延長
Q:金鑰即將到期前7天時,系統新發的金鑰是否會於信件中夾帶
A:會
Q:金鑰在到期前何時會發信通知
A:定期發送金鑰到期通知的頻率為:60天/30天/20天,若 貴司皆無動作,則系統會於到期前七天產製新金鑰並發送email到 貴司技術窗口信箱,此時舊的金鑰,因還未過期還可以使用,一旦過期,貴司仍使用舊金鑰則會無法交易
交易結果的告知
Q:交易完成的導頁會有交易結果的參數嗎
A:不論是參數中的success_returl 或 failure_returl或是後臺設定的交易成功、失敗導頁網址,均不會給予交易結果參數
Q:何時會給予交易結果
A:MyPay將在接收上游結果後,會由附錄三這邊的設定,背景主動發動通知商家
Q:當我接到MyPay通知時該做什麼
A:請直接回應8888,如沒有回應,MyPay會通知5次,若5次內均得不到8888,系統會寄發信件給予商家的技術人員以及附錄三而外設定的人員
票券交易設計概要
安全性設計
所有的要求發動都僅能從特約商店的網頁伺服器發出請求,將傳輸的資料以AES加密,再透過HTTPS加密傳輸。交易資料不由消費者端送出,可確保資料不被消費者竄改。所有付費資訊經過MYPAY Link匝道進行轉送處理,特約商店不需要處理消費者的付費流程以及安全控管。
MYTIX 目前提供之服務與格式
MYTIX 票券交易應用情境如下:
(1)購買票券交易:透過MYTIX發動購買票券,經由MYPAY交易完成後,通知特約商店購買之票券是否完成交易。
(2)購買票券交易查詢:查詢購買票券訂單結果。
(3)票券核銷交易:已完成付費的票券,可發動票券核銷。
(4)票券核銷查詢:查詢票券核銷訂單核銷結果。
(5)票券退款交易:發動對未使用的票券進行退款。
(6)票券退款交易查詢:查詢票券退款交易之結果。
我們提供介接方式是透過https連線,只接受POST方式傳送交易資料。
介接網址
特約商店模式
位置 | API介接網址 |
---|---|
測試區 | https://pay.usecase.cc/api/init |
正式區 | https://ka.mypay.tw/api/init |
Client模式(限定功能)
位置 | API介接網址 |
---|---|
測試區 | https://pay.usecase.cc/api/open |
正式區 | https://ka.mypay.tw/api/open |
資料加密方式
AES 256編碼 + base64編碼(附錄四資料加密方式)
加密金鑰
金鑰會透過mail發送,也可從管理後台取得
文字編碼
一律使用UTF-8相容編碼
購買票券交易
<?php
/**
* 特約商店串接-購買票券交易
*/
final class StoreVoucher
{
/**
* 特約商店商務代號
* @var string
*/
public $storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
* @var string
*/
public $storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
* @var string
*/
public $url = "https://pay.usecase.cc/api/init";
/**
* 取得串接欄位資料
* @return array
*/
public function getRawData()
{
$rawData = array();
$rawData['store_uid'] = $this->storeUid;
$rawData['user_data'] = [
'user_id' => 'phper',
'user_name' => '金城武',
'user_real_name' => '金城武',
'ip' => '127.0.0.1'
];
$rawData['order_id'] = "VS20210917113800";
$rawData['pfn'] = "0";
$rawData['cost'] = 100;
$rawData['currency'] = "TWD";
$rawData['items'] = [
[
'id' => 'H01',
'name' => '用餐券',
'amount' => 2,
'price' => 50,
'cost' => 50,
'total_price' => 100,
'total_cost' => 100,
'gift_number' => 0,
'trust_start_date' => '20210917',
'trust_end_date' => '20220916',
'is_custom_serial' => 1,
'serial_numbers' => '["A00049","A00050"]',
'gift_serial_numbers' => '[]',
'sharing' =>
[
'headquarters' => '0.15',
'sales' => '0.1',
'reimbursement' => '0.75'
]
]
];
$rawData['declarations'] = [
[
'name' => '用餐券',
'content' => '<h6>使用需知</h6>
<ul>
<li>本行程入住花蓮藍天麗池飯店或同等級。</li>
<li>本行程為套裝行程,旅客須完成住宿登記並於線上付款或當天於易遊網門市、ATM 付款完畢後,座位始予保留。</li>
<li>票價以套裝行程發售,價格依房型而異(發售指定區間乘車票,旅客可擇當次車指定上下車站上下車,不需另補差額,餘程亦不退費)。
</li>
<li>成人票:4,999 元/人起,孩童及敬老票另有優惠。</li>
<li>套票價格及詳細資訊請參閱易遊網郵輪式列車網頁(請點檢視行程)。</li></ul>'
]
];
return $rawData;
}
/**
* 取得服務位置
* @return array
*/
public function getService()
{
return array(
'service_name' => 'api',
'cmd' => 'api/voucherservicepurchase'
);
}
/**
* AES 256 加密
* @param array $fields
* @param string $key
* @return string
*/
public function encrypt($fields, $key)
{
$data = json_encode($fields);
$size = openssl_cipher_iv_length('AES-256-CBC');
$iv = openssl_random_pseudo_bytes($size);
$data = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$data = base64_encode($iv . $data);
return $data;
}
/**
* 資料 POST 到主機
* @param array $postData
* @return mixed
*/
public function post($postData = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 取得送出欄位資料
* @return array
*/
public function getPostData ()
{
$postData = array();
$postData['store_uid'] = $this->storeUid;
$postData['service'] = $this->encrypt($this->getService(), $this->storeKey);
$postData['encry_data'] = $this->encrypt($this->getRawData(), $this->storeKey);
return $postData;
}
/**
* 執行
*/
public function run()
{
$json = $this->post($this->getPostData());
echo $json;
}
}
$StoreVoucher = new StoreVoucher();
$StoreVoucher->run();
?>
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Dynamic;
/// <summary>
/// 建議使用.net framework4.5以上版本
/// 1.請透過NuGet安裝Newtonsoft.Json,若.net framework小於4.5請安裝Newtonsoft.Json 7.0以下版本
/// 2.若貴司.net framework小於4 可能無法使用dynamic,若是自行組陣列
/// 3.若貴司.net framework小於3.5 可能無法使用AES類別,若是參閱微軟網站使用較舊方式進行加密
/// 4.若貴司.net framework小於3.5 可能沒有Linq可使用,故data只能組字串,Newtonsoft.Json也可能無法使用
/// </summary>
namespace MyPay {
/// <summary>
/// 特約商店串接-購買票券交易
/// </summary>
public class StoreVoucher {
/// <summary>
/// 特約商店商務代號
/// </summary>
public string storeUid = "289151880142";
/// <summary>
/// 特約商店金鑰或認證碼
/// </summary>
public string storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/// <summary>
/// 串接交易位置
/// </summary>
public string url = "https://pay.usecase.cc/api/init";
/// <summary>
/// 執行
/// </summary>
static void Main() {
StoreVoucher simulator = new StoreVoucher();
//僅限走https的Tls 1.2以上版本
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
//發送至遠端
var result = simulator.Post(simulator.GetPostData());
System.Console.WriteLine(result);
}
/// <summary>
/// 取得串接欄位資料
/// </summary>
private dynamic GetRawData() {
dynamic userData = new ExpandoObject();
userData.user_id = "phper";
userData.user_name = "金城武";
userData.user_real_name = "金城武";
userData.ip = "127.0.0.1";
ArrayList items = new ArrayList();
dynamic item = new ExpandoObject();
item.id = "H01";
item.name = "用餐券";
item.amount = 2;
item.price = 50;
item.cost = 50;
item.total_price = 100;
item.total_cost = 100;
item.gift_number = 0;
item.trust_start_date = "20210917";
item.trust_end_date = "20220916";
item.is_custom_serial = 1;
item.serial_numbers = "[\"A00049\",\"A00050\"]";
item.gift_serial_numbers = "[]";
dynamic sharing = new ExpandoObject();
sharing.headquarters = "0.15";
sharing.sales = "0.1";
sharing.reimbursement = "0.75";
item.sharing = sharing;
items.Add(item);
ArrayList declarations = new ArrayList();
dynamic declaration = new ExpandoObject();
declaration.name = "用餐券";
declaration.content = "<h6>使用需知</h6>\r\n <ul>\r\n <li>本行程入住花蓮藍天麗池飯店或同等級。</li>\r\n <li>本行程為套裝行程,旅客須完成住宿登記並於線上付款或當天於易遊網門市、ATM 付款完畢後,座位始予保留。</li>\r\n <li>票價以套裝行程發售,價格依房型而異(發售指定區間乘車票,旅客可擇當次車指定上下車站上下車,不需另補差額,餘程亦不退費)。\r\n </li>\r\n <li>成人票:4,999 元/人起,孩童及敬老票另有優惠。</li>\r\n <li>套票價格及詳細資訊請參閱易遊網郵輪式列車網頁(請點檢視行程)。</li></ul>";
declarations.Add(declaration);
dynamic rawData = new ExpandoObject();
rawData.store_uid = this.storeUid;
rawData.user_data = userData;
rawData.order_id = "VS20210917113800";
rawData.pfn = "0";
rawData.cost = 100;
rawData.currency = "TWD";
rawData.items = items;
rawData.declarations = declarations;
return rawData;
}
/// <summary>
/// 取得服務位置
/// </summary>
private ServiceRequest GetService() {
ServiceRequest rawData = new ServiceRequest();
rawData.service_name = "api";
rawData.cmd = "api/voucherservicepurchase";
return rawData;
}
/// <summary>
/// 取得送出欄位資料
/// </summary>
private NameValueCollection GetPostData() {
string data_json = JsonConvert.SerializeObject(GetRawData(), Formatting.None);
string svr_json = JsonConvert.SerializeObject(GetService(), Formatting.None);; //依API種類調整
//產生AES向量
var IV = GetBytesIV();
//進行加密
var data_encode = Encrypt(data_json, this.storeKey, IV);
var svr_encode = Encrypt(svr_json, this.storeKey, IV);
//請注意使用的 Http Post 套件是否會自動加上UrlEncode,本Post範例為原始方式,故須加上UrlEncode
//若自行使用的套件會自動補上UrlEncode,則請忽略下面的UrlEncode,避免做了兩次UrlEncode
string data_toUrlEncode = HttpUtility.UrlEncode(data_encode);
string svr_toUrlEncode = HttpUtility.UrlEncode(svr_encode);
NameValueCollection postData = new NameValueCollection();
postData["store_uid"] = this.storeUid;
postData["service"] = svr_toUrlEncode;
postData["encry_data"] = data_toUrlEncode;
return postData;
}
/// <summary>
/// AES 256 加密
/// </summary>
/// <param name="data"></param>
/// <param name="key"></param>
/// <param name="byteIV"></param>
/// <returns></returns>
private string Encrypt(string data, string key, byte[] byteIV) {
var byteKey = System.Text.Encoding.UTF8.GetBytes(key);
var enBytes = AES_Encrypt(data, byteKey, byteIV);
return Convert.ToBase64String(BytesAdd(byteIV, enBytes));
}
/// <summary>
/// AES 256 加密處理
/// </summary>
/// <param name="original"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] AES_Encrypt(string original, byte[] key, byte[] iv) {
try {
var data = Encoding.UTF8.GetBytes(original);
var cipher = Aes.Create().CreateEncryptor(key, iv);
var de = cipher.TransformFinalBlock(data, 0, data.Length);
return de;
} catch {
return null;
}
}
/// <summary>
/// 轉換Bytes
/// </summary>
/// <param name="a"></param>
/// <param name="arryB"></param>
/// <returns></returns>
private byte[] BytesAdd(byte[] a, params byte[][] arryB) {
List < byte > c = new List < byte > ();
c.AddRange(a);
arryB.ToList().ForEach(b => {
c.AddRange(b);
});
return c.ToArray();
}
/// <summary>
/// 產生AES的IV
/// </summary>
/// <returns></returns>
private static byte[] GetBytesIV() {
var aes = System.Security.Cryptography.AesCryptoServiceProvider.Create();
aes.KeySize = 256;
aes.GenerateIV();
return aes.IV;
}
/// <summary>
/// 資料 POST 到主機
/// </summary>
/// <param name="pars"></param>
/// <returns></returns>
private string Post(NameValueCollection pars) {
string result = string.Empty;
string param = string.Empty;
if (pars.Count > 0) {
pars.AllKeys.ToList().ForEach(key => {
param += key + "=" + pars[key] + "&";
});
if (param[param.Length - 1] == '&') {
param = param.Remove(param.Length - 1);
}
}
byte[] bs = Encoding.UTF8.GetBytes(param);
try {
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create(this.url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = bs.Length;
using(Stream reqStream = req.GetRequestStream()) {
reqStream.Write(bs, 0, bs.Length);
}
using(WebResponse wr = req.GetResponse()) {
Encoding myEncoding = Encoding.GetEncoding("UTF-8");
using(StreamReader myStreamReader = new StreamReader(wr.GetResponseStream(), myEncoding)) {
result = myStreamReader.ReadToEnd();
}
}
req = null;
} catch (WebException ex) {
throw new WebException(ex.Message + "params : " + param, ex, ex.Status, ex.Response);
}
return result;
}
}
/// <summary>
/// 串接服務請求欄位
/// </summary>
public class ServiceRequest {
public string service_name { get; set; }
public string cmd { get; set; }
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.security.SecureRandom;
/**
* 特約商店串接-購買票券交易
* 1. jackson-core 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
* 2. jackson-databind 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
* 3. jackson-annotations 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
* 4. Spongy Castle 下載 https://mvnrepository.com/artifact/com.madgag.spongycastle/core
*/
public class StoreVoucher {
/**
* 特約商店商務代號
*/
String storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
*/
String storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
*/
String url = "https://pay.usecase.cc/api/init";
/**
* 執行
* @param args
*/
public static void main(String[] args) {
StoreVoucher simulator = new StoreVoucher();
String json = simulator.post(simulator.getPostData());
System.out.print(json);
}
@SuppressWarnings(value = { "unchecked", "deprecation" })
/**
* 取得串接欄位資料
* @return 串接原始資料
*/
public Map getRawData() {
Map<Object, Object> userData = new HashMap<Object, Object>();
userData.put("user_id", "phper");
userData.put("user_name", "金城武");
userData.put("user_real_name", "金城武");
userData.put("ip", "127.0.0.1");
ArrayList items = new ArrayList();
Map<Object, Object> item = new HashMap<Object, Object>();
item.put("id", "H01");
item.put("name", "用餐券");
item.put("amount", 2);
item.put("price", 50);
item.put("cost", 50);
item.put("total_price", 100);
item.put("total_cost", 100);
item.put("gift_number", 0);
item.put("trust_start_date", "20210917");
item.put("trust_end_date", "20220916");
item.put("is_custom_serial", 1);
item.put("serial_numbers", "[\"A00049\",\"A00050\"]");
item.put("gift_serial_numbers", "[]");
Map<Object, Object> sharing = new HashMap<Object, Object>();
sharing.put("headquarters", "0.15");
sharing.put("sales", "0.1");
sharing.put("reimbursement", "0.75");
item.put("sharing", sharing);
items.add(item);
ArrayList declarations = new ArrayList();
Map<Object, Object> declaration = new HashMap<Object, Object>();
declaration.put("name", "用餐券");
declaration.put("content", "<h6>使用需知</h6>\r\n <ul>\r\n <li>本行程入住花蓮藍天麗池飯店或同等級。</li>\r\n <li>本行程為套裝行程,旅客須完成住宿登記並於線上付款或當天於易遊網門市、ATM 付款完畢後,座位始予保留。</li>\r\n <li>票價以套裝行程發售,價格依房型而異(發售指定區間乘車票,旅客可擇當次車指定上下車站上下車,不需另補差額,餘程亦不退費)。\r\n </li>\r\n <li>成人票:4,999 元/人起,孩童及敬老票另有優惠。</li>\r\n <li>套票價格及詳細資訊請參閱易遊網郵輪式列車網頁(請點檢視行程)。</li></ul>");
declarations.add(declaration);
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("store_uid", this.storeUid);
rawData.put("user_data", userData);
rawData.put("order_id", "VS20210917113800");
rawData.put("pfn", "0");
rawData.put("cost", 100);
rawData.put("currency", "TWD");
rawData.put("items", items);
rawData.put("declarations", declarations);
return rawData;
}
/**
* 取得服務位置
* @return 串接服務資料
*/
public Map getService() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("service_name", "api");
rawData.put("cmd", "api/voucherservicepurchase");
return rawData;
}
/**
* AES 256 加密
* @param rawData 原始資料
* @param AesKey AES256金鑰字串
* @return 轉換成Base64資料
*/
public String encrypt(Map rawData, String AesKey) {
try {
ObjectMapper objMapper = new ObjectMapper();
byte[] data = objMapper.writeValueAsString(rawData).getBytes(UTF_8);
byte[] key = AesKey.getBytes(UTF_8);
// 16 bytes is the IV size for AES256
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()));
// Random iv
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[16];
rng.nextBytes(ivBytes);
cipher.init(true, new ParametersWithIV(new KeyParameter(key),
ivBytes));
byte[] outBuf = new byte[cipher.getOutputSize(data.length)];
int processed = cipher
.processBytes(data, 0, data.length, outBuf, 0);
processed += cipher.doFinal(outBuf, processed);
byte[] outBuf2 = new byte[processed + 16]; // Make room for iv
System.arraycopy(ivBytes, 0, outBuf2, 0, 16); // Add iv
System.arraycopy(outBuf, 0, outBuf2, 16, processed);
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(outBuf2);
return base64;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 資料 POST 到主機
* @param qstr 串接資料
* @return 服務回傳JSON資訊
*/
public String post(String qstr) {
String result = "";
try {
// 資料
byte[] qstr_bytes = qstr.getBytes(StandardCharsets.UTF_8);
URL iurl = new URL(this.url);
SSLContext sc = SSLContext.getInstance("TLSv1.2"); // $NON-NLS-1$
sc.init(null, null, new java.security.SecureRandom());
HttpsURLConnection con = (HttpsURLConnection) iurl.openConnection();
con.setSSLSocketFactory(sc.getSocketFactory());
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
String.valueOf(qstr_bytes.length));
con.setRequestProperty("Accept-Charset", "UTF-8");
con.setDoOutput(true);
con.setDoInput(true);
con.getOutputStream()
.write(qstr.getBytes(Charset.forName("UTF-8")));
con.getOutputStream().flush();
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine + "\r\n");
}
try {
result = response.toString();
} finally {
in.close();
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return result;
}
/**
* 取得送出欄位資料
* @return POST完整資料
*/
public String getPostData() {
String postData = "";
try {
// Base64需要使用UrlEncode做傳輸
String data_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getRawData(), this.storeKey), "UTF-8");
String svr_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getService(), this.storeKey), "UTF-8");
postData = "store_uid=" + this.storeUid + "&service="
+ svr_toUrlEncode + "&encry_data=" + data_toUrlEncode;
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return postData;
}
}
const crypto = require('crypto');
const httpRequest = require('https');
/**
* 特約商店串接-購買票券交易
*/
function StoreVoucher() {
// 特約商店商務代號
this.storeUid = "289151880142";
// 特約商店金鑰或認證碼
this.storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
// 串接交易位置
this.url = "https://pay.usecase.cc/api/init";
};
/**
* 取得串接欄位資料
*/
StoreVoucher.prototype.getRawData = function () {
return {
store_uid: this.storeUid,
user_data: {
'user_id': "phper",
'user_name': "金城武",
'user_real_name': "金城武",
'ip': "127.0.0.1"
},
order_id: "VS20210917113800",
pfn: "0",
cost: 100,
currency: "TWD",
items: [
{
'id': "H01",
'name': "用餐券",
'amount': 2,
'price': 50,
'cost': 50,
'total_price': 100,
'total_cost': 100,
'gift_number': 0,
'trust_start_date': "20210917",
'trust_end_date': "20220916",
'is_custom_serial': 1,
'serial_numbers': "[\"A00049\",\"A00050\"]",
'gift_serial_numbers': "[]",
'sharing':
{
'headquarters': "0.15",
'sales': "0.1",
'reimbursement': "0.75"
}
}
],
declarations: [
{
'name': "用餐券",
'content': "<h6>使用需知</h6>\r\n <ul>\r\n <li>本行程入住花蓮藍天麗池飯店或同等級。</li>\r\n <li>本行程為套裝行程,旅客須完成住宿登記並於線上付款或當天於易遊網門市、ATM 付款完畢後,座位始予保留。</li>\r\n <li>票價以套裝行程發售,價格依房型而異(發售指定區間乘車票,旅客可擇當次車指定上下車站上下車,不需另補差額,餘程亦不退費)。\r\n </li>\r\n <li>成人票:4,999 元/人起,孩童及敬老票另有優惠。</li>\r\n <li>套票價格及詳細資訊請參閱易遊網郵輪式列車網頁(請點檢視行程)。</li></ul>"
}
],
};
};
/**
* 取得服務位置
*/
StoreVoucher.prototype.getService = function () {
return {
service_name: "api",
cmd: "api/voucherservicepurchase"
};
};
/**
* AES 256 加密
*/
StoreVoucher.prototype.encrypt = function (fields, key) {
let eData = JSON.stringify(fields);
const blockSize = 16;
const iv = crypto.randomBytes(blockSize);
const encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
let tmpCipher = encryptor.update(Buffer.from(eData));
let finalCipher = encryptor.final();
const tempData = Buffer.concat([tmpCipher, finalCipher], tmpCipher.length + finalCipher.length);
let data = Buffer.concat([iv, tempData], iv.length + tempData.length).toString('base64');
return data;
};
/**
* 資料 POST 到主機
*/
StoreVoucher.prototype.post = function (postData) {
return new Promise((res, rej) => {
let options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
rejectUnauthorized: false
};
let send_process = httpRequest.request(this.url, options, (api_res) => {
let res_data = "";
api_res.on('data', (tmp_data) => {
res_data += tmp_data;
});
api_res.on('end', () => {
res(res_data);
});
});
send_process.write(JSON.stringify(postData));
send_process.end();
});
};
/**
* 取得送出欄位資料
*/
StoreVoucher.prototype.getPostData = function () {
return {
"store_uid": this.storeUid,
"service": this.encrypt(this.getService(), this.storeKey),
"encry_data": this.encrypt(this.getRawData(), this.storeKey)
};
};
/**
* 執行
*/
StoreVoucher.prototype.run = async function () {
json = await this.post(this.getPostData())
console.log(json);
};
StoreVoucher = new StoreVoucher();
StoreVoucher.run();
# -*- coding: utf-8 -*-
import json
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util import Padding
from Crypto.Random import get_random_bytes
"""特約商店串接-購買票券交易
"""
class StoreVoucher:
# 特約商店商務代號
storeUid = "289151880142"
# 特約商店金鑰或認證碼
storeKey = b"g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0"
# 串接交易位置
url = "https://pay.usecase.cc/api/init"
def getRawData(self):
"""取得串接欄位資料
Returns:
{dict}: 欄位資料
"""
rawData = {
'store_uid': self.storeUid,
'user_data': {
'user_id': "phper",
'user_name': "金城武",
'user_real_name': "金城武",
'ip': "127.0.0.1"
},
'order_id': "VS20210917113800",
'pfn': "0",
'cost': 100,
'currency': "TWD",
'items': [
{
'id': "H01",
'name': "用餐券",
'amount': 2,
'price': 50,
'cost': 50,
'total_price': 100,
'total_cost': 100,
'gift_number': 0,
'trust_start_date': "20210917",
'trust_end_date': "20220916",
'is_custom_serial': 1,
'serial_numbers': "[\"A00049\",\"A00050\"]",
'gift_serial_numbers': "[]",
'sharing':
{
'headquarters': "0.15",
'sales': "0.1",
'reimbursement': "0.75"
}
}
],
'declarations': [
{
'name': "用餐券",
'content': "<h6>使用需知</h6>\r\n <ul>\r\n <li>本行程入住花蓮藍天麗池飯店或同等級。</li>\r\n <li>本行程為套裝行程,旅客須完成住宿登記並於線上付款或當天於易遊網門市、ATM 付款完畢後,座位始予保留。</li>\r\n <li>票價以套裝行程發售,價格依房型而異(發售指定區間乘車票,旅客可擇當次車指定上下車站上下車,不需另補差額,餘程亦不退費)。\r\n </li>\r\n <li>成人票:4,999 元/人起,孩童及敬老票另有優惠。</li>\r\n <li>套票價格及詳細資訊請參閱易遊網郵輪式列車網頁(請點檢視行程)。</li></ul>"
}
],
}
return rawData
def getService(self):
"""取得服務位置
Returns:
{dict}: 服務位置資料
"""
return {
'service_name': 'api',
'cmd': 'api/voucherservicepurchase'
}
def encrypt(self, fields, key):
"""AES 256 加密
Args:
fields {dict}: 欄位資料
key {bytes}: AES金鑰
Returns:
{string}: 加密資料
"""
data = json.dumps(fields, separators=(',', ':'))
data = Padding.pad(data.encode('utf-8'), AES.block_size)
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.encrypt(data)
data = base64.b64encode(iv + data)
return data
def post(self, postData):
"""資料 POST 到主機
Args:
postData {dict}: 欄位資料
Returns:
{string}: JSON資料
"""
result = requests.post(self.url, postData)
return result.text
def getPostData(self):
"""取得送出欄位資料
Returns:
{dict}: 欄位資料
"""
postData = {
'store_uid': self.storeUid,
'service': self.encrypt(self.getService(), self.storeKey),
'encry_data': self.encrypt(self.getRawData(), self.storeKey)
}
return postData
def run(self):
"""執行
"""
json = self.post(self.getPostData())
print(json)
StoreVoucher = StoreVoucher()
StoreVoucher.run()
回傳 JSON 結構如下:
{
"code": "200",
"msg": "資料正確",
"uid": "93",
"key": "dd30e98b0835f90ef8a9ba5f25ac29dd23712f94",
"url": "https:\/\/pay.usecase.cc\/voucher\/93.html"
}
透過MYTIX發動購買票券,當消費者交易完成後,將交易結果通知特約商店。當特約商店確認交易成功後,購買之票券則可透過核銷來做消費。
特約商店『購買票券交易』參數說明
欄位 | 型態 | 說明 |
---|---|---|
store_uid | string(16) | 特約商店商務代號 |
service | text | {"service_name": "api", "cmd": "api\/voucherservicepurchase"} JSON格式,AES256加密資料 |
encry_data | text | 『購買票券交易』欄位參考 JSON格式,AES256加密資料 |
『購買票券交易』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
store_uid | string | 特約商店代碼 | 必填 |
order_id | string | 特約商店訂單編號(唯一) | 必填 |
pfn | string | 指定購買票券時,指定支付方式(不指定請帶0) | 必填 |
user_data | object | 消費者資訊 | 必填 『消費者資訊』欄位參考 |
service_type | integer | 票券服務類型 | 『票券服務類型』值參考 |
area_type | integer | 適用區域類型 | 『適用區域類型』值參考 |
is_pre_issue | integer | 是否為預發行 | 『是否為預發行』值參考 |
cost | string | 訂單總金額 | 必填 |
currency | string | 預設交易幣別(預設為TWD新台幣) | |
items | array | 訂單物品資訊 | 必填 每筆『票券項目』欄位參考 |
declarations | array | 票券的宣告 票券宣告與商品資訊票券種類的數量與名稱必須相同 |
必填 每筆『票券宣告』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 | |
success_returl | string | 購買交易成功導頁網址 | |
failure_returl | string | 購買交易失敗導頁網址 |
『購買票券交易』回傳欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
code | string | 交易回傳碼 | 『購買票券交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
uid | string | 購買票券交易訂單UID | |
key | string | 交易驗証碼 | |
url | string | 交易網址 |
『購買票券交易回傳資訊』回報欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
uid | string | 購買票券交易訂單UID | |
key | string | 交易驗証碼 | |
code | string | 目前狀態碼 | 『購買票券交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
finishtime | string | 交易完成時間(YYYYMMDDHHmmss) | |
order_id | string | 貴特店系統的訂單編號 | |
user_id | string | 消費者帳號 | |
cost | string | 原交易金額 | |
currency | string | 原交易幣別 | |
actual_cost | string | 實際交易金額 | |
actual_currency | string | 實際交易幣別 | |
pfn | string | 付費方法 | |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
購買票券交易查詢
<?php
/**
* 特約商店串接-購買票券交易查詢
*/
final class StoreVoucherQuery
{
/**
* 特約商店商務代號
* @var string
*/
public $storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
* @var string
*/
public $storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
* @var string
*/
public $url = "https://pay.usecase.cc/api/init";
/**
* 取得串接欄位資料
* @return array
*/
public function getRawData()
{
$rawData = array();
$rawData['store_uid'] = $this->storeUid;
$rawData['order_id'] = "VS20210908120309";
return $rawData;
}
/**
* 取得服務位置
* @return array
*/
public function getService()
{
return array(
'service_name' => 'api',
'cmd' => 'api/voucherservicequery'
);
}
/**
* AES 256 加密
* @param array $fields
* @param string $key
* @return string
*/
public function encrypt($fields, $key)
{
$data = json_encode($fields);
$size = openssl_cipher_iv_length('AES-256-CBC');
$iv = openssl_random_pseudo_bytes($size);
$data = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$data = base64_encode($iv . $data);
return $data;
}
/**
* 資料 POST 到主機
* @param array $postData
* @return mixed
*/
public function post($postData = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 取得送出欄位資料
* @return array
*/
public function getPostData ()
{
$postData = array();
$postData['store_uid'] = $this->storeUid;
$postData['service'] = $this->encrypt($this->getService(), $this->storeKey);
$postData['encry_data'] = $this->encrypt($this->getRawData(), $this->storeKey);
return $postData;
}
/**
* 執行
*/
public function run()
{
$json = $this->post($this->getPostData());
echo $json;
}
}
$StoreVoucherQuery = new StoreVoucherQuery();
$StoreVoucherQuery->run();
?>
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Dynamic;
/// <summary>
/// 建議使用.net framework4.5以上版本
/// 1.請透過NuGet安裝Newtonsoft.Json,若.net framework小於4.5請安裝Newtonsoft.Json 7.0以下版本
/// 2.若貴司.net framework小於4 可能無法使用dynamic,若是自行組陣列
/// 3.若貴司.net framework小於3.5 可能無法使用AES類別,若是參閱微軟網站使用較舊方式進行加密
/// 4.若貴司.net framework小於3.5 可能沒有Linq可使用,故data只能組字串,Newtonsoft.Json也可能無法使用
/// </summary>
namespace MyPay {
/// <summary>
/// 特約商店串接-購買票券交易查詢
/// </summary>
public class StoreVoucherQuery {
/// <summary>
/// 特約商店商務代號
/// </summary>
public string storeUid = "289151880142";
/// <summary>
/// 特約商店金鑰或認證碼
/// </summary>
public string storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/// <summary>
/// 串接交易位置
/// </summary>
public string url = "https://pay.usecase.cc/api/init";
/// <summary>
/// 執行
/// </summary>
static void Main() {
StoreVoucherQuery simulator = new StoreVoucherQuery();
//僅限走https的Tls 1.2以上版本
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
//發送至遠端
var result = simulator.Post(simulator.GetPostData());
System.Console.WriteLine(result);
}
/// <summary>
/// 取得串接欄位資料
/// </summary>
private dynamic GetRawData() {
dynamic rawData = new ExpandoObject();
rawData.store_uid = this.storeUid;
rawData.order_id = "VS20210908120309";
return rawData;
}
/// <summary>
/// 取得服務位置
/// </summary>
private ServiceRequest GetService() {
ServiceRequest rawData = new ServiceRequest();
rawData.service_name = "api";
rawData.cmd = "api/voucherservicequery";
return rawData;
}
/// <summary>
/// 取得送出欄位資料
/// </summary>
private NameValueCollection GetPostData() {
string data_json = JsonConvert.SerializeObject(GetRawData(), Formatting.None);
string svr_json = JsonConvert.SerializeObject(GetService(), Formatting.None);; //依API種類調整
//產生AES向量
var IV = GetBytesIV();
//進行加密
var data_encode = Encrypt(data_json, this.storeKey, IV);
var svr_encode = Encrypt(svr_json, this.storeKey, IV);
//請注意使用的 Http Post 套件是否會自動加上UrlEncode,本Post範例為原始方式,故須加上UrlEncode
//若自行使用的套件會自動補上UrlEncode,則請忽略下面的UrlEncode,避免做了兩次UrlEncode
string data_toUrlEncode = HttpUtility.UrlEncode(data_encode);
string svr_toUrlEncode = HttpUtility.UrlEncode(svr_encode);
NameValueCollection postData = new NameValueCollection();
postData["store_uid"] = this.storeUid;
postData["service"] = svr_toUrlEncode;
postData["encry_data"] = data_toUrlEncode;
return postData;
}
/// <summary>
/// AES 256 加密
/// </summary>
/// <param name="data"></param>
/// <param name="key"></param>
/// <param name="byteIV"></param>
/// <returns></returns>
private string Encrypt(string data, string key, byte[] byteIV) {
var byteKey = System.Text.Encoding.UTF8.GetBytes(key);
var enBytes = AES_Encrypt(data, byteKey, byteIV);
return Convert.ToBase64String(BytesAdd(byteIV, enBytes));
}
/// <summary>
/// AES 256 加密處理
/// </summary>
/// <param name="original"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] AES_Encrypt(string original, byte[] key, byte[] iv) {
try {
var data = Encoding.UTF8.GetBytes(original);
var cipher = Aes.Create().CreateEncryptor(key, iv);
var de = cipher.TransformFinalBlock(data, 0, data.Length);
return de;
} catch {
return null;
}
}
/// <summary>
/// 轉換Bytes
/// </summary>
/// <param name="a"></param>
/// <param name="arryB"></param>
/// <returns></returns>
private byte[] BytesAdd(byte[] a, params byte[][] arryB) {
List < byte > c = new List < byte > ();
c.AddRange(a);
arryB.ToList().ForEach(b => {
c.AddRange(b);
});
return c.ToArray();
}
/// <summary>
/// 產生AES的IV
/// </summary>
/// <returns></returns>
private static byte[] GetBytesIV() {
var aes = System.Security.Cryptography.AesCryptoServiceProvider.Create();
aes.KeySize = 256;
aes.GenerateIV();
return aes.IV;
}
/// <summary>
/// 資料 POST 到主機
/// </summary>
/// <param name="pars"></param>
/// <returns></returns>
private string Post(NameValueCollection pars) {
string result = string.Empty;
string param = string.Empty;
if (pars.Count > 0) {
pars.AllKeys.ToList().ForEach(key => {
param += key + "=" + pars[key] + "&";
});
if (param[param.Length - 1] == '&') {
param = param.Remove(param.Length - 1);
}
}
byte[] bs = Encoding.UTF8.GetBytes(param);
try {
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create(this.url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = bs.Length;
using(Stream reqStream = req.GetRequestStream()) {
reqStream.Write(bs, 0, bs.Length);
}
using(WebResponse wr = req.GetResponse()) {
Encoding myEncoding = Encoding.GetEncoding("UTF-8");
using(StreamReader myStreamReader = new StreamReader(wr.GetResponseStream(), myEncoding)) {
result = myStreamReader.ReadToEnd();
}
}
req = null;
} catch (WebException ex) {
throw new WebException(ex.Message + "params : " + param, ex, ex.Status, ex.Response);
}
return result;
}
}
/// <summary>
/// 串接服務請求欄位
/// </summary>
public class ServiceRequest {
public string service_name { get; set; }
public string cmd { get; set; }
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.security.SecureRandom;
/**
* 特約商店串接-購買票券交易查詢
* 1. jackson-core 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
* 2. jackson-databind 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
* 3. jackson-annotations 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
* 4. Spongy Castle 下載 https://mvnrepository.com/artifact/com.madgag.spongycastle/core
*/
public class StoreVoucherQuery {
/**
* 特約商店商務代號
*/
String storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
*/
String storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
*/
String url = "https://pay.usecase.cc/api/init";
/**
* 執行
* @param args
*/
public static void main(String[] args) {
StoreVoucherQuery simulator = new StoreVoucherQuery();
String json = simulator.post(simulator.getPostData());
System.out.print(json);
}
@SuppressWarnings(value = { "unchecked", "deprecation" })
/**
* 取得串接欄位資料
* @return 串接原始資料
*/
public Map getRawData() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("store_uid", this.storeUid);
rawData.put("order_id", "VS20210908120309");
return rawData;
}
/**
* 取得服務位置
* @return 串接服務資料
*/
public Map getService() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("service_name", "api");
rawData.put("cmd", "api/voucherservicequery");
return rawData;
}
/**
* AES 256 加密
* @param rawData 原始資料
* @param AesKey AES256金鑰字串
* @return 轉換成Base64資料
*/
public String encrypt(Map rawData, String AesKey) {
try {
ObjectMapper objMapper = new ObjectMapper();
byte[] data = objMapper.writeValueAsString(rawData).getBytes(UTF_8);
byte[] key = AesKey.getBytes(UTF_8);
// 16 bytes is the IV size for AES256
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()));
// Random iv
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[16];
rng.nextBytes(ivBytes);
cipher.init(true, new ParametersWithIV(new KeyParameter(key),
ivBytes));
byte[] outBuf = new byte[cipher.getOutputSize(data.length)];
int processed = cipher
.processBytes(data, 0, data.length, outBuf, 0);
processed += cipher.doFinal(outBuf, processed);
byte[] outBuf2 = new byte[processed + 16]; // Make room for iv
System.arraycopy(ivBytes, 0, outBuf2, 0, 16); // Add iv
System.arraycopy(outBuf, 0, outBuf2, 16, processed);
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(outBuf2);
return base64;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 資料 POST 到主機
* @param qstr 串接資料
* @return 服務回傳JSON資訊
*/
public String post(String qstr) {
String result = "";
try {
// 資料
byte[] qstr_bytes = qstr.getBytes(StandardCharsets.UTF_8);
URL iurl = new URL(this.url);
SSLContext sc = SSLContext.getInstance("TLSv1.2"); // $NON-NLS-1$
sc.init(null, null, new java.security.SecureRandom());
HttpsURLConnection con = (HttpsURLConnection) iurl.openConnection();
con.setSSLSocketFactory(sc.getSocketFactory());
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
String.valueOf(qstr_bytes.length));
con.setRequestProperty("Accept-Charset", "UTF-8");
con.setDoOutput(true);
con.setDoInput(true);
con.getOutputStream()
.write(qstr.getBytes(Charset.forName("UTF-8")));
con.getOutputStream().flush();
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine + "\r\n");
}
try {
result = response.toString();
} finally {
in.close();
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return result;
}
/**
* 取得送出欄位資料
* @return POST完整資料
*/
public String getPostData() {
String postData = "";
try {
// Base64需要使用UrlEncode做傳輸
String data_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getRawData(), this.storeKey), "UTF-8");
String svr_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getService(), this.storeKey), "UTF-8");
postData = "store_uid=" + this.storeUid + "&service="
+ svr_toUrlEncode + "&encry_data=" + data_toUrlEncode;
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return postData;
}
}
const crypto = require('crypto');
const httpRequest = require('https');
/**
* 特約商店串接-購買票券交易查詢
*/
function StoreVoucherQuery() {
// 特約商店商務代號
this.storeUid = "289151880142";
// 特約商店金鑰或認證碼
this.storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
// 串接交易位置
this.url = "https://pay.usecase.cc/api/init";
};
/**
* 取得串接欄位資料
*/
StoreVoucherQuery.prototype.getRawData = function () {
return {
store_uid: this.storeUid,
order_id: "VS20210908120309",
};
};
/**
* 取得服務位置
*/
StoreVoucherQuery.prototype.getService = function () {
return {
service_name: "api",
cmd: "api/voucherservicequery"
};
};
/**
* AES 256 加密
*/
StoreVoucherQuery.prototype.encrypt = function (fields, key) {
let eData = JSON.stringify(fields);
const blockSize = 16;
const iv = crypto.randomBytes(blockSize);
const encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
let tmpCipher = encryptor.update(Buffer.from(eData));
let finalCipher = encryptor.final();
const tempData = Buffer.concat([tmpCipher, finalCipher], tmpCipher.length + finalCipher.length);
let data = Buffer.concat([iv, tempData], iv.length + tempData.length).toString('base64');
return data;
};
/**
* 資料 POST 到主機
*/
StoreVoucherQuery.prototype.post = function (postData) {
return new Promise((res, rej) => {
let options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
rejectUnauthorized: false
};
let send_process = httpRequest.request(this.url, options, (api_res) => {
let res_data = "";
api_res.on('data', (tmp_data) => {
res_data += tmp_data;
});
api_res.on('end', () => {
res(res_data);
});
});
send_process.write(JSON.stringify(postData));
send_process.end();
});
};
/**
* 取得送出欄位資料
*/
StoreVoucherQuery.prototype.getPostData = function () {
return {
"store_uid": this.storeUid,
"service": this.encrypt(this.getService(), this.storeKey),
"encry_data": this.encrypt(this.getRawData(), this.storeKey)
};
};
/**
* 執行
*/
StoreVoucherQuery.prototype.run = async function () {
json = await this.post(this.getPostData())
console.log(json);
};
StoreVoucherQuery = new StoreVoucherQuery();
StoreVoucherQuery.run();
# -*- coding: utf-8 -*-
import json
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util import Padding
from Crypto.Random import get_random_bytes
"""特約商店串接-購買票券交易查詢
"""
class StoreVoucherQuery:
# 特約商店商務代號
storeUid = "289151880142"
# 特約商店金鑰或認證碼
storeKey = b"g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0"
# 串接交易位置
url = "https://pay.usecase.cc/api/init"
def getRawData(self):
"""取得串接欄位資料
Returns:
{dict}: 欄位資料
"""
rawData = {
'store_uid': self.storeUid,
'order_id': "VS20210908120309",
}
return rawData
def getService(self):
"""取得服務位置
Returns:
{dict}: 服務位置資料
"""
return {
'service_name': 'api',
'cmd': 'api/voucherservicequery'
}
def encrypt(self, fields, key):
"""AES 256 加密
Args:
fields {dict}: 欄位資料
key {bytes}: AES金鑰
Returns:
{string}: 加密資料
"""
data = json.dumps(fields, separators=(',', ':'))
data = Padding.pad(data.encode('utf-8'), AES.block_size)
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.encrypt(data)
data = base64.b64encode(iv + data)
return data
def post(self, postData):
"""資料 POST 到主機
Args:
postData {dict}: 欄位資料
Returns:
{string}: JSON資料
"""
result = requests.post(self.url, postData)
return result.text
def getPostData(self):
"""取得送出欄位資料
Returns:
{dict}: 欄位資料
"""
postData = {
'store_uid': self.storeUid,
'service': self.encrypt(self.getService(), self.storeKey),
'encry_data': self.encrypt(self.getRawData(), self.storeKey)
}
return postData
def run(self):
"""執行
"""
json = self.post(self.getPostData())
print(json)
StoreVoucherQuery = StoreVoucherQuery()
StoreVoucherQuery.run()
回傳 JSON 結構如下:
{
"code": "B200",
"msg": "執行成功",
"content": {
"uid": "86",
"key": "3caf6a482f79f1dd6ed16448836c7a16",
"code": "250",
"msg": "付款完成",
"finishtime": "20210916164212",
"order_id": "VS20210908120309",
"user_id": "phper",
"cost": "100",
"currency": "TWD",
"actual_cost": "100",
"actual_currency": "TWD",
"pfn": "CREDITCARD",
"echo_0": "",
"echo_1": "",
"echo_2": "",
"echo_3": "",
"echo_4": ""
}
}
發動購買票券交易後,可透過此方法查詢購買票券訂單交易結果。
特約商店『購買票券交易查詢』參數說明
欄位 | 型態 | 說明 |
---|---|---|
store_uid | string(16) | 特約商店商務代號 |
service | text | {"service_name": "api", "cmd": "api\/voucherservicequery"} JSON格式,AES256加密資料 |
encry_data | text | 『購買票券交易查詢』欄位參考 JSON格式,AES256加密資料 |
『購買票券交易查詢』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
store_uid | string | 特約商店代碼 | 必填 |
order_id | string | 特約商店訂單編號 | 必填 |
『購買票券交易查詢』回傳欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
code | string | 購買交易狀態碼 | |
msg | string | 回傳訊息 | |
content | object | 『購買票券交易查詢回傳』欄位參考 |
『購買票券交易查詢回傳』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
uid | string | 購買票券交易訂單UID | |
key | string | 交易驗証碼 | |
code | string | 目前狀態碼 | 『購買票券交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
finishtime | string | 交易完成時間(YYYYMMDDHHmmss) | |
order_id | string | 貴特店系統的訂單編號 | |
user_id | string | 消費者帳號 | |
cost | string | 原交易金額 | |
currency | string | 原交易幣別 | |
actual_cost | string | 實際交易金額 | |
actual_currency | string | 實際交易幣別 | |
pfn | string | 付費方法 | |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
票券核銷交易
<?php
/**
* 特約商店串接-票券核銷交易
*/
final class StoreVoucherTransaction
{
/**
* 特約商店商務代號
* @var string
*/
public $storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
* @var string
*/
public $storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
* @var string
*/
public $url = "https://pay.usecase.cc/api/init";
/**
* 取得串接欄位資料
* @return array
*/
public function getRawData()
{
$rawData = array();
$rawData['store_uid'] = $this->storeUid;
$rawData['order_id'] = "VT20210917114023";
$rawData['user_id'] = "phper";
$rawData['ip'] = "127.0.0.1";
$rawData['items'] = [
[
'id' => 'H01',
'serial_number' => 'A00021'
],
[
'id' => 'H01',
'serial_number' => 'A00022'
]
];
$rawData['echo_0'] = "";
$rawData['echo_1'] = "";
$rawData['echo_2'] = "";
$rawData['echo_3'] = "";
$rawData['echo_4'] = "";
return $rawData;
}
/**
* 取得服務位置
* @return array
*/
public function getService()
{
return array(
'service_name' => 'api',
'cmd' => 'api/vouchertransaction'
);
}
/**
* AES 256 加密
* @param array $fields
* @param string $key
* @return string
*/
public function encrypt($fields, $key)
{
$data = json_encode($fields);
$size = openssl_cipher_iv_length('AES-256-CBC');
$iv = openssl_random_pseudo_bytes($size);
$data = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$data = base64_encode($iv . $data);
return $data;
}
/**
* 資料 POST 到主機
* @param array $postData
* @return mixed
*/
public function post($postData = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 取得送出欄位資料
* @return array
*/
public function getPostData ()
{
$postData = array();
$postData['store_uid'] = $this->storeUid;
$postData['service'] = $this->encrypt($this->getService(), $this->storeKey);
$postData['encry_data'] = $this->encrypt($this->getRawData(), $this->storeKey);
return $postData;
}
/**
* 執行
*/
public function run()
{
$json = $this->post($this->getPostData());
echo $json;
}
}
$StoreVoucherTransaction = new StoreVoucherTransaction();
$StoreVoucherTransaction->run();
?>
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Dynamic;
/// <summary>
/// 建議使用.net framework4.5以上版本
/// 1.請透過NuGet安裝Newtonsoft.Json,若.net framework小於4.5請安裝Newtonsoft.Json 7.0以下版本
/// 2.若貴司.net framework小於4 可能無法使用dynamic,若是自行組陣列
/// 3.若貴司.net framework小於3.5 可能無法使用AES類別,若是參閱微軟網站使用較舊方式進行加密
/// 4.若貴司.net framework小於3.5 可能沒有Linq可使用,故data只能組字串,Newtonsoft.Json也可能無法使用
/// </summary>
namespace MyPay {
/// <summary>
/// 特約商店串接-票券核銷交易
/// </summary>
public class StoreVoucherTransaction {
/// <summary>
/// 特約商店商務代號
/// </summary>
public string storeUid = "289151880142";
/// <summary>
/// 特約商店金鑰或認證碼
/// </summary>
public string storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/// <summary>
/// 串接交易位置
/// </summary>
public string url = "https://pay.usecase.cc/api/init";
/// <summary>
/// 執行
/// </summary>
static void Main() {
StoreVoucherTransaction simulator = new StoreVoucherTransaction();
//僅限走https的Tls 1.2以上版本
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
//發送至遠端
var result = simulator.Post(simulator.GetPostData());
System.Console.WriteLine(result);
}
/// <summary>
/// 取得串接欄位資料
/// </summary>
private dynamic GetRawData() {
ArrayList items = new ArrayList();
dynamic item1 = new ExpandoObject();
item1.id = "H01";
item1.serial_number = "A00021";
items.Add(item1);
dynamic item2 = new ExpandoObject();
item2.id = "H01";
item2.serial_number = "A00022";
items.Add(item2);
dynamic rawData = new ExpandoObject();
rawData.store_uid = this.storeUid;
rawData.order_id = "VT20210917114023";
rawData.user_id = "phper";
rawData.ip = "127.0.0.1";
rawData.items = items;
rawData.echo_0 = "";
rawData.echo_1 = "";
rawData.echo_2 = "";
rawData.echo_3 = "";
rawData.echo_4 = "";
return rawData;
}
/// <summary>
/// 取得服務位置
/// </summary>
private ServiceRequest GetService() {
ServiceRequest rawData = new ServiceRequest();
rawData.service_name = "api";
rawData.cmd = "api/vouchertransaction";
return rawData;
}
/// <summary>
/// 取得送出欄位資料
/// </summary>
private NameValueCollection GetPostData() {
string data_json = JsonConvert.SerializeObject(GetRawData(), Formatting.None);
string svr_json = JsonConvert.SerializeObject(GetService(), Formatting.None);; //依API種類調整
//產生AES向量
var IV = GetBytesIV();
//進行加密
var data_encode = Encrypt(data_json, this.storeKey, IV);
var svr_encode = Encrypt(svr_json, this.storeKey, IV);
//請注意使用的 Http Post 套件是否會自動加上UrlEncode,本Post範例為原始方式,故須加上UrlEncode
//若自行使用的套件會自動補上UrlEncode,則請忽略下面的UrlEncode,避免做了兩次UrlEncode
string data_toUrlEncode = HttpUtility.UrlEncode(data_encode);
string svr_toUrlEncode = HttpUtility.UrlEncode(svr_encode);
NameValueCollection postData = new NameValueCollection();
postData["store_uid"] = this.storeUid;
postData["service"] = svr_toUrlEncode;
postData["encry_data"] = data_toUrlEncode;
return postData;
}
/// <summary>
/// AES 256 加密
/// </summary>
/// <param name="data"></param>
/// <param name="key"></param>
/// <param name="byteIV"></param>
/// <returns></returns>
private string Encrypt(string data, string key, byte[] byteIV) {
var byteKey = System.Text.Encoding.UTF8.GetBytes(key);
var enBytes = AES_Encrypt(data, byteKey, byteIV);
return Convert.ToBase64String(BytesAdd(byteIV, enBytes));
}
/// <summary>
/// AES 256 加密處理
/// </summary>
/// <param name="original"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] AES_Encrypt(string original, byte[] key, byte[] iv) {
try {
var data = Encoding.UTF8.GetBytes(original);
var cipher = Aes.Create().CreateEncryptor(key, iv);
var de = cipher.TransformFinalBlock(data, 0, data.Length);
return de;
} catch {
return null;
}
}
/// <summary>
/// 轉換Bytes
/// </summary>
/// <param name="a"></param>
/// <param name="arryB"></param>
/// <returns></returns>
private byte[] BytesAdd(byte[] a, params byte[][] arryB) {
List < byte > c = new List < byte > ();
c.AddRange(a);
arryB.ToList().ForEach(b => {
c.AddRange(b);
});
return c.ToArray();
}
/// <summary>
/// 產生AES的IV
/// </summary>
/// <returns></returns>
private static byte[] GetBytesIV() {
var aes = System.Security.Cryptography.AesCryptoServiceProvider.Create();
aes.KeySize = 256;
aes.GenerateIV();
return aes.IV;
}
/// <summary>
/// 資料 POST 到主機
/// </summary>
/// <param name="pars"></param>
/// <returns></returns>
private string Post(NameValueCollection pars) {
string result = string.Empty;
string param = string.Empty;
if (pars.Count > 0) {
pars.AllKeys.ToList().ForEach(key => {
param += key + "=" + pars[key] + "&";
});
if (param[param.Length - 1] == '&') {
param = param.Remove(param.Length - 1);
}
}
byte[] bs = Encoding.UTF8.GetBytes(param);
try {
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create(this.url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = bs.Length;
using(Stream reqStream = req.GetRequestStream()) {
reqStream.Write(bs, 0, bs.Length);
}
using(WebResponse wr = req.GetResponse()) {
Encoding myEncoding = Encoding.GetEncoding("UTF-8");
using(StreamReader myStreamReader = new StreamReader(wr.GetResponseStream(), myEncoding)) {
result = myStreamReader.ReadToEnd();
}
}
req = null;
} catch (WebException ex) {
throw new WebException(ex.Message + "params : " + param, ex, ex.Status, ex.Response);
}
return result;
}
}
/// <summary>
/// 串接服務請求欄位
/// </summary>
public class ServiceRequest {
public string service_name { get; set; }
public string cmd { get; set; }
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.security.SecureRandom;
/**
* 特約商店串接-票券核銷交易
* 1. jackson-core 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
* 2. jackson-databind 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
* 3. jackson-annotations 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
* 4. Spongy Castle 下載 https://mvnrepository.com/artifact/com.madgag.spongycastle/core
*/
public class StoreVoucherTransaction {
/**
* 特約商店商務代號
*/
String storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
*/
String storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
*/
String url = "https://pay.usecase.cc/api/init";
/**
* 執行
* @param args
*/
public static void main(String[] args) {
StoreVoucherTransaction simulator = new StoreVoucherTransaction();
String json = simulator.post(simulator.getPostData());
System.out.print(json);
}
@SuppressWarnings(value = { "unchecked", "deprecation" })
/**
* 取得串接欄位資料
* @return 串接原始資料
*/
public Map getRawData() {
ArrayList items = new ArrayList();
Map<Object, Object> item1 = new HashMap<Object, Object>();
item1.put("id", "H01");
item1.put("serial_number", "A00021");
items.add(item1);
Map<Object, Object> item2 = new HashMap<Object, Object>();
item2.put("id", "H01");
item2.put("serial_number", "A00022");
items.add(item2);
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("store_uid", this.storeUid);
rawData.put("order_id", "VT20210917114023");
rawData.put("user_id", "phper");
rawData.put("ip", "127.0.0.1");
rawData.put("items", items);
rawData.put("echo_0", "");
rawData.put("echo_1", "");
rawData.put("echo_2", "");
rawData.put("echo_3", "");
rawData.put("echo_4", "");
return rawData;
}
/**
* 取得服務位置
* @return 串接服務資料
*/
public Map getService() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("service_name", "api");
rawData.put("cmd", "api/vouchertransaction");
return rawData;
}
/**
* AES 256 加密
* @param rawData 原始資料
* @param AesKey AES256金鑰字串
* @return 轉換成Base64資料
*/
public String encrypt(Map rawData, String AesKey) {
try {
ObjectMapper objMapper = new ObjectMapper();
byte[] data = objMapper.writeValueAsString(rawData).getBytes(UTF_8);
byte[] key = AesKey.getBytes(UTF_8);
// 16 bytes is the IV size for AES256
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()));
// Random iv
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[16];
rng.nextBytes(ivBytes);
cipher.init(true, new ParametersWithIV(new KeyParameter(key),
ivBytes));
byte[] outBuf = new byte[cipher.getOutputSize(data.length)];
int processed = cipher
.processBytes(data, 0, data.length, outBuf, 0);
processed += cipher.doFinal(outBuf, processed);
byte[] outBuf2 = new byte[processed + 16]; // Make room for iv
System.arraycopy(ivBytes, 0, outBuf2, 0, 16); // Add iv
System.arraycopy(outBuf, 0, outBuf2, 16, processed);
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(outBuf2);
return base64;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 資料 POST 到主機
* @param qstr 串接資料
* @return 服務回傳JSON資訊
*/
public String post(String qstr) {
String result = "";
try {
// 資料
byte[] qstr_bytes = qstr.getBytes(StandardCharsets.UTF_8);
URL iurl = new URL(this.url);
SSLContext sc = SSLContext.getInstance("TLSv1.2"); // $NON-NLS-1$
sc.init(null, null, new java.security.SecureRandom());
HttpsURLConnection con = (HttpsURLConnection) iurl.openConnection();
con.setSSLSocketFactory(sc.getSocketFactory());
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
String.valueOf(qstr_bytes.length));
con.setRequestProperty("Accept-Charset", "UTF-8");
con.setDoOutput(true);
con.setDoInput(true);
con.getOutputStream()
.write(qstr.getBytes(Charset.forName("UTF-8")));
con.getOutputStream().flush();
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine + "\r\n");
}
try {
result = response.toString();
} finally {
in.close();
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return result;
}
/**
* 取得送出欄位資料
* @return POST完整資料
*/
public String getPostData() {
String postData = "";
try {
// Base64需要使用UrlEncode做傳輸
String data_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getRawData(), this.storeKey), "UTF-8");
String svr_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getService(), this.storeKey), "UTF-8");
postData = "store_uid=" + this.storeUid + "&service="
+ svr_toUrlEncode + "&encry_data=" + data_toUrlEncode;
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return postData;
}
}
const crypto = require('crypto');
const httpRequest = require('https');
/**
* 特約商店串接-票券核銷交易
*/
function StoreVoucherTransaction() {
// 特約商店商務代號
this.storeUid = "289151880142";
// 特約商店金鑰或認證碼
this.storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
// 串接交易位置
this.url = "https://pay.usecase.cc/api/init";
};
/**
* 取得串接欄位資料
*/
StoreVoucherTransaction.prototype.getRawData = function () {
return {
store_uid: this.storeUid,
order_id: "VT20210917114023",
user_id: "phper",
ip: "127.0.0.1",
items: [
{
'id': "H01",
'serial_number': "A00021"
},
{
'id': "H01",
'serial_number': "A00022"
}
],
echo_0: "",
echo_1: "",
echo_2: "",
echo_3: "",
echo_4: "",
};
};
/**
* 取得服務位置
*/
StoreVoucherTransaction.prototype.getService = function () {
return {
service_name: "api",
cmd: "api/vouchertransaction"
};
};
/**
* AES 256 加密
*/
StoreVoucherTransaction.prototype.encrypt = function (fields, key) {
let eData = JSON.stringify(fields);
const blockSize = 16;
const iv = crypto.randomBytes(blockSize);
const encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
let tmpCipher = encryptor.update(Buffer.from(eData));
let finalCipher = encryptor.final();
const tempData = Buffer.concat([tmpCipher, finalCipher], tmpCipher.length + finalCipher.length);
let data = Buffer.concat([iv, tempData], iv.length + tempData.length).toString('base64');
return data;
};
/**
* 資料 POST 到主機
*/
StoreVoucherTransaction.prototype.post = function (postData) {
return new Promise((res, rej) => {
let options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
rejectUnauthorized: false
};
let send_process = httpRequest.request(this.url, options, (api_res) => {
let res_data = "";
api_res.on('data', (tmp_data) => {
res_data += tmp_data;
});
api_res.on('end', () => {
res(res_data);
});
});
send_process.write(JSON.stringify(postData));
send_process.end();
});
};
/**
* 取得送出欄位資料
*/
StoreVoucherTransaction.prototype.getPostData = function () {
return {
"store_uid": this.storeUid,
"service": this.encrypt(this.getService(), this.storeKey),
"encry_data": this.encrypt(this.getRawData(), this.storeKey)
};
};
/**
* 執行
*/
StoreVoucherTransaction.prototype.run = async function () {
json = await this.post(this.getPostData())
console.log(json);
};
StoreVoucherTransaction = new StoreVoucherTransaction();
StoreVoucherTransaction.run();
# -*- coding: utf-8 -*-
import json
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util import Padding
from Crypto.Random import get_random_bytes
"""特約商店串接-票券核銷交易
"""
class StoreVoucherTransaction:
# 特約商店商務代號
storeUid = "289151880142"
# 特約商店金鑰或認證碼
storeKey = b"g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0"
# 串接交易位置
url = "https://pay.usecase.cc/api/init"
def getRawData(self):
"""取得串接欄位資料
Returns:
{dict}: 欄位資料
"""
rawData = {
'store_uid': self.storeUid,
'order_id': "VT20210917114023",
'user_id': "phper",
'ip': "127.0.0.1",
'items': [
{
'id': "H01",
'serial_number': "A00021"
},
{
'id': "H01",
'serial_number': "A00022"
}
],
'echo_0': "",
'echo_1': "",
'echo_2': "",
'echo_3': "",
'echo_4': "",
}
return rawData
def getService(self):
"""取得服務位置
Returns:
{dict}: 服務位置資料
"""
return {
'service_name': 'api',
'cmd': 'api/vouchertransaction'
}
def encrypt(self, fields, key):
"""AES 256 加密
Args:
fields {dict}: 欄位資料
key {bytes}: AES金鑰
Returns:
{string}: 加密資料
"""
data = json.dumps(fields, separators=(',', ':'))
data = Padding.pad(data.encode('utf-8'), AES.block_size)
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.encrypt(data)
data = base64.b64encode(iv + data)
return data
def post(self, postData):
"""資料 POST 到主機
Args:
postData {dict}: 欄位資料
Returns:
{string}: JSON資料
"""
result = requests.post(self.url, postData)
return result.text
def getPostData(self):
"""取得送出欄位資料
Returns:
{dict}: 欄位資料
"""
postData = {
'store_uid': self.storeUid,
'service': self.encrypt(self.getService(), self.storeKey),
'encry_data': self.encrypt(self.getRawData(), self.storeKey)
}
return postData
def run(self):
"""執行
"""
json = self.post(self.getPostData())
print(json)
StoreVoucherTransaction = StoreVoucherTransaction()
StoreVoucherTransaction.run()
回傳 JSON 結構如下:
{
"code": "250",
"msg": "核銷成功",
"uid": "120",
"key": "9c485e5d2c902d915aada18b2ddae3ae",
"order_id": "VT20210917114023",
"reimbursement_date": "20210917134815",
"items": [
{
"id": "H01",
"serial_number": "A00021",
"is_paid": 1,
"sharing": {
"headquarters": 0.15,
"sales": 0.1,
"reimbursement": 0.75
},
"fee": {
"cost": 50,
"gateway_fee": 1,
"escrow_fee": 1,
"issuance_layer": 2,
"issuance_fee": 1,
"headquarters": 7,
"sales": 5,
"reimbursement": 35
}
},
{
"id": "H01",
"serial_number": "A00022",
"is_paid": 1,
"sharing": {
"headquarters": 0.15,
"sales": 0.1,
"reimbursement": 0.75
},
"fee": {
"cost": 50,
"gateway_fee": 1,
"escrow_fee": 1,
"issuance_layer": 2,
"issuance_fee": 1,
"headquarters": 7,
"sales": 5,
"reimbursement": 35
}
}
],
"echo_0": "",
"echo_1": "",
"echo_2": "",
"echo_3": "",
"echo_4": ""
}
透過系統做直接票券核銷時,可發動此票券核銷交易。「平台客戶」可使用自訂票券券號做核銷,而「一般客戶」使用MYTIX票券券號做核銷。
特約商店『票券核銷交易』參數說明
欄位 | 型態 | 說明 |
---|---|---|
store_uid | string(16) | 特約商店商務代號 |
service | text | {"service_name": "api", "cmd": "api\/vouchertransaction"} JSON格式,AES256加密資料 |
encry_data | text | 『票券核銷交易』欄位參考 JSON格式,AES256加密資料 |
『票券核銷交易』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
store_uid | string | 特約商店代碼 | 必填 |
order_id | string | 特約商店訂單編號 | 必填 |
user_id | string | 消費者帳號 | 必填 |
ip | string | 消費者來源 IP | 必填 |
items | array | 核銷票券資料 | 每筆『票券核銷項目(request)』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
『票券核銷交易』回傳欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
code | string | 交易回傳碼 | 『票券核銷交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
uid | string | 票券交易訂單UID | |
key | string | 交易驗証碼 | |
order_id | string | 特約商店訂單編號 | |
reimbursement_date | string | 核銷成功時間(格式(YYYYMMDDHHmmss)) | |
items | array | 核銷票券項目 | 每筆『票券核銷項目(response)』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
『票券核銷交易回傳資訊』回報欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
order_id | string | 特店商店的訂單編號 | |
uid | string | 票券交易訂單UID | |
key | string | 驗証碼 | |
code | string | 票券核銷狀態碼 | 『票券核銷交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
reimbursement_date | string | 核銷成功時間(格式(YYYYMMDDHHmmss)) | |
items | array | 核銷票券項目 | 每筆『票券核銷項目(response)』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
票券核銷查詢
<?php
/**
* 特約商店串接-票券核銷查詢
*/
final class StoreVoucherTransactionQuery
{
/**
* 特約商店商務代號
* @var string
*/
public $storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
* @var string
*/
public $storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
* @var string
*/
public $url = "https://pay.usecase.cc/api/init";
/**
* 取得串接欄位資料
* @return array
*/
public function getRawData()
{
$rawData = array();
$rawData['store_uid'] = $this->storeUid;
$rawData['order_id'] = "VT20210917134815";
return $rawData;
}
/**
* 取得服務位置
* @return array
*/
public function getService()
{
return array(
'service_name' => 'api',
'cmd' => 'api/voucherquery'
);
}
/**
* AES 256 加密
* @param array $fields
* @param string $key
* @return string
*/
public function encrypt($fields, $key)
{
$data = json_encode($fields);
$size = openssl_cipher_iv_length('AES-256-CBC');
$iv = openssl_random_pseudo_bytes($size);
$data = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$data = base64_encode($iv . $data);
return $data;
}
/**
* 資料 POST 到主機
* @param array $postData
* @return mixed
*/
public function post($postData = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 取得送出欄位資料
* @return array
*/
public function getPostData ()
{
$postData = array();
$postData['store_uid'] = $this->storeUid;
$postData['service'] = $this->encrypt($this->getService(), $this->storeKey);
$postData['encry_data'] = $this->encrypt($this->getRawData(), $this->storeKey);
return $postData;
}
/**
* 執行
*/
public function run()
{
$json = $this->post($this->getPostData());
echo $json;
}
}
$StoreVoucherTransactionQuery = new StoreVoucherTransactionQuery();
$StoreVoucherTransactionQuery->run();
?>
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Dynamic;
/// <summary>
/// 建議使用.net framework4.5以上版本
/// 1.請透過NuGet安裝Newtonsoft.Json,若.net framework小於4.5請安裝Newtonsoft.Json 7.0以下版本
/// 2.若貴司.net framework小於4 可能無法使用dynamic,若是自行組陣列
/// 3.若貴司.net framework小於3.5 可能無法使用AES類別,若是參閱微軟網站使用較舊方式進行加密
/// 4.若貴司.net framework小於3.5 可能沒有Linq可使用,故data只能組字串,Newtonsoft.Json也可能無法使用
/// </summary>
namespace MyPay {
/// <summary>
/// 特約商店串接-票券核銷查詢
/// </summary>
public class StoreVoucherTransactionQuery {
/// <summary>
/// 特約商店商務代號
/// </summary>
public string storeUid = "289151880142";
/// <summary>
/// 特約商店金鑰或認證碼
/// </summary>
public string storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/// <summary>
/// 串接交易位置
/// </summary>
public string url = "https://pay.usecase.cc/api/init";
/// <summary>
/// 執行
/// </summary>
static void Main() {
StoreVoucherTransactionQuery simulator = new StoreVoucherTransactionQuery();
//僅限走https的Tls 1.2以上版本
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
//發送至遠端
var result = simulator.Post(simulator.GetPostData());
System.Console.WriteLine(result);
}
/// <summary>
/// 取得串接欄位資料
/// </summary>
private dynamic GetRawData() {
dynamic rawData = new ExpandoObject();
rawData.store_uid = this.storeUid;
rawData.order_id = "VT20210917134815";
return rawData;
}
/// <summary>
/// 取得服務位置
/// </summary>
private ServiceRequest GetService() {
ServiceRequest rawData = new ServiceRequest();
rawData.service_name = "api";
rawData.cmd = "api/voucherquery";
return rawData;
}
/// <summary>
/// 取得送出欄位資料
/// </summary>
private NameValueCollection GetPostData() {
string data_json = JsonConvert.SerializeObject(GetRawData(), Formatting.None);
string svr_json = JsonConvert.SerializeObject(GetService(), Formatting.None);; //依API種類調整
//產生AES向量
var IV = GetBytesIV();
//進行加密
var data_encode = Encrypt(data_json, this.storeKey, IV);
var svr_encode = Encrypt(svr_json, this.storeKey, IV);
//請注意使用的 Http Post 套件是否會自動加上UrlEncode,本Post範例為原始方式,故須加上UrlEncode
//若自行使用的套件會自動補上UrlEncode,則請忽略下面的UrlEncode,避免做了兩次UrlEncode
string data_toUrlEncode = HttpUtility.UrlEncode(data_encode);
string svr_toUrlEncode = HttpUtility.UrlEncode(svr_encode);
NameValueCollection postData = new NameValueCollection();
postData["store_uid"] = this.storeUid;
postData["service"] = svr_toUrlEncode;
postData["encry_data"] = data_toUrlEncode;
return postData;
}
/// <summary>
/// AES 256 加密
/// </summary>
/// <param name="data"></param>
/// <param name="key"></param>
/// <param name="byteIV"></param>
/// <returns></returns>
private string Encrypt(string data, string key, byte[] byteIV) {
var byteKey = System.Text.Encoding.UTF8.GetBytes(key);
var enBytes = AES_Encrypt(data, byteKey, byteIV);
return Convert.ToBase64String(BytesAdd(byteIV, enBytes));
}
/// <summary>
/// AES 256 加密處理
/// </summary>
/// <param name="original"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] AES_Encrypt(string original, byte[] key, byte[] iv) {
try {
var data = Encoding.UTF8.GetBytes(original);
var cipher = Aes.Create().CreateEncryptor(key, iv);
var de = cipher.TransformFinalBlock(data, 0, data.Length);
return de;
} catch {
return null;
}
}
/// <summary>
/// 轉換Bytes
/// </summary>
/// <param name="a"></param>
/// <param name="arryB"></param>
/// <returns></returns>
private byte[] BytesAdd(byte[] a, params byte[][] arryB) {
List < byte > c = new List < byte > ();
c.AddRange(a);
arryB.ToList().ForEach(b => {
c.AddRange(b);
});
return c.ToArray();
}
/// <summary>
/// 產生AES的IV
/// </summary>
/// <returns></returns>
private static byte[] GetBytesIV() {
var aes = System.Security.Cryptography.AesCryptoServiceProvider.Create();
aes.KeySize = 256;
aes.GenerateIV();
return aes.IV;
}
/// <summary>
/// 資料 POST 到主機
/// </summary>
/// <param name="pars"></param>
/// <returns></returns>
private string Post(NameValueCollection pars) {
string result = string.Empty;
string param = string.Empty;
if (pars.Count > 0) {
pars.AllKeys.ToList().ForEach(key => {
param += key + "=" + pars[key] + "&";
});
if (param[param.Length - 1] == '&') {
param = param.Remove(param.Length - 1);
}
}
byte[] bs = Encoding.UTF8.GetBytes(param);
try {
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create(this.url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = bs.Length;
using(Stream reqStream = req.GetRequestStream()) {
reqStream.Write(bs, 0, bs.Length);
}
using(WebResponse wr = req.GetResponse()) {
Encoding myEncoding = Encoding.GetEncoding("UTF-8");
using(StreamReader myStreamReader = new StreamReader(wr.GetResponseStream(), myEncoding)) {
result = myStreamReader.ReadToEnd();
}
}
req = null;
} catch (WebException ex) {
throw new WebException(ex.Message + "params : " + param, ex, ex.Status, ex.Response);
}
return result;
}
}
/// <summary>
/// 串接服務請求欄位
/// </summary>
public class ServiceRequest {
public string service_name { get; set; }
public string cmd { get; set; }
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.security.SecureRandom;
/**
* 特約商店串接-票券核銷查詢
* 1. jackson-core 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
* 2. jackson-databind 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
* 3. jackson-annotations 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
* 4. Spongy Castle 下載 https://mvnrepository.com/artifact/com.madgag.spongycastle/core
*/
public class StoreVoucherTransactionQuery {
/**
* 特約商店商務代號
*/
String storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
*/
String storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
*/
String url = "https://pay.usecase.cc/api/init";
/**
* 執行
* @param args
*/
public static void main(String[] args) {
StoreVoucherTransactionQuery simulator = new StoreVoucherTransactionQuery();
String json = simulator.post(simulator.getPostData());
System.out.print(json);
}
@SuppressWarnings(value = { "unchecked", "deprecation" })
/**
* 取得串接欄位資料
* @return 串接原始資料
*/
public Map getRawData() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("store_uid", this.storeUid);
rawData.put("order_id", "VT20210917134815");
return rawData;
}
/**
* 取得服務位置
* @return 串接服務資料
*/
public Map getService() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("service_name", "api");
rawData.put("cmd", "api/voucherquery");
return rawData;
}
/**
* AES 256 加密
* @param rawData 原始資料
* @param AesKey AES256金鑰字串
* @return 轉換成Base64資料
*/
public String encrypt(Map rawData, String AesKey) {
try {
ObjectMapper objMapper = new ObjectMapper();
byte[] data = objMapper.writeValueAsString(rawData).getBytes(UTF_8);
byte[] key = AesKey.getBytes(UTF_8);
// 16 bytes is the IV size for AES256
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()));
// Random iv
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[16];
rng.nextBytes(ivBytes);
cipher.init(true, new ParametersWithIV(new KeyParameter(key),
ivBytes));
byte[] outBuf = new byte[cipher.getOutputSize(data.length)];
int processed = cipher
.processBytes(data, 0, data.length, outBuf, 0);
processed += cipher.doFinal(outBuf, processed);
byte[] outBuf2 = new byte[processed + 16]; // Make room for iv
System.arraycopy(ivBytes, 0, outBuf2, 0, 16); // Add iv
System.arraycopy(outBuf, 0, outBuf2, 16, processed);
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(outBuf2);
return base64;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 資料 POST 到主機
* @param qstr 串接資料
* @return 服務回傳JSON資訊
*/
public String post(String qstr) {
String result = "";
try {
// 資料
byte[] qstr_bytes = qstr.getBytes(StandardCharsets.UTF_8);
URL iurl = new URL(this.url);
SSLContext sc = SSLContext.getInstance("TLSv1.2"); // $NON-NLS-1$
sc.init(null, null, new java.security.SecureRandom());
HttpsURLConnection con = (HttpsURLConnection) iurl.openConnection();
con.setSSLSocketFactory(sc.getSocketFactory());
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
String.valueOf(qstr_bytes.length));
con.setRequestProperty("Accept-Charset", "UTF-8");
con.setDoOutput(true);
con.setDoInput(true);
con.getOutputStream()
.write(qstr.getBytes(Charset.forName("UTF-8")));
con.getOutputStream().flush();
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine + "\r\n");
}
try {
result = response.toString();
} finally {
in.close();
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return result;
}
/**
* 取得送出欄位資料
* @return POST完整資料
*/
public String getPostData() {
String postData = "";
try {
// Base64需要使用UrlEncode做傳輸
String data_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getRawData(), this.storeKey), "UTF-8");
String svr_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getService(), this.storeKey), "UTF-8");
postData = "store_uid=" + this.storeUid + "&service="
+ svr_toUrlEncode + "&encry_data=" + data_toUrlEncode;
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return postData;
}
}
const crypto = require('crypto');
const httpRequest = require('https');
/**
* 特約商店串接-票券核銷查詢
*/
function StoreVoucherTransactionQuery() {
// 特約商店商務代號
this.storeUid = "289151880142";
// 特約商店金鑰或認證碼
this.storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
// 串接交易位置
this.url = "https://pay.usecase.cc/api/init";
};
/**
* 取得串接欄位資料
*/
StoreVoucherTransactionQuery.prototype.getRawData = function () {
return {
store_uid: this.storeUid,
order_id: "VT20210917134815",
};
};
/**
* 取得服務位置
*/
StoreVoucherTransactionQuery.prototype.getService = function () {
return {
service_name: "api",
cmd: "api/voucherquery"
};
};
/**
* AES 256 加密
*/
StoreVoucherTransactionQuery.prototype.encrypt = function (fields, key) {
let eData = JSON.stringify(fields);
const blockSize = 16;
const iv = crypto.randomBytes(blockSize);
const encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
let tmpCipher = encryptor.update(Buffer.from(eData));
let finalCipher = encryptor.final();
const tempData = Buffer.concat([tmpCipher, finalCipher], tmpCipher.length + finalCipher.length);
let data = Buffer.concat([iv, tempData], iv.length + tempData.length).toString('base64');
return data;
};
/**
* 資料 POST 到主機
*/
StoreVoucherTransactionQuery.prototype.post = function (postData) {
return new Promise((res, rej) => {
let options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
rejectUnauthorized: false
};
let send_process = httpRequest.request(this.url, options, (api_res) => {
let res_data = "";
api_res.on('data', (tmp_data) => {
res_data += tmp_data;
});
api_res.on('end', () => {
res(res_data);
});
});
send_process.write(JSON.stringify(postData));
send_process.end();
});
};
/**
* 取得送出欄位資料
*/
StoreVoucherTransactionQuery.prototype.getPostData = function () {
return {
"store_uid": this.storeUid,
"service": this.encrypt(this.getService(), this.storeKey),
"encry_data": this.encrypt(this.getRawData(), this.storeKey)
};
};
/**
* 執行
*/
StoreVoucherTransactionQuery.prototype.run = async function () {
json = await this.post(this.getPostData())
console.log(json);
};
StoreVoucherTransactionQuery = new StoreVoucherTransactionQuery();
StoreVoucherTransactionQuery.run();
# -*- coding: utf-8 -*-
import json
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util import Padding
from Crypto.Random import get_random_bytes
"""特約商店串接-票券核銷查詢
"""
class StoreVoucherTransactionQuery:
# 特約商店商務代號
storeUid = "289151880142"
# 特約商店金鑰或認證碼
storeKey = b"g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0"
# 串接交易位置
url = "https://pay.usecase.cc/api/init"
def getRawData(self):
"""取得串接欄位資料
Returns:
{dict}: 欄位資料
"""
rawData = {
'store_uid': self.storeUid,
'order_id': "VT20210917134815",
}
return rawData
def getService(self):
"""取得服務位置
Returns:
{dict}: 服務位置資料
"""
return {
'service_name': 'api',
'cmd': 'api/voucherquery'
}
def encrypt(self, fields, key):
"""AES 256 加密
Args:
fields {dict}: 欄位資料
key {bytes}: AES金鑰
Returns:
{string}: 加密資料
"""
data = json.dumps(fields, separators=(',', ':'))
data = Padding.pad(data.encode('utf-8'), AES.block_size)
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.encrypt(data)
data = base64.b64encode(iv + data)
return data
def post(self, postData):
"""資料 POST 到主機
Args:
postData {dict}: 欄位資料
Returns:
{string}: JSON資料
"""
result = requests.post(self.url, postData)
return result.text
def getPostData(self):
"""取得送出欄位資料
Returns:
{dict}: 欄位資料
"""
postData = {
'store_uid': self.storeUid,
'service': self.encrypt(self.getService(), self.storeKey),
'encry_data': self.encrypt(self.getRawData(), self.storeKey)
}
return postData
def run(self):
"""執行
"""
json = self.post(self.getPostData())
print(json)
StoreVoucherTransactionQuery = StoreVoucherTransactionQuery()
StoreVoucherTransactionQuery.run()
回傳 JSON 結構如下:
{
"code": "B200",
"msg": "執行成功",
"content": {
"order_id": "VT20210917134815",
"uid": "120",
"key": "9c485e5d2c902d915aada18b2ddae3ae",
"code": "250",
"msg": "核銷成功",
"reimbursement_date": "20210917134815",
"items": [
{
"id": "H01",
"serial_number": "A00005",
"is_paid": 1,
"sharing": {
"headquarters": 0.15,
"sales": 0.1,
"reimbursement": 0.75
},
"fee": {
"cost": 50,
"gateway_fee": 1,
"escrow_fee": 1,
"issuance_layer": 2,
"issuance_fee": 1,
"headquarters": 7,
"sales": 5,
"reimbursement": 35
}
},
{
"id": "H01",
"serial_number": "A00006",
"is_paid": 1,
"sharing": {
"headquarters": 0.15,
"sales": 0.1,
"reimbursement": 0.75
},
"fee": {
"cost": 50,
"gateway_fee": 1,
"escrow_fee": 1,
"issuance_layer": 2,
"issuance_fee": 1,
"headquarters": 7,
"sales": 5,
"reimbursement": 35
}
}
],
"echo_0": "",
"echo_1": "",
"echo_2": "",
"echo_3": "",
"echo_4": ""
}
}
當發動票券核銷交易後,若發生網路中斷或重新查詢票券核銷訂單之核銷結果,可發動此查詢確認核銷狀態與結果。
特約商店『票券核銷查詢』參數說明
欄位 | 型態 | 說明 |
---|---|---|
store_uid | string(16) | 特約商店商務代號 |
service | text | {"service_name": "api", "cmd": "api\/voucherquery"} JSON格式,AES256加密資料 |
encry_data | text | 『票券核銷查詢』欄位參考 JSON格式,AES256加密資料 |
『票券核銷查詢』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
store_uid | string | 特約商店代碼 | 必填 |
order_id | string | 特約商店訂單編號 | 必填 |
『票券核銷查詢』回傳欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
code | string | 票券交易狀態碼 | 『票券核銷交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
content | object | 查詢內容 | 『票券核銷訂單查詢內容回傳』欄位參考 |
『票券核銷訂單查詢內容回傳』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
order_id | string | 特店商店的訂單編號 | |
uid | string | 票券交易訂單UID | |
key | string | 驗証碼 | |
code | string | 狀態碼 | |
msg | string | 回傳訊息 | |
reimbursement_date | string | 核銷成功時間(格式(YYYYMMDDHHmmss)) | |
items | array | 核銷票券項目 | 每筆『票券核銷項目(response)』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
票券退款交易
<?php
/**
* 特約商店串接-票券退款交易
*/
final class StoreVoucherRefund
{
/**
* 特約商店商務代號
* @var string
*/
public $storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
* @var string
*/
public $storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
* @var string
*/
public $url = "https://pay.usecase.cc/api/init";
/**
* 取得串接欄位資料
* @return array
*/
public function getRawData()
{
$rawData = array();
$rawData['store_uid'] = $this->storeUid;
$rawData['order_id'] = "VR20211001135554";
$rawData['items'] = [
[
'id' => 'H01',
'serial_number' => 'A00052'
],
[
'id' => 'H01',
'serial_number' => 'A00053'
]
];
$rawData['echo_0'] = "";
$rawData['echo_1'] = "";
$rawData['echo_2'] = "";
$rawData['echo_3'] = "";
$rawData['echo_4'] = "";
return $rawData;
}
/**
* 取得服務位置
* @return array
*/
public function getService()
{
return array(
'service_name' => 'api',
'cmd' => 'api/voucherrefund'
);
}
/**
* AES 256 加密
* @param array $fields
* @param string $key
* @return string
*/
public function encrypt($fields, $key)
{
$data = json_encode($fields);
$size = openssl_cipher_iv_length('AES-256-CBC');
$iv = openssl_random_pseudo_bytes($size);
$data = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$data = base64_encode($iv . $data);
return $data;
}
/**
* 資料 POST 到主機
* @param array $postData
* @return mixed
*/
public function post($postData = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 取得送出欄位資料
* @return array
*/
public function getPostData ()
{
$postData = array();
$postData['store_uid'] = $this->storeUid;
$postData['service'] = $this->encrypt($this->getService(), $this->storeKey);
$postData['encry_data'] = $this->encrypt($this->getRawData(), $this->storeKey);
return $postData;
}
/**
* 執行
*/
public function run()
{
$json = $this->post($this->getPostData());
echo $json;
}
}
$StoreVoucherRefund = new StoreVoucherRefund();
$StoreVoucherRefund->run();
?>
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Dynamic;
/// <summary>
/// 建議使用.net framework4.5以上版本
/// 1.請透過NuGet安裝Newtonsoft.Json,若.net framework小於4.5請安裝Newtonsoft.Json 7.0以下版本
/// 2.若貴司.net framework小於4 可能無法使用dynamic,若是自行組陣列
/// 3.若貴司.net framework小於3.5 可能無法使用AES類別,若是參閱微軟網站使用較舊方式進行加密
/// 4.若貴司.net framework小於3.5 可能沒有Linq可使用,故data只能組字串,Newtonsoft.Json也可能無法使用
/// </summary>
namespace MyPay {
/// <summary>
/// 特約商店串接-票券退款交易
/// </summary>
public class StoreVoucherRefund {
/// <summary>
/// 特約商店商務代號
/// </summary>
public string storeUid = "289151880142";
/// <summary>
/// 特約商店金鑰或認證碼
/// </summary>
public string storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/// <summary>
/// 串接交易位置
/// </summary>
public string url = "https://pay.usecase.cc/api/init";
/// <summary>
/// 執行
/// </summary>
static void Main() {
StoreVoucherRefund simulator = new StoreVoucherRefund();
//僅限走https的Tls 1.2以上版本
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
//發送至遠端
var result = simulator.Post(simulator.GetPostData());
System.Console.WriteLine(result);
}
/// <summary>
/// 取得串接欄位資料
/// </summary>
private dynamic GetRawData() {
ArrayList items = new ArrayList();
dynamic item1 = new ExpandoObject();
item1.id = "H01";
item1.serial_number = "A00052";
items.Add(item1);
dynamic item2 = new ExpandoObject();
item2.id = "H01";
item2.serial_number = "A00053";
items.Add(item2);
dynamic rawData = new ExpandoObject();
rawData.store_uid = this.storeUid;
rawData.order_id = "VR20211001135554";
rawData.items = items;
rawData.echo_0 = "";
rawData.echo_1 = "";
rawData.echo_2 = "";
rawData.echo_3 = "";
rawData.echo_4 = "";
return rawData;
}
/// <summary>
/// 取得服務位置
/// </summary>
private ServiceRequest GetService() {
ServiceRequest rawData = new ServiceRequest();
rawData.service_name = "api";
rawData.cmd = "api/voucherrefund";
return rawData;
}
/// <summary>
/// 取得送出欄位資料
/// </summary>
private NameValueCollection GetPostData() {
string data_json = JsonConvert.SerializeObject(GetRawData(), Formatting.None);
string svr_json = JsonConvert.SerializeObject(GetService(), Formatting.None);; //依API種類調整
//產生AES向量
var IV = GetBytesIV();
//進行加密
var data_encode = Encrypt(data_json, this.storeKey, IV);
var svr_encode = Encrypt(svr_json, this.storeKey, IV);
//請注意使用的 Http Post 套件是否會自動加上UrlEncode,本Post範例為原始方式,故須加上UrlEncode
//若自行使用的套件會自動補上UrlEncode,則請忽略下面的UrlEncode,避免做了兩次UrlEncode
string data_toUrlEncode = HttpUtility.UrlEncode(data_encode);
string svr_toUrlEncode = HttpUtility.UrlEncode(svr_encode);
NameValueCollection postData = new NameValueCollection();
postData["store_uid"] = this.storeUid;
postData["service"] = svr_toUrlEncode;
postData["encry_data"] = data_toUrlEncode;
return postData;
}
/// <summary>
/// AES 256 加密
/// </summary>
/// <param name="data"></param>
/// <param name="key"></param>
/// <param name="byteIV"></param>
/// <returns></returns>
private string Encrypt(string data, string key, byte[] byteIV) {
var byteKey = System.Text.Encoding.UTF8.GetBytes(key);
var enBytes = AES_Encrypt(data, byteKey, byteIV);
return Convert.ToBase64String(BytesAdd(byteIV, enBytes));
}
/// <summary>
/// AES 256 加密處理
/// </summary>
/// <param name="original"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] AES_Encrypt(string original, byte[] key, byte[] iv) {
try {
var data = Encoding.UTF8.GetBytes(original);
var cipher = Aes.Create().CreateEncryptor(key, iv);
var de = cipher.TransformFinalBlock(data, 0, data.Length);
return de;
} catch {
return null;
}
}
/// <summary>
/// 轉換Bytes
/// </summary>
/// <param name="a"></param>
/// <param name="arryB"></param>
/// <returns></returns>
private byte[] BytesAdd(byte[] a, params byte[][] arryB) {
List < byte > c = new List < byte > ();
c.AddRange(a);
arryB.ToList().ForEach(b => {
c.AddRange(b);
});
return c.ToArray();
}
/// <summary>
/// 產生AES的IV
/// </summary>
/// <returns></returns>
private static byte[] GetBytesIV() {
var aes = System.Security.Cryptography.AesCryptoServiceProvider.Create();
aes.KeySize = 256;
aes.GenerateIV();
return aes.IV;
}
/// <summary>
/// 資料 POST 到主機
/// </summary>
/// <param name="pars"></param>
/// <returns></returns>
private string Post(NameValueCollection pars) {
string result = string.Empty;
string param = string.Empty;
if (pars.Count > 0) {
pars.AllKeys.ToList().ForEach(key => {
param += key + "=" + pars[key] + "&";
});
if (param[param.Length - 1] == '&') {
param = param.Remove(param.Length - 1);
}
}
byte[] bs = Encoding.UTF8.GetBytes(param);
try {
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create(this.url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = bs.Length;
using(Stream reqStream = req.GetRequestStream()) {
reqStream.Write(bs, 0, bs.Length);
}
using(WebResponse wr = req.GetResponse()) {
Encoding myEncoding = Encoding.GetEncoding("UTF-8");
using(StreamReader myStreamReader = new StreamReader(wr.GetResponseStream(), myEncoding)) {
result = myStreamReader.ReadToEnd();
}
}
req = null;
} catch (WebException ex) {
throw new WebException(ex.Message + "params : " + param, ex, ex.Status, ex.Response);
}
return result;
}
}
/// <summary>
/// 串接服務請求欄位
/// </summary>
public class ServiceRequest {
public string service_name { get; set; }
public string cmd { get; set; }
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.security.SecureRandom;
/**
* 特約商店串接-票券退款交易
* 1. jackson-core 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
* 2. jackson-databind 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
* 3. jackson-annotations 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
* 4. Spongy Castle 下載 https://mvnrepository.com/artifact/com.madgag.spongycastle/core
*/
public class StoreVoucherRefund {
/**
* 特約商店商務代號
*/
String storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
*/
String storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
*/
String url = "https://pay.usecase.cc/api/init";
/**
* 執行
* @param args
*/
public static void main(String[] args) {
StoreVoucherRefund simulator = new StoreVoucherRefund();
String json = simulator.post(simulator.getPostData());
System.out.print(json);
}
@SuppressWarnings(value = { "unchecked", "deprecation" })
/**
* 取得串接欄位資料
* @return 串接原始資料
*/
public Map getRawData() {
ArrayList items = new ArrayList();
Map<Object, Object> item1 = new HashMap<Object, Object>();
item1.put("id", "H01");
item1.put("serial_number", "A00052");
items.add(item1);
Map<Object, Object> item2 = new HashMap<Object, Object>();
item2.put("id", "H01");
item2.put("serial_number", "A00053");
items.add(item2);
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("store_uid", this.storeUid);
rawData.put("order_id", "VR20211001135554");
rawData.put("items", items);
rawData.put("echo_0", "");
rawData.put("echo_1", "");
rawData.put("echo_2", "");
rawData.put("echo_3", "");
rawData.put("echo_4", "");
return rawData;
}
/**
* 取得服務位置
* @return 串接服務資料
*/
public Map getService() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("service_name", "api");
rawData.put("cmd", "api/voucherrefund");
return rawData;
}
/**
* AES 256 加密
* @param rawData 原始資料
* @param AesKey AES256金鑰字串
* @return 轉換成Base64資料
*/
public String encrypt(Map rawData, String AesKey) {
try {
ObjectMapper objMapper = new ObjectMapper();
byte[] data = objMapper.writeValueAsString(rawData).getBytes(UTF_8);
byte[] key = AesKey.getBytes(UTF_8);
// 16 bytes is the IV size for AES256
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()));
// Random iv
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[16];
rng.nextBytes(ivBytes);
cipher.init(true, new ParametersWithIV(new KeyParameter(key),
ivBytes));
byte[] outBuf = new byte[cipher.getOutputSize(data.length)];
int processed = cipher
.processBytes(data, 0, data.length, outBuf, 0);
processed += cipher.doFinal(outBuf, processed);
byte[] outBuf2 = new byte[processed + 16]; // Make room for iv
System.arraycopy(ivBytes, 0, outBuf2, 0, 16); // Add iv
System.arraycopy(outBuf, 0, outBuf2, 16, processed);
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(outBuf2);
return base64;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 資料 POST 到主機
* @param qstr 串接資料
* @return 服務回傳JSON資訊
*/
public String post(String qstr) {
String result = "";
try {
// 資料
byte[] qstr_bytes = qstr.getBytes(StandardCharsets.UTF_8);
URL iurl = new URL(this.url);
SSLContext sc = SSLContext.getInstance("TLSv1.2"); // $NON-NLS-1$
sc.init(null, null, new java.security.SecureRandom());
HttpsURLConnection con = (HttpsURLConnection) iurl.openConnection();
con.setSSLSocketFactory(sc.getSocketFactory());
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
String.valueOf(qstr_bytes.length));
con.setRequestProperty("Accept-Charset", "UTF-8");
con.setDoOutput(true);
con.setDoInput(true);
con.getOutputStream()
.write(qstr.getBytes(Charset.forName("UTF-8")));
con.getOutputStream().flush();
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine + "\r\n");
}
try {
result = response.toString();
} finally {
in.close();
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return result;
}
/**
* 取得送出欄位資料
* @return POST完整資料
*/
public String getPostData() {
String postData = "";
try {
// Base64需要使用UrlEncode做傳輸
String data_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getRawData(), this.storeKey), "UTF-8");
String svr_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getService(), this.storeKey), "UTF-8");
postData = "store_uid=" + this.storeUid + "&service="
+ svr_toUrlEncode + "&encry_data=" + data_toUrlEncode;
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return postData;
}
}
const crypto = require('crypto');
const httpRequest = require('https');
/**
* 特約商店串接-票券退款交易
*/
function StoreVoucherRefund() {
// 特約商店商務代號
this.storeUid = "289151880142";
// 特約商店金鑰或認證碼
this.storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
// 串接交易位置
this.url = "https://pay.usecase.cc/api/init";
};
/**
* 取得串接欄位資料
*/
StoreVoucherRefund.prototype.getRawData = function () {
return {
store_uid: this.storeUid,
order_id: "VR20211001135554",
items: [
{
'id': "H01",
'serial_number': "A00052"
},
{
'id': "H01",
'serial_number': "A00053"
}
],
echo_0: "",
echo_1: "",
echo_2: "",
echo_3: "",
echo_4: "",
};
};
/**
* 取得服務位置
*/
StoreVoucherRefund.prototype.getService = function () {
return {
service_name: "api",
cmd: "api/voucherrefund"
};
};
/**
* AES 256 加密
*/
StoreVoucherRefund.prototype.encrypt = function (fields, key) {
let eData = JSON.stringify(fields);
const blockSize = 16;
const iv = crypto.randomBytes(blockSize);
const encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
let tmpCipher = encryptor.update(Buffer.from(eData));
let finalCipher = encryptor.final();
const tempData = Buffer.concat([tmpCipher, finalCipher], tmpCipher.length + finalCipher.length);
let data = Buffer.concat([iv, tempData], iv.length + tempData.length).toString('base64');
return data;
};
/**
* 資料 POST 到主機
*/
StoreVoucherRefund.prototype.post = function (postData) {
return new Promise((res, rej) => {
let options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
rejectUnauthorized: false
};
let send_process = httpRequest.request(this.url, options, (api_res) => {
let res_data = "";
api_res.on('data', (tmp_data) => {
res_data += tmp_data;
});
api_res.on('end', () => {
res(res_data);
});
});
send_process.write(JSON.stringify(postData));
send_process.end();
});
};
/**
* 取得送出欄位資料
*/
StoreVoucherRefund.prototype.getPostData = function () {
return {
"store_uid": this.storeUid,
"service": this.encrypt(this.getService(), this.storeKey),
"encry_data": this.encrypt(this.getRawData(), this.storeKey)
};
};
/**
* 執行
*/
StoreVoucherRefund.prototype.run = async function () {
json = await this.post(this.getPostData())
console.log(json);
};
StoreVoucherRefund = new StoreVoucherRefund();
StoreVoucherRefund.run();
# -*- coding: utf-8 -*-
import json
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util import Padding
from Crypto.Random import get_random_bytes
"""特約商店串接-票券退款交易
"""
class StoreVoucherRefund:
# 特約商店商務代號
storeUid = "289151880142"
# 特約商店金鑰或認證碼
storeKey = b"g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0"
# 串接交易位置
url = "https://pay.usecase.cc/api/init"
def getRawData(self):
"""取得串接欄位資料
Returns:
{dict}: 欄位資料
"""
rawData = {
'store_uid': self.storeUid,
'order_id': "VR20211001135554",
'items': [
{
'id': "H01",
'serial_number': "A00052"
},
{
'id': "H01",
'serial_number': "A00053"
}
],
'echo_0': "",
'echo_1': "",
'echo_2': "",
'echo_3': "",
'echo_4': "",
}
return rawData
def getService(self):
"""取得服務位置
Returns:
{dict}: 服務位置資料
"""
return {
'service_name': 'api',
'cmd': 'api/voucherrefund'
}
def encrypt(self, fields, key):
"""AES 256 加密
Args:
fields {dict}: 欄位資料
key {bytes}: AES金鑰
Returns:
{string}: 加密資料
"""
data = json.dumps(fields, separators=(',', ':'))
data = Padding.pad(data.encode('utf-8'), AES.block_size)
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.encrypt(data)
data = base64.b64encode(iv + data)
return data
def post(self, postData):
"""資料 POST 到主機
Args:
postData {dict}: 欄位資料
Returns:
{string}: JSON資料
"""
result = requests.post(self.url, postData)
return result.text
def getPostData(self):
"""取得送出欄位資料
Returns:
{dict}: 欄位資料
"""
postData = {
'store_uid': self.storeUid,
'service': self.encrypt(self.getService(), self.storeKey),
'encry_data': self.encrypt(self.getRawData(), self.storeKey)
}
return postData
def run(self):
"""執行
"""
json = self.post(self.getPostData())
print(json)
StoreVoucherRefund = StoreVoucherRefund()
StoreVoucherRefund.run()
回傳 JSON 結構如下:
{
"code": "230",
"msg": "退款成功",
"uid": "129",
"key": "2d981e1f4a5dda0d3f68eb27b5a23346",
"order_id": "VR20211001112859",
"refund_date": "20211001112900",
"items": [
{
"id": "H01",
"serial_number": "A00052"
}
],
"echo_0": "",
"echo_1": "",
"echo_2": "",
"echo_3": "",
"echo_4": ""
}
對未進行使用的票券,發動退款動作。票券購買者需為同一帳號。
特約商店『票券退款交易』參數說明
欄位 | 型態 | 說明 |
---|---|---|
store_uid | string(16) | 特約商店商務代號 |
service | text | {"service_name": "api", "cmd": "api\/voucherrefund"} JSON格式,AES256加密資料 |
encry_data | text | 『票券退款交易』欄位參考 JSON格式,AES256加密資料 |
『票券退款交易』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
store_uid | string | 特約商店代碼 | 必填 |
order_id | string | 特約商店退款訂單編號 | 必填 |
items | array | 票券退款資料 | 必填 每筆『票券退款項目』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
『票券退款交易』回傳欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
code | string | 交易回傳碼 | 『票券退款交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
uid | string | 票券退款交易訂單UID | |
key | string | 交易驗証碼 | |
order_id | string | 特約商店退款訂單編號 | |
refund_date | string | 退款成功時間(格式(YYYYMMDDHHmmss)) | |
items | array | 退款票券項目 | 每筆『票券退款項目』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
『票券退款通知』回報欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
order_id | string | 特店商店的退款訂單編號 | |
uid | string | 票券退款訂單UID | |
key | string | 驗証碼 | |
code | string | 票券退款狀態碼 | 『票券退款交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
refund_date | string | 退款成功時間(格式(YYYYMMDDHHmmss)) | |
items | array | 退款票券項目 | 每筆『票券退款項目』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
票券退款交易查詢
<?php
/**
* 特約商店串接-票券退款交易查詢
*/
final class StoreVoucherRefundQuery
{
/**
* 特約商店商務代號
* @var string
*/
public $storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
* @var string
*/
public $storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
* @var string
*/
public $url = "https://pay.usecase.cc/api/init";
/**
* 取得串接欄位資料
* @return array
*/
public function getRawData()
{
$rawData = array();
$rawData['store_uid'] = $this->storeUid;
$rawData['order_id'] = "VR20211001112859";
return $rawData;
}
/**
* 取得服務位置
* @return array
*/
public function getService()
{
return array(
'service_name' => 'api',
'cmd' => 'api/voucherrefundquery'
);
}
/**
* AES 256 加密
* @param array $fields
* @param string $key
* @return string
*/
public function encrypt($fields, $key)
{
$data = json_encode($fields);
$size = openssl_cipher_iv_length('AES-256-CBC');
$iv = openssl_random_pseudo_bytes($size);
$data = openssl_encrypt($data, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
$data = base64_encode($iv . $data);
return $data;
}
/**
* 資料 POST 到主機
* @param array $postData
* @return mixed
*/
public function post($postData = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 取得送出欄位資料
* @return array
*/
public function getPostData ()
{
$postData = array();
$postData['store_uid'] = $this->storeUid;
$postData['service'] = $this->encrypt($this->getService(), $this->storeKey);
$postData['encry_data'] = $this->encrypt($this->getRawData(), $this->storeKey);
return $postData;
}
/**
* 執行
*/
public function run()
{
$json = $this->post($this->getPostData());
echo $json;
}
}
$StoreVoucherRefundQuery = new StoreVoucherRefundQuery();
$StoreVoucherRefundQuery->run();
?>
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Dynamic;
/// <summary>
/// 建議使用.net framework4.5以上版本
/// 1.請透過NuGet安裝Newtonsoft.Json,若.net framework小於4.5請安裝Newtonsoft.Json 7.0以下版本
/// 2.若貴司.net framework小於4 可能無法使用dynamic,若是自行組陣列
/// 3.若貴司.net framework小於3.5 可能無法使用AES類別,若是參閱微軟網站使用較舊方式進行加密
/// 4.若貴司.net framework小於3.5 可能沒有Linq可使用,故data只能組字串,Newtonsoft.Json也可能無法使用
/// </summary>
namespace MyPay {
/// <summary>
/// 特約商店串接-票券退款交易查詢
/// </summary>
public class StoreVoucherRefundQuery {
/// <summary>
/// 特約商店商務代號
/// </summary>
public string storeUid = "289151880142";
/// <summary>
/// 特約商店金鑰或認證碼
/// </summary>
public string storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/// <summary>
/// 串接交易位置
/// </summary>
public string url = "https://pay.usecase.cc/api/init";
/// <summary>
/// 執行
/// </summary>
static void Main() {
StoreVoucherRefundQuery simulator = new StoreVoucherRefundQuery();
//僅限走https的Tls 1.2以上版本
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
//發送至遠端
var result = simulator.Post(simulator.GetPostData());
System.Console.WriteLine(result);
}
/// <summary>
/// 取得串接欄位資料
/// </summary>
private dynamic GetRawData() {
dynamic rawData = new ExpandoObject();
rawData.store_uid = this.storeUid;
rawData.order_id = "VR20211001112859";
return rawData;
}
/// <summary>
/// 取得服務位置
/// </summary>
private ServiceRequest GetService() {
ServiceRequest rawData = new ServiceRequest();
rawData.service_name = "api";
rawData.cmd = "api/voucherrefundquery";
return rawData;
}
/// <summary>
/// 取得送出欄位資料
/// </summary>
private NameValueCollection GetPostData() {
string data_json = JsonConvert.SerializeObject(GetRawData(), Formatting.None);
string svr_json = JsonConvert.SerializeObject(GetService(), Formatting.None);; //依API種類調整
//產生AES向量
var IV = GetBytesIV();
//進行加密
var data_encode = Encrypt(data_json, this.storeKey, IV);
var svr_encode = Encrypt(svr_json, this.storeKey, IV);
//請注意使用的 Http Post 套件是否會自動加上UrlEncode,本Post範例為原始方式,故須加上UrlEncode
//若自行使用的套件會自動補上UrlEncode,則請忽略下面的UrlEncode,避免做了兩次UrlEncode
string data_toUrlEncode = HttpUtility.UrlEncode(data_encode);
string svr_toUrlEncode = HttpUtility.UrlEncode(svr_encode);
NameValueCollection postData = new NameValueCollection();
postData["store_uid"] = this.storeUid;
postData["service"] = svr_toUrlEncode;
postData["encry_data"] = data_toUrlEncode;
return postData;
}
/// <summary>
/// AES 256 加密
/// </summary>
/// <param name="data"></param>
/// <param name="key"></param>
/// <param name="byteIV"></param>
/// <returns></returns>
private string Encrypt(string data, string key, byte[] byteIV) {
var byteKey = System.Text.Encoding.UTF8.GetBytes(key);
var enBytes = AES_Encrypt(data, byteKey, byteIV);
return Convert.ToBase64String(BytesAdd(byteIV, enBytes));
}
/// <summary>
/// AES 256 加密處理
/// </summary>
/// <param name="original"></param>
/// <param name="key"></param>
/// <param name="iv"></param>
/// <returns></returns>
private byte[] AES_Encrypt(string original, byte[] key, byte[] iv) {
try {
var data = Encoding.UTF8.GetBytes(original);
var cipher = Aes.Create().CreateEncryptor(key, iv);
var de = cipher.TransformFinalBlock(data, 0, data.Length);
return de;
} catch {
return null;
}
}
/// <summary>
/// 轉換Bytes
/// </summary>
/// <param name="a"></param>
/// <param name="arryB"></param>
/// <returns></returns>
private byte[] BytesAdd(byte[] a, params byte[][] arryB) {
List < byte > c = new List < byte > ();
c.AddRange(a);
arryB.ToList().ForEach(b => {
c.AddRange(b);
});
return c.ToArray();
}
/// <summary>
/// 產生AES的IV
/// </summary>
/// <returns></returns>
private static byte[] GetBytesIV() {
var aes = System.Security.Cryptography.AesCryptoServiceProvider.Create();
aes.KeySize = 256;
aes.GenerateIV();
return aes.IV;
}
/// <summary>
/// 資料 POST 到主機
/// </summary>
/// <param name="pars"></param>
/// <returns></returns>
private string Post(NameValueCollection pars) {
string result = string.Empty;
string param = string.Empty;
if (pars.Count > 0) {
pars.AllKeys.ToList().ForEach(key => {
param += key + "=" + pars[key] + "&";
});
if (param[param.Length - 1] == '&') {
param = param.Remove(param.Length - 1);
}
}
byte[] bs = Encoding.UTF8.GetBytes(param);
try {
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create(this.url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = bs.Length;
using(Stream reqStream = req.GetRequestStream()) {
reqStream.Write(bs, 0, bs.Length);
}
using(WebResponse wr = req.GetResponse()) {
Encoding myEncoding = Encoding.GetEncoding("UTF-8");
using(StreamReader myStreamReader = new StreamReader(wr.GetResponseStream(), myEncoding)) {
result = myStreamReader.ReadToEnd();
}
}
req = null;
} catch (WebException ex) {
throw new WebException(ex.Message + "params : " + param, ex, ex.Status, ex.Response);
}
return result;
}
}
/// <summary>
/// 串接服務請求欄位
/// </summary>
public class ServiceRequest {
public string service_name { get; set; }
public string cmd { get; set; }
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.util.*;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.spongycastle.crypto.engines.AESEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.security.SecureRandom;
/**
* 特約商店串接-票券退款交易查詢
* 1. jackson-core 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
* 2. jackson-databind 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
* 3. jackson-annotations 下載 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations
* 4. Spongy Castle 下載 https://mvnrepository.com/artifact/com.madgag.spongycastle/core
*/
public class StoreVoucherRefundQuery {
/**
* 特約商店商務代號
*/
String storeUid = "289151880142";
/**
* 特約商店金鑰或認證碼
*/
String storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
/**
* 串接交易位置
*/
String url = "https://pay.usecase.cc/api/init";
/**
* 執行
* @param args
*/
public static void main(String[] args) {
StoreVoucherRefundQuery simulator = new StoreVoucherRefundQuery();
String json = simulator.post(simulator.getPostData());
System.out.print(json);
}
@SuppressWarnings(value = { "unchecked", "deprecation" })
/**
* 取得串接欄位資料
* @return 串接原始資料
*/
public Map getRawData() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("store_uid", this.storeUid);
rawData.put("order_id", "VR20211001112859");
return rawData;
}
/**
* 取得服務位置
* @return 串接服務資料
*/
public Map getService() {
Map<Object, Object> rawData = new HashMap<Object, Object>();
rawData.put("service_name", "api");
rawData.put("cmd", "api/voucherrefundquery");
return rawData;
}
/**
* AES 256 加密
* @param rawData 原始資料
* @param AesKey AES256金鑰字串
* @return 轉換成Base64資料
*/
public String encrypt(Map rawData, String AesKey) {
try {
ObjectMapper objMapper = new ObjectMapper();
byte[] data = objMapper.writeValueAsString(rawData).getBytes(UTF_8);
byte[] key = AesKey.getBytes(UTF_8);
// 16 bytes is the IV size for AES256
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()));
// Random iv
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[16];
rng.nextBytes(ivBytes);
cipher.init(true, new ParametersWithIV(new KeyParameter(key),
ivBytes));
byte[] outBuf = new byte[cipher.getOutputSize(data.length)];
int processed = cipher
.processBytes(data, 0, data.length, outBuf, 0);
processed += cipher.doFinal(outBuf, processed);
byte[] outBuf2 = new byte[processed + 16]; // Make room for iv
System.arraycopy(ivBytes, 0, outBuf2, 0, 16); // Add iv
System.arraycopy(outBuf, 0, outBuf2, 16, processed);
Base64.Encoder encoder = Base64.getEncoder();
String base64 = encoder.encodeToString(outBuf2);
return base64;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 資料 POST 到主機
* @param qstr 串接資料
* @return 服務回傳JSON資訊
*/
public String post(String qstr) {
String result = "";
try {
// 資料
byte[] qstr_bytes = qstr.getBytes(StandardCharsets.UTF_8);
URL iurl = new URL(this.url);
SSLContext sc = SSLContext.getInstance("TLSv1.2"); // $NON-NLS-1$
sc.init(null, null, new java.security.SecureRandom());
HttpsURLConnection con = (HttpsURLConnection) iurl.openConnection();
con.setSSLSocketFactory(sc.getSocketFactory());
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
con.setRequestProperty("Content-Length",
String.valueOf(qstr_bytes.length));
con.setRequestProperty("Accept-Charset", "UTF-8");
con.setDoOutput(true);
con.setDoInput(true);
con.getOutputStream()
.write(qstr.getBytes(Charset.forName("UTF-8")));
con.getOutputStream().flush();
BufferedReader in = new BufferedReader(new InputStreamReader(
con.getInputStream(), "UTF-8"));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine + "\r\n");
}
try {
result = response.toString();
} finally {
in.close();
}
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return result;
}
/**
* 取得送出欄位資料
* @return POST完整資料
*/
public String getPostData() {
String postData = "";
try {
// Base64需要使用UrlEncode做傳輸
String data_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getRawData(), this.storeKey), "UTF-8");
String svr_toUrlEncode = URLEncoder.encode(
this.encrypt(this.getService(), this.storeKey), "UTF-8");
postData = "store_uid=" + this.storeUid + "&service="
+ svr_toUrlEncode + "&encry_data=" + data_toUrlEncode;
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return postData;
}
}
const crypto = require('crypto');
const httpRequest = require('https');
/**
* 特約商店串接-票券退款交易查詢
*/
function StoreVoucherRefundQuery() {
// 特約商店商務代號
this.storeUid = "289151880142";
// 特約商店金鑰或認證碼
this.storeKey = "g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0";
// 串接交易位置
this.url = "https://pay.usecase.cc/api/init";
};
/**
* 取得串接欄位資料
*/
StoreVoucherRefundQuery.prototype.getRawData = function () {
return {
store_uid: this.storeUid,
order_id: "VR20211001112859",
};
};
/**
* 取得服務位置
*/
StoreVoucherRefundQuery.prototype.getService = function () {
return {
service_name: "api",
cmd: "api/voucherrefundquery"
};
};
/**
* AES 256 加密
*/
StoreVoucherRefundQuery.prototype.encrypt = function (fields, key) {
let eData = JSON.stringify(fields);
const blockSize = 16;
const iv = crypto.randomBytes(blockSize);
const encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
let tmpCipher = encryptor.update(Buffer.from(eData));
let finalCipher = encryptor.final();
const tempData = Buffer.concat([tmpCipher, finalCipher], tmpCipher.length + finalCipher.length);
let data = Buffer.concat([iv, tempData], iv.length + tempData.length).toString('base64');
return data;
};
/**
* 資料 POST 到主機
*/
StoreVoucherRefundQuery.prototype.post = function (postData) {
return new Promise((res, rej) => {
let options = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
rejectUnauthorized: false
};
let send_process = httpRequest.request(this.url, options, (api_res) => {
let res_data = "";
api_res.on('data', (tmp_data) => {
res_data += tmp_data;
});
api_res.on('end', () => {
res(res_data);
});
});
send_process.write(JSON.stringify(postData));
send_process.end();
});
};
/**
* 取得送出欄位資料
*/
StoreVoucherRefundQuery.prototype.getPostData = function () {
return {
"store_uid": this.storeUid,
"service": this.encrypt(this.getService(), this.storeKey),
"encry_data": this.encrypt(this.getRawData(), this.storeKey)
};
};
/**
* 執行
*/
StoreVoucherRefundQuery.prototype.run = async function () {
json = await this.post(this.getPostData())
console.log(json);
};
StoreVoucherRefundQuery = new StoreVoucherRefundQuery();
StoreVoucherRefundQuery.run();
# -*- coding: utf-8 -*-
import json
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util import Padding
from Crypto.Random import get_random_bytes
"""特約商店串接-票券退款交易查詢
"""
class StoreVoucherRefundQuery:
# 特約商店商務代號
storeUid = "289151880142"
# 特約商店金鑰或認證碼
storeKey = b"g4LxEvWTz4ftVrsjei9zIaJLnjXo1QG0"
# 串接交易位置
url = "https://pay.usecase.cc/api/init"
def getRawData(self):
"""取得串接欄位資料
Returns:
{dict}: 欄位資料
"""
rawData = {
'store_uid': self.storeUid,
'order_id': "VR20211001112859",
}
return rawData
def getService(self):
"""取得服務位置
Returns:
{dict}: 服務位置資料
"""
return {
'service_name': 'api',
'cmd': 'api/voucherrefundquery'
}
def encrypt(self, fields, key):
"""AES 256 加密
Args:
fields {dict}: 欄位資料
key {bytes}: AES金鑰
Returns:
{string}: 加密資料
"""
data = json.dumps(fields, separators=(',', ':'))
data = Padding.pad(data.encode('utf-8'), AES.block_size)
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
data = cipher.encrypt(data)
data = base64.b64encode(iv + data)
return data
def post(self, postData):
"""資料 POST 到主機
Args:
postData {dict}: 欄位資料
Returns:
{string}: JSON資料
"""
result = requests.post(self.url, postData)
return result.text
def getPostData(self):
"""取得送出欄位資料
Returns:
{dict}: 欄位資料
"""
postData = {
'store_uid': self.storeUid,
'service': self.encrypt(self.getService(), self.storeKey),
'encry_data': self.encrypt(self.getRawData(), self.storeKey)
}
return postData
def run(self):
"""執行
"""
json = self.post(self.getPostData())
print(json)
StoreVoucherRefundQuery = StoreVoucherRefundQuery()
StoreVoucherRefundQuery.run()
回傳 JSON 結構如下:
{
"code": "B200",
"msg": "執行成功",
"content": {
"order_id": "VR20211001112859",
"uid": "129",
"key": "2d981e1f4a5dda0d3f68eb27b5a23346",
"code": "230",
"msg": "退款成功",
"refund_date": "20211001112900",
"items": [
{
"id": "H01",
"serial_number": "A00052"
}
],
"echo_0": "",
"echo_1": "",
"echo_2": "",
"echo_3": "",
"echo_4": ""
}
}
查詢已發動之票券退款交易,查詢訂單結果。
特約商店『票券退款交易查詢』參數說明
欄位 | 型態 | 說明 |
---|---|---|
store_uid | string(16) | 特約商店商務代號 |
service | text | {"service_name": "api", "cmd": "api\/voucherrefund"} JSON格式,AES256加密資料 |
encry_data | text | 『票券退款交易查詢』欄位參考 JSON格式,AES256加密資料 |
『票券退款交易查詢』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
store_uid | string | 特約商店代碼 | 必填 |
order_id | string | 特約商店訂單編號 | 必填 |
『票券退款交易查詢』回傳欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
code | string | 票券交易狀態碼 | |
msg | string | 回傳訊息 | |
content | object | 查詢內容 | 『票券退款訂單查詢內容回傳』欄位參考 |
其他關聯欄位說明
關聯欄位
『消費者資訊』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
user_id | string | 消費者帳號 | 必填 |
user_name | string | 消費者姓名 | |
user_real_name | string | 消費者真實姓名 | |
user_address_post_zone | string | 消費者地址郵遞區號 | |
user_address | string | 消費者帳單地址 | |
user_sn_type | string | 1:身分證,2:統一證號,3:護照號碼 付款人為本國人為1,外國人2 or 3 | |
user_sn | string | 付款人身分證/統一證號/護照號碼 | |
user_phone | string | 消費者家用電話 | |
user_cellphone_code | string | 消費者行動電話國碼 | |
user_cellphone | string | 消費者行動電話 | |
user_email | string | 消費者 E-Mail | |
user_birthday | string | 消費者生日 | |
ip | string | 消費者來源 IP | 必填 |
『票券項目』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
id | string | 票券產品編號(最長限32 Bytes) 限英數字([a-zA-Z0-9]) |
必填 |
name | string | 票券產品名稱 | 必填 |
amount | integer | 購買票券數量 | 必填 |
price | integer | 此批發行票券的每張票券面額 | 必填 |
cost | integer | 此批發行票券的每張票券內含價值 | 必填 |
total_price | integer | 此批票券的面額總和 | 必填 |
total_cost | integer | 此批票券內含價值總和 | 必填 |
gift_number | integer | 票券贈送張數(預設0張) | 必填 |
trust_start_date | string | 票券履約保證起始日 | 必填 |
trust_end_date | string | 票券履約保證結束日(履約保證時間必須超過一年) | 必填 |
validity_end_date | string | 票券優惠結束日(格式YYYYmmdd) | 必填 |
is_custom_serial | integer | 是否使用客製票券序號 0. 沒有 1.有 若客戶類型為「平台客戶」,此欄位必填1.有 |
必填 |
serial_numbers | string | 客製票券序號JSON資料 注意: 1.客製票券序號不能重複,且限英數字([a-zA-Z0-9]) 2.長度最長32bytes 3.若客戶類型為「平台客戶」,此欄位必填 4.若購賣票券交易狀態為交易失敗、未進行交易等確定不可再進行交易,客製券號可進行回收再利用 5.如無回收未用客製票券序號機制,則請忽略4的實作,不重複即可 |
|
gift_serial_numbers | string | 客製「贈送票券」序號JSON資料 規則同客製票券序號 |
|
sharing | object | 每張票券分潤比例 總部 + 銷售點 + 核銷點 需等於 1 |
必填 『票券分潤』欄位參考 |
『票券宣告』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
name | string | 票券產品名稱 | 必填 |
content | string | 宣告內容,只允許下列html tag,其餘會被移除 h6,ul,li,b |
『票券分潤』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
headquarters | float | 總部分潤 | 必填 |
sales | float | 銷售點分潤 | 必填 |
reimbursement | float | 核銷點分潤 | 必填 |
『票券核銷項目(request)』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
id | string | 票券產品編號(最長限32 Bytes) | 必填 |
serial_number | string | 票券號碼 | 必填 |
『票券核銷項目(response)』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
id | string | 票券產品編號(最長限32 Bytes) | |
serial_number | string | 票券號碼 | |
is_paid | integer | 價值類型 | 『票券價值類型』值參考 |
sharing | object | 原始分潤比例 | 『票券分潤』欄位參考 |
fee | object | 費用資訊 | 『票券費用』欄位參考 |
『票券費用』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
cost | float | 票券價值 | |
gateway_fee | integer | 金流手續費 | |
escrow_fee | integer | 信託手續費 | |
issuance_layer | integer | 發行費收費者 | 『發行費收費者』值參考 |
issuance_fee | integer | 票券票券發行費 | |
headquarters | integer | 總部分潤 | |
sales | integer | 銷售點分潤 | |
reimbursement | integer | 核銷點分潤 |
『票券退款項目』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
id | string | 票券產品編號(最長限32 Bytes) | 必填 |
serial_number | string | 票券號碼 | 必填 |
『票券退款訂單查詢內容回傳』欄位
參數名稱 | 型態 | 說明 | 必須 |
---|---|---|---|
order_id | string | 特店商店的退款訂單編號 | |
uid | string | 票券退款交易訂單UID | |
key | string | 驗証碼 | |
code | string | 狀態碼 | 『票券退款交易狀態碼』值參考 |
msg | string | 回傳訊息 | |
refund_date | string | 退款成功時間(格式(YYYYMMDDHHmmss)) | |
items | array | 票券退款項目 | 每筆『票券退款項目』欄位參考 |
echo_0 | string | 自訂回傳參數 1 | |
echo_1 | string | 自訂回傳參數 2 | |
echo_2 | string | 自訂回傳參數 3 | |
echo_3 | string | 自訂回傳參數 4 | |
echo_4 | string | 自訂回傳參數 5 |
值的定義
『票券服務類型』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
1 | integer | 記名票券(預設) |
『適用區域類型』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
1 | integer | 單店券(預設) |
『是否為預發行』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
1 | integer | 即時發行(預設) |
『發行費收費者』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
1 | integer | 經銷商 | |
2 | integer | 大特店 | |
3 | integer | 特約商店 |
『票券價值類型』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
1 | integer | 有償 | |
0 | integer | 無償 |
『購買票券交易狀態碼』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
100 | string | 資料不正確 | |
200 | string | 資料正確 | |
210 | string | 交易處理中 | |
250 | string | 交易成功 | |
260 | string | 交易進行中(超商代碼繳費) | |
270 | string | 交易進行中(虛擬帳號) | |
280 | string | 交易進行中(WEBATM等導頁式交易) | |
290 | string | 交易成功,但金額不符 | |
300 | string | 交易失敗 | |
380 | string | 交易逾時 | |
400 | string | 系統錯誤 | |
500 | string | 中斷交易(閘道) | |
600 | string | 交易成功且結帳 (信用卡) | |
A0001 | string | 中斷交易(金流) | |
A0002 | string | 未完成交易 | |
B200 | string | 執行成功 | |
B500 | string | 執行失敗 |
『票券核銷交易狀態碼』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
100 | string | 資料不正確 | |
250 | string | 交易成功 | |
300 | string | 交易失敗 | |
400 | string | 系統錯誤。 | |
B200 | string | 執行成功 | |
B500 | string | 執行失敗 |
『票券退款交易狀態碼』值內容
值 | 型態 | 說明 | 備註 |
---|---|---|---|
100 | string | 資料不正確 | |
230 | string | 退款成功 | |
300 | string | 退款交易失敗 | |
400 | string | 系統錯誤。 | |
B200 | string | 執行成功 | |
B500 | string | 執行失敗 |
票券相關費用規則
票券金額計算(票券單張最低50元)
1.特約商店金額 = 票券價值 - 信託手續費(四捨五入,最低1元) - 金流手續費(四捨五入,最低1元) - 票券發行費
總部分潤 + 銷售點分潤 + 核銷點分潤 = 100%
2.核銷點分潤 = (特約商店金額 * 核銷點分潤)四捨五入 3.銷售點分潤 = (特約商店金額 * 銷售點分潤)四捨五入
核銷點分潤 + 銷售點分潤 若超過特約商店金額,負差額由銷售點吸收
4.總部分潤 = 特約商店金額 - 銷核銷點分潤 - 售點分潤
案例1: 票券H000001價值50元、信託手續費2%、手續費1%、票券發行費(由經銷商收)1元 核銷點分潤 50%、銷售點分潤50%
信託手續費 = 50 * 0.02 = 1 元 金流手續費 = 50 * 0.01 = 0.5,故用最低1元計 票券發行費 = 1 元
特約商店金額 = 50 - 1 - 1 - 1= 47 元 核銷點分潤 = 47 * 0.5 = 23.5 元,四捨五入24元 銷售點分潤 = 47 * 0.5 = 23.5 元,四捨五入24元。
47 - 24 - 24 = -1 負差額由銷售點吸收
故銷售點分潤 = 24 + (-1) = 23 元
總部分潤 = 47 - 24 - 23 = 0 元
案例2: 票券H000001價值50元、信託手續費2%、手續費1%、票券發行費(由經銷商收)1元 核銷點分潤 33%、銷售點分潤33%、總部分潤34%
信託手續費 = 50 * 0.02 = 1 元 金流手續費 = 50 * 0.01 = 0.5,故用最低1元計 票券發行費 = 1 元
特約商店金額 = 50 - 1 - 1 -1 = 47 元 核銷點分潤 = 47 * 0.33 = 15.51 元,四捨五入16元。 銷售點分潤 = 47 * 0.33 = 15.51 元,四捨五入16元。
47 - 16 - 16 = 15 正差額
總部分潤 = 47 - 16 - 16 = 15 元
備註:贈送票券部分無費用
附錄一:PFN(支付工具)參數表
資料傳遞可使用編號,也可以使用代碼 。 例如:pfn=27跟pfn=PION,一樣都是使用Pi 拍錢包線上付款支付工具。
編號 | 代碼 | 狀態 | 說明 |
---|---|---|---|
1 | CREDITCARD | 啟用 | 信用卡 |
3 | CSTORECODE | 啟用 | 超商代碼 |
4 | WEBATM | 啟用 | WEBATM |
6 | E_COLLECTION | 啟用 | 虛擬帳號 (ATM轉帳) |
10 | ALIPAY | 啟用 | 支付寶 |
13 | 啟用 | 微信支付 | |
15 | LINEPAYON | 啟用 | LINE線上付款(消費者主掃) |
16 | LINEPAYOFF | 啟用 | LINE線下付款(消費者被掃) |
19 | WECHATOFF | 啟用 | 微信支付線下 |
20 | APPLEPAY | 啟用 | Apple Pay |
21 | GOOGLEPAY | 啟用 | Google Pay |
27 | PION | 啟用 | Pi 拍錢包線上付款(消費者主掃) |
28 | PIOFF | 啟用 | Pi 拍錢包線下付款(消費者被掃) |
29 | AMEX | 啟用 | 美國運通 |
31 | JKOON | 啟用 | 街口支付線上付款(消費者主掃) |
32 | JKOOF | 啟用 | 街口支付線下付款(消費者被掃) |
33 | ALIPAYOFF | 啟用 | 支付寶線下(消費者被掃) |
38 | EASYWALLETON | 啟用 | 悠遊付線上付款(消費者主掃) |
39 | EASYWALLETOFF | 啟用 | 悠遊付線下付款(消費者被掃) |
附錄三:設定調整
- 交易回傳設定
- 交易金鑰重新發送與變更
附錄四:資料加密方式說明
- 所有的API送出HTTPs請求之欄位中,service 和 encry_data 欄位皆進行 AES256+BASE64 加密處理。
- AES加密,格式為CBC,長度為256bits,金鑰長度32,IV長度16,傳遞內文為加密後組合IV並經過Base64轉換後傳出。
- IV資料建議隨機產生。
PHP加密示意:
AesEncrypt -> base64_ecode($IV . $JSON)
C#加密示意:
AesEncrypt -> (bytes)IV+(bytes)Json -> toBase64
Java加密示意:
AesEncrypt -> (bytes)IV+(bytes)Json -> toBase64
Node.js加密示意:
AesEncrypt -> concat([IV,JSON], [IV_SIZE,JSON_SIZE]) -> toString('base64')
Python加密示意:
AesEncrypt -> (bytes)IV+(bytes)Json -> base64.b64encode
附錄五:API模擬串接服務
提供模擬使用HTTP Protocol,透過POST方式傳遞資料到MYTIX, 並且得到回傳結果。