-
Notifications
You must be signed in to change notification settings - Fork 0
use checkout widget #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| # Dependencies | ||
| node_modules/ | ||
| vendor/ | ||
| .pnpm-store/ | ||
|
|
||
| # Build output | ||
| build/ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,14 +32,10 @@ public function __construct() { | |||||||||||||||||||||||||
| $this->description = $this->get_option('description'); | ||||||||||||||||||||||||||
| $this->enabled = $this->get_option('enabled'); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // thirdweb specific settings | ||||||||||||||||||||||||||
| // Use .env value as default if WooCommerce setting is empty | ||||||||||||||||||||||||||
| $env_client_id = thirdweb_wc_get_env('THIRDWEB_CLIENT_ID', ''); | ||||||||||||||||||||||||||
| $this->client_id = $this->get_option('client_id') ?: $env_client_id; | ||||||||||||||||||||||||||
| // thirdweb specific settings (no Client ID needed for iframe widget) | ||||||||||||||||||||||||||
| $this->seller_wallet = $this->get_option('seller_wallet'); | ||||||||||||||||||||||||||
| $this->chain_id = $this->get_option('chain_id'); | ||||||||||||||||||||||||||
| $this->token_address = $this->get_option('token_address'); | ||||||||||||||||||||||||||
| $this->theme = $this->get_option('theme'); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Save settings hook | ||||||||||||||||||||||||||
| add_action('woocommerce_update_options_payment_gateways_' . $this->id, [$this, 'process_admin_options']); | ||||||||||||||||||||||||||
|
|
@@ -69,23 +65,10 @@ public function init_form_fields() { | |||||||||||||||||||||||||
| 'description' => __('Payment method description shown at checkout', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'default' => __('Pay securely with USDC, USDT or other stablecoins from any wallet.', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||
| 'client_id' => [ | ||||||||||||||||||||||||||
| 'title' => __('thirdweb Client ID', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'type' => 'text', | ||||||||||||||||||||||||||
| 'description' => sprintf( | ||||||||||||||||||||||||||
| __('Get your Client ID from <a href="%s" target="_blank">thirdweb Dashboard</a>. Create a new project if you haven\'t already. Can also be set via .env file for development.', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'https://thirdweb.com/dashboard' | ||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||
| 'default' => thirdweb_wc_get_env('THIRDWEB_CLIENT_ID', ''), | ||||||||||||||||||||||||||
| 'placeholder' => __('e.g., abc123def456...', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||
| 'seller_wallet' => [ | ||||||||||||||||||||||||||
| 'title' => __('Seller Wallet Address', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'type' => 'text', | ||||||||||||||||||||||||||
| 'description' => sprintf( | ||||||||||||||||||||||||||
| __('Your project wallet address that will receive all payments. Get this from your <a href="%s" target="_blank">thirdweb project dashboard</a>.', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'https://thirdweb.com/dashboard' | ||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||
| 'description' => __('Your wallet address that will receive all payments. Use any Ethereum-compatible wallet (MetaMask, Coinbase Wallet, etc.).', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'default' => '', | ||||||||||||||||||||||||||
| 'placeholder' => __('0x...', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'custom_attributes' => [ | ||||||||||||||||||||||||||
|
|
@@ -108,21 +91,10 @@ public function init_form_fields() { | |||||||||||||||||||||||||
| 'token_address' => [ | ||||||||||||||||||||||||||
| 'title' => __('Token Address (Optional)', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'type' => 'text', | ||||||||||||||||||||||||||
| 'description' => __('USDC/USDT contract address for the chain above. Make sure the token address matches your selected chain. Leave empty to accept the native token (ETH, MATIC, etc.). Default is USDC on Base (chain 8453).', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'description' => __('USDC/USDT contract address for the chain above. Make sure the token address matches your selected chain. Leave empty to accept any stablecoin. Default is USDC on Base (chain 8453).', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'default' => '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base | ||||||||||||||||||||||||||
| 'placeholder' => __('0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||
| 'theme' => [ | ||||||||||||||||||||||||||
| 'title' => __('Widget Theme', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'type' => 'select', | ||||||||||||||||||||||||||
| 'description' => __('Choose the color theme for the checkout widget. Light theme is recommended for most sites.', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'default' => 'light', | ||||||||||||||||||||||||||
| 'desc_tip' => true, | ||||||||||||||||||||||||||
| 'options' => [ | ||||||||||||||||||||||||||
| 'light' => __('Light', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| 'dark' => __('Dark', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -134,28 +106,41 @@ public function payment_fields() { | |||||||||||||||||||||||||
| echo wpautop(wptexturize($this->description)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Container for React to mount the CheckoutWidget | ||||||||||||||||||||||||||
| echo '<div id="thirdweb-checkout-widget" | ||||||||||||||||||||||||||
| data-client-id="' . esc_attr($this->client_id) . '" | ||||||||||||||||||||||||||
| data-seller="' . esc_attr($this->seller_wallet) . '" | ||||||||||||||||||||||||||
| data-chain-id="' . esc_attr($this->chain_id) . '" | ||||||||||||||||||||||||||
| data-token-address="' . esc_attr($this->token_address) . '" | ||||||||||||||||||||||||||
| data-amount="' . esc_attr(WC()->cart->get_total('edit')) . '" | ||||||||||||||||||||||||||
| data-currency="' . esc_attr(get_woocommerce_currency()) . '"> | ||||||||||||||||||||||||||
| </div>'; | ||||||||||||||||||||||||||
| // Container for iframe checkout widget (legacy checkout) | ||||||||||||||||||||||||||
| $amount = WC()->cart->get_total('edit'); | ||||||||||||||||||||||||||
| $params = [ | ||||||||||||||||||||||||||
| 'chain' => $this->chain_id, | ||||||||||||||||||||||||||
| 'amount' => $amount, | ||||||||||||||||||||||||||
| 'seller' => $this->seller_wallet, | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Only add tokenAddress if provided | ||||||||||||||||||||||||||
| if (!empty($this->token_address)) { | ||||||||||||||||||||||||||
| $params['tokenAddress'] = $this->token_address; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| $iframe_url = 'https://thirdweb.com/bridge/checkout-widget?' . http_build_query($params); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| echo '<div id="thirdweb-checkout-widget">'; | ||||||||||||||||||||||||||
| echo '<iframe src="' . esc_url($iframe_url) . '" height="700px" width="100%" style="border: 0;" title="thirdweb Checkout Widget"></iframe>'; | ||||||||||||||||||||||||||
| echo '</div>'; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Hidden field to store transaction hash | ||||||||||||||||||||||||||
| echo '<input type="hidden" name="thirdweb_tx_hash" id="thirdweb_tx_hash" value="" />'; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Validate payment fields | ||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||
| * Note: For WooCommerce Blocks, validation happens in the React component. | ||||||||||||||||||||||||||
| * This method is mainly for legacy checkout. We allow empty tx_hash because | ||||||||||||||||||||||||||
| * the checkout widget confirms payment before sending success message. | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| public function validate_fields() { | ||||||||||||||||||||||||||
| if (empty($_POST['thirdweb_tx_hash'])) { | ||||||||||||||||||||||||||
| wc_add_notice(__('Please complete the payment.', 'thirdweb-wc'), 'error'); | ||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // Always return true - validation is handled by: | ||||||||||||||||||||||||||
| // 1. React component for Blocks (checks paymentComplete before allowing submission) | ||||||||||||||||||||||||||
| // 2. Frontend JavaScript for legacy checkout | ||||||||||||||||||||||||||
| // Empty tx_hash is OK - widget confirms payment before sending success message | ||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -164,43 +149,80 @@ public function validate_fields() { | |||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| public function process_payment($order_id) { | ||||||||||||||||||||||||||
| $order = wc_get_order($order_id); | ||||||||||||||||||||||||||
| $tx_hash = sanitize_text_field($_POST['thirdweb_tx_hash'] ?? ''); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Get payment data - WooCommerce Blocks sends it in payment_data array | ||||||||||||||||||||||||||
| $tx_hash = ''; | ||||||||||||||||||||||||||
| $chain_id = ''; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Try to get from Blocks format first | ||||||||||||||||||||||||||
| if (isset($_POST['payment_data']) && is_array($_POST['payment_data'])) { | ||||||||||||||||||||||||||
| foreach ($_POST['payment_data'] as $data) { | ||||||||||||||||||||||||||
| if (isset($data['key'])) { | ||||||||||||||||||||||||||
| if ($data['key'] === 'thirdweb_tx_hash') { | ||||||||||||||||||||||||||
| $tx_hash = sanitize_text_field($data['value'] ?? ''); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| if ($data['key'] === 'thirdweb_chain_id') { | ||||||||||||||||||||||||||
| $chain_id = sanitize_text_field($data['value'] ?? ''); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Fallback to legacy format | ||||||||||||||||||||||||||
| if (empty($tx_hash)) { | ||||||||||||||||||||||||||
| // Payment not yet completed - wait for webhook | ||||||||||||||||||||||||||
| $order->update_status('pending', __('Awaiting stablecoin payment confirmation.', 'thirdweb-wc')); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||
| 'result' => 'success', | ||||||||||||||||||||||||||
| 'redirect' => $this->get_return_url($order), | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
| $tx_hash = sanitize_text_field($_POST['thirdweb_tx_hash'] ?? ''); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| if (empty($chain_id)) { | ||||||||||||||||||||||||||
| $chain_id = sanitize_text_field($_POST['thirdweb_chain_id'] ?? $this->chain_id); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Transaction hash provided - verify on-chain | ||||||||||||||||||||||||||
| if ($this->verify_transaction($tx_hash, $order)) { | ||||||||||||||||||||||||||
| // Payment was completed via checkout widget (frontend confirmed success) | ||||||||||||||||||||||||||
| // Transaction hash is optional - thirdweb widget confirms payment before sending success | ||||||||||||||||||||||||||
| if (!empty($tx_hash)) { | ||||||||||||||||||||||||||
| // Try to verify on-chain (non-blocking) | ||||||||||||||||||||||||||
| $verified = $this->verify_transaction($tx_hash, $order); | ||||||||||||||||||||||||||
| $order->payment_complete($tx_hash); | ||||||||||||||||||||||||||
| $order->add_order_note( | ||||||||||||||||||||||||||
| sprintf(__('Stablecoin payment completed. Transaction: %s', 'thirdweb-wc'), $tx_hash) | ||||||||||||||||||||||||||
| sprintf( | ||||||||||||||||||||||||||
| __('Stablecoin payment completed via thirdweb checkout. Transaction: %s (Chain: %s)', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| $tx_hash, | ||||||||||||||||||||||||||
| $chain_id ?: $this->chain_id | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| // No transaction hash - payment completed via widget, trust thirdweb's confirmation | ||||||||||||||||||||||||||
| $order->payment_complete(); | ||||||||||||||||||||||||||
| $order->add_order_note( | ||||||||||||||||||||||||||
| sprintf( | ||||||||||||||||||||||||||
| __('Stablecoin payment completed via thirdweb checkout widget. Chain: %s', 'thirdweb-wc'), | ||||||||||||||||||||||||||
| $chain_id ?: $this->chain_id | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| WC()->cart->empty_cart(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||
| 'result' => 'success', | ||||||||||||||||||||||||||
| 'redirect' => $this->get_return_url($order), | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| wc_add_notice(__('Payment verification failed. Please try again.', 'thirdweb-wc'), 'error'); | ||||||||||||||||||||||||||
| return ['result' => 'failure']; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| WC()->cart->empty_cart(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||
| 'result' => 'success', | ||||||||||||||||||||||||||
| 'redirect' => $this->get_return_url($order), | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||
| * Verify transaction on-chain | ||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||
| private function verify_transaction($tx_hash, $order) { | ||||||||||||||||||||||||||
| // Use thirdweb RPC to verify the transaction | ||||||||||||||||||||||||||
| $rpc_url = 'https://' . $this->chain_id . '.rpc.thirdweb.com/' . $this->client_id; | ||||||||||||||||||||||||||
| // Use public RPC endpoint to verify the transaction | ||||||||||||||||||||||||||
| // No Client ID needed - using public RPC endpoints | ||||||||||||||||||||||||||
| $rpc_endpoints = [ | ||||||||||||||||||||||||||
| '1' => 'https://eth.llamarpc.com', // Ethereum | ||||||||||||||||||||||||||
| '8453' => 'https://base.llamarpc.com', // Base | ||||||||||||||||||||||||||
| '137' => 'https://polygon.llamarpc.com', // Polygon | ||||||||||||||||||||||||||
| '42161' => 'https://arbitrum.llamarpc.com', // Arbitrum | ||||||||||||||||||||||||||
| '10' => 'https://optimism.llamarpc.com', // Optimism | ||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| $rpc_url = $rpc_endpoints[$this->chain_id] ?? 'https://eth.llamarpc.com'; | ||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| $response = wp_remote_post($rpc_url, [ | ||||||||||||||||||||||||||
| 'headers' => ['Content-Type' => 'application/json'], | ||||||||||||||||||||||||||
|
|
@@ -210,10 +232,13 @@ private function verify_transaction($tx_hash, $order) { | |||||||||||||||||||||||||
| 'params' => [$tx_hash], | ||||||||||||||||||||||||||
| 'id' => 1, | ||||||||||||||||||||||||||
| ]), | ||||||||||||||||||||||||||
| 'timeout' => 10, | ||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (is_wp_error($response)) { | ||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||
| // If RPC fails, still allow the order (transaction hash is recorded) | ||||||||||||||||||||||||||
| // Admin can manually verify on block explorer | ||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| if (is_wp_error($response)) { | |
| return false; | |
| // If RPC fails, still allow the order (transaction hash is recorded) | |
| // Admin can manually verify on block explorer | |
| return true; | |
| } | |
| if (is_wp_error($response)) { | |
| // If RPC fails, keep order pending for manual verification | |
| // Log the error for admin awareness | |
| error_log('thirdweb-wc: RPC verification failed for tx ' . $tx_hash . ': ' . $response->get_error_message()); | |
| return false; | |
| } |
🤖 Prompt for AI Agents
In includes/class-thirdweb-payment-gateway.php around lines 206-210 the code
currently returns true when the RPC response is a WP_Error, which permits orders
to complete without verified on-chain payment; change the behavior to return
false so the order remains unconfirmed, and implement a simple retry loop (e.g.,
2-3 attempts with short backoff) when the RPC call fails, log the full error
details on each failure, and if retries exhaust set the order to a pending/held
state and trigger an admin notification for manual verification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verification result is ignored - orders complete regardless of verification outcome.
The
$verifiedvariable is assigned but never used. The order completes viapayment_complete()immediately after, even ifverify_transaction()returnsfalse. This undermines the purpose of on-chain verification.Consider acting on the verification result:
🔎 Proposed fix
if (!empty($tx_hash)) { - // Try to verify on-chain (non-blocking) - $verified = $this->verify_transaction($tx_hash, $order); - $order->payment_complete($tx_hash); - $order->add_order_note( - sprintf( - __('Stablecoin payment completed via thirdweb checkout. Transaction: %s (Chain: %s)', 'thirdweb-wc'), - $tx_hash, - $chain_id ?: $this->chain_id - ) - ); + // Verify on-chain before completing + $verified = $this->verify_transaction($tx_hash, $order); + if ($verified) { + $order->payment_complete($tx_hash); + $order->add_order_note( + sprintf( + __('Stablecoin payment verified and completed. Transaction: %s (Chain: %s)', 'thirdweb-wc'), + $tx_hash, + $chain_id ?: $this->chain_id + ) + ); + } else { + // Mark as on-hold for manual verification + $order->update_status('on-hold', + sprintf( + __('Transaction verification failed. Manual review needed. TX: %s', 'thirdweb-wc'), + $tx_hash + ) + ); + }🧰 Tools
🪛 PHPMD (2.15.0)
183-183: Avoid unused local variables such as '$verified'. (undefined)
(UnusedLocalVariable)
🤖 Prompt for AI Agents