Upgrade
The logic for upgrading customers lives within Stripe. You will first need to upgrade the customer within Stripe, and then Loop will ingest the updated and finalized invoice based on the upgrade that is trigged. When a customer is upgraded, there will likely be a prorated amount, where the customer is charged a percentage of a subscription’s cost to reflect partial use. Stripe will calculate this amount based on the rules you've configured in Stripe.
Charging immediately for prorated amounts
When you direct Stripe to bill immediately, it will create a draft that will not be finalized for 1 hour and the due date will be the end of the business day. You can, however, change the due date and finalization time. You can change the due_date
on the invoice to the current timestamp with this method and force an invoice to finalize immediately with this method. Also, best practice is to set the due_date
to now + 2 seconds
to account for any processing time on Stripe's side (invoices cannot be due in the past). Be sure to keep automatic_collection:false
so Loop can collect on the payment.
Once an invoice is finalized, Loop will immediately attempt to charge the customer. Stripe will mark the invoice as 'due' until it is paid onchain. This could take several minutes depending on the chain.
Payment Method Issues
There are two reasons that an invoice will remain open and unpaid
Issue | Resolution |
---|---|
Low balance | Customer needs to increase their token balance |
Low authorization | Customer needs to increase their authorization |
Loop will send the customer a "Missed Payment" email 5 minutes after the payment is due but not paid. Included in this email is an indication of why the payment was missed (e.g. low balance or low authorization) and a link to the Loop customer portal where the customer can increase their authorization.
Additionally, a late payment webhook 10 minutes after the payment is due but not paid.
Heading off payment issues
To avoid payment issues, you can query the wallet's balance and allowance and provide resolutions directly on your frontend.
Query wallets
You can retrieve from Loop the payment wallet, network, and token by calling the GET Agreements endpoint and passing in the externalSubscriptionId
.
Low balance
You can query a wallet's balance before you upgrade a customer to ensure they have the correct amount.
Low authorization
You can query a wallet's authorization amount before you upgrade a customer to ensure they have enough. You can do this, by querying the allowance
function of the payment token's smart contract on any EVM chain and the delegate amount on Solana (directions for this below)
function allowance(address owner, address spender) external view returns (uint256)
Input Description Where to find Token contract This is the address of the ERC-20 token contract Networks & tokens page Spender This is the address of your Loop smart contract Developer page on your dashboard or the GET Entities endpoint
Sample Ethers.js code
const { ethers } = require("ethers");
// Set up your provider (e.g., Infura, Alchemy, etc.)
const provider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID");
// Token contract address
const tokenAddress = '0xTokenAddressHere';
// Spender address
const spenderAddress = '0xSpenderAddressHere';
// Wallet address (owner of the tokens)
const walletAddress = '0xYourWalletAddressHere';
// ERC-20 ABI with allowance function
const tokenABI = [
"function allowance(address owner, address spender) view returns (uint256)"
];
// Create a contract instance
const tokenContract = new ethers.Contract(tokenAddress, tokenABI, provider);
// Get the allowance
async function checkAllowance() {
try {
const allowance = await tokenContract.allowance(walletAddress, spenderAddress);
console.log(`Allowance: ${ethers.utils.formatUnits(allowance, <token decimals>)} tokens`);
} catch (error) {
console.error('Error:', error);
}
}
checkAllowance();
Sample code on Solana
import {
getAccount,
getAssociatedTokenAddress,
Connection
} from "@solana/spl-token";
import {
PublicKey,
} from "@solana/web3.js";
const provider = new Connection(`${rpcUrl}/${providerApiKey}`);
const tokenAccount = await getAssociatedTokenAddress(
new PublicKey(tokenAddress),
new PublicKey(walletAddress)
);
const accountInfo = await getAccount(
provider,
tokenAccount
);
const delegatedAmount = acountInfo.delegatedAmount;
You can query a wallet's authorization amount before you upgrade a customer to ensure they have enough. You can do this, by querying the allowance
function of the payment token's smart contract on any EVM chain and the delegate amount on Solana (directions for this below)
function allowance(address owner, address spender) external view returns (uint256)
Input | Description | Where to find |
---|---|---|
Token contract | This is the address of the ERC-20 token contract | Networks & tokens page |
Spender | This is the address of your Loop smart contract | Developer page on your dashboard or the GET Entities endpoint |
Sample Ethers.js code
const { ethers } = require("ethers");
// Set up your provider (e.g., Infura, Alchemy, etc.)
const provider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID");
// Token contract address
const tokenAddress = '0xTokenAddressHere';
// Spender address
const spenderAddress = '0xSpenderAddressHere';
// Wallet address (owner of the tokens)
const walletAddress = '0xYourWalletAddressHere';
// ERC-20 ABI with allowance function
const tokenABI = [
"function allowance(address owner, address spender) view returns (uint256)"
];
// Create a contract instance
const tokenContract = new ethers.Contract(tokenAddress, tokenABI, provider);
// Get the allowance
async function checkAllowance() {
try {
const allowance = await tokenContract.allowance(walletAddress, spenderAddress);
console.log(`Allowance: ${ethers.utils.formatUnits(allowance, <token decimals>)} tokens`);
} catch (error) {
console.error('Error:', error);
}
}
checkAllowance();
Sample code on Solana
import {
getAccount,
getAssociatedTokenAddress,
Connection
} from "@solana/spl-token";
import {
PublicKey,
} from "@solana/web3.js";
const provider = new Connection(`${rpcUrl}/${providerApiKey}`);
const tokenAccount = await getAssociatedTokenAddress(
new PublicKey(tokenAddress),
new PublicKey(walletAddress)
);
const accountInfo = await getAccount(
provider,
tokenAccount
);
const delegatedAmount = acountInfo.delegatedAmount;
Resolutions
To increase the approval amount, you can construct the transaction yourself for the customer to increase their authorization on your site or you can direct them to the Loop customer portal where an end payer can manage their authorization and see the token balance of their payment token. When a subscription is updated in Stripe, that update will be reflected immediately on the Loop customer portal.
Increasing the authorization
You can call the approve
function to increase the authorization. This function must be signed by the wallet that is setting this allowance.
function approve(address spender, uint256 amount) external returns (bool);
On Solana, the equivalent of the approve
function in Ethereum is done via the approve
instruction in the SPL Token Program. You authorize an account
(the spender) to transfer a certain amount of tokens from your token account.
const {
Connection,
PublicKey,
Keypair,
Transaction,
sendAndConfirmTransaction,
} = require('@solana/web3.js');
const { Token, TOKEN_PROGRAM_ID } = require('@solana/spl-token');
async function increaseTokenApproval() {
// Set up your connection to Solana's mainnet
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
// Your wallet's keypair (Replace with your actual keypair)
const wallet = Keypair.fromSecretKey(new Uint8Array([/* your private key array */]));
// Token mint address for the SPL Token (replace with the token you want to use)
const tokenMintAddress = new PublicKey('YourTokenMintAddressHere');
// The token account where the tokens are stored
const fromTokenAccount = new PublicKey('YourTokenAccountAddressHere');
// Spender account (the account you're authorizing to spend tokens)
const spenderAddress = new PublicKey('SpenderAccountAddressHere');
// New approval amount (in token's smallest unit, e.g., 1000 tokens for a token with 9 decimals)
const amountToApprove = 1000 * Math.pow(10, 9); // For tokens with 9 decimals
// Create the Token object for the mint address
const token = new Token(connection, tokenMintAddress, TOKEN_PROGRAM_ID, wallet);
// Create the approve instruction
const approveIx = Token.createApproveInstruction(
TOKEN_PROGRAM_ID, // SPL Token Program
fromTokenAccount, // Source token account
spenderAddress, // Spender account
wallet.publicKey, // Owner of the token account
[], // Multisig signers (if any)
amountToApprove // Amount to approve
);
// Create the transaction
const transaction = new Transaction().add(approveIx);
// Send the transaction
try {
const signature = await sendAndConfirmTransaction(connection, transaction, [wallet]);
console.log('Transaction confirmed with signature:', signature);
} catch (error) {
console.error('Error sending transaction:', error);
}
}
increaseTokenApproval();
Updated 10 days ago