vr-models 1.0.55 → 1.0.57
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.
- package/dist/models/devicePaymentPlan.models.d.ts +3 -0
- package/dist/models/devicePaymentPlan.models.js +7 -0
- package/dist/models/eventLog.models.d.ts +1 -1
- package/dist/models/eventLog.models.js +2 -0
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +1 -1
- package/dist/models/payment.models.d.ts +37 -18
- package/dist/models/payment.models.js +158 -44
- package/dist/models/paymentEventLog.models.d.ts +69 -0
- package/dist/models/paymentEventLog.models.js +204 -0
- package/dist/models/types.d.ts +1 -1
- package/dist/models/user.models.d.ts +12 -2
- package/dist/models/user.models.js +57 -10
- package/package.json +1 -1
- package/dist/models/transaction.models.d.ts +0 -44
- package/dist/models/transaction.models.js +0 -97
|
@@ -2,6 +2,7 @@ import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonA
|
|
|
2
2
|
import type { Device } from "./device.models";
|
|
3
3
|
import type { User } from "./user.models";
|
|
4
4
|
import type { Installment } from "./installment.models";
|
|
5
|
+
import { Payment } from "./payment.models";
|
|
5
6
|
export type PaymentPlanStatus = "ACTIVE" | "COMPLETED" | "DEFAULTED" | "CANCELLED";
|
|
6
7
|
export declare const PAYMENT_PLAN_STATUS: readonly ["ACTIVE", "COMPLETED", "DEFAULTED", "CANCELLED"];
|
|
7
8
|
export interface DevicePaymentPlanAttributes {
|
|
@@ -19,6 +20,7 @@ export interface DevicePaymentPlanAttributes {
|
|
|
19
20
|
devices?: Device[];
|
|
20
21
|
user: User;
|
|
21
22
|
installments?: Installment[];
|
|
23
|
+
payment?: Payment;
|
|
22
24
|
}
|
|
23
25
|
export interface DevicePaymentPlanCreationAttributes extends Omit<DevicePaymentPlanAttributes, "id" | "createdAt" | "updatedAt" | "paidAmount" | "outstandingAmount" | "lastPaymentAt" | "nextInstallmentDueAt" | "status" | "completedAt" | "devices" | "installments"> {
|
|
24
26
|
id?: string;
|
|
@@ -41,6 +43,7 @@ export declare class DevicePaymentPlan extends Model<InferAttributes<DevicePayme
|
|
|
41
43
|
devices?: NonAttribute<Device[]>;
|
|
42
44
|
user: NonAttribute<User>;
|
|
43
45
|
installments?: NonAttribute<Installment[]>;
|
|
46
|
+
payment?: NonAttribute<Payment>;
|
|
44
47
|
static initialize(sequelize: any): void;
|
|
45
48
|
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
46
49
|
updateProgress(): Promise<void>;
|
|
@@ -91,6 +91,13 @@ class DevicePaymentPlan extends vr_migrations_1.Model {
|
|
|
91
91
|
onDelete: "SET NULL", // ← This is SET NULL, not CASCADE
|
|
92
92
|
onUpdate: "CASCADE",
|
|
93
93
|
});
|
|
94
|
+
// One-to-One with Payment (down payment/full payment upfront)
|
|
95
|
+
this.hasOne(models.Payment, {
|
|
96
|
+
foreignKey: "devicePaymentPlanId",
|
|
97
|
+
as: "downPayment", // DevicePaymentPlan.getDownPayment()
|
|
98
|
+
onDelete: "SET NULL",
|
|
99
|
+
onUpdate: "CASCADE",
|
|
100
|
+
});
|
|
94
101
|
}
|
|
95
102
|
// Custom instance methods
|
|
96
103
|
async updateProgress() {
|
|
@@ -10,7 +10,7 @@ export interface DeviceSessionPayload {
|
|
|
10
10
|
deviceSerialNumber: string;
|
|
11
11
|
minutesGranted: number;
|
|
12
12
|
}
|
|
13
|
-
export declare const EVENT_ACTIONS: readonly ["USER_READ", "USER_CREATED", "USER_UPDATED", "USER_DELETED", "USER_SUSPENDED", "USER_BANNED", "USER_VERIFIED", "USER_UPGRADED_TO_RIDER", "USERS_LISTED", "ADMIN_FORGOT_PASSWORD", "ADMIN_LOGGED_IN", "ADMIN_LOGGED_OUT", "PASSENGER_ACCOUNT_DEACTIVATED", "PASSENGER_ACCOUNT_DELETED", "AGENT_CREATED_RIDER", "AGENT_UPDATED_RIDER", "ADMIN_CREATED_USER", "ADMIN_UPDATED_USER", "ADMIN_CHANGED_PASSWORD", "ADMIN_RESET_USER_PASSWORD", "ADMIN_BULK_PASSWORD_RESET", "SUPER_ADMIN_PASSWORD_CHANGED", "SUPER_ADMIN_CHANGED_USER_ACCOUNT_STATUS", "SUPER_ADMIN_UPDATED_USER", "SUPER_ADMIN_CREATED_USER", "PRODUCT_CREATED", "PRODUCT_READ", "PRODUCT_UPDATED", "PRODUCT_DELETED", "PRODUCTS_LISTED", "PRODUCTS_ACTIVATED", "PRODUCTS_DEACTIVATED", "PRODUCT_STOCK_SET_MANUALLY", "PRODUCT_STOCK_INCREMENTED", "PRODUCT_STOCK_DECREMENTED", "PRICING_CREATED", "PRICING_READ", "PRICING_UPDATED", "PRICING_DELETED", "DEVICE_CREATED", "DEVICES_BULK_CREATED", "DEVICE_ASSIGNED", "DEVICE_READ", "DEVICE_UPDATED", "DEVICES_LISTED", "DEVICE_LOCKED", "DEVICE_UNLOCKED", "DEVICE_DISABLED", "DEVICE_REACTIVATED", "DEVICE_DELETED_PERMANENTLY", "DEVICE_REPOSSESSED", "DEVICE_PAYMENT_PLAN_CREATED", "DEVICE_PAYMENT_PLAN_READ", "DEVICE_PAYMENT_PLAN_UPDATED", "DEVICE_PAYMENT_PLAN_MARKED_DEFAULTED", "DEVICE_PAYMENT_PLAN_SOFT_DELETED", "DEVICE_PAYMENT_PLAN_DELETED_PERMANENTLY", "PAYMENT_RECORDED", "PAYMENT_READ", "PAYMENTS_LISTED", "TRANSACTION_READ", "TRANSACTIONS_LISTED", "EVENT_LOG_READ", "EVENT_LOGS_LISTED", "SECURITY_CLEARANCE_MANAGED", "DEVICE_LOCK_OVERRIDDEN", "SESSION_EXTENDED"];
|
|
13
|
+
export declare const EVENT_ACTIONS: readonly ["USER_READ", "USER_CREATED", "ADMIN_CREATED", "PROFILE_COMPLETED", "USER_UPDATED", "USER_DELETED", "USER_SUSPENDED", "USER_BANNED", "USER_VERIFIED", "USER_UPGRADED_TO_RIDER", "USERS_LISTED", "ADMIN_FORGOT_PASSWORD", "ADMIN_LOGGED_IN", "ADMIN_LOGGED_OUT", "PASSENGER_ACCOUNT_DEACTIVATED", "PASSENGER_ACCOUNT_DELETED", "AGENT_CREATED_RIDER", "AGENT_UPDATED_RIDER", "ADMIN_CREATED_USER", "ADMIN_UPDATED_USER", "ADMIN_CHANGED_PASSWORD", "ADMIN_RESET_USER_PASSWORD", "ADMIN_BULK_PASSWORD_RESET", "SUPER_ADMIN_PASSWORD_CHANGED", "SUPER_ADMIN_CHANGED_USER_ACCOUNT_STATUS", "SUPER_ADMIN_UPDATED_USER", "SUPER_ADMIN_CREATED_USER", "PRODUCT_CREATED", "PRODUCT_READ", "PRODUCT_UPDATED", "PRODUCT_DELETED", "PRODUCTS_LISTED", "PRODUCTS_ACTIVATED", "PRODUCTS_DEACTIVATED", "PRODUCT_STOCK_SET_MANUALLY", "PRODUCT_STOCK_INCREMENTED", "PRODUCT_STOCK_DECREMENTED", "PRICING_CREATED", "PRICING_READ", "PRICING_UPDATED", "PRICING_DELETED", "DEVICE_CREATED", "DEVICES_BULK_CREATED", "DEVICE_ASSIGNED", "DEVICE_READ", "DEVICE_UPDATED", "DEVICES_LISTED", "DEVICE_LOCKED", "DEVICE_UNLOCKED", "DEVICE_DISABLED", "DEVICE_REACTIVATED", "DEVICE_DELETED_PERMANENTLY", "DEVICE_REPOSSESSED", "DEVICE_PAYMENT_PLAN_CREATED", "DEVICE_PAYMENT_PLAN_READ", "DEVICE_PAYMENT_PLAN_UPDATED", "DEVICE_PAYMENT_PLAN_MARKED_DEFAULTED", "DEVICE_PAYMENT_PLAN_SOFT_DELETED", "DEVICE_PAYMENT_PLAN_DELETED_PERMANENTLY", "PAYMENT_RECORDED", "PAYMENT_READ", "PAYMENTS_LISTED", "TRANSACTION_READ", "TRANSACTIONS_LISTED", "EVENT_LOG_READ", "EVENT_LOGS_LISTED", "SECURITY_CLEARANCE_MANAGED", "DEVICE_LOCK_OVERRIDDEN", "SESSION_EXTENDED"];
|
|
14
14
|
export type EventAction = (typeof EVENT_ACTIONS)[number];
|
|
15
15
|
export interface EventLogAttributes {
|
|
16
16
|
id: string;
|
package/dist/models/index.d.ts
CHANGED
|
@@ -7,10 +7,10 @@ export * from "./payment.models";
|
|
|
7
7
|
export * from "./pricing.models";
|
|
8
8
|
export * from "./product.models";
|
|
9
9
|
export * from "./securityClearance.models";
|
|
10
|
-
export * from "./
|
|
10
|
+
export * from "./paymentEventLog.models";
|
|
11
11
|
export * from "./installment.models";
|
|
12
12
|
export * from "./ban.models";
|
|
13
13
|
export * from "./suspension.models";
|
|
14
14
|
export * from "./appSpecs.models";
|
|
15
15
|
export * from "./phoneContact.models";
|
|
16
|
-
export type { UserModel,
|
|
16
|
+
export type { UserModel, SecurityClearanceModel, ProductModel, PricingModel, PaymentModel, PaymentEventLogModel, IdempotencyRecordModel, EventLogModel, DevicePaymentPlanModel, DeviceModel, InstallmentModel, BanModel, SuspensionModel, AppSpecsModel, PhoneContactModel, } from "./types";
|
package/dist/models/index.js
CHANGED
|
@@ -23,7 +23,7 @@ __exportStar(require("./payment.models"), exports);
|
|
|
23
23
|
__exportStar(require("./pricing.models"), exports);
|
|
24
24
|
__exportStar(require("./product.models"), exports);
|
|
25
25
|
__exportStar(require("./securityClearance.models"), exports);
|
|
26
|
-
__exportStar(require("./
|
|
26
|
+
__exportStar(require("./paymentEventLog.models"), exports);
|
|
27
27
|
__exportStar(require("./installment.models"), exports);
|
|
28
28
|
__exportStar(require("./ban.models"), exports);
|
|
29
29
|
__exportStar(require("./suspension.models"), exports);
|
|
@@ -1,65 +1,84 @@
|
|
|
1
1
|
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
2
|
import type { User } from "./user.models";
|
|
3
3
|
import type { DevicePaymentPlan } from "./devicePaymentPlan.models";
|
|
4
|
-
import type {
|
|
4
|
+
import type { Installment } from "./installment.models";
|
|
5
5
|
import type { IdempotencyRecord } from "./idempotencyRecord.models";
|
|
6
|
-
|
|
7
|
-
export type
|
|
8
|
-
export
|
|
9
|
-
export declare const
|
|
6
|
+
import { PaymentEventLog } from "./paymentEventLog.models";
|
|
7
|
+
export type PaymentProvider = "MTN_MOMO" | "AIRTEL_MONEY" | "DPO" | "FLUTTERWAVE";
|
|
8
|
+
export type PaymentStatus = "PENDING" | "SUCCESSFUL" | "FAILED" | "REFUNDED";
|
|
9
|
+
export declare const PAYMENT_PROVIDER: readonly ["MTN_MOMO", "AIRTEL_MONEY", "DPO", "FLUTTERWAVE"];
|
|
10
|
+
export declare const PAYMENT_STATUS: readonly ["PENDING", "SUCCESSFUL", "FAILED", "REFUNDED"];
|
|
10
11
|
export interface PaymentAttributes {
|
|
11
12
|
id: string;
|
|
12
13
|
userId: string;
|
|
13
|
-
devicePaymentPlanId: string;
|
|
14
|
-
|
|
14
|
+
devicePaymentPlanId: string | null;
|
|
15
|
+
installmentId: string | null;
|
|
15
16
|
idempotencyKeyId: string | null;
|
|
16
17
|
amount: number;
|
|
18
|
+
currency: string;
|
|
17
19
|
provider: PaymentProvider;
|
|
18
20
|
providerReference: string | null;
|
|
19
21
|
status: PaymentStatus;
|
|
22
|
+
customerPhone: string;
|
|
23
|
+
customerName: string | null;
|
|
24
|
+
failureReason: string | null;
|
|
20
25
|
metadata: Record<string, any>;
|
|
26
|
+
settledAt: Date | null;
|
|
21
27
|
createdAt: Date;
|
|
22
28
|
updatedAt: Date;
|
|
23
29
|
user?: User;
|
|
24
30
|
devicePaymentPlan?: DevicePaymentPlan;
|
|
25
|
-
|
|
31
|
+
installment?: Installment;
|
|
26
32
|
idempotencyKey?: IdempotencyRecord;
|
|
33
|
+
paymentEventLogs?: PaymentEventLog[];
|
|
27
34
|
}
|
|
28
|
-
export interface PaymentCreationAttributes extends Omit<PaymentAttributes, "id" | "createdAt" | "updatedAt" | "status" | "providerReference" | "
|
|
35
|
+
export interface PaymentCreationAttributes extends Omit<PaymentAttributes, "id" | "createdAt" | "updatedAt" | "status" | "providerReference" | "failureReason" | "settledAt" | "metadata"> {
|
|
29
36
|
id?: string;
|
|
30
37
|
status?: PaymentStatus;
|
|
31
38
|
providerReference?: string | null;
|
|
39
|
+
failureReason?: string | null;
|
|
40
|
+
settledAt?: Date | null;
|
|
32
41
|
metadata?: Record<string, any>;
|
|
33
|
-
transactionId?: string | null;
|
|
34
|
-
idempotencyKeyId?: string | null;
|
|
35
42
|
}
|
|
36
43
|
export declare class Payment extends Model<InferAttributes<Payment>, InferCreationAttributes<Payment>> implements PaymentAttributes {
|
|
37
44
|
id: CreationOptional<string>;
|
|
38
45
|
userId: string;
|
|
39
|
-
devicePaymentPlanId: string
|
|
40
|
-
|
|
46
|
+
devicePaymentPlanId: CreationOptional<string | null>;
|
|
47
|
+
installmentId: CreationOptional<string | null>;
|
|
41
48
|
idempotencyKeyId: CreationOptional<string | null>;
|
|
42
49
|
amount: number;
|
|
50
|
+
currency: string;
|
|
43
51
|
provider: PaymentProvider;
|
|
44
52
|
providerReference: CreationOptional<string | null>;
|
|
45
53
|
status: PaymentStatus;
|
|
54
|
+
customerPhone: string;
|
|
55
|
+
customerName: CreationOptional<string | null>;
|
|
56
|
+
failureReason: CreationOptional<string | null>;
|
|
46
57
|
metadata: CreationOptional<Record<string, any>>;
|
|
58
|
+
settledAt: CreationOptional<Date | null>;
|
|
47
59
|
readonly createdAt: CreationOptional<Date>;
|
|
48
60
|
readonly updatedAt: CreationOptional<Date>;
|
|
49
61
|
user?: NonAttribute<User>;
|
|
50
62
|
devicePaymentPlan?: NonAttribute<DevicePaymentPlan>;
|
|
51
|
-
|
|
63
|
+
installment?: NonAttribute<Installment>;
|
|
52
64
|
idempotencyKey?: NonAttribute<IdempotencyRecord>;
|
|
65
|
+
paymentEventLogs?: NonAttribute<PaymentEventLog[]>;
|
|
53
66
|
static initialize(sequelize: any): void;
|
|
54
67
|
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
55
|
-
|
|
68
|
+
markAsSuccessful(providerReference: string, metadata?: Record<string, any>): Promise<void>;
|
|
56
69
|
markAsFailed(reason: string, metadata?: Record<string, any>): Promise<void>;
|
|
57
|
-
|
|
70
|
+
markAsRefunded(metadata?: Record<string, any>): Promise<void>;
|
|
71
|
+
markAsSettled(settledAt?: Date): Promise<void>;
|
|
58
72
|
isSuccessful(): boolean;
|
|
59
73
|
isPending(): boolean;
|
|
60
74
|
isFailed(): boolean;
|
|
61
|
-
|
|
75
|
+
isRefunded(): boolean;
|
|
76
|
+
getFormattedAmount(): string;
|
|
77
|
+
getTargetType(): "payment_plan" | "installment";
|
|
78
|
+
getTargetId(): string;
|
|
62
79
|
static getUserPayments(userId: string, limit?: number): Promise<Payment[]>;
|
|
63
|
-
static
|
|
80
|
+
static getPlanPayments(planId: string): Promise<Payment[]>;
|
|
81
|
+
static getInstallmentPayments(installmentId: string): Promise<Payment[]>;
|
|
82
|
+
static getPendingPayments(): Promise<Payment[]>;
|
|
64
83
|
}
|
|
65
84
|
export type PaymentModel = typeof Payment;
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Payment = exports.PAYMENT_STATUS = exports.PAYMENT_PROVIDER = void 0;
|
|
4
|
+
// src/models/payment.models.ts
|
|
4
5
|
const vr_migrations_1 = require("vr-migrations");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
exports.PAYMENT_PROVIDER = [
|
|
7
|
+
"MTN_MOMO",
|
|
8
|
+
"AIRTEL_MONEY",
|
|
9
|
+
"DPO",
|
|
10
|
+
"FLUTTERWAVE",
|
|
11
|
+
];
|
|
12
|
+
exports.PAYMENT_STATUS = [
|
|
13
|
+
"PENDING",
|
|
14
|
+
"SUCCESSFUL",
|
|
15
|
+
"FAILED",
|
|
16
|
+
"REFUNDED",
|
|
17
|
+
];
|
|
8
18
|
class Payment extends vr_migrations_1.Model {
|
|
9
19
|
// Static initialization method
|
|
10
20
|
static initialize(sequelize) {
|
|
@@ -14,43 +24,115 @@ class Payment extends vr_migrations_1.Model {
|
|
|
14
24
|
defaultValue: vr_migrations_1.DataTypes.UUIDV4,
|
|
15
25
|
primaryKey: true,
|
|
16
26
|
},
|
|
17
|
-
userId: {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
userId: {
|
|
28
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
29
|
+
allowNull: false,
|
|
30
|
+
},
|
|
31
|
+
devicePaymentPlanId: {
|
|
32
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
33
|
+
allowNull: true,
|
|
34
|
+
},
|
|
35
|
+
installmentId: {
|
|
36
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
37
|
+
allowNull: true,
|
|
38
|
+
},
|
|
39
|
+
idempotencyKeyId: {
|
|
40
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
41
|
+
allowNull: true,
|
|
42
|
+
},
|
|
21
43
|
amount: {
|
|
22
|
-
type: vr_migrations_1.DataTypes.
|
|
44
|
+
type: vr_migrations_1.DataTypes.DECIMAL(10, 2),
|
|
23
45
|
allowNull: false,
|
|
24
46
|
validate: { min: 1 },
|
|
25
47
|
},
|
|
48
|
+
currency: {
|
|
49
|
+
type: vr_migrations_1.DataTypes.STRING(3),
|
|
50
|
+
allowNull: false,
|
|
51
|
+
defaultValue: "RWF",
|
|
52
|
+
},
|
|
26
53
|
provider: {
|
|
27
|
-
type: vr_migrations_1.DataTypes.ENUM("
|
|
54
|
+
type: vr_migrations_1.DataTypes.ENUM("MTN_MOMO", "AIRTEL_MONEY", "DPO", "FLUTTERWAVE"),
|
|
28
55
|
allowNull: false,
|
|
29
56
|
},
|
|
30
|
-
providerReference: {
|
|
57
|
+
providerReference: {
|
|
58
|
+
type: vr_migrations_1.DataTypes.STRING(255),
|
|
59
|
+
allowNull: true,
|
|
60
|
+
},
|
|
31
61
|
status: {
|
|
32
|
-
type: vr_migrations_1.DataTypes.ENUM("PENDING", "SUCCESSFUL", "FAILED"),
|
|
62
|
+
type: vr_migrations_1.DataTypes.ENUM("PENDING", "SUCCESSFUL", "FAILED", "REFUNDED"),
|
|
33
63
|
allowNull: false,
|
|
34
64
|
defaultValue: "PENDING",
|
|
35
65
|
},
|
|
66
|
+
customerPhone: {
|
|
67
|
+
type: vr_migrations_1.DataTypes.STRING(20),
|
|
68
|
+
allowNull: false,
|
|
69
|
+
},
|
|
70
|
+
customerName: {
|
|
71
|
+
type: vr_migrations_1.DataTypes.STRING(100),
|
|
72
|
+
allowNull: true,
|
|
73
|
+
},
|
|
74
|
+
failureReason: {
|
|
75
|
+
type: vr_migrations_1.DataTypes.TEXT,
|
|
76
|
+
allowNull: true,
|
|
77
|
+
},
|
|
36
78
|
metadata: {
|
|
37
79
|
type: vr_migrations_1.DataTypes.JSONB,
|
|
38
80
|
allowNull: false,
|
|
39
81
|
defaultValue: {},
|
|
40
82
|
},
|
|
41
|
-
|
|
42
|
-
|
|
83
|
+
settledAt: {
|
|
84
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
85
|
+
allowNull: true,
|
|
86
|
+
},
|
|
87
|
+
createdAt: {
|
|
88
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
89
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
90
|
+
},
|
|
91
|
+
updatedAt: {
|
|
92
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
93
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
94
|
+
},
|
|
43
95
|
}, {
|
|
44
96
|
sequelize,
|
|
45
|
-
tableName: "payments",
|
|
46
97
|
modelName: "Payment",
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
98
|
+
tableName: "payments",
|
|
99
|
+
timestamps: true,
|
|
100
|
+
freezeTableName: true,
|
|
101
|
+
validate: {
|
|
102
|
+
exactlyOneTarget() {
|
|
103
|
+
const hasPlan = !!this.devicePaymentPlanId;
|
|
104
|
+
const hasInstallment = !!this.installmentId;
|
|
105
|
+
if ((hasPlan && hasInstallment) || (!hasPlan && !hasInstallment)) {
|
|
106
|
+
throw new Error("Payment must belong to exactly one: devicePaymentPlanId OR installmentId");
|
|
51
107
|
}
|
|
52
108
|
},
|
|
53
109
|
},
|
|
110
|
+
indexes: [
|
|
111
|
+
{
|
|
112
|
+
fields: ["userId"],
|
|
113
|
+
name: "payment_user_idx",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
fields: ["devicePaymentPlanId"],
|
|
117
|
+
name: "payment_plan_idx",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
fields: ["installmentId"],
|
|
121
|
+
name: "payment_installment_idx",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
fields: ["status"],
|
|
125
|
+
name: "payment_status_idx",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
fields: ["providerReference"],
|
|
129
|
+
name: "payment_provider_ref_idx",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
fields: ["createdAt"],
|
|
133
|
+
name: "payment_created_at_idx",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
54
136
|
});
|
|
55
137
|
}
|
|
56
138
|
// Static association method
|
|
@@ -64,12 +146,12 @@ class Payment extends vr_migrations_1.Model {
|
|
|
64
146
|
this.belongsTo(models.DevicePaymentPlan, {
|
|
65
147
|
foreignKey: "devicePaymentPlanId",
|
|
66
148
|
as: "devicePaymentPlan",
|
|
67
|
-
onDelete: "
|
|
149
|
+
onDelete: "SET NULL",
|
|
68
150
|
onUpdate: "CASCADE",
|
|
69
151
|
});
|
|
70
|
-
this.belongsTo(models.
|
|
71
|
-
foreignKey: "
|
|
72
|
-
as: "
|
|
152
|
+
this.belongsTo(models.Installment, {
|
|
153
|
+
foreignKey: "installmentId",
|
|
154
|
+
as: "installment",
|
|
73
155
|
onDelete: "SET NULL",
|
|
74
156
|
onUpdate: "CASCADE",
|
|
75
157
|
});
|
|
@@ -79,33 +161,49 @@ class Payment extends vr_migrations_1.Model {
|
|
|
79
161
|
onDelete: "SET NULL",
|
|
80
162
|
onUpdate: "CASCADE",
|
|
81
163
|
});
|
|
164
|
+
this.hasMany(models.PaymentEventLog, {
|
|
165
|
+
foreignKey: "paymentId",
|
|
166
|
+
as: "eventLogs", // Payment.getEventLogs()
|
|
167
|
+
onDelete: "SET NULL",
|
|
168
|
+
});
|
|
82
169
|
}
|
|
83
170
|
// Custom instance methods
|
|
84
|
-
async
|
|
171
|
+
async markAsSuccessful(providerReference, metadata) {
|
|
85
172
|
this.status = "SUCCESSFUL";
|
|
86
173
|
this.providerReference = providerReference;
|
|
87
174
|
if (metadata) {
|
|
88
175
|
this.metadata = { ...this.metadata, ...metadata };
|
|
89
176
|
}
|
|
90
177
|
await this.save();
|
|
178
|
+
// If this payment is for an installment, mark it as paid
|
|
179
|
+
if (this.installmentId) {
|
|
180
|
+
const Installment = this.constructor.sequelize.models
|
|
181
|
+
.Installment;
|
|
182
|
+
const installment = await Installment.findByPk(this.installmentId);
|
|
183
|
+
if (installment && installment.status !== "PAID") {
|
|
184
|
+
await installment.markAsPaid(this.id);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
91
187
|
}
|
|
92
188
|
async markAsFailed(reason, metadata) {
|
|
93
189
|
this.status = "FAILED";
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
};
|
|
99
|
-
this.metadata = { ...this.metadata, ...failureMetadata };
|
|
190
|
+
this.failureReason = reason;
|
|
191
|
+
if (metadata) {
|
|
192
|
+
this.metadata = { ...this.metadata, ...metadata };
|
|
193
|
+
}
|
|
100
194
|
await this.save();
|
|
101
195
|
}
|
|
102
|
-
async
|
|
103
|
-
this.
|
|
104
|
-
if (
|
|
105
|
-
this.
|
|
196
|
+
async markAsRefunded(metadata) {
|
|
197
|
+
this.status = "REFUNDED";
|
|
198
|
+
if (metadata) {
|
|
199
|
+
this.metadata = { ...this.metadata, ...metadata };
|
|
106
200
|
}
|
|
107
201
|
await this.save();
|
|
108
202
|
}
|
|
203
|
+
async markAsSettled(settledAt = new Date()) {
|
|
204
|
+
this.settledAt = settledAt;
|
|
205
|
+
await this.save();
|
|
206
|
+
}
|
|
109
207
|
isSuccessful() {
|
|
110
208
|
return this.status === "SUCCESSFUL";
|
|
111
209
|
}
|
|
@@ -115,26 +213,42 @@ class Payment extends vr_migrations_1.Model {
|
|
|
115
213
|
isFailed() {
|
|
116
214
|
return this.status === "FAILED";
|
|
117
215
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
216
|
+
isRefunded() {
|
|
217
|
+
return this.status === "REFUNDED";
|
|
218
|
+
}
|
|
219
|
+
getFormattedAmount() {
|
|
220
|
+
return `${this.currency} ${this.amount.toLocaleString()}`;
|
|
221
|
+
}
|
|
222
|
+
getTargetType() {
|
|
223
|
+
return this.devicePaymentPlanId ? "payment_plan" : "installment";
|
|
127
224
|
}
|
|
225
|
+
getTargetId() {
|
|
226
|
+
return (this.devicePaymentPlanId || this.installmentId);
|
|
227
|
+
}
|
|
228
|
+
// Static methods
|
|
128
229
|
static async getUserPayments(userId, limit = 50) {
|
|
129
230
|
return await this.findAll({
|
|
130
231
|
where: { userId },
|
|
131
232
|
order: [["createdAt", "DESC"]],
|
|
132
233
|
limit,
|
|
234
|
+
include: ["devicePaymentPlan", "installment"],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
static async getPlanPayments(planId) {
|
|
238
|
+
return await this.findAll({
|
|
239
|
+
where: { devicePaymentPlanId: planId },
|
|
240
|
+
order: [["createdAt", "ASC"]],
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
static async getInstallmentPayments(installmentId) {
|
|
244
|
+
return await this.findAll({
|
|
245
|
+
where: { installmentId },
|
|
246
|
+
order: [["createdAt", "ASC"]],
|
|
133
247
|
});
|
|
134
248
|
}
|
|
135
|
-
static async
|
|
249
|
+
static async getPendingPayments() {
|
|
136
250
|
return await this.findAll({
|
|
137
|
-
where: {
|
|
251
|
+
where: { status: "PENDING" },
|
|
138
252
|
order: [["createdAt", "ASC"]],
|
|
139
253
|
});
|
|
140
254
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, ModelStatic, NonAttribute } from "vr-migrations";
|
|
2
|
+
import type { Payment } from "./payment.models";
|
|
3
|
+
export type EventDirection = "OUTGOING_REQUEST" | "INCOMING_WEBHOOK";
|
|
4
|
+
export type EventStatus = "PENDING" | "SUCCESS" | "FAILED" | "RETRIED";
|
|
5
|
+
export declare const EVENT_DIRECTION: readonly ["OUTGOING_REQUEST", "INCOMING_WEBHOOK"];
|
|
6
|
+
export declare const EVENT_STATUS: readonly ["PENDING", "SUCCESS", "FAILED", "RETRIED"];
|
|
7
|
+
export interface PaymentEventLogAttributes {
|
|
8
|
+
id: string;
|
|
9
|
+
paymentId: string | null;
|
|
10
|
+
provider: string;
|
|
11
|
+
direction: EventDirection;
|
|
12
|
+
action: string;
|
|
13
|
+
externalId: string | null;
|
|
14
|
+
requestPayload: Record<string, any> | null;
|
|
15
|
+
responsePayload: Record<string, any> | null;
|
|
16
|
+
statusCode: number | null;
|
|
17
|
+
status: EventStatus;
|
|
18
|
+
error: string | null;
|
|
19
|
+
durationMs: number | null;
|
|
20
|
+
retryCount: number;
|
|
21
|
+
nextRetryAt: Date | null;
|
|
22
|
+
processedAt: Date | null;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
updatedAt: Date;
|
|
25
|
+
}
|
|
26
|
+
export interface PaymentEventLogCreationAttributes extends Omit<PaymentEventLogAttributes, "id" | "createdAt" | "updatedAt" | "status" | "error" | "retryCount" | "nextRetryAt" | "processedAt" | "durationMs"> {
|
|
27
|
+
id?: string;
|
|
28
|
+
status?: EventStatus;
|
|
29
|
+
error?: string | null;
|
|
30
|
+
retryCount?: number;
|
|
31
|
+
nextRetryAt?: Date | null;
|
|
32
|
+
processedAt?: Date | null;
|
|
33
|
+
durationMs?: number | null;
|
|
34
|
+
payment?: Payment;
|
|
35
|
+
}
|
|
36
|
+
export declare class PaymentEventLog extends Model<InferAttributes<PaymentEventLog>, InferCreationAttributes<PaymentEventLog>> implements PaymentEventLogAttributes {
|
|
37
|
+
id: CreationOptional<string>;
|
|
38
|
+
paymentId: CreationOptional<string | null>;
|
|
39
|
+
provider: string;
|
|
40
|
+
direction: EventDirection;
|
|
41
|
+
action: string;
|
|
42
|
+
externalId: CreationOptional<string | null>;
|
|
43
|
+
requestPayload: CreationOptional<Record<string, any> | null>;
|
|
44
|
+
responsePayload: CreationOptional<Record<string, any> | null>;
|
|
45
|
+
statusCode: CreationOptional<number | null>;
|
|
46
|
+
status: EventStatus;
|
|
47
|
+
error: CreationOptional<string | null>;
|
|
48
|
+
durationMs: CreationOptional<number | null>;
|
|
49
|
+
retryCount: CreationOptional<number>;
|
|
50
|
+
nextRetryAt: CreationOptional<Date | null>;
|
|
51
|
+
processedAt: CreationOptional<Date | null>;
|
|
52
|
+
payment?: NonAttribute<Payment>;
|
|
53
|
+
readonly createdAt: CreationOptional<Date>;
|
|
54
|
+
readonly updatedAt: CreationOptional<Date>;
|
|
55
|
+
static initialize(sequelize: any): void;
|
|
56
|
+
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
57
|
+
markAsSuccess(durationMs?: number): Promise<void>;
|
|
58
|
+
markAsFailed(error: string, durationMs?: number): Promise<void>;
|
|
59
|
+
scheduleRetry(retryAfterSeconds: number): Promise<void>;
|
|
60
|
+
isPending(): boolean;
|
|
61
|
+
isSuccess(): boolean;
|
|
62
|
+
isFailed(): boolean;
|
|
63
|
+
isRetryable(): boolean;
|
|
64
|
+
static getUnprocessedWebhooks(limit?: number): Promise<PaymentEventLog[]>;
|
|
65
|
+
static getPendingRetries(): Promise<PaymentEventLog[]>;
|
|
66
|
+
static getPaymentEvents(paymentId: string): Promise<PaymentEventLog[]>;
|
|
67
|
+
static getProviderEvents(provider: string, externalId: string): Promise<PaymentEventLog[]>;
|
|
68
|
+
}
|
|
69
|
+
export type PaymentEventLogModel = typeof PaymentEventLog;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PaymentEventLog = exports.EVENT_STATUS = exports.EVENT_DIRECTION = void 0;
|
|
4
|
+
// src/models/paymentEventLog.models.ts
|
|
5
|
+
const vr_migrations_1 = require("vr-migrations");
|
|
6
|
+
exports.EVENT_DIRECTION = [
|
|
7
|
+
"OUTGOING_REQUEST",
|
|
8
|
+
"INCOMING_WEBHOOK",
|
|
9
|
+
];
|
|
10
|
+
exports.EVENT_STATUS = [
|
|
11
|
+
"PENDING",
|
|
12
|
+
"SUCCESS",
|
|
13
|
+
"FAILED",
|
|
14
|
+
"RETRIED",
|
|
15
|
+
];
|
|
16
|
+
class PaymentEventLog extends vr_migrations_1.Model {
|
|
17
|
+
// Static initialization method
|
|
18
|
+
static initialize(sequelize) {
|
|
19
|
+
this.init({
|
|
20
|
+
id: {
|
|
21
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
22
|
+
defaultValue: vr_migrations_1.DataTypes.UUIDV4,
|
|
23
|
+
primaryKey: true,
|
|
24
|
+
},
|
|
25
|
+
paymentId: {
|
|
26
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
27
|
+
allowNull: true,
|
|
28
|
+
references: {
|
|
29
|
+
model: "payments",
|
|
30
|
+
key: "id",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
provider: {
|
|
34
|
+
type: vr_migrations_1.DataTypes.STRING(50),
|
|
35
|
+
allowNull: false,
|
|
36
|
+
},
|
|
37
|
+
direction: {
|
|
38
|
+
type: vr_migrations_1.DataTypes.ENUM("OUTGOING_REQUEST", "INCOMING_WEBHOOK"),
|
|
39
|
+
allowNull: false,
|
|
40
|
+
},
|
|
41
|
+
action: {
|
|
42
|
+
type: vr_migrations_1.DataTypes.STRING(50),
|
|
43
|
+
allowNull: false,
|
|
44
|
+
},
|
|
45
|
+
externalId: {
|
|
46
|
+
type: vr_migrations_1.DataTypes.STRING(255),
|
|
47
|
+
allowNull: true,
|
|
48
|
+
},
|
|
49
|
+
requestPayload: {
|
|
50
|
+
type: vr_migrations_1.DataTypes.JSONB,
|
|
51
|
+
allowNull: true,
|
|
52
|
+
},
|
|
53
|
+
responsePayload: {
|
|
54
|
+
type: vr_migrations_1.DataTypes.JSONB,
|
|
55
|
+
allowNull: true,
|
|
56
|
+
},
|
|
57
|
+
statusCode: {
|
|
58
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
59
|
+
allowNull: true,
|
|
60
|
+
},
|
|
61
|
+
status: {
|
|
62
|
+
type: vr_migrations_1.DataTypes.ENUM("PENDING", "SUCCESS", "FAILED", "RETRIED"),
|
|
63
|
+
allowNull: false,
|
|
64
|
+
defaultValue: "PENDING",
|
|
65
|
+
},
|
|
66
|
+
error: {
|
|
67
|
+
type: vr_migrations_1.DataTypes.TEXT,
|
|
68
|
+
allowNull: true,
|
|
69
|
+
},
|
|
70
|
+
durationMs: {
|
|
71
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
72
|
+
allowNull: true,
|
|
73
|
+
},
|
|
74
|
+
retryCount: {
|
|
75
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
76
|
+
allowNull: false,
|
|
77
|
+
defaultValue: 0,
|
|
78
|
+
},
|
|
79
|
+
nextRetryAt: {
|
|
80
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
81
|
+
allowNull: true,
|
|
82
|
+
},
|
|
83
|
+
processedAt: {
|
|
84
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
85
|
+
allowNull: true,
|
|
86
|
+
},
|
|
87
|
+
createdAt: {
|
|
88
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
89
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
90
|
+
},
|
|
91
|
+
updatedAt: {
|
|
92
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
93
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
94
|
+
},
|
|
95
|
+
}, {
|
|
96
|
+
sequelize,
|
|
97
|
+
modelName: "PaymentEventLog",
|
|
98
|
+
tableName: "payment_event_logs",
|
|
99
|
+
timestamps: true,
|
|
100
|
+
freezeTableName: true,
|
|
101
|
+
indexes: [
|
|
102
|
+
{
|
|
103
|
+
fields: ["paymentId"],
|
|
104
|
+
name: "payment_event_payment_idx",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
fields: ["provider", "externalId"],
|
|
108
|
+
name: "payment_event_provider_external_idx",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
fields: ["status", "nextRetryAt"],
|
|
112
|
+
name: "payment_event_retry_idx",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
fields: ["createdAt"],
|
|
116
|
+
name: "payment_event_created_at_idx",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
fields: ["direction", "action"],
|
|
120
|
+
name: "payment_event_direction_action_idx",
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Static association method
|
|
126
|
+
static associate(models) {
|
|
127
|
+
this.belongsTo(models.Payment, {
|
|
128
|
+
foreignKey: "paymentId",
|
|
129
|
+
as: "payment",
|
|
130
|
+
onDelete: "SET NULL",
|
|
131
|
+
onUpdate: "CASCADE",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Custom instance methods
|
|
135
|
+
async markAsSuccess(durationMs) {
|
|
136
|
+
this.status = "SUCCESS";
|
|
137
|
+
this.processedAt = new Date();
|
|
138
|
+
if (durationMs !== undefined) {
|
|
139
|
+
this.durationMs = durationMs;
|
|
140
|
+
}
|
|
141
|
+
await this.save();
|
|
142
|
+
}
|
|
143
|
+
async markAsFailed(error, durationMs) {
|
|
144
|
+
this.status = "FAILED";
|
|
145
|
+
this.error = error;
|
|
146
|
+
this.processedAt = new Date();
|
|
147
|
+
if (durationMs !== undefined) {
|
|
148
|
+
this.durationMs = durationMs;
|
|
149
|
+
}
|
|
150
|
+
await this.save();
|
|
151
|
+
}
|
|
152
|
+
async scheduleRetry(retryAfterSeconds) {
|
|
153
|
+
this.status = "RETRIED";
|
|
154
|
+
this.retryCount += 1;
|
|
155
|
+
this.nextRetryAt = new Date(Date.now() + retryAfterSeconds * 1000);
|
|
156
|
+
await this.save();
|
|
157
|
+
}
|
|
158
|
+
isPending() {
|
|
159
|
+
return this.status === "PENDING";
|
|
160
|
+
}
|
|
161
|
+
isSuccess() {
|
|
162
|
+
return this.status === "SUCCESS";
|
|
163
|
+
}
|
|
164
|
+
isFailed() {
|
|
165
|
+
return this.status === "FAILED";
|
|
166
|
+
}
|
|
167
|
+
isRetryable() {
|
|
168
|
+
return this.status === "FAILED" && this.retryCount < 5;
|
|
169
|
+
}
|
|
170
|
+
// Static methods
|
|
171
|
+
static async getUnprocessedWebhooks(limit = 100) {
|
|
172
|
+
return await this.findAll({
|
|
173
|
+
where: {
|
|
174
|
+
direction: "INCOMING_WEBHOOK",
|
|
175
|
+
status: "PENDING",
|
|
176
|
+
processedAt: null,
|
|
177
|
+
},
|
|
178
|
+
limit,
|
|
179
|
+
order: [["createdAt", "ASC"]],
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
static async getPendingRetries() {
|
|
183
|
+
return await this.findAll({
|
|
184
|
+
where: {
|
|
185
|
+
status: "RETRIED",
|
|
186
|
+
nextRetryAt: { [vr_migrations_1.Op.lte]: new Date() },
|
|
187
|
+
},
|
|
188
|
+
order: [["nextRetryAt", "ASC"]],
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
static async getPaymentEvents(paymentId) {
|
|
192
|
+
return await this.findAll({
|
|
193
|
+
where: { paymentId },
|
|
194
|
+
order: [["createdAt", "ASC"]],
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
static async getProviderEvents(provider, externalId) {
|
|
198
|
+
return await this.findAll({
|
|
199
|
+
where: { provider, externalId },
|
|
200
|
+
order: [["createdAt", "ASC"]],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
exports.PaymentEventLog = PaymentEventLog;
|
package/dist/models/types.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export type { UserModel } from "./user.models";
|
|
2
|
-
export type { TransactionModel } from "./transaction.models";
|
|
3
2
|
export type { SecurityClearanceModel } from "./securityClearance.models";
|
|
4
3
|
export type { ProductModel } from "./product.models";
|
|
5
4
|
export type { PricingModel } from "./pricing.models";
|
|
@@ -13,3 +12,4 @@ export type { BanModel } from "./ban.models";
|
|
|
13
12
|
export type { SuspensionModel } from "./suspension.models";
|
|
14
13
|
export type { AppSpecsModel } from "./appSpecs.models";
|
|
15
14
|
export type { PhoneContactModel } from "./phoneContact.models";
|
|
15
|
+
export type { PaymentEventLogModel } from "./paymentEventLog.models";
|
|
@@ -11,6 +11,8 @@ export interface DeletionStatus {
|
|
|
11
11
|
deletedBy?: string | null;
|
|
12
12
|
reason?: string | null;
|
|
13
13
|
}
|
|
14
|
+
export declare const GENDER_OPTIONS: readonly ["male", "female"];
|
|
15
|
+
export type Gender = (typeof GENDER_OPTIONS)[number];
|
|
14
16
|
export interface UserAttributes {
|
|
15
17
|
id: string;
|
|
16
18
|
firstName: string;
|
|
@@ -20,7 +22,10 @@ export interface UserAttributes {
|
|
|
20
22
|
password?: string | null;
|
|
21
23
|
securityClearanceId: string;
|
|
22
24
|
plateNumber?: string | null;
|
|
23
|
-
|
|
25
|
+
gender: Gender;
|
|
26
|
+
birthDate?: number | null;
|
|
27
|
+
birthMonth?: number | null;
|
|
28
|
+
birthYear?: number | null;
|
|
24
29
|
primaryPhoneId: string | null;
|
|
25
30
|
isActive: boolean;
|
|
26
31
|
forgotPassword: boolean;
|
|
@@ -54,7 +59,10 @@ export declare class User extends Model<InferAttributes<User>, InferCreationAttr
|
|
|
54
59
|
password: CreationOptional<string | null>;
|
|
55
60
|
securityClearanceId: string;
|
|
56
61
|
plateNumber: CreationOptional<string | null>;
|
|
57
|
-
|
|
62
|
+
gender: Gender;
|
|
63
|
+
birthDate: CreationOptional<number | null>;
|
|
64
|
+
birthMonth: CreationOptional<number | null>;
|
|
65
|
+
birthYear: CreationOptional<number | null>;
|
|
58
66
|
primaryPhoneId: CreationOptional<string | null>;
|
|
59
67
|
isActive: CreationOptional<boolean>;
|
|
60
68
|
forgotPassword: CreationOptional<boolean>;
|
|
@@ -78,5 +86,7 @@ export declare class User extends Model<InferAttributes<User>, InferCreationAttr
|
|
|
78
86
|
deactivate(): Promise<void>;
|
|
79
87
|
activate(): Promise<void>;
|
|
80
88
|
hasVerifiedPhone(): Promise<boolean>;
|
|
89
|
+
getBirthDate(): Date | null;
|
|
90
|
+
getAge(): number | null;
|
|
81
91
|
}
|
|
82
92
|
export type UserModel = typeof User;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.User = void 0;
|
|
3
|
+
exports.User = exports.GENDER_OPTIONS = void 0;
|
|
4
4
|
// src/models/user.models.ts
|
|
5
5
|
const vr_migrations_1 = require("vr-migrations");
|
|
6
|
+
// ==================== GENDER OPTIONS ====================
|
|
7
|
+
exports.GENDER_OPTIONS = ["male", "female"];
|
|
6
8
|
class User extends vr_migrations_1.Model {
|
|
7
9
|
// Static initialization method
|
|
8
10
|
static initialize(sequelize) {
|
|
@@ -20,11 +22,6 @@ class User extends vr_migrations_1.Model {
|
|
|
20
22
|
type: vr_migrations_1.DataTypes.STRING(100),
|
|
21
23
|
allowNull: false,
|
|
22
24
|
},
|
|
23
|
-
nationalId: {
|
|
24
|
-
type: vr_migrations_1.DataTypes.STRING(100),
|
|
25
|
-
allowNull: true,
|
|
26
|
-
unique: true,
|
|
27
|
-
},
|
|
28
25
|
jacketId: {
|
|
29
26
|
type: vr_migrations_1.DataTypes.STRING(100),
|
|
30
27
|
allowNull: true,
|
|
@@ -47,6 +44,35 @@ class User extends vr_migrations_1.Model {
|
|
|
47
44
|
type: vr_migrations_1.DataTypes.STRING(100),
|
|
48
45
|
allowNull: true,
|
|
49
46
|
},
|
|
47
|
+
gender: {
|
|
48
|
+
type: vr_migrations_1.DataTypes.ENUM("male", "female"),
|
|
49
|
+
allowNull: false,
|
|
50
|
+
defaultValue: "male",
|
|
51
|
+
},
|
|
52
|
+
birthDate: {
|
|
53
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
54
|
+
allowNull: true,
|
|
55
|
+
validate: {
|
|
56
|
+
min: 1,
|
|
57
|
+
max: 31,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
birthMonth: {
|
|
61
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
62
|
+
allowNull: true,
|
|
63
|
+
validate: {
|
|
64
|
+
min: 1,
|
|
65
|
+
max: 12,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
birthYear: {
|
|
69
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
70
|
+
allowNull: true,
|
|
71
|
+
validate: {
|
|
72
|
+
min: 1900,
|
|
73
|
+
max: new Date().getFullYear(),
|
|
74
|
+
},
|
|
75
|
+
},
|
|
50
76
|
primaryPhoneId: {
|
|
51
77
|
type: vr_migrations_1.DataTypes.UUID,
|
|
52
78
|
allowNull: true,
|
|
@@ -122,25 +148,25 @@ class User extends vr_migrations_1.Model {
|
|
|
122
148
|
this.hasMany(models.DevicePaymentPlan, {
|
|
123
149
|
foreignKey: "userId",
|
|
124
150
|
as: "paymentPlans",
|
|
125
|
-
onDelete: "CASCADE",
|
|
151
|
+
onDelete: "CASCADE",
|
|
126
152
|
onUpdate: "CASCADE",
|
|
127
153
|
});
|
|
128
154
|
this.hasMany(models.Payment, {
|
|
129
155
|
foreignKey: "userId",
|
|
130
156
|
as: "payments",
|
|
131
|
-
onDelete: "SET NULL",
|
|
157
|
+
onDelete: "SET NULL",
|
|
132
158
|
onUpdate: "CASCADE",
|
|
133
159
|
});
|
|
134
160
|
this.hasMany(models.Suspension, {
|
|
135
161
|
foreignKey: "userId",
|
|
136
162
|
as: "suspensions",
|
|
137
|
-
onDelete: "SET NULL",
|
|
163
|
+
onDelete: "SET NULL",
|
|
138
164
|
onUpdate: "CASCADE",
|
|
139
165
|
});
|
|
140
166
|
this.hasMany(models.Ban, {
|
|
141
167
|
foreignKey: "userId",
|
|
142
168
|
as: "bans",
|
|
143
|
-
onDelete: "SET NULL",
|
|
169
|
+
onDelete: "SET NULL",
|
|
144
170
|
onUpdate: "CASCADE",
|
|
145
171
|
});
|
|
146
172
|
}
|
|
@@ -182,5 +208,26 @@ class User extends vr_migrations_1.Model {
|
|
|
182
208
|
});
|
|
183
209
|
return !!verifiedPhone;
|
|
184
210
|
}
|
|
211
|
+
// Helper to get full birth date as Date object
|
|
212
|
+
getBirthDate() {
|
|
213
|
+
if (this.birthYear && this.birthMonth && this.birthDate) {
|
|
214
|
+
return new Date(this.birthYear, this.birthMonth - 1, this.birthDate);
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Helper to get age
|
|
219
|
+
getAge() {
|
|
220
|
+
const birthDate = this.getBirthDate();
|
|
221
|
+
if (!birthDate)
|
|
222
|
+
return null;
|
|
223
|
+
const today = new Date();
|
|
224
|
+
let age = today.getFullYear() - birthDate.getFullYear();
|
|
225
|
+
const monthDiff = today.getMonth() - birthDate.getMonth();
|
|
226
|
+
if (monthDiff < 0 ||
|
|
227
|
+
(monthDiff === 0 && today.getDate() < birthDate.getDate())) {
|
|
228
|
+
age--;
|
|
229
|
+
}
|
|
230
|
+
return age;
|
|
231
|
+
}
|
|
185
232
|
}
|
|
186
233
|
exports.User = User;
|
package/package.json
CHANGED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
|
-
import type { Payment } from "./payment.models";
|
|
3
|
-
import type { User } from "./user.models";
|
|
4
|
-
export type TransactionStatus = "succeeded" | "failed";
|
|
5
|
-
export interface TransactionAttributes {
|
|
6
|
-
id: string;
|
|
7
|
-
userId: string;
|
|
8
|
-
paymentId: string;
|
|
9
|
-
amount: number;
|
|
10
|
-
status: TransactionStatus;
|
|
11
|
-
providerReference: string;
|
|
12
|
-
metadata: Record<string, any>;
|
|
13
|
-
createdAt: Date;
|
|
14
|
-
updatedAt: Date;
|
|
15
|
-
payment?: Payment;
|
|
16
|
-
user?: User;
|
|
17
|
-
}
|
|
18
|
-
export interface TransactionCreationAttributes extends Omit<TransactionAttributes, "id" | "createdAt" | "updatedAt" | "metadata"> {
|
|
19
|
-
id?: string;
|
|
20
|
-
metadata?: Record<string, any>;
|
|
21
|
-
}
|
|
22
|
-
export declare class Transaction extends Model<InferAttributes<Transaction>, InferCreationAttributes<Transaction>> implements TransactionAttributes {
|
|
23
|
-
id: CreationOptional<string>;
|
|
24
|
-
userId: string;
|
|
25
|
-
paymentId: string;
|
|
26
|
-
amount: number;
|
|
27
|
-
status: TransactionStatus;
|
|
28
|
-
providerReference: string;
|
|
29
|
-
metadata: CreationOptional<Record<string, any>>;
|
|
30
|
-
readonly createdAt: CreationOptional<Date>;
|
|
31
|
-
readonly updatedAt: CreationOptional<Date>;
|
|
32
|
-
payment?: NonAttribute<Payment>;
|
|
33
|
-
user?: NonAttribute<User>;
|
|
34
|
-
static initialize(sequelize: any): void;
|
|
35
|
-
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
36
|
-
markAsSucceeded(metadata?: Record<string, any>): Promise<void>;
|
|
37
|
-
markAsFailed(reason: string, metadata?: Record<string, any>): Promise<void>;
|
|
38
|
-
isSuccessful(): boolean;
|
|
39
|
-
isFailed(): boolean;
|
|
40
|
-
getFormattedAmount(): string;
|
|
41
|
-
static getUserTransactions(userId: string, limit?: number): Promise<Transaction[]>;
|
|
42
|
-
static getPaymentTransactions(paymentId: string): Promise<Transaction[]>;
|
|
43
|
-
}
|
|
44
|
-
export type TransactionModel = typeof Transaction;
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Transaction = void 0;
|
|
4
|
-
const vr_migrations_1 = require("vr-migrations");
|
|
5
|
-
class Transaction extends vr_migrations_1.Model {
|
|
6
|
-
// Static initialization method
|
|
7
|
-
static initialize(sequelize) {
|
|
8
|
-
this.init({
|
|
9
|
-
id: {
|
|
10
|
-
type: vr_migrations_1.DataTypes.UUID,
|
|
11
|
-
defaultValue: vr_migrations_1.DataTypes.UUIDV4,
|
|
12
|
-
primaryKey: true,
|
|
13
|
-
},
|
|
14
|
-
userId: { type: vr_migrations_1.DataTypes.UUID, allowNull: false },
|
|
15
|
-
paymentId: { type: vr_migrations_1.DataTypes.UUID, allowNull: false },
|
|
16
|
-
amount: {
|
|
17
|
-
type: vr_migrations_1.DataTypes.FLOAT,
|
|
18
|
-
allowNull: false,
|
|
19
|
-
validate: { min: 1 },
|
|
20
|
-
},
|
|
21
|
-
status: {
|
|
22
|
-
type: vr_migrations_1.DataTypes.ENUM("succeeded", "failed"),
|
|
23
|
-
allowNull: false,
|
|
24
|
-
},
|
|
25
|
-
providerReference: { type: vr_migrations_1.DataTypes.STRING, allowNull: false },
|
|
26
|
-
metadata: {
|
|
27
|
-
type: vr_migrations_1.DataTypes.JSONB,
|
|
28
|
-
allowNull: false,
|
|
29
|
-
defaultValue: {},
|
|
30
|
-
},
|
|
31
|
-
createdAt: { type: vr_migrations_1.DataTypes.DATE, defaultValue: vr_migrations_1.DataTypes.NOW },
|
|
32
|
-
updatedAt: { type: vr_migrations_1.DataTypes.DATE, defaultValue: vr_migrations_1.DataTypes.NOW },
|
|
33
|
-
}, {
|
|
34
|
-
sequelize,
|
|
35
|
-
tableName: "transactions",
|
|
36
|
-
modelName: "Transaction",
|
|
37
|
-
timestamps: true,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
// Static association method
|
|
41
|
-
static associate(models) {
|
|
42
|
-
this.belongsTo(models.Payment, {
|
|
43
|
-
foreignKey: "paymentId",
|
|
44
|
-
as: "payment",
|
|
45
|
-
onDelete: "SET NULL", // ← This is SET NULL, not CASCADE
|
|
46
|
-
onUpdate: "CASCADE",
|
|
47
|
-
});
|
|
48
|
-
this.belongsTo(models.User, {
|
|
49
|
-
foreignKey: "userId",
|
|
50
|
-
as: "user",
|
|
51
|
-
onDelete: "RESTRICT",
|
|
52
|
-
onUpdate: "CASCADE",
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
// Custom instance methods
|
|
56
|
-
async markAsSucceeded(metadata) {
|
|
57
|
-
this.status = "succeeded";
|
|
58
|
-
if (metadata) {
|
|
59
|
-
this.metadata = { ...this.metadata, ...metadata };
|
|
60
|
-
}
|
|
61
|
-
await this.save();
|
|
62
|
-
}
|
|
63
|
-
async markAsFailed(reason, metadata) {
|
|
64
|
-
this.status = "failed";
|
|
65
|
-
const failureMetadata = {
|
|
66
|
-
failureReason: reason,
|
|
67
|
-
failedAt: new Date().toISOString(),
|
|
68
|
-
...metadata,
|
|
69
|
-
};
|
|
70
|
-
this.metadata = { ...this.metadata, ...failureMetadata };
|
|
71
|
-
await this.save();
|
|
72
|
-
}
|
|
73
|
-
isSuccessful() {
|
|
74
|
-
return this.status === "succeeded";
|
|
75
|
-
}
|
|
76
|
-
isFailed() {
|
|
77
|
-
return this.status === "failed";
|
|
78
|
-
}
|
|
79
|
-
getFormattedAmount() {
|
|
80
|
-
return `RWF ${this.amount.toLocaleString()}`;
|
|
81
|
-
}
|
|
82
|
-
static async getUserTransactions(userId, limit = 50) {
|
|
83
|
-
return await this.findAll({
|
|
84
|
-
where: { userId },
|
|
85
|
-
order: [["createdAt", "DESC"]],
|
|
86
|
-
limit,
|
|
87
|
-
include: ["payment"],
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
static async getPaymentTransactions(paymentId) {
|
|
91
|
-
return await this.findAll({
|
|
92
|
-
where: { paymentId },
|
|
93
|
-
order: [["createdAt", "DESC"]],
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
exports.Transaction = Transaction;
|