diff --git a/includes/Builder/Methods/PayPal/PayPal.php b/includes/Builder/Methods/PayPal/PayPal.php index b2bc0f8..55e3f2b 100644 --- a/includes/Builder/Methods/PayPal/PayPal.php +++ b/includes/Builder/Methods/PayPal/PayPal.php @@ -125,7 +125,7 @@ public function makePayment($transactionId, $entryId, $form_data) 'email' => $supporter->supporters_email, 'no_shipping' => '1', 'no_note' => '1', - 'currency_code' => $supporter->currency ? $supporter->currency : 'USD', + 'currency_code' => $supporter->currency ?? 'USD', 'charset' => 'UTF-8', 'custom' => $transactionId, 'return' => $this->successUrl($supporter), @@ -301,11 +301,14 @@ public function render($template) ?> 'Bearer ' . $stripeApiKey, + 'Content-Type' => 'application/x-www-form-urlencoded', + ); + + $requestData = array( + 'headers' => $sessionHeaders, + 'body' => http_build_query($data), + 'method' => $method, + ); + + $url = $this->apiUrl . $path; + + $sessionResponse = wp_remote_post($url, $requestData); + + if (is_wp_error($sessionResponse)) { + echo "API Error: " . esc_html($sessionResponse->get_error_message()); + exit; + } + + $sessionResponseData = wp_remote_retrieve_body($sessionResponse); + + $sessionData = json_decode($sessionResponseData, true); + + if (empty($sessionData['id'])) { + $message = Arr::get($sessionData, 'detail'); + if (!$message) { + $message = Arr::get($sessionData, 'error.message'); + } + if (!$message) { + $message = 'Unknown Stripe API request error'; + } + + return new \WP_Error(423, $message, $sessionData); + } + + return $sessionData; + } + + + public function verifyIPN() + { + if (!isset($_REQUEST['wpm_bmc_stripe_listener'])) { + return; + } + + $post_data = ''; + if (ini_get('allow_url_fopen')) { + $post_data = file_get_contents('php://input'); + } else { + // If allow_url_fopen is not enabled, then make sure that post_max_size is large enough + ini_set('post_max_size', '12M'); + } + + $data = json_decode($post_data); + + if ($data->id) { + status_header(200); + return $data; + } else { + error_log("specific event"); + error_log(print_r($data)); + return false; + } + + exit(200); + } + + public function getInvoice($eventId) + { + $api = new ApiRequest(); + $api::set_secret_key((new StripeSettings())->getApiKey()); + return $api::request([], 'events/' . $eventId, 'GET'); + } +} diff --git a/includes/Builder/Methods/Stripe/Stripe.php b/includes/Builder/Methods/Stripe/Stripe.php index b5f1148..7c1d777 100644 --- a/includes/Builder/Methods/Stripe/Stripe.php +++ b/includes/Builder/Methods/Stripe/Stripe.php @@ -4,6 +4,8 @@ use BuyMeCoffee\Builder\Methods\BaseMethods; use BuyMeCoffee\Classes\Vite; +use BuyMeCoffee\Models\Supporters; +use BuyMeCoffee\Models\Transactions; class Stripe extends BaseMethods { @@ -21,9 +23,79 @@ public function __construct() public function makePayment($transactionId, $entryId, $form_data) { - // TO-Do will implement later on upcoming version + $transactionModel = new Transactions(); + $transaction = $transactionModel->find($transactionId); + + $supportersModel = new Supporters(); + $supporter = $supportersModel->find($entryId); + $hash = $transaction->entry_hash; + + $keys = StripeSettings::getKeys(); + $apiKey = $keys['secret']; + + $paymentArgs = array( + 'payment_method_type' => ['card'], + 'client_reference_id' => $hash, + 'amount' => (int) round($transaction->payment_total, 0), + 'currency' => strtolower($transaction->currency), + 'description' => "Buy coffee from {$supporter->supporters_name}", + 'customer_email' => $supporter->supporters_email, + 'success_url' => $this->successUrl($supporter), + 'public_key' => $keys['public'] + ); + + $this->handleInlinePayment($transaction, $paymentArgs, $apiKey); + } + + public function handleInlinePayment($transaction, $paymentArgs, $apiKey) + { + try { + $intentData = $this->intentData($paymentArgs); + $invoiceResponse = (new API())->makeRequest('payment_intents', $intentData, $apiKey, 'POST'); + + $transaction->payment_args = $paymentArgs; + + $responseData = [ + 'nextAction' => 'stripe', + 'actionName' => 'custom', + 'buttonState' => 'hide', + 'intent' => $invoiceResponse, + 'order_items' => $transaction, + 'message_to_show' => __('Payment Modal is opening, Please complete the payment', 'buy-me-coffee'), + ]; + + wp_send_json_success($responseData, 200); + } catch (\Exception $e) { + wp_send_json_error([ + 'status' => 'failed', + 'message' => $e->getMessage() + ], 423); + } } + public function intentData($args) + { + $sessionPayload = array( + 'amount' => $args['amount'], + 'currency' => $args['currency'], + 'metadata' => [ + 'ref_id' => $args['client_reference_id'], + ], + ); + + return $sessionPayload; + } + public function successUrl($supporter) + { + return add_query_arg(array( + 'send_coffee' => '', + 'wpm_bmc_success' => 1, + 'hash' => $supporter->entry_hash, + 'payment_method' => 'stripe' + ), home_url()); + } + + public function sanitize($settings) { foreach ($settings as $key => $value) { @@ -55,18 +127,27 @@ public function getSettings() return wp_parse_args($settings, $defaults); } + public function maybeLoadModalScript() + { + //phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion + wp_enqueue_script('wpm-buymecoffee-checkout-sdk-' . $this->method, 'https://js.stripe.com/v3/',null, false); + Vite::enqueueScript('wpm-buymecoffee-checkout-handler-' . $this->method, 'js/PaymentMethods/stripe-checkout.js', ['wpm-buymecoffee-checkout-sdk-stripe', 'jquery'], '1.0.1', false); + } + public function render($template) { + $this->maybeLoadModalScript(); + ?> 'no', + 'payment_mode' => 'test', + 'live_pub_key' => '', + 'live_secret_key' => '', + 'test_pub_key' => '', + 'test_secret_key' => '' + ); + + $data = wp_parse_args($settings, $defaults); + return $key && isset($data[$key]) ? $data[$key] : $data; + } + + public static function getKeys($key = null) + { + $settings = self::getSettings(); + + if ($settings['payment_mode'] == 'test') { + $data = array( + 'secret' => $settings['test_secret_key'], + 'public' => $settings['test_pub_key'] + ); + } else { + $data = array( + 'secret' => $settings['live_secret_key'], + 'public' =>$settings['live_pub_key'] + ); + } + + return $key && isset($data[$key]) ? $data[$key] : $data; + + } + + + +} \ No newline at end of file diff --git a/includes/Builder/Render.php b/includes/Builder/Render.php index 23e2cf0..92249ec 100644 --- a/includes/Builder/Render.php +++ b/includes/Builder/Render.php @@ -206,7 +206,7 @@ public static function renderInputElements($template = []) -
Please complete your donation with PayPal 👇
"); } } diff --git a/src/js/PaymentMethods/stripe-checkout.js b/src/js/PaymentMethods/stripe-checkout.js new file mode 100644 index 0000000..9d8c97e --- /dev/null +++ b/src/js/PaymentMethods/stripe-checkout.js @@ -0,0 +1,54 @@ +class StripeCheckout { + constructor ($form, $response) { + this.form = $form + this.data = $response.data + this.intent = $response.data?.intent + } + + init () { + this.form.find('.wpm_submit_button').hide(); + let amounPrefix = this.form.find('.wpm_payment_total_amount_prefix').text(); + + let buttonText = "Pay " + amounPrefix + (parseInt(this.intent.amount) / 100) + " Now" + + let submitButton = ""; + var stripe = Stripe(this.data?.order_items?.payment_args?.public_key); + const elements = stripe.elements({ + clientSecret: this.intent.client_secret + }); + const paymentElement = elements.create('payment', {}); + paymentElement.mount('#wpm_bmc_pay_methods'); + + this.form.find('.wpm_bmc_pay_methods')?.parent().append("Payment processing...
"); + this.form.find('#fluent_cart_order_btn').hide(); + + let that= this; + paymentElement.on('ready', (event) => { + jQuery('.wpm_bmc_loading_processor').remove(); + jQuery('#wpm_bmc_pay_methods').append(submitButton); + this.form.find('.wpm_bmc_input_content, .wpm_bmc_payment_input_content').hide(); + this.form.prepend("Please complete your donation with Stripe 👇
"); + + jQuery('#wpm_bmc_pay_now').on('click', function(e) { + e.preventDefault() + jQuery(this).text('Processing...'); + elements.submit().then(result=> { + stripe.confirmPayment({ + elements, + confirmParams: { + return_url: that.data?.order_items?.payment_args?.success_url + } + }).then((result) => { + jQuery(this).text(buttonText); + }) + }).catch(error => { + jQuery(this).text(buttonText); + }) + }) + }); + } + } + + window.addEventListener("wpm_bmc_payment_next_action_stripe", function (e) { + new StripeCheckout(e.detail.form, e.detail.response).init(); + }); \ No newline at end of file diff --git a/src/scss/public/BasicTemplate.scss b/src/scss/public/BasicTemplate.scss index 3e7e3c2..692c787 100644 --- a/src/scss/public/BasicTemplate.scss +++ b/src/scss/public/BasicTemplate.scss @@ -199,6 +199,12 @@ span.buymecoffee_form_to { font-family: sans-serif; text-align: center; } +.complete_payment_instruction { + color: #48a891; + font-size: 14px; + font-family: sans-serif; + border-bottom: 1px solid #eceaea; +} .bmc_coffee { input { margin-left: 0px; diff --git a/src/scss/public/public-style.scss b/src/scss/public/public-style.scss index 8f0f9ee..0024732 100644 --- a/src/scss/public/public-style.scss +++ b/src/scss/public/public-style.scss @@ -10,6 +10,7 @@ form.wpm_submitting_form { .wpm_loading_svg { display: none; } + form.wpm_submitting_form { //background: #fff; .wpm_loading_svg { @@ -40,6 +41,7 @@ form.wpm_submitting_form { border: 1px solid #dedede; border-radius: 6px; opacity: 0.4; + height: 34px; } img:hover { opacity: 1;