Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
12.50% covered (danger)
12.50%
2 / 16
15.38% covered (danger)
15.38%
2 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Bitcoin_Address
12.50% covered (danger)
12.50%
2 / 16
15.38% covered (danger)
15.38%
2 / 13
165.73
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_post_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_wallet_parent_post_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_derivation_path_sequence_number
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_raw_address
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_amount_received
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 get_status
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_order_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 get_target_amount
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_tx_ids
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_modified_time
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_integration_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 was_checked_recently
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 *
4 *
5 * TODO: Update the wp_post last modified time when updating metadata.
6 *
7 * @see wp_update_post()
8 *
9 * @package    brianhenryie/bh-wp-bitcoin-gateway
10 */
11
12namespace BrianHenryIE\WP_Bitcoin_Gateway\API\Model\Wallet;
13
14use BrianHenryIE\WP_Bitcoin_Gateway\Brick\Money\Money;
15use BrianHenryIE\WP_Bitcoin_Gateway\Admin\Addresses_List_Table;
16use BrianHenryIE\WP_Bitcoin_Gateway\Integrations\WooCommerce\Bitcoin_Gateway;
17use DateTimeImmutable;
18use DateTimeInterface;
19use InvalidArgumentException;
20
21/**
22 * TODO: Should we rename this to payment address?
23 */
24class Bitcoin_Address implements Bitcoin_Address_Interface {
25
26    /**
27     * Constructor
28     *
29     * TODO: allow setting `required_confirmations`.
30     * TODO: allow setting `price_margin`.
31     *
32     * @param int                    $post_id The WordPress post ID for this address.
33     * @param int                    $wallet_parent_post_id The post ID of the parent wallet.
34     * @param string                 $raw_address The Bitcoin address string.
35     * @param int                    $derivation_path_sequence_number The derivation path sequence number.
36     * @param DateTimeInterface      $created_time When the WP Post was created.
37     * @param DateTimeInterface      $modified_time When the WP Post was last modified, presumably to check when the address was last checked.
38     * @param Bitcoin_Address_Status $status The current status of the address.
39     * @param ?Money                 $target_amount The target amount for payment.
40     * @param ?string                $integration_id The plugin the order was placed with.
41     * @param ?int                   $order_id The WooCommerce order ID associated with this address.
42     * @param array<int,string>|null $tx_ids Transaction IDs as post_id:tx_id.
43     * @param ?Money                 $received The sum of incoming transactions for the address.
44     *
45     * @throws InvalidArgumentException When the supplied post_id is not a post of this type.
46     */
47    public function __construct(
48        protected int $post_id,
49        protected int $wallet_parent_post_id,
50        protected string $raw_address,
51        protected int $derivation_path_sequence_number,
52        protected DateTimeInterface $created_time,
53        protected DateTimeInterface $modified_time,
54        protected Bitcoin_Address_Status $status = Bitcoin_Address_Status::UNKNOWN,
55        protected ?Money $target_amount = null,
56        protected ?string $integration_id = null,
57        protected ?int $order_id = null,
58        protected ?array $tx_ids = null,
59        protected ?Money $received = null,
60    ) {
61    }
62
63    /**
64     * Get the WordPress post ID where this Bitcoin payment address is stored.
65     */
66    public function get_post_id(): int {
67        return $this->post_id;
68    }
69
70
71    /**
72     * The post ID for the xpub|ypub|zpub wallet this address was derived for.
73     *
74     * @return int
75     */
76    public function get_wallet_parent_post_id(): int {
77        return $this->wallet_parent_post_id;
78    }
79
80    /**
81     * Get this Bitcoin address's derivation path.
82     *
83     * @readonly
84     */
85    public function get_derivation_path_sequence_number(): ?int {
86        return $this->derivation_path_sequence_number;
87    }
88
89    /**
90     * Return the raw Bitcoin address this object represents.
91     *
92     * @used-by API::check_new_addresses_for_transactions() When verifying newly generated addresses have no existing transactions.
93     * @used-by API::get_fresh_address_for_order() When adding the payment address to the order meta.
94     * @used-by Bitcoin_Gateway::process_payment() When adding a link in the order notes to view transactions on a 3rd party website.
95     * @used-by API::update_address_transactions() When checking has an order been paid.
96     */
97    public function get_raw_address(): string {
98        return $this->raw_address;
99    }
100
101    // TODO: `get_mempool_transactions()`.
102
103    /**
104     * Return the amount received that is saved in the post meta, or null if the address status is unknown.
105     *
106     * TODO: Might need a $confirmations parameter and calculate the total received from the transactions.
107     *
108     * @used-by Addresses_List_Table::print_columns()
109     *
110     * @return ?Money Null if unknown.
111     */
112    public function get_amount_received(): ?Money {
113        return Bitcoin_Address_Status::UNKNOWN === $this->get_status() ? null : $this->received;
114    }
115
116    /**
117     * Return the current status of the Bitcoin address object/post.
118     */
119    public function get_status(): Bitcoin_Address_Status {
120        return $this->status;
121    }
122
123    /**
124     * Get the order id associated with this address, or null if none has ever been assigned.
125     */
126    public function get_order_id(): ?int {
127        return 0 === $this->order_id ? null : $this->order_id;
128    }
129
130    /**
131     * The received amount needed to consider the order "paid".
132     */
133    public function get_target_amount(): ?Money {
134        return $this->target_amount;
135    }
136
137    /**
138     * @return null|array<int, string> <post_id, tx_id> or null if it has never been checked.
139     */
140    public function get_tx_ids(): ?array {
141        return $this->tx_ids;
142    }
143
144    /**
145     * When was the WP_Post last modified.
146     */
147    public function get_modified_time(): DateTimeInterface {
148        return $this->modified_time;
149    }
150
151    /**
152     * The internal name/id of the integration (WooCommerce...) that the address is assigned to.
153     */
154    public function get_integration_id(): ?string {
155        return $this->integration_id;
156    }
157
158
159    /**
160     * Was this address recently checked for transactions?
161     *
162     * There is no need to check more than once every ten minutes because that is the rate of blocks mined.
163     *
164     * `$address->get_modified_time() > (new DateTimeImmutable())->sub(new DateInterval('PT10M'))`.
165     *
166     * @param int $minutes Number of minutes until it is considered stale.
167     */
168    public function was_checked_recently( int $minutes = 10 ): bool {
169        $now                = new DateTimeImmutable();
170        $threshold_seconds  = $minutes * constant( 'MINUTE_IN_SECONDS' );
171        $seconds_difference = $now->getTimestamp() - $this->modified_time->getTimestamp();
172        return $seconds_difference < $threshold_seconds;
173    }
174}