
Payment Terms Optimization
- 67 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Configure flexible B2B payment terms with net-30/60/90 options, early-payment discounts, credit-limit management, and automated collections.
About
Configures B2B payment terms including net-30/60/90 invoicing, early-payment discounts, credit limits, and automated collections. A developer uses it to support wholesale and B2B buyers who pay on terms rather than at checkout.
- Net-30/60/90 terms with early-payment discounts
- Credit-limit management and automated collections
Payment Terms Optimization by the numbers
- 67 all-time installs (skills.sh)
- Ranked #3,098 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill payment-terms-optimizationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 67 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Configure flexible B2B payment terms with net-30/60/90 options, early-payment discounts, credit-limit management, and automated collections.
Files
Payment Terms Optimization
Overview
Payment terms define when a B2B customer must pay for goods delivered on credit: net-30 (due 30 days after invoice), net-60, net-90, or early-payment variants like "2/10 net-30" (2% discount if paid within 10 days). Offering flexible payment terms is a competitive advantage in B2B commerce — it reduces friction in the purchase decision — but introduces credit risk that must be carefully managed.
The right tools depend on your platform. Shopify Plus, BigCommerce B2B Edition, and several WooCommerce plugins provide native net-terms support. For smaller setups, a combination of your ecommerce platform and an invoicing tool (Invoiced, Stripe Invoicing) is the most practical approach.
When to Use This Skill
- When moving from prepay-only to net-terms for B2B customers to increase conversion
- When different customer segments need different terms (SMB net-30 vs. enterprise net-60)
- When you want to incentivize early payment with discounts to improve cash flow
- When setting up a new wholesale or distribution channel with trade accounts
- When credit losses are rising and you need better credit limit enforcement
Core Instructions
Step 1: Determine your platform and choose the right payment terms tool
| Platform | Recommended Tool | Native Support |
|---|---|---|
| Shopify (Standard) | Invoiced app or Balance app | No native net-terms; use an app |
| Shopify Plus | Shopify B2B (native) + Invoiced for advanced dunning | Net-30/60/90 built into Shopify B2B; credit limits and company accounts native |
| WooCommerce | WooCommerce B2B plugin + WooCommerce PDF Invoices | WooCommerce B2B adds payment terms per customer group |
| BigCommerce | BigCommerce B2B Edition (native) | Net-terms, credit limits, and company accounts built in |
| Custom / Headless | Stripe Invoicing + Stripe Billing for terms enforcement | Stripe Invoicing supports net-30/60/90 natively |
Step 2: Configure payment terms per customer
---
Shopify Plus (B2B native)
1. Go to Shopify Admin → Customers → Companies and create a company profile for each B2B account 2. Click a company and go to Payment methods — set their payment terms:
- Net 7, Net 15, Net 30, Net 60, Net 90
- or a custom number of days
3. Set a Credit limit on the company — orders that would exceed the limit are blocked or sent for approval 4. When a company buyer places an order, Shopify automatically creates an invoice with the correct due date and sends it to the buyer's email 5. The buyer can pay via a link in the invoice email; Shopify marks the order as paid automatically
Early payment discounts on Shopify Plus: Shopify B2B does not natively support "2/10 net-30" early payment discounts. Use Invoiced app for early payment discount terms — configure in Invoiced → Settings → Payment Terms → Early Payment Discount.
Shopify (Standard, via Invoiced app)
1. Install Invoiced from the Shopify App Store 2. Go to Invoiced → Settings → Payment Terms and create your term configurations: Net 30, Net 60, Net 90, 2/10 Net 30 3. In Invoiced → Customers, assign payment terms per customer 4. When a Shopify order is placed by a net-terms customer, Invoiced automatically generates an invoice with the correct due date 5. Configure dunning: go to Invoiced → Settings → Chasing and set up a reminder schedule (1 day before due, 1 day overdue, 7 days overdue, 14 days overdue)
WooCommerce
1. Install WooCommerce B2B plugin (by WooCommerce or a third-party B2B plugin) 2. Go to WooCommerce → B2B Settings → Payment Terms and configure terms per customer role or per customer 3. Install WooCommerce PDF Invoices & Packing Slips to automatically generate invoices with due dates 4. For early payment discounts, you can use a custom pricing rule (WooCommerce Dynamic Pricing plugin) that applies a percentage discount when a coupon code representing the early payment option is used
Credit limit enforcement in WooCommerce: 1. In the B2B plugin settings, set credit limits per customer or customer group 2. Configure the behavior when the limit is exceeded: block the order or notify an admin for approval 3. Credit is automatically released when an invoice is paid
BigCommerce B2B Edition
1. In B2B Edition → Company Management → Payment Methods, enable net-terms 2. In B2B Edition → Companies → [Company] → Credit, set:
- Credit limit
- Payment terms (Net 30, Net 60, etc.)
- Credit status (Approved, Pending, Suspended)
3. When a company buyer places an order, BigCommerce B2B Edition generates an invoice with the configured terms 4. The buyer can view and pay outstanding invoices from their account portal
---
Custom / Headless
Use Stripe Invoicing for net-terms enforcement without building a credit system from scratch:
// Create a customer with payment terms in Stripe
const customer = await stripe.customers.create({
email: customerEmail,
name: companyName,
metadata: { payment_terms: 'net_30', credit_limit: '50000' },
});
// Create an invoice with net-30 terms
const invoice = await stripe.invoices.create({
customer: customer.id,
collection_method: 'send_invoice',
days_until_due: 30, // Net-30
auto_advance: true, // Automatically finalize and send
description: `Invoice for Order ${orderNumber}`,
metadata: { order_id: orderId, po_number: poNumber },
});
// Add line items, then finalize and send
await stripe.invoiceItems.create({ customer: customer.id, invoice: invoice.id, amount: orderTotal, currency: 'usd', description: orderDescription });
await stripe.invoices.finalizeInvoice(invoice.id);
// Stripe auto-sends the invoice and handles dunning reminders via Billing settingsConfigure dunning in Stripe Billing settings: Go to Stripe Dashboard → Billing → Settings → Invoice reminders and configure automatic reminders: 3 days before due, on due date, 3 days after, 7 days after, 14 days after.
Credit limit enforcement (custom):
async function checkCreditAvailability(customerId, orderAmount) {
const customer = await db.customers.findUnique({ where: { id: customerId } });
const openInvoicesTotal = await db.invoices.aggregate({
where: { customerId, status: { in: ['sent', 'overdue', 'partially_paid'] } },
_sum: { amountDue: true },
});
const currentBalance = openInvoicesTotal._sum.amountDue ?? 0;
const availableCredit = customer.creditLimit - currentBalance;
if (orderAmount > availableCredit) {
return { approved: false, availableCredit, shortfall: orderAmount - availableCredit };
}
return { approved: true, availableCredit: availableCredit - orderAmount };
}Step 3: Optimize terms for cash flow and credit risk
The right terms for each customer tier:
| Customer Tier | Recommended Terms | Rationale |
|---|---|---|
| New account (< 3 orders) | Net 15 or prepay | Insufficient payment history to extend credit |
| Established (3–12 months on-time) | Net 30 | Standard B2B terms |
| Strategic (12+ months, large volume) | Net 60 or "2/10 Net 30" | Reward loyalty; early discount improves your cash flow |
| High-risk (2+ late payments) | Prepay or Net 15 only | Protect against bad debt |
Early payment discount economics: "2/10 net-30" means the customer gets a 2% discount if they pay within 10 days instead of 30. For the customer, this is equivalent to borrowing at ~36.7% APR — most customers with any cost of capital should take the discount. For you, paying 2% to receive payment 20 days earlier is typically better than your borrowing cost.
Annual credit review: Review every credit account annually — set a calendar reminder. A customer who qualified for $50,000 credit 2 years ago may look very different today. Reduce limits for customers who have developed slow-pay patterns.
Best Practices
- Start new B2B customers on stricter terms — onboard new accounts at net-30 or net-15 and upgrade after 6 months of on-time payment; never start with net-60 or net-90 without a credit check
- Never ship to accounts on credit hold — enforce credit holds at the order level, not just the invoicing level; once goods leave the warehouse you have lost leverage
- Price early payment discounts correctly — "2/10 net-30" implies an annualized cost of ~36.7% to the customer; communicate this value clearly
- Segment collections intensity by risk tier — high-risk customers need follow-up on day 3 overdue; long-term accounts with a perfect history deserve more grace
- Document every credit decision — store the reason for every credit limit change; you will need this if you ever need to defend a write-off to auditors
Common Pitfalls
| Problem | Solution |
|---|---|
| Customer places an order exceeding their credit limit | Check available credit before order confirmation, not just at invoicing; Shopify Plus B2B and BigCommerce B2B Edition enforce this natively |
| Early payment discounts taken after the discount period | Record the payment date strictly; Invoiced and Stripe Invoicing track this automatically |
| Credit limits not updated as AR balance changes | Use a tool that deducts from available credit when an invoice is created and restores it when paid; Invoiced and Stripe handle this automatically |
| Different departments granting different terms informally | Centralize terms configuration in your platform or AR tool; sales reps should request terms changes through the credit system |
| High bad-debt write-off rate | Implement a credit application process before approving any account for net terms above $5,000 |
Related Skills
- @accounts-receivable-automation
- @invoice-generation-automation
- @payment-reconciliation-automation
- @stripe-integration
{
"context": "Tests whether the credit check implementation enforces credit limits at order creation time (not invoicing), handles all rejection reasons with the correct response shape, reserves credit atomically, and releases it on cancellation. Based on the payment-terms-optimization skill's credit enforcement patterns.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Credit hold rejection",
"max_score": 8,
"description": "When credit_hold is true, returns approved: false with reason 'credit_hold' (exact string)"
},
{
"name": "Credit hold checked first",
"max_score": 8,
"description": "Credit hold is checked before credit_status — a customer on hold with status 'approved' still gets rejected with reason 'credit_hold', not a status error"
},
{
"name": "Non-approved status rejection",
"max_score": 8,
"description": "When credit_status is not 'approved' (e.g. 'pending', 'suspended'), returns approved: false with reason that includes the credit_status value"
},
{
"name": "No credit profile rejection",
"max_score": 8,
"description": "When no credit profile exists for the customer, returns approved: false with reason 'no_credit_profile' and requiresApplication: true"
},
{
"name": "Insufficient credit rejection fields",
"max_score": 10,
"description": "When orderAmount exceeds available_credit, the response includes available_credit, requested amount, and shortfall (the difference)"
},
{
"name": "Credit reservation on approval",
"max_score": 12,
"description": "When credit is approved, available_credit is decremented by orderAmount and current_ar_balance is incremented by orderAmount immediately"
},
{
"name": "Approved response fields",
"max_score": 8,
"description": "Approved response includes payment_terms (the customer's payment_terms_code) and available_credit_after (post-reservation balance)"
},
{
"name": "releaseReservedCredit increments",
"max_score": 10,
"description": "releaseReservedCredit increments available_credit by the amount and decrements current_ar_balance by the same amount"
},
{
"name": "Order-level enforcement (not invoice)",
"max_score": 10,
"description": "The implementation checks and reserves credit at order creation time — credit state is mutated before the function returns, not deferred to a later step"
},
{
"name": "All decision paths tested",
"max_score": 8,
"description": "The test file exercises all rejection reasons: no_credit_profile, credit_hold, non-approved status, insufficient_credit, and a successful approval"
},
{
"name": "Results file present",
"max_score": 10,
"description": "results.txt exists and contains output showing the outcomes of each test case"
}
]
}
B2B Order Credit Gate
Problem/Feature Description
TradeCo Supplies is a wholesale distributor that has just moved from prepay-only to offering net payment terms for its B2B customers. Before this change, every order required upfront payment, so there was no credit risk. Now, approved customers can place orders and pay later — but the engineering team needs to make sure the business doesn't overextend credit.
The head of finance has flagged three recurring problems from the pilot rollout: orders are occasionally being accepted for customers whose accounts have been put on hold due to disputes; some customers with "pending" applications are placing orders before their credit has been approved; and a handful of large orders have been processed that push customers well past their authorized credit ceiling. The root cause is that the current checkout service only checks credit after invoicing, not at order placement — meaning the warehouse ships the goods before the system catches the problem.
Your task is to implement a checkCreditAvailability function and a releaseReservedCredit function for the order service. The functions should handle all the edge cases the finance team identified, and when credit is approved, the system must update the customer's credit state immediately at order creation time (not after invoicing) so that a second simultaneous order cannot exceed the same limit. Also implement a releaseReservedCredit function for when orders are cancelled before confirmation.
Write the implementation as a JavaScript module (credit-check.js) along with a test file (credit-check.test.js) that demonstrates each decision path using in-memory mock data. The test file should print results to stdout so the outcomes are visible.
Output Specification
credit-check.js— The credit check module withcheckCreditAvailabilityandreleaseReservedCreditfunctionscredit-check.test.js— A runnable test/demo script (no test framework required, plain Node.js) that exercises all decision paths and prints the resultsresults.txt— Runnode credit-check.test.js > results.txt 2>&1and save the output
The test script must be runnable with node credit-check.test.js without any npm install (use no external dependencies).
{
"context": "Tests whether the agent implements the early payment discount eligibility check correctly (using issue_date + discount_days as the cutoff) and computes the annualized implied cost of credit using the correct formula from the payment-terms-optimization skill.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Discount cutoff calculation",
"max_score": 12,
"description": "Discount eligibility is determined by comparing paymentDate against (invoice.issue_date + terms.discount_days), not against due_date or any other date"
},
{
"name": "Strictly on or before cutoff",
"max_score": 8,
"description": "Discount applies when paymentDate <= discountCutoffDate (inclusive), and returns null when paymentDate > cutoffDate"
},
{
"name": "Discount amount calculation",
"max_score": 8,
"description": "Discount amount = invoice.total_amount * (discount_pct / 100); amount_due = total_amount - discount_amount"
},
{
"name": "EPD return shape",
"max_score": 8,
"description": "When eligible, the return object includes: eligible: true, discount_pct, discount_amount, amount_due_with_discount, discount_expires"
},
{
"name": "Null for zero-discount terms",
"max_score": 6,
"description": "calculateEarlyPaymentDiscount returns null when the terms have discount_pct === 0 or no discount terms exist"
},
{
"name": "Annualized cost formula",
"max_score": 15,
"description": "computeImpliedCostOfCredit uses the formula: (discountRate / (1 - discountRate)) * (365 / deferralDays) where deferralDays = net_days - discount_days"
},
{
"name": "Deferral days calculation",
"max_score": 8,
"description": "deferralDays is computed as net_days minus discount_days (not just net_days)"
},
{
"name": "2/10 net-30 APR near 36.7%",
"max_score": 12,
"description": "The computed annualized cost for '2_10_net_30' terms is approximately 36.7% (within 0.5% tolerance)"
},
{
"name": "Implied cost return shape",
"max_score": 8,
"description": "computeImpliedCostOfCredit returns an object with terms_code, discount_pct, deferral_days, and annualized_cost_pct (or equivalent_apr)"
},
{
"name": "Results file present",
"max_score": 7,
"description": "results.txt exists and contains output including at least one eligible discount case and the annualized cost for at least two discount-bearing terms"
},
{
"name": "Standard terms included",
"max_score": 8,
"description": "PAYMENT_TERMS configuration includes at minimum 2_10_net_30, 1_10_net_30, and 2_10_net_60"
}
]
}
Early Payment Discount Engine
Problem/Feature Description
FinFlow Wholesale offers several payment terms with early payment discount (EPD) options — for example, a customer on "2/10 net-30" terms can deduct 2% from their invoice if they pay within 10 days, otherwise the full amount is due in 30 days. The CFO wants two things from the engineering team.
First, the accounts receivable team is processing payments manually and keeps accepting discounts from customers who pay after the discount window has closed. The team needs a function that, given an invoice and a proposed payment date, calculates whether a discount applies and what the customer actually owes — so the AR clerk can stop making judgment calls.
Second, the CFO has been trying to convince the sales team that offering 2/10 net-30 is actually very expensive financing for customers who don't take the discount. She wants a utility function that, for any discount-bearing payment terms code, computes the annualized cost of the credit so she can use it in conversations with customers and in the company's pricing model. She suspects that most customers with a reasonable cost of capital should be taking the discount — they're essentially borrowing at an extremely high implied rate if they don't.
Implement both utilities as a single JavaScript module (early-payment.js). Also write a demo script (demo.js) that shows the discount calculation for a few sample invoices and prints the annualized cost for all discount-bearing terms in the system. Run the demo and save the output.
Output Specification
early-payment.js— Module withcalculateEarlyPaymentDiscount(invoice, paymentDate)andcomputeImpliedCostOfCredit(termsCode)functions, plus the PAYMENT_TERMS configuration objectdemo.js— Runnable Node.js script (no external dependencies) that exercises both functions and prints results to stdoutresults.txt— Output of runningnode demo.js, saved to file
The PAYMENT_TERMS configuration should cover at minimum the standard discount-bearing terms. The demo should include at least one case where the discount applies, one where it does not (payment too late), and compute the implied cost for at least two different discount terms.
{
"context": "Tests whether the agent implements the exact risk scoring algorithm (with specific score adjustments for on-time rate and average days late) and the three-tier collections escalation policy with the correct day thresholds and action sequences from the payment-terms-optimization skill.",
"type": "weighted_checklist",
"checklist": [
{
"name": "No-history default",
"max_score": 6,
"description": "A customer with no payment history gets score=50, tier='medium', and recommended terms 'net_30'"
},
{
"name": "On-time rate scoring",
"max_score": 10,
"description": "Score adjustment for on_time_pct: +25 if >=0.95, +10 if >=0.80 (but <0.95), -10 otherwise"
},
{
"name": "Avg days late scoring",
"max_score": 10,
"description": "Score adjustment for avgDaysLate: +15 if <=3, +5 if <=10 (but >3), -15 otherwise"
},
{
"name": "Payment count scoring",
"max_score": 8,
"description": "Score adjustment for totalPayments: +10 if >=12, +5 if >=6 (but <12), +0 otherwise"
},
{
"name": "Score clamped 0-100",
"max_score": 5,
"description": "Final score is clamped between 0 and 100 inclusive"
},
{
"name": "Risk tier thresholds",
"max_score": 8,
"description": "Tier assignment: 'low' for score >=75, 'medium' for score >=50 (but <75), 'high' for score <50"
},
{
"name": "Terms recommendations",
"max_score": 10,
"description": "Low risk -> 'net_60' with limit_multiplier 3.0; medium risk -> 'net_30' with limit_multiplier 1.5; high risk -> 'net_15' with limit_multiplier 0.75"
},
{
"name": "Low-risk collections steps",
"max_score": 8,
"description": "Low risk collections policy: reminder_email at 5 days, second_notice at 15 days, account_manager_call at 30 days, credit_hold at 45 days"
},
{
"name": "Medium-risk collections steps",
"max_score": 8,
"description": "Medium risk collections policy: reminder_email at 3 days, second_notice at 10 days, credit_hold at 20 days, collections_referral at 45 days"
},
{
"name": "High-risk collections steps",
"max_score": 8,
"description": "High risk collections policy: reminder_email at 1 day, credit_hold at 7 days, collections_referral at 21 days"
},
{
"name": "Already-taken actions skipped",
"max_score": 10,
"description": "getNextCollectionsAction does not return an action that appears in the actionsTaken list — it finds the next untaken action at or past the daysOverdue threshold"
},
{
"name": "Results file present",
"max_score": 9,
"description": "results.txt exists and shows scoring results for at least 3 different customer profiles and collections escalation for at least 2 risk tiers"
}
]
}
Automated Credit Scoring and Collections Escalation
Problem/Feature Description
Meridian Distribution has been extending net payment terms to B2B customers for two years. The credit manager currently reviews all accounts manually — a process that doesn't scale as the customer base has grown to several hundred accounts. Two problems have emerged: the team treats every overdue account the same way regardless of the customer's track record (sending aggressive collection emails to 20-year accounts that are 4 days late), and new accounts are being given overly generous terms because the sales reps are making judgment calls without a consistent methodology.
The engineering team has been asked to build two things: (1) an automated credit scoring function that analyzes a customer's 12-month payment history and outputs a risk score, risk tier, and recommended payment terms and credit limit multiplier, and (2) a collections escalation engine that, given a customer's risk tier and the number of days an invoice is overdue, determines what action should be taken next.
Implement these as a JavaScript module (credit-risk.js). The module should export a scoreCustomerCredit(customerId, paymentHistoryRecords) function that takes in payment history data directly (no database calls — accept the records as a parameter) and returns the scoring result. Also export a getNextCollectionsAction(riskTier, daysOverdue, actionsTaken) function that returns the next action to take given the overdue days and what has already been done, or null if no further action is warranted yet.
Write a demo/test script (demo.js) that runs several representative scenarios through both functions and prints the outcomes. Save the output to results.txt.
Output Specification
credit-risk.js— Module exportingscoreCustomerCreditandgetNextCollectionsActiondemo.js— Runnable Node.js script (no external dependencies) covering multiple customer risk profiles and overdue scenariosresults.txt— Output ofnode demo.js, saved to file
The demo should cover: a new customer with no history, a customer with excellent payment history, a customer with poor history, and collections escalation examples for all three risk tiers at various days-overdue values.
{
"name": "finsi/payment-terms-optimization",
"version": "0.1.0",
"summary": "Configure flexible payment terms for B2B customers with net-30/60/90 options, early payment discounts, credit limit management, and automated collections",
"skills": {
"payment-terms-optimization": {
"path": "SKILL.md"
}
}
}