namespace = trim( CF7PA_ADDONS_REST_API, '/' ); $this->rest_base = 'transaction'; } public function register_routes() { register_rest_route( $this->namespace, $this->rest_base . '/update-status', array( array( 'methods' => 'POST', 'callback' => array( $this, 'update_status' ), 'permission_callback' => array( $this, 'permission_check' ), ), ) ); register_rest_route( $this->namespace, $this->rest_base . '/delete', array( array( 'methods' => 'POST', 'callback' => array( $this, 'delete_transaction' ), 'permission_callback' => array( $this, 'permission_check' ), ), ) ); register_rest_route( $this->namespace, $this->rest_base . '/details', array( array( 'methods' => 'POST', 'callback' => array( $this, 'get_transaction_details' ), 'permission_callback' => array( $this, 'permission_check' ), ), ) ); register_rest_route( $this->namespace, $this->rest_base . '/retry', array( array( 'methods' => 'POST', 'callback' => array( $this, 'retry_payment' ), 'permission_callback' => array( $this, 'permission_check' ), ), ) ); } public function permission_check( WP_REST_Request $request ) { return Security_Utils::admin_check( $request ); } public function update_status( WP_REST_Request $request ) { $transaction_id = sanitize_text_field( $request->get_param( 'transaction_id' ) ); $status = sanitize_text_field( $request->get_param( 'status' ) ); $comment = sanitize_text_field( $request->get_param( 'message' ) ); if ( ! $transaction_id || ! $status ) { return new WP_REST_Response( array( 'error' => 'Missing parameters' ), 400 ); } $txn_query = new Transaction_Query(); $result = $txn_query->update_by_transaction_id( $transaction_id, array( 'payment_status' => $status, 'comment' => $comment, ) ); if ( $result === false ) { return new WP_REST_Response( array( 'error' => 'Update failed' ), 500 ); } return new WP_REST_Response( array( 'success' => true ), 200 ); } public function delete_transaction( WP_REST_Request $request ) { $id = intval( $request->get_param( 'id' ) ); if ( ! $id ) { return new WP_REST_Response( array( 'error' => 'Missing transaction ID' ), 400 ); } $txn_query = new Transaction_Query(); $deleted = $txn_query->delete_by_id( $id ); if ( $deleted ) { return new WP_REST_Response( array( 'success' => true ), 200 ); } return new WP_REST_Response( array( 'error' => 'Delete failed' ), 500 ); } public function get_transaction_details( WP_REST_Request $request ) { $id = intval( $request->get_param( 'id' ) ); if ( ! $id ) { return new WP_REST_Response( array( 'error' => 'Missing transaction ID' ), 400 ); } $txn_query = new Transaction_Query(); $txn = $txn_query->get_transaction( $id ); if ( $txn ) { return new WP_REST_Response( array( 'success' => true, 'data' => $txn ), 200 ); } return new WP_REST_Response( array( 'error' => 'Transaction not found' ), 404 ); } /** * Generate a retry payment link from a failed transaction. * * Creates a new Stripe Checkout Session using the original transaction's * amount, currency, metadata, and customer info. The link can be shared * with the customer to retry the payment without re-submitting the form. */ public function retry_payment( WP_REST_Request $request ) { $id = intval( $request->get_param( 'id' ) ); if ( ! $id ) { return new WP_REST_Response( array( 'error' => 'Missing transaction ID' ), 400 ); } $txn_query = new Transaction_Query(); $txn = $txn_query->get_transaction( $id ); if ( ! $txn ) { return new WP_REST_Response( array( 'error' => 'Transaction not found' ), 404 ); } try { $form_data = $txn['form_data'] ?? array(); $meta_data = $txn['meta_data'] ?? array(); $is_sub = ! empty( $txn['recurring'] ); // Allow the request body to override the stored transaction values. $currency = $request->get_param( 'currency' ) ?: ( $txn['currency'] ?? 'usd' ); $currency = strtolower( $currency ); $amount = $request->get_param( 'amount' ); $amount = ( $amount !== null && $amount !== '' ) ? intval( $amount ) : intval( $txn['amount'] ?? 0 ); $form_name = $request->get_param( 'product_name' ) ?: ( $txn['form_name'] ?? __( 'Payment Retry', 'accept-stripe-for-contact-form-7' ) ); $quantity = $request->get_param( 'quantity' ); $quantity = ( $quantity !== null && $quantity !== '' ) ? intval( $quantity ) : 1; $description = $request->get_param( 'description' ); if ( $description === null || $description === '' ) { $description = __( 'Retry for', 'accept-stripe-for-contact-form-7' ) . ' ' . $form_name; } // Build metadata from stored form + meta data $metadata = array( 'referer' => wp_parse_url( get_site_url(), PHP_URL_HOST ), 'retry_source' => 'transaction_' . $id, 'original_txn' => $txn['transaction_id'] ?? '', ); if ( is_array( $meta_data ) ) { $metadata = array_merge( $metadata, $meta_data ); } // Merge form_data keys too (often form fields are stored here) if ( is_array( $form_data ) ) { foreach ( $form_data as $k => $v ) { if ( ! isset( $metadata[ $k ] ) ) { $metadata[ $k ] = is_array( $v ) ? implode( ', ', $v ) : (string) $v; } } } // Cap at 48 keys (Stripe limit: 50, minus our 2 internal keys) $metadata = array_slice( $metadata, 0, 48, true ); $line_item = array( 'quantity' => $quantity, 'price_data' => array( 'currency' => $currency, 'unit_amount' => $amount, 'product_data' => array( 'name' => $form_name, 'description' => $description, ), ), ); if ( $is_sub ) { $interval = $txn['recurring_interval'] ?? 'month'; $interval_count = intval( $txn['recurring_count'] ?? 1 ); $line_item['price_data']['recurring'] = array( 'interval' => $interval, 'interval_count' => $interval_count, ); } $session_args = array( 'line_items' => array( $line_item ), 'mode' => $is_sub ? 'subscription' : 'payment', 'success_url' => $request->get_param( 'success_url' ) ?: home_url(), 'cancel_url' => $request->get_param( 'cancel_url' ) ?: home_url(), 'metadata' => $metadata, ); if ( $is_sub ) { $session_args['subscription_data'] = array( 'metadata' => $metadata, 'description' => $description, ); } else { $session_args['payment_intent_data'] = array( 'metadata' => $metadata, 'description' => $description, ); } $customer_email = $request->get_param( 'customer_email' ) ?: ( $txn['customer_email'] ?? '' ); if ( ! empty( $customer_email ) ) { $session_args['customer_email'] = $customer_email; } /** * Filter the Stripe Checkout Session args before creating a retry link. * * Use this to customize the amount, product name, description, metadata, * or any other session parameter when generating a payment link from a * transaction on the Transactions page. * * @param array $session_args Stripe Checkout Session arguments. * @param array $txn The full transaction row from the database. * @param array $request_params Extra params from the REST request body (e.g. custom amount, quantity, description). */ $session_args = apply_filters( 'cf7pa_pay_addons/transaction/retry_session_args', $session_args, $txn, $request->get_params() ); $session = Stripe_API::create_checkout_session( $session_args ); /** * Fires after a retry payment link is generated from a transaction. * * Use this to send the link via email, log it, or trigger other workflows. * * @param object $session The Stripe Checkout Session object. * @param array $txn The full transaction row from the database. * @param array $session_args The session arguments used to create the link. */ do_action( 'cf7pa_pay_addons/transaction/retry_link_generated', $session, $txn, $session_args ); return new WP_REST_Response( array( 'success' => true, 'url' => $session->url, ), 200 ); } catch ( \Exception $e ) { \CF7PA_Pay_Addons\Shared\Logger::error( 'Retry payment failed for txn ' . $id . ': ' . $e->getMessage() ); return new WP_REST_Response( array( 'error' => $e->getMessage() ), 500 ); } } }