taiwan-logistics-skill 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ field_name,field_zh,ecpay_name,type,required,max_length,format,notes
2
+ merchant_id,商店代號,MerchantID,string,true,10,,
3
+ order_id,訂單編號,MerchantTradeNo,string,true,20,英數字,需唯一
4
+ order_date,訂單日期,MerchantTradeDate,string,true,20,yyyy/MM/dd HH:mm:ss,
5
+ logistics_type,物流類型,LogisticsType,string,true,20,CVS/Home,
6
+ logistics_sub_type,物流子類型,LogisticsSubType,string,true,20,,見代碼表
7
+ ecpay_logistics_id,綠界物流編號,AllPayLogisticsID,string,false,20,,ECPay回傳
8
+ goods_amount,商品金額,GoodsAmount,integer,true,,,
9
+ goods_name,商品名稱,GoodsName,string,true,50,,
10
+ sender_name,寄件人姓名,SenderName,string,true,10,,
11
+ sender_phone,寄件人電話,SenderPhone,string,true,20,,
12
+ sender_cell_phone,寄件人手機,SenderCellPhone,string,false,20,,
13
+ sender_zipcode,寄件人郵遞區號,SenderZipCode,string,false,6,,宅配必填
14
+ sender_address,寄件人地址,SenderAddress,string,false,200,,宅配必填
15
+ receiver_name,收件人姓名,ReceiverName,string,true,10,,
16
+ receiver_phone,收件人電話,ReceiverPhone,string,true,20,,
17
+ receiver_cell_phone,收件人手機,ReceiverCellPhone,string,false,20,,
18
+ receiver_zipcode,收件人郵遞區號,ReceiverZipCode,string,false,6,,宅配必填
19
+ receiver_address,收件人地址,ReceiverAddress,string,false,200,,宅配必填
20
+ receiver_store_id,收件門市代號,ReceiverStoreID,string,false,6,,超商必填
21
+ return_store_id,退貨門市代號,ReturnStoreID,string,false,6,,
22
+ is_collection,是否代收,IsCollection,string,false,1,Y/N,超商適用
23
+ collection_amount,代收金額,CollectionAmount,integer,false,,,
24
+ temperature,溫層,Temperature,string,false,4,0001/0002/0003,宅配適用
25
+ distance,距離,Distance,string,false,2,00/01,00同縣市 01外縣市
26
+ specification,規格,Specification,string,false,4,,包裹尺寸
27
+ server_reply_url,通知網址,ServerReplyURL,string,true,200,URL,
@@ -0,0 +1,11 @@
1
+ logistics_type,logistics_sub_type,name_zh,provider,type,collection_available,max_amount,size_limit,temperature,notes
2
+ CVS,UNIMART,7-11 B2C,7-11,cvs_b2c,true,20000,45x30x30cm,常溫,大宗寄倉
3
+ CVS,UNIMARTC2C,7-11 店到店,7-11,cvs_c2c,true,20000,45x30x30cm,常溫,C2C 交貨便
4
+ CVS,FAMI,全家 B2C,全家,cvs_b2c,true,20000,45x30x30cm,常溫,大宗寄倉
5
+ CVS,FAMIC2C,全家店到店,全家,cvs_c2c,true,20000,45x30x30cm,常溫,C2C 店到店
6
+ CVS,HILIFE,萊爾富 B2C,萊爾富,cvs_b2c,true,20000,45x30x30cm,常溫,大宗寄倉
7
+ CVS,HILIFEC2C,萊爾富店到店,萊爾富,cvs_c2c,true,20000,45x30x30cm,常溫,C2C 店到店
8
+ CVS,OKMART,OK B2C,OK,cvs_b2c,true,20000,45x30x30cm,常溫,大宗寄倉
9
+ CVS,OKMARTC2C,OK 店到店,OK,cvs_c2c,true,20000,45x30x30cm,常溫,C2C 店到店
10
+ Home,TCAT,黑貓宅急便,黑貓,home,false,200000,150cm,常溫/冷藏/冷凍,宅配到府
11
+ Home,ECAN,宅配通,宅配通,home,false,100000,120cm,常溫,常溫宅配
@@ -0,0 +1,8 @@
1
+ operation,operation_zh,ecpay_endpoint,method,required_fields,optional_fields,notes
2
+ create_cvs,建立超商取貨訂單,/Express/Create,POST,"MerchantID,MerchantTradeNo,LogisticsType,LogisticsSubType,GoodsAmount,GoodsName,SenderName,SenderPhone,ReceiverName,ReceiverPhone,ReceiverStoreID,ServerReplyURL","IsCollection,CollectionAmount,ReturnStoreID",LogisticsType=CVS
3
+ create_home,建立宅配訂單,/Express/Create,POST,"MerchantID,MerchantTradeNo,LogisticsType,LogisticsSubType,GoodsAmount,GoodsName,SenderName,SenderPhone,SenderAddress,ReceiverName,ReceiverPhone,ReceiverAddress,ServerReplyURL","Temperature,Distance,Specification,ScheduledDeliveryTime",LogisticsType=Home
4
+ map,超商電子地圖,/Express/map,POST,"MerchantID,LogisticsType,LogisticsSubType,ServerReplyURL","IsCollection,ExtraData",開啟門市選擇頁面
5
+ query,查詢物流訂單,/Helper/QueryLogisticsTradeInfo/V2,POST,"MerchantID,TimeStamp","AllPayLogisticsID,MerchantTradeNo",二擇一
6
+ print,列印託運單,/helper/printTradeDocument,POST,"MerchantID,AllPayLogisticsID","LogisticsType,LogisticsSubType",需先建立訂單
7
+ return,逆物流訂單,/Express/ReturnHome,POST,"MerchantID,AllPayLogisticsID,ServerReplyURL","GoodsAmount,SenderName",退貨/換貨
8
+ update_store,更新門市,/Express/UpdateStoreInfo,POST,"MerchantID,AllPayLogisticsID,ReceiverStoreID","CVSPaymentNo,CVSValidationNo",僅限超商取貨
@@ -0,0 +1,9 @@
1
+ provider,name_zh,name_en,type,api_available,test_merchant_id,test_hash_key,test_hash_iv,test_url,prod_url,features,coverage
2
+ ecpay,綠界物流,ECPay Logistics,aggregator,true,2000132,5294y06JbISpM5x9,v77hoKGq4kWxNNIS,https://logistics-stage.ecpay.com.tw,https://logistics.ecpay.com.tw,"7-11,全家,萊爾富,OK,黑貓,宅配通",全台
3
+ unimart,7-11 交貨便,7-11 ibon,cvs,true,,,,,https://emap.pcsc.com.tw,店到店,全台 5000+ 門市
4
+ fami,全家店到店,FamilyMart,cvs,true,,,,,https://www.famiport.com.tw,店到店,全台 3500+ 門市
5
+ hilife,萊爾富,Hi-Life,cvs,true,,,,,https://www.hilife.com.tw,店到店,全台 1500+ 門市
6
+ okmart,OK 便利商店,OK Mart,cvs,true,,,,,,店到店,全台 900+ 門市
7
+ tcat,黑貓宅急便,t-cat,home,true,,,,https://www.t-cat.com.tw,https://www.t-cat.com.tw,"常溫,冷藏,冷凍",全台
8
+ hct,新竹物流,HCT,home,true,,,,https://www.hct.com.tw,https://www.hct.com.tw,常溫,全台
9
+ pelican,宅配通,Pelican Express,home,true,,,,https://www.pelican.com.tw,https://www.pelican.com.tw,常溫,全台
@@ -0,0 +1,14 @@
1
+ code,status_zh,status_en,category,description,next_action
2
+ 300,訂單建立成功,Order Created,success,物流訂單已成功建立,等待出貨
3
+ 310,訂單取消,Order Cancelled,cancelled,訂單已取消,
4
+ 2030,已交寄,Package Shipped,in_transit,商品已交給物流商,等待配送
5
+ 2063,配達完成,Delivered,delivered,宅配已送達收件人,
6
+ 2067,消費者取貨完成,Pickup Completed,completed,消費者已至超商取貨,
7
+ 2073,退貨中,Return in Progress,returning,退貨流程進行中,
8
+ 2074,退貨完成,Return Completed,returned,退貨流程已完成,
9
+ 2030,包裹已到店,Arrived at Store,at_store,包裹已送達指定門市,通知取貨
10
+ 2031,包裹已取件,Picked Up by Carrier,picked_up,物流商已取件,
11
+ 2032,轉運中,In Transit,in_transit,包裹配送中,
12
+ 2061,配達失敗,Delivery Failed,failed,配送失敗,聯繫收件人
13
+ 2070,包裹已退回,Package Returned,returned,包裹已退回寄件人,
14
+ 3001,系統錯誤,System Error,error,系統發生錯誤,聯繫客服
@@ -0,0 +1,524 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ NewebPay 藍新物流 CVS 超商物流 Python 完整範例
4
+
5
+ 依照 taiwan-logistics-skill 最高規範撰寫
6
+ 支援: C2C 店到店 (7-11, 全家, 萊爾富, OK)、B2C 大宗寄倉
7
+
8
+ API 文件: https://www.newebpay.com
9
+ """
10
+
11
+ import json
12
+ import hashlib
13
+ import time
14
+ import urllib.parse
15
+ from datetime import datetime
16
+ from typing import Dict, Literal, Optional
17
+ from dataclasses import dataclass, field
18
+
19
+ try:
20
+ from Crypto.Cipher import AES
21
+ from Crypto.Util.Padding import pad, unpad
22
+ import requests
23
+ HAS_DEPENDENCIES = True
24
+ except ImportError:
25
+ HAS_DEPENDENCIES = False
26
+
27
+
28
+ @dataclass
29
+ class CVSShipmentData:
30
+ """CVS 超商物流訂單資料"""
31
+ merchant_order_no: str
32
+ lgs_type: Literal['B2C', 'C2C']
33
+ ship_type: Literal['1', '2', '3', '4'] # 1=7-11, 2=FamilyMart, 3=Hi-Life, 4=OK
34
+ receiver_store_code: str
35
+ receiver_name: str
36
+ receiver_cell_phone: str
37
+ receiver_tel: Optional[str] = None
38
+ goods_amount: int = 0
39
+ goods_name: str = ''
40
+ sender_name: Optional[str] = None
41
+ sender_cell_phone: Optional[str] = None
42
+ is_collection: Literal['Y', 'N'] = 'N'
43
+ collection_amount: int = 0
44
+ note: Optional[str] = None
45
+
46
+
47
+ @dataclass
48
+ class CVSShipmentResponse:
49
+ """CVS 超商物流訂單回應"""
50
+ success: bool
51
+ status: str
52
+ message: str
53
+ merchant_order_no: str
54
+ logistics_no: Optional[str] = None
55
+ cvs_payment_no: Optional[str] = None
56
+ cvs_validation_no: Optional[str] = None
57
+ booking_note: Optional[str] = None
58
+ raw: Dict = field(default_factory=dict)
59
+
60
+
61
+ @dataclass
62
+ class StoreMapData:
63
+ """電子地圖門市查詢資料"""
64
+ merchant_order_no: str
65
+ lgs_type: Literal['B2C', 'C2C']
66
+ ship_type: Literal['1', '2', '3', '4']
67
+ return_url: str
68
+ extra_data: Optional[str] = None
69
+
70
+
71
+ @dataclass
72
+ class StoreMapCallback:
73
+ """電子地圖門市選擇回傳"""
74
+ lgs_type: str
75
+ ship_type: str
76
+ merchant_order_no: str
77
+ store_id: str
78
+ store_name: str
79
+ store_addr: str
80
+ store_tel: str
81
+ extra_data: str
82
+ raw: Dict = field(default_factory=dict)
83
+
84
+
85
+ class NewebPayCVSLogistics:
86
+ """
87
+ NewebPay 藍新物流 CVS 超商物流服務
88
+
89
+ 認證方式: AES-256-CBC + SHA256
90
+ 加密方式: JSON + AES-256-CBC 加密 + SHA256 驗證
91
+
92
+ 支援超商:
93
+ - 1: 7-ELEVEN 統一超商
94
+ - 2: FamilyMart 全家便利商店
95
+ - 3: Hi-Life 萊爾富便利商店
96
+ - 4: OK Mart OK 便利商店
97
+
98
+ 物流類型:
99
+ - C2C: 店到店 (店取、店寄)
100
+ - B2C: 大宗寄倉 (僅 7-11)
101
+
102
+ 測試環境:
103
+ - 需至藍新金流申請測試帳號
104
+ - API URL: https://ccore.newebpay.com/API/Logistic
105
+ """
106
+
107
+ # 測試環境
108
+ TEST_BASE_URL = 'https://ccore.newebpay.com/API/Logistic'
109
+
110
+ # 正式環境
111
+ PROD_BASE_URL = 'https://core.newebpay.com/API/Logistic'
112
+
113
+ def __init__(
114
+ self,
115
+ merchant_id: str,
116
+ hash_key: str,
117
+ hash_iv: str,
118
+ is_production: bool = False
119
+ ):
120
+ """
121
+ 初始化 NewebPay CVS 物流服務
122
+
123
+ Args:
124
+ merchant_id: 商店代號
125
+ hash_key: HashKey (32 字元)
126
+ hash_iv: HashIV (16 字元)
127
+ is_production: 是否為正式環境 (預設 False)
128
+
129
+ Raises:
130
+ ImportError: 缺少必要套件
131
+ """
132
+ if not HAS_DEPENDENCIES:
133
+ raise ImportError(
134
+ '需要安裝必要套件:\n'
135
+ ' pip install pycryptodome requests'
136
+ )
137
+
138
+ self.merchant_id = merchant_id
139
+ self.hash_key = hash_key.encode('utf-8')
140
+ self.hash_iv = hash_iv.encode('utf-8')
141
+ self.base_url = self.PROD_BASE_URL if is_production else self.TEST_BASE_URL
142
+
143
+ def aes_encrypt(self, data: str) -> str:
144
+ """
145
+ AES-256-CBC 加密
146
+
147
+ Args:
148
+ data: 明文字串
149
+
150
+ Returns:
151
+ str: 加密後的 hex 字串
152
+ """
153
+ cipher = AES.new(self.hash_key, AES.MODE_CBC, self.hash_iv)
154
+ padded_data = pad(data.encode('utf-8'), AES.block_size)
155
+ encrypted = cipher.encrypt(padded_data)
156
+ return encrypted.hex()
157
+
158
+ def aes_decrypt(self, encrypted_data: str) -> str:
159
+ """
160
+ AES-256-CBC 解密
161
+
162
+ Args:
163
+ encrypted_data: 加密的 hex 字串
164
+
165
+ Returns:
166
+ str: 解密後的明文
167
+
168
+ Raises:
169
+ ValueError: 解密失敗
170
+ """
171
+ try:
172
+ cipher = AES.new(self.hash_key, AES.MODE_CBC, self.hash_iv)
173
+ decrypted = cipher.decrypt(bytes.fromhex(encrypted_data))
174
+ unpadded = unpad(decrypted, AES.block_size)
175
+ return unpadded.decode('utf-8')
176
+ except Exception as e:
177
+ raise ValueError(f'解密失敗: {str(e)}')
178
+
179
+ def generate_hash_data(self, encrypt_data: str) -> str:
180
+ """
181
+ 產生 HashData (SHA256)
182
+
183
+ Args:
184
+ encrypt_data: 加密後的資料
185
+
186
+ Returns:
187
+ str: SHA256 雜湊值 (大寫)
188
+ """
189
+ raw = f"{self.hash_key.decode('utf-8')}{encrypt_data}{self.hash_iv.decode('utf-8')}"
190
+ return hashlib.sha256(raw.encode('utf-8')).hexdigest().upper()
191
+
192
+ def verify_hash_data(self, encrypt_data: str, hash_data: str) -> bool:
193
+ """驗證 HashData"""
194
+ calculated_hash = self.generate_hash_data(encrypt_data)
195
+ return calculated_hash == hash_data.upper()
196
+
197
+ def query_store_map(
198
+ self,
199
+ data: StoreMapData,
200
+ ) -> str:
201
+ """
202
+ 查詢電子地圖 (門市選擇)
203
+
204
+ Args:
205
+ data: 門市查詢資料 (StoreMapData)
206
+
207
+ Returns:
208
+ str: 重導向網址 (用戶選擇門市)
209
+
210
+ Example:
211
+ >>> store_data = StoreMapData(
212
+ ... merchant_order_no='ORD123',
213
+ ... lgs_type='C2C',
214
+ ... ship_type='1',
215
+ ... return_url='https://your-site.com/callback',
216
+ ... )
217
+ >>> redirect_url = service.query_store_map(store_data)
218
+ """
219
+ # 準備加密資料
220
+ encrypt_data_obj = {
221
+ 'MerchantOrderNo': data.merchant_order_no,
222
+ 'LgsType': data.lgs_type,
223
+ 'ShipType': data.ship_type,
224
+ 'ReturnURL': data.return_url,
225
+ 'TimeStamp': str(int(time.time())),
226
+ }
227
+
228
+ if data.extra_data:
229
+ encrypt_data_obj['ExtraData'] = data.extra_data
230
+
231
+ # JSON 序列化並加密
232
+ json_str = json.dumps(encrypt_data_obj, ensure_ascii=False)
233
+ encrypt_data = self.aes_encrypt(json_str)
234
+ hash_data = self.generate_hash_data(encrypt_data)
235
+
236
+ # 準備 API 請求
237
+ api_data = {
238
+ 'UID_': self.merchant_id,
239
+ 'EncryptData_': encrypt_data,
240
+ 'HashData_': hash_data,
241
+ 'Version_': '1.0',
242
+ 'RespondType_': 'JSON',
243
+ }
244
+
245
+ # 發送請求 (NewebPay 會重導向到門市選擇頁面)
246
+ response = requests.post(
247
+ f'{self.base_url}/storeMap',
248
+ data=api_data,
249
+ allow_redirects=False,
250
+ timeout=30,
251
+ )
252
+
253
+ # 回傳重導向網址
254
+ if response.status_code in [301, 302, 303]:
255
+ return response.headers.get('Location', '')
256
+
257
+ return response.url
258
+
259
+ def parse_store_map_callback(
260
+ self,
261
+ encrypt_data: str,
262
+ hash_data: str,
263
+ ) -> StoreMapCallback:
264
+ """
265
+ 解析電子地圖回傳資料
266
+
267
+ Args:
268
+ encrypt_data: 加密資料
269
+ hash_data: 雜湊驗證值
270
+
271
+ Returns:
272
+ StoreMapCallback: 門市選擇結果
273
+
274
+ Raises:
275
+ ValueError: 驗證失敗
276
+
277
+ Example:
278
+ >>> callback = service.parse_store_map_callback(
279
+ ... request.form['EncryptData_'],
280
+ ... request.form['HashData_']
281
+ ... )
282
+ >>> print(f"選擇門市: {callback.store_name}")
283
+ """
284
+ # 驗證 HashData
285
+ if not self.verify_hash_data(encrypt_data, hash_data):
286
+ raise ValueError('HashData 驗證失敗')
287
+
288
+ # 解密資料
289
+ decrypted_json = self.aes_decrypt(encrypt_data)
290
+ decrypted = json.loads(decrypted_json)
291
+
292
+ return StoreMapCallback(
293
+ lgs_type=decrypted.get('LgsType', ''),
294
+ ship_type=decrypted.get('ShipType', ''),
295
+ merchant_order_no=decrypted.get('MerchantOrderNo', ''),
296
+ store_id=decrypted.get('StoreID', ''),
297
+ store_name=decrypted.get('StoreName', ''),
298
+ store_addr=decrypted.get('StoreAddr', ''),
299
+ store_tel=decrypted.get('StoreTel', ''),
300
+ extra_data=decrypted.get('ExtraData', ''),
301
+ raw=decrypted,
302
+ )
303
+
304
+ def create_shipment(
305
+ self,
306
+ data: CVSShipmentData,
307
+ ) -> CVSShipmentResponse:
308
+ """
309
+ 建立 CVS 超商物流訂單
310
+
311
+ Args:
312
+ data: CVS 物流訂單資料 (CVSShipmentData)
313
+
314
+ Returns:
315
+ CVSShipmentResponse: 物流訂單回應
316
+
317
+ Raises:
318
+ ValueError: 參數驗證失敗
319
+ Exception: API 請求失敗
320
+
321
+ Example:
322
+ >>> shipment_data = CVSShipmentData(
323
+ ... merchant_order_no='SHIP123',
324
+ ... lgs_type='C2C',
325
+ ... ship_type='1',
326
+ ... receiver_store_code='123456',
327
+ ... receiver_name='王小明',
328
+ ... receiver_cell_phone='0912345678',
329
+ ... goods_amount=500,
330
+ ... goods_name='測試商品',
331
+ ... is_collection='N',
332
+ ... )
333
+ >>> result = service.create_shipment(shipment_data)
334
+ >>> print(result.logistics_no)
335
+ """
336
+ # 參數驗證
337
+ if len(data.merchant_order_no) > 30:
338
+ raise ValueError('訂單編號不可超過 30 字元')
339
+
340
+ # 準備加密資料
341
+ encrypt_data_obj = {
342
+ 'MerchantOrderNo': data.merchant_order_no,
343
+ 'LgsType': data.lgs_type,
344
+ 'ShipType': data.ship_type,
345
+ 'ReceiverStoreCode': data.receiver_store_code,
346
+ 'ReceiverName': data.receiver_name,
347
+ 'ReceiverCellPhone': data.receiver_cell_phone,
348
+ 'TimeStamp': str(int(time.time())),
349
+ }
350
+
351
+ # 可選參數
352
+ if data.receiver_tel:
353
+ encrypt_data_obj['ReceiverTel'] = data.receiver_tel
354
+ if data.goods_amount:
355
+ encrypt_data_obj['GoodsAmount'] = data.goods_amount
356
+ if data.goods_name:
357
+ encrypt_data_obj['GoodsName'] = data.goods_name
358
+ if data.sender_name:
359
+ encrypt_data_obj['SenderName'] = data.sender_name
360
+ if data.sender_cell_phone:
361
+ encrypt_data_obj['SenderCellPhone'] = data.sender_cell_phone
362
+ if data.is_collection == 'Y':
363
+ encrypt_data_obj['IsCollection'] = 'Y'
364
+ encrypt_data_obj['CollectionAmount'] = data.collection_amount
365
+ if data.note:
366
+ encrypt_data_obj['Note'] = data.note
367
+
368
+ # JSON 序列化並加密
369
+ json_str = json.dumps(encrypt_data_obj, ensure_ascii=False)
370
+ encrypt_data = self.aes_encrypt(json_str)
371
+ hash_data = self.generate_hash_data(encrypt_data)
372
+
373
+ # 準備 API 請求
374
+ api_data = {
375
+ 'UID_': self.merchant_id,
376
+ 'EncryptData_': encrypt_data,
377
+ 'HashData_': hash_data,
378
+ 'Version_': '1.0',
379
+ 'RespondType_': 'JSON',
380
+ }
381
+
382
+ # 發送 API 請求
383
+ try:
384
+ response = requests.post(
385
+ f'{self.base_url}/createShipment',
386
+ data=api_data,
387
+ timeout=30,
388
+ )
389
+ response.raise_for_status()
390
+ result = response.json()
391
+ except Exception as e:
392
+ raise Exception(f'API 請求失敗: {str(e)}')
393
+
394
+ # 解析回應
395
+ if result.get('Status') != 'SUCCESS':
396
+ return CVSShipmentResponse(
397
+ success=False,
398
+ status=result.get('Status', 'ERROR'),
399
+ message=result.get('Message', '未知錯誤'),
400
+ merchant_order_no=data.merchant_order_no,
401
+ raw=result,
402
+ )
403
+
404
+ # 解密回應資料
405
+ response_encrypt_data = result.get('EncryptData_', '')
406
+ response_hash_data = result.get('HashData_', '')
407
+
408
+ if response_encrypt_data and response_hash_data:
409
+ try:
410
+ if not self.verify_hash_data(response_encrypt_data, response_hash_data):
411
+ raise ValueError('回應 HashData 驗證失敗')
412
+
413
+ decrypted_json = self.aes_decrypt(response_encrypt_data)
414
+ decrypted = json.loads(decrypted_json)
415
+ except Exception as e:
416
+ return CVSShipmentResponse(
417
+ success=False,
418
+ status='ERROR',
419
+ message=f'解密失敗: {str(e)}',
420
+ merchant_order_no=data.merchant_order_no,
421
+ raw=result,
422
+ )
423
+ else:
424
+ decrypted = {}
425
+
426
+ return CVSShipmentResponse(
427
+ success=True,
428
+ status=result.get('Status', ''),
429
+ message=result.get('Message', ''),
430
+ merchant_order_no=data.merchant_order_no,
431
+ logistics_no=decrypted.get('LogisticsNo'),
432
+ cvs_payment_no=decrypted.get('CVSPaymentNo'),
433
+ cvs_validation_no=decrypted.get('CVSValidationNo'),
434
+ booking_note=decrypted.get('BookingNote'),
435
+ raw=result,
436
+ )
437
+
438
+
439
+ # Usage Example
440
+ if __name__ == '__main__':
441
+ print('=' * 60)
442
+ print('NewebPay 藍新物流 CVS - Python 範例')
443
+ print('=' * 60)
444
+ print()
445
+
446
+ # 檢查依賴套件
447
+ if not HAS_DEPENDENCIES:
448
+ print('✗ 錯誤: 需要安裝必要套件')
449
+ print(' 請執行: pip install pycryptodome requests')
450
+ exit(1)
451
+
452
+ print('[注意] 請先至藍新金流申請測試帳號')
453
+ print('並將以下參數替換為您的測試環境資訊')
454
+ print()
455
+
456
+ # 初始化服務 (使用測試環境)
457
+ service = NewebPayCVSLogistics(
458
+ merchant_id='YOUR_MERCHANT_ID',
459
+ hash_key='YOUR_HASH_KEY', # 32 字元
460
+ hash_iv='YOUR_HASH_IV', # 16 字元
461
+ is_production=False,
462
+ )
463
+
464
+ # 範例 1: 查詢電子地圖 (門市選擇)
465
+ print('[範例 1] 查詢電子地圖 (門市選擇)')
466
+ print('-' * 60)
467
+
468
+ store_data = StoreMapData(
469
+ merchant_order_no=f'MAP{int(time.time())}',
470
+ lgs_type='C2C',
471
+ ship_type='1', # 7-ELEVEN
472
+ return_url='https://your-site.com/callback/store-map',
473
+ extra_data='order_id=123',
474
+ )
475
+
476
+ try:
477
+ redirect_url = service.query_store_map(store_data)
478
+ print(f'✓ 電子地圖網址: {redirect_url}')
479
+ print('請將使用者導向此網址選擇門市')
480
+ except Exception as e:
481
+ print(f'✗ 發生例外: {str(e)}')
482
+
483
+ print()
484
+ print('-' * 60)
485
+
486
+ # 範例 2: 建立 C2C 物流訂單
487
+ print('[範例 2] 建立 C2C 物流訂單')
488
+ print('-' * 60)
489
+
490
+ shipment_data = CVSShipmentData(
491
+ merchant_order_no=f'SHIP{int(time.time())}',
492
+ lgs_type='C2C',
493
+ ship_type='1', # 7-ELEVEN
494
+ receiver_store_code='123456', # 收件門市代號 (從電子地圖取得)
495
+ receiver_name='王小明',
496
+ receiver_cell_phone='0912345678',
497
+ goods_amount=500,
498
+ goods_name='測試商品',
499
+ sender_name='賣家',
500
+ sender_cell_phone='0987654321',
501
+ is_collection='N', # 不代收貨款
502
+ )
503
+
504
+ try:
505
+ result = service.create_shipment(shipment_data)
506
+
507
+ if result.success:
508
+ print(f'✓ 物流訂單建立成功')
509
+ print(f' 訂單編號: {result.merchant_order_no}')
510
+ print(f' 物流編號: {result.logistics_no}')
511
+ print(f' 寄貨編號: {result.cvs_payment_no}')
512
+ print(f' 驗證碼: {result.cvs_validation_no}')
513
+ print(f' 托運單號: {result.booking_note}')
514
+ else:
515
+ print(f'✗ 物流訂單建立失敗')
516
+ print(f' 狀態: {result.status}')
517
+ print(f' 訊息: {result.message}')
518
+ except Exception as e:
519
+ print(f'✗ 發生例外: {str(e)}')
520
+
521
+ print()
522
+ print('=' * 60)
523
+ print('範例執行完成')
524
+ print('=' * 60)