Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Card/Event/Authorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { isoly } from "isoly"
import { isly } from "isly"
import { Amount } from "../../Amount"

export interface Authorization {
type: "authorization"
id: string
outcome: Authorization.Outcome
at: isoly.DateTime
amount: Amount
reason?: string
}
export namespace Authorization {
export const outcomes = ["created", "rejected", "cancelled"] as const
export type Outcome = typeof outcomes[number]
export const type = isly.object<Authorization>({
type: isly.string("authorization"),
id: isly.string(),
outcome: isly.string(outcomes),
at: isly.fromIs("isoly.DateTime", isoly.DateTime.is),
amount: Amount.type,
reason: isly.string().optional(),
})
}
13 changes: 13 additions & 0 deletions Card/Event/Cancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { isoly } from "isoly"
import { isly } from "isly"

export interface Cancel {
type: "cancel"
at: isoly.DateTime
}
export namespace Cancel {
export const type = isly.object<Cancel>({
type: isly.string(),
at: isly.fromIs("isoly.DateTime", isoly.DateTime.is),
})
}
18 changes: 18 additions & 0 deletions Card/Event/Change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { isoly } from "isoly"
import { isly } from "isly"
import { Changeable } from "../Changeable"

export interface Change {
type: "change"
at: isoly.DateTime
from: Changeable
to: Changeable
}
export namespace Change {
export const type = isly.object<Change>({
type: isly.string("change"),
at: isly.fromIs("isoly.DateTime", isoly.DateTime.is),
from: Changeable.type,
to: Changeable.type,
})
}
25 changes: 25 additions & 0 deletions Card/Event/Clearing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { isoly } from "isoly"
import { isly } from "isly"

export interface Clearing {
type: Clearing.Type
at: isoly.DateTime
currency: isoly.Currency
total: number
net: number
fee: number
charge?: number
}
export namespace Clearing {
export const types = ["capture", "refund"] as const
export type Type = typeof types[number]
export const type = isly.object<Clearing>({
type: isly.string<Type>(types),
at: isly.fromIs("isoly.DateTime", isoly.DateTime.is),
currency: isly.string(),
total: isly.number(),
net: isly.number(),
fee: isly.number(),
charge: isly.number().optional(),
})
}
16 changes: 16 additions & 0 deletions Card/Event/Create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { isoly } from "isoly"
import { isly } from "isly"
import { Amount } from "../../Amount"

export interface Create {
type: "create"
at: isoly.DateTime
limit: Amount
}
export namespace Create {
export const type = isly.object<Create>({
type: isly.string("create"),
at: isly.fromIs("isoly.DateTime", isoly.DateTime.is),
limit: Amount.type,
})
}
14 changes: 14 additions & 0 deletions Card/Event/Index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { pax2pay } from "../../index"

describe("Card.Event", () => {
const event: pax2pay.Card.Event = {
type: "authorization",
id: "19236hf",
outcome: "created",
created: "2023-12-13T12:11:00.000Z",
amount: ["UAH", 9999],
}
it("authorization", () => {
expect(pax2pay.Card.Event.type.is(event)).toBeTruthy()
})
})
43 changes: 43 additions & 0 deletions Card/Event/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { isoly } from "isoly"
import { isly } from "isly"
import type { Authorization as ModelAuthorization } from "../../Authorization"
import type { Entry } from "../../Settlement/Entry"
import { Authorization as EventAuthorization } from "./Authorization"
import { Cancel as EventCancel } from "./Cancel"
import { Change as EventChange } from "./Change"
import { Clearing as EventClearing } from "./Clearing"
import { Create as EventCreate } from "./Create"

export type Event = Event.Create | Event.Cancel | Event.Change | Event.Authorization | Event.Clearing

export namespace Event {
export import Create = EventCreate
export import Cancel = EventCancel
export import Change = EventChange
export import Authorization = EventAuthorization
export import Clearing = EventClearing
export const type = isly.union<Event>(Create.type, Cancel.type, Change.type, Event.Authorization.type, Clearing.type)
export function fromAuthorization(authorization: ModelAuthorization): Event {
return {
type: "authorization",
id: authorization.id,
outcome: authorization.status != "approved" ? "rejected" : "created",
at: authorization.created,
reason: authorization.status == "approved" ? undefined : authorization.status,
amount: authorization.amount, // FIXME: we need the total transaction amount on auth
}
}
export function fromEntry(entry: Entry): Event | undefined {
return entry.type == "unknown" || entry.type == "cancel"
? undefined
: {
type: entry.type,
at: isoly.DateTime.now(),
currency: entry.amount[0],
net: entry.amount[1],
fee: entry.fee.other[entry.amount[0]] ?? 0,
total: isoly.Currency.add(entry.amount[0], entry.amount[1], entry.fee.other[entry.amount[0]] ?? 0), // FIXME: this computation should probably be done in entry
// FIXME: charge: entry.charge,
}
}
}
22 changes: 0 additions & 22 deletions Card/Operation/Authorization.ts

This file was deleted.

22 changes: 0 additions & 22 deletions Card/Operation/Card.ts

This file was deleted.

13 changes: 0 additions & 13 deletions Card/Operation/Operation.spec.ts

This file was deleted.

42 changes: 0 additions & 42 deletions Card/Operation/index.ts

This file was deleted.

43 changes: 29 additions & 14 deletions Card/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import type { Rule } from "../Rule"
import { type as ruleType } from "../Rule/type"
import { Changeable as CardChangeable } from "./Changeable"
import { Creatable as CardCreatable } from "./Creatable"
import { Event as CardEvent } from "./Event"
import { Expiry as CardExpiry } from "./Expiry"
import { Meta as CardMeta } from "./Meta"
import { Operation as CardOperation } from "./Operation"
import { Preset as CardPreset } from "./Preset"
import { Scheme as CardScheme } from "./Scheme"
import { Stack as CardStack } from "./Stack"
Expand All @@ -24,17 +24,12 @@ export interface Card {
preset: CardPreset
scheme: CardScheme
reference?: string
details: {
iin: string
last4: string
expiry: CardExpiry
holder: string
token?: string
}
details: { iin: string; last4: string; expiry: CardExpiry; holder: string; token?: string }
limit: Amount
spent: Amount
status: "active" | "cancelled"
history: CardOperation[]
history: any[]
events?: Card.Event[]
rules: Rule[]
meta?: CardMeta
}
Expand All @@ -59,7 +54,8 @@ export namespace Card {
limit: isly.tuple(isly.fromIs("isoly.Currency", isoly.Currency.is), isly.number()),
spent: isly.tuple(isly.fromIs("isoly.Currency", isoly.Currency.is), isly.number()),
status: isly.union(isly.string("active"), isly.string("cancelled")),
history: isly.array(CardOperation.type),
history: isly.any(),
events: CardEvent.type.array().optional(),
rules: ruleType.array(),
meta: isly.fromIs("Card.Meta", CardMeta.is).optional(),
})
Expand All @@ -69,13 +65,22 @@ export namespace Card {
export import Meta = CardMeta
export import Expiry = CardExpiry
export import Changeable = CardChangeable
export import Operation = CardOperation
export import Event = CardEvent
export import Scheme = CardScheme
export import Stack = CardStack
const csvMap: Record<string, (card: Card) => string | number | undefined> = {
id: card => card.id,
created: card => readableDate(card.created),
cancelled: card => readableDate(card.history.find(o => o.type == "card" && o.status == "cancelled")?.created),
cancelled: card => {
if (card.events) {
return readableDate(card.events.find(o => o.type == "cancel")?.at)
} else {
// for legacy reasons
return readableDate(
card.history?.find(o => o.type == "card" && "status" in o && o.status == "cancelled")?.created
)
}
},
"organization.code": card => card.organization,
realm: card => card.realm,
account: card => card.account,
Expand All @@ -89,8 +94,18 @@ export namespace Card {
expiry: card => readableDate(Expiry.toDateTime(card.details.expiry)),
iin: card => card.details.iin,
holder: card => card.details.holder,
"authorization.count": card => card.history.filter(o => o.type == "authorization" && o.status == "created").length,
"capture.count": card => card.history.filter(o => o.type == "authorization" && o.status == "captured").length,
"authorization.count": card => {
const authorizations = card.events?.filter(o => o.type == "authorization" && o.outcome == "created").length ?? 0
const legacy =
card.history?.filter(o => o.type == "authorization" && "status" in o && o.status == "created").length ?? 0
return authorizations + legacy
},
"capture.count": card => {
const captures = card.events?.filter(o => o.type == "capture").length ?? 0
const legacy =
card.history?.filter(o => o.type == "authorization" && "status" in o && o.status == "captured").length ?? 0
return captures + legacy
},
}
function readableDate(date: isoly.DateTime | undefined): string | undefined {
return date && date.slice(0, 10) + " " + (date.endsWith("Z") ? date.slice(11, -1) : date.slice(11))
Expand Down