To use this as a scripter, simply:
a) Place it in your item, making sure this script has full perms.
b) Whenever your script moves into a state which has a money() event, call llMessageLinked(LINK_THIS,250002,"",NULL_KEY);
c) Whenever your script moves into a state which does not have a money() event, call llMessageLinked(LINK_THIS,250003,"",NULL_KEY);
( b) and c) prevent the money() event in the script below from making a bogus "pay" option appear on the menu )
d) In your money() event, as the very first line, insert llMessageLinked(LINK_THIS,250004,"",NULL_KEY);
( d) is a failsafe if somehow a bogus "pay" item does show up - if your script doesn't acknowledge that it was expecting the "money" event when a payment is made, payments will be frozen )
e) Whenever your script calls for debit permission, remove the llRequestPermissions call and instead call llMessageLinked(LINK_THIS,250001,"",NULL_KEY);
f) Whenever your script gives out money, remove the llGiveMoney(target, amount); call and instead call llMessageLinked(LINK_THIS,250000,(string)amount,target);
You will also need to add a link_message handler that deals with messages with the following int values:
250007 - Debit permission was granted (equivalent to run_time_permissions with DEBIT set)
250008 - Debit permission was denied (equivalent to run_time_permissions with DEBIT unset)
250009 - "Pay" menu item enabled
250010 - "Pay" menu item disabled; if this happens unexpectedly the script may also have been reset - ask for debit permission again (if the script already has debit permission it won't ask the user again)
You may also wish to handle the following link messages:
250005 - A payment succeeded
250006 - A payment failed; string part of the linked message gives the reason; this should not happen on production scripts
250011 - Payment rejected because 250004 was not sent since the last payment; this should not happen on production scripts
CODE
// Trusted Money Script by Yumi Murakami
// This script is intended to help you trust this object not to steal your money.
// This script should be the only part of this object which recieves debit permission.
// Since this script is open source, you (or a scripter friend) can confirm that this
// script cannot give out more money than it has taken in, and thus neither can the
// object it is within.
// Note: although this script attempts to ensure your trust is not breached, no
// guarantee nor warranty is offered. You must agree that neither Yumi Murakami nor
// anyone else is liable if an item using this script still nonetheless does steal your
// money.
// If you trust the item this script is in, please go ahead and use it.
// If you do not trust the item, first, DELETE all the text in this script. Then go to
// http://forums.secondlife.com/showthread.php?p=1374429
// and download the reference copy of this script, which you should paste into this
// window.
// Then, set the 'secret' string below, by modifying the text between the " marks. Make
// sure not to modify any other part of the line. For example, you could change it to:
// string secret = "Badgers walk the earth";
// Then, click on "save".
// When the object asks for debit permission, it should do so only once and will display
// your secret immediately before doing so. This should enable you to know that it is
// this script, not any other part of the object, that is asking for debit permission -
// because the creator of the other parts of the object would have no way of knowing what
// you would choose to enter as your secret string.
// If the item requests debit permission more than once, or does not display your
// secret first, refuse the debit permission request and delete the object.
string secret = "Secret not set. (If you trust this object you don't need to set a secret.)";
// If you want the object to run in test mode, where any money paid in is immediately paid back
// and no money is actually paid out, change the line below to:
// integer test = TRUE;
// And then click "save". To return to normal mode, change the line to:
// integer test = FALSE;
integer test = FALSE;
// Link message commands for this script
integer PAY_COMMAND = 250000; // Command to pay out: string = L$, key = payee
integer REQUEST_PERM_COMMAND = 250001; // Command to try permissions request again
integer ENABLE_PAYMENT_COMMAND = 250002; // Command to enable payments
integer DISABLE_PAYMENT_COMMAND = 250003; // Command to disable payments
integer ACK_PAYMENT = 250004; // Acknowledge payments
integer PAY_SUCCESS = 250005; // Returned if pay succeeded
integer PAY_FAILED = 250006; // Returned if pay failed: string = reason
integer PERM_GRANTED = 250007; // Returned if permission was granted by user
integer PERM_DENIED = 250008; // Returned if permission was denied by user
integer PAYMENTS_ENABLED = 250009; // Returned if payments are enabled
integer PAYMENTS_DISABLED = 250010; // Returned if payments are disabled
integer PAYMENT_PENDING = 250011; // Return if command was refused because a payment is pending
integer got_permission = FALSE; // Do we have debit permission?
integer pending_payack = FALSE; // Payment acknowledgement pending?
integer credit = 0; // Amount of credit
key credit_owner; // Owner of credit
// (This prevent somebody running up a big credit by paying money to themselves, then
// giving the object to you and letting the "credit" be paid out of your account)
checkOwner() {
if (credit_owner != llGetOwner()) { // If not still owned by credit owner
credit = 0; // Clear credit
credit_owner = llGetOwner(); // And credit owner is now object owner
got_permission = FALSE; // Need permission from the new owner
}
}
payCommand(string str_msg, key key_msg) {
checkOwner();
if (!got_permission) {
if (test) llSay(0,"TEST MODE: Tried to pay L$" + str_msg + " to " + llKey2Name(key_msg) + " without debit permission.");
llMessageLinked(LINK_THIS,PAY_FAILED,"Permission denied",NULL_KEY);
return;
}
integer amount = (integer)str_msg;
if (((string)amount) != str_msg) {
if (test) llSay(0,"TEST MODE: Tried to play mangled amount, '" + str_msg + "'.");
llMessageLinked(LINK_THIS,PAY_FAILED,"Bad amount: " + str_msg,NULL_KEY);
return;
}
if (amount > credit) {
if (test) llSay(0,"TEST MODE: Tried to pay L$" + str_msg + " to " + llKey2Name(key_msg) + " but insufficient credit (credit only L$" + (string)credit + ".)");
llMessageLinked(LINK_THIS,PAY_FAILED,"Insufficient credit",NULL_KEY);
return;
}
credit -= amount;
if (test) {
llSay(0,"TEST MODE: Virtually paying L$" + (string)amount + " to " + llKey2Name(key_msg) + ". (Virtual credit is L$" + (string)credit + ".)");
} else {
llGiveMoney(key_msg,amount);
}
llMessageLinked(LINK_THIS,PAY_SUCCESS,"",NULL_KEY);
}
requestPermCommand() {
checkOwner();
if (got_permission) {
llMessageLinked(LINK_THIS,PERM_GRANTED,"",NULL_KEY);
return;
}
llOwnerSay("I am about to ask for debit permission.");
llOwnerSay("Please wait for a moment.");
llSleep( 2+((integer)(llFrand(21.0))) );
llOwnerSay("Only one request for debit permission should be made.");
llOwnerSay("No other permissions should be requested at the same time.");
llOwnerSay("Your secret is: " + secret);
llOwnerSay("The debit permission dialog is about to appear.");
llRequestPermissions(llGetOwner(),PERMISSION_DEBIT);
llOwnerSay("The debit permission dialog should have just appeared.");
}
default
{
// Always returns to zero credit when script or object is reset.
state_entry() {
got_permission = FALSE;
credit = 0;
credit_owner = llGetOwner();
pending_payack = FALSE;
state nopayments;
}
// Credit value retained through re-rezzing, as long as owner is same.
on_rez(integer junk) {
got_permission = FALSE;
checkOwner();
state nopayments;
}
}
state nopayments {
state_entry() {
llMessageLinked(LINK_THIS,PAYMENTS_DISABLED,"",NULL_KEY);
}
on_rez(integer junk) {
got_permission = FALSE;
checkOwner();
}
run_time_permissions(integer permissions) {
if (permissions & PERMISSION_DEBIT) {
got_permission = TRUE;
llOwnerSay("You have granted debit permissions.");
llMessageLinked(LINK_THIS,PERM_GRANTED,"",NULL_KEY);
} else {
llOwnerSay("You have denied debit permissions.");
llMessageLinked(LINK_THIS,PERM_DENIED,"",NULL_KEY);
}
}
link_message(integer sender, integer int_msg, string str_msg, key key_msg) {
if (sender == llGetLinkNumber()) {
if (int_msg == PAY_COMMAND) {
payCommand(str_msg,key_msg);
return;
}
if (int_msg == REQUEST_PERM_COMMAND) {
requestPermCommand();
return;
}
if (int_msg == ENABLE_PAYMENT_COMMAND) {
state payments;
}
if (int_msg == DISABLE_PAYMENT_COMMAND) {
llMessageLinked(LINK_THIS,PAYMENTS_DISABLED,"",NULL_KEY);
}
}
}
}
state payments {
money(key giver, integer amount) {
// We know 'amount' has been paid to current owner now, but check past credit
checkOwner();
// If a previous payment still hasn't been acknowledged by master, return money
if (pending_payack) {
llSay(0,"ERROR: Money returned because last payment wasn't properly acknowledged.");
llGiveMoney(giver,amount);
return;
}
credit += amount;
pending_payack = TRUE;
if (test) {
llSay(0,"TEST MODE: Repaying L$" + (string)amount + " payment. (Virtual credit is L$" + (string)credit + ".)");
llGiveMoney(giver,amount);
}
}
state_entry() {
llMessageLinked(LINK_THIS,PAYMENTS_ENABLED,"",NULL_KEY);
}
on_rez(integer junk) {
got_permission = FALSE;
checkOwner();
}
run_time_permissions(integer permissions) {
if (permissions & PERMISSION_DEBIT) {
got_permission = TRUE;
llOwnerSay("You have granted debit permissions.");
llMessageLinked(LINK_THIS,PERM_GRANTED,"",NULL_KEY);
} else {
llOwnerSay("You have denied debit permissions.");
llMessageLinked(LINK_THIS,PERM_DENIED,"",NULL_KEY);
}
}
link_message(integer sender, integer int_msg, string str_msg, key key_msg) {
if (sender == llGetLinkNumber()) {
if (int_msg == ACK_PAYMENT) {
pending_payack = FALSE;
}
if (pending_payack && (int_msg != PAYMENT_PENDING)) {
llMessageLinked(LINK_THIS,PAYMENT_PENDING,"",NULL_KEY);
return;
}
if (int_msg == PAY_COMMAND) {
payCommand(str_msg,key_msg);
return;
}
if (int_msg == REQUEST_PERM_COMMAND) {
requestPermCommand();
return;
}
if (int_msg == ENABLE_PAYMENT_COMMAND) {
llMessageLinked(LINK_THIS,PAYMENTS_ENABLED,"",NULL_KEY);
}
if (int_msg == DISABLE_PAYMENT_COMMAND) {
state nopayments;
}
}
}
}
I'd welcome any comments or improvements

Edit 25-5-07: add a random delay when asking for debit permission, to prevent the untrusted script popping up a debit permission dialog immediately after sending the "request perms" call. Although it will be suspicious since two dialogs will appear, this doesn't help if the user doesn't notice one of them appear, the untrusted script happens to get there first, and the user's L$ are already stolen by the time they see the second one.