moltspay 0.5.3 → 0.5.4
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/README.md +124 -0
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +547 -1
- package/dist/index.d.ts +547 -1
- package/dist/index.js +988 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +981 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3541,6 +3541,980 @@ function verifyPaymentHeader(header, expectedRecipient, expectedAmount) {
|
|
|
3541
3541
|
|
|
3542
3542
|
// src/index.ts
|
|
3543
3543
|
init_cdp();
|
|
3544
|
+
|
|
3545
|
+
// src/deferred/DeferredPaymentManager.ts
|
|
3546
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3547
|
+
|
|
3548
|
+
// src/deferred/MemoryStore.ts
|
|
3549
|
+
import { randomUUID } from "crypto";
|
|
3550
|
+
var DEFAULT_TERMS = {
|
|
3551
|
+
netDays: 30,
|
|
3552
|
+
graceDays: 7,
|
|
3553
|
+
settlementFrequency: "on-demand"
|
|
3554
|
+
};
|
|
3555
|
+
var MemoryDeferredStore = class {
|
|
3556
|
+
accounts = /* @__PURE__ */ new Map();
|
|
3557
|
+
payments = /* @__PURE__ */ new Map();
|
|
3558
|
+
// Index for faster lookups
|
|
3559
|
+
buyerSellerIndex = /* @__PURE__ */ new Map();
|
|
3560
|
+
// "buyerId:sellerId" -> accountId
|
|
3561
|
+
makeKey(buyerId, sellerId) {
|
|
3562
|
+
return `${buyerId}:${sellerId}`;
|
|
3563
|
+
}
|
|
3564
|
+
// ============ Credit Accounts ============
|
|
3565
|
+
async createAccount(params) {
|
|
3566
|
+
const accountId = `ca_${randomUUID().slice(0, 8)}`;
|
|
3567
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3568
|
+
const account = {
|
|
3569
|
+
accountId,
|
|
3570
|
+
buyerId: params.buyerId,
|
|
3571
|
+
sellerId: params.sellerId,
|
|
3572
|
+
creditLimit: params.creditLimit,
|
|
3573
|
+
balance: 0,
|
|
3574
|
+
status: "active",
|
|
3575
|
+
chain: params.chain || "base",
|
|
3576
|
+
terms: { ...DEFAULT_TERMS, ...params.terms },
|
|
3577
|
+
createdAt: now,
|
|
3578
|
+
updatedAt: now,
|
|
3579
|
+
transactions: [],
|
|
3580
|
+
metadata: params.metadata
|
|
3581
|
+
};
|
|
3582
|
+
this.accounts.set(accountId, account);
|
|
3583
|
+
this.buyerSellerIndex.set(this.makeKey(params.buyerId, params.sellerId), accountId);
|
|
3584
|
+
return account;
|
|
3585
|
+
}
|
|
3586
|
+
async getAccount(accountId) {
|
|
3587
|
+
return this.accounts.get(accountId) || null;
|
|
3588
|
+
}
|
|
3589
|
+
async getAccountByBuyer(buyerId, sellerId) {
|
|
3590
|
+
const accountId = this.buyerSellerIndex.get(this.makeKey(buyerId, sellerId));
|
|
3591
|
+
if (!accountId) return null;
|
|
3592
|
+
return this.accounts.get(accountId) || null;
|
|
3593
|
+
}
|
|
3594
|
+
async updateAccount(accountId, updates) {
|
|
3595
|
+
const account = this.accounts.get(accountId);
|
|
3596
|
+
if (!account) {
|
|
3597
|
+
throw new Error(`Account not found: ${accountId}`);
|
|
3598
|
+
}
|
|
3599
|
+
const updated = {
|
|
3600
|
+
...account,
|
|
3601
|
+
...updates,
|
|
3602
|
+
accountId,
|
|
3603
|
+
// Prevent overwriting ID
|
|
3604
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3605
|
+
};
|
|
3606
|
+
this.accounts.set(accountId, updated);
|
|
3607
|
+
return updated;
|
|
3608
|
+
}
|
|
3609
|
+
async listAccounts(sellerId) {
|
|
3610
|
+
const result = [];
|
|
3611
|
+
for (const account of this.accounts.values()) {
|
|
3612
|
+
if (account.sellerId === sellerId) {
|
|
3613
|
+
result.push(account);
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
return result;
|
|
3617
|
+
}
|
|
3618
|
+
// ============ Deferred Payments ============
|
|
3619
|
+
async createPayment(params) {
|
|
3620
|
+
const paymentId = `dp_${randomUUID().slice(0, 8)}`;
|
|
3621
|
+
const now = /* @__PURE__ */ new Date();
|
|
3622
|
+
const dueDate = new Date(now);
|
|
3623
|
+
dueDate.setDate(dueDate.getDate() + (params.dueInDays || 30));
|
|
3624
|
+
const payment = {
|
|
3625
|
+
paymentId,
|
|
3626
|
+
accountId: params.accountId,
|
|
3627
|
+
orderId: params.orderId,
|
|
3628
|
+
service: params.service,
|
|
3629
|
+
amount: params.amount,
|
|
3630
|
+
paidAmount: 0,
|
|
3631
|
+
status: "pending",
|
|
3632
|
+
dueDate: dueDate.toISOString(),
|
|
3633
|
+
chain: params.chain || "base",
|
|
3634
|
+
buyerId: params.buyerId,
|
|
3635
|
+
sellerAddress: params.sellerAddress,
|
|
3636
|
+
plan: params.plan ? { ...params.plan, completedInstallments: 0 } : void 0,
|
|
3637
|
+
createdAt: now.toISOString(),
|
|
3638
|
+
updatedAt: now.toISOString(),
|
|
3639
|
+
settlements: [],
|
|
3640
|
+
metadata: params.metadata
|
|
3641
|
+
};
|
|
3642
|
+
this.payments.set(paymentId, payment);
|
|
3643
|
+
if (params.accountId) {
|
|
3644
|
+
await this.addTransaction(params.accountId, {
|
|
3645
|
+
type: "charge",
|
|
3646
|
+
amount: params.amount,
|
|
3647
|
+
balanceAfter: 0,
|
|
3648
|
+
// Will be calculated
|
|
3649
|
+
reference: params.orderId,
|
|
3650
|
+
notes: params.service
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
return payment;
|
|
3654
|
+
}
|
|
3655
|
+
async getPayment(paymentId) {
|
|
3656
|
+
return this.payments.get(paymentId) || null;
|
|
3657
|
+
}
|
|
3658
|
+
async updatePayment(paymentId, updates) {
|
|
3659
|
+
const payment = this.payments.get(paymentId);
|
|
3660
|
+
if (!payment) {
|
|
3661
|
+
throw new Error(`Payment not found: ${paymentId}`);
|
|
3662
|
+
}
|
|
3663
|
+
const updated = {
|
|
3664
|
+
...payment,
|
|
3665
|
+
...updates,
|
|
3666
|
+
paymentId,
|
|
3667
|
+
// Prevent overwriting ID
|
|
3668
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3669
|
+
};
|
|
3670
|
+
this.payments.set(paymentId, updated);
|
|
3671
|
+
return updated;
|
|
3672
|
+
}
|
|
3673
|
+
async listPayments(filter) {
|
|
3674
|
+
const now = /* @__PURE__ */ new Date();
|
|
3675
|
+
const result = [];
|
|
3676
|
+
for (const payment of this.payments.values()) {
|
|
3677
|
+
if (filter.accountId && payment.accountId !== filter.accountId) continue;
|
|
3678
|
+
if (filter.buyerId && payment.buyerId !== filter.buyerId) continue;
|
|
3679
|
+
if (filter.status) {
|
|
3680
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
3681
|
+
if (!statuses.includes(payment.status)) continue;
|
|
3682
|
+
}
|
|
3683
|
+
if (filter.overdueOnly) {
|
|
3684
|
+
const dueDate = new Date(payment.dueDate);
|
|
3685
|
+
if (dueDate >= now || payment.status === "paid" || payment.status === "settled") {
|
|
3686
|
+
continue;
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
result.push(payment);
|
|
3690
|
+
}
|
|
3691
|
+
return result;
|
|
3692
|
+
}
|
|
3693
|
+
// ============ Transactions ============
|
|
3694
|
+
async addTransaction(accountId, tx) {
|
|
3695
|
+
const account = this.accounts.get(accountId);
|
|
3696
|
+
if (!account) {
|
|
3697
|
+
throw new Error(`Account not found: ${accountId}`);
|
|
3698
|
+
}
|
|
3699
|
+
let newBalance = account.balance;
|
|
3700
|
+
if (tx.type === "charge" || tx.type === "late_fee") {
|
|
3701
|
+
newBalance += tx.amount;
|
|
3702
|
+
} else if (tx.type === "payment" || tx.type === "credit" || tx.type === "refund") {
|
|
3703
|
+
newBalance -= Math.abs(tx.amount);
|
|
3704
|
+
} else if (tx.type === "adjustment") {
|
|
3705
|
+
newBalance += tx.amount;
|
|
3706
|
+
}
|
|
3707
|
+
const transaction = {
|
|
3708
|
+
txId: `ctx_${randomUUID().slice(0, 8)}`,
|
|
3709
|
+
...tx,
|
|
3710
|
+
balanceAfter: newBalance,
|
|
3711
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3712
|
+
};
|
|
3713
|
+
account.balance = newBalance;
|
|
3714
|
+
account.transactions.push(transaction);
|
|
3715
|
+
account.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3716
|
+
return transaction;
|
|
3717
|
+
}
|
|
3718
|
+
// ============ Utilities ============
|
|
3719
|
+
/**
|
|
3720
|
+
* Get all data (for debugging/export)
|
|
3721
|
+
*/
|
|
3722
|
+
export() {
|
|
3723
|
+
return {
|
|
3724
|
+
accounts: Array.from(this.accounts.values()),
|
|
3725
|
+
payments: Array.from(this.payments.values())
|
|
3726
|
+
};
|
|
3727
|
+
}
|
|
3728
|
+
/**
|
|
3729
|
+
* Import data (for restore)
|
|
3730
|
+
*/
|
|
3731
|
+
import(data) {
|
|
3732
|
+
this.accounts.clear();
|
|
3733
|
+
this.payments.clear();
|
|
3734
|
+
this.buyerSellerIndex.clear();
|
|
3735
|
+
for (const account of data.accounts) {
|
|
3736
|
+
this.accounts.set(account.accountId, account);
|
|
3737
|
+
this.buyerSellerIndex.set(this.makeKey(account.buyerId, account.sellerId), account.accountId);
|
|
3738
|
+
}
|
|
3739
|
+
for (const payment of data.payments) {
|
|
3740
|
+
this.payments.set(payment.paymentId, payment);
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
/**
|
|
3744
|
+
* Clear all data
|
|
3745
|
+
*/
|
|
3746
|
+
clear() {
|
|
3747
|
+
this.accounts.clear();
|
|
3748
|
+
this.payments.clear();
|
|
3749
|
+
this.buyerSellerIndex.clear();
|
|
3750
|
+
}
|
|
3751
|
+
};
|
|
3752
|
+
|
|
3753
|
+
// src/deferred/DeferredPaymentManager.ts
|
|
3754
|
+
var DeferredPaymentManager = class {
|
|
3755
|
+
config;
|
|
3756
|
+
constructor(config) {
|
|
3757
|
+
this.config = {
|
|
3758
|
+
sellerAddress: config.sellerAddress,
|
|
3759
|
+
sellerId: config.sellerId,
|
|
3760
|
+
chain: config.chain || "base",
|
|
3761
|
+
store: config.store || new MemoryDeferredStore(),
|
|
3762
|
+
autoVerify: config.autoVerify ?? true
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
// ============ Credit Account Management ============
|
|
3766
|
+
/**
|
|
3767
|
+
* Create a new credit account for a buyer
|
|
3768
|
+
*/
|
|
3769
|
+
async createCreditAccount(params) {
|
|
3770
|
+
const existing = await this.config.store.getAccountByBuyer(
|
|
3771
|
+
params.buyerId,
|
|
3772
|
+
this.config.sellerId
|
|
3773
|
+
);
|
|
3774
|
+
if (existing) {
|
|
3775
|
+
throw new Error(`Credit account already exists for buyer: ${params.buyerId}`);
|
|
3776
|
+
}
|
|
3777
|
+
return this.config.store.createAccount({
|
|
3778
|
+
buyerId: params.buyerId,
|
|
3779
|
+
sellerId: this.config.sellerId,
|
|
3780
|
+
creditLimit: params.creditLimit,
|
|
3781
|
+
chain: this.config.chain,
|
|
3782
|
+
terms: params.netDays ? { netDays: params.netDays, graceDays: 7, settlementFrequency: "on-demand" } : void 0,
|
|
3783
|
+
metadata: params.metadata
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
3786
|
+
/**
|
|
3787
|
+
* Get or create a credit account for a buyer
|
|
3788
|
+
*/
|
|
3789
|
+
async getOrCreateAccount(params) {
|
|
3790
|
+
const existing = await this.config.store.getAccountByBuyer(
|
|
3791
|
+
params.buyerId,
|
|
3792
|
+
this.config.sellerId
|
|
3793
|
+
);
|
|
3794
|
+
if (existing) return existing;
|
|
3795
|
+
return this.createCreditAccount({
|
|
3796
|
+
buyerId: params.buyerId,
|
|
3797
|
+
creditLimit: params.creditLimit || 100
|
|
3798
|
+
// Default $100 credit limit
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Get credit account by ID
|
|
3803
|
+
*/
|
|
3804
|
+
async getAccount(accountId) {
|
|
3805
|
+
return this.config.store.getAccount(accountId);
|
|
3806
|
+
}
|
|
3807
|
+
/**
|
|
3808
|
+
* Get credit account by buyer ID
|
|
3809
|
+
*/
|
|
3810
|
+
async getAccountByBuyer(buyerId) {
|
|
3811
|
+
return this.config.store.getAccountByBuyer(buyerId, this.config.sellerId);
|
|
3812
|
+
}
|
|
3813
|
+
/**
|
|
3814
|
+
* Update credit limit
|
|
3815
|
+
*/
|
|
3816
|
+
async updateCreditLimit(accountId, newLimit) {
|
|
3817
|
+
return this.config.store.updateAccount(accountId, { creditLimit: newLimit });
|
|
3818
|
+
}
|
|
3819
|
+
/**
|
|
3820
|
+
* Suspend an account
|
|
3821
|
+
*/
|
|
3822
|
+
async suspendAccount(accountId, reason) {
|
|
3823
|
+
const account = await this.config.store.updateAccount(accountId, { status: "suspended" });
|
|
3824
|
+
await this.config.store.addTransaction(accountId, {
|
|
3825
|
+
type: "adjustment",
|
|
3826
|
+
amount: 0,
|
|
3827
|
+
balanceAfter: account.balance,
|
|
3828
|
+
reference: "account_suspended",
|
|
3829
|
+
notes: reason || "Account suspended"
|
|
3830
|
+
});
|
|
3831
|
+
return account;
|
|
3832
|
+
}
|
|
3833
|
+
/**
|
|
3834
|
+
* Reactivate a suspended account
|
|
3835
|
+
*/
|
|
3836
|
+
async reactivateAccount(accountId) {
|
|
3837
|
+
return this.config.store.updateAccount(accountId, { status: "active" });
|
|
3838
|
+
}
|
|
3839
|
+
/**
|
|
3840
|
+
* Get account summary with pending/overdue payments
|
|
3841
|
+
*/
|
|
3842
|
+
async getAccountSummary(accountId) {
|
|
3843
|
+
const account = await this.config.store.getAccount(accountId);
|
|
3844
|
+
if (!account) return null;
|
|
3845
|
+
const allPayments = await this.config.store.listPayments({ accountId });
|
|
3846
|
+
const now = /* @__PURE__ */ new Date();
|
|
3847
|
+
const pendingPayments = allPayments.filter(
|
|
3848
|
+
(p) => p.status === "pending" || p.status === "partial"
|
|
3849
|
+
);
|
|
3850
|
+
const overduePayments = allPayments.filter((p) => {
|
|
3851
|
+
if (p.status === "paid" || p.status === "settled" || p.status === "cancelled") {
|
|
3852
|
+
return false;
|
|
3853
|
+
}
|
|
3854
|
+
return new Date(p.dueDate) < now;
|
|
3855
|
+
});
|
|
3856
|
+
return {
|
|
3857
|
+
account,
|
|
3858
|
+
pendingPayments,
|
|
3859
|
+
overduePayments,
|
|
3860
|
+
availableCredit: Math.max(0, account.creditLimit - account.balance),
|
|
3861
|
+
totalOwed: account.balance
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3864
|
+
/**
|
|
3865
|
+
* List all credit accounts for this seller
|
|
3866
|
+
*/
|
|
3867
|
+
async listAccounts() {
|
|
3868
|
+
return this.config.store.listAccounts(this.config.sellerId);
|
|
3869
|
+
}
|
|
3870
|
+
// ============ Deferred Payment Operations ============
|
|
3871
|
+
/**
|
|
3872
|
+
* Charge a service to a credit account (deferred payment)
|
|
3873
|
+
*/
|
|
3874
|
+
async charge(params) {
|
|
3875
|
+
let account = await this.getAccountByBuyer(params.buyerId);
|
|
3876
|
+
if (!account) {
|
|
3877
|
+
try {
|
|
3878
|
+
account = await this.createCreditAccount({
|
|
3879
|
+
buyerId: params.buyerId,
|
|
3880
|
+
creditLimit: 100
|
|
3881
|
+
// Default $100 limit
|
|
3882
|
+
});
|
|
3883
|
+
} catch (error) {
|
|
3884
|
+
return {
|
|
3885
|
+
success: false,
|
|
3886
|
+
error: `Failed to create credit account: ${error}`
|
|
3887
|
+
};
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
if (account.status !== "active") {
|
|
3891
|
+
return {
|
|
3892
|
+
success: false,
|
|
3893
|
+
error: `Credit account is ${account.status}`
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
const newBalance = account.balance + params.amount;
|
|
3897
|
+
if (newBalance > account.creditLimit) {
|
|
3898
|
+
return {
|
|
3899
|
+
success: false,
|
|
3900
|
+
error: `Credit limit exceeded. Available: $${(account.creditLimit - account.balance).toFixed(2)}, Required: $${params.amount.toFixed(2)}`,
|
|
3901
|
+
creditExceeded: true
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
const payment = await this.config.store.createPayment({
|
|
3905
|
+
accountId: account.accountId,
|
|
3906
|
+
orderId: params.orderId,
|
|
3907
|
+
service: params.service,
|
|
3908
|
+
amount: params.amount,
|
|
3909
|
+
buyerId: params.buyerId,
|
|
3910
|
+
sellerAddress: this.config.sellerAddress,
|
|
3911
|
+
chain: this.config.chain,
|
|
3912
|
+
dueInDays: params.dueInDays || account.terms.netDays,
|
|
3913
|
+
metadata: params.metadata
|
|
3914
|
+
});
|
|
3915
|
+
const updatedAccount = await this.config.store.getAccount(account.accountId);
|
|
3916
|
+
const lastTx = updatedAccount?.transactions[updatedAccount.transactions.length - 1];
|
|
3917
|
+
return {
|
|
3918
|
+
success: true,
|
|
3919
|
+
payment,
|
|
3920
|
+
transaction: lastTx
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Create a deferred payment without a credit account (standalone)
|
|
3925
|
+
*/
|
|
3926
|
+
async createDeferredPayment(params) {
|
|
3927
|
+
return this.config.store.createPayment({
|
|
3928
|
+
...params,
|
|
3929
|
+
sellerAddress: params.sellerAddress || this.config.sellerAddress,
|
|
3930
|
+
chain: params.chain || this.config.chain
|
|
3931
|
+
});
|
|
3932
|
+
}
|
|
3933
|
+
/**
|
|
3934
|
+
* Get deferred payment by ID
|
|
3935
|
+
*/
|
|
3936
|
+
async getPayment(paymentId) {
|
|
3937
|
+
return this.config.store.getPayment(paymentId);
|
|
3938
|
+
}
|
|
3939
|
+
/**
|
|
3940
|
+
* List deferred payments with filters
|
|
3941
|
+
*/
|
|
3942
|
+
async listPayments(filter) {
|
|
3943
|
+
return this.config.store.listPayments(filter || {});
|
|
3944
|
+
}
|
|
3945
|
+
/**
|
|
3946
|
+
* Get overdue payments
|
|
3947
|
+
*/
|
|
3948
|
+
async getOverduePayments() {
|
|
3949
|
+
return this.config.store.listPayments({ overdueOnly: true });
|
|
3950
|
+
}
|
|
3951
|
+
// ============ Settlement ============
|
|
3952
|
+
/**
|
|
3953
|
+
* Record a settlement (payment received)
|
|
3954
|
+
*/
|
|
3955
|
+
async recordSettlement(params) {
|
|
3956
|
+
const payment = await this.config.store.getPayment(params.paymentId);
|
|
3957
|
+
if (!payment) {
|
|
3958
|
+
return { success: false, error: `Payment not found: ${params.paymentId}` };
|
|
3959
|
+
}
|
|
3960
|
+
let verified = false;
|
|
3961
|
+
if (this.config.autoVerify) {
|
|
3962
|
+
try {
|
|
3963
|
+
const verification = await verifyPayment({
|
|
3964
|
+
txHash: params.txHash,
|
|
3965
|
+
chain: params.chain || payment.chain,
|
|
3966
|
+
expectedTo: payment.sellerAddress,
|
|
3967
|
+
expectedAmount: params.amount
|
|
3968
|
+
});
|
|
3969
|
+
verified = verification.verified;
|
|
3970
|
+
if (!verified) {
|
|
3971
|
+
return {
|
|
3972
|
+
success: false,
|
|
3973
|
+
error: `Payment verification failed: ${verification.error || "Unknown error"}`
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
} catch (error) {
|
|
3977
|
+
return {
|
|
3978
|
+
success: false,
|
|
3979
|
+
error: `Verification error: ${error}`
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
} else {
|
|
3983
|
+
verified = true;
|
|
3984
|
+
}
|
|
3985
|
+
const settlement = {
|
|
3986
|
+
settlementId: `stl_${randomUUID2().slice(0, 8)}`,
|
|
3987
|
+
amount: params.amount,
|
|
3988
|
+
txHash: params.txHash,
|
|
3989
|
+
chain: params.chain || payment.chain,
|
|
3990
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3991
|
+
verified
|
|
3992
|
+
};
|
|
3993
|
+
const newPaidAmount = payment.paidAmount + params.amount;
|
|
3994
|
+
const newStatus = newPaidAmount >= payment.amount ? "settled" : newPaidAmount > 0 ? "partial" : "pending";
|
|
3995
|
+
const updatedPayment = await this.config.store.updatePayment(params.paymentId, {
|
|
3996
|
+
paidAmount: newPaidAmount,
|
|
3997
|
+
status: newStatus,
|
|
3998
|
+
settlements: [...payment.settlements, settlement]
|
|
3999
|
+
});
|
|
4000
|
+
let transaction;
|
|
4001
|
+
if (payment.accountId) {
|
|
4002
|
+
transaction = await this.config.store.addTransaction(payment.accountId, {
|
|
4003
|
+
type: "payment",
|
|
4004
|
+
amount: params.amount,
|
|
4005
|
+
balanceAfter: 0,
|
|
4006
|
+
// Will be calculated by store
|
|
4007
|
+
reference: payment.orderId,
|
|
4008
|
+
onChainTxHash: params.txHash,
|
|
4009
|
+
notes: `Settlement for ${payment.service}`
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
4012
|
+
return {
|
|
4013
|
+
success: true,
|
|
4014
|
+
settlement,
|
|
4015
|
+
payment: updatedPayment,
|
|
4016
|
+
transaction
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
/**
|
|
4020
|
+
* Settle all pending charges for an account
|
|
4021
|
+
*/
|
|
4022
|
+
async settleAccount(accountId, txHash) {
|
|
4023
|
+
const account = await this.config.store.getAccount(accountId);
|
|
4024
|
+
if (!account) {
|
|
4025
|
+
return { success: false, error: `Account not found: ${accountId}` };
|
|
4026
|
+
}
|
|
4027
|
+
if (account.balance <= 0) {
|
|
4028
|
+
return { success: false, error: "No balance to settle" };
|
|
4029
|
+
}
|
|
4030
|
+
if (this.config.autoVerify) {
|
|
4031
|
+
const verification = await verifyPayment({
|
|
4032
|
+
txHash,
|
|
4033
|
+
chain: account.chain,
|
|
4034
|
+
expectedTo: this.config.sellerAddress,
|
|
4035
|
+
expectedAmount: account.balance
|
|
4036
|
+
});
|
|
4037
|
+
if (!verification.verified) {
|
|
4038
|
+
return {
|
|
4039
|
+
success: false,
|
|
4040
|
+
error: `Payment verification failed: ${verification.error || "Unknown error"}`
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
const transaction = await this.config.store.addTransaction(accountId, {
|
|
4045
|
+
type: "payment",
|
|
4046
|
+
amount: account.balance,
|
|
4047
|
+
balanceAfter: 0,
|
|
4048
|
+
reference: `account_settlement_${accountId}`,
|
|
4049
|
+
onChainTxHash: txHash,
|
|
4050
|
+
notes: "Full account settlement"
|
|
4051
|
+
});
|
|
4052
|
+
const pendingPayments = await this.config.store.listPayments({
|
|
4053
|
+
accountId,
|
|
4054
|
+
status: ["pending", "partial"]
|
|
4055
|
+
});
|
|
4056
|
+
const settlement = {
|
|
4057
|
+
settlementId: `stl_${randomUUID2().slice(0, 8)}`,
|
|
4058
|
+
amount: account.balance,
|
|
4059
|
+
txHash,
|
|
4060
|
+
chain: account.chain,
|
|
4061
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4062
|
+
verified: true
|
|
4063
|
+
};
|
|
4064
|
+
for (const payment of pendingPayments) {
|
|
4065
|
+
await this.config.store.updatePayment(payment.paymentId, {
|
|
4066
|
+
paidAmount: payment.amount,
|
|
4067
|
+
status: "settled",
|
|
4068
|
+
settlements: [...payment.settlements, settlement]
|
|
4069
|
+
});
|
|
4070
|
+
}
|
|
4071
|
+
return {
|
|
4072
|
+
success: true,
|
|
4073
|
+
settlement,
|
|
4074
|
+
transaction
|
|
4075
|
+
};
|
|
4076
|
+
}
|
|
4077
|
+
// ============ Credit Operations ============
|
|
4078
|
+
/**
|
|
4079
|
+
* Issue a credit (reduce balance owed)
|
|
4080
|
+
*/
|
|
4081
|
+
async issueCredit(params) {
|
|
4082
|
+
return this.config.store.addTransaction(params.accountId, {
|
|
4083
|
+
type: "credit",
|
|
4084
|
+
amount: params.amount,
|
|
4085
|
+
balanceAfter: 0,
|
|
4086
|
+
reference: "credit_issued",
|
|
4087
|
+
notes: params.reason
|
|
4088
|
+
});
|
|
4089
|
+
}
|
|
4090
|
+
/**
|
|
4091
|
+
* Issue a refund
|
|
4092
|
+
*/
|
|
4093
|
+
async issueRefund(params) {
|
|
4094
|
+
const payment = await this.config.store.getPayment(params.paymentId);
|
|
4095
|
+
if (!payment) {
|
|
4096
|
+
return { success: false, error: `Payment not found: ${params.paymentId}` };
|
|
4097
|
+
}
|
|
4098
|
+
const newPaidAmount = Math.max(0, payment.paidAmount - params.amount);
|
|
4099
|
+
await this.config.store.updatePayment(params.paymentId, {
|
|
4100
|
+
paidAmount: newPaidAmount,
|
|
4101
|
+
status: newPaidAmount >= payment.amount ? "settled" : newPaidAmount > 0 ? "partial" : "pending"
|
|
4102
|
+
});
|
|
4103
|
+
let transaction;
|
|
4104
|
+
if (payment.accountId) {
|
|
4105
|
+
transaction = await this.config.store.addTransaction(payment.accountId, {
|
|
4106
|
+
type: "refund",
|
|
4107
|
+
amount: params.amount,
|
|
4108
|
+
balanceAfter: 0,
|
|
4109
|
+
reference: payment.orderId,
|
|
4110
|
+
notes: params.reason
|
|
4111
|
+
});
|
|
4112
|
+
}
|
|
4113
|
+
return { success: true, transaction };
|
|
4114
|
+
}
|
|
4115
|
+
// ============ Maintenance ============
|
|
4116
|
+
/**
|
|
4117
|
+
* Mark overdue payments
|
|
4118
|
+
*/
|
|
4119
|
+
async markOverduePayments() {
|
|
4120
|
+
const now = /* @__PURE__ */ new Date();
|
|
4121
|
+
const pending = await this.config.store.listPayments({
|
|
4122
|
+
status: ["pending", "partial"]
|
|
4123
|
+
});
|
|
4124
|
+
const overdue = [];
|
|
4125
|
+
for (const payment of pending) {
|
|
4126
|
+
if (new Date(payment.dueDate) < now) {
|
|
4127
|
+
const updated = await this.config.store.updatePayment(payment.paymentId, {
|
|
4128
|
+
status: "overdue"
|
|
4129
|
+
});
|
|
4130
|
+
overdue.push(updated);
|
|
4131
|
+
if (payment.accountId) {
|
|
4132
|
+
const account = await this.config.store.getAccount(payment.accountId);
|
|
4133
|
+
if (account && account.status === "active") {
|
|
4134
|
+
const graceEnd = new Date(payment.dueDate);
|
|
4135
|
+
graceEnd.setDate(graceEnd.getDate() + account.terms.graceDays);
|
|
4136
|
+
if (now > graceEnd) {
|
|
4137
|
+
await this.suspendAccount(payment.accountId, "Overdue payment past grace period");
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
return overdue;
|
|
4144
|
+
}
|
|
4145
|
+
/**
|
|
4146
|
+
* Apply late fees to overdue accounts
|
|
4147
|
+
*/
|
|
4148
|
+
async applyLateFees() {
|
|
4149
|
+
const accounts = await this.listAccounts();
|
|
4150
|
+
const fees = [];
|
|
4151
|
+
for (const account of accounts) {
|
|
4152
|
+
if (account.terms.lateFeePercent && account.balance > 0) {
|
|
4153
|
+
const overduePayments = await this.config.store.listPayments({
|
|
4154
|
+
accountId: account.accountId,
|
|
4155
|
+
status: "overdue"
|
|
4156
|
+
});
|
|
4157
|
+
if (overduePayments.length > 0) {
|
|
4158
|
+
const feeAmount = account.balance * (account.terms.lateFeePercent / 100);
|
|
4159
|
+
const tx = await this.config.store.addTransaction(account.accountId, {
|
|
4160
|
+
type: "late_fee",
|
|
4161
|
+
amount: feeAmount,
|
|
4162
|
+
balanceAfter: 0,
|
|
4163
|
+
reference: "late_fee",
|
|
4164
|
+
notes: `${account.terms.lateFeePercent}% late fee on $${account.balance.toFixed(2)}`
|
|
4165
|
+
});
|
|
4166
|
+
fees.push(tx);
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
return fees;
|
|
4171
|
+
}
|
|
4172
|
+
};
|
|
4173
|
+
|
|
4174
|
+
// src/deferred/JsonStore.ts
|
|
4175
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync5 } from "fs";
|
|
4176
|
+
import { dirname as dirname2 } from "path";
|
|
4177
|
+
var JsonDeferredStore = class {
|
|
4178
|
+
memory;
|
|
4179
|
+
config;
|
|
4180
|
+
dirty = false;
|
|
4181
|
+
constructor(config) {
|
|
4182
|
+
this.config = {
|
|
4183
|
+
filePath: config.filePath,
|
|
4184
|
+
autoSave: config.autoSave ?? true,
|
|
4185
|
+
prettyPrint: config.prettyPrint ?? true
|
|
4186
|
+
};
|
|
4187
|
+
this.memory = new MemoryDeferredStore();
|
|
4188
|
+
this.load();
|
|
4189
|
+
}
|
|
4190
|
+
// ============ Persistence ============
|
|
4191
|
+
/**
|
|
4192
|
+
* Load data from file
|
|
4193
|
+
*/
|
|
4194
|
+
load() {
|
|
4195
|
+
if (!existsSync5(this.config.filePath)) {
|
|
4196
|
+
return;
|
|
4197
|
+
}
|
|
4198
|
+
try {
|
|
4199
|
+
const data = JSON.parse(readFileSync5(this.config.filePath, "utf-8"));
|
|
4200
|
+
this.memory.import(data);
|
|
4201
|
+
} catch (error) {
|
|
4202
|
+
console.error(`Failed to load deferred store from ${this.config.filePath}:`, error);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
/**
|
|
4206
|
+
* Save data to file
|
|
4207
|
+
*/
|
|
4208
|
+
save() {
|
|
4209
|
+
try {
|
|
4210
|
+
const dir = dirname2(this.config.filePath);
|
|
4211
|
+
if (!existsSync5(dir)) {
|
|
4212
|
+
mkdirSync5(dir, { recursive: true });
|
|
4213
|
+
}
|
|
4214
|
+
const data = this.memory.export();
|
|
4215
|
+
const json = this.config.prettyPrint ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
4216
|
+
writeFileSync4(this.config.filePath, json, "utf-8");
|
|
4217
|
+
this.dirty = false;
|
|
4218
|
+
} catch (error) {
|
|
4219
|
+
console.error(`Failed to save deferred store to ${this.config.filePath}:`, error);
|
|
4220
|
+
throw error;
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
/**
|
|
4224
|
+
* Mark as dirty and optionally auto-save
|
|
4225
|
+
*/
|
|
4226
|
+
markDirty() {
|
|
4227
|
+
this.dirty = true;
|
|
4228
|
+
if (this.config.autoSave) {
|
|
4229
|
+
this.save();
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
/**
|
|
4233
|
+
* Check if there are unsaved changes
|
|
4234
|
+
*/
|
|
4235
|
+
isDirty() {
|
|
4236
|
+
return this.dirty;
|
|
4237
|
+
}
|
|
4238
|
+
// ============ Credit Accounts (delegate to memory store) ============
|
|
4239
|
+
async createAccount(params) {
|
|
4240
|
+
const result = await this.memory.createAccount(params);
|
|
4241
|
+
this.markDirty();
|
|
4242
|
+
return result;
|
|
4243
|
+
}
|
|
4244
|
+
async getAccount(accountId) {
|
|
4245
|
+
return this.memory.getAccount(accountId);
|
|
4246
|
+
}
|
|
4247
|
+
async getAccountByBuyer(buyerId, sellerId) {
|
|
4248
|
+
return this.memory.getAccountByBuyer(buyerId, sellerId);
|
|
4249
|
+
}
|
|
4250
|
+
async updateAccount(accountId, updates) {
|
|
4251
|
+
const result = await this.memory.updateAccount(accountId, updates);
|
|
4252
|
+
this.markDirty();
|
|
4253
|
+
return result;
|
|
4254
|
+
}
|
|
4255
|
+
async listAccounts(sellerId) {
|
|
4256
|
+
return this.memory.listAccounts(sellerId);
|
|
4257
|
+
}
|
|
4258
|
+
// ============ Deferred Payments ============
|
|
4259
|
+
async createPayment(params) {
|
|
4260
|
+
const result = await this.memory.createPayment(params);
|
|
4261
|
+
this.markDirty();
|
|
4262
|
+
return result;
|
|
4263
|
+
}
|
|
4264
|
+
async getPayment(paymentId) {
|
|
4265
|
+
return this.memory.getPayment(paymentId);
|
|
4266
|
+
}
|
|
4267
|
+
async updatePayment(paymentId, updates) {
|
|
4268
|
+
const result = await this.memory.updatePayment(paymentId, updates);
|
|
4269
|
+
this.markDirty();
|
|
4270
|
+
return result;
|
|
4271
|
+
}
|
|
4272
|
+
async listPayments(filter) {
|
|
4273
|
+
return this.memory.listPayments(filter);
|
|
4274
|
+
}
|
|
4275
|
+
// ============ Transactions ============
|
|
4276
|
+
async addTransaction(accountId, tx) {
|
|
4277
|
+
const result = await this.memory.addTransaction(accountId, tx);
|
|
4278
|
+
this.markDirty();
|
|
4279
|
+
return result;
|
|
4280
|
+
}
|
|
4281
|
+
// ============ Utilities ============
|
|
4282
|
+
/**
|
|
4283
|
+
* Export all data
|
|
4284
|
+
*/
|
|
4285
|
+
export() {
|
|
4286
|
+
return this.memory.export();
|
|
4287
|
+
}
|
|
4288
|
+
/**
|
|
4289
|
+
* Clear all data (including file)
|
|
4290
|
+
*/
|
|
4291
|
+
clear() {
|
|
4292
|
+
this.memory.clear();
|
|
4293
|
+
this.markDirty();
|
|
4294
|
+
}
|
|
4295
|
+
};
|
|
4296
|
+
|
|
4297
|
+
// src/deferred/templates.ts
|
|
4298
|
+
var DeferredStatusMarkers = {
|
|
4299
|
+
creditAccountCreated: (accountId, limit) => `[status:credit_account_created id=${accountId} limit=${limit} USDC]`,
|
|
4300
|
+
chargeAdded: (paymentId, amount) => `[status:charge_added payment=${paymentId} amount=${amount} USDC]`,
|
|
4301
|
+
settlementReceived: (paymentId, txHash, amount) => `[status:settlement_received payment=${paymentId} tx=${txHash} amount=${amount} USDC]`,
|
|
4302
|
+
accountSettled: (accountId, txHash, amount) => `[status:account_settled id=${accountId} tx=${txHash} amount=${amount} USDC]`,
|
|
4303
|
+
creditIssued: (accountId, amount) => `[status:credit_issued id=${accountId} amount=${amount} USDC]`,
|
|
4304
|
+
accountSuspended: (accountId, reason) => `[status:account_suspended id=${accountId} reason="${reason}"]`,
|
|
4305
|
+
paymentOverdue: (paymentId, amount) => `[status:payment_overdue payment=${paymentId} amount=${amount} USDC]`
|
|
4306
|
+
};
|
|
4307
|
+
var DeferredSellerTemplates = {
|
|
4308
|
+
/**
|
|
4309
|
+
* Offer deferred payment option
|
|
4310
|
+
*/
|
|
4311
|
+
offerDeferredPayment: (params) => {
|
|
4312
|
+
const days = params.netDays || 30;
|
|
4313
|
+
return `I can offer you two payment options for ${params.service}:
|
|
4314
|
+
|
|
4315
|
+
**Option A: Pay Now**
|
|
4316
|
+
- Price: ${params.price} USDC
|
|
4317
|
+
- Pay via on-chain transfer, service delivered immediately
|
|
4318
|
+
|
|
4319
|
+
**Option B: Pay Later (Net-${days})**
|
|
4320
|
+
- Price: ${params.price} USDC
|
|
4321
|
+
- Service delivered now, pay within ${days} days
|
|
4322
|
+
- Requires a credit account (I can set one up for you)
|
|
4323
|
+
|
|
4324
|
+
Which option do you prefer?`;
|
|
4325
|
+
},
|
|
4326
|
+
/**
|
|
4327
|
+
* Explain credit account setup
|
|
4328
|
+
*/
|
|
4329
|
+
explainCreditAccount: () => `
|
|
4330
|
+
To use deferred payment, I'll set up a credit account for you. Here's how it works:
|
|
4331
|
+
|
|
4332
|
+
1. **Credit Limit** - I'll extend you a credit line (e.g., $100 USDC)
|
|
4333
|
+
2. **Use Services** - Each service gets charged to your account
|
|
4334
|
+
3. **Pay Later** - Settle your balance whenever you want, or by the due date
|
|
4335
|
+
4. **On-chain Settlement** - When you pay, send USDC to my address and share the tx hash
|
|
4336
|
+
|
|
4337
|
+
Would you like me to set up a credit account for you?`,
|
|
4338
|
+
/**
|
|
4339
|
+
* Confirm credit account creation
|
|
4340
|
+
*/
|
|
4341
|
+
creditAccountCreated: (account) => `Great! I've set up a credit account for you.
|
|
4342
|
+
|
|
4343
|
+
**Account Details:**
|
|
4344
|
+
- Account ID: ${account.accountId}
|
|
4345
|
+
- Credit Limit: $${account.creditLimit.toFixed(2)} USDC
|
|
4346
|
+
- Payment Terms: Net-${account.terms.netDays}
|
|
4347
|
+
- Current Balance: $${account.balance.toFixed(2)}
|
|
4348
|
+
|
|
4349
|
+
You can now use services on credit. I'll track charges and you can settle anytime.
|
|
4350
|
+
${DeferredStatusMarkers.creditAccountCreated(account.accountId, account.creditLimit)}`,
|
|
4351
|
+
/**
|
|
4352
|
+
* Charge confirmation
|
|
4353
|
+
*/
|
|
4354
|
+
chargeConfirmation: (payment, availableCredit) => `Service charged to your account.
|
|
4355
|
+
|
|
4356
|
+
**Charge Details:**
|
|
4357
|
+
- Service: ${payment.service}
|
|
4358
|
+
- Amount: $${payment.amount.toFixed(2)} USDC
|
|
4359
|
+
- Order ID: ${payment.orderId}
|
|
4360
|
+
- Due Date: ${new Date(payment.dueDate).toLocaleDateString()}
|
|
4361
|
+
|
|
4362
|
+
**Account Status:**
|
|
4363
|
+
- Available Credit: $${availableCredit.toFixed(2)} USDC
|
|
4364
|
+
|
|
4365
|
+
I'll proceed with your service now.
|
|
4366
|
+
${DeferredStatusMarkers.chargeAdded(payment.paymentId, payment.amount)}`,
|
|
4367
|
+
/**
|
|
4368
|
+
* Credit limit exceeded
|
|
4369
|
+
*/
|
|
4370
|
+
creditLimitExceeded: (params) => `Sorry, this charge would exceed your credit limit.
|
|
4371
|
+
|
|
4372
|
+
**Current Status:**
|
|
4373
|
+
- Credit Limit: $${params.limit.toFixed(2)} USDC
|
|
4374
|
+
- Current Balance: $${params.balance.toFixed(2)} USDC
|
|
4375
|
+
- Available Credit: $${params.available.toFixed(2)} USDC
|
|
4376
|
+
- Requested: $${params.requested.toFixed(2)} USDC
|
|
4377
|
+
|
|
4378
|
+
You can either:
|
|
4379
|
+
1. **Settle some balance** - Pay down your balance to free up credit
|
|
4380
|
+
2. **Pay for this service directly** - Skip credit, pay on-chain now
|
|
4381
|
+
3. **Request credit increase** - Ask for a higher limit (subject to approval)
|
|
4382
|
+
|
|
4383
|
+
What would you like to do?`,
|
|
4384
|
+
/**
|
|
4385
|
+
* Account summary/statement
|
|
4386
|
+
*/
|
|
4387
|
+
accountStatement: (summary) => {
|
|
4388
|
+
const { account, pendingPayments, overduePayments } = summary;
|
|
4389
|
+
let statement = `**Account Statement**
|
|
4390
|
+
- Account ID: ${account.accountId}
|
|
4391
|
+
- Status: ${account.status.toUpperCase()}
|
|
4392
|
+
- Credit Limit: $${account.creditLimit.toFixed(2)} USDC
|
|
4393
|
+
- Current Balance: $${account.balance.toFixed(2)} USDC
|
|
4394
|
+
- Available Credit: $${summary.availableCredit.toFixed(2)} USDC
|
|
4395
|
+
|
|
4396
|
+
`;
|
|
4397
|
+
if (pendingPayments.length > 0) {
|
|
4398
|
+
statement += `**Pending Charges (${pendingPayments.length}):**
|
|
4399
|
+
`;
|
|
4400
|
+
for (const p of pendingPayments) {
|
|
4401
|
+
statement += `- ${p.service}: $${p.amount.toFixed(2)} (due ${new Date(p.dueDate).toLocaleDateString()})
|
|
4402
|
+
`;
|
|
4403
|
+
}
|
|
4404
|
+
statement += "\n";
|
|
4405
|
+
}
|
|
4406
|
+
if (overduePayments.length > 0) {
|
|
4407
|
+
statement += `**\u26A0\uFE0F OVERDUE (${overduePayments.length}):**
|
|
4408
|
+
`;
|
|
4409
|
+
for (const p of overduePayments) {
|
|
4410
|
+
statement += `- ${p.service}: $${p.amount.toFixed(2)} (was due ${new Date(p.dueDate).toLocaleDateString()})
|
|
4411
|
+
`;
|
|
4412
|
+
}
|
|
4413
|
+
statement += "\n";
|
|
4414
|
+
}
|
|
4415
|
+
if (account.balance > 0) {
|
|
4416
|
+
statement += `**To Settle:**
|
|
4417
|
+
Send $${account.balance.toFixed(2)} USDC to my address and share the transaction hash.`;
|
|
4418
|
+
}
|
|
4419
|
+
return statement;
|
|
4420
|
+
},
|
|
4421
|
+
/**
|
|
4422
|
+
* Settlement confirmation
|
|
4423
|
+
*/
|
|
4424
|
+
settlementConfirmation: (params) => `Payment received and verified on-chain. Thank you!
|
|
4425
|
+
|
|
4426
|
+
**Settlement Details:**
|
|
4427
|
+
- Amount: $${params.amount.toFixed(2)} USDC
|
|
4428
|
+
- Transaction: ${params.txHash}
|
|
4429
|
+
- New Balance: $${params.newBalance.toFixed(2)} USDC
|
|
4430
|
+
|
|
4431
|
+
${params.paymentId ? DeferredStatusMarkers.settlementReceived(params.paymentId, params.txHash, params.amount) : ""}`,
|
|
4432
|
+
/**
|
|
4433
|
+
* Overdue notice
|
|
4434
|
+
*/
|
|
4435
|
+
overdueNotice: (payments) => {
|
|
4436
|
+
const total = payments.reduce((sum, p) => sum + (p.amount - p.paidAmount), 0);
|
|
4437
|
+
let notice = `\u26A0\uFE0F **Payment Overdue Notice**
|
|
4438
|
+
|
|
4439
|
+
You have ${payments.length} overdue payment(s) totaling $${total.toFixed(2)} USDC:
|
|
4440
|
+
|
|
4441
|
+
`;
|
|
4442
|
+
for (const p of payments) {
|
|
4443
|
+
const overdueDays = Math.floor((Date.now() - new Date(p.dueDate).getTime()) / (1e3 * 60 * 60 * 24));
|
|
4444
|
+
notice += `- ${p.service}: $${(p.amount - p.paidAmount).toFixed(2)} (${overdueDays} days overdue)
|
|
4445
|
+
`;
|
|
4446
|
+
}
|
|
4447
|
+
notice += `
|
|
4448
|
+
Please settle your balance to avoid account suspension. Send USDC to my address and share the transaction hash.`;
|
|
4449
|
+
return notice;
|
|
4450
|
+
},
|
|
4451
|
+
/**
|
|
4452
|
+
* Credit issued confirmation
|
|
4453
|
+
*/
|
|
4454
|
+
creditIssued: (params) => `I've issued a credit to your account.
|
|
4455
|
+
|
|
4456
|
+
**Credit Details:**
|
|
4457
|
+
- Amount: $${params.amount.toFixed(2)} USDC
|
|
4458
|
+
- Reason: ${params.reason}
|
|
4459
|
+
- New Balance: $${params.newBalance.toFixed(2)} USDC
|
|
4460
|
+
|
|
4461
|
+
${DeferredStatusMarkers.creditIssued(params.accountId, params.amount)}`
|
|
4462
|
+
};
|
|
4463
|
+
var DeferredBuyerTemplates = {
|
|
4464
|
+
/**
|
|
4465
|
+
* Request deferred payment
|
|
4466
|
+
*/
|
|
4467
|
+
requestDeferredPayment: (service) => `I'd like to use ${service}, but prefer to pay later. Do you offer deferred payment or credit terms?`,
|
|
4468
|
+
/**
|
|
4469
|
+
* Accept credit account offer
|
|
4470
|
+
*/
|
|
4471
|
+
acceptCreditAccount: () => `Yes, please set up a credit account for me. I'll settle the balance by the due date.`,
|
|
4472
|
+
/**
|
|
4473
|
+
* Request credit limit increase
|
|
4474
|
+
*/
|
|
4475
|
+
requestCreditIncrease: (currentLimit, requestedLimit) => `My current credit limit is $${currentLimit.toFixed(2)}. Could you increase it to $${requestedLimit.toFixed(2)}? I have a larger purchase coming up.`,
|
|
4476
|
+
/**
|
|
4477
|
+
* Request account statement
|
|
4478
|
+
*/
|
|
4479
|
+
requestStatement: () => `Can you show me my current account balance and any pending charges?`,
|
|
4480
|
+
/**
|
|
4481
|
+
* Announce settlement payment
|
|
4482
|
+
*/
|
|
4483
|
+
announceSettlement: (params) => `I've sent a payment to settle my balance.
|
|
4484
|
+
|
|
4485
|
+
**Payment Details:**
|
|
4486
|
+
- Amount: $${params.amount.toFixed(2)} USDC
|
|
4487
|
+
- Transaction Hash: ${params.txHash}
|
|
4488
|
+
|
|
4489
|
+
Please verify and update my account.
|
|
4490
|
+
${params.accountId ? `[status:settlement_sent account=${params.accountId} tx=${params.txHash} amount=${params.amount} USDC]` : ""}`,
|
|
4491
|
+
/**
|
|
4492
|
+
* Dispute a charge
|
|
4493
|
+
*/
|
|
4494
|
+
disputeCharge: (paymentId, reason) => `I'd like to dispute charge ${paymentId}. Reason: ${reason}
|
|
4495
|
+
|
|
4496
|
+
Please review and let me know how to resolve this.
|
|
4497
|
+
[status:charge_disputed payment=${paymentId}]`
|
|
4498
|
+
};
|
|
4499
|
+
function parseDeferredStatusMarker(text) {
|
|
4500
|
+
const match = text.match(/\[status:(\w+)\s+(.+?)\]/);
|
|
4501
|
+
if (!match) return null;
|
|
4502
|
+
const type = match[1];
|
|
4503
|
+
const dataStr = match[2];
|
|
4504
|
+
const data = {};
|
|
4505
|
+
const pairs = dataStr.match(/(\w+)=("[^"]+"|[\w.]+)/g);
|
|
4506
|
+
if (pairs) {
|
|
4507
|
+
for (const pair of pairs) {
|
|
4508
|
+
const [key, ...valueParts] = pair.split("=");
|
|
4509
|
+
let value = valueParts.join("=");
|
|
4510
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
4511
|
+
value = value.slice(1, -1);
|
|
4512
|
+
}
|
|
4513
|
+
data[key] = value;
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
return { type, data };
|
|
4517
|
+
}
|
|
3544
4518
|
export {
|
|
3545
4519
|
AgentWallet,
|
|
3546
4520
|
AllowanceWallet,
|
|
@@ -3548,7 +4522,13 @@ export {
|
|
|
3548
4522
|
BuyerTemplates,
|
|
3549
4523
|
CDPWallet,
|
|
3550
4524
|
CHAINS,
|
|
4525
|
+
DeferredBuyerTemplates,
|
|
4526
|
+
DeferredPaymentManager,
|
|
4527
|
+
DeferredSellerTemplates,
|
|
4528
|
+
DeferredStatusMarkers,
|
|
3551
4529
|
ERC20_ABI,
|
|
4530
|
+
JsonDeferredStore,
|
|
4531
|
+
MemoryDeferredStore,
|
|
3552
4532
|
MemoryOrderStore,
|
|
3553
4533
|
OrderManager,
|
|
3554
4534
|
PAYMENT_HEADER,
|
|
@@ -3594,6 +4574,7 @@ export {
|
|
|
3594
4574
|
loadCDPWallet,
|
|
3595
4575
|
loadWallet,
|
|
3596
4576
|
networkToChain,
|
|
4577
|
+
parseDeferredStatusMarker,
|
|
3597
4578
|
parsePaymentRequired,
|
|
3598
4579
|
parseStatusMarker,
|
|
3599
4580
|
signEIP3009,
|