vr-models 1.0.56 → 1.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
2
+ import type { User } from "./user.models";
3
+ export declare const CONTACT_TYPES: readonly ["EMAIL", "PHONE", "WHATSAPP"];
4
+ export type ContactType = (typeof CONTACT_TYPES)[number];
5
+ export interface VerificationMetadata {
6
+ lastOtpSentAt?: Date;
7
+ otpAttempts?: number;
8
+ lastFailedAttemptAt?: Date;
9
+ isLocked?: boolean;
10
+ lockedUntil: Date | null;
11
+ carrier?: string;
12
+ countryCode?: string;
13
+ deactivatedAt?: Date | null;
14
+ deactivationReason?: string;
15
+ verificationToken?: string | null;
16
+ tokenExpiresAt?: Date | null;
17
+ }
18
+ export interface ContactAttributes {
19
+ id: string;
20
+ contactValue: string;
21
+ type: ContactType;
22
+ userId: string | null;
23
+ isVerified: boolean;
24
+ verifiedAt: Date | null;
25
+ isPrimary: boolean;
26
+ isActive: boolean;
27
+ otp: string | null;
28
+ otpExpiresAt: Date | null;
29
+ metadata: VerificationMetadata;
30
+ createdAt: Date;
31
+ updatedAt: Date;
32
+ user?: NonAttribute<User>;
33
+ }
34
+ export interface ContactCreationAttributes extends Omit<ContactAttributes, "id" | "createdAt" | "updatedAt" | "metadata"> {
35
+ id?: string;
36
+ metadata?: Partial<VerificationMetadata>;
37
+ }
38
+ export declare class Contact extends Model<InferAttributes<Contact>, InferCreationAttributes<Contact>> implements ContactAttributes {
39
+ id: CreationOptional<string>;
40
+ contactValue: string;
41
+ type: ContactType;
42
+ userId: CreationOptional<string | null>;
43
+ isVerified: CreationOptional<boolean>;
44
+ verifiedAt: CreationOptional<Date | null>;
45
+ isPrimary: CreationOptional<boolean>;
46
+ isActive: CreationOptional<boolean>;
47
+ otp: CreationOptional<string | null>;
48
+ otpExpiresAt: CreationOptional<Date | null>;
49
+ metadata: CreationOptional<VerificationMetadata>;
50
+ readonly createdAt: CreationOptional<Date>;
51
+ readonly updatedAt: CreationOptional<Date>;
52
+ user?: NonAttribute<User>;
53
+ static initialize(sequelize: any): void;
54
+ static associate(models: Record<string, ModelStatic<Model>>): void;
55
+ deactivate(reason?: string): Promise<void>;
56
+ reactivate(): Promise<void>;
57
+ markAsVerified(): Promise<void>;
58
+ isLocked(): boolean;
59
+ getRemainingLockTime(): number | null;
60
+ isExpired(): boolean;
61
+ getRemainingOTPTime(): number | null;
62
+ static findByContactValue(contactValue: string, type: ContactType): Promise<Contact | null>;
63
+ static findVerifiedByContactValue(contactValue: string, type: ContactType): Promise<Contact | null>;
64
+ static getUserContacts(userId: string): Promise<Contact[]>;
65
+ static getUserPrimaryContact(userId: string): Promise<Contact | null>;
66
+ static isContactAvailable(contactValue: string, type: ContactType): Promise<boolean>;
67
+ static cleanupExpiredOTPs(): Promise<void>;
68
+ getDisplayValue(): string;
69
+ getVerificationMethod(): string;
70
+ }
71
+ export type ContactModel = typeof Contact;
@@ -1,8 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PhoneContact = void 0;
3
+ exports.Contact = exports.CONTACT_TYPES = void 0;
4
+ // src/models/contact.models.ts
4
5
  const vr_migrations_1 = require("vr-migrations");
5
- class PhoneContact extends vr_migrations_1.Model {
6
+ // ==================== CONTACT TYPES ====================
7
+ exports.CONTACT_TYPES = ["EMAIL", "PHONE", "WHATSAPP"];
8
+ class Contact extends vr_migrations_1.Model {
6
9
  // Static initialization method
7
10
  static initialize(sequelize) {
8
11
  this.init({
@@ -12,13 +15,13 @@ class PhoneContact extends vr_migrations_1.Model {
12
15
  primaryKey: true,
13
16
  allowNull: false,
14
17
  },
15
- phoneNumber: {
16
- type: vr_migrations_1.DataTypes.STRING(20),
18
+ contactValue: {
19
+ type: vr_migrations_1.DataTypes.STRING(255),
20
+ allowNull: false,
21
+ },
22
+ type: {
23
+ type: vr_migrations_1.DataTypes.ENUM("EMAIL", "PHONE", "WHATSAPP"),
17
24
  allowNull: false,
18
- unique: true,
19
- validate: {
20
- is: /^\+?[0-9]{10,15}$/,
21
- },
22
25
  },
23
26
  userId: {
24
27
  type: vr_migrations_1.DataTypes.UUID,
@@ -79,27 +82,47 @@ class PhoneContact extends vr_migrations_1.Model {
79
82
  },
80
83
  }, {
81
84
  sequelize,
82
- modelName: "PhoneContact",
83
- tableName: "phone_contacts",
85
+ modelName: "Contact",
86
+ tableName: "contacts",
84
87
  timestamps: true,
85
88
  freezeTableName: true,
89
+ validate: {
90
+ validContactValue() {
91
+ if (this.type === "EMAIL") {
92
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
93
+ if (!emailRegex.test(this.contactValue)) {
94
+ throw new Error("Invalid email address format");
95
+ }
96
+ }
97
+ else if (this.type === "PHONE" || this.type === "WHATSAPP") {
98
+ const phoneRegex = /^\+?[0-9]{10,15}$/;
99
+ if (!phoneRegex.test(this.contactValue)) {
100
+ throw new Error("Invalid phone number format");
101
+ }
102
+ }
103
+ },
104
+ },
86
105
  indexes: [
87
106
  {
88
107
  unique: true,
89
- fields: ["phoneNumber"],
90
- name: "phone_contacts_phone_number_unique",
108
+ fields: ["contactValue", "type"],
109
+ name: "contacts_unique_value_type_idx",
91
110
  },
92
111
  {
93
112
  fields: ["userId"],
94
- name: "phone_contacts_user_id_idx",
113
+ name: "contacts_user_id_idx",
114
+ },
115
+ {
116
+ fields: ["type"],
117
+ name: "contacts_type_idx",
95
118
  },
96
119
  {
97
120
  fields: ["isVerified", "isPrimary"],
98
- name: "phone_contacts_verified_primary_idx",
121
+ name: "contacts_verified_primary_idx",
99
122
  },
100
123
  {
101
124
  fields: ["otpExpiresAt"],
102
- name: "phone_contacts_otp_expiry_idx",
125
+ name: "contacts_otp_expiry_idx",
103
126
  },
104
127
  ],
105
128
  });
@@ -113,6 +136,9 @@ class PhoneContact extends vr_migrations_1.Model {
113
136
  onUpdate: "CASCADE",
114
137
  });
115
138
  }
139
+ // ===========================================================================
140
+ // INSTANCE METHODS
141
+ // ===========================================================================
116
142
  async deactivate(reason) {
117
143
  this.isActive = false;
118
144
  this.metadata = {
@@ -131,6 +157,13 @@ class PhoneContact extends vr_migrations_1.Model {
131
157
  };
132
158
  await this.save();
133
159
  }
160
+ async markAsVerified() {
161
+ this.isVerified = true;
162
+ this.verifiedAt = new Date();
163
+ this.otp = null;
164
+ this.otpExpiresAt = null;
165
+ await this.save();
166
+ }
134
167
  isLocked() {
135
168
  if (!this.metadata.isLocked)
136
169
  return false;
@@ -161,34 +194,37 @@ class PhoneContact extends vr_migrations_1.Model {
161
194
  const remaining = new Date(this.otpExpiresAt).getTime() - new Date().getTime();
162
195
  return Math.max(0, Math.ceil(remaining / 1000));
163
196
  }
164
- // Static methods
165
- static async findByPhoneNumber(phoneNumber) {
166
- return await this.findOne({
167
- where: { phoneNumber },
168
- });
169
- }
170
- static async findVerifiedByPhoneNumber(phoneNumber) {
197
+ // ===========================================================================
198
+ // STATIC METHODS
199
+ // ===========================================================================
200
+ static async findByContactValue(contactValue, type) {
171
201
  return await this.findOne({
172
- where: { phoneNumber, isVerified: true, isActive: true },
202
+ where: { contactValue, type },
173
203
  });
174
204
  }
175
- static async getUserPrimaryPhone(userId) {
205
+ static async findVerifiedByContactValue(contactValue, type) {
176
206
  return await this.findOne({
177
- where: { userId, isPrimary: true, isActive: true },
207
+ where: { contactValue, type, isVerified: true, isActive: true },
178
208
  });
179
209
  }
180
- static async getUserPhones(userId) {
210
+ static async getUserContacts(userId) {
181
211
  return await this.findAll({
182
212
  where: { userId, isActive: true },
183
213
  order: [
184
214
  ["isPrimary", "DESC"],
215
+ ["type", "ASC"],
185
216
  ["createdAt", "ASC"],
186
217
  ],
187
218
  });
188
219
  }
189
- static async isPhoneNumberAvailable(phoneNumber) {
220
+ static async getUserPrimaryContact(userId) {
221
+ return await this.findOne({
222
+ where: { userId, isPrimary: true, isActive: true },
223
+ });
224
+ }
225
+ static async isContactAvailable(contactValue, type) {
190
226
  const existing = await this.findOne({
191
- where: { phoneNumber, isVerified: true },
227
+ where: { contactValue, type, isVerified: true },
192
228
  });
193
229
  return !existing;
194
230
  }
@@ -200,5 +236,33 @@ class PhoneContact extends vr_migrations_1.Model {
200
236
  },
201
237
  });
202
238
  }
239
+ // ===========================================================================
240
+ // TYPE-SPECIFIC HELPERS
241
+ // ===========================================================================
242
+ getDisplayValue() {
243
+ if (this.type === "EMAIL") {
244
+ return this.contactValue;
245
+ }
246
+ else if (this.type === "PHONE" || this.type === "WHATSAPP") {
247
+ // Mask phone number
248
+ const phone = this.contactValue;
249
+ if (phone.length <= 4)
250
+ return phone;
251
+ return phone.slice(0, 3) + "****" + phone.slice(-3);
252
+ }
253
+ return this.contactValue;
254
+ }
255
+ getVerificationMethod() {
256
+ switch (this.type) {
257
+ case "EMAIL":
258
+ return "email";
259
+ case "PHONE":
260
+ return "sms";
261
+ case "WHATSAPP":
262
+ return "whatsapp";
263
+ default:
264
+ return "unknown";
265
+ }
266
+ }
203
267
  }
204
- exports.PhoneContact = PhoneContact;
268
+ exports.Contact = Contact;
@@ -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() {
@@ -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 "./transaction.models";
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
- export * from "./phoneContact.models";
16
- export type { UserModel, TransactionModel, SecurityClearanceModel, ProductModel, PricingModel, PaymentModel, IdempotencyRecordModel, EventLogModel, DevicePaymentPlanModel, DeviceModel, InstallmentModel, BanModel, SuspensionModel, AppSpecsModel, PhoneContactModel, } from "./types";
15
+ export * from "./contact.models";
16
+ export type { UserModel, SecurityClearanceModel, ProductModel, PricingModel, PaymentModel, PaymentEventLogModel, IdempotencyRecordModel, EventLogModel, DevicePaymentPlanModel, DeviceModel, InstallmentModel, BanModel, SuspensionModel, AppSpecsModel, ContactModel, } from "./types";
@@ -23,9 +23,9 @@ __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("./transaction.models"), exports);
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);
30
30
  __exportStar(require("./appSpecs.models"), exports);
31
- __exportStar(require("./phoneContact.models"), exports);
31
+ __exportStar(require("./contact.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 { Transaction } from "./transaction.models";
4
+ import type { Installment } from "./installment.models";
5
5
  import type { IdempotencyRecord } from "./idempotencyRecord.models";
6
- export type PaymentProvider = "mtn_momo" | "airtel_money";
7
- export type PaymentStatus = "PENDING" | "SUCCESSFUL" | "FAILED";
8
- export declare const PAYMENT_PROVIDER: readonly ["mtn_momo", "airtel_money"];
9
- export declare const PAYMENT_STATUS: readonly ["PENDING", "SUCCESSFUL", "FAILED"];
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
- transactionId: string | null;
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
- transaction?: Transaction;
31
+ installment?: Installment;
26
32
  idempotencyKey?: IdempotencyRecord;
33
+ paymentEventLogs?: PaymentEventLog[];
27
34
  }
28
- export interface PaymentCreationAttributes extends Omit<PaymentAttributes, "id" | "createdAt" | "updatedAt" | "status" | "providerReference" | "metadata" | "transactionId" | "idempotencyKeyId"> {
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
- transactionId: CreationOptional<string | null>;
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
- transaction?: NonAttribute<Transaction>;
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
- markAsSucceeded(providerReference: string, metadata?: Record<string, any>): Promise<void>;
68
+ markAsSuccessful(providerReference: string, metadata?: Record<string, any>): Promise<void>;
56
69
  markAsFailed(reason: string, metadata?: Record<string, any>): Promise<void>;
57
- process(transactionId: string, idempotencyKeyId?: string): Promise<void>;
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
- getProviderDisplayName(): string;
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 getPaymentPlanPayments(devicePaymentPlanId: string): Promise<Payment[]>;
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;