UnityのIAPButtonには以下の2つの問題があります。

(1) たまにボタンを押しても反応しない
(2) 連打すると複数の決済が走る

(1)が起きる場合、IAPButton.IAPButtonStoreManager.InitiatePurchaseにおいて、controllerがnullになっており、Null Pointer Exceptionが発生しています。これは、IAPButtonがActiveになったタイミングでストアへの問い合わせが発生しており、問い合わせが完了するまでIAPButtonが有効でないことに起因しています。しかし、このエラーを通知するコールバックは存在しないため、IAPButton.csを書き換えず、クライアントからcontroller==nullを検出するには、GetProductの戻り値をnullチェックするのが良さそうです。

(2)は、Editorでは即時にストアが立ち上がるのですが、iOSではストアの立ち上がりが遅延するため、その間にもう一回同じボタンが押せてしまうのが問題です。IAPButtonにはonPurchaseCompleteもしくはonPurchaseFailedしかなく、onPurchaseStartが存在しません。IAPButton.csを書き換えず、クライアントから購入の開始を検出するには、Button側にAddListenerすると良さそうです。ButtonのonClickでis_purchasingフラグを設定して、他のUIを無効化するアプローチです。尚、その際、(1)の対策をしていないと、異常系で全くUIが押せなくなり、悲惨なことになるので注意です。

以上を踏まえて、以下のような実装が良さそうです。

button.GetComponent<Button>().onClick.AddListener(
	() => {
		if(UnityEngine.Purchasing.IAPButton.IAPButtonStoreManager.Instance.GetProduct(cash.GetComponent<UnityEngine.Purchasing.IAPButton>().productId)==null){
			//ストア情報の取得に失敗するなど、IAPの初期化に失敗した場合、
			//PurchaseFailureEventが呼ばれないまま、NullPointerExceptionになるので、
			//自前でケアする必要がある
			invalid_state(info.StoreID);
		}else{
			//ボタンの連打を防ぐ
			button.GetComponent<Button>().interactable=false;
			is_purchasing=true;
		}
	}
);

button.gameObject.GetComponent<UnityEngine.Purchasing.IAPButton>().onPurchaseComplete.AddListener(
(UnityEngine.Purchasing.Product product) => {
	success(product);
}
);

button.gameObject.GetComponent<UnityEngine.Purchasing.IAPButton>().onPurchaseFailed.AddListener(
(UnityEngine.Purchasing.Product product,UnityEngine.Purchasing.PurchaseFailureReason reason) => {
	button.GetComponent<Button>().interactable=true;	//購入キャンセルした場合はボタンを有効化
	is_purchasing=false;
	
	failed(product,reason);
}
);


尚、アプリ決済直後にアプリを終了した場合、onPurchaseCompleteが呼ばれませんが、次回のアプリ起動時、IAPButtonがActiveになった際に、onPurchaseCompleteが呼ばれます。ただし、IAPButton.csのPurchaseProcessingResultはデフォルトで以下のような実装になっています。TODOに記載されているように、このままの実装では、決済に失敗したIAPButtonがInactiveの際に、別のIAPButtonを表示した場合に、復帰処理が正しく行われず、IAPButtonに設定したPurchaseCompleteコールバックが呼ばれないまま、IAPがCOMPLETEしてしまいます。

public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
	foreach (var button in activeButtons) {
		if (button.productId == e.purchasedProduct.definition.id) {
			return button.ProcessPurchase(e);
		}
	}
	return PurchaseProcessingResult.Complete; // TODO: Maybe this shouldn't return complete
}

この問題を回避するために、PurchaseProcessingResult.Pendingを返すことで、決済の復帰シーケンスで対象のIAPButtonが有効になる前にCompleteが呼ばれるのを防止した方がよいです。Pendingを返しておくと、次回のアプリ起動時に、再度、決済の終了処理が走ります。

public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
	foreach (var button in activeButtons) {
		if (button.productId == e.purchasedProduct.definition.id) {
			return button.ProcessPurchase(e);
		}
	}
	return PurchaseProcessingResult.Pending;
}