Android Open Source - bitfynd-wallet-android Payment Intent






From Project

Back to project page bitfynd-wallet-android.

License

The source code is released under:

GNU General Public License

If you think the Android project bitfynd-wallet-android listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2014 the original author or authors.
 */*from  w w w .j  a v  a  2s  . co  m*/
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.schildbach.wallet.data;

import static com.google.common.base.Preconditions.checkArgument;

import java.util.Arrays;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ScriptException;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Wallet.SendRequest;
import org.bitcoinj.core.WrongNetworkException;
import org.bitcoinj.protocols.payments.PaymentProtocol;
import org.bitcoinj.protocols.payments.PaymentProtocolException;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.uri.BitcoinURI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import android.os.Parcel;
import android.os.Parcelable;

import com.google.common.io.BaseEncoding;

import de.schildbach.wallet.Constants;
import de.schildbach.wallet.util.Bluetooth;
import de.schildbach.wallet.util.GenericUtils;

/**
 * @author Andreas Schildbach
 */
public final class PaymentIntent implements Parcelable
{
  public enum Standard
  {
    BIP21, BIP70
  }

  public final static class Output implements Parcelable
  {
    public final Coin amount;
    public final Script script;

    public Output(final Coin amount, final Script script)
    {
      this.amount = amount;
      this.script = script;
    }

    public static Output valueOf(final PaymentProtocol.Output output) throws PaymentProtocolException.InvalidOutputs
    {
      try
      {
        final Script script = new Script(output.scriptData);
        return new PaymentIntent.Output(output.amount, script);
      }
      catch (final ScriptException x)
      {
        throw new PaymentProtocolException.InvalidOutputs("unparseable script in output: " + Arrays.toString(output.scriptData));
      }
    }

    public boolean hasAmount()
    {
      return amount != null && amount.signum() != 0;
    }

    @Override
    public String toString()
    {
      final StringBuilder builder = new StringBuilder();

      builder.append(((Object) this).getClass().getSimpleName());
      builder.append('[');
      builder.append(hasAmount() ? amount.toPlainString() : "null");
      builder.append(',');
      if (script.isSentToAddress() || script.isPayToScriptHash())
        builder.append(script.getToAddress(Constants.NETWORK_PARAMETERS));
      else if (script.isSentToRawPubKey())
        for (final byte b : script.getPubKey())
          builder.append(String.format("%02x", b));
      else if (script.isSentToMultiSig())
        builder.append("multisig");
      else
        builder.append("unknown");
      builder.append(']');

      return builder.toString();
    }

    @Override
    public int describeContents()
    {
      return 0;
    }

    @Override
    public void writeToParcel(final Parcel dest, final int flags)
    {
      dest.writeSerializable(amount);

      final byte[] program = script.getProgram();
      dest.writeInt(program.length);
      dest.writeByteArray(program);
    }

    public static final Parcelable.Creator<Output> CREATOR = new Parcelable.Creator<Output>()
    {
      @Override
      public Output createFromParcel(final Parcel in)
      {
        return new Output(in);
      }

      @Override
      public Output[] newArray(final int size)
      {
        return new Output[size];
      }
    };

    private Output(final Parcel in)
    {
      amount = (Coin) in.readSerializable();

      final int programLength = in.readInt();
      final byte[] program = new byte[programLength];
      in.readByteArray(program);
      script = new Script(program);
    }
  }

  @CheckForNull
  public final Standard standard;

  @CheckForNull
  public final String payeeName;

  @CheckForNull
  public final String payeeVerifiedBy;

  @CheckForNull
  public final Output[] outputs;

  @CheckForNull
  public final String memo;

  @CheckForNull
  public final String paymentUrl;

  @CheckForNull
  public final byte[] payeeData;

  @CheckForNull
  public final String paymentRequestUrl;

  @CheckForNull
  public final byte[] paymentRequestHash;

  private static final Logger log = LoggerFactory.getLogger(PaymentIntent.class);

  public PaymentIntent(@Nullable final Standard standard, @Nullable final String payeeName, @Nullable final String payeeVerifiedBy,
      @Nullable final Output[] outputs, @Nullable final String memo, @Nullable final String paymentUrl, @Nullable final byte[] payeeData,
      @Nullable final String paymentRequestUrl, @Nullable final byte[] paymentRequestHash)
  {
    this.standard = standard;
    this.payeeName = payeeName;
    this.payeeVerifiedBy = payeeVerifiedBy;
    this.outputs = outputs;
    this.memo = memo;
    this.paymentUrl = paymentUrl;
    this.payeeData = payeeData;
    this.paymentRequestUrl = paymentRequestUrl;
    this.paymentRequestHash = paymentRequestHash;
  }

  private PaymentIntent(@Nonnull final Address address, @Nullable final String addressLabel)
  {
    this(null, null, null, buildSimplePayTo(Coin.ZERO, address), addressLabel, null, null, null, null);
  }

  public static PaymentIntent blank()
  {
    return new PaymentIntent(null, null, null, null, null, null, null, null, null);
  }

  public static PaymentIntent fromAddress(@Nonnull final Address address, @Nullable final String addressLabel)
  {
    return new PaymentIntent(address, addressLabel);
  }

  public static PaymentIntent fromAddress(@Nonnull final String address, @Nullable final String addressLabel) throws WrongNetworkException,
      AddressFormatException
  {
    return new PaymentIntent(new Address(Constants.NETWORK_PARAMETERS, address), addressLabel);
  }

  public static PaymentIntent fromBitcoinUri(@Nonnull final BitcoinURI bitcoinUri)
  {
    final Address address = bitcoinUri.getAddress();
    final Output[] outputs = address != null ? buildSimplePayTo(bitcoinUri.getAmount(), address) : null;
    final String bluetoothMac = (String) bitcoinUri.getParameterByName(Bluetooth.MAC_URI_PARAM);
    final String paymentRequestHashStr = (String) bitcoinUri.getParameterByName("h");
    final byte[] paymentRequestHash = paymentRequestHashStr != null ? base64UrlDecode(paymentRequestHashStr) : null;

    return new PaymentIntent(PaymentIntent.Standard.BIP21, null, null, outputs, bitcoinUri.getLabel(), bluetoothMac != null ? "bt:"
        + bluetoothMac : null, null, bitcoinUri.getPaymentRequestUrl(), paymentRequestHash);
  }

  private static final BaseEncoding BASE64URL = BaseEncoding.base64Url().omitPadding();

  private static byte[] base64UrlDecode(final String encoded)
  {
    try
    {
      return BASE64URL.decode(encoded);
    }
    catch (final IllegalArgumentException x)
    {
      log.info("cannot base64url-decode: " + encoded);
      return null;
    }
  }

  public PaymentIntent mergeWithEditedValues(@Nullable final Coin editedAmount, @Nullable final Address editedAddress)
  {
    final Output[] outputs;

    if (hasOutputs())
    {
      if (mayEditAmount())
      {
        checkArgument(editedAmount != null);

        // put all coins on first output, skip the others
        outputs = new Output[] { new Output(editedAmount, this.outputs[0].script) };
      }
      else
      {
        // exact copy of outputs
        outputs = this.outputs;
      }
    }
    else
    {
      checkArgument(editedAmount != null);
      checkArgument(editedAddress != null);

      // custom output
      outputs = buildSimplePayTo(editedAmount, editedAddress);
    }

    return new PaymentIntent(standard, payeeName, payeeVerifiedBy, outputs, memo, null, payeeData, null, null);
  }

  public SendRequest toSendRequest()
  {
    final Transaction transaction = new Transaction(Constants.NETWORK_PARAMETERS);
    for (final PaymentIntent.Output output : outputs)
      transaction.addOutput(output.amount, output.script);
    return SendRequest.forTx(transaction);
  }

  private static Output[] buildSimplePayTo(final Coin amount, final Address address)
  {
    return new Output[] { new Output(amount, ScriptBuilder.createOutputScript(address)) };
  }

  public boolean hasPayee()
  {
    return payeeName != null;
  }

  public boolean hasOutputs()
  {
    return outputs != null && outputs.length > 0;
  }

  public boolean hasAddress()
  {
    if (outputs == null || outputs.length != 1)
      return false;

    final Script script = outputs[0].script;
    return script.isSentToAddress() || script.isPayToScriptHash() || script.isSentToRawPubKey();
  }

  public Address getAddress()
  {
    if (!hasAddress())
      throw new IllegalStateException();

    final Script script = outputs[0].script;
    return script.getToAddress(Constants.NETWORK_PARAMETERS, true);
  }

  public boolean mayEditAddress()
  {
    return standard == null;
  }

  public boolean hasAmount()
  {
    if (hasOutputs())
      for (final Output output : outputs)
        if (output.hasAmount())
          return true;

    return false;
  }

  public Coin getAmount()
  {
    Coin amount = Coin.ZERO;

    if (hasOutputs())
      for (final Output output : outputs)
        if (output.hasAmount())
          amount = amount.add(output.amount);

    if (amount.signum() != 0)
      return amount;
    else
      return null;
  }

  public boolean mayEditAmount()
  {
    return !(standard == Standard.BIP70 && hasAmount());
  }

  public boolean hasPaymentUrl()
  {
    return paymentUrl != null;
  }

  public boolean isSupportedPaymentUrl()
  {
    return isHttpPaymentUrl() || isBluetoothPaymentUrl();
  }

  public boolean isHttpPaymentUrl()
  {
    return paymentUrl != null
        && (GenericUtils.startsWithIgnoreCase(paymentUrl, "http:") || GenericUtils.startsWithIgnoreCase(paymentUrl, "https:"));
  }

  public boolean isBluetoothPaymentUrl()
  {
    return Bluetooth.isBluetoothUrl(paymentUrl);
  }

  public boolean hasPaymentRequestUrl()
  {
    return paymentRequestUrl != null;
  }

  public boolean isSupportedPaymentRequestUrl()
  {
    return isHttpPaymentRequestUrl() || isBluetoothPaymentRequestUrl();
  }

  public boolean isHttpPaymentRequestUrl()
  {
    return paymentRequestUrl != null
        && (GenericUtils.startsWithIgnoreCase(paymentRequestUrl, "http:") || GenericUtils.startsWithIgnoreCase(paymentRequestUrl, "https:"));
  }

  public boolean isBluetoothPaymentRequestUrl()
  {
    return Bluetooth.isBluetoothUrl(paymentRequestUrl);
  }

  /**
   * Check if given payment intent is only extending on <i>this</i> one, that is it does not alter any of the fields.
   * Address and amount fields must be equal, respectively (non-existence included).
   * 
   * Alternatively, a BIP21+BIP72 request can provide a hash of the BIP70 request.
   * 
   * @param other
   *            payment intent that is checked if it extends this one
   * @return true if it extends
   */
  public boolean isExtendedBy(final PaymentIntent other)
  {
    // shortcut via hash
    if (standard == Standard.BIP21 && other.standard == Standard.BIP70)
      if (paymentRequestHash != null && Arrays.equals(paymentRequestHash, other.paymentRequestHash))
        return true;

    // TODO memo
    return equalsAmount(other) && equalsAddress(other);
  }

  public boolean equalsAmount(final PaymentIntent other)
  {
    final boolean hasAmount = hasAmount();
    if (hasAmount != other.hasAmount())
      return false;
    if (hasAmount && !getAmount().equals(other.getAmount()))
      return false;
    return true;
  }

  public boolean equalsAddress(final PaymentIntent other)
  {
    final boolean hasAddress = hasAddress();
    if (hasAddress != other.hasAddress())
      return false;
    if (hasAddress && !getAddress().equals(other.getAddress()))
      return false;
    return true;
  }

  @Override
  public String toString()
  {
    final StringBuilder builder = new StringBuilder();

    builder.append(((Object) this).getClass().getSimpleName());
    builder.append('[');
    builder.append(standard);
    builder.append(',');
    if (hasPayee())
    {
      builder.append(payeeName);
      if (payeeVerifiedBy != null)
        builder.append("/").append(payeeVerifiedBy);
      builder.append(',');
    }
    builder.append(hasOutputs() ? Arrays.toString(outputs) : "null");
    builder.append(',');
    builder.append(paymentUrl);
    if (payeeData != null)
    {
      builder.append(',');
      builder.append(Arrays.toString(payeeData));
    }
    if (paymentRequestUrl != null)
    {
      builder.append(",paymentRequestUrl=");
      builder.append(paymentRequestUrl);
    }
    if (paymentRequestHash != null)
    {
      builder.append(",paymentRequestHash=");
      builder.append(BaseEncoding.base16().lowerCase().encode(paymentRequestHash));
    }
    builder.append(']');

    return builder.toString();
  }

  @Override
  public int describeContents()
  {
    return 0;
  }

  @Override
  public void writeToParcel(final Parcel dest, final int flags)
  {
    dest.writeSerializable(standard);

    dest.writeString(payeeName);
    dest.writeString(payeeVerifiedBy);

    if (outputs != null)
    {
      dest.writeInt(outputs.length);
      dest.writeTypedArray(outputs, 0);
    }
    else
    {
      dest.writeInt(0);
    }

    dest.writeString(memo);

    dest.writeString(paymentUrl);

    if (payeeData != null)
    {
      dest.writeInt(payeeData.length);
      dest.writeByteArray(payeeData);
    }
    else
    {
      dest.writeInt(0);
    }

    dest.writeString(paymentRequestUrl);

    if (paymentRequestHash != null)
    {
      dest.writeInt(paymentRequestHash.length);
      dest.writeByteArray(paymentRequestHash);
    }
    else
    {
      dest.writeInt(0);
    }
  }

  public static final Parcelable.Creator<PaymentIntent> CREATOR = new Parcelable.Creator<PaymentIntent>()
  {
    @Override
    public PaymentIntent createFromParcel(final Parcel in)
    {
      return new PaymentIntent(in);
    }

    @Override
    public PaymentIntent[] newArray(final int size)
    {
      return new PaymentIntent[size];
    }
  };

  private PaymentIntent(final Parcel in)
  {
    standard = (Standard) in.readSerializable();

    payeeName = in.readString();
    payeeVerifiedBy = in.readString();

    final int outputsLength = in.readInt();
    if (outputsLength > 0)
    {
      outputs = new Output[outputsLength];
      in.readTypedArray(outputs, Output.CREATOR);
    }
    else
    {
      outputs = null;
    }

    memo = in.readString();

    paymentUrl = in.readString();

    final int payeeDataLength = in.readInt();
    if (payeeDataLength > 0)
    {
      payeeData = new byte[payeeDataLength];
      in.readByteArray(payeeData);
    }
    else
    {
      payeeData = null;
    }

    paymentRequestUrl = in.readString();

    final int paymentRequestHashLength = in.readInt();
    if (paymentRequestHashLength > 0)
    {
      paymentRequestHash = new byte[paymentRequestHashLength];
      in.readByteArray(paymentRequestHash);
    }
    else
    {
      paymentRequestHash = null;
    }
  }
}




Java Source Code List

de.schildbach.wallet.AddressBookProvider.java
de.schildbach.wallet.Configuration.java
de.schildbach.wallet.Constants.java
de.schildbach.wallet.ExchangeRatesProvider.java
de.schildbach.wallet.FileAttachmentProvider.java
de.schildbach.wallet.WalletApplication.java
de.schildbach.wallet.WalletBalanceWidgetProvider.java
de.schildbach.wallet.camera.CameraManager.java
de.schildbach.wallet.data.PaymentIntent.java
de.schildbach.wallet.integration.android.BitcoinIntegration.java
de.schildbach.wallet.offline.AcceptBluetoothService.java
de.schildbach.wallet.offline.AcceptBluetoothThread.java
de.schildbach.wallet.offline.DirectPaymentTask.java
de.schildbach.wallet.service.AutosyncReceiver.java
de.schildbach.wallet.service.BlockchainServiceImpl.java
de.schildbach.wallet.service.BlockchainService.java
de.schildbach.wallet.service.BlockchainStateLoader.java
de.schildbach.wallet.service.BlockchainState.java
de.schildbach.wallet.service.UpgradeWalletService.java
de.schildbach.wallet.ui.AbstractBindServiceActivity.java
de.schildbach.wallet.ui.AbstractWalletActivity.java
de.schildbach.wallet.ui.AddressAndLabel.java
de.schildbach.wallet.ui.AddressBookActivity.java
de.schildbach.wallet.ui.BlockListFragment.java
de.schildbach.wallet.ui.CurrencyAmountView.java
de.schildbach.wallet.ui.CurrencyCalculatorLink.java
de.schildbach.wallet.ui.CurrencySymbolDrawable.java
de.schildbach.wallet.ui.CurrencyTextView.java
de.schildbach.wallet.ui.DialogBuilder.java
de.schildbach.wallet.ui.EditAddressBookEntryFragment.java
de.schildbach.wallet.ui.EncryptKeysDialogFragment.java
de.schildbach.wallet.ui.ExchangeRateLoader.java
de.schildbach.wallet.ui.ExchangeRatesActivity.java
de.schildbach.wallet.ui.ExchangeRatesFragment.java
de.schildbach.wallet.ui.FancyListFragment.java
de.schildbach.wallet.ui.FileAdapter.java
de.schildbach.wallet.ui.HelpDialogFragment.java
de.schildbach.wallet.ui.ImportDialogButtonEnablerListener.java
de.schildbach.wallet.ui.InputParser.java
de.schildbach.wallet.ui.MaybeMaintenanceFragment.java
de.schildbach.wallet.ui.NetworkMonitorActivity.java
de.schildbach.wallet.ui.PeerListFragment.java
de.schildbach.wallet.ui.ProgressDialogFragment.java
de.schildbach.wallet.ui.ReportIssueDialogBuilder.java
de.schildbach.wallet.ui.RequestCoinsActivity.java
de.schildbach.wallet.ui.RequestCoinsFragment.java
de.schildbach.wallet.ui.RestoreWalletActivity.java
de.schildbach.wallet.ui.ScanActivity.java
de.schildbach.wallet.ui.ScannerView.java
de.schildbach.wallet.ui.SendCoinsQrActivity.java
de.schildbach.wallet.ui.SendingAddressesFragment.java
de.schildbach.wallet.ui.ShowPasswordCheckListener.java
de.schildbach.wallet.ui.TransactionsListAdapter.java
de.schildbach.wallet.ui.TransactionsListFragment.java
de.schildbach.wallet.ui.WalletActionsFragment.java
de.schildbach.wallet.ui.WalletActivity.java
de.schildbach.wallet.ui.WalletAddressFragment.java
de.schildbach.wallet.ui.WalletAddressesAdapter.java
de.schildbach.wallet.ui.WalletAddressesFragment.java
de.schildbach.wallet.ui.WalletBalanceFragment.java
de.schildbach.wallet.ui.WalletBalanceLoader.java
de.schildbach.wallet.ui.WalletDisclaimerFragment.java
de.schildbach.wallet.ui.WalletTransactionsFragment.java
de.schildbach.wallet.ui.preference.AboutFragment.java
de.schildbach.wallet.ui.preference.DiagnosticsFragment.java
de.schildbach.wallet.ui.preference.PreferenceActivity.java
de.schildbach.wallet.ui.preference.SettingsFragment.java
de.schildbach.wallet.ui.send.DecodePrivateKeyTask.java
de.schildbach.wallet.ui.send.DeriveKeyTask.java
de.schildbach.wallet.ui.send.MaintenanceDialogFragment.java
de.schildbach.wallet.ui.send.RequestPaymentRequestTask.java
de.schildbach.wallet.ui.send.RequestWalletBalanceTask.java
de.schildbach.wallet.ui.send.SendCoinsActivity.java
de.schildbach.wallet.ui.send.SendCoinsFragment.java
de.schildbach.wallet.ui.send.SendCoinsOfflineTask.java
de.schildbach.wallet.ui.send.SweepWalletActivity.java
de.schildbach.wallet.ui.send.SweepWalletFragment.java
de.schildbach.wallet.util.Base43.java
de.schildbach.wallet.util.BitmapFragment.java
de.schildbach.wallet.util.Bluetooth.java
de.schildbach.wallet.util.CircularProgressView.java
de.schildbach.wallet.util.CrashReporter.java
de.schildbach.wallet.util.Crypto.java
de.schildbach.wallet.util.Formats.java
de.schildbach.wallet.util.GenericUtils.java
de.schildbach.wallet.util.HttpGetThread.java
de.schildbach.wallet.util.Io.java
de.schildbach.wallet.util.Iso8601Format.java
de.schildbach.wallet.util.LinuxSecureRandom.java
de.schildbach.wallet.util.MonetarySpannable.java
de.schildbach.wallet.util.Nfc.java
de.schildbach.wallet.util.Qr.java
de.schildbach.wallet.util.ThrottlingWalletChangeListener.java
de.schildbach.wallet.util.ViewPagerTabs.java
de.schildbach.wallet.util.WalletUtils.java
de.schildbach.wallet.util.WholeStringBuilder.java