feat: improve CreditCall receipt layout
This commit is contained in:
parent
afb8bb0e9e
commit
c8cbd1e7a3
@ -245,95 +245,174 @@ func printCardholderReceipt(cardholderReceipt string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiptColumns follows the existing 44-column normal-size text convention.
|
||||
const receiptColumns = 44
|
||||
|
||||
func BuildCardholderReceipt(entries []ReceiptEntryXML) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
rendered := make([]bool, len(entries))
|
||||
take := func(id string) string {
|
||||
for i, entry := range entries {
|
||||
if !rendered[i] && entry.ReceiptEntryId == id && entry.Value != "" {
|
||||
rendered[i] = true
|
||||
return receiptEntryText(entry)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
write := func(b []byte) { buf.Write(b) }
|
||||
// writeStr := func(s string) { buf.WriteString(s) }
|
||||
writeln := func(s string) {
|
||||
buf.WriteString(s)
|
||||
// Set and restore only text state; leave printer setup and transport alone.
|
||||
normal := func() { buf.Write([]byte{ESC, 'a', 0, ESC, 'E', BOLD_OFF, GS, '!', NORMAL_FONT}) }
|
||||
normal()
|
||||
hasRows, newSection := false, false
|
||||
beginLine := func(center bool) {
|
||||
if hasRows && newSection {
|
||||
buf.WriteString(strings.Repeat("-", receiptColumns) + "\n")
|
||||
}
|
||||
hasRows, newSection = true, false
|
||||
if center {
|
||||
buf.Write([]byte{ESC, 'a', CENTER})
|
||||
}
|
||||
}
|
||||
text := func(value string, bold bool) {
|
||||
if bold {
|
||||
buf.Write([]byte{ESC, 'E', BOLD_ON})
|
||||
}
|
||||
buf.WriteString(value)
|
||||
if bold {
|
||||
buf.Write([]byte{ESC, 'E', BOLD_OFF})
|
||||
}
|
||||
}
|
||||
line := func(value string, bold, center bool) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
beginLine(center)
|
||||
text(value, bold)
|
||||
buf.WriteByte('\n')
|
||||
normal()
|
||||
}
|
||||
pair := func(left, right string, boldRight bool) {
|
||||
if gap, fits := receiptPairGap(left, right); fits {
|
||||
beginLine(false)
|
||||
text(left+gap, false)
|
||||
text(right, boldRight)
|
||||
buf.WriteByte('\n')
|
||||
normal()
|
||||
return
|
||||
}
|
||||
line(left, false, false)
|
||||
line(right, boldRight, false)
|
||||
}
|
||||
|
||||
// Build a lookup map by ReceiptEntryId
|
||||
m := make(map[string]ReceiptEntryXML, len(entries))
|
||||
for _, e := range entries {
|
||||
m[e.ReceiptEntryId] = e
|
||||
line(take("Recipient"), true, true)
|
||||
line(take("MerchantName"), false, true)
|
||||
newSection = true
|
||||
line(take("CardScheme"), false, false)
|
||||
line(take("PanMasked"), false, false)
|
||||
pair(take("Aid"), take("PanSequence"), false)
|
||||
pair(take("TransactionSource"), take("TransactionType"), false)
|
||||
newSection = true
|
||||
// Align the provider amount without parsing or modifying its value.
|
||||
for i, entry := range entries {
|
||||
if entry.ReceiptEntryId == "TotalAmount" && entry.Value != "" {
|
||||
rendered[i] = true
|
||||
label := entry.Label
|
||||
if label == "" {
|
||||
label = "TOTAL"
|
||||
}
|
||||
label = strings.TrimSuffix(label, ":") + ":"
|
||||
if gap, fits := receiptPairGap(label, entry.Value); fits {
|
||||
line(label+gap+entry.Value, true, false)
|
||||
} else {
|
||||
line(receiptEntryText(entry), true, false)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Utility to center text
|
||||
// center := func(s string) {
|
||||
// write([]byte{ESC, 'a', CENTER})
|
||||
// writeln(s)
|
||||
// write([]byte{ESC, 'a', 0})
|
||||
// }
|
||||
|
||||
// Utility for bold lines
|
||||
// boldOn := func() { write([]byte{GS, '!', WIDER_FONT}) }
|
||||
// boldOff := func() { write([]byte{GS, '!', NORMAL_FONT}) }
|
||||
|
||||
// boldOn()
|
||||
|
||||
// 1) Header: CARDHOLDER COPY
|
||||
writeln(m["Recipient"].Value)
|
||||
// writeStr("\n\n")
|
||||
// 2) Merchant name
|
||||
writeln(m["MerchantName"].Value)
|
||||
// writeStr("\n\n")
|
||||
// 3) AID
|
||||
writeln("AID: " + m["Aid"].Value)
|
||||
|
||||
// 4) Card scheme & pan
|
||||
writeln(fmt.Sprintf("%s Card: %s", m["CardScheme"].Value, m["PanMasked"].Value))
|
||||
|
||||
// 5) PAN Seq No
|
||||
writeln("PAN Seq No: " + m["PanSequence"].Value)
|
||||
|
||||
// 6) Transaction source
|
||||
writeln(m["TransactionSource"].Value)
|
||||
|
||||
// 7) Transaction type (Sale/Account Verification)
|
||||
writeln(m["TransactionType"].Value)
|
||||
|
||||
// 8) Total
|
||||
// assuming value like "GBP1.50" — insert space after currency
|
||||
total := m["TotalAmount"].Value
|
||||
if !strings.HasPrefix(total, "GBP") && len(total) > 3 {
|
||||
total = total[:3] + " " + total[3:]
|
||||
pair(take("CardholderVerification"), take("TransactionResult"), true)
|
||||
newSection = true
|
||||
line(take("AuthCode"), false, false)
|
||||
line(take("AuthReference"), false, false)
|
||||
line(take("TransactionSeqNumber"), false, false)
|
||||
pair(take("MerchantIdMasked"), take("TerminalIdMasked"), false)
|
||||
line(take("AuthDateTime"), false, false)
|
||||
footer := take("RetentionMessage")
|
||||
// Preserve every remaining occurrence, including unknown IDs, in provider order.
|
||||
for i, entry := range entries {
|
||||
if !rendered[i] && entry.Value != "" {
|
||||
line(receiptEntryText(entry), false, false)
|
||||
}
|
||||
}
|
||||
writeln("TOTAL: " + total)
|
||||
|
||||
// 9) Cardholder verification
|
||||
writeln(m["CardholderVerification"].Value)
|
||||
|
||||
// 10) Approved/Declined
|
||||
writeln(m["TransactionResult"].Value)
|
||||
|
||||
// 11) Auth code
|
||||
writeln("Auth Code: " + m["AuthCode"].Value)
|
||||
|
||||
// 12) Reference
|
||||
writeln("Ref: " + m["AuthReference"].Value)
|
||||
|
||||
// 13) Merchant & terminal IDs
|
||||
writeln("MID: " + m["MerchantIdMasked"].Value)
|
||||
writeln("TID: " + m["TerminalIdMasked"].Value)
|
||||
|
||||
// 14) Date/time
|
||||
writeln(m["AuthDateTime"].Value)
|
||||
|
||||
// 15) Retention message
|
||||
writeln(m["RetentionMessage"].Value)
|
||||
|
||||
// boldOff()
|
||||
|
||||
// finally feed & cut
|
||||
write([]byte{ESC, 'd', 7}) // Feed 5 lines
|
||||
write([]byte{GS, 'V', 1})
|
||||
|
||||
newSection = true
|
||||
line(footer, false, true)
|
||||
normal()
|
||||
buf.Write([]byte{ESC, 'd', 7})
|
||||
buf.Write([]byte{GS, 'V', 1})
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func receiptEntryText(entry ReceiptEntryXML) string {
|
||||
label := entry.Label
|
||||
if label == "" {
|
||||
switch entry.ReceiptEntryId {
|
||||
case "Recipient", "MerchantName", "CardScheme", "TransactionSource", "TransactionType",
|
||||
"CardholderVerification", "TransactionResult", "AuthDateTime", "RetentionMessage":
|
||||
return entry.Value
|
||||
case "PanMasked":
|
||||
label = "Card"
|
||||
case "Aid":
|
||||
label = "AID"
|
||||
case "PanSequence":
|
||||
label = "PAN Seq No"
|
||||
case "TotalAmount":
|
||||
label = "TOTAL"
|
||||
case "AuthCode":
|
||||
label = "Auth Code"
|
||||
case "AuthReference":
|
||||
label = "Ref"
|
||||
case "TransactionSeqNumber":
|
||||
label = "Transaction Seq. No"
|
||||
case "MerchantIdMasked":
|
||||
label = "MID"
|
||||
case "TerminalIdMasked":
|
||||
label = "TID"
|
||||
default:
|
||||
var readable strings.Builder
|
||||
runes := []rune(entry.ReceiptEntryId)
|
||||
for i, r := range runes {
|
||||
if i > 0 && r >= 'A' && r <= 'Z' &&
|
||||
((runes[i-1] >= 'a' && runes[i-1] <= 'z') ||
|
||||
(i+1 < len(runes) && runes[i+1] >= 'a' && runes[i+1] <= 'z')) {
|
||||
readable.WriteByte(' ')
|
||||
}
|
||||
if r == '_' || r == '-' {
|
||||
r = ' '
|
||||
}
|
||||
readable.WriteRune(r)
|
||||
}
|
||||
label = strings.TrimSpace(readable.String())
|
||||
}
|
||||
}
|
||||
if label == "" {
|
||||
return entry.Value
|
||||
}
|
||||
return strings.TrimSuffix(label, ":") + ": " + entry.Value
|
||||
}
|
||||
|
||||
func receiptPairGap(left, right string) (string, bool) {
|
||||
if left == "" || right == "" || len(left)+2+len(right) > receiptColumns {
|
||||
return "", false
|
||||
}
|
||||
// Only printable ASCII has a predictable single-column width here.
|
||||
for _, r := range left + right {
|
||||
if r < ' ' || r > '~' {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return strings.Repeat(" ", receiptColumns-len(left)-len(right)), true
|
||||
}
|
||||
|
||||
func printLogo(path string) ([]byte, error) {
|
||||
const maxLogoWidth = 384
|
||||
f, err := os.Open(path)
|
||||
|
||||
280
internal/printer/receipt_test.go
Normal file
280
internal/printer/receipt_test.go
Normal file
@ -0,0 +1,280 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type receiptTestChar struct {
|
||||
value byte
|
||||
center, bold bool
|
||||
}
|
||||
|
||||
type receiptTestOutput struct {
|
||||
lines []string
|
||||
chars []receiptTestChar
|
||||
feeds, cuts int
|
||||
}
|
||||
|
||||
// readReceiptText understands only the commands emitted by this receipt builder.
|
||||
func readReceiptText(t *testing.T, data []byte) receiptTestOutput {
|
||||
t.Helper()
|
||||
var out receiptTestOutput
|
||||
var line strings.Builder
|
||||
var center, bold bool
|
||||
for i := 0; i < len(data); {
|
||||
if data[i] == ESC || data[i] == GS {
|
||||
if i+2 >= len(data) {
|
||||
t.Fatalf("BuildCardholderReceipt control bytes = %v, want complete command", data[i:])
|
||||
}
|
||||
prefix, command, value := data[i], data[i+1], data[i+2]
|
||||
switch {
|
||||
case prefix == ESC && command == 'a':
|
||||
if value != 0 && value != CENTER {
|
||||
t.Errorf("receipt alignment = %d, want left or center", value)
|
||||
}
|
||||
center = value == CENTER
|
||||
case prefix == ESC && command == 'E':
|
||||
if value != BOLD_OFF && value != BOLD_ON {
|
||||
t.Errorf("receipt bold = %d, want on or off", value)
|
||||
}
|
||||
bold = value == BOLD_ON
|
||||
case prefix == GS && command == '!':
|
||||
if value != NORMAL_FONT {
|
||||
t.Errorf("receipt size = %d, want normal size", value)
|
||||
}
|
||||
case prefix == ESC && command == 'd':
|
||||
out.feeds++
|
||||
if value != 7 || center || bold || i != len(data)-6 {
|
||||
t.Errorf("receipt feed = %d, center=%t bold=%t offset=%d, want final seven-line feed in normal state", value, center, bold, i)
|
||||
}
|
||||
case prefix == GS && command == 'V':
|
||||
out.cuts++
|
||||
if value != 1 || i != len(data)-3 {
|
||||
t.Errorf("receipt cut = %d at %d, want final GS V 1", value, i)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("BuildCardholderReceipt command = %v, want supported text command", data[i:i+3])
|
||||
}
|
||||
i += 3
|
||||
continue
|
||||
}
|
||||
if data[i] == '\n' {
|
||||
out.lines = append(out.lines, line.String())
|
||||
line.Reset()
|
||||
} else {
|
||||
line.WriteByte(data[i])
|
||||
out.chars = append(out.chars, receiptTestChar{value: data[i], center: center, bold: bold})
|
||||
}
|
||||
i++
|
||||
}
|
||||
if line.Len() != 0 || center || bold || out.feeds != 1 || out.cuts != 1 {
|
||||
t.Errorf("receipt termination: trailing=%q center=%t bold=%t feeds=%d cuts=%d, want normal state and one feed/cut", line.String(), center, bold, out.feeds, out.cuts)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildReceiptText(t *testing.T, entries []ReceiptEntryXML) receiptTestOutput {
|
||||
t.Helper()
|
||||
before := append([]ReceiptEntryXML(nil), entries...)
|
||||
data, err := BuildCardholderReceipt(entries)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildCardholderReceipt(%v) error = %v, want nil", entries, err)
|
||||
}
|
||||
if !reflect.DeepEqual(entries, before) {
|
||||
t.Errorf("BuildCardholderReceipt input = %v, want unchanged %v", entries, before)
|
||||
}
|
||||
if !bytes.HasSuffix(data, []byte{ESC, 'd', 7, GS, 'V', 1}) {
|
||||
t.Errorf("BuildCardholderReceipt suffix = %v, want unchanged feed/cut", data)
|
||||
}
|
||||
return readReceiptText(t, data)
|
||||
}
|
||||
|
||||
func sampleReceipt(recipient string) []ReceiptEntryXML {
|
||||
return []ReceiptEntryXML{
|
||||
{ReceiptEntryId: "Recipient", Value: recipient},
|
||||
{ReceiptEntryId: "MerchantName", Value: "Standard Test Merchant"},
|
||||
{ReceiptEntryId: "Aid", Value: "A0000000031010", Label: "AID"},
|
||||
{ReceiptEntryId: "CardScheme", Value: "VISA DEBIT"},
|
||||
{ReceiptEntryId: "PanMasked", Value: "************1133", Label: "Card"},
|
||||
{ReceiptEntryId: "PanSequence", Value: "01", Label: "PAN Seq No"},
|
||||
{ReceiptEntryId: "TransactionSource", Value: "ICC"},
|
||||
{ReceiptEntryId: "TransactionType", Value: "SALE"},
|
||||
{ReceiptEntryId: "TotalAmount", Value: "GBP120.00", Label: "TOTAL"},
|
||||
{ReceiptEntryId: "CardholderVerification", Value: "PIN VERIFIED"},
|
||||
{ReceiptEntryId: "TransactionResult", Value: "APPROVED"},
|
||||
{ReceiptEntryId: "AuthCode", Value: "D0A35D", Label: "Auth Code"},
|
||||
{ReceiptEntryId: "AuthReference", Value: "ChipDNA-Sale-20260908144731", Label: "Ref"},
|
||||
{ReceiptEntryId: "MerchantIdMasked", Value: "******7897", Label: "MID"},
|
||||
{ReceiptEntryId: "TerminalIdMasked", Value: "****9508", Label: "TID"},
|
||||
{ReceiptEntryId: "TransactionSeqNumber", Value: "00001059", Label: "Transaction Seq. No"},
|
||||
{ReceiptEntryId: "AuthDateTime", Value: "08/09/2026 14:48:06"},
|
||||
{ReceiptEntryId: "RetentionMessage", Value: "Please retain for your records"},
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCardholderReceiptCompleteLayout(t *testing.T) {
|
||||
for _, recipient := range []string{"CARDHOLDER COPY", "MERCHANT COPY"} {
|
||||
t.Run(recipient, func(t *testing.T) {
|
||||
entries := sampleReceipt(recipient)
|
||||
out := buildReceiptText(t, entries)
|
||||
// Ignore padding used for left/right placement, not semantic row order.
|
||||
var rows []string
|
||||
for _, line := range out.lines {
|
||||
rows = append(rows, strings.Join(strings.Fields(line), " "))
|
||||
}
|
||||
rule := strings.Repeat("-", 44)
|
||||
want := []string{recipient, "Standard Test Merchant", rule, "VISA DEBIT", "Card: ************1133",
|
||||
"AID: A0000000031010 PAN Seq No: 01", "ICC SALE", rule, "TOTAL: GBP120.00", "PIN VERIFIED APPROVED", rule,
|
||||
"Auth Code: D0A35D", "Ref: ChipDNA-Sale-20260908144731", "Transaction Seq. No: 00001059",
|
||||
"MID: ******7897 TID: ****9508", "08/09/2026 14:48:06", rule, "Please retain for your records"}
|
||||
if !reflect.DeepEqual(rows, want) {
|
||||
t.Errorf("BuildCardholderReceipt(%q) rows = %q, want %q", recipient, rows, want)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if !strings.Contains(strings.Join(out.lines, "\n"), entry.Value) {
|
||||
t.Errorf("BuildCardholderReceipt(%q) lost %s=%q", recipient, entry.ReceiptEntryId, entry.Value)
|
||||
}
|
||||
}
|
||||
for _, line := range out.lines {
|
||||
if line == "" || len(line) > 44 {
|
||||
t.Errorf("BuildCardholderReceipt(%q) row = %q, want compact nonempty row within 44 columns", recipient, line)
|
||||
}
|
||||
}
|
||||
if len(out.lines) > 19 {
|
||||
t.Errorf("BuildCardholderReceipt(%q) rows = %d, want representative receipt at most 19", recipient, len(out.lines))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCardholderReceiptFormattingState(t *testing.T) {
|
||||
out := buildReceiptText(t, sampleReceipt("CARDHOLDER COPY"))
|
||||
var plain strings.Builder
|
||||
for _, char := range out.chars {
|
||||
plain.WriteByte(char.value)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
text string
|
||||
center, bold bool
|
||||
}{
|
||||
{"CARDHOLDER COPY", true, true}, {"Standard Test Merchant", true, false},
|
||||
{"VISA DEBIT", false, false}, {"TOTAL:", false, true}, {"GBP120.00", false, true},
|
||||
{"PIN VERIFIED", false, false}, {"APPROVED", false, true}, {"Auth Code:", false, false},
|
||||
{"Please retain for your records", true, false},
|
||||
} {
|
||||
start := strings.Index(plain.String(), tc.text)
|
||||
if start < 0 {
|
||||
t.Fatalf("receipt text lacks %q", tc.text)
|
||||
}
|
||||
for _, char := range out.chars[start : start+len(tc.text)] {
|
||||
if char.center != tc.center || char.bold != tc.bold {
|
||||
t.Errorf("receipt style for %q = center:%t bold:%t, want center:%t bold:%t", tc.text, char.center, char.bold, tc.center, tc.bold)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCardholderReceiptMissingFields(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
entries []ReceiptEntryXML
|
||||
want []string
|
||||
}{
|
||||
{name: "empty"},
|
||||
{name: "empty values", entries: []ReceiptEntryXML{{ReceiptEntryId: "Aid", Label: "AID"}, {ReceiptEntryId: "Unknown", Label: "Unused"}}},
|
||||
{name: "one partner", entries: []ReceiptEntryXML{{ReceiptEntryId: "PanSequence", Value: "02"}}, want: []string{"PAN Seq No: 02"}},
|
||||
{name: "result only", entries: []ReceiptEntryXML{{ReceiptEntryId: "TransactionResult", Value: "DECLINED"}}, want: []string{"DECLINED"}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out := buildReceiptText(t, tc.entries)
|
||||
if !reflect.DeepEqual(out.lines, tc.want) {
|
||||
t.Errorf("BuildCardholderReceipt(%v) rows = %q, want %q", tc.entries, out.lines, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCardholderReceiptPreservesRemainingEntries(t *testing.T) {
|
||||
entries := []ReceiptEntryXML{
|
||||
{ReceiptEntryId: "AuthCode", Value: ""},
|
||||
{ReceiptEntryId: "FooBar", Value: "first-extra", Priority: "9"},
|
||||
{ReceiptEntryId: "AuthCode", Value: "primary", Label: "Provider authorization"},
|
||||
{ReceiptEntryId: "AuthCode", Value: "duplicate", Label: "Second authorization"},
|
||||
{ReceiptEntryId: "FooBar", Value: "second-extra", Label: "Foo", Priority: "1"},
|
||||
{ReceiptEntryId: "RetentionMessage", Value: "Retain this"},
|
||||
{ReceiptEntryId: "RetentionMessage", Value: "Extra retention"},
|
||||
{ReceiptEntryId: "HTTPCode", Value: "provider-code"},
|
||||
{Value: "unlabelled-value"},
|
||||
}
|
||||
out := buildReceiptText(t, entries)
|
||||
want := []string{"Provider authorization: primary", "Foo Bar: first-extra", "Second authorization: duplicate",
|
||||
"Foo: second-extra", "Extra retention", "HTTP Code: provider-code", "unlabelled-value", strings.Repeat("-", 44), "Retain this"}
|
||||
if !reflect.DeepEqual(out.lines, want) {
|
||||
t.Errorf("BuildCardholderReceipt remaining rows = %q, want %q", out.lines, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCardholderReceiptPairBoundaries(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, left, right string
|
||||
paired bool
|
||||
}{
|
||||
{"short", "ICC", "SALE", true},
|
||||
{"exact width", strings.Repeat("L", 20), strings.Repeat("R", 22), true},
|
||||
{"overflow", strings.Repeat("L", 21), strings.Repeat("R", 22), false},
|
||||
{"unicode", "TAP \u00c9", "SALE", false},
|
||||
{"multiline", "ICC\nFALLBACK", "SALE", false},
|
||||
{"left missing", "", "SALE", false},
|
||||
{"right missing", "ICC", "", false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out := buildReceiptText(t, []ReceiptEntryXML{{ReceiptEntryId: "TransactionSource", Value: tc.left}, {ReceiptEntryId: "TransactionType", Value: tc.right}})
|
||||
var want []string
|
||||
if tc.paired {
|
||||
want = []string{tc.left + strings.Repeat(" ", 44-len(tc.left)-len(tc.right)) + tc.right}
|
||||
} else {
|
||||
if tc.left != "" {
|
||||
want = append(want, strings.Split(tc.left, "\n")...)
|
||||
}
|
||||
if tc.right != "" {
|
||||
want = append(want, tc.right)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(out.lines, want) {
|
||||
t.Errorf("BuildCardholderReceipt pair(%q,%q) rows = %q, want %q", tc.left, tc.right, out.lines, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCardholderReceiptLongValuesAndLabels(t *testing.T) {
|
||||
ref := strings.Repeat("long-reference-", 8)
|
||||
verify := "CONSUMER DEVICE VERIFICATION WITH ADDITIONAL PROVIDER TEXT"
|
||||
out := buildReceiptText(t, []ReceiptEntryXML{
|
||||
{ReceiptEntryId: "CardholderVerification", Value: verify},
|
||||
{ReceiptEntryId: "TransactionResult", Value: "DECLINED"},
|
||||
{ReceiptEntryId: "AuthReference", Label: "Provider reference:", Value: ref},
|
||||
{ReceiptEntryId: "MerchantIdMasked", Label: strings.Repeat("Merchant", 6), Value: "******7897"},
|
||||
{ReceiptEntryId: "TerminalIdMasked", Value: "****9508"},
|
||||
})
|
||||
want := []string{verify, "DECLINED", strings.Repeat("-", 44), "Provider reference: " + ref,
|
||||
strings.Repeat("Merchant", 6) + ": ******7897", "TID: ****9508"}
|
||||
if !reflect.DeepEqual(out.lines, want) {
|
||||
t.Errorf("BuildCardholderReceipt long fields rows = %q, want %q", out.lines, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCardholderReceiptProviderAmounts(t *testing.T) {
|
||||
for _, amount := range []string{"GBP120.00", "EUR 42,50", "USD1.23", "amount supplied verbatim", strings.Repeat("9", 50)} {
|
||||
t.Run(amount, func(t *testing.T) {
|
||||
out := buildReceiptText(t, []ReceiptEntryXML{{ReceiptEntryId: "TotalAmount", Label: "Provider total", Value: amount}})
|
||||
if len(out.lines) != 1 || !strings.HasPrefix(out.lines[0], "Provider total:") || !strings.HasSuffix(out.lines[0], amount) {
|
||||
t.Errorf("BuildCardholderReceipt amount(%q) rows = %q, want provider label and unchanged amount", amount, out.lines)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user