ECR Payment Json
Send ECR Request to mPOS
Process:
a.Ensure that the terminal interface is the POS integration interface of QR code (open ECR through API, re-enter the app or refresh the status to enter this page)

b.Pair the terminal with "Pair" (the value of MessageCategory is "Pair"). After it is successfully paired, the page for Waiting Ecr Instructions is displayed
Refer to the encryption demo at the bottom
Communication Methods Supported
OnlineAsync:
-
POS and mPOS can not be on the same LAN and are not limited by distance
-
No need to wait after the communication is initiated, and the query results can be polled later (or callback via url).
`https:
OfflineAsync:
-
POS and mPOS are on the same LAN
-
Communication is faster and more stable
-
No need to wait after the communication is initiated, and the query results can be polled later (or callback via url).
172.16.91.29:8443/wonder-terminal/async
OfflineSync: Synchronous Communication under the same LAN
172.16.91.29:8443/wonder-terminal/sync
API Illustration
Currently Supported Functions
- Pair
- Payment
- PreAuth
- TransactionStatus
- Reversal
- Void
- Abort
- Push
- SwitchBusiness
Request Format
{
"SaleToPOIRequest": {
"MessageHeader": {
"ProtocolVersion": "1.0",
"MessageClass": "Service",
"MessageCategory": "Pair",
"MessageType": "Request",
"ServiceID": "4e412543v1v14e3",
"POIID": "PAX-A930-1170270945",
"BusinessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f"
},
"SecurityTrailer": {
"KeyVersion": "1",
"KeyIdentifier": "",
"Hmac": "",
"Nonce": "",
"WonderCryptoVersion": 0
},
"WonderBlob":"",
"PushRequest": {
"Action": "ECR_CANCEL"
},
"SwitchBusinessRequest":{
"BusinessID":"a5467f02-2914-5d1a-21bb-bffcca55fe94"
},
"TransactionStatusRequest": {
"MessageReference": {
"ServiceID": "59b90600-13cf-11ee-bab2-99e6a6ef5392",
"MessageCategory": "Payment"
}
},
"ReversalRequest": {
"SaleData": {
"SaleToAcquirerData": "currency=HKD"
},
"OriginalPOITransaction": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5049",
"OriginBRN": "3267887011708553217",
"ReferenceNumber":"8daf4dc0-6ad6-44b1-8256-a0f1549c0fa6"
}
},
"ReversedAmount": 2.0
},
"VoidRequest": {
"OriginalPOITransaction": {
"POITransactionID": {
"TransactionID": "guid",
"OriginBRN": "3267887011708553217",
"ReferenceNumber":"8daf4dc0-6ad6-44b1-8256-a0f1549c0fa6"
}
}
},
"PaymentRequest": {
"SaleData": {
"SaleTransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392",
"TimeStamp": "2019-03-07T10:11:04+00:00",
"CallBackUrl":"https:",
"ReferenceNumber":"8daf4dc0-6ad6-44b1-8256-a0f1549c0fa6",
"AppKey":"ca82cb30-44af-4c1d-a211-98c0403d1f9f",
"AppSlug":"1wU7Au"
}
},
"PaymentTransaction": {
"AmountsReq": {
"Currency": "HKD",
"RequestedAmount": "11.2"
}
}
},
"AbortRequest": {
"AbortReason": "MerchantAbort",
"MessageReference": {
"MessageCategory": "Payment",
"ServiceID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
}
}
}
MessageHeader:
| key | value | Remark |
|---|---|---|
| ProtocolVersion | 1.0 | Agreement Version(Fixed to be “1.0") |
| MessageClass | Service | Agreement(Fixed to be “Service") |
| MessageCategory | Pair | Pair Payment TransactionStatus Reversal Void Abort Push -Six functions supported |
| MessageType | Request | Fixed to be "Request" |
| ServiceID | Suggest:uuid | It is used to distinguish each request and prevent repeated processing PostMan "guid" |
| POIID | Device SN | Eg:PAX-A930-1191851002 |
| BusinessID | Business ID | eg:ff467f02-5b69-45f3-81aa-bffcca55fe21 You can obtain it on the binding QR code page |
WonderBlob:
WonderBlob is an encrypted string, and the source data corresponds to the data in **Request. Developers need to decrypt for their own use
PushRequest
PushRequest is a special case and it doesn't need to be encryted. If it is needed for use, need to reserve it into the request structure.
Response Result Returning Format
{
"MessageHeader": {
"BusinessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f",
"MessageCategory": "TransactionStatus",
"MessageClass": "Service",
"MessageType": "Response",
"POIID": "PAX-A930-1170270945",
"ProtocolVersion": "1.0",
"ServiceID": "31fd1gg4fg6211"
},
"SecurityTrailer": {
"KeyVersion": "",
"KeyIdentifier": "",
"Hmac": "",
"Nonce": "",
"WonderCryptoVersion": 0
},
"WonderBlob": null,
"AbortResponse": null,
"PairResponse": null,
"PaymentResponse": null,
"PushResponse": null,
"ReversalResponse": null,
"SecurityTrailer": null,
"SwitchBusinessResponse":null,
"TransactionStatusResponse": {
"RepeatedMessageResponse": {
"MessageHeader": {
"BusinessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f",
"MessageCategory": "TransactionStatus",
"MessageClass": "Service",
"MessageType": "Response",
"POIID": "PAX-A930-1170270945",
"ProtocolVersion": "1.0",
"ServiceID": "31fd1gg4fg6211"
},
"RepeatedResponseMessageBody": {
"PaymentResponse": null,
"ReversalResponse": {
"POIData": null,
"PaymentReceipt": null,
"Response": {
"AdditionalResponse": "UUID Conflict",
"ErrorCondition": null,
"Result": "Failure"
},
"ReversedAmount": null
}
}
},
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
}
}
}
- WonderBlob is an encrypted string, and the source data corresponds to the data in **Response. You can use the type of MessageCategory to find the corresponding ****Response to decrypt it.
- PushResponse will also keep the structure but will not be encrypted
- "MessageType": Response corresponds to Request
- Every Response corresponds to Request
- Response returns in two formats
- Sync. When these two requests come, the service does not return immediately. Instead, it wait for the operation to finish and then return the result. TransactionStatus(querying payment status) is not supported in this mode.
- Async .Under this model, all request return immediatly (Payment,Reversal)but it will not return the actual payment result, instead, it will receive a structure body "Request Received". The real payment result requires the client to query the result through TransactionStatus in the mode of rotation training.
Payment
{
"PaymentRequest": {
"SaleData": {
"SaleTransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392",
"TimeStamp": "2019-03-07T10:11:04+00:00",
"CallBackUrl": "https:",
"ReferenceNumber": "8daf4dc0-6ad6-44b1-8256-a0f1549c0fa6",
"AppKey": "ca82cb30-44af-4c1d-a211-98c0403d1f9f",
"AppSlug": "1wU7Au",
"PaymentType": "Optional",
"CodeType": "qrcode",
"CodeData": "https:",
"PrinterMsg": "MCC ID: 84930838505\n"
}
},
"PaymentTransaction": {
"AmountsReq": {
"Currency": "HKD",
"RequestedAmount": "10.2"
}
}
}
}
{
"PaymentResponse": {
"POIData": null,
"PaymentReceipt": null,
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
}
}
}
{
"PaymentResponse": {
"POIData": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
},
"Response": {
"Result": "Failure",
"ErrorCondition": "Aborted",
"AdditionalResponse": null
},
"PaymentReceipt": null
}
}
{
"PaymentResponse": {
"POIData": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
},
"Response": {
"Result": "Success",
"ErrorCondition": null,
"AdditionalResponse": null
},
"PaymentReceipt": {
"amount": "10.20",
"tipsAmount": "0.00",
"currency": "HKD",
"payment_method": "fps",
"transaction_state": "success",
"date_time": "2023-06-30T09:08:52+00:00",
"merchant_id": "3333",
"terminal_id": null,
"rrn": "3263492852830699521",
"brn": "3263492852495159296",
"acquirer_type": "fps",
"receiptData": {
"reference_number": "W-158637380062",
"sale_transactions": [
{
"success": true,
"amount": "10.2",
"payment_method": 22,
"payment": "FPS",
"payment_data": {
"credit_card_type": "fps",
"new_gateway_txn_id": "3263492852495159296",
"rrn": "3263492852830699521",
"merchant_id": "3333",
"terminal_id": "",
"created_at": "2023-06-30T09:08:52+00:00",
"reference_id": "3263492852495159296",
"auth_code": "3263492852830699522"
}
}
],
"initial_total": "10.2",
"initial_tips": null,
"subtotal": "10.2"
}
}
}
}
About PaymentType:
- can be null, if null, then the PaymentType will be "Optional"
- "Card": use Card payment like: "Visa", "MasterCard", "JCB", "American Express", "Diners Club", "Discover", "Union Pay", "Apple Pay","Google Pay","Huawei Pay","Samsung Pay"
- "Wallets": use Wallets payment like: "Alipay", "WechatPay", "Xpay","Other" et. need client show their QRCode by smartphone
- "Octopus": use Octopus payment like: "Octopus"
- "QRCodePresent": use QRCodePresent payment like: "FPS" et. need client scan the QRCode by smartphone
- "Optional": No Specified Payment Type. The above modes need to be specified manually
- before use the above modes, need to check the merchant's payment method support that
About “extra business data”
- can be null
- show a QRCode or BarCode at Receipt
- barcode type is "code39"
- like this

Preauth
{
"PreauthRequest": {
"SaleData": {
"SaleTransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392",
"TimeStamp": "2019-03-07T10:11:04+00:00",
"ReferenceNumber": "8daf4dc0-6ad6-44b1-8256-a0f1549c0fa6",
"CallBackUrl": "https:",
"AppKey": "ca82cb30-44af-4c1d-a211-98c0403d1f9f",
"AppSlug": "1wU7Au"
}
},
"PaymentTransaction": {
"AmountsReq": {
"Currency": "HKD",
"RequestedAmount": "10.2"
}
}
}
}
{
"PreauthResponse": {
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
}
}
}
{
"PreauthResponse": {
"POIData": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
},
"Response": {
"Result": "Failure",
"ErrorCondition": "Aborted",
"AdditionalResponse": null
}
}
}
{
"PreauthResponse": {
"POIData": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
},
"Response": {
"Result": "Success",
"ErrorCondition": null,
"AdditionalResponse": null
},
"PaymentReceipt": {
"amount": "10.20",
"tipsAmount": "0.00",
"currency": "HKD",
"payment_method": "fps",
"transaction_state": "success",
"date_time": "2023-06-30T09:08:52+00:00",
"merchant_id": "3333",
"terminal_id": null,
"rrn": "3263492852830699521",
"brn": "3263492852495159296",
"acquirer_type": "fps",
"receiptData": {
"reference_number": "W-158637380062",
"sale_transactions": [
{
"success": true,
"amount": "10.2",
"payment_method": 22,
"payment": "FPS",
"payment_data": {
"credit_card_type": "fps",
"new_gateway_txn_id": "3263492852495159296",
"rrn": "3263492852830699521",
"merchant_id": "3333",
"terminal_id": "",
"created_at": "2023-06-30T09:08:52+00:00",
"reference_id": "3263492852495159296",
"auth_code": "3263492852830699522"
}
}
],
"initial_total": "10.2",
"initial_tips": null,
"subtotal": "10.2"
}
}
}
}
Abort
{
"AbortRequest": {
"AbortReason": "MerchantAbort",
"MessageReference": {
"MessageCategory": "Payment",
"ServiceID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
}
}
- AbortReason: can be empty
- MessageCategory: Support Payment/Preauth
- ServiceID: TransactionID uuid of PaymentRequest/PreauthRequest
Reversal
{
"ReversalRequest": {
"SaleData": {
"SaleToAcquirerData": "currency=HKD"
},
"OriginalPOITransaction": {
"OriginalPOITransaction": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5049",
"OriginBRN": "3267887011708553217",
"ReferenceNumber": "8daf4dc0-6ad6-44b1-8256-a0f1549c0fa6"
}
}
},
"ReversedAmount": 2.0
}
}
{
"ReversalResponse": {
"POIData": null,
"PaymentReceipt": null,
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
},
"ReversedAmount": null
}
}
Void( The same as Reversal)
{
"VoidRequest": {
"OriginalPOITransaction": {
"POITransactionID": {
"TransactionID": "guid",
"OriginBRN": "3267887011708553217",
"ReferenceNumber": "8daf4dc0-6ad6-44b1-8256-a0f1549c0fa6"
}
}
}
}
TransactionStatus(Payment/Void/Reversal/Preauth)
{
"TransactionStatusRequest": {
"MessageReference": {
"ServiceID": "59b90600-13cf-11ee-bab2-99e6a6ef5392",
"MessageCategory": "Payment"
}
}
}
{
"TransactionStatusResponse": {
"RepeatedMessageResponse": null,
"Response": {
"AdditionalResponse": "TransactionStatusResponse is inProgress",
"ErrorCondition": "InProgress",
"Result": "Failure"
}
}
}
{
"TransactionStatusResponse": {
"RepeatedMessageResponse": {
"MessageHeader": {
"BusinessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f",
"MessageCategory": "TransactionStatus",
"MessageClass": "Service",
"MessageType": "Response",
"POIID": "PAX-A930-1170270945",
"ProtocolVersion": "1.0",
"ServiceID": "31fdrg23gg45人d34f4fg6211"
},
"RepeatedResponseMessageBody": {
"PaymentResponse": {
"POIData": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
},
"PaymentReceipt": null,
"Response": {
"AdditionalResponse": null,
"ErrorCondition": "Aborted",
"Result": "Failure"
}
},
"ReversalResponse": null
}
},
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
}
}
}
{
"TransactionStatusResponse": {
"RepeatedMessageResponse": {
"MessageHeader": {
"BusinessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f",
"MessageCategory": "TransactionStatus",
"MessageClass": "Service",
"MessageType": "Response",
"POIID": "PAX-A930-1170270945",
"ProtocolVersion": "1.0",
"ServiceID": "31fdrg23gg4f42a5人d34f4fg6211"
},
"RepeatedResponseMessageBody": {
"PaymentResponse": {
"POIData": {
"POITransactionID": {
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5392"
}
},
"PaymentReceipt": {
"acquirer_type": "fps",
"amount": "10.20",
"brn": "3263590179977302016",
"currency": "HKD",
"date_time": "2023-06-30T10:45:33+00:00",
"merchant_id": "3333",
"payment_method": "fps",
"receiptData": {
"initial_tips": null,
"initial_total": "10.2",
"reference_number": "W-142631801078",
"sale_transactions": [
{
"amount": "10.2",
"payment": "FPS",
"payment_data": {
"auth_code": "3263590180312842242",
"created_at": "2023-06-30T10:45:33+00:00",
"credit_card_type": "fps",
"merchant_id": "3333",
"new_gateway_txn_id": "3263590179977302016",
"reference_id": "3263590179977302016",
"rrn": "3263590180312842241",
"terminal_id": ""
},
"payment_method": 22,
"success": true
}
],
"subtotal": "10.2"
},
"rrn": "3263590180312842241",
"terminal_id": null,
"tipsAmount": "0.00",
"transaction_state": "success"
},
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
}
},
"ReversalResponse": null
}
},
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
}
}
}
The Result of Response are “Success” and “Failure”
ErrorCondition:
- InProgress(The task is underworking)
- Aborted(The task is cancelled)
- NotFound(Can not find the transaction)
- UnKnown(Unknown error)
AdditionalResponse: Normally it has no content. If any, this is for reminding the developer.
TransactionStatus(Reversal)
"TransactionStatusRequest": {
"MessageReference": {
"ServiceID": "59b90600-13cf-11ee-bab2-99e6a6ef5941",
"MessageCategory": "Reversal"
}
}
"TransactionStatusResponse": {
"RepeatedMessageResponse": {
"MessageHeader": {
"BusinessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f",
"MessageCategory": "TransactionStatus",
"MessageClass": "Service",
"MessageType": "Response",
"POIID": "PAX-A930-1170270945",
"ProtocolVersion": "1.0",
"ServiceID": "31fdrg234g4g4f42a5人d34f4fg6211"
},
"RepeatedResponseMessageBody": {
"PaymentResponse": null,
"ReversalResponse": {
"POIData": {
"POITransactionID": {
"OriginBRN": null,
"TimeStamp": "2023-06-30T18:47:12.686525",
"TransactionID": "59b90600-13cf-11ee-bab2-99e6a6ef5941"
}
},
"PaymentReceipt": {
"acquirer_auth_code": "3263590180312842242",
"acquirer_rrn": "3263590180312842241",
"acquirer_settlement_at": null,
"adjust_times": 0,
"amount": "10.2",
"closed_at": null,
"consumer_identify": "",
"created_at": "2023-06-30T10:45:33.38Z",
"credit_card_fall_back": false,
"currency": "HKD",
"deleted_at": null,
"fee_amount": "0.1",
"fee_currency": "HKD",
"id": "3263590179977302016",
"initial_total": 0,
"mid": "3333",
"oms_transaction_id": 75780958,
"order": {
"additional_documents": null,
"auth_code": "bzoRKzdmKypmwPQ",
"business_name": "Bindo Labs Limited Test 2",
"confidence_store": null,
"correspondence_state": "paid",
"created_at": "2023-06-30T10:45:33Z",
"created_by": "Wonder POS",
"currency": "HKD",
"custom_attributes": null,
"customer_country_code": null,
"customer_dial_code": null,
"customer_email": null,
"customer_linked_source_type": null,
"customer_name": null,
"customer_phone": null,
"customer_reference": null,
"delivery_date": null,
"discount_total": 0,
"due_date": "2023-07-01 23:59:59",
"fail_transactions": [],
"files": null,
"from": 11,
"id": 15619648,
"initial_normal_tax": 0,
"initial_tax": 0,
"initial_tips": 0,
"initial_total": 10.2,
"inventory_status": null,
"line_items": [
{
"discount_total": 0,
"id": 18096861,
"image_url": "",
"label": "Charge",
"price": 10.2,
"purchasable_type": "Charge",
"purchase_id": null,
"quantity": 1,
"total": 10.2
}
],
"note": null,
"number": "202306301845331287548306",
"oms_delivery_note": {
"address1": null,
"address2": null,
"city": null,
"country": null,
"email": null,
"name": null,
"note": null,
"phone": null
},
"paid_total": "10.2",
"payment_link_url": "https:
"reference_number": "W-142631801078",
"refund_transactions": [],
"sale_transactions": [
{
"3ds": {
"enrolled": "",
"redirect_url": ""
},
"acquirer_response_body": "",
"allow_refund": false,
"allow_void": true,
"amount": 10.2,
"created_at": "2023-06-30T10:45:34Z",
"currency": "HKD",
"extra": {
"credit_card_type": null,
"first_6_digits": null,
"holder_name": null,
"last_4_digits": null,
"number": null
},
"from": 16,
"id": 75780958,
"is_pending": false,
"note": null,
"payment": "HKFPS",
"payment_data": {
"acquirer_name": "fake",
"acquirer_type": "fps",
"auth_code": "3263590180312842242",
"batch_no": "",
"brn": "75780958",
"device_id": "PAX-A930-1170270945",
"merchant_id": "3333",
"new_gateway_txn_id": "3263590179977302016",
"payment_inst": "",
"payment_method": "fps",
"rrn": "3263590180312842241",
"trace_no": "",
"transaction_state": "success"
},
"payment_method": 22,
"reference_id": "3263590179977302016",
"refunded_amount": 0,
"success": true,
"tips_amount": null,
"voided_at": null
}
],
"sale_type": "invoice_sale",
"signatures": [],
"state": "completed",
"subtotal": 10.2,
"surcharge_fee": 0,
"type": "Invoice",
"unpaid_total": "0",
"updated_at": "2023-06-30T10:45:34Z",
"void_line_items": [],
"void_transactions": []
},
"order_info": {
"customer_reference": null,
"reference_number": "W-142631801078"
},
"order_num": "202306301845331287548306",
"org_txn_id": "0",
"p_business_id": "ff467f02-5b69-45f3-81aa-bffcca55fe8f",
"payment_entry_type": "merchant_presented_qr_code",
"payment_method": "fps",
"payment_type": "sale",
"remark": "",
"settlement_amount": "10.1",
"settlement_currency": "HKD",
"signature": "",
"store_id": "538047",
"tag_length_values": null,
"tid": "",
"tid_batch_num": 0,
"tid_trace_num": 0,
"tipsAmount": "0",
"tipsTransaction": null,
"total_amount": "10.2",
"transacted_on": "0001-01-01T00:00:00Z",
"updated_at": "2023-06-30T10:45:33.905Z",
"void_at": null
},
"Response": {
"AdditionalResponse": "Success",
"ErrorCondition": null,
"Result": "Success"
},
"ReversedAmount": 2
}
}
},
"Response": {
"AdditionalResponse": null,
"ErrorCondition": null,
"Result": "Success"
}
}
SwitchBusiness
{
"SwitchBusinessRequest": {
"BusinessID": "a5467f02-2914-5d1a-21bb-bffcca55fe94"
},
"SwitchBusinessResponse": {
"Response": {
"Result": "Success",
"AdditionalResponse": null,
"ErrorCondition": null
}
}
}
Attentions
- If you need to use SwitchBusiness, call SwitchBusiness every time you call another API
- all the slug/key/authorization/businessId both use store data with device bind(Except PaymentRequest)
- We recommend that AppKey/AppSlug in PaymentRequest use the value corresponding to the (A/B/C/D) store
Encrypted Code(dart):
- import thrid packages
- encrypt: 5.0.1
- pbkdf2ns: 0.0.2
class EncryptionUtils {
static const salt = 'WMSaltV1';
static const int rounds = 4000;
static const int keyLength = 80;
static KeyMaterial keyMaterial = KeyMaterial('123456');
static init(String passphrase) {
keyMaterial = KeyMaterial(passphrase);
}
static SaleToPoiResponse encryptWonderBlob(Map<String, dynamic>? map) {
if (map == null) return SaleToPoiResponse();
var str = json.encode(map);
var iv_2 = List<int>.generate(16, (i) => Random.secure().nextInt(256));
var iv = List<int>.generate(16, (i) => keyMaterial.iv[i] ^ iv_2[i]);
final encrypter = Encrypter(
AES(Key(Uint8List.fromList(keyMaterial.cipherKey)), mode: AESMode.cbc));
final encrypted =
encrypter.encrypt(str, iv: IV(Uint8List.fromList(iv))).base64;
String keyhex = keyMaterial.hmacKey
.map((i) => i.toRadixString(16).padLeft(2, '0'))
.join("");
var hash = Hmac(sha256, utf8.encode(keyhex)).convert(str.codeUnits).bytes;
return SaleToPoiResponse.fromJson({
"SecurityTrailer": {
"KeyVersion": "",
"KeyIdentifier": "",
"Hmac": hash.toBase64(),
"Nonce": iv_2.toBase64(),
"WonderCryptoVersion": 1
},
"WonderBlob": encrypted
});
}
static decryptWonderBlob(String wonderBlob, SecurityTrailer securityTrailer) {
try {
var hmac_key = base64.decode(securityTrailer.hmac!);
var iv_2 = base64.decode(securityTrailer.nonce!);
var iv = List<int>.generate(16, (i) => keyMaterial.iv[i] ^ iv_2[i]);
final key = Key(Uint8List.fromList(keyMaterial.cipherKey));
final encrypter = Encrypter(AES(key, mode: AESMode.cbc));
var result = encrypter.decrypt(Encrypted.fromBase64(wonderBlob),
iv: IV(Uint8List.fromList(iv)));
String keyhex = keyMaterial.hmacKey
.map((i) => i.toRadixString(16).padLeft(2, '0'))
.join("");
var hash =
Hmac(sha256, utf8.encode(keyhex)).convert(result.codeUnits).bytes;
if (hmac_key.toBase64() != hash.toBase64()) {
throw DecryptException('');
}
return result;
} catch (e) {
throw DecryptException('Decryption failure');
}
}
}
class KeyMaterial {
late List<int> hmacKey;
late List<int> cipherKey;
late List<int> iv;
KeyMaterial(String passphrase) {
PBKDF2NS gen = PBKDF2NS(hash: sha1);
List<int> key = gen.generateKey(passphrase, EncryptionUtils.salt,
EncryptionUtils.rounds, EncryptionUtils.keyLength);
hmacKey = key.sublist(0, 32);
cipherKey = key.sublist(32, 64);
iv = key.sublist(64, 80);
}
}
Encrypted Code(go):
- import thrid packages
- https:
package encrypt
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/forgoer/openssl"
"golang.org/x/crypto/pbkdf2"
)
type SecretResp struct {
Data struct {
EnableEcr bool `json:"enable_ecr"`
EcrKey string `json:"ecr_key"`
} `json:"data"`
Code int `json:"code"`
Message string `json:"message"`
}
type KeyMaterial struct {
HmacKey []byte
CipherKey []byte
IV []byte
}
type SecurityTrailer struct {
KeyVersion string `json:"KeyVersion"`
KeyIdentifier string `json:"KeyIdentifier"`
Hmac string `json:"Hmac"`
Nonce string `json:"Nonce"`
WonderCryptoVersion int `json:"WonderCryptoVersion"`
}
type SaleToPoiResponse struct {
SecurityTrailer SecurityTrailer `json:"SecurityTrailer"`
WonderBlob string `json:"WonderBlob"`
WonderCryptoVersion int `json:"WonderCryptoVersion"`
}
type Encryption struct {
salt string
rounds int
keyLength int
}
func NewEncryption() *Encryption {
return &Encryption{
salt: "WMSaltV1",
rounds: 4000,
keyLength: 80,
}
}
func (e *Encryption) GenerateKeyMaterial(passphrase string) KeyMaterial {
var keyMaterial KeyMaterial
key := pbkdf2.Key([]byte(passphrase), []byte(e.salt), e.rounds, e.keyLength, sha1.New)
keyMaterial.HmacKey = key[:32]
keyMaterial.CipherKey = key[32:64]
keyMaterial.IV = key[64:80]
return keyMaterial
}
func (e *Encryption) EncryptWonderBlob(src interface{}, keyMaterial KeyMaterial) (*SaleToPoiResponse, error) {
if src == nil {
return nil, errors.New("")
}
plaintext, err := json.Marshal(src)
if err != nil {
return nil, err
}
iv2 := []byte{255, 88, 159, 70, 231, 201, 10, 60, 95, 140, 221, 100, 82, 152, 239, 115}
iv := make([]byte, 16)
for i := range iv {
iv[i] = keyMaterial.IV[i] ^ iv2[i]
}
ciphertext, err := openssl.AesCBCEncrypt(plaintext, keyMaterial.CipherKey, iv, openssl.PKCS7_PADDING)
if err != nil {
return nil, err
}
fmt.Println(base64.StdEncoding.EncodeToString(ciphertext))
hash, err := calculateHash(plaintext, keyMaterial.HmacKey)
if err != nil {
return nil, err
}
fmt.Println(hash)
return &SaleToPoiResponse{
SecurityTrailer: SecurityTrailer{
Hmac: base64.StdEncoding.EncodeToString(hash),
Nonce: base64.StdEncoding.EncodeToString(iv2),
WonderCryptoVersion: 1,
},
WonderBlob: base64.StdEncoding.EncodeToString(ciphertext),
}, nil
}
func (e *Encryption) DecryptWonderBlob(ciphertext []byte, securityTrailer SecurityTrailer, keyMaterial KeyMaterial) ([]byte, error) {
iv2, err := base64.StdEncoding.DecodeString(securityTrailer.Nonce)
if err != nil {
return nil, err
}
iv := make([]byte, 16)
for i := range iv {
iv[i] = keyMaterial.IV[i] ^ iv2[i]
}
plaintext, err := openssl.AesCBCDecrypt(ciphertext, keyMaterial.CipherKey, iv, openssl.PKCS7_PADDING)
if err != nil {
return nil, err
}
fmt.Println(string(plaintext))
hash, err := calculateHash(plaintext, keyMaterial.HmacKey)
if err != nil {
return nil, err
}
if securityTrailer.Hmac != base64.StdEncoding.EncodeToString(hash) {
return nil, errors.New("HMAC verification failed")
}
return plaintext, nil
}
func calculateHash(message []byte, hmacKey []byte) ([]byte, error) {
var keyHex string
for _, b := range hmacKey {
keyHex += fmt.Sprintf("%02x", b)
}
mac := hmac.New(sha256.New, []byte(keyHex))
_, err := mac.Write(message)
if err != nil {
return nil, err
}
return mac.Sum(nil), nil
}
Encrypted data flow
[165, 138, 241, 50, 162, 203, 20, 42, 15, 10, 129, 234, 91, 89, 216, 213, 54, 156, 17, 145, 252, 246, 97, 56, 79, 254, 159, 155, 195, 102, 221, 214]
[124, 16, 196, 209, 149, 179, 163, 231, 22, 222, 229, 9, 69, 253, 158, 112, 161, 10, 5, 57, 4, 59, 181, 224, 116, 159, 128, 19, 26, 176, 103, 124]
[97, 99, 31, 10, 239, 170, 238, 34, 14, 249, 144, 174, 230, 229, 97, 15]
[255, 88, 159, 70, 231, 201, 10, 60, 95, 140, 221, 100, 82, 152, 239, 115]
[158, 59, 128, 76, 8, 99, 228, 30, 81, 117, 77, 202, 180, 125, 142, 124]
xd+O7byVDPt6YCm9FagPmQ==
a58af132a2cb142a0f0a81ea5b59d8d5369c1191fcf661384ffe9f9bc366ddd6
[68, 206, 161, 97, 35, 65, 25, 67, 195, 186, 190, 10, 218, 98, 97, 76, 138, 1, 72, 81, 164, 30, 124, 193, 250, 129, 2, 41, 48, 4, 111, 231]
return SaleToPoiResponse.fromJson({
"SecurityTrailer": {
"KeyVersion": "",
"KeyIdentifier": "",
"Hmac": hash.toBase64(),
"Nonce": iv_2.toBase64(),
"WonderCryptoVersion": 1
},
"WonderBlob": encrypted
});