'afridirect_payment', 'post_title' => $reference, 'post_status' => 'publish', 'post_author' => 0, // No author for standalone payments. ); $payment_id = wp_insert_post( $post_data ); if ( is_wp_error( $payment_id ) ) { return $payment_id; } // Save payment meta. $meta_data = array( 'afridirect_form_id' => isset( $data['form_id'] ) ? absint( $data['form_id'] ) : 0, 'afridirect_reference' => $reference, 'afridirect_amount' => isset( $data['amount'] ) ? floatval( $data['amount'] ) : 0, 'afridirect_currency' => isset( $data['currency'] ) ? sanitize_text_field( $data['currency'] ) : 'USD', 'afridirect_method_id' => isset( $data['method_id'] ) ? sanitize_key( $data['method_id'] ) : '', 'afridirect_method_label' => isset( $data['method_label'] ) ? sanitize_text_field( $data['method_label'] ) : '', 'afridirect_payer_name' => isset( $data['payer_name'] ) ? sanitize_text_field( $data['payer_name'] ) : '', 'afridirect_payer_email' => isset( $data['payer_email'] ) ? sanitize_email( $data['payer_email'] ) : '', 'afridirect_payer_phone' => isset( $data['payer_phone'] ) ? sanitize_text_field( $data['payer_phone'] ) : '', 'afridirect_timer_minutes' => isset( $data['timer_minutes'] ) ? absint( $data['timer_minutes'] ) : 0, 'afridirect_expires_at' => isset( $data['expires_at'] ) ? absint( $data['expires_at'] ) : 0, 'afridirect_status' => 'pending_proof', 'afridirect_proof_url' => '', 'afridirect_created_at' => current_time( 'mysql' ), 'afridirect_access_key' => wp_generate_password( 32, false ), // Secure key for access. ); foreach ( $meta_data as $key => $value ) { update_post_meta( $payment_id, '_' . $key, $value ); } return $payment_id; } /** * Get a payment by ID. * * @param int $payment_id Payment ID. * @return array|false Payment data or false. */ public static function get_payment( $payment_id ) { $post = get_post( $payment_id ); if ( ! $post || 'afridirect_payment' !== $post->post_type ) { return false; } return array( 'id' => $payment_id, 'form_id' => absint( get_post_meta( $payment_id, '_afridirect_form_id', true ) ), 'reference' => get_post_meta( $payment_id, '_afridirect_reference', true ), 'amount' => floatval( get_post_meta( $payment_id, '_afridirect_amount', true ) ), 'currency' => get_post_meta( $payment_id, '_afridirect_currency', true ), 'method_id' => get_post_meta( $payment_id, '_afridirect_method_id', true ), 'method_label' => get_post_meta( $payment_id, '_afridirect_method_label', true ), 'payer_name' => get_post_meta( $payment_id, '_afridirect_payer_name', true ), 'payer_email' => get_post_meta( $payment_id, '_afridirect_payer_email', true ), 'payer_phone' => get_post_meta( $payment_id, '_afridirect_payer_phone', true ), 'status' => get_post_meta( $payment_id, '_afridirect_status', true ), 'proof_url' => get_post_meta( $payment_id, '_afridirect_proof_url', true ), 'timer_minutes' => absint( get_post_meta( $payment_id, '_afridirect_timer_minutes', true ) ), 'expires_at' => absint( get_post_meta( $payment_id, '_afridirect_expires_at', true ) ), 'created_at' => get_post_meta( $payment_id, '_afridirect_created_at', true ), 'access_key' => get_post_meta( $payment_id, '_afridirect_access_key', true ), 'note' => (string) get_post_meta( $payment_id, '_afridirect_note', true ), 'request_id' => absint( get_post_meta( $payment_id, '_afridirect_request_id', true ) ), ); } /** * Update payment status. * * @param int $payment_id Payment ID. * @param string $status New status. * @return bool */ public static function update_status( $payment_id, $status ) { $valid_statuses = array( 'pending_proof', 'needs_review', 'approved', 'rejected', 'expired' ); if ( ! in_array( $status, $valid_statuses, true ) ) { return false; } return update_post_meta( $payment_id, '_afridirect_status', $status ); } /** * Mark one payment as expired if the customer ran out of time to send proof. * * Only a payment that is still waiting for proof can expire. Once proof has been uploaded the * payment is waiting on the seller, so it never expires because of the seller's delay. * * @param int $payment_id Payment ID. * @return bool True if the payment is now expired. */ public static function maybe_expire( $payment_id ) { $status = get_post_meta( $payment_id, '_afridirect_status', true ); $expires_at = (int) get_post_meta( $payment_id, '_afridirect_expires_at', true ); if ( 'expired' === $status ) { return true; } if ( 'pending_proof' !== $status || $expires_at <= 0 || time() <= $expires_at ) { return false; } self::update_status( $payment_id, 'expired' ); /** * Fires when a standalone payment expires because no proof arrived in time. * * @param int $payment_id Payment ID. */ do_action( 'afridirect_payment_expired', $payment_id ); return true; } /** * Expire every standalone payment whose time has run out. Runs from the plugin's recurring schedule. */ public static function expire_overdue() { $ids = get_posts( array( 'post_type' => 'afridirect_payment', 'post_status' => 'publish', 'posts_per_page' => 100, 'fields' => 'ids', 'no_found_rows' => true, // No non-meta way to find payments by status and deadline; this runs every few minutes on a small batch. // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( 'relation' => 'AND', array( 'key' => '_afridirect_status', 'value' => 'pending_proof', ), array( 'key' => '_afridirect_expires_at', 'value' => array( 1, time() ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC', ), ), ) ); foreach ( $ids as $payment_id ) { self::maybe_expire( $payment_id ); } } /** * Link to the customer's payment page (timer, details and proof upload) for a payment. * * @param array $payment Payment data from get_payment(). * @return string */ public static function get_status_url( $payment ) { $page_id = (int) get_option( 'afridirect_pending_page_id', 0 ); $base = $page_id ? get_permalink( $page_id ) : ''; if ( ! $base ) { $base = home_url( '/' ); } return add_query_arg( array( 'pid' => $payment['id'], 'key' => rawurlencode( $payment['access_key'] ), ), $base ); } /** * Verify access key for payment. * * @param int $payment_id Payment ID. * @param string $key Access key. * @return bool */ public static function verify_key( $payment_id, $key ) { $stored_key = get_post_meta( $payment_id, '_afridirect_access_key', true ); return ! empty( $stored_key ) && hash_equals( $stored_key, $key ); } /** * Generate unique reference number. * * @return string */ private static function generate_reference() { $prefix = 'ADP-'; $timestamp = time(); $random = wp_generate_password( 6, false, false ); return $prefix . $timestamp . '-' . strtoupper( $random ); } }